am d6951fd0: am e0e126ba: merge -s ours from froyo-release so that upgrading to gingerbread is a git fast-forward
Merge commit 'd6951fd0f65027cceda39090c52ba49b2308ecd9'
* commit 'd6951fd0f65027cceda39090c52ba49b2308ecd9':
Compare only network portion to determine if number is voicemail.
Fixing use of green button in call log
diff --git a/Android.mk b/Android.mk
index d574b3f..1bebf33 100644
--- a/Android.mk
+++ b/Android.mk
@@ -5,7 +5,7 @@
LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_STATIC_JAVA_LIBRARIES := com.android.phone.common
+LOCAL_STATIC_JAVA_LIBRARIES := com.android.phone.common com.android.vcard android-common
LOCAL_PACKAGE_NAME := Contacts
LOCAL_CERTIFICATE := shared
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 09bdb1d..992bf5d 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -116,11 +116,11 @@
</intent-filter>
</activity>
- <!-- Tab container for all tabs -->
- <activity-alias android:name="DialtactsContactsEntryActivity"
- android:targetActivity="DialtactsActivity"
+ <!-- Front door proxy that picks the right UI based on the screen config -->
+ <activity android:name=".activities.ContactsFrontDoor"
android:label="@string/contactsList"
android:icon="@drawable/ic_launcher_contacts"
+ android:theme="@android:style/Theme.NoTitleBar"
>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -135,8 +135,7 @@
<data android:mimeType="vnd.android.cursor.dir/person" android:host="contacts" />
<data android:mimeType="vnd.android.cursor.dir/contact" android:host="com.android.contacts" />
</intent-filter>
-
- </activity-alias>
+ </activity>
<!-- Main launch Intent to open the Contacts app. This will open the app in its last manual
state. This is the state that has been explicitly set by the user (e.g. by clicking a tab).
@@ -164,10 +163,11 @@
</intent-filter>
</activity-alias>
- <!-- The actual list of contacts, usually embedded in ContactsActivity -->
- <activity android:name="ContactsListActivity"
+ <!-- The actual list of contacts -->
+ <activity android:name=".activities.ContactBrowserActivity"
android:label="@string/contactsList"
android:clearTaskOnLaunch="true"
+ android:theme="@style/ContactBrowserTheme"
>
<intent-filter>
<action android:name="com.android.contacts.action.LIST_DEFAULT" />
@@ -210,7 +210,13 @@
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.TAB" />
</intent-filter>
-
+ </activity>
+
+ <activity android:name=".activities.ContactSelectionActivity"
+ android:label="@string/contactsList"
+ android:clearTaskOnLaunch="true"
+ android:theme="@style/ContactPickerTheme"
+ >
<intent-filter>
<action android:name="android.intent.action.INSERT_OR_EDIT" />
<category android:name="android.intent.category.DEFAULT" />
@@ -240,36 +246,49 @@
<data android:mimeType="vnd.android.cursor.item/postal-address_v2" android:host="com.android.contacts" />
<data android:mimeType="vnd.android.cursor.item/postal-address" android:host="contacts" />
</intent-filter>
-
</activity>
+ <!-- Backwards compatibility: somebody may have hard coded this activity name -->
+ <activity-alias android:name="ContactsListActivity"
+ android:targetActivity=".activities.ContactBrowserActivity"
+ />
+
<!-- An activity for joining contacts -->
- <activity android:name="ContactsListActivity$JoinContactActivity"
+ <activity android:name="JoinContactActivity"
android:theme="@style/TallTitleBarTheme"
android:clearTaskOnLaunch="true"
>
<intent-filter>
- <action android:name="com.android.contacts.action.JOIN_AGGREGATE" />
+ <action android:name="com.android.contacts.action.JOIN_CONTACT" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
+ <!-- An activity for selecting multiple phone numbers -->
+ <activity android:name="MultiplePhonePickerActivity">
+ <intent-filter>
+ <action android:name="com.android.contacts.action.GET_MULTIPLE_PHONES" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <data android:mimeType="vnd.android.cursor.dir/phone_v2" android:host="com.android.contacts" />
+ </intent-filter>
+ </activity>
+
<!-- The contacts search/filter UI -->
- <activity android:name="ContactsListActivity$ContactsSearchActivity"
+ <activity android:name=".activities.ContactSearchActivity"
android:theme="@style/ContactsSearchTheme"
- android:windowSoftInputMode="stateAlwaysVisible|adjustPan"
+ android:windowSoftInputMode="stateAlwaysVisible"
>
<intent-filter>
<action android:name="com.android.contacts.action.FILTER_CONTACTS" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="vnd.android.cursor.dir/contact" android:host="com.android.contacts" />
</intent-filter>
+
</activity>
<!-- The contacts search/filter UI -->
- <activity android:name="SearchResultsActivity"
- android:theme="@style/TallTitleBarTheme"
- android:label="@string/contactsList"
+ <activity-alias android:name="SearchResultsActivity"
+ android:targetActivity=".activities.ContactSearchActivity"
>
<intent-filter>
<action android:name="android.intent.action.SEARCH" />
@@ -279,10 +298,13 @@
<meta-data android:name="android.app.searchable"
android:resource="@xml/searchable"
/>
- </activity>
+ </activity-alias>
<!-- Used to select display and sync groups -->
- <activity android:name=".ui.ContactsPreferencesActivity" android:label="@string/displayGroups" />
+ <activity
+ android:name=".ui.ContactsPreferencesActivity"
+ android:label="@string/displayGroups"
+ android:theme="@style/ContactsPreferencesTheme" />
<activity
android:name=".ui.ShowOrCreateActivity"
@@ -316,7 +338,7 @@
</activity>
<activity-alias android:name="ContactShortcut"
- android:targetActivity="ContactsListActivity"
+ android:targetActivity=".activities.ContactSelectionActivity"
android:label="@string/shortcutContact"
android:icon="@drawable/ic_launcher_shortcut_contact">
@@ -328,7 +350,7 @@
</activity-alias>
<activity-alias android:name="alias.DialShortcut"
- android:targetActivity="ContactsListActivity"
+ android:targetActivity=".activities.ContactSelectionActivity"
android:label="@string/shortcutDialContact"
android:icon="@drawable/ic_launcher_shortcut_directdial">
@@ -341,7 +363,7 @@
</activity-alias>
<activity-alias android:name="alias.MessageShortcut"
- android:targetActivity="ContactsListActivity"
+ android:targetActivity=".activities.ContactSelectionActivity"
android:label="@string/shortcutMessageContact"
android:icon="@drawable/ic_launcher_shortcut_directmessage">
@@ -364,7 +386,7 @@
</activity>
<!-- Views the details of a single contact -->
- <activity android:name="ViewContactActivity"
+ <activity android:name=".activities.ContactDetailActivity"
android:label="@string/viewContactTitle"
android:theme="@style/TallTitleBarTheme">
@@ -377,10 +399,11 @@
</intent-filter>
</activity>
- <!-- Edit or insert details for a contact -->
+ <!-- Create a new or edit an existing contact -->
<activity
- android:name=".ui.EditContactActivity"
- android:windowSoftInputMode="stateHidden|adjustResize">
+ android:name=".activities.ContactEditorActivity"
+ android:theme="@style/TallTitleBarTheme"
+ android:windowSoftInputMode="adjustResize">
<intent-filter android:label="@string/editContactDescription">
<action android:name="android.intent.action.EDIT" />
@@ -389,7 +412,6 @@
<data android:mimeType="vnd.android.cursor.item/contact" android:host="com.android.contacts" />
<data android:mimeType="vnd.android.cursor.item/raw_contact" android:host="com.android.contacts" />
</intent-filter>
-
<intent-filter android:label="@string/insertContactDescription">
<action android:name="android.intent.action.INSERT" />
<category android:name="android.intent.category.DEFAULT" />
@@ -397,7 +419,6 @@
<data android:mimeType="vnd.android.cursor.dir/contact" />
<data android:mimeType="vnd.android.cursor.dir/raw_contact" />
</intent-filter>
-
</activity>
<!-- Stub service used to keep our process alive long enough for
@@ -406,6 +427,11 @@
android:name=".util.EmptyService"
android:exported="false" />
+ <!-- Service to save a contact -->
+ <service
+ android:name=".views.ContactSaveService"
+ android:exported="false" />
+
<!-- Views the details of a single contact -->
<activity android:name="ContactOptionsActivity"
android:label="@string/contactOptionsTitle"
@@ -428,10 +454,15 @@
/>
</activity>
- <!-- Makes .ContactsListActivity the search target for any activity in Contacts -->
+ <!-- Interstitial activity that shows a phone disambig dialog -->
+ <activity android:name="CallContactActivity"
+ android:theme="@android:style/Theme.Translucent">
+ </activity>
+
+ <!-- Makes .ContactListActivity the search target for any activity in Contacts -->
<meta-data
android:name="android.app.default_searchable"
- android:value=".ContactsListActivity" />
+ android:value=".activities.ContactBrowserActivity" />
<!-- LIVE FOLDERS -->
@@ -465,17 +496,37 @@
</intent-filter>
</activity>
- <activity android:name=".ImportVCardActivity"
+ <!-- vCard related -->
+ <activity android:name=".vcard.ImportVCardActivity"
android:theme="@style/BackgroundOnly">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<data android:mimeType="text/directory" />
+ <data android:mimeType="text/vcard" />
<data android:mimeType="text/x-vcard" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
- <activity android:name=".ExportVCardActivity"
+ <activity android:name=".vcard.CancelImportActivity"
android:theme="@style/BackgroundOnly" />
+
+ <activity android:name=".vcard.SelectAccountActivity"
+ android:theme="@style/BackgroundOnly" />
+
+ <activity android:name=".vcard.ExportVCardActivity"
+ android:theme="@style/BackgroundOnly" />
+
+ <service
+ android:name=".vcard.VCardService"
+ android:exported="false" />
+
+ <!-- Pinned header list demo -->
+ <activity android:name=".widget.PinnedHeaderListDemoActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
</application>
</manifest>
diff --git a/proguard.flags b/proguard.flags
index 6cc704c..577144b 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -12,3 +12,8 @@
-keep class com.android.contacts.model.EntityDelta$ValuesDelta {
public android.content.ContentValues getAfter();
}
+
+# Any methods whose name is '*ForTest' are preserved.
+-keep class ** {
+ *** *ForTest(...);
+}
\ No newline at end of file
diff --git a/res/anim/footer_appear.xml b/res/anim/footer_appear.xml
new file mode 100644
index 0000000..941454a
--- /dev/null
+++ b/res/anim/footer_appear.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2009, 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.
+*/
+-->
+
+<translate xmlns:android="http://schemas.android.com/apk/res/android"
+ android:fromYDelta="+10%p"
+ android:toYDelta="0"
+ android:duration="300" />
\ No newline at end of file
diff --git a/res/drawable-finger/btn_dial_textfield.xml b/res/drawable-finger/btn_dial_textfield.xml
deleted file mode 100644
index 109f6ae..0000000
--- a/res/drawable-finger/btn_dial_textfield.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2008 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.
--->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_pressed="true"
- android:drawable="@drawable/btn_dial_textfield_pressed" />
- <item android:state_focused="true"
- android:drawable="@drawable/btn_dial_textfield_normal" />
- <item
- android:drawable="@drawable/btn_dial_textfield_normal" />
-</selector>
-
diff --git a/res/drawable-hdpi/aizy_bottom.png b/res/drawable-hdpi/aizy_bottom.png
new file mode 100644
index 0000000..1f3d332
--- /dev/null
+++ b/res/drawable-hdpi/aizy_bottom.png
Binary files differ
diff --git a/res/drawable-hdpi/appointment_indicator_leftside_1.9.png b/res/drawable-hdpi/appointment_indicator_leftside_1.9.png
new file mode 100644
index 0000000..b72652b
--- /dev/null
+++ b/res/drawable-hdpi/appointment_indicator_leftside_1.9.png
Binary files differ
diff --git a/res/drawable-hdpi/appointment_indicator_leftside_10.9.png b/res/drawable-hdpi/appointment_indicator_leftside_10.9.png
new file mode 100644
index 0000000..ff09049
--- /dev/null
+++ b/res/drawable-hdpi/appointment_indicator_leftside_10.9.png
Binary files differ
diff --git a/res/drawable-hdpi/appointment_indicator_leftside_11.9.png b/res/drawable-hdpi/appointment_indicator_leftside_11.9.png
new file mode 100644
index 0000000..6a2e4f2
--- /dev/null
+++ b/res/drawable-hdpi/appointment_indicator_leftside_11.9.png
Binary files differ
diff --git a/res/drawable-hdpi/appointment_indicator_leftside_12.9.png b/res/drawable-hdpi/appointment_indicator_leftside_12.9.png
new file mode 100644
index 0000000..0f19c83
--- /dev/null
+++ b/res/drawable-hdpi/appointment_indicator_leftside_12.9.png
Binary files differ
diff --git a/res/drawable-hdpi/appointment_indicator_leftside_13.9.png b/res/drawable-hdpi/appointment_indicator_leftside_13.9.png
new file mode 100644
index 0000000..7501e35
--- /dev/null
+++ b/res/drawable-hdpi/appointment_indicator_leftside_13.9.png
Binary files differ
diff --git a/res/drawable-hdpi/appointment_indicator_leftside_14.9.png b/res/drawable-hdpi/appointment_indicator_leftside_14.9.png
new file mode 100644
index 0000000..53f97a6
--- /dev/null
+++ b/res/drawable-hdpi/appointment_indicator_leftside_14.9.png
Binary files differ
diff --git a/res/drawable-hdpi/appointment_indicator_leftside_15.9.png b/res/drawable-hdpi/appointment_indicator_leftside_15.9.png
new file mode 100644
index 0000000..846f6f8
--- /dev/null
+++ b/res/drawable-hdpi/appointment_indicator_leftside_15.9.png
Binary files differ
diff --git a/res/drawable-hdpi/appointment_indicator_leftside_16.9.png b/res/drawable-hdpi/appointment_indicator_leftside_16.9.png
new file mode 100644
index 0000000..1707540
--- /dev/null
+++ b/res/drawable-hdpi/appointment_indicator_leftside_16.9.png
Binary files differ
diff --git a/res/drawable-hdpi/appointment_indicator_leftside_17.9.png b/res/drawable-hdpi/appointment_indicator_leftside_17.9.png
new file mode 100644
index 0000000..7fd945d
--- /dev/null
+++ b/res/drawable-hdpi/appointment_indicator_leftside_17.9.png
Binary files differ
diff --git a/res/drawable-hdpi/appointment_indicator_leftside_18.9.png b/res/drawable-hdpi/appointment_indicator_leftside_18.9.png
new file mode 100644
index 0000000..8cf47ae
--- /dev/null
+++ b/res/drawable-hdpi/appointment_indicator_leftside_18.9.png
Binary files differ
diff --git a/res/drawable-hdpi/appointment_indicator_leftside_19.9.png b/res/drawable-hdpi/appointment_indicator_leftside_19.9.png
new file mode 100644
index 0000000..6831c01
--- /dev/null
+++ b/res/drawable-hdpi/appointment_indicator_leftside_19.9.png
Binary files differ
diff --git a/res/drawable-hdpi/appointment_indicator_leftside_2.9.png b/res/drawable-hdpi/appointment_indicator_leftside_2.9.png
new file mode 100644
index 0000000..b4cee11
--- /dev/null
+++ b/res/drawable-hdpi/appointment_indicator_leftside_2.9.png
Binary files differ
diff --git a/res/drawable-hdpi/appointment_indicator_leftside_20.9.png b/res/drawable-hdpi/appointment_indicator_leftside_20.9.png
new file mode 100644
index 0000000..d07d826
--- /dev/null
+++ b/res/drawable-hdpi/appointment_indicator_leftside_20.9.png
Binary files differ
diff --git a/res/drawable-hdpi/appointment_indicator_leftside_21.9.png b/res/drawable-hdpi/appointment_indicator_leftside_21.9.png
new file mode 100644
index 0000000..f410269
--- /dev/null
+++ b/res/drawable-hdpi/appointment_indicator_leftside_21.9.png
Binary files differ
diff --git a/res/drawable-hdpi/appointment_indicator_leftside_3.9.png b/res/drawable-hdpi/appointment_indicator_leftside_3.9.png
new file mode 100644
index 0000000..69bd6a9
--- /dev/null
+++ b/res/drawable-hdpi/appointment_indicator_leftside_3.9.png
Binary files differ
diff --git a/res/drawable-hdpi/appointment_indicator_leftside_4.9.png b/res/drawable-hdpi/appointment_indicator_leftside_4.9.png
new file mode 100644
index 0000000..d09ea90
--- /dev/null
+++ b/res/drawable-hdpi/appointment_indicator_leftside_4.9.png
Binary files differ
diff --git a/res/drawable-hdpi/appointment_indicator_leftside_5.9.png b/res/drawable-hdpi/appointment_indicator_leftside_5.9.png
new file mode 100644
index 0000000..d27fc91
--- /dev/null
+++ b/res/drawable-hdpi/appointment_indicator_leftside_5.9.png
Binary files differ
diff --git a/res/drawable-hdpi/appointment_indicator_leftside_6.9.png b/res/drawable-hdpi/appointment_indicator_leftside_6.9.png
new file mode 100644
index 0000000..c014633
--- /dev/null
+++ b/res/drawable-hdpi/appointment_indicator_leftside_6.9.png
Binary files differ
diff --git a/res/drawable-hdpi/appointment_indicator_leftside_7.9.png b/res/drawable-hdpi/appointment_indicator_leftside_7.9.png
new file mode 100644
index 0000000..febb514
--- /dev/null
+++ b/res/drawable-hdpi/appointment_indicator_leftside_7.9.png
Binary files differ
diff --git a/res/drawable-hdpi/appointment_indicator_leftside_8.9.png b/res/drawable-hdpi/appointment_indicator_leftside_8.9.png
new file mode 100644
index 0000000..1415e44
--- /dev/null
+++ b/res/drawable-hdpi/appointment_indicator_leftside_8.9.png
Binary files differ
diff --git a/res/drawable-hdpi/appointment_indicator_leftside_9.9.png b/res/drawable-hdpi/appointment_indicator_leftside_9.9.png
new file mode 100644
index 0000000..d018fcf
--- /dev/null
+++ b/res/drawable-hdpi/appointment_indicator_leftside_9.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/badge_action_call.png b/res/drawable-hdpi/badge_action_call.png
similarity index 100%
rename from res/drawable-hdpi-finger/badge_action_call.png
rename to res/drawable-hdpi/badge_action_call.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/badge_action_sms.png b/res/drawable-hdpi/badge_action_sms.png
similarity index 100%
rename from res/drawable-hdpi-finger/badge_action_sms.png
rename to res/drawable-hdpi/badge_action_sms.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/bg_blk_search_contact.9.png b/res/drawable-hdpi/bg_blk_search_contact.9.png
similarity index 100%
rename from res/drawable-hdpi-finger/bg_blk_search_contact.9.png
rename to res/drawable-hdpi/bg_blk_search_contact.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/btn_circle_disable.png b/res/drawable-hdpi/btn_circle_disable.png
similarity index 100%
rename from res/drawable-hdpi-finger/btn_circle_disable.png
rename to res/drawable-hdpi/btn_circle_disable.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/btn_circle_disable_focused.png b/res/drawable-hdpi/btn_circle_disable_focused.png
similarity index 100%
rename from res/drawable-hdpi-finger/btn_circle_disable_focused.png
rename to res/drawable-hdpi/btn_circle_disable_focused.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/btn_circle_normal.png b/res/drawable-hdpi/btn_circle_normal.png
similarity index 100%
rename from res/drawable-hdpi-finger/btn_circle_normal.png
rename to res/drawable-hdpi/btn_circle_normal.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/btn_circle_pressed.png b/res/drawable-hdpi/btn_circle_pressed.png
similarity index 100%
rename from res/drawable-hdpi-finger/btn_circle_pressed.png
rename to res/drawable-hdpi/btn_circle_pressed.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/btn_circle_selected.png b/res/drawable-hdpi/btn_circle_selected.png
similarity index 100%
rename from res/drawable-hdpi-finger/btn_circle_selected.png
rename to res/drawable-hdpi/btn_circle_selected.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/btn_dial_action_left_disable.9.png b/res/drawable-hdpi/btn_dial_action_left_disable.9.png
similarity index 100%
rename from res/drawable-hdpi-finger/btn_dial_action_left_disable.9.png
rename to res/drawable-hdpi/btn_dial_action_left_disable.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/btn_dial_action_left_disable_focused.9.png b/res/drawable-hdpi/btn_dial_action_left_disable_focused.9.png
similarity index 100%
rename from res/drawable-hdpi-finger/btn_dial_action_left_disable_focused.9.png
rename to res/drawable-hdpi/btn_dial_action_left_disable_focused.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/btn_dial_action_left_normal.9.png b/res/drawable-hdpi/btn_dial_action_left_normal.9.png
similarity index 100%
rename from res/drawable-hdpi-finger/btn_dial_action_left_normal.9.png
rename to res/drawable-hdpi/btn_dial_action_left_normal.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/btn_dial_action_left_pressed.9.png b/res/drawable-hdpi/btn_dial_action_left_pressed.9.png
similarity index 100%
rename from res/drawable-hdpi-finger/btn_dial_action_left_pressed.9.png
rename to res/drawable-hdpi/btn_dial_action_left_pressed.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/btn_dial_action_left_selected.9.png b/res/drawable-hdpi/btn_dial_action_left_selected.9.png
similarity index 100%
rename from res/drawable-hdpi-finger/btn_dial_action_left_selected.9.png
rename to res/drawable-hdpi/btn_dial_action_left_selected.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/btn_dial_action_middle_disable.9.png b/res/drawable-hdpi/btn_dial_action_middle_disable.9.png
similarity index 100%
rename from res/drawable-hdpi-finger/btn_dial_action_middle_disable.9.png
rename to res/drawable-hdpi/btn_dial_action_middle_disable.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/btn_dial_action_middle_disable_focused.9.png b/res/drawable-hdpi/btn_dial_action_middle_disable_focused.9.png
similarity index 100%
rename from res/drawable-hdpi-finger/btn_dial_action_middle_disable_focused.9.png
rename to res/drawable-hdpi/btn_dial_action_middle_disable_focused.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/btn_dial_action_middle_normal.9.png b/res/drawable-hdpi/btn_dial_action_middle_normal.9.png
similarity index 100%
rename from res/drawable-hdpi-finger/btn_dial_action_middle_normal.9.png
rename to res/drawable-hdpi/btn_dial_action_middle_normal.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/btn_dial_action_middle_pressed.9.png b/res/drawable-hdpi/btn_dial_action_middle_pressed.9.png
similarity index 100%
rename from res/drawable-hdpi-finger/btn_dial_action_middle_pressed.9.png
rename to res/drawable-hdpi/btn_dial_action_middle_pressed.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/btn_dial_action_middle_selected.9.png b/res/drawable-hdpi/btn_dial_action_middle_selected.9.png
similarity index 100%
rename from res/drawable-hdpi-finger/btn_dial_action_middle_selected.9.png
rename to res/drawable-hdpi/btn_dial_action_middle_selected.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/btn_dial_action_right_disable.9.png b/res/drawable-hdpi/btn_dial_action_right_disable.9.png
similarity index 100%
rename from res/drawable-hdpi-finger/btn_dial_action_right_disable.9.png
rename to res/drawable-hdpi/btn_dial_action_right_disable.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/btn_dial_action_right_disable_focused.9.png b/res/drawable-hdpi/btn_dial_action_right_disable_focused.9.png
similarity index 100%
rename from res/drawable-hdpi-finger/btn_dial_action_right_disable_focused.9.png
rename to res/drawable-hdpi/btn_dial_action_right_disable_focused.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/btn_dial_action_right_normal.9.png b/res/drawable-hdpi/btn_dial_action_right_normal.9.png
similarity index 100%
rename from res/drawable-hdpi-finger/btn_dial_action_right_normal.9.png
rename to res/drawable-hdpi/btn_dial_action_right_normal.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/btn_dial_action_right_pressed.9.png b/res/drawable-hdpi/btn_dial_action_right_pressed.9.png
similarity index 100%
rename from res/drawable-hdpi-finger/btn_dial_action_right_pressed.9.png
rename to res/drawable-hdpi/btn_dial_action_right_pressed.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/btn_dial_action_right_selected.9.png b/res/drawable-hdpi/btn_dial_action_right_selected.9.png
similarity index 100%
rename from res/drawable-hdpi-finger/btn_dial_action_right_selected.9.png
rename to res/drawable-hdpi/btn_dial_action_right_selected.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/btn_dial_normal.9.png b/res/drawable-hdpi/btn_dial_normal.9.png
similarity index 100%
rename from res/drawable-hdpi-finger/btn_dial_normal.9.png
rename to res/drawable-hdpi/btn_dial_normal.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/btn_dial_pressed.9.png b/res/drawable-hdpi/btn_dial_pressed.9.png
similarity index 100%
rename from res/drawable-hdpi-finger/btn_dial_pressed.9.png
rename to res/drawable-hdpi/btn_dial_pressed.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/btn_dial_selected.9.png b/res/drawable-hdpi/btn_dial_selected.9.png
similarity index 100%
rename from res/drawable-hdpi-finger/btn_dial_selected.9.png
rename to res/drawable-hdpi/btn_dial_selected.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/btn_dial_textfield_activated.9.png b/res/drawable-hdpi/btn_dial_textfield_activated.9.png
similarity index 100%
rename from res/drawable-hdpi-finger/btn_dial_textfield_activated.9.png
rename to res/drawable-hdpi/btn_dial_textfield_activated.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/btn_dial_textfield_normal.9.png b/res/drawable-hdpi/btn_dial_textfield_normal.9.png
similarity index 100%
rename from res/drawable-hdpi-finger/btn_dial_textfield_normal.9.png
rename to res/drawable-hdpi/btn_dial_textfield_normal.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/btn_dial_textfield_pressed.9.png b/res/drawable-hdpi/btn_dial_textfield_pressed.9.png
similarity index 100%
rename from res/drawable-hdpi-finger/btn_dial_textfield_pressed.9.png
rename to res/drawable-hdpi/btn_dial_textfield_pressed.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/btn_dial_textfield_selected.9.png b/res/drawable-hdpi/btn_dial_textfield_selected.9.png
similarity index 100%
rename from res/drawable-hdpi-finger/btn_dial_textfield_selected.9.png
rename to res/drawable-hdpi/btn_dial_textfield_selected.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/btn_search_dialog_default.9.png b/res/drawable-hdpi/btn_search_dialog_default.9.png
similarity index 100%
rename from res/drawable-hdpi-finger/btn_search_dialog_default.9.png
rename to res/drawable-hdpi/btn_search_dialog_default.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/btn_search_dialog_pressed.9.png b/res/drawable-hdpi/btn_search_dialog_pressed.9.png
similarity index 100%
rename from res/drawable-hdpi-finger/btn_search_dialog_pressed.9.png
rename to res/drawable-hdpi/btn_search_dialog_pressed.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/btn_search_dialog_selected.9.png b/res/drawable-hdpi/btn_search_dialog_selected.9.png
similarity index 100%
rename from res/drawable-hdpi-finger/btn_search_dialog_selected.9.png
rename to res/drawable-hdpi/btn_search_dialog_selected.9.png
Binary files differ
diff --git a/res/drawable-hdpi/contact_none.png b/res/drawable-hdpi/contact_none.png
new file mode 100644
index 0000000..b079f96
--- /dev/null
+++ b/res/drawable-hdpi/contact_none.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/contact_picture_border_highlight.9.png b/res/drawable-hdpi/contact_picture_border_highlight.9.png
similarity index 100%
rename from res/drawable-hdpi-finger/contact_picture_border_highlight.9.png
rename to res/drawable-hdpi/contact_picture_border_highlight.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/contact_picture_border_normal.9.png b/res/drawable-hdpi/contact_picture_border_normal.9.png
similarity index 100%
rename from res/drawable-hdpi-finger/contact_picture_border_normal.9.png
rename to res/drawable-hdpi/contact_picture_border_normal.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/contact_picture_border_pressed.9.png b/res/drawable-hdpi/contact_picture_border_pressed.9.png
similarity index 100%
rename from res/drawable-hdpi-finger/contact_picture_border_pressed.9.png
rename to res/drawable-hdpi/contact_picture_border_pressed.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/dial_num_0_blk.png b/res/drawable-hdpi/dial_num_0_blk.png
similarity index 100%
rename from res/drawable-hdpi-finger/dial_num_0_blk.png
rename to res/drawable-hdpi/dial_num_0_blk.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/dial_num_0_wht.png b/res/drawable-hdpi/dial_num_0_wht.png
similarity index 100%
rename from res/drawable-hdpi-finger/dial_num_0_wht.png
rename to res/drawable-hdpi/dial_num_0_wht.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/dial_num_1_no_vm_blk.png b/res/drawable-hdpi/dial_num_1_no_vm_blk.png
similarity index 100%
rename from res/drawable-hdpi-finger/dial_num_1_no_vm_blk.png
rename to res/drawable-hdpi/dial_num_1_no_vm_blk.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/dial_num_1_no_vm_wht.png b/res/drawable-hdpi/dial_num_1_no_vm_wht.png
similarity index 100%
rename from res/drawable-hdpi-finger/dial_num_1_no_vm_wht.png
rename to res/drawable-hdpi/dial_num_1_no_vm_wht.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/dial_num_2_blk.png b/res/drawable-hdpi/dial_num_2_blk.png
similarity index 100%
rename from res/drawable-hdpi-finger/dial_num_2_blk.png
rename to res/drawable-hdpi/dial_num_2_blk.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/dial_num_2_wht.png b/res/drawable-hdpi/dial_num_2_wht.png
similarity index 100%
rename from res/drawable-hdpi-finger/dial_num_2_wht.png
rename to res/drawable-hdpi/dial_num_2_wht.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/dial_num_3_blk.png b/res/drawable-hdpi/dial_num_3_blk.png
similarity index 100%
rename from res/drawable-hdpi-finger/dial_num_3_blk.png
rename to res/drawable-hdpi/dial_num_3_blk.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/dial_num_3_wht.png b/res/drawable-hdpi/dial_num_3_wht.png
similarity index 100%
rename from res/drawable-hdpi-finger/dial_num_3_wht.png
rename to res/drawable-hdpi/dial_num_3_wht.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/dial_num_4_blk.png b/res/drawable-hdpi/dial_num_4_blk.png
similarity index 100%
rename from res/drawable-hdpi-finger/dial_num_4_blk.png
rename to res/drawable-hdpi/dial_num_4_blk.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/dial_num_4_wht.png b/res/drawable-hdpi/dial_num_4_wht.png
similarity index 100%
rename from res/drawable-hdpi-finger/dial_num_4_wht.png
rename to res/drawable-hdpi/dial_num_4_wht.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/dial_num_5_blk.png b/res/drawable-hdpi/dial_num_5_blk.png
similarity index 100%
rename from res/drawable-hdpi-finger/dial_num_5_blk.png
rename to res/drawable-hdpi/dial_num_5_blk.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/dial_num_5_wht.png b/res/drawable-hdpi/dial_num_5_wht.png
similarity index 100%
rename from res/drawable-hdpi-finger/dial_num_5_wht.png
rename to res/drawable-hdpi/dial_num_5_wht.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/dial_num_6_blk.png b/res/drawable-hdpi/dial_num_6_blk.png
similarity index 100%
rename from res/drawable-hdpi-finger/dial_num_6_blk.png
rename to res/drawable-hdpi/dial_num_6_blk.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/dial_num_6_wht.png b/res/drawable-hdpi/dial_num_6_wht.png
similarity index 100%
rename from res/drawable-hdpi-finger/dial_num_6_wht.png
rename to res/drawable-hdpi/dial_num_6_wht.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/dial_num_7_blk.png b/res/drawable-hdpi/dial_num_7_blk.png
similarity index 100%
rename from res/drawable-hdpi-finger/dial_num_7_blk.png
rename to res/drawable-hdpi/dial_num_7_blk.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/dial_num_7_wht.png b/res/drawable-hdpi/dial_num_7_wht.png
similarity index 100%
rename from res/drawable-hdpi-finger/dial_num_7_wht.png
rename to res/drawable-hdpi/dial_num_7_wht.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/dial_num_8_blk.png b/res/drawable-hdpi/dial_num_8_blk.png
similarity index 100%
rename from res/drawable-hdpi-finger/dial_num_8_blk.png
rename to res/drawable-hdpi/dial_num_8_blk.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/dial_num_8_wht.png b/res/drawable-hdpi/dial_num_8_wht.png
similarity index 100%
rename from res/drawable-hdpi-finger/dial_num_8_wht.png
rename to res/drawable-hdpi/dial_num_8_wht.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/dial_num_9_blk.png b/res/drawable-hdpi/dial_num_9_blk.png
similarity index 100%
rename from res/drawable-hdpi-finger/dial_num_9_blk.png
rename to res/drawable-hdpi/dial_num_9_blk.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/dial_num_9_wht.png b/res/drawable-hdpi/dial_num_9_wht.png
similarity index 100%
rename from res/drawable-hdpi-finger/dial_num_9_wht.png
rename to res/drawable-hdpi/dial_num_9_wht.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/dial_num_pound_blk.png b/res/drawable-hdpi/dial_num_pound_blk.png
similarity index 100%
rename from res/drawable-hdpi-finger/dial_num_pound_blk.png
rename to res/drawable-hdpi/dial_num_pound_blk.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/dial_num_pound_wht.png b/res/drawable-hdpi/dial_num_pound_wht.png
similarity index 100%
rename from res/drawable-hdpi-finger/dial_num_pound_wht.png
rename to res/drawable-hdpi/dial_num_pound_wht.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/dial_num_star_blk.png b/res/drawable-hdpi/dial_num_star_blk.png
similarity index 100%
rename from res/drawable-hdpi-finger/dial_num_star_blk.png
rename to res/drawable-hdpi/dial_num_star_blk.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/dial_num_star_wht.png b/res/drawable-hdpi/dial_num_star_wht.png
similarity index 100%
rename from res/drawable-hdpi-finger/dial_num_star_wht.png
rename to res/drawable-hdpi/dial_num_star_wht.png
Binary files differ
diff --git a/res/drawable-hdpi/directory_bg.9.png b/res/drawable-hdpi/directory_bg.9.png
new file mode 100644
index 0000000..f0a92d4
--- /dev/null
+++ b/res/drawable-hdpi/directory_bg.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/divider_vertical_dark.9.png b/res/drawable-hdpi/divider_vertical_dark.9.png
similarity index 100%
rename from res/drawable-hdpi-finger/divider_vertical_dark.9.png
rename to res/drawable-hdpi/divider_vertical_dark.9.png
Binary files differ
diff --git a/res/drawable-hdpi/edit_rawcontact_bg.png b/res/drawable-hdpi/edit_rawcontact_bg.png
new file mode 100644
index 0000000..84bd50b
--- /dev/null
+++ b/res/drawable-hdpi/edit_rawcontact_bg.png
Binary files differ
diff --git a/res/drawable-hdpi/edit_rawcontact_bottom.png b/res/drawable-hdpi/edit_rawcontact_bottom.png
new file mode 100644
index 0000000..58ae4b2
--- /dev/null
+++ b/res/drawable-hdpi/edit_rawcontact_bottom.png
Binary files differ
diff --git a/res/drawable-hdpi/edit_rawcontact_top.png b/res/drawable-hdpi/edit_rawcontact_top.png
new file mode 100644
index 0000000..6ca737d
--- /dev/null
+++ b/res/drawable-hdpi/edit_rawcontact_top.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_aggregated.png b/res/drawable-hdpi/ic_aggregated.png
new file mode 100644
index 0000000..7ca15b1
--- /dev/null
+++ b/res/drawable-hdpi/ic_aggregated.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/ic_btn_round_less.png b/res/drawable-hdpi/ic_btn_round_less.png
similarity index 100%
rename from res/drawable-hdpi-finger/ic_btn_round_less.png
rename to res/drawable-hdpi/ic_btn_round_less.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/ic_btn_round_minus.png b/res/drawable-hdpi/ic_btn_round_minus.png
similarity index 100%
rename from res/drawable-hdpi-finger/ic_btn_round_minus.png
rename to res/drawable-hdpi/ic_btn_round_minus.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/ic_btn_round_more.png b/res/drawable-hdpi/ic_btn_round_more.png
similarity index 100%
rename from res/drawable-hdpi-finger/ic_btn_round_more.png
rename to res/drawable-hdpi/ic_btn_round_more.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/ic_btn_round_plus.png b/res/drawable-hdpi/ic_btn_round_plus.png
similarity index 100%
rename from res/drawable-hdpi-finger/ic_btn_round_plus.png
rename to res/drawable-hdpi/ic_btn_round_plus.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/ic_call_log_header_incoming_call.png b/res/drawable-hdpi/ic_call_log_header_incoming_call.png
similarity index 100%
rename from res/drawable-hdpi-finger/ic_call_log_header_incoming_call.png
rename to res/drawable-hdpi/ic_call_log_header_incoming_call.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/ic_call_log_header_missed_call.png b/res/drawable-hdpi/ic_call_log_header_missed_call.png
similarity index 100%
rename from res/drawable-hdpi-finger/ic_call_log_header_missed_call.png
rename to res/drawable-hdpi/ic_call_log_header_missed_call.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/ic_call_log_header_outgoing_call.png b/res/drawable-hdpi/ic_call_log_header_outgoing_call.png
similarity index 100%
rename from res/drawable-hdpi-finger/ic_call_log_header_outgoing_call.png
rename to res/drawable-hdpi/ic_call_log_header_outgoing_call.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/ic_call_log_list_incoming_call.png b/res/drawable-hdpi/ic_call_log_list_incoming_call.png
similarity index 100%
rename from res/drawable-hdpi-finger/ic_call_log_list_incoming_call.png
rename to res/drawable-hdpi/ic_call_log_list_incoming_call.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/ic_call_log_list_missed_call.png b/res/drawable-hdpi/ic_call_log_list_missed_call.png
similarity index 100%
rename from res/drawable-hdpi-finger/ic_call_log_list_missed_call.png
rename to res/drawable-hdpi/ic_call_log_list_missed_call.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/ic_call_log_list_outgoing_call.png b/res/drawable-hdpi/ic_call_log_list_outgoing_call.png
similarity index 100%
rename from res/drawable-hdpi-finger/ic_call_log_list_outgoing_call.png
rename to res/drawable-hdpi/ic_call_log_list_outgoing_call.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/ic_contact_list_picture.png b/res/drawable-hdpi/ic_contact_list_picture.png
similarity index 100%
rename from res/drawable-hdpi-finger/ic_contact_list_picture.png
rename to res/drawable-hdpi/ic_contact_list_picture.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/ic_contact_picture.png b/res/drawable-hdpi/ic_contact_picture.png
similarity index 100%
rename from res/drawable-hdpi-finger/ic_contact_picture.png
rename to res/drawable-hdpi/ic_contact_picture.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/ic_contact_picture_2.png b/res/drawable-hdpi/ic_contact_picture_2.png
similarity index 100%
rename from res/drawable-hdpi-finger/ic_contact_picture_2.png
rename to res/drawable-hdpi/ic_contact_picture_2.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/ic_contact_picture_3.png b/res/drawable-hdpi/ic_contact_picture_3.png
similarity index 100%
rename from res/drawable-hdpi-finger/ic_contact_picture_3.png
rename to res/drawable-hdpi/ic_contact_picture_3.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/ic_default_number.png b/res/drawable-hdpi/ic_default_number.png
similarity index 100%
rename from res/drawable-hdpi-finger/ic_default_number.png
rename to res/drawable-hdpi/ic_default_number.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/ic_dial_action_call.png b/res/drawable-hdpi/ic_dial_action_call.png
similarity index 100%
rename from res/drawable-hdpi-finger/ic_dial_action_call.png
rename to res/drawable-hdpi/ic_dial_action_call.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/ic_dial_action_delete.png b/res/drawable-hdpi/ic_dial_action_delete.png
similarity index 100%
rename from res/drawable-hdpi-finger/ic_dial_action_delete.png
rename to res/drawable-hdpi/ic_dial_action_delete.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/ic_dial_action_voice_mail.png b/res/drawable-hdpi/ic_dial_action_voice_mail.png
similarity index 100%
rename from res/drawable-hdpi-finger/ic_dial_action_voice_mail.png
rename to res/drawable-hdpi/ic_dial_action_voice_mail.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/ic_dial_number_blk.png b/res/drawable-hdpi/ic_dial_number_blk.png
similarity index 100%
rename from res/drawable-hdpi-finger/ic_dial_number_blk.png
rename to res/drawable-hdpi/ic_dial_number_blk.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/ic_dial_number_wht.png b/res/drawable-hdpi/ic_dial_number_wht.png
similarity index 100%
rename from res/drawable-hdpi-finger/ic_dial_number_wht.png
rename to res/drawable-hdpi/ic_dial_number_wht.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/ic_dialer_fork_add_call.png b/res/drawable-hdpi/ic_dialer_fork_add_call.png
similarity index 100%
rename from res/drawable-hdpi-finger/ic_dialer_fork_add_call.png
rename to res/drawable-hdpi/ic_dialer_fork_add_call.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/ic_dialer_fork_current_call.png b/res/drawable-hdpi/ic_dialer_fork_current_call.png
similarity index 100%
rename from res/drawable-hdpi-finger/ic_dialer_fork_current_call.png
rename to res/drawable-hdpi/ic_dialer_fork_current_call.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/ic_dialer_fork_tt_keypad.png b/res/drawable-hdpi/ic_dialer_fork_tt_keypad.png
similarity index 100%
rename from res/drawable-hdpi-finger/ic_dialer_fork_tt_keypad.png
rename to res/drawable-hdpi/ic_dialer_fork_tt_keypad.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/ic_join.png b/res/drawable-hdpi/ic_join.png
similarity index 100%
rename from res/drawable-hdpi-finger/ic_join.png
rename to res/drawable-hdpi/ic_join.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_menu_2sec_pause.png b/res/drawable-hdpi/ic_menu_2sec_pause.png
new file mode 100644
index 0000000..3951948
--- /dev/null
+++ b/res/drawable-hdpi/ic_menu_2sec_pause.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/ic_menu_account_list.png b/res/drawable-hdpi/ic_menu_account_list.png
similarity index 100%
rename from res/drawable-hdpi-finger/ic_menu_account_list.png
rename to res/drawable-hdpi/ic_menu_account_list.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/ic_menu_add_picture.png b/res/drawable-hdpi/ic_menu_add_picture.png
similarity index 100%
rename from res/drawable-hdpi-finger/ic_menu_add_picture.png
rename to res/drawable-hdpi/ic_menu_add_picture.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_menu_display_all.png b/res/drawable-hdpi/ic_menu_display_all.png
new file mode 100755
index 0000000..563083c
--- /dev/null
+++ b/res/drawable-hdpi/ic_menu_display_all.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_menu_display_selected.png b/res/drawable-hdpi/ic_menu_display_selected.png
new file mode 100644
index 0000000..76b2e22
--- /dev/null
+++ b/res/drawable-hdpi/ic_menu_display_selected.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/ic_menu_import_export.png b/res/drawable-hdpi/ic_menu_import_export.png
similarity index 100%
rename from res/drawable-hdpi-finger/ic_menu_import_export.png
rename to res/drawable-hdpi/ic_menu_import_export.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/ic_menu_merge.png b/res/drawable-hdpi/ic_menu_merge.png
similarity index 100%
rename from res/drawable-hdpi-finger/ic_menu_merge.png
rename to res/drawable-hdpi/ic_menu_merge.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_menu_select.png b/res/drawable-hdpi/ic_menu_select.png
new file mode 100644
index 0000000..c5bb503
--- /dev/null
+++ b/res/drawable-hdpi/ic_menu_select.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/ic_menu_split.png b/res/drawable-hdpi/ic_menu_split.png
similarity index 100%
rename from res/drawable-hdpi-finger/ic_menu_split.png
rename to res/drawable-hdpi/ic_menu_split.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_menu_unselect.png b/res/drawable-hdpi/ic_menu_unselect.png
new file mode 100644
index 0000000..178f314
--- /dev/null
+++ b/res/drawable-hdpi/ic_menu_unselect.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_menu_wait.png b/res/drawable-hdpi/ic_menu_wait.png
new file mode 100644
index 0000000..6886e5d
--- /dev/null
+++ b/res/drawable-hdpi/ic_menu_wait.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/ic_tab_selected_contacts.png b/res/drawable-hdpi/ic_tab_selected_contacts.png
similarity index 100%
rename from res/drawable-hdpi-finger/ic_tab_selected_contacts.png
rename to res/drawable-hdpi/ic_tab_selected_contacts.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/ic_tab_selected_dialer.png b/res/drawable-hdpi/ic_tab_selected_dialer.png
similarity index 100%
rename from res/drawable-hdpi-finger/ic_tab_selected_dialer.png
rename to res/drawable-hdpi/ic_tab_selected_dialer.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/ic_tab_selected_friends_list.png b/res/drawable-hdpi/ic_tab_selected_friends_list.png
similarity index 100%
rename from res/drawable-hdpi-finger/ic_tab_selected_friends_list.png
rename to res/drawable-hdpi/ic_tab_selected_friends_list.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/ic_tab_selected_recent.png b/res/drawable-hdpi/ic_tab_selected_recent.png
similarity index 100%
rename from res/drawable-hdpi-finger/ic_tab_selected_recent.png
rename to res/drawable-hdpi/ic_tab_selected_recent.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/ic_tab_selected_starred.png b/res/drawable-hdpi/ic_tab_selected_starred.png
similarity index 100%
rename from res/drawable-hdpi-finger/ic_tab_selected_starred.png
rename to res/drawable-hdpi/ic_tab_selected_starred.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/ic_tab_unselected_contacts.png b/res/drawable-hdpi/ic_tab_unselected_contacts.png
similarity index 100%
rename from res/drawable-hdpi-finger/ic_tab_unselected_contacts.png
rename to res/drawable-hdpi/ic_tab_unselected_contacts.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/ic_tab_unselected_dialer.png b/res/drawable-hdpi/ic_tab_unselected_dialer.png
similarity index 100%
rename from res/drawable-hdpi-finger/ic_tab_unselected_dialer.png
rename to res/drawable-hdpi/ic_tab_unselected_dialer.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/ic_tab_unselected_friends_list.png b/res/drawable-hdpi/ic_tab_unselected_friends_list.png
similarity index 100%
rename from res/drawable-hdpi-finger/ic_tab_unselected_friends_list.png
rename to res/drawable-hdpi/ic_tab_unselected_friends_list.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/ic_tab_unselected_recent.png b/res/drawable-hdpi/ic_tab_unselected_recent.png
similarity index 100%
rename from res/drawable-hdpi-finger/ic_tab_unselected_recent.png
rename to res/drawable-hdpi/ic_tab_unselected_recent.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/ic_tab_unselected_starred.png b/res/drawable-hdpi/ic_tab_unselected_starred.png
similarity index 100%
rename from res/drawable-hdpi-finger/ic_tab_unselected_starred.png
rename to res/drawable-hdpi/ic_tab_unselected_starred.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/infobar_dark.9.png b/res/drawable-hdpi/infobar_dark.9.png
similarity index 100%
rename from res/drawable-hdpi-finger/infobar_dark.9.png
rename to res/drawable-hdpi/infobar_dark.9.png
Binary files differ
diff --git a/res/drawable-hdpi/list_item_checked_bg.9.png b/res/drawable-hdpi/list_item_checked_bg.9.png
new file mode 100644
index 0000000..b8b515c
--- /dev/null
+++ b/res/drawable-hdpi/list_item_checked_bg.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/quickcontact_arrow_down.png b/res/drawable-hdpi/quickcontact_arrow_down.png
similarity index 100%
rename from res/drawable-hdpi-finger/quickcontact_arrow_down.png
rename to res/drawable-hdpi/quickcontact_arrow_down.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/quickcontact_arrow_up.png b/res/drawable-hdpi/quickcontact_arrow_up.png
similarity index 100%
rename from res/drawable-hdpi-finger/quickcontact_arrow_up.png
rename to res/drawable-hdpi/quickcontact_arrow_up.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/quickcontact_bottom_frame.9.png b/res/drawable-hdpi/quickcontact_bottom_frame.9.png
similarity index 100%
rename from res/drawable-hdpi-finger/quickcontact_bottom_frame.9.png
rename to res/drawable-hdpi/quickcontact_bottom_frame.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/quickcontact_disambig_bottom_bg.9.png b/res/drawable-hdpi/quickcontact_disambig_bottom_bg.9.png
similarity index 100%
rename from res/drawable-hdpi-finger/quickcontact_disambig_bottom_bg.9.png
rename to res/drawable-hdpi/quickcontact_disambig_bottom_bg.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/quickcontact_disambig_checkbox_off.png b/res/drawable-hdpi/quickcontact_disambig_checkbox_off.png
similarity index 100%
rename from res/drawable-hdpi-finger/quickcontact_disambig_checkbox_off.png
rename to res/drawable-hdpi/quickcontact_disambig_checkbox_off.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/quickcontact_disambig_checkbox_on.png b/res/drawable-hdpi/quickcontact_disambig_checkbox_on.png
similarity index 100%
rename from res/drawable-hdpi-finger/quickcontact_disambig_checkbox_on.png
rename to res/drawable-hdpi/quickcontact_disambig_checkbox_on.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/quickcontact_disambig_divider.9.png b/res/drawable-hdpi/quickcontact_disambig_divider.9.png
similarity index 100%
rename from res/drawable-hdpi-finger/quickcontact_disambig_divider.9.png
rename to res/drawable-hdpi/quickcontact_disambig_divider.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/quickcontact_drop_shadow.9.png b/res/drawable-hdpi/quickcontact_drop_shadow.9.png
similarity index 100%
rename from res/drawable-hdpi-finger/quickcontact_drop_shadow.9.png
rename to res/drawable-hdpi/quickcontact_drop_shadow.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/quickcontact_photo_frame.9.png b/res/drawable-hdpi/quickcontact_photo_frame.9.png
similarity index 100%
rename from res/drawable-hdpi-finger/quickcontact_photo_frame.9.png
rename to res/drawable-hdpi/quickcontact_photo_frame.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/quickcontact_slider_background.png b/res/drawable-hdpi/quickcontact_slider_background.png
similarity index 100%
rename from res/drawable-hdpi-finger/quickcontact_slider_background.png
rename to res/drawable-hdpi/quickcontact_slider_background.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/quickcontact_slider_btn_normal.9.png b/res/drawable-hdpi/quickcontact_slider_btn_normal.9.png
similarity index 100%
rename from res/drawable-hdpi-finger/quickcontact_slider_btn_normal.9.png
rename to res/drawable-hdpi/quickcontact_slider_btn_normal.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/quickcontact_slider_btn_on.9.png b/res/drawable-hdpi/quickcontact_slider_btn_on.9.png
similarity index 100%
rename from res/drawable-hdpi-finger/quickcontact_slider_btn_on.9.png
rename to res/drawable-hdpi/quickcontact_slider_btn_on.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/quickcontact_slider_btn_pressed.9.png b/res/drawable-hdpi/quickcontact_slider_btn_pressed.9.png
similarity index 100%
rename from res/drawable-hdpi-finger/quickcontact_slider_btn_pressed.9.png
rename to res/drawable-hdpi/quickcontact_slider_btn_pressed.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/quickcontact_slider_btn_selected.9.png b/res/drawable-hdpi/quickcontact_slider_btn_selected.9.png
similarity index 100%
rename from res/drawable-hdpi-finger/quickcontact_slider_btn_selected.9.png
rename to res/drawable-hdpi/quickcontact_slider_btn_selected.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/quickcontact_slider_grip_left.png b/res/drawable-hdpi/quickcontact_slider_grip_left.png
similarity index 100%
rename from res/drawable-hdpi-finger/quickcontact_slider_grip_left.png
rename to res/drawable-hdpi/quickcontact_slider_grip_left.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/quickcontact_slider_grip_right.png b/res/drawable-hdpi/quickcontact_slider_grip_right.png
similarity index 100%
rename from res/drawable-hdpi-finger/quickcontact_slider_grip_right.png
rename to res/drawable-hdpi/quickcontact_slider_grip_right.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/quickcontact_slider_presence_active.png b/res/drawable-hdpi/quickcontact_slider_presence_active.png
similarity index 100%
rename from res/drawable-hdpi-finger/quickcontact_slider_presence_active.png
rename to res/drawable-hdpi/quickcontact_slider_presence_active.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/quickcontact_slider_presence_away.png b/res/drawable-hdpi/quickcontact_slider_presence_away.png
similarity index 100%
rename from res/drawable-hdpi-finger/quickcontact_slider_presence_away.png
rename to res/drawable-hdpi/quickcontact_slider_presence_away.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/quickcontact_slider_presence_busy.png b/res/drawable-hdpi/quickcontact_slider_presence_busy.png
similarity index 100%
rename from res/drawable-hdpi-finger/quickcontact_slider_presence_busy.png
rename to res/drawable-hdpi/quickcontact_slider_presence_busy.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/quickcontact_slider_presence_inactive.png b/res/drawable-hdpi/quickcontact_slider_presence_inactive.png
similarity index 100%
rename from res/drawable-hdpi-finger/quickcontact_slider_presence_inactive.png
rename to res/drawable-hdpi/quickcontact_slider_presence_inactive.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/quickcontact_top_frame.9.png b/res/drawable-hdpi/quickcontact_top_frame.9.png
similarity index 100%
rename from res/drawable-hdpi-finger/quickcontact_top_frame.9.png
rename to res/drawable-hdpi/quickcontact_top_frame.9.png
Binary files differ
diff --git a/res/drawable-hdpi/suggestion_bg.9.png b/res/drawable-hdpi/suggestion_bg.9.png
new file mode 100644
index 0000000..687cc08
--- /dev/null
+++ b/res/drawable-hdpi/suggestion_bg.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/sym_action_add.png b/res/drawable-hdpi/sym_action_add.png
similarity index 100%
rename from res/drawable-hdpi-finger/sym_action_add.png
rename to res/drawable-hdpi/sym_action_add.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/sym_action_map.png b/res/drawable-hdpi/sym_action_map.png
similarity index 100%
rename from res/drawable-hdpi-finger/sym_action_map.png
rename to res/drawable-hdpi/sym_action_map.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/sym_action_organization.png b/res/drawable-hdpi/sym_action_organization.png
similarity index 100%
rename from res/drawable-hdpi-finger/sym_action_organization.png
rename to res/drawable-hdpi/sym_action_organization.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/sym_action_view_contact.png b/res/drawable-hdpi/sym_action_view_contact.png
similarity index 100%
rename from res/drawable-hdpi-finger/sym_action_view_contact.png
rename to res/drawable-hdpi/sym_action_view_contact.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/sym_note.png b/res/drawable-hdpi/sym_note.png
similarity index 100%
rename from res/drawable-hdpi-finger/sym_note.png
rename to res/drawable-hdpi/sym_note.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/tab_focused.9.png b/res/drawable-hdpi/tab_focused.9.png
similarity index 100%
rename from res/drawable-hdpi-finger/tab_focused.9.png
rename to res/drawable-hdpi/tab_focused.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/tab_focused_bottom.9.png b/res/drawable-hdpi/tab_focused_bottom.9.png
similarity index 100%
rename from res/drawable-hdpi-finger/tab_focused_bottom.9.png
rename to res/drawable-hdpi/tab_focused_bottom.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/tab_left_arrow.png b/res/drawable-hdpi/tab_left_arrow.png
similarity index 100%
rename from res/drawable-hdpi-finger/tab_left_arrow.png
rename to res/drawable-hdpi/tab_left_arrow.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/tab_pressed.9.png b/res/drawable-hdpi/tab_pressed.9.png
similarity index 100%
rename from res/drawable-hdpi-finger/tab_pressed.9.png
rename to res/drawable-hdpi/tab_pressed.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/tab_pressed_bottom.9.png b/res/drawable-hdpi/tab_pressed_bottom.9.png
similarity index 100%
rename from res/drawable-hdpi-finger/tab_pressed_bottom.9.png
rename to res/drawable-hdpi/tab_pressed_bottom.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/tab_right_arrow.png b/res/drawable-hdpi/tab_right_arrow.png
similarity index 100%
rename from res/drawable-hdpi-finger/tab_right_arrow.png
rename to res/drawable-hdpi/tab_right_arrow.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/tab_selected.9.png b/res/drawable-hdpi/tab_selected.9.png
similarity index 100%
rename from res/drawable-hdpi-finger/tab_selected.9.png
rename to res/drawable-hdpi/tab_selected.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/tab_selected_bottom.9.png b/res/drawable-hdpi/tab_selected_bottom.9.png
similarity index 100%
rename from res/drawable-hdpi-finger/tab_selected_bottom.9.png
rename to res/drawable-hdpi/tab_selected_bottom.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/tab_unselected.9.png b/res/drawable-hdpi/tab_unselected.9.png
similarity index 100%
rename from res/drawable-hdpi-finger/tab_unselected.9.png
rename to res/drawable-hdpi/tab_unselected.9.png
Binary files differ
diff --git a/res/drawable-hdpi/title_bar_medium.9.png b/res/drawable-hdpi/title_bar_medium.9.png
new file mode 100644
index 0000000..311a54a
--- /dev/null
+++ b/res/drawable-hdpi/title_bar_medium.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/title_bar_shadow.9.png b/res/drawable-hdpi/title_bar_shadow.9.png
similarity index 100%
rename from res/drawable-hdpi-finger/title_bar_shadow.9.png
rename to res/drawable-hdpi/title_bar_shadow.9.png
Binary files differ
diff --git a/res/drawable-hdpi/unknown_source.png b/res/drawable-hdpi/unknown_source.png
new file mode 100644
index 0000000..0a8f37d
--- /dev/null
+++ b/res/drawable-hdpi/unknown_source.png
Binary files differ
diff --git a/res/drawable-hdpi/view_bg.9.png b/res/drawable-hdpi/view_bg.9.png
new file mode 100644
index 0000000..4e5f824
--- /dev/null
+++ b/res/drawable-hdpi/view_bg.9.png
Binary files differ
diff --git a/res/drawable-mdpi/aizy_bottom.png b/res/drawable-mdpi/aizy_bottom.png
new file mode 100644
index 0000000..1f3d332
--- /dev/null
+++ b/res/drawable-mdpi/aizy_bottom.png
Binary files differ
diff --git a/res/drawable-mdpi/appointment_indicator_leftside_1.9.png b/res/drawable-mdpi/appointment_indicator_leftside_1.9.png
new file mode 100644
index 0000000..5e40235
--- /dev/null
+++ b/res/drawable-mdpi/appointment_indicator_leftside_1.9.png
Binary files differ
diff --git a/res/drawable-mdpi/appointment_indicator_leftside_10.9.png b/res/drawable-mdpi/appointment_indicator_leftside_10.9.png
new file mode 100644
index 0000000..d0cb144
--- /dev/null
+++ b/res/drawable-mdpi/appointment_indicator_leftside_10.9.png
Binary files differ
diff --git a/res/drawable-mdpi/appointment_indicator_leftside_11.9.png b/res/drawable-mdpi/appointment_indicator_leftside_11.9.png
new file mode 100644
index 0000000..034f496
--- /dev/null
+++ b/res/drawable-mdpi/appointment_indicator_leftside_11.9.png
Binary files differ
diff --git a/res/drawable-mdpi/appointment_indicator_leftside_12.9.png b/res/drawable-mdpi/appointment_indicator_leftside_12.9.png
new file mode 100644
index 0000000..6371b3a
--- /dev/null
+++ b/res/drawable-mdpi/appointment_indicator_leftside_12.9.png
Binary files differ
diff --git a/res/drawable-mdpi/appointment_indicator_leftside_13.9.png b/res/drawable-mdpi/appointment_indicator_leftside_13.9.png
new file mode 100644
index 0000000..a8b42c6
--- /dev/null
+++ b/res/drawable-mdpi/appointment_indicator_leftside_13.9.png
Binary files differ
diff --git a/res/drawable-mdpi/appointment_indicator_leftside_14.9.png b/res/drawable-mdpi/appointment_indicator_leftside_14.9.png
new file mode 100644
index 0000000..a69e519
--- /dev/null
+++ b/res/drawable-mdpi/appointment_indicator_leftside_14.9.png
Binary files differ
diff --git a/res/drawable-mdpi/appointment_indicator_leftside_15.9.png b/res/drawable-mdpi/appointment_indicator_leftside_15.9.png
new file mode 100644
index 0000000..5d68470
--- /dev/null
+++ b/res/drawable-mdpi/appointment_indicator_leftside_15.9.png
Binary files differ
diff --git a/res/drawable-mdpi/appointment_indicator_leftside_16.9.png b/res/drawable-mdpi/appointment_indicator_leftside_16.9.png
new file mode 100644
index 0000000..d9420c1
--- /dev/null
+++ b/res/drawable-mdpi/appointment_indicator_leftside_16.9.png
Binary files differ
diff --git a/res/drawable-mdpi/appointment_indicator_leftside_17.9.png b/res/drawable-mdpi/appointment_indicator_leftside_17.9.png
new file mode 100644
index 0000000..d0875c4
--- /dev/null
+++ b/res/drawable-mdpi/appointment_indicator_leftside_17.9.png
Binary files differ
diff --git a/res/drawable-mdpi/appointment_indicator_leftside_18.9.png b/res/drawable-mdpi/appointment_indicator_leftside_18.9.png
new file mode 100644
index 0000000..fc152f7
--- /dev/null
+++ b/res/drawable-mdpi/appointment_indicator_leftside_18.9.png
Binary files differ
diff --git a/res/drawable-mdpi/appointment_indicator_leftside_19.9.png b/res/drawable-mdpi/appointment_indicator_leftside_19.9.png
new file mode 100644
index 0000000..6506a94
--- /dev/null
+++ b/res/drawable-mdpi/appointment_indicator_leftside_19.9.png
Binary files differ
diff --git a/res/drawable-mdpi/appointment_indicator_leftside_2.9.png b/res/drawable-mdpi/appointment_indicator_leftside_2.9.png
new file mode 100644
index 0000000..3baf5cc
--- /dev/null
+++ b/res/drawable-mdpi/appointment_indicator_leftside_2.9.png
Binary files differ
diff --git a/res/drawable-mdpi/appointment_indicator_leftside_20.9.png b/res/drawable-mdpi/appointment_indicator_leftside_20.9.png
new file mode 100644
index 0000000..28340ba
--- /dev/null
+++ b/res/drawable-mdpi/appointment_indicator_leftside_20.9.png
Binary files differ
diff --git a/res/drawable-mdpi/appointment_indicator_leftside_21.9.png b/res/drawable-mdpi/appointment_indicator_leftside_21.9.png
new file mode 100644
index 0000000..5319f07
--- /dev/null
+++ b/res/drawable-mdpi/appointment_indicator_leftside_21.9.png
Binary files differ
diff --git a/res/drawable-mdpi/appointment_indicator_leftside_3.9.png b/res/drawable-mdpi/appointment_indicator_leftside_3.9.png
new file mode 100644
index 0000000..9850791
--- /dev/null
+++ b/res/drawable-mdpi/appointment_indicator_leftside_3.9.png
Binary files differ
diff --git a/res/drawable-mdpi/appointment_indicator_leftside_4.9.png b/res/drawable-mdpi/appointment_indicator_leftside_4.9.png
new file mode 100644
index 0000000..e344ccb
--- /dev/null
+++ b/res/drawable-mdpi/appointment_indicator_leftside_4.9.png
Binary files differ
diff --git a/res/drawable-mdpi/appointment_indicator_leftside_5.9.png b/res/drawable-mdpi/appointment_indicator_leftside_5.9.png
new file mode 100644
index 0000000..11b4dfb
--- /dev/null
+++ b/res/drawable-mdpi/appointment_indicator_leftside_5.9.png
Binary files differ
diff --git a/res/drawable-mdpi/appointment_indicator_leftside_6.9.png b/res/drawable-mdpi/appointment_indicator_leftside_6.9.png
new file mode 100644
index 0000000..7419d47
--- /dev/null
+++ b/res/drawable-mdpi/appointment_indicator_leftside_6.9.png
Binary files differ
diff --git a/res/drawable-mdpi/appointment_indicator_leftside_7.9.png b/res/drawable-mdpi/appointment_indicator_leftside_7.9.png
new file mode 100644
index 0000000..0a3a272
--- /dev/null
+++ b/res/drawable-mdpi/appointment_indicator_leftside_7.9.png
Binary files differ
diff --git a/res/drawable-mdpi/appointment_indicator_leftside_8.9.png b/res/drawable-mdpi/appointment_indicator_leftside_8.9.png
new file mode 100644
index 0000000..db18d27
--- /dev/null
+++ b/res/drawable-mdpi/appointment_indicator_leftside_8.9.png
Binary files differ
diff --git a/res/drawable-mdpi/appointment_indicator_leftside_9.9.png b/res/drawable-mdpi/appointment_indicator_leftside_9.9.png
new file mode 100644
index 0000000..5037de8
--- /dev/null
+++ b/res/drawable-mdpi/appointment_indicator_leftside_9.9.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/badge_action_call.png b/res/drawable-mdpi/badge_action_call.png
similarity index 100%
rename from res/drawable-mdpi-finger/badge_action_call.png
rename to res/drawable-mdpi/badge_action_call.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/badge_action_sms.png b/res/drawable-mdpi/badge_action_sms.png
similarity index 100%
rename from res/drawable-mdpi-finger/badge_action_sms.png
rename to res/drawable-mdpi/badge_action_sms.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/bg_blk_search_contact.9.png b/res/drawable-mdpi/bg_blk_search_contact.9.png
similarity index 100%
rename from res/drawable-mdpi-finger/bg_blk_search_contact.9.png
rename to res/drawable-mdpi/bg_blk_search_contact.9.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/btn_circle_disable.png b/res/drawable-mdpi/btn_circle_disable.png
similarity index 100%
rename from res/drawable-mdpi-finger/btn_circle_disable.png
rename to res/drawable-mdpi/btn_circle_disable.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/btn_circle_disable_focused.png b/res/drawable-mdpi/btn_circle_disable_focused.png
similarity index 100%
rename from res/drawable-mdpi-finger/btn_circle_disable_focused.png
rename to res/drawable-mdpi/btn_circle_disable_focused.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/btn_circle_normal.png b/res/drawable-mdpi/btn_circle_normal.png
similarity index 100%
rename from res/drawable-mdpi-finger/btn_circle_normal.png
rename to res/drawable-mdpi/btn_circle_normal.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/btn_circle_pressed.png b/res/drawable-mdpi/btn_circle_pressed.png
similarity index 100%
rename from res/drawable-mdpi-finger/btn_circle_pressed.png
rename to res/drawable-mdpi/btn_circle_pressed.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/btn_circle_selected.png b/res/drawable-mdpi/btn_circle_selected.png
similarity index 100%
rename from res/drawable-mdpi-finger/btn_circle_selected.png
rename to res/drawable-mdpi/btn_circle_selected.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/btn_dial_action_left_disable.9.png b/res/drawable-mdpi/btn_dial_action_left_disable.9.png
similarity index 100%
rename from res/drawable-mdpi-finger/btn_dial_action_left_disable.9.png
rename to res/drawable-mdpi/btn_dial_action_left_disable.9.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/btn_dial_action_left_disable_focused.9.png b/res/drawable-mdpi/btn_dial_action_left_disable_focused.9.png
similarity index 100%
rename from res/drawable-mdpi-finger/btn_dial_action_left_disable_focused.9.png
rename to res/drawable-mdpi/btn_dial_action_left_disable_focused.9.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/btn_dial_action_left_normal.9.png b/res/drawable-mdpi/btn_dial_action_left_normal.9.png
similarity index 100%
rename from res/drawable-mdpi-finger/btn_dial_action_left_normal.9.png
rename to res/drawable-mdpi/btn_dial_action_left_normal.9.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/btn_dial_action_left_pressed.9.png b/res/drawable-mdpi/btn_dial_action_left_pressed.9.png
similarity index 100%
rename from res/drawable-mdpi-finger/btn_dial_action_left_pressed.9.png
rename to res/drawable-mdpi/btn_dial_action_left_pressed.9.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/btn_dial_action_left_selected.9.png b/res/drawable-mdpi/btn_dial_action_left_selected.9.png
similarity index 100%
rename from res/drawable-mdpi-finger/btn_dial_action_left_selected.9.png
rename to res/drawable-mdpi/btn_dial_action_left_selected.9.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/btn_dial_action_middle_disable.9.png b/res/drawable-mdpi/btn_dial_action_middle_disable.9.png
similarity index 100%
rename from res/drawable-mdpi-finger/btn_dial_action_middle_disable.9.png
rename to res/drawable-mdpi/btn_dial_action_middle_disable.9.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/btn_dial_action_middle_disable_focused.9.png b/res/drawable-mdpi/btn_dial_action_middle_disable_focused.9.png
similarity index 100%
rename from res/drawable-mdpi-finger/btn_dial_action_middle_disable_focused.9.png
rename to res/drawable-mdpi/btn_dial_action_middle_disable_focused.9.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/btn_dial_action_middle_normal.9.png b/res/drawable-mdpi/btn_dial_action_middle_normal.9.png
similarity index 100%
rename from res/drawable-mdpi-finger/btn_dial_action_middle_normal.9.png
rename to res/drawable-mdpi/btn_dial_action_middle_normal.9.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/btn_dial_action_middle_pressed.9.png b/res/drawable-mdpi/btn_dial_action_middle_pressed.9.png
similarity index 100%
rename from res/drawable-mdpi-finger/btn_dial_action_middle_pressed.9.png
rename to res/drawable-mdpi/btn_dial_action_middle_pressed.9.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/btn_dial_action_middle_selected.9.png b/res/drawable-mdpi/btn_dial_action_middle_selected.9.png
similarity index 100%
rename from res/drawable-mdpi-finger/btn_dial_action_middle_selected.9.png
rename to res/drawable-mdpi/btn_dial_action_middle_selected.9.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/btn_dial_action_right_disable.9.png b/res/drawable-mdpi/btn_dial_action_right_disable.9.png
similarity index 100%
rename from res/drawable-mdpi-finger/btn_dial_action_right_disable.9.png
rename to res/drawable-mdpi/btn_dial_action_right_disable.9.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/btn_dial_action_right_disable_focused.9.png b/res/drawable-mdpi/btn_dial_action_right_disable_focused.9.png
similarity index 100%
rename from res/drawable-mdpi-finger/btn_dial_action_right_disable_focused.9.png
rename to res/drawable-mdpi/btn_dial_action_right_disable_focused.9.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/btn_dial_action_right_normal.9.png b/res/drawable-mdpi/btn_dial_action_right_normal.9.png
similarity index 100%
rename from res/drawable-mdpi-finger/btn_dial_action_right_normal.9.png
rename to res/drawable-mdpi/btn_dial_action_right_normal.9.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/btn_dial_action_right_pressed.9.png b/res/drawable-mdpi/btn_dial_action_right_pressed.9.png
similarity index 100%
rename from res/drawable-mdpi-finger/btn_dial_action_right_pressed.9.png
rename to res/drawable-mdpi/btn_dial_action_right_pressed.9.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/btn_dial_action_right_selected.9.png b/res/drawable-mdpi/btn_dial_action_right_selected.9.png
similarity index 100%
rename from res/drawable-mdpi-finger/btn_dial_action_right_selected.9.png
rename to res/drawable-mdpi/btn_dial_action_right_selected.9.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/btn_dial_normal.9.png b/res/drawable-mdpi/btn_dial_normal.9.png
similarity index 100%
rename from res/drawable-mdpi-finger/btn_dial_normal.9.png
rename to res/drawable-mdpi/btn_dial_normal.9.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/btn_dial_pressed.9.png b/res/drawable-mdpi/btn_dial_pressed.9.png
similarity index 100%
rename from res/drawable-mdpi-finger/btn_dial_pressed.9.png
rename to res/drawable-mdpi/btn_dial_pressed.9.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/btn_dial_selected.9.png b/res/drawable-mdpi/btn_dial_selected.9.png
similarity index 100%
rename from res/drawable-mdpi-finger/btn_dial_selected.9.png
rename to res/drawable-mdpi/btn_dial_selected.9.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/btn_dial_textfield_activated.9.png b/res/drawable-mdpi/btn_dial_textfield_activated.9.png
similarity index 100%
rename from res/drawable-mdpi-finger/btn_dial_textfield_activated.9.png
rename to res/drawable-mdpi/btn_dial_textfield_activated.9.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/btn_dial_textfield_normal.9.png b/res/drawable-mdpi/btn_dial_textfield_normal.9.png
similarity index 100%
rename from res/drawable-mdpi-finger/btn_dial_textfield_normal.9.png
rename to res/drawable-mdpi/btn_dial_textfield_normal.9.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/btn_dial_textfield_pressed.9.png b/res/drawable-mdpi/btn_dial_textfield_pressed.9.png
similarity index 100%
rename from res/drawable-mdpi-finger/btn_dial_textfield_pressed.9.png
rename to res/drawable-mdpi/btn_dial_textfield_pressed.9.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/btn_dial_textfield_selected.9.png b/res/drawable-mdpi/btn_dial_textfield_selected.9.png
similarity index 100%
rename from res/drawable-mdpi-finger/btn_dial_textfield_selected.9.png
rename to res/drawable-mdpi/btn_dial_textfield_selected.9.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/btn_search_dialog_default.9.png b/res/drawable-mdpi/btn_search_dialog_default.9.png
similarity index 100%
rename from res/drawable-mdpi-finger/btn_search_dialog_default.9.png
rename to res/drawable-mdpi/btn_search_dialog_default.9.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/btn_search_dialog_pressed.9.png b/res/drawable-mdpi/btn_search_dialog_pressed.9.png
similarity index 100%
rename from res/drawable-mdpi-finger/btn_search_dialog_pressed.9.png
rename to res/drawable-mdpi/btn_search_dialog_pressed.9.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/btn_search_dialog_selected.9.png b/res/drawable-mdpi/btn_search_dialog_selected.9.png
similarity index 100%
rename from res/drawable-mdpi-finger/btn_search_dialog_selected.9.png
rename to res/drawable-mdpi/btn_search_dialog_selected.9.png
Binary files differ
diff --git a/res/drawable-mdpi/contact_none.png b/res/drawable-mdpi/contact_none.png
new file mode 100644
index 0000000..dd2a301
--- /dev/null
+++ b/res/drawable-mdpi/contact_none.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/contact_picture_border_highlight.9.png b/res/drawable-mdpi/contact_picture_border_highlight.9.png
similarity index 100%
rename from res/drawable-mdpi-finger/contact_picture_border_highlight.9.png
rename to res/drawable-mdpi/contact_picture_border_highlight.9.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/contact_picture_border_normal.9.png b/res/drawable-mdpi/contact_picture_border_normal.9.png
similarity index 100%
rename from res/drawable-mdpi-finger/contact_picture_border_normal.9.png
rename to res/drawable-mdpi/contact_picture_border_normal.9.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/contact_picture_border_pressed.9.png b/res/drawable-mdpi/contact_picture_border_pressed.9.png
similarity index 100%
rename from res/drawable-mdpi-finger/contact_picture_border_pressed.9.png
rename to res/drawable-mdpi/contact_picture_border_pressed.9.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/dial_num_0_blk.png b/res/drawable-mdpi/dial_num_0_blk.png
similarity index 100%
rename from res/drawable-mdpi-finger/dial_num_0_blk.png
rename to res/drawable-mdpi/dial_num_0_blk.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/dial_num_0_wht.png b/res/drawable-mdpi/dial_num_0_wht.png
similarity index 100%
rename from res/drawable-mdpi-finger/dial_num_0_wht.png
rename to res/drawable-mdpi/dial_num_0_wht.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/dial_num_1_no_vm_blk.png b/res/drawable-mdpi/dial_num_1_no_vm_blk.png
similarity index 100%
rename from res/drawable-mdpi-finger/dial_num_1_no_vm_blk.png
rename to res/drawable-mdpi/dial_num_1_no_vm_blk.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/dial_num_1_no_vm_wht.png b/res/drawable-mdpi/dial_num_1_no_vm_wht.png
similarity index 100%
rename from res/drawable-mdpi-finger/dial_num_1_no_vm_wht.png
rename to res/drawable-mdpi/dial_num_1_no_vm_wht.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/dial_num_2_blk.png b/res/drawable-mdpi/dial_num_2_blk.png
similarity index 100%
rename from res/drawable-mdpi-finger/dial_num_2_blk.png
rename to res/drawable-mdpi/dial_num_2_blk.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/dial_num_2_wht.png b/res/drawable-mdpi/dial_num_2_wht.png
similarity index 100%
rename from res/drawable-mdpi-finger/dial_num_2_wht.png
rename to res/drawable-mdpi/dial_num_2_wht.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/dial_num_3_blk.png b/res/drawable-mdpi/dial_num_3_blk.png
similarity index 100%
rename from res/drawable-mdpi-finger/dial_num_3_blk.png
rename to res/drawable-mdpi/dial_num_3_blk.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/dial_num_3_wht.png b/res/drawable-mdpi/dial_num_3_wht.png
similarity index 100%
rename from res/drawable-mdpi-finger/dial_num_3_wht.png
rename to res/drawable-mdpi/dial_num_3_wht.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/dial_num_4_blk.png b/res/drawable-mdpi/dial_num_4_blk.png
similarity index 100%
rename from res/drawable-mdpi-finger/dial_num_4_blk.png
rename to res/drawable-mdpi/dial_num_4_blk.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/dial_num_4_wht.png b/res/drawable-mdpi/dial_num_4_wht.png
similarity index 100%
rename from res/drawable-mdpi-finger/dial_num_4_wht.png
rename to res/drawable-mdpi/dial_num_4_wht.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/dial_num_5_blk.png b/res/drawable-mdpi/dial_num_5_blk.png
similarity index 100%
rename from res/drawable-mdpi-finger/dial_num_5_blk.png
rename to res/drawable-mdpi/dial_num_5_blk.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/dial_num_5_wht.png b/res/drawable-mdpi/dial_num_5_wht.png
similarity index 100%
rename from res/drawable-mdpi-finger/dial_num_5_wht.png
rename to res/drawable-mdpi/dial_num_5_wht.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/dial_num_6_blk.png b/res/drawable-mdpi/dial_num_6_blk.png
similarity index 100%
rename from res/drawable-mdpi-finger/dial_num_6_blk.png
rename to res/drawable-mdpi/dial_num_6_blk.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/dial_num_6_wht.png b/res/drawable-mdpi/dial_num_6_wht.png
similarity index 100%
rename from res/drawable-mdpi-finger/dial_num_6_wht.png
rename to res/drawable-mdpi/dial_num_6_wht.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/dial_num_7_blk.png b/res/drawable-mdpi/dial_num_7_blk.png
similarity index 100%
rename from res/drawable-mdpi-finger/dial_num_7_blk.png
rename to res/drawable-mdpi/dial_num_7_blk.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/dial_num_7_wht.png b/res/drawable-mdpi/dial_num_7_wht.png
similarity index 100%
rename from res/drawable-mdpi-finger/dial_num_7_wht.png
rename to res/drawable-mdpi/dial_num_7_wht.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/dial_num_8_blk.png b/res/drawable-mdpi/dial_num_8_blk.png
similarity index 100%
rename from res/drawable-mdpi-finger/dial_num_8_blk.png
rename to res/drawable-mdpi/dial_num_8_blk.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/dial_num_8_wht.png b/res/drawable-mdpi/dial_num_8_wht.png
similarity index 100%
rename from res/drawable-mdpi-finger/dial_num_8_wht.png
rename to res/drawable-mdpi/dial_num_8_wht.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/dial_num_9_blk.png b/res/drawable-mdpi/dial_num_9_blk.png
similarity index 100%
rename from res/drawable-mdpi-finger/dial_num_9_blk.png
rename to res/drawable-mdpi/dial_num_9_blk.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/dial_num_9_wht.png b/res/drawable-mdpi/dial_num_9_wht.png
similarity index 100%
rename from res/drawable-mdpi-finger/dial_num_9_wht.png
rename to res/drawable-mdpi/dial_num_9_wht.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/dial_num_pound_blk.png b/res/drawable-mdpi/dial_num_pound_blk.png
similarity index 100%
rename from res/drawable-mdpi-finger/dial_num_pound_blk.png
rename to res/drawable-mdpi/dial_num_pound_blk.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/dial_num_pound_wht.png b/res/drawable-mdpi/dial_num_pound_wht.png
similarity index 100%
rename from res/drawable-mdpi-finger/dial_num_pound_wht.png
rename to res/drawable-mdpi/dial_num_pound_wht.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/dial_num_star_blk.png b/res/drawable-mdpi/dial_num_star_blk.png
similarity index 100%
rename from res/drawable-mdpi-finger/dial_num_star_blk.png
rename to res/drawable-mdpi/dial_num_star_blk.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/dial_num_star_wht.png b/res/drawable-mdpi/dial_num_star_wht.png
similarity index 100%
rename from res/drawable-mdpi-finger/dial_num_star_wht.png
rename to res/drawable-mdpi/dial_num_star_wht.png
Binary files differ
diff --git a/res/drawable-mdpi/directory_bg.9.png b/res/drawable-mdpi/directory_bg.9.png
new file mode 100644
index 0000000..80578cd
--- /dev/null
+++ b/res/drawable-mdpi/directory_bg.9.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/divider_vertical_dark.9.png b/res/drawable-mdpi/divider_vertical_dark.9.png
similarity index 100%
rename from res/drawable-mdpi-finger/divider_vertical_dark.9.png
rename to res/drawable-mdpi/divider_vertical_dark.9.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_aggregated.png b/res/drawable-mdpi/ic_aggregated.png
new file mode 100644
index 0000000..7c2e2b0
--- /dev/null
+++ b/res/drawable-mdpi/ic_aggregated.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/ic_btn_round_less.png b/res/drawable-mdpi/ic_btn_round_less.png
similarity index 100%
rename from res/drawable-mdpi-finger/ic_btn_round_less.png
rename to res/drawable-mdpi/ic_btn_round_less.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/ic_btn_round_minus.png b/res/drawable-mdpi/ic_btn_round_minus.png
similarity index 100%
rename from res/drawable-mdpi-finger/ic_btn_round_minus.png
rename to res/drawable-mdpi/ic_btn_round_minus.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/ic_btn_round_more.png b/res/drawable-mdpi/ic_btn_round_more.png
similarity index 100%
rename from res/drawable-mdpi-finger/ic_btn_round_more.png
rename to res/drawable-mdpi/ic_btn_round_more.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/ic_btn_round_plus.png b/res/drawable-mdpi/ic_btn_round_plus.png
similarity index 100%
rename from res/drawable-mdpi-finger/ic_btn_round_plus.png
rename to res/drawable-mdpi/ic_btn_round_plus.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/ic_call_log_header_incoming_call.png b/res/drawable-mdpi/ic_call_log_header_incoming_call.png
similarity index 100%
rename from res/drawable-mdpi-finger/ic_call_log_header_incoming_call.png
rename to res/drawable-mdpi/ic_call_log_header_incoming_call.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/ic_call_log_header_missed_call.png b/res/drawable-mdpi/ic_call_log_header_missed_call.png
similarity index 100%
rename from res/drawable-mdpi-finger/ic_call_log_header_missed_call.png
rename to res/drawable-mdpi/ic_call_log_header_missed_call.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/ic_call_log_header_outgoing_call.png b/res/drawable-mdpi/ic_call_log_header_outgoing_call.png
similarity index 100%
rename from res/drawable-mdpi-finger/ic_call_log_header_outgoing_call.png
rename to res/drawable-mdpi/ic_call_log_header_outgoing_call.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/ic_call_log_list_incoming_call.png b/res/drawable-mdpi/ic_call_log_list_incoming_call.png
similarity index 100%
rename from res/drawable-mdpi-finger/ic_call_log_list_incoming_call.png
rename to res/drawable-mdpi/ic_call_log_list_incoming_call.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/ic_call_log_list_missed_call.png b/res/drawable-mdpi/ic_call_log_list_missed_call.png
similarity index 100%
rename from res/drawable-mdpi-finger/ic_call_log_list_missed_call.png
rename to res/drawable-mdpi/ic_call_log_list_missed_call.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/ic_call_log_list_outgoing_call.png b/res/drawable-mdpi/ic_call_log_list_outgoing_call.png
similarity index 100%
rename from res/drawable-mdpi-finger/ic_call_log_list_outgoing_call.png
rename to res/drawable-mdpi/ic_call_log_list_outgoing_call.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/ic_contact_list_picture.png b/res/drawable-mdpi/ic_contact_list_picture.png
similarity index 100%
rename from res/drawable-mdpi-finger/ic_contact_list_picture.png
rename to res/drawable-mdpi/ic_contact_list_picture.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/ic_contact_picture.png b/res/drawable-mdpi/ic_contact_picture.png
similarity index 100%
rename from res/drawable-mdpi-finger/ic_contact_picture.png
rename to res/drawable-mdpi/ic_contact_picture.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/ic_contact_picture_2.png b/res/drawable-mdpi/ic_contact_picture_2.png
similarity index 100%
rename from res/drawable-mdpi-finger/ic_contact_picture_2.png
rename to res/drawable-mdpi/ic_contact_picture_2.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/ic_contact_picture_3.png b/res/drawable-mdpi/ic_contact_picture_3.png
similarity index 100%
rename from res/drawable-mdpi-finger/ic_contact_picture_3.png
rename to res/drawable-mdpi/ic_contact_picture_3.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/ic_default_number.png b/res/drawable-mdpi/ic_default_number.png
similarity index 100%
rename from res/drawable-mdpi-finger/ic_default_number.png
rename to res/drawable-mdpi/ic_default_number.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/ic_dial_action_call.png b/res/drawable-mdpi/ic_dial_action_call.png
similarity index 100%
rename from res/drawable-mdpi-finger/ic_dial_action_call.png
rename to res/drawable-mdpi/ic_dial_action_call.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/ic_dial_action_delete.png b/res/drawable-mdpi/ic_dial_action_delete.png
similarity index 100%
rename from res/drawable-mdpi-finger/ic_dial_action_delete.png
rename to res/drawable-mdpi/ic_dial_action_delete.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/ic_dial_action_voice_mail.png b/res/drawable-mdpi/ic_dial_action_voice_mail.png
similarity index 100%
rename from res/drawable-mdpi-finger/ic_dial_action_voice_mail.png
rename to res/drawable-mdpi/ic_dial_action_voice_mail.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/ic_dial_number_blk.png b/res/drawable-mdpi/ic_dial_number_blk.png
similarity index 100%
rename from res/drawable-mdpi-finger/ic_dial_number_blk.png
rename to res/drawable-mdpi/ic_dial_number_blk.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/ic_dial_number_wht.png b/res/drawable-mdpi/ic_dial_number_wht.png
similarity index 100%
rename from res/drawable-mdpi-finger/ic_dial_number_wht.png
rename to res/drawable-mdpi/ic_dial_number_wht.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/ic_dialer_fork_add_call.png b/res/drawable-mdpi/ic_dialer_fork_add_call.png
similarity index 100%
rename from res/drawable-mdpi-finger/ic_dialer_fork_add_call.png
rename to res/drawable-mdpi/ic_dialer_fork_add_call.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/ic_dialer_fork_current_call.png b/res/drawable-mdpi/ic_dialer_fork_current_call.png
similarity index 100%
rename from res/drawable-mdpi-finger/ic_dialer_fork_current_call.png
rename to res/drawable-mdpi/ic_dialer_fork_current_call.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/ic_dialer_fork_tt_keypad.png b/res/drawable-mdpi/ic_dialer_fork_tt_keypad.png
similarity index 100%
rename from res/drawable-mdpi-finger/ic_dialer_fork_tt_keypad.png
rename to res/drawable-mdpi/ic_dialer_fork_tt_keypad.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/ic_join.png b/res/drawable-mdpi/ic_join.png
similarity index 100%
rename from res/drawable-mdpi-finger/ic_join.png
rename to res/drawable-mdpi/ic_join.png
Binary files differ
diff --git a/res/drawable/ic_menu_2sec_pause.png b/res/drawable-mdpi/ic_menu_2sec_pause.png
similarity index 100%
rename from res/drawable/ic_menu_2sec_pause.png
rename to res/drawable-mdpi/ic_menu_2sec_pause.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/ic_menu_account_list.png b/res/drawable-mdpi/ic_menu_account_list.png
similarity index 100%
rename from res/drawable-mdpi-finger/ic_menu_account_list.png
rename to res/drawable-mdpi/ic_menu_account_list.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/ic_menu_add_picture.png b/res/drawable-mdpi/ic_menu_add_picture.png
similarity index 100%
rename from res/drawable-mdpi-finger/ic_menu_add_picture.png
rename to res/drawable-mdpi/ic_menu_add_picture.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_menu_display_all.png b/res/drawable-mdpi/ic_menu_display_all.png
new file mode 100644
index 0000000..61a9e35
--- /dev/null
+++ b/res/drawable-mdpi/ic_menu_display_all.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_menu_display_selected.png b/res/drawable-mdpi/ic_menu_display_selected.png
new file mode 100644
index 0000000..b4ec7a8
--- /dev/null
+++ b/res/drawable-mdpi/ic_menu_display_selected.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/ic_menu_import_export.png b/res/drawable-mdpi/ic_menu_import_export.png
similarity index 100%
rename from res/drawable-mdpi-finger/ic_menu_import_export.png
rename to res/drawable-mdpi/ic_menu_import_export.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/ic_menu_merge.png b/res/drawable-mdpi/ic_menu_merge.png
similarity index 100%
rename from res/drawable-mdpi-finger/ic_menu_merge.png
rename to res/drawable-mdpi/ic_menu_merge.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_menu_select.png b/res/drawable-mdpi/ic_menu_select.png
new file mode 100644
index 0000000..29e3d7e
--- /dev/null
+++ b/res/drawable-mdpi/ic_menu_select.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/ic_menu_split.png b/res/drawable-mdpi/ic_menu_split.png
similarity index 100%
rename from res/drawable-mdpi-finger/ic_menu_split.png
rename to res/drawable-mdpi/ic_menu_split.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_menu_unselect.png b/res/drawable-mdpi/ic_menu_unselect.png
new file mode 100644
index 0000000..2b69bc8
--- /dev/null
+++ b/res/drawable-mdpi/ic_menu_unselect.png
Binary files differ
diff --git a/res/drawable/ic_menu_wait.png b/res/drawable-mdpi/ic_menu_wait.png
similarity index 100%
rename from res/drawable/ic_menu_wait.png
rename to res/drawable-mdpi/ic_menu_wait.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/ic_tab_selected_contacts.png b/res/drawable-mdpi/ic_tab_selected_contacts.png
similarity index 100%
rename from res/drawable-mdpi-finger/ic_tab_selected_contacts.png
rename to res/drawable-mdpi/ic_tab_selected_contacts.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/ic_tab_selected_dialer.png b/res/drawable-mdpi/ic_tab_selected_dialer.png
similarity index 100%
rename from res/drawable-mdpi-finger/ic_tab_selected_dialer.png
rename to res/drawable-mdpi/ic_tab_selected_dialer.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/ic_tab_selected_friends_list.png b/res/drawable-mdpi/ic_tab_selected_friends_list.png
similarity index 100%
rename from res/drawable-mdpi-finger/ic_tab_selected_friends_list.png
rename to res/drawable-mdpi/ic_tab_selected_friends_list.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/ic_tab_selected_recent.png b/res/drawable-mdpi/ic_tab_selected_recent.png
similarity index 100%
rename from res/drawable-mdpi-finger/ic_tab_selected_recent.png
rename to res/drawable-mdpi/ic_tab_selected_recent.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/ic_tab_selected_starred.png b/res/drawable-mdpi/ic_tab_selected_starred.png
similarity index 100%
rename from res/drawable-mdpi-finger/ic_tab_selected_starred.png
rename to res/drawable-mdpi/ic_tab_selected_starred.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/ic_tab_unselected_contacts.png b/res/drawable-mdpi/ic_tab_unselected_contacts.png
similarity index 100%
rename from res/drawable-mdpi-finger/ic_tab_unselected_contacts.png
rename to res/drawable-mdpi/ic_tab_unselected_contacts.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/ic_tab_unselected_dialer.png b/res/drawable-mdpi/ic_tab_unselected_dialer.png
similarity index 100%
rename from res/drawable-mdpi-finger/ic_tab_unselected_dialer.png
rename to res/drawable-mdpi/ic_tab_unselected_dialer.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/ic_tab_unselected_friends_list.png b/res/drawable-mdpi/ic_tab_unselected_friends_list.png
similarity index 100%
rename from res/drawable-mdpi-finger/ic_tab_unselected_friends_list.png
rename to res/drawable-mdpi/ic_tab_unselected_friends_list.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/ic_tab_unselected_recent.png b/res/drawable-mdpi/ic_tab_unselected_recent.png
similarity index 100%
rename from res/drawable-mdpi-finger/ic_tab_unselected_recent.png
rename to res/drawable-mdpi/ic_tab_unselected_recent.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/ic_tab_unselected_starred.png b/res/drawable-mdpi/ic_tab_unselected_starred.png
similarity index 100%
rename from res/drawable-mdpi-finger/ic_tab_unselected_starred.png
rename to res/drawable-mdpi/ic_tab_unselected_starred.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/infobar_dark.9.png b/res/drawable-mdpi/infobar_dark.9.png
similarity index 100%
rename from res/drawable-mdpi-finger/infobar_dark.9.png
rename to res/drawable-mdpi/infobar_dark.9.png
Binary files differ
diff --git a/res/drawable-mdpi/list_item_checked_bg.9.png b/res/drawable-mdpi/list_item_checked_bg.9.png
new file mode 100644
index 0000000..8b773d5
--- /dev/null
+++ b/res/drawable-mdpi/list_item_checked_bg.9.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/quickcontact_arrow_down.png b/res/drawable-mdpi/quickcontact_arrow_down.png
similarity index 100%
rename from res/drawable-mdpi-finger/quickcontact_arrow_down.png
rename to res/drawable-mdpi/quickcontact_arrow_down.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/quickcontact_arrow_up.png b/res/drawable-mdpi/quickcontact_arrow_up.png
similarity index 100%
rename from res/drawable-mdpi-finger/quickcontact_arrow_up.png
rename to res/drawable-mdpi/quickcontact_arrow_up.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/quickcontact_bottom_frame.9.png b/res/drawable-mdpi/quickcontact_bottom_frame.9.png
similarity index 100%
rename from res/drawable-mdpi-finger/quickcontact_bottom_frame.9.png
rename to res/drawable-mdpi/quickcontact_bottom_frame.9.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/quickcontact_disambig_bottom_bg.9.png b/res/drawable-mdpi/quickcontact_disambig_bottom_bg.9.png
similarity index 100%
rename from res/drawable-mdpi-finger/quickcontact_disambig_bottom_bg.9.png
rename to res/drawable-mdpi/quickcontact_disambig_bottom_bg.9.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/quickcontact_disambig_checkbox_off.png b/res/drawable-mdpi/quickcontact_disambig_checkbox_off.png
similarity index 100%
rename from res/drawable-mdpi-finger/quickcontact_disambig_checkbox_off.png
rename to res/drawable-mdpi/quickcontact_disambig_checkbox_off.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/quickcontact_disambig_checkbox_on.png b/res/drawable-mdpi/quickcontact_disambig_checkbox_on.png
similarity index 100%
rename from res/drawable-mdpi-finger/quickcontact_disambig_checkbox_on.png
rename to res/drawable-mdpi/quickcontact_disambig_checkbox_on.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/quickcontact_disambig_divider.9.png b/res/drawable-mdpi/quickcontact_disambig_divider.9.png
similarity index 100%
rename from res/drawable-mdpi-finger/quickcontact_disambig_divider.9.png
rename to res/drawable-mdpi/quickcontact_disambig_divider.9.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/quickcontact_drop_shadow.9.png b/res/drawable-mdpi/quickcontact_drop_shadow.9.png
similarity index 100%
rename from res/drawable-mdpi-finger/quickcontact_drop_shadow.9.png
rename to res/drawable-mdpi/quickcontact_drop_shadow.9.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/quickcontact_photo_frame.9.png b/res/drawable-mdpi/quickcontact_photo_frame.9.png
similarity index 100%
rename from res/drawable-mdpi-finger/quickcontact_photo_frame.9.png
rename to res/drawable-mdpi/quickcontact_photo_frame.9.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/quickcontact_slider_background.png b/res/drawable-mdpi/quickcontact_slider_background.png
similarity index 100%
rename from res/drawable-mdpi-finger/quickcontact_slider_background.png
rename to res/drawable-mdpi/quickcontact_slider_background.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/quickcontact_slider_btn_normal.9.png b/res/drawable-mdpi/quickcontact_slider_btn_normal.9.png
similarity index 100%
rename from res/drawable-mdpi-finger/quickcontact_slider_btn_normal.9.png
rename to res/drawable-mdpi/quickcontact_slider_btn_normal.9.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/quickcontact_slider_btn_on.9.png b/res/drawable-mdpi/quickcontact_slider_btn_on.9.png
similarity index 100%
rename from res/drawable-mdpi-finger/quickcontact_slider_btn_on.9.png
rename to res/drawable-mdpi/quickcontact_slider_btn_on.9.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/quickcontact_slider_btn_pressed.9.png b/res/drawable-mdpi/quickcontact_slider_btn_pressed.9.png
similarity index 100%
rename from res/drawable-mdpi-finger/quickcontact_slider_btn_pressed.9.png
rename to res/drawable-mdpi/quickcontact_slider_btn_pressed.9.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/quickcontact_slider_btn_selected.9.png b/res/drawable-mdpi/quickcontact_slider_btn_selected.9.png
similarity index 100%
rename from res/drawable-mdpi-finger/quickcontact_slider_btn_selected.9.png
rename to res/drawable-mdpi/quickcontact_slider_btn_selected.9.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/quickcontact_slider_grip_left.png b/res/drawable-mdpi/quickcontact_slider_grip_left.png
similarity index 100%
rename from res/drawable-mdpi-finger/quickcontact_slider_grip_left.png
rename to res/drawable-mdpi/quickcontact_slider_grip_left.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/quickcontact_slider_grip_right.png b/res/drawable-mdpi/quickcontact_slider_grip_right.png
similarity index 100%
rename from res/drawable-mdpi-finger/quickcontact_slider_grip_right.png
rename to res/drawable-mdpi/quickcontact_slider_grip_right.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/quickcontact_slider_presence_active.png b/res/drawable-mdpi/quickcontact_slider_presence_active.png
similarity index 100%
rename from res/drawable-mdpi-finger/quickcontact_slider_presence_active.png
rename to res/drawable-mdpi/quickcontact_slider_presence_active.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/quickcontact_slider_presence_away.png b/res/drawable-mdpi/quickcontact_slider_presence_away.png
similarity index 100%
rename from res/drawable-mdpi-finger/quickcontact_slider_presence_away.png
rename to res/drawable-mdpi/quickcontact_slider_presence_away.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/quickcontact_slider_presence_busy.png b/res/drawable-mdpi/quickcontact_slider_presence_busy.png
similarity index 100%
rename from res/drawable-mdpi-finger/quickcontact_slider_presence_busy.png
rename to res/drawable-mdpi/quickcontact_slider_presence_busy.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/quickcontact_slider_presence_inactive.png b/res/drawable-mdpi/quickcontact_slider_presence_inactive.png
similarity index 100%
rename from res/drawable-mdpi-finger/quickcontact_slider_presence_inactive.png
rename to res/drawable-mdpi/quickcontact_slider_presence_inactive.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/quickcontact_top_frame.9.png b/res/drawable-mdpi/quickcontact_top_frame.9.png
similarity index 100%
rename from res/drawable-mdpi-finger/quickcontact_top_frame.9.png
rename to res/drawable-mdpi/quickcontact_top_frame.9.png
Binary files differ
diff --git a/res/drawable-mdpi/suggestion_bg.9.png b/res/drawable-mdpi/suggestion_bg.9.png
new file mode 100644
index 0000000..687cc08
--- /dev/null
+++ b/res/drawable-mdpi/suggestion_bg.9.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/sym_action_add.png b/res/drawable-mdpi/sym_action_add.png
similarity index 100%
rename from res/drawable-mdpi-finger/sym_action_add.png
rename to res/drawable-mdpi/sym_action_add.png
Binary files differ
diff --git a/res/drawable-mdpi/sym_action_audiochat.png b/res/drawable-mdpi/sym_action_audiochat.png
new file mode 100644
index 0000000..9bbaa37
--- /dev/null
+++ b/res/drawable-mdpi/sym_action_audiochat.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/sym_action_map.png b/res/drawable-mdpi/sym_action_map.png
similarity index 100%
rename from res/drawable-mdpi-finger/sym_action_map.png
rename to res/drawable-mdpi/sym_action_map.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/sym_action_organization.png b/res/drawable-mdpi/sym_action_organization.png
similarity index 100%
rename from res/drawable-mdpi-finger/sym_action_organization.png
rename to res/drawable-mdpi/sym_action_organization.png
Binary files differ
diff --git a/res/drawable-mdpi/sym_action_videochat.png b/res/drawable-mdpi/sym_action_videochat.png
new file mode 100644
index 0000000..84e1b98
--- /dev/null
+++ b/res/drawable-mdpi/sym_action_videochat.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/sym_action_view_contact.png b/res/drawable-mdpi/sym_action_view_contact.png
similarity index 100%
rename from res/drawable-mdpi-finger/sym_action_view_contact.png
rename to res/drawable-mdpi/sym_action_view_contact.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/sym_note.png b/res/drawable-mdpi/sym_note.png
similarity index 100%
rename from res/drawable-mdpi-finger/sym_note.png
rename to res/drawable-mdpi/sym_note.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/tab_focused.9.png b/res/drawable-mdpi/tab_focused.9.png
similarity index 100%
rename from res/drawable-mdpi-finger/tab_focused.9.png
rename to res/drawable-mdpi/tab_focused.9.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/tab_focused_bottom.9.png b/res/drawable-mdpi/tab_focused_bottom.9.png
similarity index 100%
rename from res/drawable-mdpi-finger/tab_focused_bottom.9.png
rename to res/drawable-mdpi/tab_focused_bottom.9.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/tab_left_arrow.png b/res/drawable-mdpi/tab_left_arrow.png
similarity index 100%
rename from res/drawable-mdpi-finger/tab_left_arrow.png
rename to res/drawable-mdpi/tab_left_arrow.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/tab_pressed.9.png b/res/drawable-mdpi/tab_pressed.9.png
similarity index 100%
rename from res/drawable-mdpi-finger/tab_pressed.9.png
rename to res/drawable-mdpi/tab_pressed.9.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/tab_pressed_bottom.9.png b/res/drawable-mdpi/tab_pressed_bottom.9.png
similarity index 100%
rename from res/drawable-mdpi-finger/tab_pressed_bottom.9.png
rename to res/drawable-mdpi/tab_pressed_bottom.9.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/tab_right_arrow.png b/res/drawable-mdpi/tab_right_arrow.png
similarity index 100%
rename from res/drawable-mdpi-finger/tab_right_arrow.png
rename to res/drawable-mdpi/tab_right_arrow.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/tab_selected.9.png b/res/drawable-mdpi/tab_selected.9.png
similarity index 100%
rename from res/drawable-mdpi-finger/tab_selected.9.png
rename to res/drawable-mdpi/tab_selected.9.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/tab_selected_bottom.9.png b/res/drawable-mdpi/tab_selected_bottom.9.png
similarity index 100%
rename from res/drawable-mdpi-finger/tab_selected_bottom.9.png
rename to res/drawable-mdpi/tab_selected_bottom.9.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/tab_unselected.9.png b/res/drawable-mdpi/tab_unselected.9.png
similarity index 100%
rename from res/drawable-mdpi-finger/tab_unselected.9.png
rename to res/drawable-mdpi/tab_unselected.9.png
Binary files differ
diff --git a/res/drawable-mdpi/title_bar_medium.9.png b/res/drawable-mdpi/title_bar_medium.9.png
new file mode 100644
index 0000000..2d41d02
--- /dev/null
+++ b/res/drawable-mdpi/title_bar_medium.9.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/title_bar_shadow.9.png b/res/drawable-mdpi/title_bar_shadow.9.png
similarity index 100%
rename from res/drawable-mdpi-finger/title_bar_shadow.9.png
rename to res/drawable-mdpi/title_bar_shadow.9.png
Binary files differ
diff --git a/res/drawable/unknown_source.png b/res/drawable-mdpi/unknown_source.png
similarity index 100%
rename from res/drawable/unknown_source.png
rename to res/drawable-mdpi/unknown_source.png
Binary files differ
diff --git a/res/drawable-mdpi/view_bg.9.png b/res/drawable-mdpi/view_bg.9.png
new file mode 100644
index 0000000..f7ed606
--- /dev/null
+++ b/res/drawable-mdpi/view_bg.9.png
Binary files differ
diff --git a/res/drawable-finger/btn_circle.xml b/res/drawable/btn_circle.xml
similarity index 100%
rename from res/drawable-finger/btn_circle.xml
rename to res/drawable/btn_circle.xml
diff --git a/res/drawable-finger/btn_contact_picture.xml b/res/drawable/btn_contact_picture.xml
similarity index 100%
rename from res/drawable-finger/btn_contact_picture.xml
rename to res/drawable/btn_contact_picture.xml
diff --git a/res/drawable-finger/btn_dial.xml b/res/drawable/btn_dial.xml
similarity index 100%
rename from res/drawable-finger/btn_dial.xml
rename to res/drawable/btn_dial.xml
diff --git a/res/drawable-finger/btn_dial_action.xml b/res/drawable/btn_dial_action.xml
similarity index 100%
rename from res/drawable-finger/btn_dial_action.xml
rename to res/drawable/btn_dial_action.xml
diff --git a/res/drawable-finger/btn_dial_delete.xml b/res/drawable/btn_dial_delete.xml
similarity index 100%
rename from res/drawable-finger/btn_dial_delete.xml
rename to res/drawable/btn_dial_delete.xml
diff --git a/res/drawable-finger/btn_dial_voicemail.xml b/res/drawable/btn_dial_voicemail.xml
similarity index 100%
rename from res/drawable-finger/btn_dial_voicemail.xml
rename to res/drawable/btn_dial_voicemail.xml
diff --git a/res/drawable-finger/btn_dial_textfield_active.xml b/res/drawable/btn_nav_bar.xml
similarity index 66%
rename from res/drawable-finger/btn_dial_textfield_active.xml
rename to res/drawable/btn_nav_bar.xml
index 246d543..e597db4 100644
--- a/res/drawable-finger/btn_dial_textfield_active.xml
+++ b/res/drawable/btn_nav_bar.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2008 The Android Open Source Project
+<!-- 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.
@@ -14,12 +14,8 @@
limitations under the License.
-->
+<!-- THIS FILE IS TEMPORARY - REMOVE WHEN FRAMEWORK RESOURCE IS AVAILABLE -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_pressed="true"
- android:drawable="@drawable/btn_dial_textfield_pressed" />
- <item android:state_focused="true"
- android:drawable="@drawable/btn_dial_textfield_activated" />
- <item
- android:drawable="@drawable/btn_dial_textfield_activated" />
+ <item android:state_checked="false" android:drawable="@*android:drawable/btn_toggle_off" />
+ <item android:state_checked="true" android:drawable="@*android:drawable/btn_toggle_on" />
</selector>
-
diff --git a/res/drawable-finger/dial_num_0.xml b/res/drawable/dial_num_0.xml
similarity index 100%
rename from res/drawable-finger/dial_num_0.xml
rename to res/drawable/dial_num_0.xml
diff --git a/res/drawable-finger/dial_num_1_no_vm.xml b/res/drawable/dial_num_1_no_vm.xml
similarity index 100%
rename from res/drawable-finger/dial_num_1_no_vm.xml
rename to res/drawable/dial_num_1_no_vm.xml
diff --git a/res/drawable-finger/dial_num_2.xml b/res/drawable/dial_num_2.xml
similarity index 100%
rename from res/drawable-finger/dial_num_2.xml
rename to res/drawable/dial_num_2.xml
diff --git a/res/drawable-finger/dial_num_3.xml b/res/drawable/dial_num_3.xml
similarity index 100%
rename from res/drawable-finger/dial_num_3.xml
rename to res/drawable/dial_num_3.xml
diff --git a/res/drawable-finger/dial_num_4.xml b/res/drawable/dial_num_4.xml
similarity index 100%
rename from res/drawable-finger/dial_num_4.xml
rename to res/drawable/dial_num_4.xml
diff --git a/res/drawable-finger/dial_num_5.xml b/res/drawable/dial_num_5.xml
similarity index 100%
rename from res/drawable-finger/dial_num_5.xml
rename to res/drawable/dial_num_5.xml
diff --git a/res/drawable-finger/dial_num_6.xml b/res/drawable/dial_num_6.xml
similarity index 100%
rename from res/drawable-finger/dial_num_6.xml
rename to res/drawable/dial_num_6.xml
diff --git a/res/drawable-finger/dial_num_7.xml b/res/drawable/dial_num_7.xml
similarity index 100%
rename from res/drawable-finger/dial_num_7.xml
rename to res/drawable/dial_num_7.xml
diff --git a/res/drawable-finger/dial_num_8.xml b/res/drawable/dial_num_8.xml
similarity index 100%
rename from res/drawable-finger/dial_num_8.xml
rename to res/drawable/dial_num_8.xml
diff --git a/res/drawable-finger/dial_num_9.xml b/res/drawable/dial_num_9.xml
similarity index 100%
rename from res/drawable-finger/dial_num_9.xml
rename to res/drawable/dial_num_9.xml
diff --git a/res/drawable-finger/dial_num_pound.xml b/res/drawable/dial_num_pound.xml
similarity index 100%
rename from res/drawable-finger/dial_num_pound.xml
rename to res/drawable/dial_num_pound.xml
diff --git a/res/drawable-finger/dial_num_star.xml b/res/drawable/dial_num_star.xml
similarity index 100%
rename from res/drawable-finger/dial_num_star.xml
rename to res/drawable/dial_num_star.xml
diff --git a/res/drawable-finger/ic_tab_contacts.xml b/res/drawable/ic_tab_contacts.xml
similarity index 100%
rename from res/drawable-finger/ic_tab_contacts.xml
rename to res/drawable/ic_tab_contacts.xml
diff --git a/res/drawable-finger/ic_tab_dialer.xml b/res/drawable/ic_tab_dialer.xml
similarity index 100%
rename from res/drawable-finger/ic_tab_dialer.xml
rename to res/drawable/ic_tab_dialer.xml
diff --git a/res/drawable-finger/ic_tab_recent.xml b/res/drawable/ic_tab_recent.xml
similarity index 100%
rename from res/drawable-finger/ic_tab_recent.xml
rename to res/drawable/ic_tab_recent.xml
diff --git a/res/drawable-finger/ic_tab_starred.xml b/res/drawable/ic_tab_starred.xml
similarity index 100%
rename from res/drawable-finger/ic_tab_starred.xml
rename to res/drawable/ic_tab_starred.xml
diff --git a/res/drawable-finger/quickcontact_disambig_checkbox.xml b/res/drawable/quickcontact_disambig_checkbox.xml
similarity index 100%
rename from res/drawable-finger/quickcontact_disambig_checkbox.xml
rename to res/drawable/quickcontact_disambig_checkbox.xml
diff --git a/res/drawable-finger/quickcontact_slider_btn.xml b/res/drawable/quickcontact_slider_btn.xml
similarity index 100%
rename from res/drawable-finger/quickcontact_slider_btn.xml
rename to res/drawable/quickcontact_slider_btn.xml
diff --git a/res/drawable-finger/tab_bottom.xml b/res/drawable/tab_bottom.xml
similarity index 100%
rename from res/drawable-finger/tab_bottom.xml
rename to res/drawable/tab_bottom.xml
diff --git a/res/drawable-finger/tab_indicator_bg.xml b/res/drawable/tab_indicator_bg.xml
similarity index 100%
rename from res/drawable-finger/tab_indicator_bg.xml
rename to res/drawable/tab_indicator_bg.xml
diff --git a/res/layout-finger/contacts_list_search_results.xml b/res/layout-finger/contacts_list_search_results.xml
deleted file mode 100644
index 244ca80..0000000
--- a/res/layout-finger/contacts_list_search_results.xml
+++ /dev/null
@@ -1,65 +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.
--->
-
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/pinned_header_list_layout"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical"
- >
-
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- android:background="@*android:drawable/title_bar_medium"
- android:paddingLeft="10dip"
- android:paddingRight="10dip"
- android:gravity="center_vertical"
- >
-
- <TextView
- android:id="@+id/search_results_for"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="@string/titleJoinContactDataWith"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:shadowColor="#BB000000"
- android:shadowRadius="2.75"
- />
-
- <TextView
- android:id="@+id/search_results_found"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="-2dip"
- android:textAppearance="?android:attr/textAppearanceSmall"
- />
-
- </LinearLayout>
-
- <view
- class="com.android.contacts.PinnedHeaderListView"
- android:id="@android:id/list"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:fastScrollEnabled="true"
- />
-
- <include layout="@layout/contacts_list_empty"/>
-
-</LinearLayout>
diff --git a/res/layout-land-finger/twelve_key_dialer.xml b/res/layout-land/twelve_key_dialer.xml
similarity index 100%
rename from res/layout-land-finger/twelve_key_dialer.xml
rename to res/layout-land/twelve_key_dialer.xml
diff --git a/res/layout-long-land-finger/twelve_key_dialer.xml b/res/layout-long-land/twelve_key_dialer.xml
similarity index 100%
rename from res/layout-long-land-finger/twelve_key_dialer.xml
rename to res/layout-long-land/twelve_key_dialer.xml
diff --git a/res/layout-long-finger/dialpad.xml b/res/layout-long/dialpad.xml
similarity index 100%
rename from res/layout-long-finger/dialpad.xml
rename to res/layout-long/dialpad.xml
diff --git a/res/layout-long-finger/twelve_key_dialer.xml b/res/layout-long/twelve_key_dialer.xml
similarity index 100%
rename from res/layout-long-finger/twelve_key_dialer.xml
rename to res/layout-long/twelve_key_dialer.xml
diff --git a/res/layout-long-finger/voicemail_dial_delete.xml b/res/layout-long/voicemail_dial_delete.xml
similarity index 100%
rename from res/layout-long-finger/voicemail_dial_delete.xml
rename to res/layout-long/voicemail_dial_delete.xml
diff --git a/res/layout-xlarge/contact_browser.xml b/res/layout-xlarge/contact_browser.xml
new file mode 100644
index 0000000..c1541eb
--- /dev/null
+++ b/res/layout-xlarge/contact_browser.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/two_pane_activity"
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <FrameLayout
+ android:id="@+id/list_container"
+ android:layout_width="400dip"
+ android:layout_height="match_parent" />
+
+ <!-- Holder for detail- or editor-fragment. -->
+ <FrameLayout
+ android:id="@+id/detail_container"
+ android:layout_width="0px"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:background="@drawable/view_bg"
+ android:layout_marginLeft="-4dip" />
+</LinearLayout>
diff --git a/res/layout-xlarge/contact_editor_activity.xml b/res/layout-xlarge/contact_editor_activity.xml
new file mode 100644
index 0000000..e3d36a7
--- /dev/null
+++ b/res/layout-xlarge/contact_editor_activity.xml
@@ -0,0 +1,45 @@
+<?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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="600dip"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <fragment class="com.android.contacts.views.editor.ContactEditorFragment"
+ android:id="@+id/contact_editor_fragment"
+ android:layout_width="match_parent"
+ android:layout_height="0px"
+ android:layout_weight="1" />
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="50dip"
+ android:orientation="horizontal"
+ android:gravity="center_horizontal">
+
+ <Button
+ android:id="@+id/done"
+ android:layout_width="150dip"
+ android:layout_height="match_parent"
+ android:text="@string/menu_done" />
+ <Button
+ android:id="@+id/revert"
+ android:layout_width="150dip"
+ android:layout_height="match_parent"
+ android:text="@string/menu_doNotSave" />
+ </LinearLayout>
+</LinearLayout>
diff --git a/res/layout-xlarge/contacts_list_content.xml b/res/layout-xlarge/contacts_list_content.xml
new file mode 100644
index 0000000..403a46a
--- /dev/null
+++ b/res/layout-xlarge/contacts_list_content.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 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.
+-->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/pinned_header_list_layout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="horizontal"
+ >
+
+ <com.android.contacts.list.ContactListAizyView
+ android:id="@+id/contacts_list_aizy"
+ android:layout_width="40dip"
+ android:layout_height="match_parent"
+ />
+
+ <LinearLayout
+ android:layout_width="0px"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:layout_weight="1"
+ >
+
+ <view
+ class="com.android.contacts.ContactEntryListView"
+ android:id="@android:id/list"
+ android:layout_width="match_parent"
+ android:layout_height="0dip"
+ android:fastScrollEnabled="true"
+ android:layout_weight="1"
+ />
+
+ <include layout="@layout/contacts_list_empty"/>
+
+ <ViewStub android:id="@+id/footer_stub"
+ android:layout="@layout/footer_panel"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ />
+ </LinearLayout>
+</LinearLayout>
diff --git a/res/layout/act_edit.xml b/res/layout/act_edit.xml
deleted file mode 100644
index 2a71717..0000000
--- a/res/layout/act_edit.xml
+++ /dev/null
@@ -1,61 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2007 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.
--->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical"
->
-
- <ScrollView
- android:layout_width="match_parent"
- android:layout_height="1px"
- android:layout_weight="1"
- android:fillViewport="true"
- >
-
- <LinearLayout android:id="@+id/editors"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical"
- />
-
- </ScrollView>
-
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- style="@android:style/ButtonBar"
- >
-
- <Button android:id="@+id/btn_done"
- android:layout_width="0dip"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:text="@string/menu_done"
- />
-
- <Button android:id="@+id/btn_discard"
- android:layout_width="0dip"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:text="@string/menu_doNotSave"
- />
-
- </LinearLayout>
-
-</LinearLayout>
diff --git a/res/layout/aggregation_suggestions.xml b/res/layout/aggregation_suggestions.xml
new file mode 100644
index 0000000..684fe91
--- /dev/null
+++ b/res/layout/aggregation_suggestions.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright 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.
+ */
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:background="@drawable/suggestion_bg">
+ <TextView
+ android:id="@+id/aggregation_suggestion_title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentLeft="true"
+ android:layout_marginLeft="5dip"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textColor="?android:attr/textColorPrimary"
+ android:textStyle="bold"
+ />
+
+ <LinearLayout
+ android:id="@+id/aggregation_suggestions"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ />
+</LinearLayout>
diff --git a/res/layout/aggregation_suggestions_item.xml b/res/layout/aggregation_suggestions_item.xml
new file mode 100644
index 0000000..06d153e
--- /dev/null
+++ b/res/layout/aggregation_suggestions_item.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright 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.
+ */
+-->
+
+<view xmlns:android="http://schemas.android.com/apk/res/android"
+ class="com.android.contacts.ui.widget.AggregationSuggestionView"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:paddingLeft="5dip"
+ android:paddingRight="15dip"
+>
+ <Button
+ android:id="@+id/aggregation_suggestion_join_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/aggregation_suggestion_join_button"
+ android:layout_centerInParent="true"
+ android:layout_alignParentRight="true"
+ />
+
+ <ImageView
+ android:id="@+id/aggregation_suggestion_photo"
+ android:layout_width="@dimen/aggregation_suggestion_icon_size"
+ android:layout_height="@dimen/aggregation_suggestion_icon_size"
+ android:layout_alignParentLeft="true"
+ android:layout_centerInParent="true"
+ android:layout_marginTop="4dip"
+ android:scaleType="fitCenter"
+ />
+
+ <TextView
+ android:id="@+id/aggregation_suggestion_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_toRightOf="@id/aggregation_suggestion_photo"
+ android:layout_toLeftOf="@id/aggregation_suggestion_join_button"
+ android:layout_marginLeft="10dip"
+ android:layout_marginTop="4dip"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textColor="?android:attr/textColorSecondary"
+ />
+
+ <TextView
+ android:id="@+id/aggregation_suggestion_data"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_toRightOf="@id/aggregation_suggestion_photo"
+ android:layout_toLeftOf="@id/aggregation_suggestion_join_button"
+ android:layout_below="@id/aggregation_suggestion_name"
+ android:layout_marginLeft="10dip"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textColor="?android:attr/textColorSecondary"
+ />
+</view>
diff --git a/res/layout/aizy_popup_window.xml b/res/layout/aizy_popup_window.xml
new file mode 100644
index 0000000..801b481
--- /dev/null
+++ b/res/layout/aizy_popup_window.xml
@@ -0,0 +1,33 @@
+<?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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:background="@drawable/aizy_bottom"
+ >
+
+ <TextView
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:id="@+id/caption"
+ android:width="75dip"
+ android:textSize="70sp"
+ android:paddingLeft="20dip"
+ android:gravity="center" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/res/layout-finger/call_detail.xml b/res/layout/call_detail.xml
similarity index 100%
rename from res/layout-finger/call_detail.xml
rename to res/layout/call_detail.xml
diff --git a/res/layout-finger/call_detail_list_item.xml b/res/layout/call_detail_list_item.xml
similarity index 100%
rename from res/layout-finger/call_detail_list_item.xml
rename to res/layout/call_detail_list_item.xml
diff --git a/res/layout/item_editor_field.xml b/res/layout/contact_browser.xml
similarity index 74%
rename from res/layout/item_editor_field.xml
rename to res/layout/contact_browser.xml
index 9ad8689..0b4bb63 100644
--- a/res/layout/item_editor_field.xml
+++ b/res/layout/contact_browser.xml
@@ -1,12 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2008 The Android Open Source Project
+<!-- Copyright (C) 2009 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.
@@ -14,7 +14,8 @@
limitations under the License.
-->
-<EditText
- xmlns:android="http://schemas.android.com/apk/res/android"
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/list_container"
android:layout_width="match_parent"
- android:layout_height="wrap_content" />
+ android:layout_height="match_parent">
+</FrameLayout>
diff --git a/res/layout-finger/contact_card_layout.xml b/res/layout/contact_card_layout.xml
similarity index 100%
rename from res/layout-finger/contact_card_layout.xml
rename to res/layout/contact_card_layout.xml
diff --git a/res/layout/item_editor_field.xml b/res/layout/contact_detail_activity.xml
similarity index 60%
copy from res/layout/item_editor_field.xml
copy to res/layout/contact_detail_activity.xml
index 9ad8689..ab5ec69 100644
--- a/res/layout/item_editor_field.xml
+++ b/res/layout/contact_detail_activity.xml
@@ -1,12 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2008 The Android Open Source Project
+<!-- Copyright (C) 2009 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.
@@ -14,7 +14,12 @@
limitations under the License.
-->
-<EditText
- xmlns:android="http://schemas.android.com/apk/res/android"
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
- android:layout_height="wrap_content" />
+ android:layout_height="match_parent">
+
+ <fragment class="com.android.contacts.views.detail.ContactDetailFragment"
+ android:id="@+id/contact_detail_fragment"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+</FrameLayout>
diff --git a/res/layout/contact_detail_fragment.xml b/res/layout/contact_detail_fragment.xml
new file mode 100644
index 0000000..8c1bef4
--- /dev/null
+++ b/res/layout/contact_detail_fragment.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/contact_detail"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <com.android.contacts.views.detail.ContactDetailHeaderView
+ android:id="@+id/contact_header_widget"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
+
+ <ListView android:id="@android:id/list"
+ android:layout_width="match_parent"
+ android:layout_height="0px"
+ android:layout_weight="1"
+ android:background="@drawable/title_bar_shadow"
+ />
+
+ <ScrollView android:id="@android:id/empty"
+ android:layout_width="match_parent"
+ android:layout_height="0px"
+ android:layout_weight="1"
+ android:visibility="gone"
+ >
+ <TextView android:id="@+id/emptyText"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/no_contact_details"
+ android:textSize="20sp"
+ android:textColor="?android:attr/textColorSecondary"
+ android:paddingLeft="10dip"
+ android:paddingRight="10dip"
+ android:paddingTop="10dip"
+ android:lineSpacingMultiplier="0.92"
+ />
+ </ScrollView>
+</LinearLayout>
+
diff --git a/res/layout/contact_detail_header_view.xml b/res/layout/contact_detail_header_view.xml
new file mode 100644
index 0000000..c201bf4
--- /dev/null
+++ b/res/layout/contact_detail_header_view.xml
@@ -0,0 +1,98 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/banner"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="horizontal"
+ android:background="@drawable/title_bar_medium"
+ android:paddingRight="5dip">
+
+ <android.widget.QuickContactBadge android:id="@+id/photo"
+ android:layout_gravity="center_vertical"
+ android:layout_marginRight="8dip"
+ android:layout_marginLeft="-1dip"
+ style="@*android:style/Widget.QuickContactBadge.WindowSmall" />
+ />
+
+ <LinearLayout
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:orientation="vertical"
+ android:layout_gravity="center_vertical" >
+
+ <TextView android:id="@+id/name"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textStyle="bold"
+ android:shadowColor="#BB000000"
+ android:shadowRadius="2.75"
+ />
+
+ <TextView android:id="@+id/phonetic_name"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:layout_marginTop="-2dip"
+ android:visibility="gone"
+ />
+
+ <TextView android:id="@+id/status"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:layout_marginTop="-2dip"
+ android:visibility="gone"
+ />
+
+ <TextView android:id="@+id/status_date"
+ android:layout_width="match_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textSize="12sp"
+ android:layout_marginTop="-2dip"
+ android:visibility="gone"
+ />
+ </LinearLayout>
+
+ <ImageView
+ android:id="@+id/presence"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:paddingLeft="3dip"
+ android:paddingRight="6dip"
+ android:visibility="gone"
+ />
+
+ <CheckBox
+ android:id="@+id/star"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:contentDescription="@string/description_star"
+ style="?android:attr/starStyle" />
+</LinearLayout>
diff --git a/res/layout/item_editor_field.xml b/res/layout/contact_editor_activity.xml
similarity index 60%
copy from res/layout/item_editor_field.xml
copy to res/layout/contact_editor_activity.xml
index 9ad8689..ee4d6ac 100644
--- a/res/layout/item_editor_field.xml
+++ b/res/layout/contact_editor_activity.xml
@@ -1,12 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2008 The Android Open Source Project
+<!-- Copyright (C) 2009 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.
@@ -14,7 +14,12 @@
limitations under the License.
-->
-<EditText
- xmlns:android="http://schemas.android.com/apk/res/android"
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
- android:layout_height="wrap_content" />
+ android:layout_height="match_parent">
+
+ <fragment class="com.android.contacts.views.editor.ContactEditorFragment"
+ android:id="@+id/contact_editor_fragment"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+</FrameLayout>
diff --git a/res/layout/contact_editor_fragment.xml b/res/layout/contact_editor_fragment.xml
new file mode 100644
index 0000000..602f7f9
--- /dev/null
+++ b/res/layout/contact_editor_fragment.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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="match_parent"
+>
+
+ <ScrollView
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:fillViewport="true"
+ >
+
+ <LinearLayout android:id="@+id/editors"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ />
+
+ </ScrollView>
+</FrameLayout>
diff --git a/res/layout/contact_editor_header_view.xml b/res/layout/contact_editor_header_view.xml
new file mode 100644
index 0000000..fb5596e
--- /dev/null
+++ b/res/layout/contact_editor_header_view.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/banner"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="horizontal"
+ android:background="@drawable/title_bar_medium"
+ android:paddingRight="5dip">
+
+ <TextView android:id="@+id/caption"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textStyle="bold"
+ android:shadowColor="#BB000000"
+ android:shadowRadius="2.75"
+ android:text="@string/edit_contact"
+ android:layout_gravity="center_vertical"
+ />
+
+ <LinearLayout
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:orientation="vertical"
+ android:layout_gravity="center_vertical" />
+
+ <TextView android:id="@+id/merge_info"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textStyle="bold"
+ android:shadowColor="#BB000000"
+ android:shadowRadius="2.75"
+ android:layout_gravity="center_vertical"
+ />
+</LinearLayout>
diff --git a/res/layout/item_editor_field.xml b/res/layout/contact_none_fragment.xml
similarity index 63%
copy from res/layout/item_editor_field.xml
copy to res/layout/contact_none_fragment.xml
index 9ad8689..15dfba5 100644
--- a/res/layout/item_editor_field.xml
+++ b/res/layout/contact_none_fragment.xml
@@ -1,12 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2008 The Android Open Source Project
+<!-- Copyright (C) 2007 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.
@@ -14,7 +14,15 @@
limitations under the License.
-->
-<EditText
- xmlns:android="http://schemas.android.com/apk/res/android"
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
- android:layout_height="wrap_content" />
+ android:layout_height="match_parent"
+>
+
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/contact_none"
+ android:layout_centerInParent="true"/>
+
+</RelativeLayout>
diff --git a/res/layout-finger/contact_options.xml b/res/layout/contact_options.xml
similarity index 100%
rename from res/layout-finger/contact_options.xml
rename to res/layout/contact_options.xml
diff --git a/res/layout/contacts_list_content.xml b/res/layout/contacts_list_content.xml
new file mode 100644
index 0000000..8d1c50a
--- /dev/null
+++ b/res/layout/contacts_list_content.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 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.
+-->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/pinned_header_list_layout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="horizontal"
+ >
+
+ <LinearLayout
+ android:layout_width="0px"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:layout_weight="1"
+ >
+
+ <view
+ class="com.android.contacts.ContactEntryListView"
+ android:id="@android:id/list"
+ android:layout_width="match_parent"
+ android:layout_height="0dip"
+ android:fastScrollEnabled="true"
+ android:layout_weight="1"
+ />
+
+ <include layout="@layout/contacts_list_empty"/>
+
+ <ViewStub android:id="@+id/footer_stub"
+ android:layout="@layout/footer_panel"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ />
+ </LinearLayout>
+ <com.android.contacts.list.ContactListAizyView
+ android:id="@+id/contacts_list_aizy"
+ android:layout_width="30dip"
+ android:layout_height="match_parent"
+ />
+</LinearLayout>
diff --git a/res/layout-finger/contacts_list_content_join.xml b/res/layout/contacts_list_content_join.xml
similarity index 80%
rename from res/layout-finger/contacts_list_content_join.xml
rename to res/layout/contacts_list_content_join.xml
index b50713b..1e91dab 100644
--- a/res/layout-finger/contacts_list_content_join.xml
+++ b/res/layout/contacts_list_content_join.xml
@@ -61,10 +61,27 @@
</LinearLayout>
</LinearLayout>
- <ListView android:id="@android:id/list"
+ <LinearLayout
+ android:id="@+id/pinned_header_list_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:orientation="horizontal"
+ >
+
+ <com.android.contacts.list.ContactListAizyView
+ android:id="@+id/contacts_list_aizy"
+ android:layout_width="50dip"
+ android:layout_height="match_parent"
+ />
+
+ <view
+ class="com.android.contacts.ContactEntryListView"
+ android:id="@android:id/list"
+ android:layout_width="0px"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
android:fastScrollEnabled="true"
- />
+ />
+ </LinearLayout>
</LinearLayout>
diff --git a/res/layout-finger/contacts_list_empty.xml b/res/layout/contacts_list_empty.xml
similarity index 95%
rename from res/layout-finger/contacts_list_empty.xml
rename to res/layout/contacts_list_empty.xml
index 195da1e..d655899 100644
--- a/res/layout-finger/contacts_list_empty.xml
+++ b/res/layout/contacts_list_empty.xml
@@ -13,8 +13,9 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<ScrollView
+<view
xmlns:android="http://schemas.android.com/apk/res/android"
+ class="com.android.contacts.ContactListEmptyView"
android:id="@android:id/empty"
android:layout_width="match_parent"
android:layout_height="match_parent"
@@ -35,6 +36,7 @@
android:paddingRight="10dip"
android:paddingTop="10dip"
android:lineSpacingMultiplier="0.92"
+ android:visibility="gone"
/>
<LinearLayout android:id="@+id/import_failure"
@@ -61,4 +63,4 @@
android:text="@string/upgrade_out_of_memory_retry"/>
</LinearLayout>
</LinearLayout>
-</ScrollView>
+</view>
diff --git a/res/layout-finger/contacts_list_search_all_item.xml b/res/layout/contacts_list_search_all_item.xml
similarity index 100%
rename from res/layout-finger/contacts_list_search_all_item.xml
rename to res/layout/contacts_list_search_all_item.xml
diff --git a/res/layout-finger/contacts_list_show_all_item.xml b/res/layout/contacts_list_show_all_item.xml
similarity index 100%
rename from res/layout-finger/contacts_list_show_all_item.xml
rename to res/layout/contacts_list_show_all_item.xml
diff --git a/res/layout-finger/contacts_search_content.xml b/res/layout/contacts_search_content.xml
similarity index 76%
rename from res/layout-finger/contacts_search_content.xml
rename to res/layout/contacts_search_content.xml
index ae72376..d36ad00 100644
--- a/res/layout-finger/contacts_search_content.xml
+++ b/res/layout/contacts_search_content.xml
@@ -26,10 +26,11 @@
<include android:id="@+id/searchView"
layout="@layout/search_bar"/>
- <ListView
- android:id="@android:id/list"
+ <FrameLayout
+ android:id="@+id/list_container"
android:layout_width="match_parent"
- android:layout_height="match_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1"
android:fastScrollEnabled="true"
android:background="@android:color/background_dark"
/>
@@ -37,6 +38,13 @@
<!-- Transparent filler -->
<View android:id="@android:id/empty"
android:layout_width="match_parent"
- android:layout_height="match_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1"
+ android:visibility="gone"
+ />
+ <ViewStub android:id="@+id/footer_stub"
+ android:layout="@layout/footer_panel"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
/>
</LinearLayout>
diff --git a/res/layout-finger/create_new_contact.xml b/res/layout/create_new_contact.xml
similarity index 100%
rename from res/layout-finger/create_new_contact.xml
rename to res/layout/create_new_contact.xml
diff --git a/res/layout-finger/dialer_activity.xml b/res/layout/dialer_activity.xml
similarity index 100%
rename from res/layout-finger/dialer_activity.xml
rename to res/layout/dialer_activity.xml
diff --git a/res/layout-finger/dialpad.xml b/res/layout/dialpad.xml
similarity index 100%
rename from res/layout-finger/dialpad.xml
rename to res/layout/dialpad.xml
diff --git a/res/layout-finger/dialpad_chooser_list_item.xml b/res/layout/dialpad_chooser_list_item.xml
similarity index 100%
rename from res/layout-finger/dialpad_chooser_list_item.xml
rename to res/layout/dialpad_chooser_list_item.xml
diff --git a/res/layout/directory_header.xml b/res/layout/directory_header.xml
new file mode 100644
index 0000000..53207e0
--- /dev/null
+++ b/res/layout/directory_header.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<!-- Layout used for list section separators. -->
+<RelativeLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="48dip"
+ android:background="@drawable/directory_bg"
+ >
+ <TextView
+ android:id="@+id/count"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layout_centerVertical="true"
+ android:layout_alignParentRight="true"
+ android:layout_marginRight="8dip"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textColor="?android:attr/textColorSecondary"
+ />
+ <TextView
+ android:id="@+id/display_name"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentBottom="true"
+ android:layout_alignWithParentIfMissing="true"
+ android:layout_marginLeft="6dip"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:singleLine="true"
+ />
+ <TextView
+ android:id="@+id/directory_type"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layout_alignParentLeft="true"
+ android:layout_centerVertical="true"
+ android:layout_marginLeft="6dip"
+ android:layout_marginBottom="-4dip"
+ android:layout_above="@id/display_name"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:singleLine="true"
+ />
+
+</RelativeLayout>
diff --git a/res/layout-finger/display_child.xml b/res/layout/display_child.xml
similarity index 100%
rename from res/layout-finger/display_child.xml
rename to res/layout/display_child.xml
diff --git a/res/layout-finger/display_group.xml b/res/layout/display_group.xml
similarity index 100%
rename from res/layout-finger/display_group.xml
rename to res/layout/display_group.xml
diff --git a/res/layout-finger/display_options_phones_only.xml b/res/layout/display_options_phones_only.xml
similarity index 100%
rename from res/layout-finger/display_options_phones_only.xml
rename to res/layout/display_options_phones_only.xml
diff --git a/res/layout-finger/edit_contact_entry_voicemail.xml b/res/layout/edit_contact_entry_voicemail.xml
similarity index 100%
rename from res/layout-finger/edit_contact_entry_voicemail.xml
rename to res/layout/edit_contact_entry_voicemail.xml
diff --git a/res/layout-finger/edit_divider.xml b/res/layout/edit_divider.xml
similarity index 100%
rename from res/layout-finger/edit_divider.xml
rename to res/layout/edit_divider.xml
diff --git a/res/layout-finger/edit_phonetic_name.xml b/res/layout/edit_phonetic_name.xml
similarity index 100%
rename from res/layout-finger/edit_phonetic_name.xml
rename to res/layout/edit_phonetic_name.xml
diff --git a/res/layout-finger/empty.xml b/res/layout/empty.xml
similarity index 100%
rename from res/layout-finger/empty.xml
rename to res/layout/empty.xml
diff --git a/res/layout/footer_panel.xml b/res/layout/footer_panel.xml
new file mode 100644
index 0000000..2625a43
--- /dev/null
+++ b/res/layout/footer_panel.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/footer"
+ android:orientation="horizontal"
+ android:visibility="gone"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ style="@android:style/ButtonBar"
+>
+
+ <Button android:id="@+id/done"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="@string/menu_done"
+ />
+
+ <Button android:id="@+id/revert"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="@string/menu_doNotSave"
+ />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/res/layout-finger/horizontal_divider.xml b/res/layout/horizontal_divider.xml
similarity index 100%
rename from res/layout-finger/horizontal_divider.xml
rename to res/layout/horizontal_divider.xml
diff --git a/res/layout/item_contact_editor.xml b/res/layout/item_contact_editor.xml
index f5e7bd3..6886503 100644
--- a/res/layout/item_contact_editor.xml
+++ b/res/layout/item_contact_editor.xml
@@ -14,85 +14,74 @@
limitations under the License.
-->
-<!-- placed inside act_edit as tabcontent -->
<com.android.contacts.ui.widget.ContactEditorView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:orientation="horizontal"
+ android:orientation="vertical"
>
- <!-- Left side color bar -->
- <ImageView
- android:id="@+id/color_bar"
- android:layout_width="4dip"
- android:layout_height="match_parent"
- android:visibility="gone"
- />
-
- <!-- The content -->
- <LinearLayout
- android:layout_width="0dip"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:orientation="vertical"
+ <!-- Account info header -->
+ <RelativeLayout android:id="@+id/header"
+ android:layout_height="64dip"
+ android:layout_width="match_parent"
+ android:background="@android:drawable/list_selector_background"
>
- <!-- Account info header -->
- <RelativeLayout android:id="@+id/header"
- android:layout_height="64dip"
+ <ImageView android:id="@+id/header_color_bar"
android:layout_width="match_parent"
- >
+ android:layout_height="4dip"
+ android:layout_marginBottom="5dip"
+ android:background="@color/edit_divider"
+ />
- <ImageView android:id="@+id/header_color_bar"
- android:layout_width="match_parent"
- android:layout_height="4dip"
- android:layout_marginBottom="5dip"
- android:background="@color/edit_divider"
- />
+ <ImageView android:id="@+id/header_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="7dip"
+ android:layout_marginRight="7dip"
+ android:layout_centerVertical="true"
+ android:layout_below="@id/header_color_bar"
+ />
- <ImageView android:id="@+id/header_icon"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginLeft="7dip"
- android:layout_marginRight="7dip"
- android:layout_centerVertical="true"
- android:layout_below="@id/header_color_bar"
- />
+ <TextView android:id="@+id/header_account_type"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_toRightOf="@+id/header_icon"
+ android:layout_alignTop="@id/header_icon"
+ android:layout_marginTop="-4dip"
- <TextView android:id="@+id/header_account_type"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_toRightOf="@+id/header_icon"
- android:layout_alignTop="@id/header_icon"
- android:layout_marginTop="-4dip"
+ android:textSize="24sp"
+ android:textColor="?android:attr/textColorPrimary"
+ android:singleLine="true"
+ />
- android:textSize="24sp"
- android:textColor="?android:attr/textColorPrimary"
- android:singleLine="true"
- />
+ <TextView android:id="@+id/header_account_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_toRightOf="@+id/header_icon"
+ android:layout_alignBottom="@+id/header_icon"
+ android:layout_marginBottom="2dip"
- <TextView android:id="@+id/header_account_name"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_toRightOf="@+id/header_icon"
- android:layout_alignBottom="@+id/header_icon"
- android:layout_marginBottom="2dip"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textColor="?android:attr/textColorPrimary"
+ android:singleLine="true"
+ />
- android:textAppearance="?android:attr/textAppearanceSmall"
- android:textColor="?android:attr/textColorPrimary"
- android:singleLine="true"
- />
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="1px"
+ android:layout_alignParentBottom="true"
- <View
- android:layout_width="match_parent"
- android:layout_height="1px"
- android:layout_alignParentBottom="true"
+ android:background="?android:attr/listDivider"
+ />
+ </RelativeLayout>
- android:background="?android:attr/listDivider"
- />
-
- </RelativeLayout>
+ <LinearLayout
+ android:id="@+id/body"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
<FrameLayout
android:id="@+id/stub_photo"
@@ -107,74 +96,43 @@
</FrameLayout>
- <include
+ <com.android.contacts.ui.widget.GenericEditorView
android:id="@+id/edit_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:paddingRight="?android:attr/scrollbarSize"
android:layout_below="@id/stub_photo"
android:layout_marginTop="6dip"
- android:layout_marginBottom="4dip"
- layout="@layout/item_generic_editor" />
+ android:layout_marginBottom="4dip" />
- <TextView android:id="@+id/read_only_name"
+ <ViewStub android:id="@+id/aggregation_suggestion_stub"
+ android:inflatedId="@+id/aggregation_suggestion"
+ android:layout="@layout/aggregation_suggestions"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginTop="6dip"
- android:layout_marginBottom="6dip"
- android:layout_marginLeft="10dip"
-
- android:textAppearance="?android:attr/textAppearanceLarge"
- />
+ android:visibility="visible"/>
<LinearLayout
- android:id="@+id/sect_general"
+ android:id="@+id/sect_fields"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
/>
- <View android:id="@+id/head_secondary_divider"
+ <View
android:layout_width="match_parent"
android:layout_height="1px"
- android:background="?android:attr/listDivider" />
-
- <TextView
- android:id="@+id/head_secondary"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
-
- android:gravity="center_vertical"
- android:minHeight="?android:attr/listPreferredItemHeight"
- android:text="@string/edit_secondary_collapse"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:textColor="@color/kind_title"
- android:singleLine="true"
- android:ellipsize="marquee"
- android:focusable="true"
- android:clickable="true"
- android:paddingLeft="10dip"
- android:drawablePadding="10dip" />
-
- <LinearLayout
- android:id="@+id/sect_secondary"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical" />
-
- <TextView
- android:id="@+id/edit_read_only"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="10dip"
- android:layout_marginBottom="10dip"
- android:layout_marginLeft="10dip"
-
- android:textAppearance="?android:attr/textAppearanceSmall"
- android:textColor="?android:attr/textColorPrimary"
- android:drawableLeft="@android:drawable/ic_dialog_alert"
- android:drawablePadding="10dip"
+ android:layout_alignParentBottom="true"
+ android:background="?android:attr/listDivider"
/>
+ <Button
+ android:id="@+id/button_add_field"
+ android:text="@string/add_field"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="right"
+ android:layout_marginTop="10dip"
+ />
</LinearLayout>
-
</com.android.contacts.ui.widget.ContactEditorView>
diff --git a/res/layout/item_generic_editor.xml b/res/layout/item_generic_editor.xml
deleted file mode 100644
index e672eba..0000000
--- a/res/layout/item_generic_editor.xml
+++ /dev/null
@@ -1,68 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2008 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.
--->
-
-<com.android.contacts.ui.widget.GenericEditorView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:baselineAligned="false"
- android:paddingRight="?android:attr/scrollbarSize">
-
- <Button
- android:id="@+id/edit_label"
- android:layout_width="100dip"
- android:layout_height="wrap_content"
- android:visibility="gone"
- android:gravity="left|center_vertical" />
-
- <ImageButton
- android:id="@+id/edit_delete"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentRight="true"
- style="@style/MinusButton" />
-
- <LinearLayout
- android:id="@+id/edit_fields"
- android:layout_width="0dip"
- android:layout_height="wrap_content"
- android:layout_marginLeft="4dip"
- android:layout_alignWithParentIfMissing="true"
- android:layout_toRightOf="@id/edit_label"
- android:layout_toLeftOf="@id/edit_delete"
- android:orientation="vertical"
- android:baselineAligned="false"
- android:gravity="center_vertical" />
-
- <ImageButton
- android:id="@+id/edit_more"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentRight="true"
- android:layout_alignBottom="@id/edit_fields"
- android:visibility="gone"
- style="@style/MoreButton" />
-
- <ImageButton
- android:id="@+id/edit_less"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentRight="true"
- android:layout_alignBottom="@id/edit_fields"
- android:visibility="gone"
- style="@style/LessButton" />
-
-</com.android.contacts.ui.widget.GenericEditorView>
diff --git a/res/layout/item_kind_section.xml b/res/layout/item_kind_section.xml
index d1dec5e..a78896b 100644
--- a/res/layout/item_kind_section.xml
+++ b/res/layout/item_kind_section.xml
@@ -25,7 +25,7 @@
<View
android:layout_width="match_parent"
- android:layout_height="wrap_content"
+ android:layout_height="1px"
android:background="?android:attr/listDivider" />
<LinearLayout
diff --git a/res/layout-finger/list_item_text_icons.xml b/res/layout/list_item_text_icons.xml
similarity index 100%
rename from res/layout-finger/list_item_text_icons.xml
rename to res/layout/list_item_text_icons.xml
diff --git a/res/layout-finger/list_section.xml b/res/layout/list_section.xml
similarity index 100%
rename from res/layout-finger/list_section.xml
rename to res/layout/list_section.xml
diff --git a/res/layout-finger/list_separator.xml b/res/layout/list_separator.xml
similarity index 100%
rename from res/layout-finger/list_separator.xml
rename to res/layout/list_separator.xml
diff --git a/res/layout/navigation_bar.xml b/res/layout/navigation_bar.xml
new file mode 100644
index 0000000..5c76b44
--- /dev/null
+++ b/res/layout/navigation_bar.xml
@@ -0,0 +1,88 @@
+<?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.
+-->
+
+<RelativeLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/navigation_bar"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <ToggleButton
+ android:id="@+id/nav_contacts"
+ android:layout_height="match_parent"
+ android:layout_width="wrap_content"
+ android:textOn="@string/contactsList"
+ android:textOff="@string/contactsList"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentBottom="true"
+ android:background="@drawable/btn_nav_bar"
+ android:paddingLeft="15dip"
+ android:paddingRight="15dip"
+ />
+ <ToggleButton
+ android:id="@+id/nav_favorites"
+ android:layout_height="match_parent"
+ android:layout_width="wrap_content"
+ android:textOn="@string/strequentList"
+ android:textOff="@string/strequentList"
+ android:layout_toRightOf="@id/nav_contacts"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentBottom="true"
+ android:background="@drawable/btn_nav_bar"
+ android:paddingLeft="15dip"
+ android:paddingRight="15dip"
+ />
+ <ToggleButton
+ android:id="@+id/nav_search"
+ android:layout_height="match_parent"
+ android:layout_width="wrap_content"
+ android:textOff="@string/menu_search"
+ android:textOn="@string/menu_search"
+ android:layout_toRightOf="@id/nav_favorites"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentBottom="true"
+ android:background="@drawable/btn_nav_bar"
+ android:paddingLeft="15dip"
+ android:paddingRight="15dip"
+ />
+ <view
+ class="com.android.contacts.widget.SearchEditText"
+ android:id="@+id/search_src_text"
+ android:layout_height="wrap_content"
+ android:layout_width="320dip"
+ android:layout_marginLeft="4dip"
+ android:layout_marginBottom="0dip"
+ android:layout_toRightOf="@id/nav_favorites"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:inputType="textNoSuggestions"
+ android:imeOptions="flagNoExtractUi"
+ android:hint="@string/search_bar_hint"
+ android:drawableRight="@drawable/magnifying_glass"
+ android:freezesText="true"
+ android:visibility="gone"
+ />
+ <ImageView
+ android:id="@+id/nav_cancel_search"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:src="@android:drawable/ic_menu_close_clear_cancel"
+ android:layout_toRightOf="@id/search_src_text"
+ android:layout_centerVertical="true"
+ android:visibility="gone"/>
+
+</RelativeLayout>
diff --git a/res/layout-finger/phone_disambig_item.xml b/res/layout/phone_disambig_item.xml
similarity index 100%
rename from res/layout-finger/phone_disambig_item.xml
rename to res/layout/phone_disambig_item.xml
diff --git a/res/layout-finger/contacts_list_content.xml b/res/layout/pinned_header_list_demo.xml
similarity index 81%
rename from res/layout-finger/contacts_list_content.xml
rename to res/layout/pinned_header_list_demo.xml
index 36c03ce..9a26e98 100644
--- a/res/layout-finger/contacts_list_content.xml
+++ b/res/layout/pinned_header_list_demo.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2008 The Android Open Source Project
+<!-- 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.
@@ -14,7 +14,7 @@
limitations under the License.
-->
-<LinearLayout
+<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/pinned_header_list_layout"
android:layout_width="match_parent"
@@ -23,13 +23,11 @@
>
<view
- class="com.android.contacts.PinnedHeaderListView"
+ class="com.android.contacts.widget.PinnedHeaderListView"
android:id="@android:id/list"
android:layout_width="match_parent"
- android:layout_height="match_parent"
+ android:layout_height="0dip"
android:fastScrollEnabled="true"
+ android:layout_weight="1"
/>
-
- <include layout="@layout/contacts_list_empty"/>
-
</LinearLayout>
diff --git a/res/layout-finger/preference_with_more_button.xml b/res/layout/preference_with_more_button.xml
similarity index 100%
rename from res/layout-finger/preference_with_more_button.xml
rename to res/layout/preference_with_more_button.xml
diff --git a/res/layout-finger/quickcontact.xml b/res/layout/quickcontact.xml
similarity index 100%
rename from res/layout-finger/quickcontact.xml
rename to res/layout/quickcontact.xml
diff --git a/res/layout-finger/quickcontact_header_large.xml b/res/layout/quickcontact_header_large.xml
similarity index 100%
rename from res/layout-finger/quickcontact_header_large.xml
rename to res/layout/quickcontact_header_large.xml
diff --git a/res/layout-finger/quickcontact_header_med.xml b/res/layout/quickcontact_header_med.xml
similarity index 100%
rename from res/layout-finger/quickcontact_header_med.xml
rename to res/layout/quickcontact_header_med.xml
diff --git a/res/layout-finger/quickcontact_header_small.xml b/res/layout/quickcontact_header_small.xml
similarity index 100%
rename from res/layout-finger/quickcontact_header_small.xml
rename to res/layout/quickcontact_header_small.xml
diff --git a/res/layout-finger/quickcontact_item.xml b/res/layout/quickcontact_item.xml
similarity index 100%
rename from res/layout-finger/quickcontact_item.xml
rename to res/layout/quickcontact_item.xml
diff --git a/res/layout-finger/quickcontact_resolve_item.xml b/res/layout/quickcontact_resolve_item.xml
similarity index 100%
rename from res/layout-finger/quickcontact_resolve_item.xml
rename to res/layout/quickcontact_resolve_item.xml
diff --git a/res/layout-finger/recent_calls.xml b/res/layout/recent_calls.xml
similarity index 100%
rename from res/layout-finger/recent_calls.xml
rename to res/layout/recent_calls.xml
diff --git a/res/layout-finger/recent_calls_list_child_item.xml b/res/layout/recent_calls_list_child_item.xml
similarity index 93%
rename from res/layout-finger/recent_calls_list_child_item.xml
rename to res/layout/recent_calls_list_child_item.xml
index 14eb24d..527e259 100644
--- a/res/layout-finger/recent_calls_list_child_item.xml
+++ b/res/layout/recent_calls_list_child_item.xml
@@ -21,7 +21,7 @@
android:background="@drawable/list_item_background_secondary"
>
- <com.android.contacts.ui.widget.DontPressWithParentImageView android:id="@+id/call_icon"
+ <ImageView android:id="@+id/call_icon"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:paddingLeft="14dip"
diff --git a/res/layout-finger/recent_calls_list_group_item.xml b/res/layout/recent_calls_list_group_item.xml
similarity index 97%
rename from res/layout-finger/recent_calls_list_group_item.xml
rename to res/layout/recent_calls_list_group_item.xml
index 2d3e7af..5b4cf21 100644
--- a/res/layout-finger/recent_calls_list_group_item.xml
+++ b/res/layout/recent_calls_list_group_item.xml
@@ -20,7 +20,7 @@
android:paddingLeft="7dip"
>
- <com.android.contacts.ui.widget.DontPressWithParentImageView android:id="@+id/call_icon"
+ <ImageView android:id="@+id/call_icon"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:paddingLeft="14dip"
diff --git a/res/layout-finger/recent_calls_list_item.xml b/res/layout/recent_calls_list_item.xml
similarity index 93%
rename from res/layout-finger/recent_calls_list_item.xml
rename to res/layout/recent_calls_list_item.xml
index 8efa23c..2c519d6 100644
--- a/res/layout-finger/recent_calls_list_item.xml
+++ b/res/layout/recent_calls_list_item.xml
@@ -20,7 +20,7 @@
android:paddingLeft="7dip"
>
- <com.android.contacts.ui.widget.DontPressWithParentImageView android:id="@+id/call_icon"
+ <ImageView android:id="@+id/call_icon"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:paddingLeft="14dip"
diff --git a/res/layout-finger/recent_calls_list_item_layout.xml b/res/layout/recent_calls_list_item_layout.xml
similarity index 100%
rename from res/layout-finger/recent_calls_list_item_layout.xml
rename to res/layout/recent_calls_list_item_layout.xml
diff --git a/res/layout-finger/search_bar.xml b/res/layout/search_bar.xml
similarity index 97%
rename from res/layout-finger/search_bar.xml
rename to res/layout/search_bar.xml
index d322948..304c35d 100644
--- a/res/layout-finger/search_bar.xml
+++ b/res/layout/search_bar.xml
@@ -52,7 +52,7 @@
android:scaleType="centerInside" />
<view
- class="com.android.contacts.SearchEditText"
+ class="com.android.contacts.widget.SearchEditText"
android:id="@+id/search_src_text"
android:layout_height="wrap_content"
android:layout_width="0dip"
diff --git a/res/layout-finger/set_primary_checkbox.xml b/res/layout/set_primary_checkbox.xml
similarity index 100%
rename from res/layout-finger/set_primary_checkbox.xml
rename to res/layout/set_primary_checkbox.xml
diff --git a/res/layout-finger/split_aggregate_list_item.xml b/res/layout/split_aggregate_list_item.xml
similarity index 100%
rename from res/layout-finger/split_aggregate_list_item.xml
rename to res/layout/split_aggregate_list_item.xml
diff --git a/res/layout/status_bar_ongoing_event_progress_bar.xml b/res/layout/status_bar_ongoing_event_progress_bar.xml
new file mode 100644
index 0000000..3c7530a
--- /dev/null
+++ b/res/layout/status_bar_ongoing_event_progress_bar.xml
@@ -0,0 +1,94 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright 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.
+ /
+TODO: This is copied from DownloadProvider, and looks similar to Bluetooth's.
+ It might be better to have this in framework.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:background="@android:drawable/status_bar_item_app_background"
+ >
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="horizontal"
+ >
+ <LinearLayout
+ android:layout_width="40dp"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:paddingTop="8dp"
+ android:focusable="true"
+ >
+ <ImageView
+ android:id="@+id/status_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:src="@android:drawable/sym_def_app_icon"
+ />
+ <TextView android:id="@+id/status_progress_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textColor="#ff000000"
+ android:singleLine="true"
+ android:textSize="14sp"
+ android:layout_gravity="center_horizontal" />
+ </LinearLayout>
+
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:focusable="true">
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:focusable="true"
+ android:layout_alignParentTop="true"
+ android:paddingTop="10dp">
+ <TextView android:id="@+id/status_description"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textColor="#ff000000"
+ android:singleLine="true"
+ android:textSize="18sp"
+ android:paddingLeft="5dp" />
+ </LinearLayout>
+ <ProgressBar
+ android:id="@+id/status_progress_bar"
+ style="?android:attr/progressBarStyleHorizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentBottom="true"
+ android:paddingBottom="8dp"
+ android:paddingRight="25dp" />
+ </RelativeLayout>
+ </LinearLayout>
+
+ <ImageView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:src="@android:drawable/divider_horizontal_bright"
+ />
+</LinearLayout>
+
diff --git a/res/layout-finger/tab_account_name.xml b/res/layout/tab_account_name.xml
similarity index 100%
rename from res/layout-finger/tab_account_name.xml
rename to res/layout/tab_account_name.xml
diff --git a/res/layout-finger/tab_indicator.xml b/res/layout/tab_indicator.xml
similarity index 100%
rename from res/layout-finger/tab_indicator.xml
rename to res/layout/tab_indicator.xml
diff --git a/res/layout-finger/tab_layout.xml b/res/layout/tab_layout.xml
similarity index 100%
rename from res/layout-finger/tab_layout.xml
rename to res/layout/tab_layout.xml
diff --git a/res/layout-finger/tab_left_arrow.xml b/res/layout/tab_left_arrow.xml
similarity index 100%
rename from res/layout-finger/tab_left_arrow.xml
rename to res/layout/tab_left_arrow.xml
diff --git a/res/layout-finger/tab_right_arrow.xml b/res/layout/tab_right_arrow.xml
similarity index 100%
rename from res/layout-finger/tab_right_arrow.xml
rename to res/layout/tab_right_arrow.xml
diff --git a/res/layout-finger/total_contacts.xml b/res/layout/total_contacts.xml
similarity index 100%
rename from res/layout-finger/total_contacts.xml
rename to res/layout/total_contacts.xml
diff --git a/res/layout-finger/twelve_key_dialer.xml b/res/layout/twelve_key_dialer.xml
similarity index 100%
rename from res/layout-finger/twelve_key_dialer.xml
rename to res/layout/twelve_key_dialer.xml
diff --git a/res/layout-finger/voicemail_dial_delete.xml b/res/layout/voicemail_dial_delete.xml
similarity index 100%
rename from res/layout-finger/voicemail_dial_delete.xml
rename to res/layout/voicemail_dial_delete.xml
diff --git a/res/menu/actions.xml b/res/menu/actions.xml
new file mode 100644
index 0000000..282f996
--- /dev/null
+++ b/res/menu/actions.xml
@@ -0,0 +1,38 @@
+<?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.
+-->
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:id="@+id/menu_add"
+ android:icon="@android:drawable/ic_menu_add"
+ android:title="@string/menu_newContact"
+ android:showAsAction="always" />
+
+ <item
+ android:id="@+id/menu_display_groups"
+ android:icon="@*android:drawable/ic_menu_allfriends"
+ android:title="@string/menu_displayGroup" />
+
+ <item
+ android:id="@+id/menu_accounts"
+ android:icon="@drawable/ic_menu_account_list"
+ android:title="@string/menu_accounts" />
+
+ <item
+ android:id="@+id/menu_import_export"
+ android:icon="@drawable/ic_menu_import_export"
+ android:title="@string/menu_import_export" />
+
+</menu>
diff --git a/res/menu/edit.xml b/res/menu/edit.xml
index 6fafbcf..e3c7e09 100644
--- a/res/menu/edit.xml
+++ b/res/menu/edit.xml
@@ -19,7 +19,8 @@
android:id="@+id/menu_done"
android:alphabeticShortcut="\n"
android:icon="@android:drawable/ic_menu_save"
- android:title="@string/menu_done" />
+ android:title="@string/menu_done"
+ android:showAsAction="always" />
<item
android:id="@+id/menu_discard"
@@ -28,7 +29,7 @@
android:title="@string/menu_doNotSave" />
<item
- android:id="@+id/menu_add"
+ android:id="@+id/menu_add_raw_contact"
android:icon="@android:drawable/ic_menu_add"
android:title="@string/menu_newContact" />
diff --git a/res/menu/pick.xml b/res/menu/pick.xml
new file mode 100644
index 0000000..5302dd9
--- /dev/null
+++ b/res/menu/pick.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <item
+ android:id="@+id/menu_display_selected"
+ android:icon="@drawable/ic_menu_display_selected"
+ android:title="@string/menu_display_selected" />
+
+ <item
+ android:id="@+id/menu_display_all"
+ android:icon="@drawable/ic_menu_display_all"
+ android:title="@string/menu_display_all" />
+
+ <item
+ android:id="@+id/menu_select_all"
+ android:icon="@drawable/ic_menu_select"
+ android:title="@string/menu_select_all" />
+
+ <item
+ android:id="@+id/menu_select_none"
+ android:icon="@drawable/ic_menu_unselect"
+ android:title="@string/menu_select_none" />
+
+</menu>
diff --git a/res/layout/item_editor_field.xml b/res/menu/search.xml
similarity index 67%
copy from res/layout/item_editor_field.xml
copy to res/menu/search.xml
index 9ad8689..4e2e8e3 100644
--- a/res/layout/item_editor_field.xml
+++ b/res/menu/search.xml
@@ -1,12 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2008 The Android Open Source Project
+<!-- 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.
@@ -14,7 +14,11 @@
limitations under the License.
-->
-<EditText
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content" />
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <item
+ android:id="@+id/menu_search"
+ android:icon="@android:drawable/ic_menu_search"
+ android:title="@string/menu_search" />
+
+</menu>
diff --git a/res/menu/view.xml b/res/menu/view.xml
index 428434d..7a1c26c 100644
--- a/res/menu/view.xml
+++ b/res/menu/view.xml
@@ -19,7 +19,8 @@
android:id="@+id/menu_edit"
android:icon="@android:drawable/ic_menu_edit"
android:title="@string/menu_editContact"
- android:alphabeticShortcut="e" />
+ android:alphabeticShortcut="e"
+ android:showAsAction="always" />
<item
android:id="@+id/menu_share"
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index 4e0a0d9..9155fa3 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -31,6 +31,8 @@
<string name="viewContactTitle" msgid="7989394521836644384">"Kontaktní informace"</string>
<string name="viewContactDesription" msgid="214186610887547860">"Zobrazit kontakt"</string>
<string name="editContactDescription" msgid="2947202828256214947">"Upravit kontakt"</string>
+ <string name="edit_email_caption" msgid="2401084399676154272">"E-mailová adresa:"</string>
+ <string name="edit_email_type_caption" msgid="66331218163770698">"Typ:"</string>
<string name="insertContactDescription" msgid="4709878105452681987">"Vytvořit kontakt"</string>
<string name="searchHint" msgid="8482945356247760701">"Vyhledat kontakty"</string>
<string name="menu_search" msgid="9147752853603483719">"Hledat"</string>
@@ -123,13 +125,13 @@
</plurals>
<string name="listTotalAllContactsZero" msgid="6811347506748072822">"Žádné kontakty nejsou viditelné"</string>
<plurals name="listFoundAllContacts">
- <item quantity="one" msgid="2830107332033967280">"Nalezen 1 kontakt"</item>
- <item quantity="other" msgid="7752927996850263152">"Počet nalezených kontaktů: <xliff:g id="COUNT">%d</xliff:g>"</item>
+ <item quantity="one" msgid="5517063038754171134">"Počet nalezených položek: 1"</item>
+ <item quantity="other" msgid="3852668542926965042">"Počet nalezených položek: <xliff:g id="COUNT">%d</xliff:g>"</item>
</plurals>
- <string name="listFoundAllContactsZero" msgid="5554368784319460828">"Kontakt nebyl nalezen"</string>
+ <string name="listFoundAllContactsZero" msgid="777952841930508289">"Nenalezeno"</string>
<plurals name="searchFoundContacts">
- <item quantity="one" msgid="916648718690661777">"1 kontakt"</item>
- <item quantity="other" msgid="5660384247071761844">"Počet kontaktů: <xliff:g id="COUNT">%d</xliff:g>"</item>
+ <item quantity="one" msgid="4826918429708286628">"Počet nalezených položek: 1"</item>
+ <item quantity="other" msgid="7988132539476575389">"Počet nalezených položek: <xliff:g id="COUNT">%d</xliff:g>"</item>
</plurals>
<string name="contactsIconLabel" msgid="7666609097606552806">"Kontakty"</string>
<string name="contactsFavoritesLabel" msgid="8417039765586853670">"Oblíbené"</string>
@@ -159,10 +161,10 @@
<string name="simContacts_title" msgid="27341688347689769">"Kontakty na kartě SIM"</string>
<string name="noContactsHelpTextWithSyncForCreateShortcut" msgid="801504710275614594">"Nemáte uloženy žádné kontakty, které by bylo možno zobrazit. (Pokud jste účet přidali právě teď, může synchronizace kontaktů trvat několik minut.)"</string>
<string name="noContactsHelpTextForCreateShortcut" msgid="3081286388667108335">"Nemáte uloženy žádné kontakty, které by bylo možno zobrazit."</string>
- <string name="noContactsHelpText" msgid="6788487368878712350">"Nemáte žádné kontakty, které by bylo možné zobrazit."\n\n"Chcete-li přidat kontakty, stiskněte tlačítko "<font fgcolor="#ffffffff"><b>"Menu"</b></font>" a dotkněte se položky:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Účty"</b></font>", pokud chcete přidat nebo nakonfigurovat účet a kontakty v něm synchronizovat s telefonem;"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Nový kontakt"</b></font>", pokud chcete vytvořit úplně nový kontakt;"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importovat nebo exportovat"</b></font>"."\n</li></string>
- <string name="noContactsHelpTextWithSync" msgid="3734101165712848179">"Nemáte žádné kontakty, které by bylo možné zobrazit. (Pokud jste právě přidali účet, může synchronizace kontaktů trvat několik minut.)"\n\n"Chcete-li přidat kontakty, stiskněte tlačítko "<font fgcolor="#ffffffff"><b>"Menu"</b></font>" a dotkněte se položky:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Účty"</b></font>", pokud chcete přidat nebo nakonfigurovat účet a kontakty v něm synchronizovat s telefonem;"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Možnosti zobrazení"</b></font>", pokud chcete změnit, které kontakty budou zobrazeny;"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Nový kontakt"</b></font>", pokud chcete vytvořit úplně nový kontakt;"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importovat nebo exportovat"</b></font>"."\n</li></string>
- <string name="noContactsNoSimHelpText" msgid="6553845386917463292">"Nemáte žádné kontakty, které by bylo možné zobrazit."\n\n"Chcete-li přidat kontakty, stiskněte tlačítko "<font fgcolor="#ffffffff"><b>"Menu"</b></font>" a dotkněte se položky:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Účty"</b></font>", pokud chcete přidat nebo nakonfigurovat účet a kontakty v něm synchronizovat s telefonem;"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Nový kontakt"</b></font>", pokud chcete vytvořit úplně nový kontakt;"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importovat nebo exportovat"</b></font>"."\n</li></string>
- <string name="noContactsNoSimHelpTextWithSync" msgid="1122296298361373488">"Nemáte žádné kontakty, které by bylo možné zobrazit. (Pokud jste právě přidali účet, může synchronizace kontaktů trvat několik minut.)"\n\n"Chcete-li přidat kontakty, stiskněte tlačítko "<font fgcolor="#ffffffff"><b>"Menu"</b></font>" a dotkněte se položky:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Účty"</b></font>", pokud chcete přidat nebo nakonfigurovat účet a kontakty v něm synchronizovat s telefonem;"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Možnosti zobrazení"</b></font>", pokud chcete změnit, které kontakty budou zobrazeny;"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Nový kontakt"</b></font>", pokud chcete vytvořit úplně nový kontakt;"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importovat nebo exportovat"</b></font>"."\n</li></string>
+ <string name="noContactsHelpText" msgid="7633826236417884130">"Nemáte žádné kontakty, které by bylo možné zobrazit."\n\n"Chcete-li přidat kontakty, stiskněte tlačítko "<font fgcolor="#ffffffff"><b>"Menu"</b></font>" a dotkněte se položky:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Účty"</b></font>", pokud chcete přidat nebo nakonfigurovat účet a kontakty v něm synchronizovat s telefonem;"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Nový kontakt"</b></font>", pokud chcete vytvořit úplně nový kontakt;"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importovat nebo exportovat"</b></font>", pokud chcete importovat kontakty z karty SIM nebo SD."\n</li></string>
+ <string name="noContactsHelpTextWithSync" msgid="3017521127042216243">"Nemáte žádné kontakty, které by bylo možné zobrazit. (Pokud jste právě přidali účet, může synchronizace kontaktů trvat několik minut.)"\n\n"Chcete-li přidat kontakty, stiskněte tlačítko "<font fgcolor="#ffffffff"><b>"Menu"</b></font>" a dotkněte se položky:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Účty"</b></font>", pokud chcete přidat nebo nakonfigurovat účet a kontakty v něm synchronizovat s telefonem;"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Možnosti zobrazení"</b></font>", pokud chcete změnit, které kontakty budou zobrazeny;"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Nový kontakt"</b></font>", pokud chcete vytvořit úplně nový kontakt;"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importovat nebo exportovat"</b></font>", pokud chcete importovat kontakty z karty SIM nebo SD."\n</li></string>
+ <string name="noContactsNoSimHelpText" msgid="467658807711582876">"Nemáte žádné kontakty, které by bylo možné zobrazit."\n\n"Chcete-li přidat kontakty, stiskněte tlačítko "<font fgcolor="#ffffffff"><b>"Menu"</b></font>" a dotkněte se položky:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Účty"</b></font>", pokud chcete přidat nebo nakonfigurovat účet a kontakty v něm synchronizovat s telefonem;"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Nový kontakt"</b></font>", pokud chcete vytvořit úplně nový kontakt;"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importovat nebo exportovat"</b></font>", pokud chcete importovat kontakty z karty SD."\n</li></string>
+ <string name="noContactsNoSimHelpTextWithSync" msgid="9040060730467973050">"Nemáte žádné kontakty, které by bylo možné zobrazit. (Pokud jste právě přidali účet, může synchronizace kontaktů trvat několik minut.)"\n\n"Chcete-li přidat kontakty, stiskněte tlačítko "<font fgcolor="#ffffffff"><b>"Menu"</b></font>" a dotkněte se položky:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Účty"</b></font>", pokud chcete přidat nebo nakonfigurovat účet a kontakty v něm synchronizovat s telefonem;"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Možnosti zobrazení"</b></font>", pokud chcete změnit, které kontakty budou zobrazeny;"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Nový kontakt"</b></font>", pokud chcete vytvořit úplně nový kontakt;"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importovat nebo exportovat"</b></font>", pokud chcete importovat kontakty z karty SD."\n</li></string>
<string name="noFavoritesHelpText" msgid="3744655776704833277">"Nemáte žádné oblíbené kontakty."\n\n"Přidání kontaktu do seznamu oblíbených:"\n\n" "<li>"Dotkněte se karty "<b>"Kontakty"</b>"."\n</li>" "\n<li>"Dotkněte se kontaktu, který chcete přidat mezi oblíbené."\n</li>" "\n<li>"Dotkněte se hvězdičky vedle jména kontaktu."\n</li></string>
<string name="liveFolder_all_label" msgid="5961411940473276616">"Všechny kontakty"</string>
<string name="liveFolder_favorites_label" msgid="2674341514070517105">"Označené hvězdičkou"</string>
@@ -179,7 +181,7 @@
<string name="callBack" msgid="5498224409038809224">"Zavolat zpět"</string>
<string name="callAgain" msgid="3197312117049874778">"Zavolat znovu"</string>
<string name="returnCall" msgid="8171961914203617813">"Zpětné volání"</string>
- <string name="callDetailsDurationFormat" msgid="8157706382818184268">"<xliff:g id="MINUTES">%1$s</xliff:g> min <xliff:g id="SECONDS">%2$s</xliff:g> s"</string>
+ <string name="callDetailsDurationFormat" msgid="8157706382818184268">"<xliff:g id="MINUTES">%s</xliff:g> min <xliff:g id="SECONDS">%s</xliff:g> s"</string>
<string name="favoritesFrquentSeparator" msgid="8107518433381283736">"Nejčastěji používané kontakty"</string>
<string name="add_contact_dlg_title" msgid="2896685845822146494">"Přidat kontakt"</string>
<string name="add_contact_dlg_message_fmt" msgid="7986472669444326576">"Chcete přidat „<xliff:g id="EMAIL">%s</xliff:g>“ do kontaktů?"</string>
@@ -217,18 +219,24 @@
<string name="scanning_sdcard_failed_title" msgid="3506782007953167180">"Vyhledávání na kartě SD se nezdařilo"</string>
<string name="scanning_sdcard_failed_message" msgid="3761992500690182922">"Vyhledávání na kartě SD se nezdařilo. (Důvod: <xliff:g id="FAIL_REASON">%s</xliff:g>)"</string>
<string name="fail_reason_io_error" msgid="5922864781066136340">"Chyba V/V"</string>
+ <string name="fail_reason_low_memory_during_import" msgid="7514918659342886381">"Není k dispozici dostatek paměti (soubor může být příliš velký)"</string>
<string name="fail_reason_vcard_parse_error" msgid="1201233722762680214">"Analýza karty vCard se z neznámého důvodu nezdařila."</string>
<string name="fail_reason_vcard_not_supported_error" msgid="655208100451286027">"Analýza karty vCard se nezdařila. Přestože se zřejmě jedná o správný formát, aktuální implementace jej nepodporuje."</string>
<string name="fail_reason_no_vcard_file" msgid="6376516175882881595">"Na kartě SD nebyl nalezen žádný soubor vCard"</string>
+ <string name="fail_reason_failed_to_collect_vcard_meta_info" msgid="4154492282316067754">"Shromáždění metainformací zadaných souborů vCard se nezdařilo."</string>
<string name="fail_reason_failed_to_read_files" msgid="3659521123567134029">"Import jednoho nebo více souborů se nezdařil (%s)."</string>
<string name="fail_reason_unknown" msgid="999034019513096768">"Neznámá chyba"</string>
<string name="select_vcard_title" msgid="3968948173786172468">"Výběr souboru vCard"</string>
- <string name="progress_shower_message" msgid="5636525578293752526">"<xliff:g id="ACTION">%1$s</xliff:g>"\n"<xliff:g id="FILENAME">%2$s</xliff:g>"</string>
- <string name="reading_vcard_title" msgid="4723433501579653199">"Čtení karty vCard"</string>
- <string name="reading_vcard_message" msgid="6381368920030748743">"Čtení souborů vCard"</string>
- <string name="reading_vcard_failed_title" msgid="4923008144735294994">"Čtení dat karty vCard se nezdařilo"</string>
- <string name="reading_vcard_contacts" msgid="3066834102042012868">"<xliff:g id="CURRENT_NUMBER">%1$s</xliff:g> z <xliff:g id="TOTAL_NUMBER">%2$s</xliff:g> kontaktů"</string>
- <string name="reading_vcard_files" msgid="34180143726972661">"<xliff:g id="CURRENT_NUMBER">%1$s</xliff:g> z <xliff:g id="TOTAL_NUMBER">%2$s</xliff:g> souborů"</string>
+ <string name="caching_vcard_title" msgid="5009556022082659780">"Načítání vizitek vCard do mezipaměti místního dočasného úložiště"</string>
+ <string name="caching_vcard_message" msgid="2380844718093378900">"Nástroj pro import načítá vizitky vCard do mezipaměti místního dočasného úložiště. Vlastní import bude zahájen v krátké době."</string>
+ <string name="progress_notifier_message" msgid="8412196553907067659">"<xliff:g id="CURRENT_NUMBER">%s</xliff:g> z <xliff:g id="TOTAL_NUMBER">%s</xliff:g>: <xliff:g id="FILENAME">%s</xliff:g>"</string>
+ <string name="reading_vcard_title" msgid="3324988724123717025">"Čtení vizitek vCard"</string>
+ <string name="reading_vcard_failed_title" msgid="2162610359561887043">"Čtení dat vizitky vCard se nezdařilo."</string>
+ <string name="reading_vcard_canceled_title" msgid="1770608329958463131">"Čtení dat vizitky vCard bylo zrušeno"</string>
+ <string name="importing_vcard_finished_title" msgid="3213257229785702182">"Import vizitky vCard byl dokončen"</string>
+ <string name="vcard_importer_start_message" msgid="5520549352987753320">"Nástroj pro import vizitky vCard byl spuštěn."</string>
+ <string name="vcard_importer_will_start_message" msgid="2055145402481822160">"Nástroj pro import vizitek vCard za okamžik importuje vizitku vCard."</string>
+ <string name="percentage" msgid="34897865327092209">"%s %%"</string>
<string name="confirm_export_title" msgid="7648747763127442983">"Potvrdit export"</string>
<string name="confirm_export_message" msgid="3875683519257829750">"Opravdu chcete exportovat seznam kontaktů do souboru <xliff:g id="VCARD_FILENAME">%s</xliff:g>?"</string>
<string name="exporting_contact_failed_title" msgid="585823094820602526">"Export dat kontaktů se nezdařil"</string>
@@ -236,6 +244,8 @@
<string name="fail_reason_no_exportable_contact" msgid="4919714086648344495">"Žádný kontakt nelze exportovat"</string>
<string name="fail_reason_too_many_vcard" msgid="7084146295639672658">"Na kartě SD je příliš mnoho souborů vCard"</string>
<string name="fail_reason_too_long_filename" msgid="1915716071321839166">"Požadovaný název souboru (<xliff:g id="FILENAME">%s</xliff:g>) je příliš dlouhý"</string>
+ <string name="vcard_exporter_start_message" msgid="6497218576519292369">"Nástroj pro export vizitky vCard byl spuštěn."</string>
+ <string name="exporting_vcard_finished_title" msgid="1268425436375550862">"Export vizitky vCard byl dokončen"</string>
<string name="exporting_contact_list_title" msgid="9072240631534457415">"Export dat kontaktů"</string>
<string name="exporting_contact_list_message" msgid="5640326540405486055">"Probíhá export dat kontaktů do souboru <xliff:g id="FILE_NAME">%s</xliff:g>"</string>
<string name="fail_reason_could_not_initialize_exporter" msgid="4943708332700987376">"Nelze spustit exportní program: <xliff:g id="EXACT_REASON">%s</xliff:g>"</string>
@@ -243,8 +253,8 @@
<string name="composer_failed_to_get_database_infomation" msgid="3723109558155169053">"Načtení informací z databáze se nezdařilo"</string>
<string name="composer_has_no_exportable_contact" msgid="754734132189369094">"Nelze exportovat žádné kontakty. Pokud máte v telefonu skutečně uložené kontakty, je možné, že některý poskytovatel datových služeb zakázal jejich export mimo telefon."</string>
<string name="composer_not_initialized" msgid="8041534450748388843">"Editor karty vCard není správně inicializován"</string>
- <string name="fail_reason_could_not_open_file" msgid="4013520943128739511">"Soubor <xliff:g id="FILE_NAME">%1$s</xliff:g> nelze otevřít: <xliff:g id="EXACT_REASON">%2$s</xliff:g>"</string>
- <string name="exporting_contact_list_progress" msgid="560522409559101193">"<xliff:g id="CURRENT_NUMBER">%1$s</xliff:g> z <xliff:g id="TOTAL_NUMBER">%2$s</xliff:g> kontaktů"</string>
+ <string name="fail_reason_could_not_open_file" msgid="4013520943128739511">"Soubor <xliff:g id="FILE_NAME">%s</xliff:g> nelze otevřít: <xliff:g id="EXACT_REASON">%s</xliff:g>"</string>
+ <string name="exporting_contact_list_progress" msgid="560522409559101193">"<xliff:g id="CURRENT_NUMBER">%s</xliff:g> z <xliff:g id="TOTAL_NUMBER">%s</xliff:g> kontaktů"</string>
<string name="search_settings_description" msgid="2675223022992445813">"Jména vašich kontaktů"</string>
<string name="add_2sec_pause" msgid="9214012315201040129">"Přidat 2s pauzu"</string>
<string name="add_wait" msgid="3360818652790319634">"Přidat čekání"</string>
@@ -254,6 +264,7 @@
<string name="quickcontact_missing_app" msgid="4600366393134289038">"Aplikace potřebná k provedení této akce nebyla nalezena"</string>
<string name="quickcontact_remember_choice" msgid="5964536411579749424">"Zapamatovat tuto volbu"</string>
<string name="quickcontact_missing_name" msgid="5590266114306996632">"Neznámé"</string>
+ <string name="quickcontact_no_data" msgid="2098000859125253675">"Žádná data"</string>
<string name="menu_accounts" msgid="8499114602017077970">"Účty"</string>
<string name="menu_import_export" msgid="3765725645491577190">"Importovat/Exportovat"</string>
<string name="dialog_import_export" msgid="4771877268244096596">"Importovat nebo exportovat kontakty"</string>
@@ -359,6 +370,7 @@
<string name="name_phonetic_family" msgid="462095502140180305">"Příjmení (foneticky)"</string>
<string name="account_type_format" msgid="718948015590343010">"Kontakt ze zdroje <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
<string name="from_account_format" msgid="687567483928582084">"z účtu <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
+ <string name="account_type_and_name" msgid="2876220035411512934">"Kontakt služby <xliff:g id="SOURCE_0">%1$s</xliff:g> od uživatele <xliff:g id="SOURCE_1">%2$s</xliff:g>"</string>
<string name="use_photo_as_primary" msgid="8807110122951157246">"Použít tuto fotografii"</string>
<string name="contact_read_only" msgid="1203216914575723978">"Kontaktní informace ze zdroje <xliff:g id="SOURCE">%1$s</xliff:g> není možné na tomto zařízení upravit."</string>
<string name="no_contact_details" msgid="6754415338321837001">"U tohoto kontaktu nejsou uvedeny dodatečné informace"</string>
@@ -374,9 +386,42 @@
<string name="pick_photo" msgid="448886509158039462">"Vybrat fotografii z galerie"</string>
<string name="locale_change_in_progress" msgid="1124266507671178413">"V souvislosti se změnou jazyka probíhá aktualizace seznamu kontaktů."\n\n"Čekejte prosím..."</string>
<string name="upgrade_in_progress" msgid="7530893673211750223">"Aktualizace seznamu kontaktů."\n\n"Čekejte prosím..."</string>
- <string name="upgrade_out_of_memory" msgid="492151063059824962">"Probíhá upgrade kontaktů. "\n\n"Upgrade vyžaduje přibližně <xliff:g id="SIZE_IN_MEGABYTES">%d</xliff:g> MB vnitřní paměti telefonu. "\n\n"Zvolte jednu z následujících možností:"</string>
+ <string name="upgrade_out_of_memory" msgid="6713914687111678777">"Probíhá upgrade kontaktů. "\n\n"Upgrade vyžaduje přibližně <xliff:g id="SIZE_IN_MEGABYTES">%d</xliff:g> MB vnitřní paměti telefonu. "\n\n"Zvolte jednu z následujících možností:"</string>
<string name="upgrade_out_of_memory_uninstall" msgid="1721798828992091432">"Odinstalovat některé aplikace"</string>
<string name="upgrade_out_of_memory_retry" msgid="8431289830472724609">"Zkusit upgradovat znovu"</string>
<string name="search_results_for" msgid="8705490885073188513">"Výsledky vyhledávání pro dotaz: <xliff:g id="QUERY">%s</xliff:g>"</string>
<string name="search_results_searching" msgid="7755623475227227314">"Vyhledávání..."</string>
+ <string name="adding_recipients" msgid="5123647646892106631">"Načítání…"</string>
+ <string name="menu_display_selected" msgid="6470001164297969034">"Zobrazit vybrané"</string>
+ <string name="menu_display_all" msgid="8887488642609786198">"Zobrazit vše"</string>
+ <string name="menu_select_all" msgid="621719255150713545">"Vybrat vše"</string>
+ <string name="menu_select_none" msgid="7093222469852132345">"Zrušit výběr všech"</string>
+ <plurals name="multiple_picker_title">
+ <item quantity="one" msgid="4761009734586319101">"Počet vybraných příjemců: 1"</item>
+ <item quantity="other" msgid="4608837420986126229">"Počet vybraných příjemců: <xliff:g id="COUNT">%d</xliff:g>"</item>
+ </plurals>
+ <string name="unknown_contacts_separator" msgid="2490675836664397214">"Neznámé kontakty"</string>
+ <string name="no_contacts_selected" msgid="5877803471037324613">"Nevybrali jste žádné kontakty."</string>
+ <string name="add_field" msgid="5257149039253569615">"Přidat informace"</string>
+ <string name="contact_status_update_attribution" msgid="752179367353018597">"pomocí služby <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
+ <string name="contact_status_update_attribution_with_date" msgid="7358045508107825068">"<xliff:g id="DATE">%1$s</xliff:g> prostřednictvím služby <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
+ <string name="description_edit" msgid="1601490950771217014">"upravit"</string>
+ <string name="description_star" msgid="2605854427360036550">"oblíbené"</string>
+ <string name="edit_contact" msgid="7529281274005689512">"Upravit kontakt"</string>
+ <plurals name="merge_info">
+ <item quantity="one" msgid="148365587896371969">"sloučení se nezdařilo"</item>
+ <item quantity="other" msgid="425683718017380845">"sloučeno ze <xliff:g id="COUNT">%0$d</xliff:g> zdrojů"</item>
+ </plurals>
+ <string name="edit_delete_rawcontact" msgid="5054644778227879148">"Smazat"</string>
+ <string name="edit_error_saving" msgid="2153949392245240166">"Chyba při ukládání"</string>
+ <string name="edit_structured_editor_button" msgid="3830078388142808106">"..."</string>
+ <string name="local_invisible_directory" msgid="6046691709127661065">"Ostatní"</string>
+ <!-- no translation found for aggregation_suggestion_title:one (7676930148670768645) -->
+ <!-- no translation found for aggregation_suggestion_title:other (3744098467672876675) -->
+ <!-- no translation found for aggregation_suggestion_joined_title (5342738708069602301) -->
+ <skip />
+ <!-- no translation found for aggregation_suggestion_join_button (7248325487963514260) -->
+ <skip />
+ <!-- no translation found for aggregation_suggestion_view_button (8232061564083962522) -->
+ <skip />
</resources>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index 6d9d0cf..712d0a8 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -31,6 +31,8 @@
<string name="viewContactTitle" msgid="7989394521836644384">"Kontaktoplysninger"</string>
<string name="viewContactDesription" msgid="214186610887547860">"Vis kontakt"</string>
<string name="editContactDescription" msgid="2947202828256214947">"Rediger kontakt"</string>
+ <string name="edit_email_caption" msgid="2401084399676154272">"E-mail-adresse:"</string>
+ <string name="edit_email_type_caption" msgid="66331218163770698">"Type: "</string>
<string name="insertContactDescription" msgid="4709878105452681987">"Opret kontakt"</string>
<string name="searchHint" msgid="8482945356247760701">"Søg i kontakter"</string>
<string name="menu_search" msgid="9147752853603483719">"Søg"</string>
@@ -123,13 +125,13 @@
</plurals>
<string name="listTotalAllContactsZero" msgid="6811347506748072822">"Ingen synlige kontakter"</string>
<plurals name="listFoundAllContacts">
- <item quantity="one" msgid="2830107332033967280">"Fandt 1 kontakt"</item>
- <item quantity="other" msgid="7752927996850263152">"Fandt <xliff:g id="COUNT">%d</xliff:g> kontaktpersoner"</item>
+ <item quantity="one" msgid="5517063038754171134">"1 fundet"</item>
+ <item quantity="other" msgid="3852668542926965042">"<xliff:g id="COUNT">%d</xliff:g> fundet"</item>
</plurals>
- <string name="listFoundAllContactsZero" msgid="5554368784319460828">"Kontakten blev ikke fundet"</string>
+ <string name="listFoundAllContactsZero" msgid="777952841930508289">"Ikke fundet"</string>
<plurals name="searchFoundContacts">
- <item quantity="one" msgid="916648718690661777">"1 kontaktperson"</item>
- <item quantity="other" msgid="5660384247071761844">"<xliff:g id="COUNT">%d</xliff:g> kontaktpersoner"</item>
+ <item quantity="one" msgid="4826918429708286628">"1 fundet"</item>
+ <item quantity="other" msgid="7988132539476575389">"<xliff:g id="COUNT">%d</xliff:g> fundet"</item>
</plurals>
<string name="contactsIconLabel" msgid="7666609097606552806">"Kontakter"</string>
<string name="contactsFavoritesLabel" msgid="8417039765586853670">"Favorit"</string>
@@ -159,10 +161,10 @@
<string name="simContacts_title" msgid="27341688347689769">"Kontakter på SIM-kort"</string>
<string name="noContactsHelpTextWithSyncForCreateShortcut" msgid="801504710275614594">"Du har ingen kontaktpersoner. (Hvis du lige har tilføjet en konto, kan det tage et par minutter at synkronisere kontaktpersoner)."</string>
<string name="noContactsHelpTextForCreateShortcut" msgid="3081286388667108335">"Du har ingen kontaktpersoner."</string>
- <string name="noContactsHelpText" msgid="6788487368878712350">"Du har ingen kontaktpersoner, der kan vises."\n\n"Hvis du vil tilføje kontaktpersoner, skal du trykke på "<font fgcolor="#ffffffff"><b>"Menu"</b></font>" og på"\n" "\n<li><font fgcolor="#ffffffff"><b>"Konti"</b></font>" for at tilføje eller konfigurere en konto med kontaktpersoner, som du kan synkronisere med telefonen"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Ny kontaktperson"</b></font>" for at oprette en ny kontaktperson helt fra bunden"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importer/eksporter"</b></font>\n</li></string>
- <string name="noContactsHelpTextWithSync" msgid="3734101165712848179">"Du har ingen kontaktpersoner, der kan vises. (Hvis du lige har tilføjet en konto, kan det tage et par minutter at synkronisere kontaktpersoner)."\n\n"Hvis du vil tilføje kontaktpersoner, skal du trykke på "<font fgcolor="#ffffffff"><b>"Menu"</b></font>" og på"\n" "\n<li><font fgcolor="#ffffffff"><b>"Konti"</b></font>" for at tilføje eller konfigurere en konto med kontaktpersoner, du kan synkronisere til telefonen"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Visningsindstillinger"</b></font>" for at ændre, hvilke kontaktpersoner der er synlige"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Ny kontaktperson"</b></font>" for at oprette en ny kontaktperson fra bunden"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importer/eksporter"</b></font>\n</li></string>
- <string name="noContactsNoSimHelpText" msgid="6553845386917463292">"Du har ingen kontaktpersoner, der kan vises."\n\n"Hvis du vil tilføje kontaktpersoner, skal du trykke på "<font fgcolor="#ffffffff"><b>"Menu"</b></font>" og på"\n" "\n<li><font fgcolor="#ffffffff"><b>"Konti"</b></font>" for at tilføje eller konfigurere en konto med kontaktpersoner, du kan synkronisere med telefonen"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Ny kontaktperson"</b></font>" for at oprette en ny kontaktperson fra bunden"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importer/eksporter"</b></font>\n</li></string>
- <string name="noContactsNoSimHelpTextWithSync" msgid="1122296298361373488">"Du har ingen kontaktpersoner, der kan vises. (Hvis du lige har tilføjet en konto, kan det tage et par minutter at synkronisere kontaktpersonerne)."\n\n"Hvis du vil tilføje kontaktpersoner, skal du trykke på "<font fgcolor="#ffffffff"><b>"Menu"</b></font>" og på"\n" "\n<li><font fgcolor="#ffffffff"><b>"Konti"</b></font>" for at tilføje eller konfigurere en konto med kontaktpersoner, du kan synkronisere til telefonen"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Visningsindstillinger"</b></font>" for at ændre, hvilke kontaktpersoner der er synlige"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Ny kontaktperson"</b></font>" for at oprette en ny kontaktperson fra bunden"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importer/eksporter"</b></font>\n</li></string>
+ <string name="noContactsHelpText" msgid="7633826236417884130">"Du har ingen kontaktpersoner."\n\n"Hvis du vil tilføje kontaktpersoner, skal du trykke på "<font fgcolor="#ffffffff"><b>"Menu"</b></font>" og "\n" "\n<li><font fgcolor="#ffffffff"><b>"Konti"</b></font>" for at tilføje eller konfigurere en konto med kontaktpersoner, som du kan synkronisere med telefonen"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Ny kontaktperson"</b></font>" for at oprette en helt ny kontaktperson"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importer/eksporter"</b></font>" for at importere kontaktpersoner fra dit SIM-kort eller SD-kort"\n</li></string>
+ <string name="noContactsHelpTextWithSync" msgid="3017521127042216243">"Du har ingen kontaktpersoner. (Hvis du lige har tilføjet en konto, kan det tage et par minutter at synkronisere kontaktpersoner)."\n\n"Hvis du vil tilføje kontaktpersoner, skal du trykke på "<font fgcolor="#ffffffff"><b>"Menu"</b></font>" og "\n" "\n<li><font fgcolor="#ffffffff"><b>"Konti"</b></font>" for at tilføje eller konfigurere en konto med kontaktpersoner, som du kan synkronisere til telefonen"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Visningsindstillinger"</b></font>" for at ændre, hvilke kontaktpersoner der er synlige"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Ny kontaktperson"</b></font>" for at oprette en helt ny kontaktperson"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importer/eksporter"</b></font>" for at importere kontaktpersoner fra dit SIM-kort eller SD.kort"\n</li></string>
+ <string name="noContactsNoSimHelpText" msgid="467658807711582876">"Du har ingen kontaktpersoner."\n\n"Hvis du vil tilføje kontaktpersoner, skal du trykke på "<font fgcolor="#ffffffff"><b>"Menu"</b></font>" og "\n" "\n<li><font fgcolor="#ffffffff"><b>"Konti"</b></font>" for at tilføje eller konfigurere en konto med kontaktpersoner, som du kan synkronisere med telefonen"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Ny kontaktperson"</b></font>" for at oprette en helt ny kontaktperson "\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importer/eksporter"</b></font>" for at importere kontaktpersoner fra dit SD-kort"\n</li></string>
+ <string name="noContactsNoSimHelpTextWithSync" msgid="9040060730467973050">"Du har ingen kontaktpersoner. (Hvis du lige har tilføjet en konto, kan det tage et par minutter at synkronisere kontaktpersoner)."\n\n"Hvis du vil tilføje kontaktpersoner, skal du trykke på "<font fgcolor="#ffffffff"><b>"Menu"</b></font>" og "\n" "\n<li><font fgcolor="#ffffffff"><b>"Konti"</b></font>" for at tilføje eller konfigurere en konto med kontaktpersoner, som du kan synkronisere til telefonen"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Visningsindstillinger"</b></font>" for at ændre, hvilke kontaktpersoner der er synlige"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Ny kontaktperson"</b></font>" for at oprette en ny kontaktperson fra bunden"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importer/eksporter"</b></font>" for at importere kontaktpersoner fra dit SD-kort"\n</li></string>
<string name="noFavoritesHelpText" msgid="3744655776704833277">"Du har ingen favoritter."\n\n"Sådan føjer du en kontaktperson til din liste over favoritter:"\n\n" "<li>"Tryk på fanen "<b>"Kontaktpersoner"</b>\n</li>" "\n<li>"Tryk på den kontaktperson, du ønsker at føje til dine foretrukne"\n</li>" "\n<li>"Tryk på stjerne ud for kontaktpersonens navn"\n</li></string>
<string name="liveFolder_all_label" msgid="5961411940473276616">"Alle kontakter"</string>
<string name="liveFolder_favorites_label" msgid="2674341514070517105">"Stjernemarkerede"</string>
@@ -179,7 +181,7 @@
<string name="callBack" msgid="5498224409038809224">"Ring tilbage"</string>
<string name="callAgain" msgid="3197312117049874778">"Ring op igen"</string>
<string name="returnCall" msgid="8171961914203617813">"Ring tilbage"</string>
- <string name="callDetailsDurationFormat" msgid="8157706382818184268">"<xliff:g id="MINUTES">%1$s</xliff:g> min. <xliff:g id="SECONDS">%2$s</xliff:g> sek."</string>
+ <string name="callDetailsDurationFormat" msgid="8157706382818184268">"<xliff:g id="MINUTES">%s</xliff:g> min. <xliff:g id="SECONDS">%s</xliff:g> sek."</string>
<string name="favoritesFrquentSeparator" msgid="8107518433381283736">"Jævnligt kontaktet"</string>
<string name="add_contact_dlg_title" msgid="2896685845822146494">"Tilføj kontakt"</string>
<string name="add_contact_dlg_message_fmt" msgid="7986472669444326576">"Føj \"<xliff:g id="EMAIL">%s</xliff:g>\" til kontaktpersoner?"</string>
@@ -217,18 +219,24 @@
<string name="scanning_sdcard_failed_title" msgid="3506782007953167180">"Scanningen af SD-kortet mislykkedes"</string>
<string name="scanning_sdcard_failed_message" msgid="3761992500690182922">"Scanningen af SD-kortet mislykkedes: (Årsag: \"<xliff:g id="FAIL_REASON">%s</xliff:g>\")"</string>
<string name="fail_reason_io_error" msgid="5922864781066136340">"I/O-fejl"</string>
+ <string name="fail_reason_low_memory_during_import" msgid="7514918659342886381">"Der er ikke nok hukommelse (filen er muligvis for stor)"</string>
<string name="fail_reason_vcard_parse_error" msgid="1201233722762680214">"vCard kunne ikke parses pga. en uventet årsag"</string>
<string name="fail_reason_vcard_not_supported_error" msgid="655208100451286027">"vCard kunne ikke parses, selv om det ser ud til at være et gyldigt format, da den nuværende implementering ikke understøtter det"</string>
<string name="fail_reason_no_vcard_file" msgid="6376516175882881595">"Der blev ikke fundet nogen VCard-fil på SD-kortet"</string>
+ <string name="fail_reason_failed_to_collect_vcard_meta_info" msgid="4154492282316067754">"Metaoplysninger om angivne vCard-filer kunne ikke hentes."</string>
<string name="fail_reason_failed_to_read_files" msgid="3659521123567134029">"En eller flere filer blev ikke importeret (%s)."</string>
<string name="fail_reason_unknown" msgid="999034019513096768">"Ukendt fejl"</string>
<string name="select_vcard_title" msgid="3968948173786172468">"Vælg vCard-fil"</string>
- <string name="progress_shower_message" msgid="5636525578293752526">"<xliff:g id="ACTION">%1$s</xliff:g>"\n"<xliff:g id="FILENAME">%2$s</xliff:g>"</string>
- <string name="reading_vcard_title" msgid="4723433501579653199">"Læser vCard"</string>
- <string name="reading_vcard_message" msgid="6381368920030748743">"Læser vCard-fil(er)"</string>
- <string name="reading_vcard_failed_title" msgid="4923008144735294994">"Læsning af vCard-data mislykkedes"</string>
- <string name="reading_vcard_contacts" msgid="3066834102042012868">"<xliff:g id="CURRENT_NUMBER">%1$s</xliff:g> af <xliff:g id="TOTAL_NUMBER">%2$s</xliff:g> kontakter"</string>
- <string name="reading_vcard_files" msgid="34180143726972661">"<xliff:g id="CURRENT_NUMBER">%1$s</xliff:g> af <xliff:g id="TOTAL_NUMBER">%2$s</xliff:g> filer"</string>
+ <string name="caching_vcard_title" msgid="5009556022082659780">"Gemmer vCard-fil(er) midlertidigt på lokal lagerplads"</string>
+ <string name="caching_vcard_message" msgid="2380844718093378900">"Importøren gemmer vCard-fil(er) midlertidigt på lokal lagerplads. Den egentlige import begynder snart."</string>
+ <string name="progress_notifier_message" msgid="8412196553907067659">"<xliff:g id="CURRENT_NUMBER">%s</xliff:g>/<xliff:g id="TOTAL_NUMBER">%s</xliff:g>: <xliff:g id="FILENAME">%s</xliff:g>"</string>
+ <string name="reading_vcard_title" msgid="3324988724123717025">"Læser vCard-fil(er)"</string>
+ <string name="reading_vcard_failed_title" msgid="2162610359561887043">"vCard-data kunne ikke læses"</string>
+ <string name="reading_vcard_canceled_title" msgid="1770608329958463131">"Læsning af vCard-data blev annulleret"</string>
+ <string name="importing_vcard_finished_title" msgid="3213257229785702182">"Import af vCard afsluttet"</string>
+ <string name="vcard_importer_start_message" msgid="5520549352987753320">"Import af vCard startet."</string>
+ <string name="vcard_importer_will_start_message" msgid="2055145402481822160">"vCard-importøren importerer vCard-filen efter et stykke tid."</string>
+ <string name="percentage" msgid="34897865327092209">"%s%%"</string>
<string name="confirm_export_title" msgid="7648747763127442983">"Bekræft eksport"</string>
<string name="confirm_export_message" msgid="3875683519257829750">"Er du sikker på, at du vil eksportere listen over kontaktpersoner til \"<xliff:g id="VCARD_FILENAME">%s</xliff:g>\"?"</string>
<string name="exporting_contact_failed_title" msgid="585823094820602526">"Eksport af kontaktdata mislykkedes"</string>
@@ -236,6 +244,8 @@
<string name="fail_reason_no_exportable_contact" msgid="4919714086648344495">"Der er ingen kontakter, der kan eksporteres"</string>
<string name="fail_reason_too_many_vcard" msgid="7084146295639672658">"Der er for mange vCard-data på SD-kortet"</string>
<string name="fail_reason_too_long_filename" msgid="1915716071321839166">"Det krævede filnavn er for langt (\"<xliff:g id="FILENAME">%s</xliff:g>\")"</string>
+ <string name="vcard_exporter_start_message" msgid="6497218576519292369">"Eksport af vCard startet."</string>
+ <string name="exporting_vcard_finished_title" msgid="1268425436375550862">"Eksport af vCard afsluttet"</string>
<string name="exporting_contact_list_title" msgid="9072240631534457415">"Eksporterer kontaktdata"</string>
<string name="exporting_contact_list_message" msgid="5640326540405486055">"Kontaktdata eksporteres til \"<xliff:g id="FILE_NAME">%s</xliff:g>\""</string>
<string name="fail_reason_could_not_initialize_exporter" msgid="4943708332700987376">"Eksportfunktionen kunne ikke startes: \"<xliff:g id="EXACT_REASON">%s</xliff:g>\""</string>
@@ -243,8 +253,8 @@
<string name="composer_failed_to_get_database_infomation" msgid="3723109558155169053">"Kunne ikke hente databaseoplysninger"</string>
<string name="composer_has_no_exportable_contact" msgid="754734132189369094">"Der er ingen kontaktpersoner, der kan eksporteres. Hvis du har kontaktpersoner på din telefon, har en dataudbyder muligvis forbudt, at kontaktpersonerne eksporteres fra telefonen."</string>
<string name="composer_not_initialized" msgid="8041534450748388843">"vCard-oprettelse ikke korrekt initialiseret"</string>
- <string name="fail_reason_could_not_open_file" msgid="4013520943128739511">"\"<xliff:g id="FILE_NAME">%1$s</xliff:g>\" kunne ikke åbnes: <xliff:g id="EXACT_REASON">%2$s</xliff:g>"</string>
- <string name="exporting_contact_list_progress" msgid="560522409559101193">"<xliff:g id="CURRENT_NUMBER">%1$s</xliff:g> af <xliff:g id="TOTAL_NUMBER">%2$s</xliff:g> kontakter"</string>
+ <string name="fail_reason_could_not_open_file" msgid="4013520943128739511">"\"<xliff:g id="FILE_NAME">%s</xliff:g>\" kunne ikke åbnes: <xliff:g id="EXACT_REASON">%s</xliff:g>"</string>
+ <string name="exporting_contact_list_progress" msgid="560522409559101193">"<xliff:g id="CURRENT_NUMBER">%s</xliff:g> af <xliff:g id="TOTAL_NUMBER">%s</xliff:g> kontakter"</string>
<string name="search_settings_description" msgid="2675223022992445813">"Navne på dine kontakter"</string>
<string name="add_2sec_pause" msgid="9214012315201040129">"Tilføj pause på 2 sek."</string>
<string name="add_wait" msgid="3360818652790319634">"Tilføj Vent"</string>
@@ -254,6 +264,7 @@
<string name="quickcontact_missing_app" msgid="4600366393134289038">"Der blev ikke fundet noget program til denne handling"</string>
<string name="quickcontact_remember_choice" msgid="5964536411579749424">"Husk dette valg"</string>
<string name="quickcontact_missing_name" msgid="5590266114306996632">"Ukendte"</string>
+ <string name="quickcontact_no_data" msgid="2098000859125253675">"Ingen data"</string>
<string name="menu_accounts" msgid="8499114602017077970">"Konti"</string>
<string name="menu_import_export" msgid="3765725645491577190">"Import/eksport"</string>
<string name="dialog_import_export" msgid="4771877268244096596">"Importer/eksporter kontakter"</string>
@@ -359,6 +370,7 @@
<string name="name_phonetic_family" msgid="462095502140180305">"Fonetisk efternavn"</string>
<string name="account_type_format" msgid="718948015590343010">"<xliff:g id="SOURCE">%1$s</xliff:g> kontaktperson"</string>
<string name="from_account_format" msgid="687567483928582084">"fra<xliff:g id="SOURCE">%1$s</xliff:g>"</string>
+ <string name="account_type_and_name" msgid="2876220035411512934">"<xliff:g id="SOURCE_0">%1$s</xliff:g> kontaktperson fra <xliff:g id="SOURCE_1">%2$s</xliff:g>"</string>
<string name="use_photo_as_primary" msgid="8807110122951157246">"Brug dette billede"</string>
<string name="contact_read_only" msgid="1203216914575723978">"<xliff:g id="SOURCE">%1$s</xliff:g> kontaktoplysninger kan ikke redigeres på denne enhed."</string>
<string name="no_contact_details" msgid="6754415338321837001">"Ingen yderligere oplysninger for denne kontakt"</string>
@@ -374,9 +386,42 @@
<string name="pick_photo" msgid="448886509158039462">"Vælg foto fra Galleri"</string>
<string name="locale_change_in_progress" msgid="1124266507671178413">"Kontaktlisten opdateres for at afspejle ændringen af sprog."\n\n"Vent et øjeblik..."</string>
<string name="upgrade_in_progress" msgid="7530893673211750223">"Listen over kontaktpersoner opdateres."\n\n"Vent et øjeblik ..."</string>
- <string name="upgrade_out_of_memory" msgid="492151063059824962">"Kontaktpersonerne er ved at blive opgraderet. "\n\n"Opgraderingen kræver ca. <xliff:g id="SIZE_IN_MEGABYTES">%d</xliff:g> MB intern lagerplads på telefonen."\n\n"Vælg et af følgende:"</string>
+ <string name="upgrade_out_of_memory" msgid="6713914687111678777">"Kontaktpersonerne er ved at blive opgraderet. "\n\n"Opgraderingen kræver ca. <xliff:g id="SIZE_IN_MEGABYTES">%d</xliff:g> MB intern lagerplads på telefonen."\n\n"Vælg et af følgende:"</string>
<string name="upgrade_out_of_memory_uninstall" msgid="1721798828992091432">"Afinstaller nogle programmer"</string>
<string name="upgrade_out_of_memory_retry" msgid="8431289830472724609">"Prøv at opgradere igen"</string>
<string name="search_results_for" msgid="8705490885073188513">"Søgeresultater for: <xliff:g id="QUERY">%s</xliff:g>"</string>
<string name="search_results_searching" msgid="7755623475227227314">"Søger ..."</string>
+ <string name="adding_recipients" msgid="5123647646892106631">"Indlæser…"</string>
+ <string name="menu_display_selected" msgid="6470001164297969034">"Vis valgte"</string>
+ <string name="menu_display_all" msgid="8887488642609786198">"Vis alle"</string>
+ <string name="menu_select_all" msgid="621719255150713545">"Vælg alle"</string>
+ <string name="menu_select_none" msgid="7093222469852132345">"Fravælg alle"</string>
+ <plurals name="multiple_picker_title">
+ <item quantity="one" msgid="4761009734586319101">"1 modtager er valgt"</item>
+ <item quantity="other" msgid="4608837420986126229">"<xliff:g id="COUNT">%d</xliff:g> modtagere er valgt"</item>
+ </plurals>
+ <string name="unknown_contacts_separator" msgid="2490675836664397214">"Ukendte kontaktpersoner"</string>
+ <string name="no_contacts_selected" msgid="5877803471037324613">"Ingen kontaktpersoner er valgt."</string>
+ <string name="add_field" msgid="5257149039253569615">"Tilføj oplysninger"</string>
+ <string name="contact_status_update_attribution" msgid="752179367353018597">"via <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
+ <string name="contact_status_update_attribution_with_date" msgid="7358045508107825068">"<xliff:g id="DATE">%1$s</xliff:g> via <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
+ <string name="description_edit" msgid="1601490950771217014">"rediger"</string>
+ <string name="description_star" msgid="2605854427360036550">"foretrukken"</string>
+ <string name="edit_contact" msgid="7529281274005689512">"Rediger kontaktperson"</string>
+ <plurals name="merge_info">
+ <item quantity="one" msgid="148365587896371969">"blev ikke flettet"</item>
+ <item quantity="other" msgid="425683718017380845">"flettet fra <xliff:g id="COUNT">%0$d</xliff:g> kilder "</item>
+ </plurals>
+ <string name="edit_delete_rawcontact" msgid="5054644778227879148">"Slet"</string>
+ <string name="edit_error_saving" msgid="2153949392245240166">"Fejl ved lagring"</string>
+ <string name="edit_structured_editor_button" msgid="3830078388142808106">"..."</string>
+ <string name="local_invisible_directory" msgid="6046691709127661065">"Andre"</string>
+ <!-- no translation found for aggregation_suggestion_title:one (7676930148670768645) -->
+ <!-- no translation found for aggregation_suggestion_title:other (3744098467672876675) -->
+ <!-- no translation found for aggregation_suggestion_joined_title (5342738708069602301) -->
+ <skip />
+ <!-- no translation found for aggregation_suggestion_join_button (7248325487963514260) -->
+ <skip />
+ <!-- no translation found for aggregation_suggestion_view_button (8232061564083962522) -->
+ <skip />
</resources>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index 42d5c14..0c6a822 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -31,6 +31,8 @@
<string name="viewContactTitle" msgid="7989394521836644384">"Kontaktinformationen"</string>
<string name="viewContactDesription" msgid="214186610887547860">"Kontakt anzeigen"</string>
<string name="editContactDescription" msgid="2947202828256214947">"Kontakt bearbeiten"</string>
+ <string name="edit_email_caption" msgid="2401084399676154272">"E-Mail-Adresse:"</string>
+ <string name="edit_email_type_caption" msgid="66331218163770698">"Typ:"</string>
<string name="insertContactDescription" msgid="4709878105452681987">"Kontakt erstellen"</string>
<string name="searchHint" msgid="8482945356247760701">"Kontakte durchsuchen"</string>
<string name="menu_search" msgid="9147752853603483719">"Suche"</string>
@@ -123,13 +125,13 @@
</plurals>
<string name="listTotalAllContactsZero" msgid="6811347506748072822">"Keine sichtbaren Kontakte"</string>
<plurals name="listFoundAllContacts">
- <item quantity="one" msgid="2830107332033967280">"1 Kontakt gefunden"</item>
- <item quantity="other" msgid="7752927996850263152">"<xliff:g id="COUNT">%d</xliff:g> Kontakte gefunden"</item>
+ <item quantity="one" msgid="5517063038754171134">"1 gefunden"</item>
+ <item quantity="other" msgid="3852668542926965042">"<xliff:g id="COUNT">%d</xliff:g> gefunden"</item>
</plurals>
- <string name="listFoundAllContactsZero" msgid="5554368784319460828">"Kein Kontakt gefunden"</string>
+ <string name="listFoundAllContactsZero" msgid="777952841930508289">"Nicht gefunden"</string>
<plurals name="searchFoundContacts">
- <item quantity="one" msgid="916648718690661777">"1 Kontakt"</item>
- <item quantity="other" msgid="5660384247071761844">"<xliff:g id="COUNT">%d</xliff:g> Kontakte"</item>
+ <item quantity="one" msgid="4826918429708286628">"1 gefunden"</item>
+ <item quantity="other" msgid="7988132539476575389">"<xliff:g id="COUNT">%d</xliff:g> gefunden"</item>
</plurals>
<string name="contactsIconLabel" msgid="7666609097606552806">"Kontakte"</string>
<string name="contactsFavoritesLabel" msgid="8417039765586853670">"Favoriten"</string>
@@ -159,10 +161,10 @@
<string name="simContacts_title" msgid="27341688347689769">"Kontakte auf SIM-Karte"</string>
<string name="noContactsHelpTextWithSyncForCreateShortcut" msgid="801504710275614594">"Sie haben keine Kontakte. Wenn Sie gerade ein Konto hinzugefügt haben, kann die Synchronisierung der Kontakte einige Minuten dauern."</string>
<string name="noContactsHelpTextForCreateShortcut" msgid="3081286388667108335">"Sie haben keine Kontakte."</string>
- <string name="noContactsHelpText" msgid="6788487368878712350">"Es sind keine Kontakte vorhanden."\n\n"Drücken Sie zum Hinzufügen von Kontakten die "<font fgcolor="#ffffffff"><b>"Menütaste"</b></font>" und tippen Sie anschließend auf "\n" "\n<li><font fgcolor="#ffffffff"><b>"Konten"</b></font>", um ein Konto mit Kontakten, die Sie mit dem Telefon synchronisieren möchten, hinzuzufügen oder zu konfigurieren."\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Neuer Kontakt"</b></font>", um einen neuen Kontakt von Grund auf zu erstellen."\n</li>\n<li><font fgcolor="#ffffffff"><b>"Importieren/Exportieren"</b></font>\n</li></string>
- <string name="noContactsHelpTextWithSync" msgid="3734101165712848179">"Es sind keine Kontakte vorhanden. Wenn Sie gerade ein Konto hinzugefügt haben, kann es einige Minuten dauern, bis die Kontakte synchronisiert sind."\n\n"Drücken Sie zum Hinzufügen von Kontakten die "<font fgcolor="#ffffffff"><b>"Menütaste"</b></font>" und tippen Sie anschließend auf "\n" "\n<li><font fgcolor="#ffffffff"><b>"Konten"</b></font>", um ein Konto mit Kontakten, die Sie mit dem Telefon synchronisieren möchten, hinzuzufügen oder zu konfigurieren."\n" "</li>\n<li><font fgcolor="#ffffffff"><b>"Anzeigeoptionen"</b></font>", um zu ändern, welche Kontakte angezeigt werden."\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Neuer Kontakt"</b></font>", um einen neuen Kontakt von Grund auf zu erstellen."\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importieren/Exportieren"</b></font>\n</li></string>
- <string name="noContactsNoSimHelpText" msgid="6553845386917463292">"Es sind keine Kontakte vorhanden."\n\n"Drücken Sie zum Hinzufügen von Kontakten die "<font fgcolor="#ffffffff"><b>"Menütaste"</b></font>" und tippen Sie anschließend auf "\n" "\n<li><font fgcolor="#ffffffff"><b>"Konten"</b></font>", um ein Konto mit Kontakten, die Sie mit dem Telefon synchronisieren möchten, hinzuzufügen oder zu konfigurieren."\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Neuer Kontakt"</b></font>", um einen neuen Kontakt von Grund auf zu erstellen."\n</li>\n<li><font fgcolor="#ffffffff"><b>"Importieren/Exportieren"</b></font>\n</li></string>
- <string name="noContactsNoSimHelpTextWithSync" msgid="1122296298361373488">"Es sind keine Kontakte vorhanden. Wenn Sie gerade ein Konto hinzugefügt haben, kann es einige Minuten dauern, bis die Kontakte synchronisiert sind."\n\n"Drücken Sie zum Hinzufügen von Kontakten die "<font fgcolor="#ffffffff"><b>"Menütaste"</b></font>" und tippen Sie anschließend auf "\n" "\n<li><font fgcolor="#ffffffff"><b>"Konten"</b></font>", um ein Konto mit Kontakten, die Sie mit dem Telefon synchronisieren möchten, hinzuzufügen oder zu konfigurieren."\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Anzeigeoptionen"</b></font>", um zu ändern, welche Kontakte angezeigt werden."\n</li>\n<li><font fgcolor="#ffffffff"><b>"Neuer Kontakt"</b></font>", um einen neuen Kontakt von Grund auf zu erstellen."\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importieren/Exportieren"</b></font>\n</li></string>
+ <string name="noContactsHelpText" msgid="7633826236417884130">"Es können keine Kontakte angezeigt werden."\n\n"Tippen Sie zum Hinzufügen von Kontakten auf "<font fgcolor="#ffffffff"><b>"Menü"</b></font>" und dann auf "\n" "\n<li><font fgcolor="#ffffffff"><b>"Konten"</b></font>", um ein Konto hinzuzufügen oder mit Kontakten für die Synchronisierung mit dem Telefon zu konfigurieren"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Neuer Kontakt"</b></font>", um einen Kontakt neu zu erstellen"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Import/Export"</b></font>", um Kontakte aus Ihrer SIM- oder SD-Karte zu importieren"\n</li></string>
+ <string name="noContactsHelpTextWithSync" msgid="3017521127042216243">"Es können keine Kontakte angezeigt werden. Wenn Sie gerade ein Konto hinzugefügt haben, kann die Kontaktsynchronisierung etwas dauern."\n\n"Zum Hinzufügen von Kontakten drücken Sie auf "<font fgcolor="#ffffffff"><b>"Menü"</b></font>" und tippen auf "\n" "\n<li><font fgcolor="#ffffffff"><b>"Konten"</b></font>", um ein Konto hinzuzufügen oder mit Kontakten für die Synchronisierung mit dem Telefon zu konfigurieren."\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Anzeigeoptionen"</b></font>", um zu ändern, welche Kontakte angezeigt werden"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Neuer Kontakt"</b></font>", um einen Kontakt neu zu erstellen"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Import/Export"</b></font>", um Kontakte aus Ihrer SIM- oder SD-Karte zu importieren"\n</li></string>
+ <string name="noContactsNoSimHelpText" msgid="467658807711582876">"Es können keine Kontakte angezeigt werden."\n\n"Tippen Sie zum Hinzufügen von Kontakten auf "<font fgcolor="#ffffffff"><b>"Menü"</b></font>" und dann auf "\n" "\n<li><font fgcolor="#ffffffff"><b>"Konten"</b></font>", um ein Konto hinzuzufügen oder mit Kontakten für die Synchronisierung mit dem Telefon zu konfigurieren."\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Neuer Kontakt"</b></font>", um einen Kontakt neu zu erstellen"\n</li>"."\n<li><font fgcolor="#ffffffff"><b>"Import/Export"</b></font>", um Kontakte aus Ihrer SD-Karte zu importieren"\n</li></string>
+ <string name="noContactsNoSimHelpTextWithSync" msgid="9040060730467973050">"Es können keine Kontakte angezeigt werden. Wenn Sie gerade ein Konto hinzugefügt haben, kann die Kontaktsynchronisierung etwas dauern."\n\n"Zum Hinzufügen von Kontakten drücken Sie auf "<font fgcolor="#ffffffff"><b>"Menü"</b></font>" und tippen dann auf "\n" "\n<li><font fgcolor="#ffffffff"><b>"Konten"</b></font>", um ein Konto hinzuzufügen oder mit Kontakten für die Synchronisierung mit dem Telefon zu konfigurieren."\n" "</li>\n<li><font fgcolor="#ffffffff"><b>"Anzeigeoptionen"</b></font>", um zu ändern, welche Kontakte angezeigt werden"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Neuer Kontakt"</b></font>", um einen Kontakt neu zu erstellen"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Import/Export"</b></font>\n</li></string>
<string name="noFavoritesHelpText" msgid="3744655776704833277">"Es sind keine Favoriten vorhanden."\n\n"So fügen Sie einen Kontakt zu Ihrer Favoritenliste hinzu:"\n\n" "<li>"Tippen Sie auf den Tab "<b>"Kontakte"</b>"."\n</li>" "\n<li>"Tippen Sie auf den Kontakt, den Sie zu Ihren Favoriten hinzufügen möchten."\n</li>" "\n<li>"Tippen Sie auf die Markierung neben dem Kontaktnamen."\n</li></string>
<string name="liveFolder_all_label" msgid="5961411940473276616">"Alle Kontakte"</string>
<string name="liveFolder_favorites_label" msgid="2674341514070517105">"Markiert"</string>
@@ -179,7 +181,7 @@
<string name="callBack" msgid="5498224409038809224">"Rückruf"</string>
<string name="callAgain" msgid="3197312117049874778">"Erneut anrufen"</string>
<string name="returnCall" msgid="8171961914203617813">"Zurückrufen"</string>
- <string name="callDetailsDurationFormat" msgid="8157706382818184268">"<xliff:g id="MINUTES">%1$s</xliff:g> Minuten, <xliff:g id="SECONDS">%2$s</xliff:g> Sekunden"</string>
+ <string name="callDetailsDurationFormat" msgid="8157706382818184268">"<xliff:g id="MINUTES">%s</xliff:g> Minuten, <xliff:g id="SECONDS">%s</xliff:g> Sekunden"</string>
<string name="favoritesFrquentSeparator" msgid="8107518433381283736">"Häufig kontaktiert"</string>
<string name="add_contact_dlg_title" msgid="2896685845822146494">"Kontakt hinzufügen"</string>
<string name="add_contact_dlg_message_fmt" msgid="7986472669444326576">"\"<xliff:g id="EMAIL">%s</xliff:g>\" zu den Kontakten hinzufügen?"</string>
@@ -217,18 +219,24 @@
<string name="scanning_sdcard_failed_title" msgid="3506782007953167180">"Fehler beim Lesen der SD-Karte"</string>
<string name="scanning_sdcard_failed_message" msgid="3761992500690182922">"Fehler beim Lesen der SD-Karte. Grund: \"<xliff:g id="FAIL_REASON">%s</xliff:g>\""</string>
<string name="fail_reason_io_error" msgid="5922864781066136340">"E/A-Fehler"</string>
+ <string name="fail_reason_low_memory_during_import" msgid="7514918659342886381">"Zu wenig Speicherplatz (Datei ist eventuell zu groß)"</string>
<string name="fail_reason_vcard_parse_error" msgid="1201233722762680214">"Die vCard konnte aus einem unbekannten Grund nicht geparst werden."</string>
<string name="fail_reason_vcard_not_supported_error" msgid="655208100451286027">"Beim Parsen der vCard ist ein Fehler aufgetreten. Obwohl die vCard anscheinend ein gültiges Format aufweist, wird sie von der aktuellen Implementierung nicht unterstützt."</string>
<string name="fail_reason_no_vcard_file" msgid="6376516175882881595">"Keine VCard-Datei auf der SD-Karte gefunden"</string>
+ <string name="fail_reason_failed_to_collect_vcard_meta_info" msgid="4154492282316067754">"Metainformationen konnten nicht von den angegebenen vCards abgerufen werden."</string>
<string name="fail_reason_failed_to_read_files" msgid="3659521123567134029">"Fehler beim Import einer oder mehrerer Dateien (%s)"</string>
<string name="fail_reason_unknown" msgid="999034019513096768">"Unbekannter Fehler"</string>
<string name="select_vcard_title" msgid="3968948173786172468">"vCard-Datei auswählen"</string>
- <string name="progress_shower_message" msgid="5636525578293752526">"<xliff:g id="ACTION">%1$s</xliff:g>"\n"<xliff:g id="FILENAME">%2$s</xliff:g>"</string>
- <string name="reading_vcard_title" msgid="4723433501579653199">"vCard wird gelesen."</string>
- <string name="reading_vcard_message" msgid="6381368920030748743">"vCard-Dateien werden gelesen."</string>
- <string name="reading_vcard_failed_title" msgid="4923008144735294994">"Fehler beim Lesen der vCard-Daten"</string>
- <string name="reading_vcard_contacts" msgid="3066834102042012868">"<xliff:g id="CURRENT_NUMBER">%1$s</xliff:g> von <xliff:g id="TOTAL_NUMBER">%2$s</xliff:g> Kontakten"</string>
- <string name="reading_vcard_files" msgid="34180143726972661">"<xliff:g id="CURRENT_NUMBER">%1$s</xliff:g> von <xliff:g id="TOTAL_NUMBER">%2$s</xliff:g> Dateien"</string>
+ <string name="caching_vcard_title" msgid="5009556022082659780">"Caching von vCards in lokalen temporären Speicher"</string>
+ <string name="caching_vcard_message" msgid="2380844718093378900">"Importprogramm führt gerade Caching der vCard(s) in lokalen temporären Speicher durch. Der eigentliche Import beginnt gleich."</string>
+ <string name="progress_notifier_message" msgid="8412196553907067659">"<xliff:g id="CURRENT_NUMBER">%s</xliff:g>/<xliff:g id="TOTAL_NUMBER">%s</xliff:g>: <xliff:g id="FILENAME">%s</xliff:g>"</string>
+ <string name="reading_vcard_title" msgid="3324988724123717025">"vCards werden gelesen"</string>
+ <string name="reading_vcard_failed_title" msgid="2162610359561887043">"Fehler beim Lesen von vCard-Daten"</string>
+ <string name="reading_vcard_canceled_title" msgid="1770608329958463131">"Lesen von vCard-Daten abgebrochen"</string>
+ <string name="importing_vcard_finished_title" msgid="3213257229785702182">"Import der vCard abgeschlossen"</string>
+ <string name="vcard_importer_start_message" msgid="5520549352987753320">"vCard-Importprogramm gestartet"</string>
+ <string name="vcard_importer_will_start_message" msgid="2055145402481822160">"Das vCard-Importprogramm importiert nach kurzer Zeit die vCard."</string>
+ <string name="percentage" msgid="34897865327092209">"%s %%"</string>
<string name="confirm_export_title" msgid="7648747763127442983">"Export bestätigen"</string>
<string name="confirm_export_message" msgid="3875683519257829750">"Soll Ihre Kontaktliste wirklich an \"<xliff:g id="VCARD_FILENAME">%s</xliff:g>\" exportiert werden?"</string>
<string name="exporting_contact_failed_title" msgid="585823094820602526">"Fehler beim Exportieren von Kontaktdaten"</string>
@@ -236,6 +244,8 @@
<string name="fail_reason_no_exportable_contact" msgid="4919714086648344495">"Es ist kein exportierbarer Kontakt vorhanden"</string>
<string name="fail_reason_too_many_vcard" msgid="7084146295639672658">"Zu viele vCard-Dateien auf der SD-Karte"</string>
<string name="fail_reason_too_long_filename" msgid="1915716071321839166">"Erforderlicher Dateiname ist zu lang (\"<xliff:g id="FILENAME">%s</xliff:g>\")"</string>
+ <string name="vcard_exporter_start_message" msgid="6497218576519292369">"vCard-Exportprogramm gestartet"</string>
+ <string name="exporting_vcard_finished_title" msgid="1268425436375550862">"Export der vCard abgeschlossen"</string>
<string name="exporting_contact_list_title" msgid="9072240631534457415">"Kontaktdaten werden exportiert."</string>
<string name="exporting_contact_list_message" msgid="5640326540405486055">"Kontaktdaten werden in \"<xliff:g id="FILE_NAME">%s</xliff:g>\" exportiert."</string>
<string name="fail_reason_could_not_initialize_exporter" msgid="4943708332700987376">"Exportprogramm konnte nicht initialisiert werden: \"<xliff:g id="EXACT_REASON">%s</xliff:g>\""</string>
@@ -243,8 +253,8 @@
<string name="composer_failed_to_get_database_infomation" msgid="3723109558155169053">"Fehler bei der Ermittlung von Datenbank-Informationen"</string>
<string name="composer_has_no_exportable_contact" msgid="754734132189369094">"Es ist kein exportierbarer Kontakt vorhanden. Falls sich Kontakte auf Ihrem Telefon befinden, ist das Exportieren der Kontakte möglicherweise durch einen Datenanbieter gesperrt."</string>
<string name="composer_not_initialized" msgid="8041534450748388843">"Das Programm zum Erstellen der vCard wurde nicht richtig initialisiert."</string>
- <string name="fail_reason_could_not_open_file" msgid="4013520943128739511">"\"<xliff:g id="FILE_NAME">%1$s</xliff:g>\" konnte nicht geöffnet werden: <xliff:g id="EXACT_REASON">%2$s</xliff:g>"</string>
- <string name="exporting_contact_list_progress" msgid="560522409559101193">"<xliff:g id="CURRENT_NUMBER">%1$s</xliff:g> von <xliff:g id="TOTAL_NUMBER">%2$s</xliff:g> Kontakten"</string>
+ <string name="fail_reason_could_not_open_file" msgid="4013520943128739511">"\"<xliff:g id="FILE_NAME">%s</xliff:g>\" konnte nicht geöffnet werden: <xliff:g id="EXACT_REASON">%s</xliff:g>"</string>
+ <string name="exporting_contact_list_progress" msgid="560522409559101193">"<xliff:g id="CURRENT_NUMBER">%s</xliff:g> von <xliff:g id="TOTAL_NUMBER">%s</xliff:g> Kontakten"</string>
<string name="search_settings_description" msgid="2675223022992445813">"Namen meiner Kontakte"</string>
<string name="add_2sec_pause" msgid="9214012315201040129">"2 Sekunden Pause hinzufügen"</string>
<string name="add_wait" msgid="3360818652790319634">"Warten hinzufügen"</string>
@@ -254,6 +264,7 @@
<string name="quickcontact_missing_app" msgid="4600366393134289038">"Für diese Aktion wurde keine Anwendung gefunden."</string>
<string name="quickcontact_remember_choice" msgid="5964536411579749424">"Diese Auswahl speichern"</string>
<string name="quickcontact_missing_name" msgid="5590266114306996632">"Unbekannt"</string>
+ <string name="quickcontact_no_data" msgid="2098000859125253675">"Keine Daten"</string>
<string name="menu_accounts" msgid="8499114602017077970">"Konten"</string>
<string name="menu_import_export" msgid="3765725645491577190">"Importieren/Exportieren"</string>
<string name="dialog_import_export" msgid="4771877268244096596">"Kontakte importieren/exportieren"</string>
@@ -359,6 +370,7 @@
<string name="name_phonetic_family" msgid="462095502140180305">"Phonetischer Nachname"</string>
<string name="account_type_format" msgid="718948015590343010">"<xliff:g id="SOURCE">%1$s</xliff:g>-Kontakt"</string>
<string name="from_account_format" msgid="687567483928582084">"von <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
+ <string name="account_type_and_name" msgid="2876220035411512934">"<xliff:g id="SOURCE_0">%1$s</xliff:g>-Kontakt aus <xliff:g id="SOURCE_1">%2$s</xliff:g>"</string>
<string name="use_photo_as_primary" msgid="8807110122951157246">"Dieses Foto verwenden"</string>
<string name="contact_read_only" msgid="1203216914575723978">"<xliff:g id="SOURCE">%1$s</xliff:g>-Kontaktinformationen können auf diesem Gerät nicht bearbeitet werden."</string>
<string name="no_contact_details" msgid="6754415338321837001">"Keine Zusatzinformationen zu diesem Kontakt"</string>
@@ -374,9 +386,42 @@
<string name="pick_photo" msgid="448886509158039462">"Foto aus Galerie auswählen"</string>
<string name="locale_change_in_progress" msgid="1124266507671178413">"Die Kontaktliste wird an die geänderte Sprache angepasst."\n\n"Bitte warten..."</string>
<string name="upgrade_in_progress" msgid="7530893673211750223">"Die Kontaktliste wird aktualisiert."\n\n"Bitte warten..."</string>
- <string name="upgrade_out_of_memory" msgid="492151063059824962">"Kontakte werden gerade aktualisiert. "\n\n"Das Upgrade erfordert etwa <xliff:g id="SIZE_IN_MEGABYTES">%d</xliff:g>MB des internen Telefonspeichers."\n\n"Wählen Sie eine der folgenden Optionen:"</string>
+ <string name="upgrade_out_of_memory" msgid="6713914687111678777">"Kontakte werden gerade aktualisiert. "\n\n"Das Upgrade erfordert etwa <xliff:g id="SIZE_IN_MEGABYTES">%d</xliff:g> MB des internen Telefonspeichers."\n\n"Wählen Sie eine der folgenden Optionen:"</string>
<string name="upgrade_out_of_memory_uninstall" msgid="1721798828992091432">"Einige Anwendungen deinstallieren"</string>
<string name="upgrade_out_of_memory_retry" msgid="8431289830472724609">"Upgrade wiederholen"</string>
<string name="search_results_for" msgid="8705490885073188513">"Suchergebnisse für: <xliff:g id="QUERY">%s</xliff:g>"</string>
<string name="search_results_searching" msgid="7755623475227227314">"Suche..."</string>
+ <string name="adding_recipients" msgid="5123647646892106631">"Wird geladen..."</string>
+ <string name="menu_display_selected" msgid="6470001164297969034">"Auswahl anzeigen"</string>
+ <string name="menu_display_all" msgid="8887488642609786198">"Alle anzeigen"</string>
+ <string name="menu_select_all" msgid="621719255150713545">"Alles auswählen"</string>
+ <string name="menu_select_none" msgid="7093222469852132345">"Auswahl für alle aufheben"</string>
+ <plurals name="multiple_picker_title">
+ <item quantity="one" msgid="4761009734586319101">"Ein Empfänger ausgewählt"</item>
+ <item quantity="other" msgid="4608837420986126229">"<xliff:g id="COUNT">%d</xliff:g> Empfänger ausgewählt"</item>
+ </plurals>
+ <string name="unknown_contacts_separator" msgid="2490675836664397214">"Unbekannte Kontakte"</string>
+ <string name="no_contacts_selected" msgid="5877803471037324613">"Kein Kontakt ausgewählt"</string>
+ <string name="add_field" msgid="5257149039253569615">"Informationen hinzufügen"</string>
+ <string name="contact_status_update_attribution" msgid="752179367353018597">"über <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
+ <string name="contact_status_update_attribution_with_date" msgid="7358045508107825068">"<xliff:g id="DATE">%1$s</xliff:g> über <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
+ <string name="description_edit" msgid="1601490950771217014">"bearbeiten"</string>
+ <string name="description_star" msgid="2605854427360036550">"Favorit"</string>
+ <string name="edit_contact" msgid="7529281274005689512">"Kontakt bearbeiten"</string>
+ <plurals name="merge_info">
+ <item quantity="one" msgid="148365587896371969">"nicht zusammengeführt"</item>
+ <item quantity="other" msgid="425683718017380845">"aus <xliff:g id="COUNT">%0$d</xliff:g> Quellen zusammengeführt"</item>
+ </plurals>
+ <string name="edit_delete_rawcontact" msgid="5054644778227879148">"Löschen"</string>
+ <string name="edit_error_saving" msgid="2153949392245240166">"Fehler beim Speichern"</string>
+ <string name="edit_structured_editor_button" msgid="3830078388142808106">"..."</string>
+ <string name="local_invisible_directory" msgid="6046691709127661065">"Andere"</string>
+ <!-- no translation found for aggregation_suggestion_title:one (7676930148670768645) -->
+ <!-- no translation found for aggregation_suggestion_title:other (3744098467672876675) -->
+ <!-- no translation found for aggregation_suggestion_joined_title (5342738708069602301) -->
+ <skip />
+ <!-- no translation found for aggregation_suggestion_join_button (7248325487963514260) -->
+ <skip />
+ <!-- no translation found for aggregation_suggestion_view_button (8232061564083962522) -->
+ <skip />
</resources>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index 56cf27a1..f16f976 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -31,6 +31,8 @@
<string name="viewContactTitle" msgid="7989394521836644384">"Λεπτομέρειες επαφής"</string>
<string name="viewContactDesription" msgid="214186610887547860">"Προβολή επαφής"</string>
<string name="editContactDescription" msgid="2947202828256214947">"Επεξεργασία επαφής"</string>
+ <string name="edit_email_caption" msgid="2401084399676154272">"Διεύθυνση ηλεκτρονικού ταχυδρομείου:"</string>
+ <string name="edit_email_type_caption" msgid="66331218163770698">"Τύπος:"</string>
<string name="insertContactDescription" msgid="4709878105452681987">"Δημιουργία επαφής"</string>
<string name="searchHint" msgid="8482945356247760701">"Αναζήτηση επαφών"</string>
<string name="menu_search" msgid="9147752853603483719">"Αναζήτηση"</string>
@@ -123,13 +125,13 @@
</plurals>
<string name="listTotalAllContactsZero" msgid="6811347506748072822">"Δεν υπάρχουν ορατές επαφές"</string>
<plurals name="listFoundAllContacts">
- <item quantity="one" msgid="2830107332033967280">"Βρέθηκε 1 επαφή"</item>
- <item quantity="other" msgid="7752927996850263152">"Βρέθηκαν <xliff:g id="COUNT">%d</xliff:g> επαφές"</item>
+ <item quantity="one" msgid="5517063038754171134">"Βρέθηκε 1"</item>
+ <item quantity="other" msgid="3852668542926965042">"Βρέθηκαν <xliff:g id="COUNT">%d</xliff:g>"</item>
</plurals>
- <string name="listFoundAllContactsZero" msgid="5554368784319460828">"Δεν βρέθηκε η επαφή"</string>
+ <string name="listFoundAllContactsZero" msgid="777952841930508289">"Δεν βρέθηκε"</string>
<plurals name="searchFoundContacts">
- <item quantity="one" msgid="916648718690661777">"1 επαφή"</item>
- <item quantity="other" msgid="5660384247071761844">"<xliff:g id="COUNT">%d</xliff:g> επαφές"</item>
+ <item quantity="one" msgid="4826918429708286628">"Βρέθηκε 1"</item>
+ <item quantity="other" msgid="7988132539476575389">"Βρέθηκαν <xliff:g id="COUNT">%d</xliff:g>"</item>
</plurals>
<string name="contactsIconLabel" msgid="7666609097606552806">"Επαφές"</string>
<string name="contactsFavoritesLabel" msgid="8417039765586853670">"Αγαπ."</string>
@@ -159,10 +161,10 @@
<string name="simContacts_title" msgid="27341688347689769">"Επαφές στην κάρτα SIM"</string>
<string name="noContactsHelpTextWithSyncForCreateShortcut" msgid="801504710275614594">"Δεν υπάρχουν επαφές για προβολή. (Εάν προσθέσατε τώρα ένα λογαριασμό, ο συγχρονισμός των επαφών μπορεί να καθυστερήσει μερικά λεπτά.)"</string>
<string name="noContactsHelpTextForCreateShortcut" msgid="3081286388667108335">"Δεν υπάρχουν επαφές για προβολή."</string>
- <string name="noContactsHelpText" msgid="6788487368878712350">"Δεν έχετε επαφές προς προβολή."\n\n"Για προσθήκη επαφών, πατήστε "<font fgcolor="#ffffffff"><b>"Μενού"</b></font>" και αγγίξτε:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Λογαριασμοί"</b></font>" για προσθήκη ή διαμόρφωση λογαριασμού με επαφές που μπορείτε να συγχρονίσετε στο τηλέφωνο"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Νέα επαφή"</b></font>" για δημιουργία νέας επαφής από το μηδέν"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Εισαγωγή/Εξαγωγή"</b></font>\n</li></string>
- <string name="noContactsHelpTextWithSync" msgid="3734101165712848179">"Δεν έχετε επαφές προς προβολή. (Αν προσθέσατε κάποιον λογαριασμό μόλις τώρα, θα χρειαστούν λίγα λεπτά έως ότου γίνει ο συγχρονισμός.)"\n\n"Για την προσθήκη επαφών, πατήστε "<font fgcolor="#ffffffff"><b>"Μενού"</b></font>" και αγγίξτε την επιλογή:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Λογαριασμοί"</b></font>" για την προσθήκη ή διαμόρφωση λογαριασμού με επαφές τις οποίες μπορείτε να συγχρονίσετε στο τηλέφωνο"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Επιλογές προβολής"</b></font>" για να αλλάξετε τις επαφές που είναι ορατές"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Νέα επαφή"</b></font>" για δημιουργία νέας επαφής από το μηδέν"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Εισαγωγή/Εξαγωγή"</b></font>\n</li></string>
- <string name="noContactsNoSimHelpText" msgid="6553845386917463292">"Δεν έχετε επαφές προς προβολή."\n\n"Για προσθήκη επαφών, πατήστε "<font fgcolor="#ffffffff"><b>"Μενού"</b></font>" και αγγίξτε την επιλογή:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Λογαριασμοί"</b></font>" για προσθήκη ή διαμόρφωση λογαριασμού με επαφές που μπορείτε να συγχρονίσετε στο τηλέφωνο"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Νέα επαφή"</b></font>" για δημιουργία νέας επαφής από το μηδέν"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Εισαγωγή/Εξαγωγή"</b></font>\n</li></string>
- <string name="noContactsNoSimHelpTextWithSync" msgid="1122296298361373488">"Δεν έχετε επαφές προς προβολή. (Αν προσθέσατε κάποιον λογαριασμό μόλις τώρα, θα χρειαστούν λίγα λεπτά έως ότου γίνει ο συγχρονισμός.)"\n\n"Για την προσθήκη επαφών, πατήστε "<font fgcolor="#ffffffff"><b>"Μενού"</b></font>" και αγγίξτε την επιλογή:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Λογαριασμοί"</b></font>" για την προσθήκη ή διαμόρφωση λογαριασμού με επαφές τις οποίες μπορείτε να συγχρονίσετε στο τηλέφωνο"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Προβολή επιλογών"</b></font>" για να αλλάξετε τις επαφές που είναι ορατές"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Νέα επαφή"</b></font>" για δημιουργία νέας επαφής από το μηδέν"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Εισαγωγή/Εξαγωγή"</b></font>\n</li></string>
+ <string name="noContactsHelpText" msgid="7633826236417884130">"Δεν έχετε επαφές προς προβολή."\n\n"Για προσθήκη επαφών, πατήστε "<font fgcolor="#ffffffff"><b>"Μενού"</b></font>" και αγγίξτε την επιλογή:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Λογαριασμοί"</b></font>" για προσθήκη ή διαμόρφωση λογαριασμού με επαφές που μπορείτε να συγχρονίσετε στο τηλέφωνο"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Νέα επαφή"</b></font>" για δημιουργία νέας επαφής από το μηδέν"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Εισαγωγή/Εξαγωγή"</b></font>" για την εισαγωγή επαφών από την κάρτα SIM ή SD"\n</li></string>
+ <string name="noContactsHelpTextWithSync" msgid="3017521127042216243">"Δεν έχετε επαφές προς προβολή. (Αν προσθέσατε κάποιο λογαριασμό μόλις τώρα, θα χρειαστούν λίγα λεπτά έως ότου γίνει ο συγχρονισμός.)"\n\n"Για την προσθήκη επαφών, πατήστε "<font fgcolor="#ffffffff"><b>"Μενού"</b></font>" και αγγίξτε την επιλογή:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Λογαριασμοί"</b></font>" για την προσθήκη ή διαμόρφωση λογαριασμού με επαφές τις οποίες μπορείτε να συγχρονίσετε στο τηλέφωνο"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Προβολή επιλογών"</b></font>" για να αλλάξετε τις επαφές που είναι ορατές"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Νέα επαφή"</b></font>" για δημιουργία νέας επαφής από το μηδέν"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Εισαγωγή/Εξαγωγή"</b></font>" για την εισαγωγή επαφών από την κάρτα SIM ή SD"\n</li></string>
+ <string name="noContactsNoSimHelpText" msgid="467658807711582876">"Δεν έχετε επαφές προς προβολή."\n\n"Για προσθήκη επαφών, πατήστε "<font fgcolor="#ffffffff"><b>"Μενού"</b></font>" και αγγίξτε την επιλογή:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Λογαριασμοί"</b></font>" για προσθήκη ή διαμόρφωση λογαριασμού με επαφές που μπορείτε να συγχρονίσετε στο τηλέφωνο"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Νέα επαφή"</b></font>" για δημιουργία νέας επαφής από το μηδέν"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Εισαγωγή/Εξαγωγή"</b></font>" για την εισαγωγή επαφών από την κάρτα SD"\n</li></string>
+ <string name="noContactsNoSimHelpTextWithSync" msgid="9040060730467973050">"Δεν έχετε επαφές προς προβολή. (Αν προσθέσατε κάποιον λογαριασμό μόλις τώρα, θα χρειαστούν λίγα λεπτά έως ότου γίνει ο συγχρονισμός.)"\n\n"Για την προσθήκη επαφών, πατήστε "<font fgcolor="#ffffffff"><b>"Μενού"</b></font>" και αγγίξτε την επιλογή:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Λογαριασμοί"</b></font>" για την προσθήκη ή διαμόρφωση λογαριασμού με επαφές τις οποίες μπορείτε να συγχρονίσετε στο τηλέφωνο"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Προβολή επιλογών"</b></font>" για να αλλάξετε τις επαφές που είναι ορατές"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Νέα επαφή"</b></font>" για δημιουργία νέας επαφής από το μηδέν"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Εισαγωγή/Εξαγωγή"</b></font>" για την εισαγωγή επαφών από την κάρτα SD"\n</li></string>
<string name="noFavoritesHelpText" msgid="3744655776704833277">"Δεν έχετε αγαπημένα."\n\n"Για προσθήκη επαφής στη λίστα των αγαπημένων σας:"\n\n" "<li>"Αγγίξτε την καρτέλα "<b>"Επαφές"</b>\n</li>" "\n<li>"Αγγίξτε το όνομα της επαφής που θέλετε να προσθέσετε στα αγαπημένα σας"\n</li>" "\n<li>"Αγγίξτε το αστέρι πλάι στο όνομα της επαφής"\n</li></string>
<string name="liveFolder_all_label" msgid="5961411940473276616">"Όλες οι επαφές"</string>
<string name="liveFolder_favorites_label" msgid="2674341514070517105">"Με αστέρι"</string>
@@ -179,7 +181,7 @@
<string name="callBack" msgid="5498224409038809224">"Επανάκληση"</string>
<string name="callAgain" msgid="3197312117049874778">"Επανάληψη κλήσης"</string>
<string name="returnCall" msgid="8171961914203617813">"Επιστροφή κλήσης"</string>
- <string name="callDetailsDurationFormat" msgid="8157706382818184268">"<xliff:g id="MINUTES">%1$s</xliff:g> λεπτά <xliff:g id="SECONDS">%2$s</xliff:g> δευτερόλεπτα"</string>
+ <string name="callDetailsDurationFormat" msgid="8157706382818184268">"<xliff:g id="MINUTES">%s</xliff:g> λεπτά <xliff:g id="SECONDS">%s</xliff:g> δευτερόλεπτα"</string>
<string name="favoritesFrquentSeparator" msgid="8107518433381283736">"Επαφές με τις οποίες έχετε συχνή επικοινωνία"</string>
<string name="add_contact_dlg_title" msgid="2896685845822146494">"Προσθήκη επαφής"</string>
<string name="add_contact_dlg_message_fmt" msgid="7986472669444326576">"Προσθήκη του \"<xliff:g id="EMAIL">%s</xliff:g>\" στις επαφές?"</string>
@@ -217,18 +219,24 @@
<string name="scanning_sdcard_failed_title" msgid="3506782007953167180">"Η σάρωση της κάρτας SD απέτυχε"</string>
<string name="scanning_sdcard_failed_message" msgid="3761992500690182922">"Η σάρωση της κάρτας SD απέτυχε (Αιτία:\"<xliff:g id="FAIL_REASON">%s</xliff:g>\")"</string>
<string name="fail_reason_io_error" msgid="5922864781066136340">"Σφάλμα I/O"</string>
+ <string name="fail_reason_low_memory_during_import" msgid="7514918659342886381">"Η μνήμη είναι ανεπαρκής (το αρχείο μπορεί να είναι πολύ μεγάλο)"</string>
<string name="fail_reason_vcard_parse_error" msgid="1201233722762680214">"Η ανάλυση της κάρτας vCard απέτυχε εξαιτίας μη αναμενόμενου συμβάντος"</string>
<string name="fail_reason_vcard_not_supported_error" msgid="655208100451286027">"Αν και το VCard φαίνεται να βρίσκεται σε έγκυρη μορφή, η ανάλυσή του απέτυχε, εφόσον η τρέχουσα εφαρμογή δεν το υποστηρίζει"</string>
<string name="fail_reason_no_vcard_file" msgid="6376516175882881595">"Δεν βρέθηκε αρχείο VCard στην κάρτα SD"</string>
+ <string name="fail_reason_failed_to_collect_vcard_meta_info" msgid="4154492282316067754">"Η συλλογή μετα-πληροφοριών του δεδομένου αρχείου(ων) vCard απέτυχε."</string>
<string name="fail_reason_failed_to_read_files" msgid="3659521123567134029">"Η εισαγωγή ενός ή περισσοτέρων αρχείων απέτυχε (%s)."</string>
<string name="fail_reason_unknown" msgid="999034019513096768">"Άγνωστο σφάλμα"</string>
<string name="select_vcard_title" msgid="3968948173786172468">"Επιλογή αρχείου vCard"</string>
- <string name="progress_shower_message" msgid="5636525578293752526">"<xliff:g id="ACTION">%1$s</xliff:g>"\n"<xliff:g id="FILENAME">%2$s</xliff:g>"</string>
- <string name="reading_vcard_title" msgid="4723433501579653199">"Ανάγνωση vCard"</string>
- <string name="reading_vcard_message" msgid="6381368920030748743">"Ανάγνωση αρχείου(ων) vCard"</string>
- <string name="reading_vcard_failed_title" msgid="4923008144735294994">"Η ανάγνωση των δεδομένων vCard απέτυχε"</string>
- <string name="reading_vcard_contacts" msgid="3066834102042012868">"<xliff:g id="CURRENT_NUMBER">%1$s</xliff:g> από <xliff:g id="TOTAL_NUMBER">%2$s</xliff:g> επαφές"</string>
- <string name="reading_vcard_files" msgid="34180143726972661">"<xliff:g id="CURRENT_NUMBER">%1$s</xliff:g> από <xliff:g id="TOTAL_NUMBER">%2$s</xliff:g> αρχεία"</string>
+ <string name="caching_vcard_title" msgid="5009556022082659780">"Αποθήκευση vCard(s) σε τοπικό προσωρινό χώρο αποθήκευσης"</string>
+ <string name="caching_vcard_message" msgid="2380844718093378900">"Το πρόγραμμα εισαγωγής αποθηκεύει προσωρινά vCard(s) σε τοπικό χώρο προσωρινής αποθήκευσης. Η κανονική εισαγωγή θα ξεκινήσει σύντομα."</string>
+ <string name="progress_notifier_message" msgid="8412196553907067659">"<xliff:g id="CURRENT_NUMBER">%s</xliff:g>/<xliff:g id="TOTAL_NUMBER">%s</xliff:g>: <xliff:g id="FILENAME">%s</xliff:g>"</string>
+ <string name="reading_vcard_title" msgid="3324988724123717025">"Ανάγνωση vCard(s)"</string>
+ <string name="reading_vcard_failed_title" msgid="2162610359561887043">"Αποτυχία ανάγνωσης δεδομένων vCard"</string>
+ <string name="reading_vcard_canceled_title" msgid="1770608329958463131">"Η ανάγνωση των δεδομένων vCard ακυρώθηκε"</string>
+ <string name="importing_vcard_finished_title" msgid="3213257229785702182">"Η εισαγωγή vCard ολοκληρώθηκε"</string>
+ <string name="vcard_importer_start_message" msgid="5520549352987753320">"Έγινε εκκίνηση του προγράμματος εισαγωγής vCard."</string>
+ <string name="vcard_importer_will_start_message" msgid="2055145402481822160">"Το πρόγραμμα εισαγωγής vCard θα εισαγάγει την vCard μετά από λίγο."</string>
+ <string name="percentage" msgid="34897865327092209">"%s%%"</string>
<string name="confirm_export_title" msgid="7648747763127442983">"Επιβεβαίωση\nεξαγωγής"</string>
<string name="confirm_export_message" msgid="3875683519257829750">"Είστε σίγουροι ότι θέλετε να εξαγάγετε τη λίστα των επαφών σας στο \"<xliff:g id="VCARD_FILENAME">%s</xliff:g>\";"</string>
<string name="exporting_contact_failed_title" msgid="585823094820602526">"Αποτυχία εξαγωγής δεδομένων επαφής"</string>
@@ -236,6 +244,8 @@
<string name="fail_reason_no_exportable_contact" msgid="4919714086648344495">"Δεν υπάρχει επαφή με δυνατότητα εξαγωγής"</string>
<string name="fail_reason_too_many_vcard" msgid="7084146295639672658">"Υπερβολικά μεγάλος όγκος αρχείων VCard στην κάρτα SD"</string>
<string name="fail_reason_too_long_filename" msgid="1915716071321839166">"Το απαιτούμενο όνομα αρχείου είναι υπερβολικά μεγάλο (\"<xliff:g id="FILENAME">%s</xliff:g>\")"</string>
+ <string name="vcard_exporter_start_message" msgid="6497218576519292369">"Εκκινήθηκε το πρόγραμμα εξαγωγής vCard."</string>
+ <string name="exporting_vcard_finished_title" msgid="1268425436375550862">"Η εξαγωγή της vCard ολοκληρώθηκε"</string>
<string name="exporting_contact_list_title" msgid="9072240631534457415">"Εξαγωγή δεδομένων επαφών"</string>
<string name="exporting_contact_list_message" msgid="5640326540405486055">"Εξαγωγή δεδομένων επαφών προς \"<xliff:g id="FILE_NAME">%s</xliff:g>\""</string>
<string name="fail_reason_could_not_initialize_exporter" msgid="4943708332700987376">"Δεν ήταν δυνατή η εκκίνηση της εξαγωγής: \"<xliff:g id="EXACT_REASON">%s</xliff:g>\""</string>
@@ -243,8 +253,8 @@
<string name="composer_failed_to_get_database_infomation" msgid="3723109558155169053">"Αποτυχία λήψης πληροφοριών βάσης δεδομένων"</string>
<string name="composer_has_no_exportable_contact" msgid="754734132189369094">"Δεν υπάρχει επαφή με δυνατότητα εξαγωγής. Αν έχετε επαφές στο τηλέφωνό σας, ενδέχεται να μην επιτρέπεται η εξαγωγή όλων των επαφών εκτός του τηλεφώνου από κάποιον πάροχο δεδομένων."</string>
<string name="composer_not_initialized" msgid="8041534450748388843">"Δεν έχει γίνει σωστή εκκίνηση του προγράμματος σύνθεσης της vCard"</string>
- <string name="fail_reason_could_not_open_file" msgid="4013520943128739511">"Δεν ήταν δυνατό το άνοιγμα του \"<xliff:g id="FILE_NAME">%1$s</xliff:g>\": <xliff:g id="EXACT_REASON">%2$s</xliff:g>"</string>
- <string name="exporting_contact_list_progress" msgid="560522409559101193">"<xliff:g id="CURRENT_NUMBER">%1$s</xliff:g> από <xliff:g id="TOTAL_NUMBER">%2$s</xliff:g> επαφές"</string>
+ <string name="fail_reason_could_not_open_file" msgid="4013520943128739511">"Δεν ήταν δυνατό το άνοιγμα του \"<xliff:g id="FILE_NAME">%s</xliff:g>\": <xliff:g id="EXACT_REASON">%s</xliff:g>"</string>
+ <string name="exporting_contact_list_progress" msgid="560522409559101193">"<xliff:g id="CURRENT_NUMBER">%s</xliff:g> από <xliff:g id="TOTAL_NUMBER">%s</xliff:g> επαφές"</string>
<string name="search_settings_description" msgid="2675223022992445813">"Ονόματα ων επαφών σας"</string>
<string name="add_2sec_pause" msgid="9214012315201040129">"Προσθήκη παύσης 2 δευτερολέπτων"</string>
<string name="add_wait" msgid="3360818652790319634">"Προσθήκη αναμονής"</string>
@@ -254,6 +264,7 @@
<string name="quickcontact_missing_app" msgid="4600366393134289038">"Δεν βρέθηκε εφαρμογή για τη διαχείριση αυτής της ενέργειας"</string>
<string name="quickcontact_remember_choice" msgid="5964536411579749424">"Διατήρηση αυτής της επιλογής"</string>
<string name="quickcontact_missing_name" msgid="5590266114306996632">"Άγνωστος"</string>
+ <string name="quickcontact_no_data" msgid="2098000859125253675">"Δεν υπάρχουν δεδομένα"</string>
<string name="menu_accounts" msgid="8499114602017077970">"Λογαριασμοί"</string>
<string name="menu_import_export" msgid="3765725645491577190">"Εισαγωγή/Εξαγωγή"</string>
<string name="dialog_import_export" msgid="4771877268244096596">"Εισαγωγή/Εξαγωγή επαφών"</string>
@@ -359,6 +370,7 @@
<string name="name_phonetic_family" msgid="462095502140180305">"Φωνητική γραφή επιθέτου"</string>
<string name="account_type_format" msgid="718948015590343010">"<xliff:g id="SOURCE">%1$s</xliff:g> επαφή"</string>
<string name="from_account_format" msgid="687567483928582084">"από <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
+ <string name="account_type_and_name" msgid="2876220035411512934">"Επαφή <xliff:g id="SOURCE_0">%1$s</xliff:g> από <xliff:g id="SOURCE_1">%2$s</xliff:g>"</string>
<string name="use_photo_as_primary" msgid="8807110122951157246">"Χρήση αυτής της φωτογραφίας"</string>
<string name="contact_read_only" msgid="1203216914575723978">"Τα στοιχεία επικοινωνίας της επαφής <xliff:g id="SOURCE">%1$s</xliff:g> δεν μπορούν να υποβληθούν σε επεξεργασία σε αυτήν τη συσκευή."</string>
<string name="no_contact_details" msgid="6754415338321837001">"Δεν υπάρχουν πρόσθετες πληροφορίες για αυτήν την επαφή"</string>
@@ -374,9 +386,42 @@
<string name="pick_photo" msgid="448886509158039462">"Επιλογή φωτογραφίας από τη συλλογή"</string>
<string name="locale_change_in_progress" msgid="1124266507671178413">"Η λίστα επαφών ενημερώνεται ώστε να αντικατοπτρίζει την αλλαγή γλώσσας."\n\n"Περιμένετε..."</string>
<string name="upgrade_in_progress" msgid="7530893673211750223">"Γίνεται ενημέρωση της λίστας επαφών."\n\n"Περιμένετε..."</string>
- <string name="upgrade_out_of_memory" msgid="492151063059824962">"Γίνεται αναβάθμιση των επαφών. "\n\n"Η διαδικασία αναβάθμισης απαιτεί περίπου <xliff:g id="SIZE_IN_MEGABYTES">%d</xliff:g>Mb από τον εσωτερικό χώρο αποθήκευσης του τηλεφώνου."\n\n"Ορίστε μία από τις παρακάτω επιλογές:"</string>
+ <string name="upgrade_out_of_memory" msgid="6713914687111678777">"Γίνεται αναβάθμιση των επαφών. "\n\n"Η διαδικασία αναβάθμισης απαιτεί περίπου <xliff:g id="SIZE_IN_MEGABYTES">%d</xliff:g>Mb από τον εσωτερικό χώρο αποθήκευσης του τηλεφώνου."\n\n"Ορίστε μία από τις παρακάτω επιλογές:"</string>
<string name="upgrade_out_of_memory_uninstall" msgid="1721798828992091432">"Καταργήστε την εγκατάσταση ορισμένων εφαρμογών"</string>
<string name="upgrade_out_of_memory_retry" msgid="8431289830472724609">"Επανάληψη αναβάθμισης"</string>
<string name="search_results_for" msgid="8705490885073188513">"Αποτελέσματα αναζήτησης για: <xliff:g id="QUERY">%s</xliff:g>"</string>
<string name="search_results_searching" msgid="7755623475227227314">"Αναζήτηση..."</string>
+ <string name="adding_recipients" msgid="5123647646892106631">"Φόρτωση …"</string>
+ <string name="menu_display_selected" msgid="6470001164297969034">"Εμφάνιση επιλεγμένων"</string>
+ <string name="menu_display_all" msgid="8887488642609786198">"Εμφάνιση όλων\n"</string>
+ <string name="menu_select_all" msgid="621719255150713545">"Επιλογή όλων"</string>
+ <string name="menu_select_none" msgid="7093222469852132345">"Αποεπιλογή όλων"</string>
+ <plurals name="multiple_picker_title">
+ <item quantity="one" msgid="4761009734586319101">"επιλέχθηκε 1 παραλήπτης"</item>
+ <item quantity="other" msgid="4608837420986126229">"επιλέχθηκαν <xliff:g id="COUNT">%d</xliff:g> παραλήπτες"</item>
+ </plurals>
+ <string name="unknown_contacts_separator" msgid="2490675836664397214">"Άγνωστες επαφές"</string>
+ <string name="no_contacts_selected" msgid="5877803471037324613">"Δεν έχουν επιλεγεί επαφές."</string>
+ <string name="add_field" msgid="5257149039253569615">"Προσθήκη πληροφοριών"</string>
+ <string name="contact_status_update_attribution" msgid="752179367353018597">"μέσω <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
+ <string name="contact_status_update_attribution_with_date" msgid="7358045508107825068">"<xliff:g id="DATE">%1$s</xliff:g> μέσω <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
+ <string name="description_edit" msgid="1601490950771217014">"επεξεργασία"</string>
+ <string name="description_star" msgid="2605854427360036550">"αγαπημένο"</string>
+ <string name="edit_contact" msgid="7529281274005689512">"Επεξεργασία επαφής"</string>
+ <plurals name="merge_info">
+ <item quantity="one" msgid="148365587896371969">"δεν συγχωνεύθηκαν"</item>
+ <item quantity="other" msgid="425683718017380845">"συγχώνευση από <xliff:g id="COUNT">%0$d</xliff:g> προελεύσεις"</item>
+ </plurals>
+ <string name="edit_delete_rawcontact" msgid="5054644778227879148">"Διαγραφή"</string>
+ <string name="edit_error_saving" msgid="2153949392245240166">"Σφάλμα κατά την αποθήκευση"</string>
+ <string name="edit_structured_editor_button" msgid="3830078388142808106">"..."</string>
+ <string name="local_invisible_directory" msgid="6046691709127661065">"Άλλο"</string>
+ <!-- no translation found for aggregation_suggestion_title:one (7676930148670768645) -->
+ <!-- no translation found for aggregation_suggestion_title:other (3744098467672876675) -->
+ <!-- no translation found for aggregation_suggestion_joined_title (5342738708069602301) -->
+ <skip />
+ <!-- no translation found for aggregation_suggestion_join_button (7248325487963514260) -->
+ <skip />
+ <!-- no translation found for aggregation_suggestion_view_button (8232061564083962522) -->
+ <skip />
</resources>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index 70a3296..5a3ab99 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -31,6 +31,8 @@
<string name="viewContactTitle" msgid="7989394521836644384">"Detalles de contacto"</string>
<string name="viewContactDesription" msgid="214186610887547860">"Ver contacto"</string>
<string name="editContactDescription" msgid="2947202828256214947">"Editar contacto"</string>
+ <string name="edit_email_caption" msgid="2401084399676154272">"Dirección de correo electrónico:"</string>
+ <string name="edit_email_type_caption" msgid="66331218163770698">"Tipo:"</string>
<string name="insertContactDescription" msgid="4709878105452681987">"Crear contacto"</string>
<string name="searchHint" msgid="8482945356247760701">"Buscar contactos"</string>
<string name="menu_search" msgid="9147752853603483719">"Buscar"</string>
@@ -123,13 +125,13 @@
</plurals>
<string name="listTotalAllContactsZero" msgid="6811347506748072822">"No hay contactos visibles"</string>
<plurals name="listFoundAllContacts">
- <item quantity="one" msgid="2830107332033967280">"Se encontró 1 contacto"</item>
- <item quantity="other" msgid="7752927996850263152">"Se encontraron <xliff:g id="COUNT">%d</xliff:g> contactos"</item>
+ <item quantity="one" msgid="5517063038754171134">"Se encontró uno (1)"</item>
+ <item quantity="other" msgid="3852668542926965042">"<xliff:g id="COUNT">%d</xliff:g>Se encontró un "</item>
</plurals>
- <string name="listFoundAllContactsZero" msgid="5554368784319460828">"No se ha encontrado este contacto."</string>
+ <string name="listFoundAllContactsZero" msgid="777952841930508289">"No se ha encontrado"</string>
<plurals name="searchFoundContacts">
- <item quantity="one" msgid="916648718690661777">"1 contacto"</item>
- <item quantity="other" msgid="5660384247071761844">"<xliff:g id="COUNT">%d</xliff:g> contactos"</item>
+ <item quantity="one" msgid="4826918429708286628">"Se encontró uno (1)"</item>
+ <item quantity="other" msgid="7988132539476575389">"<xliff:g id="COUNT">%d</xliff:g>Se encontró un "</item>
</plurals>
<string name="contactsIconLabel" msgid="7666609097606552806">"Contactos"</string>
<string name="contactsFavoritesLabel" msgid="8417039765586853670">"Favoritos"</string>
@@ -159,10 +161,10 @@
<string name="simContacts_title" msgid="27341688347689769">"Contactos de tarjeta SIM"</string>
<string name="noContactsHelpTextWithSyncForCreateShortcut" msgid="801504710275614594">"No tienes ningún contacto para mostrar. (Si has agregado una cuenta recientemente, la sincronización de los contactos puede demorar algunos minutos)."</string>
<string name="noContactsHelpTextForCreateShortcut" msgid="3081286388667108335">"No tienes ningún contacto para mostrar."</string>
- <string name="noContactsHelpText" msgid="6788487368878712350">"No tienes ningún contacto para mostrar."\n\n"Para agregar contactos, presiona "<font fgcolor="#ffffffff"><b>"Menú"</b></font>" y toca:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Cuentas"</b></font>" para agregar o configurar una cuenta con contactos que puedes sincronizar en el teléfono"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Contacto nuevo"</b></font>" para crear tú mismo un contacto nuevo"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importar/Exportar"</b></font>\n</li></string>
- <string name="noContactsHelpTextWithSync" msgid="3734101165712848179">"No tienes ningún contacto para mostrar. (Si has agregado una cuenta, la sincronización de contactos puede demorar pocos minutos)."\n\n"Para agregar contactos, presiona "<font fgcolor="#ffffffff"><b>"Menú"</b></font>" y toca:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Cuentas"</b></font>" para agregar y configurar una cuenta con contactos que puedes sincronizar en el teléfono"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Mostrar opciones"</b></font>" para cambiar los contactos que sean visibles"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Contacto nuevo"</b></font>" para crear tú mismo un contacto nuevo"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importar/Exportar"</b></font>\n</li></string>
- <string name="noContactsNoSimHelpText" msgid="6553845386917463292">"No tienes ningún contacto para mostrar."\n\n"Para agregar contactos, presiona "<font fgcolor="#ffffffff"><b>"Menú"</b></font>" y toca:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Cuentas"</b></font>" para agregar o configurar una cuenta con contactos que puedes sincronizar en el teléfono"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Contacto nuevo"</b></font>" para crear tú mismo un contacto nuevo"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importar/Exportar"</b></font>\n</li></string>
- <string name="noContactsNoSimHelpTextWithSync" msgid="1122296298361373488">"No tienes ningún contacto para mostrar. (Si has agregado una cuenta, la sincronización de contactos puede demorar pocos minutos)."\n\n"Para agregar contactos, presiona "<font fgcolor="#ffffffff"><b>"Menú"</b></font>" y toca:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Cuentas"</b></font>" para agregar y configurar una cuenta con contactos que puedes sincronizar en el teléfono"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Mostrar opciones"</b></font>" para cambiar los contactos que sean visibles"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Contacto nuevo"</b></font>" para crear tú mismo un contacto nuevo"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importar/Exportar"</b></font>\n</li></string>
+ <string name="noContactsHelpText" msgid="7633826236417884130">"No tienes ningún contacto para mostrar."\n\n"Para agregar contactos, presiona "<font fgcolor="#ffffffff"><b>"Menú"</b></font>" y toca:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Cuentas"</b></font>" para agregar o configurar una cuenta con contacto que puedas sincronizar en el teléfono"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Contacto nuevo"</b></font>" para crear un contacto desde cero"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importar/Exportar"</b></font>" para importar contactos desde tu tarjeta SIM o SD"\n</li></string>
+ <string name="noContactsHelpTextWithSync" msgid="3017521127042216243">"No tienes ningún contacto para mostrar. (Si has agregado una cuenta, la sincronización de contactos puede demorar algunos minutos)."\n\n"Para agregar contactos, presiona "<font fgcolor="#ffffffff"><b>"Menú"</b></font>" y toca:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Cuentas"</b></font>" para agregar y configurar una cuenta con contactos que puedes sincronizar en el teléfono"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Mostrar opciones"</b></font>" para cambiar los contactos que sean visibles"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Contacto nuevo"</b></font>" para crear tú mismo un contacto nuevo"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importar/Exportar"</b></font>" para importar contactos de tu tarjeta SIM o SD"\n</li></string>
+ <string name="noContactsNoSimHelpText" msgid="467658807711582876">"No tienes ningún contacto para mostrar."\n\n"Para agregar contactos, presiona "<font fgcolor="#ffffffff"><b>"Menú"</b></font>" y toca:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Cuentas"</b></font>" para agregar o configurar una cuenta con contactos que puedes sincronizar en el teléfono"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Contacto nuevo"</b></font>" para crear tú mismo un contacto nuevo"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importar/Exportar"</b></font>" para importar contactos desde tu tarjeta SD"\n</li></string>
+ <string name="noContactsNoSimHelpTextWithSync" msgid="9040060730467973050">"No tienes ningún contacto para mostrar. (Si has agregado una cuenta, la sincronización de contactos puede demorar algunos minutos)."\n\n"Para agregar contactos, presiona "<font fgcolor="#ffffffff"><b>"Menú"</b></font>" y toca:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Cuentas"</b></font>" para agregar y configurar una cuenta con contactos que puedes sincronizar en el teléfono"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Mostrar opciones"</b></font>" para cambiar los contactos que sean visibles"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Contacto nuevo"</b></font>" para crear tú mismo un contacto nuevo"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importar/Exportar"</b></font>" para importar contactos desde tu tarjeta SD"\n</li></string>
<string name="noFavoritesHelpText" msgid="3744655776704833277">"No tienes ningún favorito."\n\n"Para agregar un contacto a tu lista de favoritos:"\n\n<li>"toca la pestaña "<b>"Contactos"</b>" "\n</li>" "\n<li>"toca el contacto que deseas agregar a tus favoritos"\n</li>" "\n<li>"toca el asterisco situado junto al nombre del contacto"\n</li></string>
<string name="liveFolder_all_label" msgid="5961411940473276616">"Todos los contactos"</string>
<string name="liveFolder_favorites_label" msgid="2674341514070517105">"Marcado con asterisco"</string>
@@ -179,7 +181,7 @@
<string name="callBack" msgid="5498224409038809224">"Volver a llamar"</string>
<string name="callAgain" msgid="3197312117049874778">"Llamar nuevamente"</string>
<string name="returnCall" msgid="8171961914203617813">"Regresar llamada"</string>
- <string name="callDetailsDurationFormat" msgid="8157706382818184268">"<xliff:g id="MINUTES">%1$s</xliff:g> mins <xliff:g id="SECONDS">%2$s</xliff:g> s"</string>
+ <string name="callDetailsDurationFormat" msgid="8157706382818184268">"<xliff:g id="MINUTES">%s</xliff:g> mins <xliff:g id="SECONDS">%s</xliff:g> s"</string>
<string name="favoritesFrquentSeparator" msgid="8107518433381283736">"Contactado con frecuencia"</string>
<string name="add_contact_dlg_title" msgid="2896685845822146494">"Agregar contacto"</string>
<string name="add_contact_dlg_message_fmt" msgid="7986472669444326576">"¿Deseas agregar \"<xliff:g id="EMAIL">%s</xliff:g>\" a los contactos?"</string>
@@ -217,18 +219,24 @@
<string name="scanning_sdcard_failed_title" msgid="3506782007953167180">"No se ha podido explorar la tarjeta SD"</string>
<string name="scanning_sdcard_failed_message" msgid="3761992500690182922">"No se ha podido explorar la tarjeta SD. (Motivo: \"<xliff:g id="FAIL_REASON">%s</xliff:g>\")"</string>
<string name="fail_reason_io_error" msgid="5922864781066136340">"Error de E/S"</string>
+ <string name="fail_reason_low_memory_during_import" msgid="7514918659342886381">"Memoria insuficiente (es probable que el archivo sea muy grande)"</string>
<string name="fail_reason_vcard_parse_error" msgid="1201233722762680214">"No se ha podido analizar vCard debido a un motivo inesperado"</string>
<string name="fail_reason_vcard_not_supported_error" msgid="655208100451286027">"No se ha podido analizar vCard aunque el formato parece válido, ya que la implementación actual no lo admite"</string>
<string name="fail_reason_no_vcard_file" msgid="6376516175882881595">"No se ha encontrado un archivo de vCard en la Tarjeta SD."</string>
+ <string name="fail_reason_failed_to_collect_vcard_meta_info" msgid="4154492282316067754">"Error al recopilar metadatos de un archivo específico de la vCard."</string>
<string name="fail_reason_failed_to_read_files" msgid="3659521123567134029">"No se pudieron importar uno o más archivos (%s)."</string>
<string name="fail_reason_unknown" msgid="999034019513096768">"Error desconocido"</string>
<string name="select_vcard_title" msgid="3968948173786172468">"Seleccionar archivo de vCard"</string>
- <string name="progress_shower_message" msgid="5636525578293752526">"Segmento <xliff:g id="ACTION">%1$s</xliff:g>"\n"<xliff:g id="FILENAME">%2$s</xliff:g>"</string>
- <string name="reading_vcard_title" msgid="4723433501579653199">"Leyendo vCard"</string>
- <string name="reading_vcard_message" msgid="6381368920030748743">"Leyendo archivo(s) de vCard"</string>
- <string name="reading_vcard_failed_title" msgid="4923008144735294994">"No se han podido leer los datos de vCard"</string>
- <string name="reading_vcard_contacts" msgid="3066834102042012868">"<xliff:g id="CURRENT_NUMBER">%1$s</xliff:g> de <xliff:g id="TOTAL_NUMBER">%2$s</xliff:g> contactos"</string>
- <string name="reading_vcard_files" msgid="34180143726972661">"<xliff:g id="CURRENT_NUMBER">%1$s</xliff:g> de <xliff:g id="TOTAL_NUMBER">%2$s</xliff:g> archivos"</string>
+ <string name="caching_vcard_title" msgid="5009556022082659780">"Almacenando vCard(s) en caché local temporal"</string>
+ <string name="caching_vcard_message" msgid="2380844718093378900">"El importador está almacenando la(s) vCard(s) en un caché local temporal. La importación comenzará pronto."</string>
+ <string name="progress_notifier_message" msgid="8412196553907067659">"<xliff:g id="CURRENT_NUMBER">%s</xliff:g>/<xliff:g id="TOTAL_NUMBER">%s</xliff:g>: <xliff:g id="FILENAME">%s</xliff:g>"</string>
+ <string name="reading_vcard_title" msgid="3324988724123717025">"Leyendo vCard(s)"</string>
+ <string name="reading_vcard_failed_title" msgid="2162610359561887043">"Error al leer los datos de la vCard"</string>
+ <string name="reading_vcard_canceled_title" msgid="1770608329958463131">"Se canceló la lectura de datos de la vCard"</string>
+ <string name="importing_vcard_finished_title" msgid="3213257229785702182">"Finalizó la importación de la vCard"</string>
+ <string name="vcard_importer_start_message" msgid="5520549352987753320">"Importador de vCard iniciado."</string>
+ <string name="vcard_importer_will_start_message" msgid="2055145402481822160">"El Importador de vCard importará la vCard en un momento."</string>
+ <string name="percentage" msgid="34897865327092209">"%s%%"</string>
<string name="confirm_export_title" msgid="7648747763127442983">"Confirmar exportación"</string>
<string name="confirm_export_message" msgid="3875683519257829750">"¿Estás seguro de que deseas exportar tu lista de contactos a \"<xliff:g id="VCARD_FILENAME">%s</xliff:g>\"?"</string>
<string name="exporting_contact_failed_title" msgid="585823094820602526">"No se han podido exportar los datos de contacto"</string>
@@ -236,6 +244,8 @@
<string name="fail_reason_no_exportable_contact" msgid="4919714086648344495">"No hay ningún contacto exportable"</string>
<string name="fail_reason_too_many_vcard" msgid="7084146295639672658">"Demasiados archivos de vCard en la tarjeta SD"</string>
<string name="fail_reason_too_long_filename" msgid="1915716071321839166">"El nombre de archivo requerido es demasiado largo (\"<xliff:g id="FILENAME">%s</xliff:g>\")"</string>
+ <string name="vcard_exporter_start_message" msgid="6497218576519292369">"Exportador de vCard iniciado."</string>
+ <string name="exporting_vcard_finished_title" msgid="1268425436375550862">"Finalizó la importación de vCard"</string>
<string name="exporting_contact_list_title" msgid="9072240631534457415">"Exportando datos de contacto"</string>
<string name="exporting_contact_list_message" msgid="5640326540405486055">"Exportando datos de contacto a \"<xliff:g id="FILE_NAME">%s</xliff:g>\""</string>
<string name="fail_reason_could_not_initialize_exporter" msgid="4943708332700987376">"No se ha podido inicializar el exportador: \"<xliff:g id="EXACT_REASON">%s</xliff:g>\""</string>
@@ -243,8 +253,8 @@
<string name="composer_failed_to_get_database_infomation" msgid="3723109558155169053">"No se ha podido obtener información de la base de datos"</string>
<string name="composer_has_no_exportable_contact" msgid="754734132189369094">"No hay ningún contacto exportable. Si en realidad tienes contactos en tu teléfono, es posible que algún proveedor de datos prohíba la exportación de todos los contactos."</string>
<string name="composer_not_initialized" msgid="8041534450748388843">"El redactor de vCard no se ha inicializado correctamente"</string>
- <string name="fail_reason_could_not_open_file" msgid="4013520943128739511">"No se pudo abrir \"<xliff:g id="FILE_NAME">%1$s</xliff:g>\": <xliff:g id="EXACT_REASON">%2$s</xliff:g>"</string>
- <string name="exporting_contact_list_progress" msgid="560522409559101193">"<xliff:g id="CURRENT_NUMBER">%1$s</xliff:g> de <xliff:g id="TOTAL_NUMBER">%2$s</xliff:g> contactos"</string>
+ <string name="fail_reason_could_not_open_file" msgid="4013520943128739511">"No se pudo abrir \"<xliff:g id="FILE_NAME">%s</xliff:g>\": <xliff:g id="EXACT_REASON">%s</xliff:g>"</string>
+ <string name="exporting_contact_list_progress" msgid="560522409559101193">"<xliff:g id="CURRENT_NUMBER">%s</xliff:g> de <xliff:g id="TOTAL_NUMBER">%s</xliff:g> contactos"</string>
<string name="search_settings_description" msgid="2675223022992445813">"Nombres de tus contactos"</string>
<string name="add_2sec_pause" msgid="9214012315201040129">"Agregar pausa de 2 segundos"</string>
<string name="add_wait" msgid="3360818652790319634">"Agregar espera"</string>
@@ -254,6 +264,7 @@
<string name="quickcontact_missing_app" msgid="4600366393134289038">"No se encontró una aplicación para manejar esta acción."</string>
<string name="quickcontact_remember_choice" msgid="5964536411579749424">"Recuerda esta opción."</string>
<string name="quickcontact_missing_name" msgid="5590266114306996632">"Desconocido"</string>
+ <string name="quickcontact_no_data" msgid="2098000859125253675">"Sin datos"</string>
<string name="menu_accounts" msgid="8499114602017077970">"Cuentas"</string>
<string name="menu_import_export" msgid="3765725645491577190">"Importar/Exportar"</string>
<string name="dialog_import_export" msgid="4771877268244096596">"Importar/Exportar contactos"</string>
@@ -359,6 +370,7 @@
<string name="name_phonetic_family" msgid="462095502140180305">"Nombre familiar fonético"</string>
<string name="account_type_format" msgid="718948015590343010">"<xliff:g id="SOURCE">%1$s</xliff:g> contacto"</string>
<string name="from_account_format" msgid="687567483928582084">"de <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
+ <string name="account_type_and_name" msgid="2876220035411512934">"Contacto <xliff:g id="SOURCE_0">%1$s</xliff:g> de <xliff:g id="SOURCE_1">%2$s</xliff:g>"</string>
<string name="use_photo_as_primary" msgid="8807110122951157246">"Usar esta foto"</string>
<string name="contact_read_only" msgid="1203216914575723978">"La información de <xliff:g id="SOURCE">%1$s</xliff:g> contactos no se puede editar en este dispositivo."</string>
<string name="no_contact_details" msgid="6754415338321837001">"No hay información adicional para este contacto."</string>
@@ -374,9 +386,42 @@
<string name="pick_photo" msgid="448886509158039462">"Seleccionar foto de la galería"</string>
<string name="locale_change_in_progress" msgid="1124266507671178413">"La lista de contactos se está actualizando para reflejar el cambio de idioma."\n\n" Espera."</string>
<string name="upgrade_in_progress" msgid="7530893673211750223">"La lista de contactos se está actualizando."\n\n" Espera..."</string>
- <string name="upgrade_out_of_memory" msgid="492151063059824962">"La actualización de los contactos está en proceso. "\n\n"El proceso de actualización requiere aproximadamente <xliff:g id="SIZE_IN_MEGABYTES">%d</xliff:g> Mb de almacenamiento interno en el teléfono."\n\n" Elije una de las siguientes opciones:"</string>
+ <string name="upgrade_out_of_memory" msgid="6713914687111678777">"La actualización de los contactos está en proceso. "\n\n"El proceso de actualización requiere aproximadamente <xliff:g id="SIZE_IN_MEGABYTES">%d</xliff:g> Mb de almacenamiento interno en el teléfono."\n\n" Elije una de las siguientes opciones:"</string>
<string name="upgrade_out_of_memory_uninstall" msgid="1721798828992091432">"Desinstalar algunas aplicaciones"</string>
<string name="upgrade_out_of_memory_retry" msgid="8431289830472724609">"Intentar actualizar nuevamente"</string>
<string name="search_results_for" msgid="8705490885073188513">"Resultados de búsqueda para: <xliff:g id="QUERY">%s</xliff:g>"</string>
<string name="search_results_searching" msgid="7755623475227227314">"Buscando..."</string>
+ <string name="adding_recipients" msgid="5123647646892106631">"Cargando …"</string>
+ <string name="menu_display_selected" msgid="6470001164297969034">"Mostrar los seleccionados"</string>
+ <string name="menu_display_all" msgid="8887488642609786198">"Mostrar todos"</string>
+ <string name="menu_select_all" msgid="621719255150713545">"Seleccionar todo"</string>
+ <string name="menu_select_none" msgid="7093222469852132345">"Desmarcar todos"</string>
+ <plurals name="multiple_picker_title">
+ <item quantity="one" msgid="4761009734586319101">"1 destinatario seleccionado"</item>
+ <item quantity="other" msgid="4608837420986126229">"<xliff:g id="COUNT">%d</xliff:g> destinatarios seleccionados"</item>
+ </plurals>
+ <string name="unknown_contacts_separator" msgid="2490675836664397214">"Contactos desconocidos"</string>
+ <string name="no_contacts_selected" msgid="5877803471037324613">"No hay contactos seleccionados."</string>
+ <string name="add_field" msgid="5257149039253569615">"Agregar información"</string>
+ <string name="contact_status_update_attribution" msgid="752179367353018597">"a través de <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
+ <string name="contact_status_update_attribution_with_date" msgid="7358045508107825068">"<xliff:g id="DATE">%1$s</xliff:g> a través de <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
+ <string name="description_edit" msgid="1601490950771217014">"editar"</string>
+ <string name="description_star" msgid="2605854427360036550">"favorito"</string>
+ <string name="edit_contact" msgid="7529281274005689512">"Editar contacto"</string>
+ <plurals name="merge_info">
+ <item quantity="one" msgid="148365587896371969">"no se combinó"</item>
+ <item quantity="other" msgid="425683718017380845">"combinado a partir de fuentes <xliff:g id="COUNT">%0$d</xliff:g> "</item>
+ </plurals>
+ <string name="edit_delete_rawcontact" msgid="5054644778227879148">"Eliminar"</string>
+ <string name="edit_error_saving" msgid="2153949392245240166">"Error al guardar"</string>
+ <string name="edit_structured_editor_button" msgid="3830078388142808106">"..."</string>
+ <string name="local_invisible_directory" msgid="6046691709127661065">"Otro"</string>
+ <!-- no translation found for aggregation_suggestion_title:one (7676930148670768645) -->
+ <!-- no translation found for aggregation_suggestion_title:other (3744098467672876675) -->
+ <!-- no translation found for aggregation_suggestion_joined_title (5342738708069602301) -->
+ <skip />
+ <!-- no translation found for aggregation_suggestion_join_button (7248325487963514260) -->
+ <skip />
+ <!-- no translation found for aggregation_suggestion_view_button (8232061564083962522) -->
+ <skip />
</resources>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index 587ec84..3911587 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -31,6 +31,8 @@
<string name="viewContactTitle" msgid="7989394521836644384">"Detalles del contacto"</string>
<string name="viewContactDesription" msgid="214186610887547860">"Ver contacto"</string>
<string name="editContactDescription" msgid="2947202828256214947">"Editar contacto"</string>
+ <string name="edit_email_caption" msgid="2401084399676154272">"Correo electrónico:"</string>
+ <string name="edit_email_type_caption" msgid="66331218163770698">"Tipo:"</string>
<string name="insertContactDescription" msgid="4709878105452681987">"Crear contacto"</string>
<string name="searchHint" msgid="8482945356247760701">"Buscar contactos"</string>
<string name="menu_search" msgid="9147752853603483719">"Buscar"</string>
@@ -123,13 +125,13 @@
</plurals>
<string name="listTotalAllContactsZero" msgid="6811347506748072822">"No hay contactos visibles."</string>
<plurals name="listFoundAllContacts">
- <item quantity="one" msgid="2830107332033967280">"Se ha encontrado un contacto."</item>
- <item quantity="other" msgid="7752927996850263152">"Se han encontrado <xliff:g id="COUNT">%d</xliff:g> contactos."</item>
+ <item quantity="one" msgid="5517063038754171134">"1 encontrado"</item>
+ <item quantity="other" msgid="3852668542926965042">"<xliff:g id="COUNT">%d</xliff:g> encontrados"</item>
</plurals>
- <string name="listFoundAllContactsZero" msgid="5554368784319460828">"No se ha encontrado el contacto."</string>
+ <string name="listFoundAllContactsZero" msgid="777952841930508289">"No se ha encontrado."</string>
<plurals name="searchFoundContacts">
- <item quantity="one" msgid="916648718690661777">"1 contacto"</item>
- <item quantity="other" msgid="5660384247071761844">"<xliff:g id="COUNT">%d</xliff:g> contactos"</item>
+ <item quantity="one" msgid="4826918429708286628">"1 encontrado"</item>
+ <item quantity="other" msgid="7988132539476575389">"<xliff:g id="COUNT">%d</xliff:g> encontrados"</item>
</plurals>
<string name="contactsIconLabel" msgid="7666609097606552806">"Contactos"</string>
<string name="contactsFavoritesLabel" msgid="8417039765586853670">"Favoritos"</string>
@@ -159,10 +161,10 @@
<string name="simContacts_title" msgid="27341688347689769">"Contactos de tarjeta SIM"</string>
<string name="noContactsHelpTextWithSyncForCreateShortcut" msgid="801504710275614594">"No tienes ningún contacto que mostrar. (Si acabas de añadir una cuenta, es posible que la sincronización de contactos tarde algunos minutos)."</string>
<string name="noContactsHelpTextForCreateShortcut" msgid="3081286388667108335">"No tienes ningún contacto que mostrar."</string>
- <string name="noContactsHelpText" msgid="6788487368878712350">"No tienes ningún contacto que mostrar."\n\n"Para añadir contactos, pulsa la tecla de "<font fgcolor="#ffffffff"><b>"menú"</b></font>" y toca en:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Cuentas"</b></font>" para añadir o configurar una cuenta con los contactos que puedes sincronizar en el teléfono,"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Contacto nuevo"</b></font>" para crear un nuevo contacto,"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importar/exportar."</b></font>\n</li></string>
- <string name="noContactsHelpTextWithSync" msgid="3734101165712848179">"No tienes ningún contacto que mostrar. (Si acabas de añadir una cuenta, es posible que la sincronización de contactos tarde algunos minutos)."\n\n"Para añadir contactos, pulsa la tecla de "<font fgcolor="#ffffffff"><b>"menú"</b></font>" y toca en:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Cuentas"</b></font>" para añadir o configurar una cuenta con contactos que puedes sincronizar en el teléfono,"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Opciones de visualización"</b></font>" para modificar los contactos visibles,"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Contacto nuevo"</b></font>" para crear un nuevo contacto,"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importar/exportar."</b></font>\n</li></string>
- <string name="noContactsNoSimHelpText" msgid="6553845386917463292">"No tienes ningún contacto que mostrar."\n\n"Para añadir contactos, pulsa la tecla de "<font fgcolor="#ffffffff"><b>"menú"</b></font>" y toca en:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Cuentas"</b></font>" para añadir o configurar una cuenta con los contactos que puedes sincronizar en el teléfono,"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Contacto nuevo"</b></font>" para crear un nuevo contacto,"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importar/exportar."</b></font>\n</li></string>
- <string name="noContactsNoSimHelpTextWithSync" msgid="1122296298361373488">"No tienes ningún contacto que mostrar. (Si acabas de añadir una cuenta, es posible que la sincronización de contactos tarde algunos minutos)."\n\n"Para añadir contactos, pulsa la tecla de "<font fgcolor="#ffffffff"><b>"menú"</b></font>" y toca en:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Cuentas"</b></font>" para añadir o configurar una cuenta con contactos que puedes sincronizar en el teléfono,"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Opciones de visualización"</b></font>" para modificar los contactos visibles,"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Contacto nuevo"</b></font>" para crear un nuevo contacto,"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importar/exportar."</b></font>\n</li></string>
+ <string name="noContactsHelpText" msgid="7633826236417884130">"No tienes contactos."\n\n"Para añadir uno, pulsa la tecla de menú"<font fgcolor="#ffffffff"><b></b></font>" y toca:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Cuentas"</b></font>" para añadir o configurar una cuenta con los contactos que puedes sincronizar,"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Contacto nuevo"</b></font>" para crear un nuevo contacto,"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importar/exportar"</b></font>" para importar contactos desde la tarjeta SIM o SD"\n</li>"."</string>
+ <string name="noContactsHelpTextWithSync" msgid="3017521127042216243">"No tienes contactos. Si acabas de añadir una cuenta, la sincronización puede tardar."\n\n"Para añadir contactos, pulsa la tecla de menú"<font fgcolor="#ffffffff"><b></b></font>" y toca:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Cuentas"</b></font>" para añadir o configurar una cuenta con contactos que puedes sincronizar,"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Opciones de visualización"</b></font>" para modificar los contactos visibles,"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Contacto nuevo"</b></font>" para crear un nuevo contacto,"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importar/exportar"</b></font>" para importar contactos desde la tarjeta SIM o SD"\n</li>"."</string>
+ <string name="noContactsNoSimHelpText" msgid="467658807711582876">"No tienes contactos."\n\n"Para añadir uno, pulsa la tecla de menú"<font fgcolor="#ffffffff"><b></b></font>" y toca:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Cuentas"</b></font>" para añadir o configurar una cuenta con los contactos que puedes sincronizar,"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Contacto nuevo"</b></font>" para crear un nuevo contacto,"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importar/exportar"</b></font>" para importar contactos desde la tarjeta SD"\n</li>"."</string>
+ <string name="noContactsNoSimHelpTextWithSync" msgid="9040060730467973050">"No tienes contactos. Si acabas de añadir una cuenta, la sincronización puede tardar."\n\n"Para añadir contactos, pulsa la tecla de menú"<font fgcolor="#ffffffff"><b></b></font>" y toca:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Cuentas"</b></font>" para añadir o configurar una cuenta con contactos que puedes sincronizar,"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Opciones de visualización"</b></font>" para modificar los contactos visibles,"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Contacto nuevo"</b></font>" para crear un nuevo contacto,"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importar/exportar"</b></font>" para importar contactos desde la tarjeta SD"\n</li>"."</string>
<string name="noFavoritesHelpText" msgid="3744655776704833277">"No tienes favoritos."\n\n"Para añadir un contacto a tu lista de favoritos, sigue estos pasos:"\n\n" "<li>"Toca en la pestaña "<b>"Contactos"</b>"."\n</li>" "\n<li>"Selecciona el contacto que quieras añadir a tus favoritos."\n</li>" "\n<li>"Toca la estrella situada junto al nombre del contacto."\n</li></string>
<string name="liveFolder_all_label" msgid="5961411940473276616">"Todos los contactos"</string>
<string name="liveFolder_favorites_label" msgid="2674341514070517105">"Destacados"</string>
@@ -179,7 +181,7 @@
<string name="callBack" msgid="5498224409038809224">"Volver a llamar"</string>
<string name="callAgain" msgid="3197312117049874778">"Volver a llamar"</string>
<string name="returnCall" msgid="8171961914203617813">"Devolver llamada"</string>
- <string name="callDetailsDurationFormat" msgid="8157706382818184268">"<xliff:g id="MINUTES">%1$s</xliff:g> min. y <xliff:g id="SECONDS">%2$s</xliff:g> seg."</string>
+ <string name="callDetailsDurationFormat" msgid="8157706382818184268">"<xliff:g id="MINUTES">%s</xliff:g> min. y <xliff:g id="SECONDS">%s</xliff:g> seg."</string>
<string name="favoritesFrquentSeparator" msgid="8107518433381283736">"Contactos frecuentes"</string>
<string name="add_contact_dlg_title" msgid="2896685845822146494">"Añadir contacto"</string>
<string name="add_contact_dlg_message_fmt" msgid="7986472669444326576">"¿Deseas añadir \"<xliff:g id="EMAIL">%s</xliff:g>\" a Contactos?"</string>
@@ -217,18 +219,24 @@
<string name="scanning_sdcard_failed_title" msgid="3506782007953167180">"Error al buscar en la tarjeta SD"</string>
<string name="scanning_sdcard_failed_message" msgid="3761992500690182922">"Error al buscar en la tarjeta SD (motivo: \"<xliff:g id="FAIL_REASON">%s</xliff:g>\")"</string>
<string name="fail_reason_io_error" msgid="5922864781066136340">"Error de E/S"</string>
+ <string name="fail_reason_low_memory_during_import" msgid="7514918659342886381">"Memoria insuficiente (el archivo puede ser demasiado grande)"</string>
<string name="fail_reason_vcard_parse_error" msgid="1201233722762680214">"No se ha podido analizar el archivo de vCard debido a un error inesperado."</string>
<string name="fail_reason_vcard_not_supported_error" msgid="655208100451286027">"No se ha podido analizar el archivo de vCard (a pesar de que su formato parece ser válido) porque no es compatible con la implementación actual."</string>
<string name="fail_reason_no_vcard_file" msgid="6376516175882881595">"No se ha encontrado ningún archivo de vCard en la tarjeta SD."</string>
+ <string name="fail_reason_failed_to_collect_vcard_meta_info" msgid="4154492282316067754">"Error al recopilar metainformación de los archivos vCard"</string>
<string name="fail_reason_failed_to_read_files" msgid="3659521123567134029">"No ha sido posible importar uno o varios archivos (%s)."</string>
<string name="fail_reason_unknown" msgid="999034019513096768">"Error desconocido"</string>
<string name="select_vcard_title" msgid="3968948173786172468">"Seleccionar archivo de vCard"</string>
- <string name="progress_shower_message" msgid="5636525578293752526">"<xliff:g id="ACTION">%1$s</xliff:g>"\n"<xliff:g id="FILENAME">%2$s</xliff:g>"</string>
- <string name="reading_vcard_title" msgid="4723433501579653199">"Leyendo vCard"</string>
- <string name="reading_vcard_message" msgid="6381368920030748743">"Leyendo archivos de vCard"</string>
- <string name="reading_vcard_failed_title" msgid="4923008144735294994">"Error al leer datos de vCard"</string>
- <string name="reading_vcard_contacts" msgid="3066834102042012868">"<xliff:g id="CURRENT_NUMBER">%1$s</xliff:g> de <xliff:g id="TOTAL_NUMBER">%2$s</xliff:g> contactos"</string>
- <string name="reading_vcard_files" msgid="34180143726972661">"<xliff:g id="CURRENT_NUMBER">%1$s</xliff:g> de <xliff:g id="TOTAL_NUMBER">%2$s</xliff:g> archivos"</string>
+ <string name="caching_vcard_title" msgid="5009556022082659780">"Almacenamiento temporal local de vCard(s) en caché"</string>
+ <string name="caching_vcard_message" msgid="2380844718093378900">"El importador está almacenando de forma temporal local vCard(s). La importación empezará pronto."</string>
+ <string name="progress_notifier_message" msgid="8412196553907067659">"<xliff:g id="CURRENT_NUMBER">%s</xliff:g>/<xliff:g id="TOTAL_NUMBER">%s</xliff:g>: <xliff:g id="FILENAME">%s</xliff:g>"</string>
+ <string name="reading_vcard_title" msgid="3324988724123717025">"Leyendo vCard(s)"</string>
+ <string name="reading_vcard_failed_title" msgid="2162610359561887043">"Error al leer los datos de vCard"</string>
+ <string name="reading_vcard_canceled_title" msgid="1770608329958463131">"Lectura de vCard cancelada"</string>
+ <string name="importing_vcard_finished_title" msgid="3213257229785702182">"Importación de vCard terminada"</string>
+ <string name="vcard_importer_start_message" msgid="5520549352987753320">"Importador de vCard iniciado"</string>
+ <string name="vcard_importer_will_start_message" msgid="2055145402481822160">"El importador de vCard importará vCard transcurrido un tiempo."</string>
+ <string name="percentage" msgid="34897865327092209">"%s%%"</string>
<string name="confirm_export_title" msgid="7648747763127442983">"Confirmar la exportación"</string>
<string name="confirm_export_message" msgid="3875683519257829750">"¿Estás seguro de que quieres exportar tu lista de contactos a \"<xliff:g id="VCARD_FILENAME">%s</xliff:g>\"?"</string>
<string name="exporting_contact_failed_title" msgid="585823094820602526">"Error al exportar los datos del contacto"</string>
@@ -236,6 +244,8 @@
<string name="fail_reason_no_exportable_contact" msgid="4919714086648344495">"No hay contactos que exportar."</string>
<string name="fail_reason_too_many_vcard" msgid="7084146295639672658">"Hay demasiados archivos de vCard en la tarjeta SD."</string>
<string name="fail_reason_too_long_filename" msgid="1915716071321839166">"El nombre de archivo necesario es demasiado largo (\"<xliff:g id="FILENAME">%s</xliff:g>\")."</string>
+ <string name="vcard_exporter_start_message" msgid="6497218576519292369">"Exportador de vCard iniciado"</string>
+ <string name="exporting_vcard_finished_title" msgid="1268425436375550862">"Exportación de vCard finalizada"</string>
<string name="exporting_contact_list_title" msgid="9072240631534457415">"Exportando datos de contacto"</string>
<string name="exporting_contact_list_message" msgid="5640326540405486055">"Exportando datos de contacto a \"<xliff:g id="FILE_NAME">%s</xliff:g>\"..."</string>
<string name="fail_reason_could_not_initialize_exporter" msgid="4943708332700987376">"No se ha podido inicializar el exportador: \"<xliff:g id="EXACT_REASON">%s</xliff:g>\""</string>
@@ -243,8 +253,8 @@
<string name="composer_failed_to_get_database_infomation" msgid="3723109558155169053">"Error al obtener información de la base de datos"</string>
<string name="composer_has_no_exportable_contact" msgid="754734132189369094">"No hay contactos que exportar. Si ya tienes contactos en el teléfono, es posible que algunos proveedores de datos no permitan que se exporten todos los contactos a otros dispositivos."</string>
<string name="composer_not_initialized" msgid="8041534450748388843">"El redactor de vCard no se ha inicializado correctamente."</string>
- <string name="fail_reason_could_not_open_file" msgid="4013520943128739511">"No se ha podido abrir el archivo \"<xliff:g id="FILE_NAME">%1$s</xliff:g>\": <xliff:g id="EXACT_REASON">%2$s</xliff:g>"</string>
- <string name="exporting_contact_list_progress" msgid="560522409559101193">"<xliff:g id="CURRENT_NUMBER">%1$s</xliff:g> de <xliff:g id="TOTAL_NUMBER">%2$s</xliff:g> contactos"</string>
+ <string name="fail_reason_could_not_open_file" msgid="4013520943128739511">"No se ha podido abrir el archivo \"<xliff:g id="FILE_NAME">%s</xliff:g>\": <xliff:g id="EXACT_REASON">%s</xliff:g>"</string>
+ <string name="exporting_contact_list_progress" msgid="560522409559101193">"<xliff:g id="CURRENT_NUMBER">%s</xliff:g> de <xliff:g id="TOTAL_NUMBER">%s</xliff:g> contactos"</string>
<string name="search_settings_description" msgid="2675223022992445813">"Nombres de tus contactos"</string>
<string name="add_2sec_pause" msgid="9214012315201040129">"Añadir pausa de dos segundos"</string>
<string name="add_wait" msgid="3360818652790319634">"Añadir espera"</string>
@@ -254,6 +264,7 @@
<string name="quickcontact_missing_app" msgid="4600366393134289038">"No se ha encontrado ninguna aplicación que pueda realizar esta acción."</string>
<string name="quickcontact_remember_choice" msgid="5964536411579749424">"Recordar esta opción"</string>
<string name="quickcontact_missing_name" msgid="5590266114306996632">"Desconocido"</string>
+ <string name="quickcontact_no_data" msgid="2098000859125253675">"Ningún dato"</string>
<string name="menu_accounts" msgid="8499114602017077970">"Cuentas"</string>
<string name="menu_import_export" msgid="3765725645491577190">"Importar/exportar"</string>
<string name="dialog_import_export" msgid="4771877268244096596">"Importar/exportar contactos"</string>
@@ -359,6 +370,7 @@
<string name="name_phonetic_family" msgid="462095502140180305">"Apellido fonético"</string>
<string name="account_type_format" msgid="718948015590343010">"Contacto de <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
<string name="from_account_format" msgid="687567483928582084">"de <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
+ <string name="account_type_and_name" msgid="2876220035411512934">"Contacto de <xliff:g id="SOURCE_0">%1$s</xliff:g> de <xliff:g id="SOURCE_1">%2$s</xliff:g>"</string>
<string name="use_photo_as_primary" msgid="8807110122951157246">"Utilizar esta foto"</string>
<string name="contact_read_only" msgid="1203216914575723978">"No se puede editar la información del contacto de <xliff:g id="SOURCE">%1$s</xliff:g> en este dispositivo."</string>
<string name="no_contact_details" msgid="6754415338321837001">"No hay información adicional para este contacto."</string>
@@ -374,9 +386,42 @@
<string name="pick_photo" msgid="448886509158039462">"Seleccionar foto de la galería"</string>
<string name="locale_change_in_progress" msgid="1124266507671178413">"La lista de contactos se está actualizando para reflejar el cambio de idioma."\n\n"Por favor, espera..."</string>
<string name="upgrade_in_progress" msgid="7530893673211750223">"La lista de contactos se está actualizando."\n\n"Por favor, espera..."</string>
- <string name="upgrade_out_of_memory" msgid="492151063059824962">"Los contactos se están actualizando. "\n\n"El proceso de actualización necesita aproximadamente <xliff:g id="SIZE_IN_MEGABYTES">%d</xliff:g> MB de almacenamiento interno del teléfono."\n\n"Selecciona una de las siguientes opciones:"</string>
+ <string name="upgrade_out_of_memory" msgid="6713914687111678777">"Los contactos se están actualizando. "\n\n"El proceso de actualización necesita aproximadamente <xliff:g id="SIZE_IN_MEGABYTES">%d</xliff:g> MB de almacenamiento interno."\n\n"Selecciona una de las siguientes opciones:"</string>
<string name="upgrade_out_of_memory_uninstall" msgid="1721798828992091432">"Desinstalar algunas aplicaciones"</string>
<string name="upgrade_out_of_memory_retry" msgid="8431289830472724609">"Reintentar actualización"</string>
<string name="search_results_for" msgid="8705490885073188513">"Resultados de la búsqueda de: <xliff:g id="QUERY">%s</xliff:g>"</string>
<string name="search_results_searching" msgid="7755623475227227314">"Buscando..."</string>
+ <string name="adding_recipients" msgid="5123647646892106631">"Cargando..."</string>
+ <string name="menu_display_selected" msgid="6470001164297969034">"Mostrar seleccionados"</string>
+ <string name="menu_display_all" msgid="8887488642609786198">"Mostrar todos"</string>
+ <string name="menu_select_all" msgid="621719255150713545">"Seleccionar todo"</string>
+ <string name="menu_select_none" msgid="7093222469852132345">"Desmarcar todo"</string>
+ <plurals name="multiple_picker_title">
+ <item quantity="one" msgid="4761009734586319101">"Un destinatario seleccionado"</item>
+ <item quantity="other" msgid="4608837420986126229">"<xliff:g id="COUNT">%d</xliff:g> destinatarios seleccionados"</item>
+ </plurals>
+ <string name="unknown_contacts_separator" msgid="2490675836664397214">"Contactos desconocidos"</string>
+ <string name="no_contacts_selected" msgid="5877803471037324613">"No hay ningún contacto seleccionado."</string>
+ <string name="add_field" msgid="5257149039253569615">"Añadir información"</string>
+ <string name="contact_status_update_attribution" msgid="752179367353018597">"con <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
+ <string name="contact_status_update_attribution_with_date" msgid="7358045508107825068">"<xliff:g id="DATE">%1$s</xliff:g> con <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
+ <string name="description_edit" msgid="1601490950771217014">"editar"</string>
+ <string name="description_star" msgid="2605854427360036550">"favoritos"</string>
+ <string name="edit_contact" msgid="7529281274005689512">"Editar contacto"</string>
+ <plurals name="merge_info">
+ <item quantity="one" msgid="148365587896371969">"sin fusionar"</item>
+ <item quantity="other" msgid="425683718017380845">"fusionados desde <xliff:g id="COUNT">%0$d</xliff:g> fuentes"</item>
+ </plurals>
+ <string name="edit_delete_rawcontact" msgid="5054644778227879148">"Eliminar"</string>
+ <string name="edit_error_saving" msgid="2153949392245240166">"Error al guardar"</string>
+ <string name="edit_structured_editor_button" msgid="3830078388142808106">"..."</string>
+ <string name="local_invisible_directory" msgid="6046691709127661065">"Otro"</string>
+ <!-- no translation found for aggregation_suggestion_title:one (7676930148670768645) -->
+ <!-- no translation found for aggregation_suggestion_title:other (3744098467672876675) -->
+ <!-- no translation found for aggregation_suggestion_joined_title (5342738708069602301) -->
+ <skip />
+ <!-- no translation found for aggregation_suggestion_join_button (7248325487963514260) -->
+ <skip />
+ <!-- no translation found for aggregation_suggestion_view_button (8232061564083962522) -->
+ <skip />
</resources>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index f72f8c5..7e2779d 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -31,6 +31,8 @@
<string name="viewContactTitle" msgid="7989394521836644384">"Détails du contact"</string>
<string name="viewContactDesription" msgid="214186610887547860">"Afficher le contact"</string>
<string name="editContactDescription" msgid="2947202828256214947">"Modifier le contact"</string>
+ <string name="edit_email_caption" msgid="2401084399676154272">"Adresse e-mail :"</string>
+ <string name="edit_email_type_caption" msgid="66331218163770698">"Type :"</string>
<string name="insertContactDescription" msgid="4709878105452681987">"Créer un contact"</string>
<string name="searchHint" msgid="8482945356247760701">"Rech. des contacts"</string>
<string name="menu_search" msgid="9147752853603483719">"Rechercher"</string>
@@ -123,13 +125,13 @@
</plurals>
<string name="listTotalAllContactsZero" msgid="6811347506748072822">"Aucun contact visible"</string>
<plurals name="listFoundAllContacts">
- <item quantity="one" msgid="2830107332033967280">"1 contact trouvé"</item>
- <item quantity="other" msgid="7752927996850263152">"<xliff:g id="COUNT">%d</xliff:g> contacts trouvés"</item>
+ <item quantity="one" msgid="5517063038754171134">"1 contact trouvé"</item>
+ <item quantity="other" msgid="3852668542926965042">"<xliff:g id="COUNT">%d</xliff:g> contact(s) trouvé(s)"</item>
</plurals>
- <string name="listFoundAllContactsZero" msgid="5554368784319460828">"Contact introuvable"</string>
+ <string name="listFoundAllContactsZero" msgid="777952841930508289">"Introuvable"</string>
<plurals name="searchFoundContacts">
- <item quantity="one" msgid="916648718690661777">"1 contact"</item>
- <item quantity="other" msgid="5660384247071761844">"<xliff:g id="COUNT">%d</xliff:g> contacts"</item>
+ <item quantity="one" msgid="4826918429708286628">"1 contact trouvé"</item>
+ <item quantity="other" msgid="7988132539476575389">"<xliff:g id="COUNT">%d</xliff:g> contact(s) trouvé(s)"</item>
</plurals>
<string name="contactsIconLabel" msgid="7666609097606552806">"Contacts"</string>
<string name="contactsFavoritesLabel" msgid="8417039765586853670">"Favoris"</string>
@@ -159,10 +161,10 @@
<string name="simContacts_title" msgid="27341688347689769">"Contacts de carte SIM"</string>
<string name="noContactsHelpTextWithSyncForCreateShortcut" msgid="801504710275614594">"Vous n\'avez aucun contact à afficher. Si vous venez d\'ajouter un compte, la synchronisation des contacts peut prendre quelques minutes."</string>
<string name="noContactsHelpTextForCreateShortcut" msgid="3081286388667108335">"Vous n\'avez aucun contact à afficher."</string>
- <string name="noContactsHelpText" msgid="6788487368878712350">"Vous n\'avez aucun contact à afficher."\n\n"Pour ajouter des contacts, appuyez sur "<font fgcolor="#ffffffff"><b>"Menu"</b></font>" et sélectionnez :"\n" "\n<li><font fgcolor="#ffffffff"><b>"Comptes"</b></font>" pour ajouter ou configurer un compte dont vous pourrez synchroniser les contacts sur le téléphone ;"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Nouveau contact"</b></font>" pour créer un contact ;"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importer/Exporter."</b></font>\n</li></string>
- <string name="noContactsHelpTextWithSync" msgid="3734101165712848179">"Vous n\'avez aucun contact à afficher (si vous venez d\'ajouter un compte, la synchronisation des contacts peut prendre quelques minutes)."\n\n"Pour ajouter des contacts, appuyez sur "<font fgcolor="#ffffffff"><b>"Menu"</b></font>" et sélectionnez :"\n" "\n<li><font fgcolor="#ffffffff"><b>"Comptes"</b></font>" pour ajouter ou configurer un compte comportant des contacts que vous pourrez synchroniser sur le téléphone ;"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Options d\'affichage"</b></font>" pour modifier le paramètre de visibilité des contacts ;"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Nouveau contact"</b></font>" pour créer une entrée ;"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importer/Exporter."</b></font>\n</li></string>
- <string name="noContactsNoSimHelpText" msgid="6553845386917463292">"Vous n\'avez aucun contact à afficher."\n\n"Pour ajouter des contacts, appuyez sur "<font fgcolor="#ffffffff"><b>"Menu"</b></font>" et sélectionnez :"\n" "\n<li><font fgcolor="#ffffffff"><b>"Comptes"</b></font>" pour ajouter ou configurer un compte dont vous pourrez synchroniser les contacts sur le téléphone ;"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Nouveau contact"</b></font>" pour créer un contact ;"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importer/Exporter."</b></font>\n</li></string>
- <string name="noContactsNoSimHelpTextWithSync" msgid="1122296298361373488">"Vous n\'avez aucun contact à afficher (si vous venez d\'ajouter un compte, la synchronisation des contacts peut prendre quelques minutes)."\n\n"Pour ajouter des contacts, appuyez sur "<font fgcolor="#ffffffff"><b>"Menu"</b></font>" et sélectionnez :"\n" "\n<li><font fgcolor="#ffffffff"><b>"Comptes"</b></font>" pour ajouter ou configurer un compte comportant des contacts que vous pourrez synchroniser sur le téléphone ;"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Options d\'affichage"</b></font>" pour modifier le paramètre de visibilité des contacts ;"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Nouveau contact"</b></font>" pour créer une entrée ;"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importer/Exporter."</b></font>\n</li></string>
+ <!-- syntax error in translation for noContactsHelpText (7633826236417884130) org.xmlpull.v1.XmlPullParserException: expected: /b read: font (position:END_TAG </font>@1:549 in java.io.StringReader@5456a499) -->
+ <string name="noContactsHelpTextWithSync" msgid="3017521127042216243">"Vous n\'avez aucun contact à afficher (si vous venez d\'ajouter un compte, la synchronisation des contacts peut prendre quelques minutes)."\n\n"Pour ajouter des contacts, appuyez sur "<font fgcolor="#ffffffff"><b>"Menu"</b></font>" et sélectionnez :"\n" "\n<li><font fgcolor="#ffffffff"><b>"Comptes"</b></font>" pour ajouter ou configurer un compte dont vous pourrez synchroniser les contacts sur le téléphone ;"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Options d\'affichage"</b></font>" pour modifier le paramètre de visibilité des contacts ;"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Nouveau contact"</b></font>" pour créer un contact ;"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importer/Exporter"</b></font>" pour importer des contacts depuis votre carte SIM ou SD."\n</li></string>
+ <!-- syntax error in translation for noContactsNoSimHelpText (467658807711582876) org.xmlpull.v1.XmlPullParserException: expected: /b read: font (position:END_TAG </font>@1:553 in java.io.StringReader@4506411) -->
+ <string name="noContactsNoSimHelpTextWithSync" msgid="9040060730467973050">"Vous n\'avez aucun contact à afficher (si vous venez d\'ajouter un compte, la synchronisation des contacts peut prendre quelques minutes)."\n\n"Pour ajouter des contacts, appuyez sur "<font fgcolor="#ffffffff"><b>"Menu"</b></font>" et sélectionnez :"\n" "\n<li><font fgcolor="#ffffffff"><b>"Comptes"</b></font>" pour ajouter ou configurer un compte dont vous pourrez synchroniser les contacts sur le téléphone ;"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Options d\'affichage"</b></font>" pour modifier le paramètre de visibilité des contacts ;"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Nouveau contact"</b></font>" pour créer un contact ;"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importer/Exporter"</b></font>" pour importer des contacts depuis votre carte SD."\n</li></string>
<string name="noFavoritesHelpText" msgid="3744655776704833277">"Vous ne disposez d\'aucun favoris."\n\n"Pour ajouter un contact à la liste de favoris :"\n\n" "<li>"Appuyez sur l\'onglet "<b>"Contacts"</b>"."\n</li>" "\n<li>"Appuyez sur le contact à ajouter à vos favoris."\n</li>" "\n<li>"Appuyez sur l\'étoile en regard du nom du contact."\n</li></string>
<string name="liveFolder_all_label" msgid="5961411940473276616">"Tous les contacts"</string>
<string name="liveFolder_favorites_label" msgid="2674341514070517105">"Suivis"</string>
@@ -179,7 +181,7 @@
<string name="callBack" msgid="5498224409038809224">"Rappeler"</string>
<string name="callAgain" msgid="3197312117049874778">"Renouveler l\'appel"</string>
<string name="returnCall" msgid="8171961914203617813">"Rappeler"</string>
- <string name="callDetailsDurationFormat" msgid="8157706382818184268">"<xliff:g id="MINUTES">%1$s</xliff:g> mn <xliff:g id="SECONDS">%2$s</xliff:g> s"</string>
+ <string name="callDetailsDurationFormat" msgid="8157706382818184268">"<xliff:g id="MINUTES">%s</xliff:g> mn <xliff:g id="SECONDS">%s</xliff:g> s"</string>
<string name="favoritesFrquentSeparator" msgid="8107518433381283736">"Contactés fréquemment"</string>
<string name="add_contact_dlg_title" msgid="2896685845822146494">"Ajouter un contact"</string>
<string name="add_contact_dlg_message_fmt" msgid="7986472669444326576">"Ajouter \"<xliff:g id="EMAIL">%s</xliff:g>\" aux contacts ?"</string>
@@ -217,18 +219,24 @@
<string name="scanning_sdcard_failed_title" msgid="3506782007953167180">"Échec de l\'analyse de la carte SD"</string>
<string name="scanning_sdcard_failed_message" msgid="3761992500690182922">"Échec de l\'analyse de la carte SD (Raison : \"<xliff:g id="FAIL_REASON">%s</xliff:g>\")"</string>
<string name="fail_reason_io_error" msgid="5922864781066136340">"Erreur d\'E/S"</string>
+ <string name="fail_reason_low_memory_during_import" msgid="7514918659342886381">"Mémoire insuffisante (fichier probablement trop volumineux)"</string>
<string name="fail_reason_vcard_parse_error" msgid="1201233722762680214">"Échec de l\'analyse des données VCard pour une raison inattendue"</string>
<string name="fail_reason_vcard_not_supported_error" msgid="655208100451286027">"Échec de l\'analyse des données VCard. Le format semble valide, mais l\'implémentation actuelle ne le prend pas en charge."</string>
<string name="fail_reason_no_vcard_file" msgid="6376516175882881595">"Aucun fichier VCard n\'a été trouvé sur la carte SD."</string>
+ <string name="fail_reason_failed_to_collect_vcard_meta_info" msgid="4154492282316067754">"Échec de la collecte des métadonnées contenues dans le(s) fichier(s) vCard"</string>
<string name="fail_reason_failed_to_read_files" msgid="3659521123567134029">"Échec de l\'importation d\'un ou de plusieurs fichiers (%s)"</string>
<string name="fail_reason_unknown" msgid="999034019513096768">"Erreur inconnue"</string>
<string name="select_vcard_title" msgid="3968948173786172468">"Sélectionner un fichier VCard"</string>
- <string name="progress_shower_message" msgid="5636525578293752526">"<xliff:g id="ACTION">%1$s</xliff:g>"\n"<xliff:g id="FILENAME">%2$s</xliff:g>"</string>
- <string name="reading_vcard_title" msgid="4723433501579653199">"Lecture des données VCard"</string>
- <string name="reading_vcard_message" msgid="6381368920030748743">"Lecture des fichiers VCard"</string>
- <string name="reading_vcard_failed_title" msgid="4923008144735294994">"Échec de la lecture des données VCard"</string>
- <string name="reading_vcard_contacts" msgid="3066834102042012868">"<xliff:g id="CURRENT_NUMBER">%1$s</xliff:g> sur <xliff:g id="TOTAL_NUMBER">%2$s</xliff:g> contacts"</string>
- <string name="reading_vcard_files" msgid="34180143726972661">"<xliff:g id="CURRENT_NUMBER">%1$s</xliff:g> sur <xliff:g id="TOTAL_NUMBER">%2$s</xliff:g> fichiers"</string>
+ <string name="caching_vcard_title" msgid="5009556022082659780">"Mise en cache de fichier(s) vCard dans l\'espace de stockage temporaire local"</string>
+ <string name="caching_vcard_message" msgid="2380844718093378900">"Le programme d\'importation met en cache le(s) fichier(s) vCard dans l\'espace de stockage temporaire local. L\'importation effective va bientôt commencer."</string>
+ <string name="progress_notifier_message" msgid="8412196553907067659">"<xliff:g id="CURRENT_NUMBER">%s</xliff:g>/<xliff:g id="TOTAL_NUMBER">%s</xliff:g> : <xliff:g id="FILENAME">%s</xliff:g>"</string>
+ <string name="reading_vcard_title" msgid="3324988724123717025">"Lecture de fichier(s) vCard"</string>
+ <string name="reading_vcard_failed_title" msgid="2162610359561887043">"Échec de la lecture des données vCard"</string>
+ <string name="reading_vcard_canceled_title" msgid="1770608329958463131">"Lecture des données vCard annulée"</string>
+ <string name="importing_vcard_finished_title" msgid="3213257229785702182">"Importation du fichier vCard annulée"</string>
+ <string name="vcard_importer_start_message" msgid="5520549352987753320">"Programme d\'importation de fichiers vCard démarré"</string>
+ <string name="vcard_importer_will_start_message" msgid="2055145402481822160">"Le fichier vCard sera importé dans quelques instants."</string>
+ <string name="percentage" msgid="34897865327092209">"%s%%"</string>
<string name="confirm_export_title" msgid="7648747763127442983">"Confirmer l\'exportation"</string>
<string name="confirm_export_message" msgid="3875683519257829750">"Voulez-vous vraiment exporter la liste de contacts vers \"<xliff:g id="VCARD_FILENAME">%s</xliff:g>\" ?"</string>
<string name="exporting_contact_failed_title" msgid="585823094820602526">"Échec lors de l\'exportation des données du contact"</string>
@@ -236,6 +244,8 @@
<string name="fail_reason_no_exportable_contact" msgid="4919714086648344495">"Aucun contact exportable"</string>
<string name="fail_reason_too_many_vcard" msgid="7084146295639672658">"La carte SD contient trop de fichiers VCard."</string>
<string name="fail_reason_too_long_filename" msgid="1915716071321839166">"Le nom de fichier requis est trop long (\"<xliff:g id="FILENAME">%s</xliff:g>\")."</string>
+ <string name="vcard_exporter_start_message" msgid="6497218576519292369">"Programme d\'exportation de fichiers vCard démarré"</string>
+ <string name="exporting_vcard_finished_title" msgid="1268425436375550862">"Exportation du fichier vCard interrompue"</string>
<string name="exporting_contact_list_title" msgid="9072240631534457415">"Exportation des données des contacts"</string>
<string name="exporting_contact_list_message" msgid="5640326540405486055">"Exportation des données des contacts vers \"<xliff:g id="FILE_NAME">%s</xliff:g>\""</string>
<string name="fail_reason_could_not_initialize_exporter" msgid="4943708332700987376">"Échec de l\'initialisation du module d\'exportation : \"<xliff:g id="EXACT_REASON">%s</xliff:g>\""</string>
@@ -243,8 +253,8 @@
<string name="composer_failed_to_get_database_infomation" msgid="3723109558155169053">"Impossible d\'obtenir les informations concernant la base de données"</string>
<string name="composer_has_no_exportable_contact" msgid="754734132189369094">"Aucun contact n\'est exportable. Si votre téléphone comporte actuellement des contacts, il se peut qu\'un fournisseur de données empêche leur exportation hors de votre téléphone."</string>
<string name="composer_not_initialized" msgid="8041534450748388843">"Le système de composition vCard n\'est pas correctement initialisé."</string>
- <string name="fail_reason_could_not_open_file" msgid="4013520943128739511">"Impossible d\'ouvrir \"<xliff:g id="FILE_NAME">%1$s</xliff:g>\" : <xliff:g id="EXACT_REASON">%2$s</xliff:g>"</string>
- <string name="exporting_contact_list_progress" msgid="560522409559101193">"<xliff:g id="CURRENT_NUMBER">%1$s</xliff:g> sur <xliff:g id="TOTAL_NUMBER">%2$s</xliff:g> contacts"</string>
+ <string name="fail_reason_could_not_open_file" msgid="4013520943128739511">"Impossible d\'ouvrir \"<xliff:g id="FILE_NAME">%s</xliff:g>\" : <xliff:g id="EXACT_REASON">%s</xliff:g>"</string>
+ <string name="exporting_contact_list_progress" msgid="560522409559101193">"<xliff:g id="CURRENT_NUMBER">%s</xliff:g> sur <xliff:g id="TOTAL_NUMBER">%s</xliff:g> contacts"</string>
<string name="search_settings_description" msgid="2675223022992445813">"Noms de vos contacts"</string>
<string name="add_2sec_pause" msgid="9214012315201040129">"Ajouter une pause de 2 s"</string>
<string name="add_wait" msgid="3360818652790319634">"Ajouter Attendre"</string>
@@ -254,6 +264,7 @@
<string name="quickcontact_missing_app" msgid="4600366393134289038">"Aucune application pour gérer cette action"</string>
<string name="quickcontact_remember_choice" msgid="5964536411579749424">"Mémoriser ce choix"</string>
<string name="quickcontact_missing_name" msgid="5590266114306996632">"Inconnu"</string>
+ <string name="quickcontact_no_data" msgid="2098000859125253675">"Aucune donnée"</string>
<string name="menu_accounts" msgid="8499114602017077970">"Comptes"</string>
<string name="menu_import_export" msgid="3765725645491577190">"Importer/Exporter"</string>
<string name="dialog_import_export" msgid="4771877268244096596">"Importer/Exporter les contacts"</string>
@@ -359,6 +370,7 @@
<string name="name_phonetic_family" msgid="462095502140180305">"Phonétique du nom de famille"</string>
<string name="account_type_format" msgid="718948015590343010">"Contact <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
<string name="from_account_format" msgid="687567483928582084">"de <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
+ <string name="account_type_and_name" msgid="2876220035411512934">"Contact <xliff:g id="SOURCE_0">%1$s</xliff:g> de <xliff:g id="SOURCE_1">%2$s</xliff:g>"</string>
<string name="use_photo_as_primary" msgid="8807110122951157246">"Utiliser cette photo"</string>
<string name="contact_read_only" msgid="1203216914575723978">"Vous ne pouvez pas modifier les informations du contact <xliff:g id="SOURCE">%1$s</xliff:g> sur cet appareil."</string>
<string name="no_contact_details" msgid="6754415338321837001">"Aucune autre information pour ce contact"</string>
@@ -374,9 +386,42 @@
<string name="pick_photo" msgid="448886509158039462">"Sélectionner une photo dans la galerie"</string>
<string name="locale_change_in_progress" msgid="1124266507671178413">"Mise à jour de la liste des contacts en cours suite au changement de langue."\n\n"Veuillez patienter..."</string>
<string name="upgrade_in_progress" msgid="7530893673211750223">"Mise à jour de la liste des contacts en cours."\n\n"Veuillez patienter..."</string>
- <string name="upgrade_out_of_memory" msgid="492151063059824962">"Les contacts sont en cours de mise à niveau. "\n\n"La mise à niveau requiert environ <xliff:g id="SIZE_IN_MEGABYTES">%d</xliff:g> Mo de mémoire interne du téléphone."\n\n"Choisissez l\'une des options suivantes :"</string>
+ <string name="upgrade_out_of_memory" msgid="6713914687111678777">"Les contacts sont en cours de mise à niveau. "\n\n"La mise à niveau requiert environ <xliff:g id="SIZE_IN_MEGABYTES">%d</xliff:g> Mo de mémoire interne du téléphone."\n\n"Choisissez l\'une des options suivantes :"</string>
<string name="upgrade_out_of_memory_uninstall" msgid="1721798828992091432">"Désinstaller certaines applications"</string>
<string name="upgrade_out_of_memory_retry" msgid="8431289830472724609">"Réessayer la mise à niveau"</string>
<string name="search_results_for" msgid="8705490885073188513">"Résultats de recherche pour : <xliff:g id="QUERY">%s</xliff:g>"</string>
<string name="search_results_searching" msgid="7755623475227227314">"Recherche en cours..."</string>
+ <string name="adding_recipients" msgid="5123647646892106631">"Chargement en cours…"</string>
+ <string name="menu_display_selected" msgid="6470001164297969034">"Afficher la sélection"</string>
+ <string name="menu_display_all" msgid="8887488642609786198">"Tout afficher"</string>
+ <string name="menu_select_all" msgid="621719255150713545">"Tout sélectionner"</string>
+ <string name="menu_select_none" msgid="7093222469852132345">"Tout désélectionner"</string>
+ <plurals name="multiple_picker_title">
+ <item quantity="one" msgid="4761009734586319101">"1 destinataire sélectionné"</item>
+ <item quantity="other" msgid="4608837420986126229">"<xliff:g id="COUNT">%d</xliff:g> destinataires sélectionnés"</item>
+ </plurals>
+ <string name="unknown_contacts_separator" msgid="2490675836664397214">"Contacts inconnus"</string>
+ <string name="no_contacts_selected" msgid="5877803471037324613">"Aucun contact sélectionné"</string>
+ <string name="add_field" msgid="5257149039253569615">"Ajouter des informations"</string>
+ <string name="contact_status_update_attribution" msgid="752179367353018597">"via <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
+ <string name="contact_status_update_attribution_with_date" msgid="7358045508107825068">"<xliff:g id="DATE">%1$s</xliff:g> via <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
+ <string name="description_edit" msgid="1601490950771217014">"modifier"</string>
+ <string name="description_star" msgid="2605854427360036550">"favori"</string>
+ <string name="edit_contact" msgid="7529281274005689512">"Modifier le contact"</string>
+ <plurals name="merge_info">
+ <item quantity="one" msgid="148365587896371969">"1 contact non fusionné"</item>
+ <item quantity="other" msgid="425683718017380845">"1 contact fusionné à partir de <xliff:g id="COUNT">%0$d</xliff:g> sources"</item>
+ </plurals>
+ <string name="edit_delete_rawcontact" msgid="5054644778227879148">"Supprimer"</string>
+ <string name="edit_error_saving" msgid="2153949392245240166">"Erreur d\'enregistrement"</string>
+ <string name="edit_structured_editor_button" msgid="3830078388142808106">"..."</string>
+ <string name="local_invisible_directory" msgid="6046691709127661065">"Autre"</string>
+ <!-- no translation found for aggregation_suggestion_title:one (7676930148670768645) -->
+ <!-- no translation found for aggregation_suggestion_title:other (3744098467672876675) -->
+ <!-- no translation found for aggregation_suggestion_joined_title (5342738708069602301) -->
+ <skip />
+ <!-- no translation found for aggregation_suggestion_join_button (7248325487963514260) -->
+ <skip />
+ <!-- no translation found for aggregation_suggestion_view_button (8232061564083962522) -->
+ <skip />
</resources>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index cc890cc..c723b04 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -31,6 +31,8 @@
<string name="viewContactTitle" msgid="7989394521836644384">"Dettagli contatto"</string>
<string name="viewContactDesription" msgid="214186610887547860">"Visualizza contatto"</string>
<string name="editContactDescription" msgid="2947202828256214947">"Modifica contatto"</string>
+ <string name="edit_email_caption" msgid="2401084399676154272">"Indirizzo email:"</string>
+ <string name="edit_email_type_caption" msgid="66331218163770698">"Tipo:"</string>
<string name="insertContactDescription" msgid="4709878105452681987">"Crea contatto"</string>
<string name="searchHint" msgid="8482945356247760701">"Cerca contatti"</string>
<string name="menu_search" msgid="9147752853603483719">"Cerca"</string>
@@ -123,13 +125,13 @@
</plurals>
<string name="listTotalAllContactsZero" msgid="6811347506748072822">"Nessun contatto visibile"</string>
<plurals name="listFoundAllContacts">
- <item quantity="one" msgid="2830107332033967280">"1 contatto trovato"</item>
- <item quantity="other" msgid="7752927996850263152">"<xliff:g id="COUNT">%d</xliff:g> contatti trovati"</item>
+ <item quantity="one" msgid="5517063038754171134">"1 trovato"</item>
+ <item quantity="other" msgid="3852668542926965042">"<xliff:g id="COUNT">%d</xliff:g> trovati"</item>
</plurals>
- <string name="listFoundAllContactsZero" msgid="5554368784319460828">"Contatto non trovato"</string>
+ <string name="listFoundAllContactsZero" msgid="777952841930508289">"Non trovati"</string>
<plurals name="searchFoundContacts">
- <item quantity="one" msgid="916648718690661777">"1 contatto"</item>
- <item quantity="other" msgid="5660384247071761844">"<xliff:g id="COUNT">%d</xliff:g> contatti"</item>
+ <item quantity="one" msgid="4826918429708286628">"1 trovato"</item>
+ <item quantity="other" msgid="7988132539476575389">"<xliff:g id="COUNT">%d</xliff:g> trovati"</item>
</plurals>
<string name="contactsIconLabel" msgid="7666609097606552806">"Contatti"</string>
<string name="contactsFavoritesLabel" msgid="8417039765586853670">"Preferiti"</string>
@@ -159,10 +161,10 @@
<string name="simContacts_title" msgid="27341688347689769">"Contatti SIM"</string>
<string name="noContactsHelpTextWithSyncForCreateShortcut" msgid="801504710275614594">"Non ci sono contatti da visualizzare (se hai appena aggiunto un account, la sincronizzazione dei contatti potrebbe richiedere alcuni minuti)."</string>
<string name="noContactsHelpTextForCreateShortcut" msgid="3081286388667108335">"Non ci sono contatti da visualizzare."</string>
- <string name="noContactsHelpText" msgid="6788487368878712350">"Non sono presenti contatti da visualizzare."\n\n"Per aggiungere contatti, premi "<font fgcolor="#ffffffff"><b>"Menu"</b></font>" e tocca:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Account"</b></font>" per aggiungere o configurare un account con contatti sincronizzabili con il telefono"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Nuovo contatto"</b></font>" per creare da zero un nuovo contatto"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importa/esporta"</b></font>\n</li></string>
- <string name="noContactsHelpTextWithSync" msgid="3734101165712848179">"Non sono presenti contatti da visualizzare (se hai appena aggiunto un account, la sincronizzazione dei contatti potrebbe richiedere alcuni minuti)."\n\n"Per aggiungere contatti, premi "<font fgcolor="#ffffffff"><b>"Menu"</b></font>" e tocca:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Account"</b></font>" per aggiungere o configurare un account con contatti sincronizzabili con il telefono"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Opzioni di visualizzazione"</b></font>" per modificare i contatti visibili"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Nuovo contatto"</b></font>" per creare da zero un nuovo contatto"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importa/esporta"</b></font>\n</li></string>
- <string name="noContactsNoSimHelpText" msgid="6553845386917463292">"Non sono presenti contatti da visualizzare."\n\n"Per aggiungere contatti, premi "<font fgcolor="#ffffffff"><b>"Menu"</b></font>" e tocca:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Account"</b></font>" per aggiungere o configurare un account con contatti sincronizzabili con il telefono"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Nuovo contatto"</b></font>" per creare da zero un nuovo contatto"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importa/esporta"</b></font>\n</li></string>
- <string name="noContactsNoSimHelpTextWithSync" msgid="1122296298361373488">"Non sono presenti contatti da visualizzare (se hai appena aggiunto un account, la sincronizzazione dei contatti potrebbe richiedere alcuni minuti)."\n\n"Per aggiungere contatti, premi "<font fgcolor="#ffffffff"><b>"Menu"</b></font>" e tocca:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Account"</b></font>" per aggiungere o configurare un account con contatti sincronizzabili con il telefono"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Opzioni di visualizzazione"</b></font>" per modificare i contatti visibili"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Nuovo contatto"</b></font>" per creare da zero un nuovo contatto"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importa/esporta"</b></font>\n</li></string>
+ <string name="noContactsHelpText" msgid="7633826236417884130">"Non sono presenti contatti da visualizzare."\n\n"Per aggiungere contatti, premi "<font fgcolor="#ffffffff"><b>"Menu"</b></font>" e tocca:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Account"</b></font>" per aggiungere o configurare un account con contatti sincronizzabili con il telefono"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Nuovo contatto"</b></font>" per creare da zero un nuovo contatto"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importa/esporta"</b></font>" per importare contatti dalla SIM o dalla scheda SD"\n</li></string>
+ <string name="noContactsHelpTextWithSync" msgid="3017521127042216243">"Non sono presenti contatti da visualizzare (se hai appena aggiunto un account, la sincronizzazione dei contatti potrebbe richiedere alcuni minuti)."\n\n"Per aggiungere contatti, premi "<font fgcolor="#ffffffff"><b>"Menu"</b></font>" e tocca:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Account"</b></font>" per aggiungere o configurare un account con contatti sincronizzabili con il telefono"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Opzioni di visualizzazione"</b></font>" per modificare i contatti visibili"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Nuovo contatto"</b></font>" per creare da zero un nuovo contatto"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importa/esporta"</b></font>" per importare contatti dalla SIM o dalla scheda SD"\n</li></string>
+ <string name="noContactsNoSimHelpText" msgid="467658807711582876">"Non sono presenti contatti da visualizzare."\n\n"Per aggiungere contatti, premi "<font fgcolor="#ffffffff"><b>"Menu"</b></font>" e tocca:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Account"</b></font>" per aggiungere o configurare un account con contatti sincronizzabili con il telefono"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Nuovo contatto"</b></font>" per creare da zero un nuovo contatto"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importa/esporta"</b></font>" per importare contatti dalla scheda SD"\n</li></string>
+ <string name="noContactsNoSimHelpTextWithSync" msgid="9040060730467973050">"Non sono presenti contatti da visualizzare (se hai appena aggiunto un account, la sincronizzazione dei contatti potrebbe richiedere alcuni minuti)."\n\n"Per aggiungere contatti, premi "<font fgcolor="#ffffffff"><b>"Menu"</b></font>" e tocca:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Account"</b></font>" per aggiungere o configurare un account con contatti sincronizzabili con il telefono"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Opzioni di visualizzazione"</b></font>" per modificare i contatti visibili"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Nuovo contatto"</b></font>" per creare da zero un nuovo contatto"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importa/esporta"</b></font>" per importare contatti dalla scheda SD"\n</li></string>
<string name="noFavoritesHelpText" msgid="3744655776704833277">"Non sono presenti preferiti."\n\n"Per aggiungere un contatto al tuo elenco di preferiti:"\n\n<li>"Tocca la scheda "<b>"Contatti."</b>\n</li>" "\n<li>"Tocca il contatto da aggiungere ai preferiti."\n</li>" "\n<li>"Tocca la stella accanto al nome del contatto."\n</li></string>
<string name="liveFolder_all_label" msgid="5961411940473276616">"Tutti i contatti"</string>
<string name="liveFolder_favorites_label" msgid="2674341514070517105">"Speciali"</string>
@@ -179,7 +181,7 @@
<string name="callBack" msgid="5498224409038809224">"Richiama"</string>
<string name="callAgain" msgid="3197312117049874778">"Richiama"</string>
<string name="returnCall" msgid="8171961914203617813">"Chiama numero"</string>
- <string name="callDetailsDurationFormat" msgid="8157706382818184268">"<xliff:g id="MINUTES">%1$s</xliff:g> min. <xliff:g id="SECONDS">%2$s</xliff:g> sec."</string>
+ <string name="callDetailsDurationFormat" msgid="8157706382818184268">"<xliff:g id="MINUTES">%s</xliff:g> min. <xliff:g id="SECONDS">%s</xliff:g> sec."</string>
<string name="favoritesFrquentSeparator" msgid="8107518433381283736">"Contattati spesso"</string>
<string name="add_contact_dlg_title" msgid="2896685845822146494">"Aggiungi contatto"</string>
<string name="add_contact_dlg_message_fmt" msgid="7986472669444326576">"Aggiungi \"<xliff:g id="EMAIL">%s</xliff:g>\" ai contatti?"</string>
@@ -217,18 +219,24 @@
<string name="scanning_sdcard_failed_title" msgid="3506782007953167180">"Analisi scheda SD non riuscita"</string>
<string name="scanning_sdcard_failed_message" msgid="3761992500690182922">"Analisi scheda SD non riuscita (motivo: \"<xliff:g id="FAIL_REASON">%s</xliff:g>\")"</string>
<string name="fail_reason_io_error" msgid="5922864781066136340">"Errore I/O"</string>
+ <string name="fail_reason_low_memory_during_import" msgid="7514918659342886381">"Memoria insufficiente (il file potrebbe essere di dimensioni eccessive)"</string>
<string name="fail_reason_vcard_parse_error" msgid="1201233722762680214">"Impossibile analizzare la vCard per motivi imprevisti"</string>
<string name="fail_reason_vcard_not_supported_error" msgid="655208100451286027">"Impossibile analizzare la vCard nonostante sembri avere un formato valido perché l\'implementazione corrente non la supporta"</string>
<string name="fail_reason_no_vcard_file" msgid="6376516175882881595">"Nessun file vCard trovato sulla scheda SD"</string>
+ <string name="fail_reason_failed_to_collect_vcard_meta_info" msgid="4154492282316067754">"Raccolta dei metadati dei file vCard forniti non riuscita."</string>
<string name="fail_reason_failed_to_read_files" msgid="3659521123567134029">"Impossibile importare uno o più file (%s)."</string>
<string name="fail_reason_unknown" msgid="999034019513096768">"Errore sconosciuto"</string>
<string name="select_vcard_title" msgid="3968948173786172468">"Seleziona file vCard"</string>
- <string name="progress_shower_message" msgid="5636525578293752526">"<xliff:g id="ACTION">%1$s</xliff:g>"\n"<xliff:g id="FILENAME">%2$s</xliff:g>"</string>
- <string name="reading_vcard_title" msgid="4723433501579653199">"Lettura vCard"</string>
- <string name="reading_vcard_message" msgid="6381368920030748743">"Lettura file vCard"</string>
- <string name="reading_vcard_failed_title" msgid="4923008144735294994">"Lettura dati vCard non riuscita"</string>
- <string name="reading_vcard_contacts" msgid="3066834102042012868">"<xliff:g id="CURRENT_NUMBER">%1$s</xliff:g> contatti su <xliff:g id="TOTAL_NUMBER">%2$s</xliff:g>"</string>
- <string name="reading_vcard_files" msgid="34180143726972661">"<xliff:g id="CURRENT_NUMBER">%1$s</xliff:g> file su <xliff:g id="TOTAL_NUMBER">%2$s</xliff:g>"</string>
+ <string name="caching_vcard_title" msgid="5009556022082659780">"Memorizzazione delle vCard nella cache di archiviazione temporanea locale"</string>
+ <string name="caching_vcard_message" msgid="2380844718093378900">"L\'importazione memorizzerà le vCard nella cache di archiviazione temporanea locale. L\'importazione reale inizierà a breve."</string>
+ <string name="progress_notifier_message" msgid="8412196553907067659">"<xliff:g id="CURRENT_NUMBER">%s</xliff:g>/<xliff:g id="TOTAL_NUMBER">%s</xliff:g>: <xliff:g id="FILENAME">%s</xliff:g>"</string>
+ <string name="reading_vcard_title" msgid="3324988724123717025">"Lettura vCard"</string>
+ <string name="reading_vcard_failed_title" msgid="2162610359561887043">"Lettura dati vCard non riuscita"</string>
+ <string name="reading_vcard_canceled_title" msgid="1770608329958463131">"Lettura dati vCard annullata"</string>
+ <string name="importing_vcard_finished_title" msgid="3213257229785702182">"Importazione vCard terminata"</string>
+ <string name="vcard_importer_start_message" msgid="5520549352987753320">"Importazione vCard avviata."</string>
+ <string name="vcard_importer_will_start_message" msgid="2055145402481822160">"La vCard verrà importata successivamente."</string>
+ <string name="percentage" msgid="34897865327092209">"%s%%"</string>
<string name="confirm_export_title" msgid="7648747763127442983">"Conferma esportazione"</string>
<string name="confirm_export_message" msgid="3875683519257829750">"Esportare l\'elenco di contatti in \"<xliff:g id="VCARD_FILENAME">%s</xliff:g>\"?"</string>
<string name="exporting_contact_failed_title" msgid="585823094820602526">"Esportazione dati contatti non riuscita"</string>
@@ -236,6 +244,8 @@
<string name="fail_reason_no_exportable_contact" msgid="4919714086648344495">"Nessun contatto esportabile"</string>
<string name="fail_reason_too_many_vcard" msgid="7084146295639672658">"Troppi dati vCard sulla scheda SD"</string>
<string name="fail_reason_too_long_filename" msgid="1915716071321839166">"Il nome file richiesto è troppo lungo (\"<xliff:g id="FILENAME">%s</xliff:g>\")"</string>
+ <string name="vcard_exporter_start_message" msgid="6497218576519292369">"Esportazione vCard avviata."</string>
+ <string name="exporting_vcard_finished_title" msgid="1268425436375550862">"Esportazione vCard terminata"</string>
<string name="exporting_contact_list_title" msgid="9072240631534457415">"Esportazione dati di contatto"</string>
<string name="exporting_contact_list_message" msgid="5640326540405486055">"Esportazione dati di contatto in \"<xliff:g id="FILE_NAME">%s</xliff:g>\""</string>
<string name="fail_reason_could_not_initialize_exporter" msgid="4943708332700987376">"Impossibile inizializzare l\'utilità di esportazione: \"<xliff:g id="EXACT_REASON">%s</xliff:g>\""</string>
@@ -243,8 +253,8 @@
<string name="composer_failed_to_get_database_infomation" msgid="3723109558155169053">"Recupero informazioni database non riuscito"</string>
<string name="composer_has_no_exportable_contact" msgid="754734132189369094">"Nessun contatto esportabile. Se nel tuo telefono sono presenti dei contatti, qualche provider di dati potrebbe impedire l\'esportazione di tutti i contatti all\'esterno del telefono."</string>
<string name="composer_not_initialized" msgid="8041534450748388843">"Il compositore di vCard non è correttamente inizializzato"</string>
- <string name="fail_reason_could_not_open_file" msgid="4013520943128739511">"Impossibile aprire \"<xliff:g id="FILE_NAME">%1$s</xliff:g>\": <xliff:g id="EXACT_REASON">%2$s</xliff:g>"</string>
- <string name="exporting_contact_list_progress" msgid="560522409559101193">"<xliff:g id="CURRENT_NUMBER">%1$s</xliff:g> contatti su <xliff:g id="TOTAL_NUMBER">%2$s</xliff:g>"</string>
+ <string name="fail_reason_could_not_open_file" msgid="4013520943128739511">"Impossibile aprire \"<xliff:g id="FILE_NAME">%s</xliff:g>\": <xliff:g id="EXACT_REASON">%s</xliff:g>"</string>
+ <string name="exporting_contact_list_progress" msgid="560522409559101193">"<xliff:g id="CURRENT_NUMBER">%s</xliff:g> contatti su <xliff:g id="TOTAL_NUMBER">%s</xliff:g>"</string>
<string name="search_settings_description" msgid="2675223022992445813">"I nomi dei tuoi contatti"</string>
<string name="add_2sec_pause" msgid="9214012315201040129">"Aggiungi pausa 2 sec"</string>
<string name="add_wait" msgid="3360818652790319634">"Aggiungi attesa"</string>
@@ -254,6 +264,7 @@
<string name="quickcontact_missing_app" msgid="4600366393134289038">"Nessuna applicazione trovata in grado di gestire questa azione"</string>
<string name="quickcontact_remember_choice" msgid="5964536411579749424">"Memorizza questa scelta"</string>
<string name="quickcontact_missing_name" msgid="5590266114306996632">"Sconosciuto"</string>
+ <string name="quickcontact_no_data" msgid="2098000859125253675">"Nessun dato"</string>
<string name="menu_accounts" msgid="8499114602017077970">"Account"</string>
<string name="menu_import_export" msgid="3765725645491577190">"Importa/esporta"</string>
<string name="dialog_import_export" msgid="4771877268244096596">"Importa/esporta contatti"</string>
@@ -359,6 +370,7 @@
<string name="name_phonetic_family" msgid="462095502140180305">"Cognome fonetico"</string>
<string name="account_type_format" msgid="718948015590343010">"Contatto da <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
<string name="from_account_format" msgid="687567483928582084">"da <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
+ <string name="account_type_and_name" msgid="2876220035411512934">"Contatto <xliff:g id="SOURCE_0">%1$s</xliff:g> da <xliff:g id="SOURCE_1">%2$s</xliff:g>"</string>
<string name="use_photo_as_primary" msgid="8807110122951157246">"Utilizza questa foto"</string>
<string name="contact_read_only" msgid="1203216914575723978">"Informazioni del contatto da <xliff:g id="SOURCE">%1$s</xliff:g> non modificabili su questo dispositivo."</string>
<string name="no_contact_details" msgid="6754415338321837001">"Nessuna informazione aggiuntiva per questo contatto"</string>
@@ -374,9 +386,42 @@
<string name="pick_photo" msgid="448886509158039462">"Seleziona foto da galleria"</string>
<string name="locale_change_in_progress" msgid="1124266507671178413">"L\'elenco contatti verrà aggiornato per rispecchiare il cambio di lingua."\n\n"Attendi..."</string>
<string name="upgrade_in_progress" msgid="7530893673211750223">"Elenco contatti in fase di aggiornamento."\n\n"Attendi..."</string>
- <string name="upgrade_out_of_memory" msgid="492151063059824962">"Contatti in fase di aggiornamento. "\n\n"Il processo di aggiornamento richiede circa <xliff:g id="SIZE_IN_MEGABYTES">%d</xliff:g> MB di memoria interna del telefono."\n\n"Scegli una delle seguenti opzioni:"</string>
+ <string name="upgrade_out_of_memory" msgid="6713914687111678777">"Contatti in fase di aggiornamento. "\n\n"Il processo di aggiornamento richiede circa <xliff:g id="SIZE_IN_MEGABYTES">%d</xliff:g> MB di memoria interna del telefono."\n\n"Scegli una delle seguenti opzioni:"</string>
<string name="upgrade_out_of_memory_uninstall" msgid="1721798828992091432">"Disinstalla alcune applicazioni"</string>
<string name="upgrade_out_of_memory_retry" msgid="8431289830472724609">"Riprova l\'aggiornamento"</string>
<string name="search_results_for" msgid="8705490885073188513">"Risultati di ricerca per: <xliff:g id="QUERY">%s</xliff:g>"</string>
<string name="search_results_searching" msgid="7755623475227227314">"Ricerca in corso..."</string>
+ <string name="adding_recipients" msgid="5123647646892106631">"Caricamento in corso…"</string>
+ <string name="menu_display_selected" msgid="6470001164297969034">"Mostra selezionati"</string>
+ <string name="menu_display_all" msgid="8887488642609786198">"Mostra tutto"</string>
+ <string name="menu_select_all" msgid="621719255150713545">"Seleziona tutto"</string>
+ <string name="menu_select_none" msgid="7093222469852132345">"Deseleziona tutto"</string>
+ <plurals name="multiple_picker_title">
+ <item quantity="one" msgid="4761009734586319101">"1 destinatario selezionato"</item>
+ <item quantity="other" msgid="4608837420986126229">"<xliff:g id="COUNT">%d</xliff:g> destinatari selezionati"</item>
+ </plurals>
+ <string name="unknown_contacts_separator" msgid="2490675836664397214">"Contatti sconosciuti"</string>
+ <string name="no_contacts_selected" msgid="5877803471037324613">"Nessun contatto selezionato."</string>
+ <string name="add_field" msgid="5257149039253569615">"Aggiungi informazioni"</string>
+ <string name="contact_status_update_attribution" msgid="752179367353018597">"tramite <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
+ <string name="contact_status_update_attribution_with_date" msgid="7358045508107825068">"<xliff:g id="DATE">%1$s</xliff:g> tramite <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
+ <string name="description_edit" msgid="1601490950771217014">"modifica"</string>
+ <string name="description_star" msgid="2605854427360036550">"preferiti"</string>
+ <string name="edit_contact" msgid="7529281274005689512">"Modifica contatto"</string>
+ <plurals name="merge_info">
+ <item quantity="one" msgid="148365587896371969">"non uniti"</item>
+ <item quantity="other" msgid="425683718017380845">"uniti da <xliff:g id="COUNT">%0$d</xliff:g> origini"</item>
+ </plurals>
+ <string name="edit_delete_rawcontact" msgid="5054644778227879148">"Elimina"</string>
+ <string name="edit_error_saving" msgid="2153949392245240166">"Errore di salvataggio"</string>
+ <string name="edit_structured_editor_button" msgid="3830078388142808106">"..."</string>
+ <string name="local_invisible_directory" msgid="6046691709127661065">"Altro"</string>
+ <!-- no translation found for aggregation_suggestion_title:one (7676930148670768645) -->
+ <!-- no translation found for aggregation_suggestion_title:other (3744098467672876675) -->
+ <!-- no translation found for aggregation_suggestion_joined_title (5342738708069602301) -->
+ <skip />
+ <!-- no translation found for aggregation_suggestion_join_button (7248325487963514260) -->
+ <skip />
+ <!-- no translation found for aggregation_suggestion_view_button (8232061564083962522) -->
+ <skip />
</resources>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index fe912ab..6da8cb0 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -31,6 +31,8 @@
<string name="viewContactTitle" msgid="7989394521836644384">"連絡先情報"</string>
<string name="viewContactDesription" msgid="214186610887547860">"連絡先を表示"</string>
<string name="editContactDescription" msgid="2947202828256214947">"連絡先を編集"</string>
+ <string name="edit_email_caption" msgid="2401084399676154272">"メールアドレス:"</string>
+ <string name="edit_email_type_caption" msgid="66331218163770698">"種類:"</string>
<string name="insertContactDescription" msgid="4709878105452681987">"連絡先を新規登録"</string>
<string name="searchHint" msgid="8482945356247760701">"連絡先を検索"</string>
<string name="menu_search" msgid="9147752853603483719">"検索"</string>
@@ -123,13 +125,13 @@
</plurals>
<string name="listTotalAllContactsZero" msgid="6811347506748072822">"表示可能な連絡先はありません"</string>
<plurals name="listFoundAllContacts">
- <item quantity="one" msgid="2830107332033967280">"連絡先が1件あります"</item>
- <item quantity="other" msgid="7752927996850263152">"連絡先が<xliff:g id="COUNT">%d</xliff:g>件あります"</item>
+ <item quantity="one" msgid="5517063038754171134">"1件見つかりました"</item>
+ <item quantity="other" msgid="3852668542926965042">"<xliff:g id="COUNT">%d</xliff:g>件見つかりました"</item>
</plurals>
- <string name="listFoundAllContactsZero" msgid="5554368784319460828">"連絡先が見つかりません"</string>
+ <string name="listFoundAllContactsZero" msgid="777952841930508289">"見つかりませんでした"</string>
<plurals name="searchFoundContacts">
- <item quantity="one" msgid="916648718690661777">"連絡先1件"</item>
- <item quantity="other" msgid="5660384247071761844">"連絡先<xliff:g id="COUNT">%d</xliff:g>件"</item>
+ <item quantity="one" msgid="4826918429708286628">"1件見つかりました"</item>
+ <item quantity="other" msgid="7988132539476575389">"<xliff:g id="COUNT">%d</xliff:g>件見つかりました"</item>
</plurals>
<string name="contactsIconLabel" msgid="7666609097606552806">"連絡先"</string>
<string name="contactsFavoritesLabel" msgid="8417039765586853670">"お気入り"</string>
@@ -159,10 +161,10 @@
<string name="simContacts_title" msgid="27341688347689769">"SIMカードの連絡先"</string>
<string name="noContactsHelpTextWithSyncForCreateShortcut" msgid="801504710275614594">"表示する連絡先がありません(アカウントを追加した直後の場合は、連絡先の同期に数分かかることがあります)。"</string>
<string name="noContactsHelpTextForCreateShortcut" msgid="3081286388667108335">"表示する連絡先がありません。"</string>
- <string name="noContactsHelpText" msgid="6788487368878712350">"表示できる連絡先がありません。"\n\n"連絡先を追加するには、まず"<font fgcolor="#ffffffff"><b>"MENU"</b></font>"キーを押し: "\n" "\n<li>"電話との同期が可能な連絡先のアカウントを追加または設定する場合は["<font fgcolor="#ffffffff"><b>"アカウント"</b></font>"]をタップします"\n</li>" "\n<li>"新しい連絡先を一から作成する場合は["<font fgcolor="#ffffffff"><b>"連絡先を新規登録"</b></font>"]をタップします"\n</li>" "\n<li>"["<font fgcolor="#ffffffff"><b>"インポート/エクスポート"</b></font>"]"\n</li></string>
- <string name="noContactsHelpTextWithSync" msgid="3734101165712848179">"表示できる連絡先がありません(アカウントを追加した場合は、連絡先が同期されるまでに数分かかることがあります)。"\n\n"連絡先を追加するには、まず"<font fgcolor="#ffffffff"><b>"MENU"</b></font>"キーを押し: "\n" "\n<li>"電話との同期が可能な連絡先のアカウントを追加または設定する場合は["<font fgcolor="#ffffffff"><b>"アカウント"</b></font>"]をタップします"\n</li>" "\n<li>"表示される連絡先を変更するには["<font fgcolor="#ffffffff"><b>"表示オプション"</b></font>"]をタップします"\n</li>" "\n<li>"新しい連絡先を最初から作成する場合は["<font fgcolor="#ffffffff"><b>"連絡先を新規登録"</b></font>"]をタップします"\n</li>" "\n<li>"["<font fgcolor="#ffffffff"><b>"インポート/エクスポート"</b></font>"]"\n</li></string>
- <string name="noContactsNoSimHelpText" msgid="6553845386917463292">"表示できる連絡先がありません。"\n\n"連絡先を追加するには、まず"<font fgcolor="#ffffffff"><b>"MENU"</b></font>"キーを押し: "\n" "\n<li>"電話との同期が可能な連絡先のアカウントを追加または設定する場合は["<font fgcolor="#ffffffff"><b>"アカウント"</b></font>"]をタップします"\n</li>" "\n<li>"新しい連絡先を最初から作成する場合は["<font fgcolor="#ffffffff"><b>"連絡先を新規登録"</b></font>"]をタップします"\n</li>" "\n<li>"["<font fgcolor="#ffffffff"><b>"インポート/エクスポート"</b></font>"]"\n</li></string>
- <string name="noContactsNoSimHelpTextWithSync" msgid="1122296298361373488">"表示できる連絡先がありません(アカウントを追加した場合は、連絡先が同期されるまでに数分かかることがあります)。"\n\n"連絡先を追加するには、まず"<font fgcolor="#ffffffff"><b>"MENU"</b></font>"キーを押し: "\n" "\n<li>"電話との同期が可能な連絡先のアカウントを追加または設定する場合は["<font fgcolor="#ffffffff"><b>"アカウント"</b></font>"]をタップします"\n</li>" "\n<li>"表示される連絡先を変更するには["<font fgcolor="#ffffffff"><b>"表示オプション"</b></font>"]をタップします"\n</li>" "\n<li>"新しい連絡先を最初から作成する場合は["<font fgcolor="#ffffffff"><b>"連絡先を新規登録"</b></font>"]をタップします"\n</li>" "\n<li>"["<font fgcolor="#ffffffff"><b>"インポート/エクスポート"</b></font>"]"\n</li></string>
+ <string name="noContactsHelpText" msgid="7633826236417884130">"表示できる連絡先がありません。"\n\n"連絡先を追加するには、まず"<font fgcolor="#ffffffff"><b>"MENU"</b></font>"キーを押し:"\n\n<li>"電話との同期が可能な連絡先のアカウントを追加または設定する場合は["<font fgcolor="#ffffffff"><b>"アカウント"</b></font>"]をタップします"\n</li>\n<li>"新しい連絡先を最初から作成する場合は["<font fgcolor="#ffffffff"><b>"連絡先を新規登録"</b></font>"]をタップします"\n</li>" "\n<li>"SIMまたはSDカードから連絡先をインポートする場合は["<font fgcolor="#ffffffff"><b>"インポート/エクスポート"</b></font>"]をタップします"\n</li></string>
+ <string name="noContactsHelpTextWithSync" msgid="3017521127042216243">"表示できる連絡先がありません(アカウントを追加した場合は、連絡先が同期されるまでに数分かかることがあります)。"\n\n"連絡先を追加するには、まず"<font fgcolor="#ffffffff"><b>"MENU"</b></font>"キーを押し:"\n\n<li>"電話との同期が可能な連絡先のアカウントを追加または設定する場合は["<font fgcolor="#ffffffff"><b>"アカウント"</b></font>"]をタップします"\n</li>\n<li>"表示される連絡先を変更するには["<font fgcolor="#ffffffff"><b>"表示オプション"</b></font>"]をタップします"\n</li>" "\n<li>"新しい連絡先を最初から作成する場合は["<font fgcolor="#ffffffff"><b>"連絡先を新規登録"</b></font>"]をタップします"\n</li>" "\n<li>"SIMまたはSDカードから連絡先をインポートする場合は["<font fgcolor="#ffffffff"><b>"インポート/エクスポート"</b></font>"]をタップします"\n</li></string>
+ <string name="noContactsNoSimHelpText" msgid="467658807711582876">"表示できる連絡先がありません。"\n\n"連絡先を追加するには、まず"<font fgcolor="#ffffffff"><b>"MENU"</b></font>"キーを押し:"\n\n<li>"電話との同期が可能な連絡先のアカウントを追加または設定する場合は["<font fgcolor="#ffffffff"><b>"アカウント"</b></font>"]をタップします"\n</li>\n<li>"新しい連絡先を最初から作成する場合は["<font fgcolor="#ffffffff"><b>"連絡先を新規登録"</b></font>"]をタップします"\n</li>" "\n<li>"SDカードから連絡先をインポートする場合は["<font fgcolor="#ffffffff"><b>"インポート/エクスポート"</b></font>"]をタップします"\n</li></string>
+ <string name="noContactsNoSimHelpTextWithSync" msgid="9040060730467973050">"表示できる連絡先がありません(アカウントを追加した場合は、連絡先が同期されるまでに数分かかることがあります)。"\n\n"連絡先を追加するには、まず"<font fgcolor="#ffffffff"><b>"MENU"</b></font>"キーを押し:"\n\n<li>"電話との同期が可能な連絡先のアカウントを追加または設定する場合は["<font fgcolor="#ffffffff"><b>"アカウント"</b></font>"]をタップします"\n</li>\n<li>"表示される連絡先を変更するには["<font fgcolor="#ffffffff"><b>"表示オプション"</b></font>"]をタップします"\n</li>" "\n<li>"新しい連絡先を最初から作成する場合は["<font fgcolor="#ffffffff"><b>"連絡先を新規登録"</b></font>"]をタップします"\n</li>" "\n<li>"SDカードから連絡先をインポートする場合は["<font fgcolor="#ffffffff"><b>"インポート/エクスポート"</b></font>"]をタップします"\n</li></string>
<string name="noFavoritesHelpText" msgid="3744655776704833277">"お気に入りはありません。"\n\n"お気に入りのリストに連絡先を追加するには: "\n\n" "<li>"["<b>"連絡先"</b>"]タブをタップします"\n</li>" "\n<li>"お気に入りに追加する連絡先をタップします"\n</li>" "\n<li>"連絡先名の横にあるスターをタップします"\n</li></string>
<string name="liveFolder_all_label" msgid="5961411940473276616">"すべての連絡先"</string>
<string name="liveFolder_favorites_label" msgid="2674341514070517105">"スター付き"</string>
@@ -179,7 +181,7 @@
<string name="callBack" msgid="5498224409038809224">"コールバック"</string>
<string name="callAgain" msgid="3197312117049874778">"再発信"</string>
<string name="returnCall" msgid="8171961914203617813">"発信"</string>
- <string name="callDetailsDurationFormat" msgid="8157706382818184268">"<xliff:g id="MINUTES">%1$s</xliff:g>分<xliff:g id="SECONDS">%2$s</xliff:g>秒"</string>
+ <string name="callDetailsDurationFormat" msgid="8157706382818184268">"<xliff:g id="MINUTES">%s</xliff:g>分<xliff:g id="SECONDS">%s</xliff:g>秒"</string>
<string name="favoritesFrquentSeparator" msgid="8107518433381283736">"よく使う連絡先"</string>
<string name="add_contact_dlg_title" msgid="2896685845822146494">"連絡先を追加"</string>
<string name="add_contact_dlg_message_fmt" msgid="7986472669444326576">"<xliff:g id="EMAIL">%s</xliff:g> を連絡先に追加しますか?"</string>
@@ -217,18 +219,24 @@
<string name="scanning_sdcard_failed_title" msgid="3506782007953167180">"SDカードのスキャンに失敗しました"</string>
<string name="scanning_sdcard_failed_message" msgid="3761992500690182922">"SDカードのスキャンに失敗しました(理由: <xliff:g id="FAIL_REASON">%s</xliff:g>)"</string>
<string name="fail_reason_io_error" msgid="5922864781066136340">"送受信エラー"</string>
+ <string name="fail_reason_low_memory_during_import" msgid="7514918659342886381">"メモリが不足しています(ファイルが大きすぎる可能性があります)"</string>
<string name="fail_reason_vcard_parse_error" msgid="1201233722762680214">"予期しない理由によりvCardの解析に失敗しました"</string>
<string name="fail_reason_vcard_not_supported_error" msgid="655208100451286027">"vCardの解析に失敗しました。正しいフォーマットですが、現在サポートされていません。"</string>
<string name="fail_reason_no_vcard_file" msgid="6376516175882881595">"SDカードでvCardファイルが見つかりません"</string>
+ <string name="fail_reason_failed_to_collect_vcard_meta_info" msgid="4154492282316067754">"指定されたvCardファイルのメタ情報を取得できませんでした。"</string>
<string name="fail_reason_failed_to_read_files" msgid="3659521123567134029">"1つ以上のファイルをインポートできませんでした(%s)。"</string>
<string name="fail_reason_unknown" msgid="999034019513096768">"不明なエラー"</string>
<string name="select_vcard_title" msgid="3968948173786172468">"vCardファイルの選択"</string>
- <string name="progress_shower_message" msgid="5636525578293752526">"<xliff:g id="ACTION">%1$s</xliff:g>"\n"<xliff:g id="FILENAME">%2$s</xliff:g>"</string>
- <string name="reading_vcard_title" msgid="4723433501579653199">"vCardを読み取り中"</string>
- <string name="reading_vcard_message" msgid="6381368920030748743">"vCardファイルを読み取り中"</string>
- <string name="reading_vcard_failed_title" msgid="4923008144735294994">"vCardデータの読み取りに失敗しました"</string>
- <string name="reading_vcard_contacts" msgid="3066834102042012868">"<xliff:g id="CURRENT_NUMBER">%1$s</xliff:g>/<xliff:g id="TOTAL_NUMBER">%2$s</xliff:g>件の連絡先"</string>
- <string name="reading_vcard_files" msgid="34180143726972661">"<xliff:g id="CURRENT_NUMBER">%1$s</xliff:g>/<xliff:g id="TOTAL_NUMBER">%2$s</xliff:g>件のファイル"</string>
+ <string name="caching_vcard_title" msgid="5009556022082659780">"vCardをローカル一時ストレージにキャッシュしています"</string>
+ <string name="caching_vcard_message" msgid="2380844718093378900">"vCardをローカル一時ストレージにキャッシュしています。間もなくインポート処理を開始します。"</string>
+ <string name="progress_notifier_message" msgid="8412196553907067659">"<xliff:g id="CURRENT_NUMBER">%s</xliff:g>/<xliff:g id="TOTAL_NUMBER">%s</xliff:g>件: <xliff:g id="FILENAME">%s</xliff:g>"</string>
+ <string name="reading_vcard_title" msgid="3324988724123717025">"vCardを読み取り中"</string>
+ <string name="reading_vcard_failed_title" msgid="2162610359561887043">"vCardデータを読み取れませんでした"</string>
+ <string name="reading_vcard_canceled_title" msgid="1770608329958463131">"vCardの読み取りがキャンセルされました"</string>
+ <string name="importing_vcard_finished_title" msgid="3213257229785702182">"vCardのインポートを停止しました"</string>
+ <string name="vcard_importer_start_message" msgid="5520549352987753320">"vCardインポータが起動しました。"</string>
+ <string name="vcard_importer_will_start_message" msgid="2055145402481822160">"vCardは後ほどvCardインポータによってインポートされます。"</string>
+ <string name="percentage" msgid="34897865327092209">"%s%%"</string>
<string name="confirm_export_title" msgid="7648747763127442983">"エクスポートの確認"</string>
<string name="confirm_export_message" msgid="3875683519257829750">"連絡先リストを「<xliff:g id="VCARD_FILENAME">%s</xliff:g>」にエクスポートしますか?"</string>
<string name="exporting_contact_failed_title" msgid="585823094820602526">"連絡先データのエクスポートに失敗しました"</string>
@@ -236,6 +244,8 @@
<string name="fail_reason_no_exportable_contact" msgid="4919714086648344495">"エクスポートできる連絡先がありません"</string>
<string name="fail_reason_too_many_vcard" msgid="7084146295639672658">"SDカードのvCardファイルが多すぎます"</string>
<string name="fail_reason_too_long_filename" msgid="1915716071321839166">"指定したファイル名が長すぎます(「<xliff:g id="FILENAME">%s</xliff:g>」)"</string>
+ <string name="vcard_exporter_start_message" msgid="6497218576519292369">"vCardエクスポータが起動しました。"</string>
+ <string name="exporting_vcard_finished_title" msgid="1268425436375550862">"vCardのエクスポートを停止しました"</string>
<string name="exporting_contact_list_title" msgid="9072240631534457415">"連絡先データのエクスポート"</string>
<string name="exporting_contact_list_message" msgid="5640326540405486055">"連絡先データを\"<xliff:g id="FILE_NAME">%s</xliff:g>\"にエクスポートしています"</string>
<string name="fail_reason_could_not_initialize_exporter" msgid="4943708332700987376">"エクスポータを初期化できませんでした: \"<xliff:g id="EXACT_REASON">%s</xliff:g>\""</string>
@@ -243,8 +253,8 @@
<string name="composer_failed_to_get_database_infomation" msgid="3723109558155169053">"データベース情報の取得に失敗しました"</string>
<string name="composer_has_no_exportable_contact" msgid="754734132189369094">"エクスポートできる連絡先がありません。携帯電話に連絡先を登録している場合、データプロバイダが外部への連絡先のエクスポートをすべて禁止している可能性があります。"</string>
<string name="composer_not_initialized" msgid="8041534450748388843">"vCardコンポーザーが正しく初期化されていません"</string>
- <string name="fail_reason_could_not_open_file" msgid="4013520943128739511">"\"<xliff:g id="FILE_NAME">%1$s</xliff:g>\"を開けませんでした: <xliff:g id="EXACT_REASON">%2$s</xliff:g>"</string>
- <string name="exporting_contact_list_progress" msgid="560522409559101193">"<xliff:g id="CURRENT_NUMBER">%1$s</xliff:g>/<xliff:g id="TOTAL_NUMBER">%2$s</xliff:g>件のファイル"</string>
+ <string name="fail_reason_could_not_open_file" msgid="4013520943128739511">"\"<xliff:g id="FILE_NAME">%s</xliff:g>\"を開けませんでした: <xliff:g id="EXACT_REASON">%s</xliff:g>"</string>
+ <string name="exporting_contact_list_progress" msgid="560522409559101193">"<xliff:g id="CURRENT_NUMBER">%s</xliff:g>/<xliff:g id="TOTAL_NUMBER">%s</xliff:g>件のファイル"</string>
<string name="search_settings_description" msgid="2675223022992445813">"連絡先の名前"</string>
<string name="add_2sec_pause" msgid="9214012315201040129">"2秒間の停止を追加"</string>
<string name="add_wait" msgid="3360818652790319634">"着信を追加"</string>
@@ -254,6 +264,7 @@
<string name="quickcontact_missing_app" msgid="4600366393134289038">"この操作を行うアプリケーションが見つかりません"</string>
<string name="quickcontact_remember_choice" msgid="5964536411579749424">"この選択を保存"</string>
<string name="quickcontact_missing_name" msgid="5590266114306996632">"不明"</string>
+ <string name="quickcontact_no_data" msgid="2098000859125253675">"データがありません"</string>
<string name="menu_accounts" msgid="8499114602017077970">"アカウント"</string>
<string name="menu_import_export" msgid="3765725645491577190">"インポート/エクスポート"</string>
<string name="dialog_import_export" msgid="4771877268244096596">"連絡先のインポート/エクスポート"</string>
@@ -359,6 +370,7 @@
<string name="name_phonetic_family" msgid="462095502140180305">"姓のよみがな"</string>
<string name="account_type_format" msgid="718948015590343010">"<xliff:g id="SOURCE">%1$s</xliff:g>からの連絡先"</string>
<string name="from_account_format" msgid="687567483928582084">"<xliff:g id="SOURCE">%1$s</xliff:g>からの連絡先"</string>
+ <string name="account_type_and_name" msgid="2876220035411512934">"<xliff:g id="SOURCE_1">%2$s</xliff:g>からの<xliff:g id="SOURCE_0">%1$s</xliff:g>連絡先"</string>
<string name="use_photo_as_primary" msgid="8807110122951157246">"この写真を使用"</string>
<string name="contact_read_only" msgid="1203216914575723978">"<xliff:g id="SOURCE">%1$s</xliff:g>からの連絡先情報はこの携帯端末では編集できません。"</string>
<string name="no_contact_details" msgid="6754415338321837001">"この連絡先の詳細情報はありません"</string>
@@ -374,9 +386,42 @@
<string name="pick_photo" msgid="448886509158039462">"ギャラリーから写真を選ぶ"</string>
<string name="locale_change_in_progress" msgid="1124266507671178413">"言語の変更に伴い連絡先リストを更新しています。"\n\n"しばらくお待ちください..."</string>
<string name="upgrade_in_progress" msgid="7530893673211750223">"連絡先リストを更新しています。"\n\n"お待ちください..."</string>
- <string name="upgrade_out_of_memory" msgid="492151063059824962">"連絡先をアップグレードしています。"\n\n"アップグレード処理には約<xliff:g id="SIZE_IN_MEGABYTES">%d</xliff:g>MBの内部ストレージが必要です。"\n\n"次のいずれかのオプションを選択してください:"</string>
+ <string name="upgrade_out_of_memory" msgid="6713914687111678777">"連絡先をアップグレードしています。"\n\n"アップグレード処理には約<xliff:g id="SIZE_IN_MEGABYTES">%d</xliff:g>MBの内部ストレージが必要です。"\n\n"次のいずれかのオプションを選択してください:"</string>
<string name="upgrade_out_of_memory_uninstall" msgid="1721798828992091432">"一部のアプリケーションをアンインストール"</string>
<string name="upgrade_out_of_memory_retry" msgid="8431289830472724609">"アップグレードを再試行"</string>
<string name="search_results_for" msgid="8705490885073188513">"検索結果: <xliff:g id="QUERY">%s</xliff:g>"</string>
<string name="search_results_searching" msgid="7755623475227227314">"検索中..."</string>
+ <string name="adding_recipients" msgid="5123647646892106631">"読み込み中..."</string>
+ <string name="menu_display_selected" msgid="6470001164297969034">"選択した連絡先を表示"</string>
+ <string name="menu_display_all" msgid="8887488642609786198">"すべて表示"</string>
+ <string name="menu_select_all" msgid="621719255150713545">"すべて選択"</string>
+ <string name="menu_select_none" msgid="7093222469852132345">"選択をすべて解除"</string>
+ <plurals name="multiple_picker_title">
+ <item quantity="one" msgid="4761009734586319101">"1件の宛先を選択済み"</item>
+ <item quantity="other" msgid="4608837420986126229">"<xliff:g id="COUNT">%d</xliff:g>件の宛先を選択済み"</item>
+ </plurals>
+ <string name="unknown_contacts_separator" msgid="2490675836664397214">"不明な連絡先"</string>
+ <string name="no_contacts_selected" msgid="5877803471037324613">"連絡先が選択されていません。"</string>
+ <string name="add_field" msgid="5257149039253569615">"情報を追加"</string>
+ <string name="contact_status_update_attribution" msgid="752179367353018597">"<xliff:g id="SOURCE">%1$s</xliff:g>経由"</string>
+ <string name="contact_status_update_attribution_with_date" msgid="7358045508107825068">"<xliff:g id="DATE">%1$s</xliff:g>、<xliff:g id="SOURCE">%2$s</xliff:g>経由"</string>
+ <string name="description_edit" msgid="1601490950771217014">"編集"</string>
+ <string name="description_star" msgid="2605854427360036550">"お気に入り"</string>
+ <string name="edit_contact" msgid="7529281274005689512">"連絡先の編集"</string>
+ <plurals name="merge_info">
+ <item quantity="one" msgid="148365587896371969">"結合する連絡先はありません"</item>
+ <item quantity="other" msgid="425683718017380845">"<xliff:g id="COUNT">%0$d</xliff:g>件の連絡先が結合されました"</item>
+ </plurals>
+ <string name="edit_delete_rawcontact" msgid="5054644778227879148">"削除"</string>
+ <string name="edit_error_saving" msgid="2153949392245240166">"保存エラー"</string>
+ <string name="edit_structured_editor_button" msgid="3830078388142808106">"..."</string>
+ <string name="local_invisible_directory" msgid="6046691709127661065">"その他"</string>
+ <!-- no translation found for aggregation_suggestion_title:one (7676930148670768645) -->
+ <!-- no translation found for aggregation_suggestion_title:other (3744098467672876675) -->
+ <!-- no translation found for aggregation_suggestion_joined_title (5342738708069602301) -->
+ <skip />
+ <!-- no translation found for aggregation_suggestion_join_button (7248325487963514260) -->
+ <skip />
+ <!-- no translation found for aggregation_suggestion_view_button (8232061564083962522) -->
+ <skip />
</resources>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index f98c89a..80c4a2d 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -31,6 +31,8 @@
<string name="viewContactTitle" msgid="7989394521836644384">"연락처 세부정보"</string>
<string name="viewContactDesription" msgid="214186610887547860">"연락처 보기"</string>
<string name="editContactDescription" msgid="2947202828256214947">"연락처 수정"</string>
+ <string name="edit_email_caption" msgid="2401084399676154272">"이메일 주소:"</string>
+ <string name="edit_email_type_caption" msgid="66331218163770698">"유형:"</string>
<string name="insertContactDescription" msgid="4709878105452681987">"연락처 만들기"</string>
<string name="searchHint" msgid="8482945356247760701">"주소록 검색"</string>
<string name="menu_search" msgid="9147752853603483719">"검색"</string>
@@ -123,13 +125,13 @@
</plurals>
<string name="listTotalAllContactsZero" msgid="6811347506748072822">"표시할 수 있는 주소록이 없습니다."</string>
<plurals name="listFoundAllContacts">
- <item quantity="one" msgid="2830107332033967280">"연락처 1개를 찾았습니다."</item>
- <item quantity="other" msgid="7752927996850263152">"연락처 <xliff:g id="COUNT">%d</xliff:g>개를 찾았습니다."</item>
+ <item quantity="one" msgid="5517063038754171134">"1개를 찾았습니다."</item>
+ <item quantity="other" msgid="3852668542926965042">"<xliff:g id="COUNT">%d</xliff:g>개를 찾았습니다."</item>
</plurals>
- <string name="listFoundAllContactsZero" msgid="5554368784319460828">"연락처를 찾을 수 없습니다."</string>
+ <string name="listFoundAllContactsZero" msgid="777952841930508289">"찾을 수 없음"</string>
<plurals name="searchFoundContacts">
- <item quantity="one" msgid="916648718690661777">"1개의 연락처"</item>
- <item quantity="other" msgid="5660384247071761844">"<xliff:g id="COUNT">%d</xliff:g>개의 연락처"</item>
+ <item quantity="one" msgid="4826918429708286628">"1개를 찾았습니다."</item>
+ <item quantity="other" msgid="7988132539476575389">"<xliff:g id="COUNT">%d</xliff:g>개를 찾았습니다."</item>
</plurals>
<string name="contactsIconLabel" msgid="7666609097606552806">"주소록"</string>
<string name="contactsFavoritesLabel" msgid="8417039765586853670">"즐겨찾기"</string>
@@ -159,10 +161,10 @@
<string name="simContacts_title" msgid="27341688347689769">"SIM 카드 주소록"</string>
<string name="noContactsHelpTextWithSyncForCreateShortcut" msgid="801504710275614594">"표시할 연락처가 없습니다. 방금 계정을 추가한 경우 연락처를 동기화하는 데 몇 분 정도 걸릴 수 있습니다."</string>
<string name="noContactsHelpTextForCreateShortcut" msgid="3081286388667108335">"표시할 연락처가 없습니다."</string>
- <string name="noContactsHelpText" msgid="6788487368878712350">"표시할 연락처가 없습니다."\n\n"연락처를 추가하려면 "<font fgcolor="#ffffffff"><b>"메뉴"</b></font>"를 누르고 다음을 터치합니다. "\n" "\n<li><font fgcolor="#ffffffff"><b>"계정"</b></font>": 휴대전화에 동기화할 수 있는 연락처가 있는 계정을 구성하거나 추가하려면 터치합니다. "\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"새 연락처"</b></font>": 연락처를 새로 만들려면 터치합니다."\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"가져오기/내보내기"</b></font>\n</li></string>
- <string name="noContactsHelpTextWithSync" msgid="3734101165712848179">"표시할 연락처가 없습니다. 방금 계정을 추가한 경우 연락처를 동기화하는 데 몇 분 정도 걸릴 수 있습니다."\n\n"연락처를 추가하려면 "<font fgcolor="#ffffffff"><b>"메뉴"</b></font>"를 누르고 다음을 터치합니다. "\n" "\n<li><font fgcolor="#ffffffff"><b>"계정"</b></font>": 휴대전화에 동기화할 수 있는 연락처가 있는 계정을 구성하거나 추가하려면 터치합니다. "\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"표시 옵션"</b></font>": 표시되는 연락처를 변경하려면 터치합니다."\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"새 연락처"</b></font>": 연락처를 새로 만들려면 터치합니다. "\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"가져오기/내보내기"</b></font>\n</li></string>
- <string name="noContactsNoSimHelpText" msgid="6553845386917463292">"표시할 연락처가 없습니다"\n\n"연락처를 추가하려면 "<font fgcolor="#ffffffff"><b>"메뉴"</b></font>"를 누르고 다음을 터치합니다. "\n" "\n<li><font fgcolor="#ffffffff"><b>"계정"</b></font>": 휴대전화에 동기화할 수 있는 연락처가 있는 계정을 구성하거나 추가하려는 경우 터치합니다. "\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"새 연락처"</b></font>": 연락처를 새로 만들려면 터치합니다."\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"가져오기/내보내기"</b></font>\n</li></string>
- <string name="noContactsNoSimHelpTextWithSync" msgid="1122296298361373488">"표시할 연락처가 없습니다. 방금 계정을 추가한 경우 연락처를 동기화하는 데 몇 분 정도 걸릴 수 있습니다."\n\n"연락처를 추가하려면 "<font fgcolor="#ffffffff"><b>"메뉴"</b></font>"를 누르고 다음을 터치합니다. "\n" "\n<li><font fgcolor="#ffffffff"><b>"계정"</b></font>": 휴대전화에 동기화할 수 있는 연락처가 있는 계정을 구성하거나 추가하려면 터치합니다. "\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"표시 옵션"</b></font>": 표시되는 연락처를 변경하려면 터치합니다."\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"새 연락처"</b></font>": 연락처를 새로 만들려면 터치합니다. "\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"가져오기/내보내기"</b></font>\n</li></string>
+ <string name="noContactsHelpText" msgid="7633826236417884130">"표시할 연락처가 없습니다."\n\n"연락처를 추가하려면 "<font fgcolor="#ffffffff"><b>"메뉴"</b></font>"를 누르고 다음을 터치합니다."\n" "\n<li><font fgcolor="#ffffffff"><b>"계정"</b></font>": 휴대전화에 동기화할 수 있는 연락처가 있는 계정을 구성하거나 추가하려는 경우 터치합니다."\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"새 연락처"</b></font>": 연락처를 새로 만들려면 터치합니다."\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"가져오기/내보내기"</b></font>": SIM 또는 SD 카드에서 연락처를 가져오려면 터치합니다."\n</li></string>
+ <string name="noContactsHelpTextWithSync" msgid="3017521127042216243">"표시할 연락처가 없습니다. 방금 계정을 추가한 경우 연락처를 동기화하는 데 몇 분 정도 걸릴 수 있습니다."\n\n"연락처를 추가하려면 "<font fgcolor="#ffffffff"><b>"메뉴"</b></font>"를 누르고 다음을 터치합니다."\n" "\n<li><font fgcolor="#ffffffff"><b>"계정"</b></font>": 휴대전화에 동기화할 수 있는 연락처가 있는 계정을 구성하거나 추가하려면 터치합니다."\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"표시 옵션"</b></font>": 표시할 연락처를 변경하려면 터치합니다."\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"새 연락처"</b></font>": 연락처를 새로 만들려면 터치합니다."\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"가져오기/내보내기"</b></font>": SIM 또는 SD 카드에서 연락처를 가져오려면 터치합니다."\n</li></string>
+ <string name="noContactsNoSimHelpText" msgid="467658807711582876">"표시할 연락처가 없습니다."\n\n"연락처를 추가하려면 "<font fgcolor="#ffffffff"><b>"메뉴"</b></font>"를 누르고 다음을 터치합니다."\n" "\n<li><font fgcolor="#ffffffff"><b>"계정"</b></font>": 휴대전화에 동기화할 수 있는 연락처가 있는 계정을 구성하거나 추가하려면 터치합니다."\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"새 연락처"</b></font>": 연락처를 새로 만들려면 터치합니다."\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"가져오기/내보내기"</b></font>": SD 카드에서 연락처를 가져오려면 터치합니다."\n</li></string>
+ <string name="noContactsNoSimHelpTextWithSync" msgid="9040060730467973050">"표시할 연락처가 없습니다. 방금 계정을 추가한 경우 연락처를 동기화하는 데 몇 분 정도 걸릴 수 있습니다."\n\n"연락처를 추가하려면 "<font fgcolor="#ffffffff"><b>"메뉴"</b></font>"를 누르고 다음을 터치합니다."\n" "\n<li><font fgcolor="#ffffffff"><b>"계정"</b></font>": 휴대전화에 동기화할 수 있는 연락처가 있는 계정을 구성하거나 추가하려면 터치합니다."\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"표시 옵션"</b></font>": 표시되는 연락처를 변경하려면 터치합니다."\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"새 연락처"</b></font>": 연락처를 새로 만들려면 터치합니다."\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"가져오기/내보내기"</b></font>": SD 카드에서 연락처를 가져오려면 터치합니다."\n</li></string>
<string name="noFavoritesHelpText" msgid="3744655776704833277">"즐겨찾기가 없습니다. "\n\n"즐겨찾기 목록에 연락처를 추가하려면 "\n\n<li><b>"주소록"</b>" 탭을 터치하고"\n</li>" "\n<li>"즐겨찾기에 추가할 연락처를 터치하고"\n</li>" "\n<li>"연락처 이름 옆의 별표를 터치합니다."\n</li></string>
<string name="liveFolder_all_label" msgid="5961411940473276616">"모든 연락처"</string>
<string name="liveFolder_favorites_label" msgid="2674341514070517105">"중요 주소록"</string>
@@ -179,7 +181,7 @@
<string name="callBack" msgid="5498224409038809224">"전화 걸기"</string>
<string name="callAgain" msgid="3197312117049874778">"다시 걸기"</string>
<string name="returnCall" msgid="8171961914203617813">"전화 걸기"</string>
- <string name="callDetailsDurationFormat" msgid="8157706382818184268">"<xliff:g id="MINUTES">%1$s</xliff:g>분 <xliff:g id="SECONDS">%2$s</xliff:g>초"</string>
+ <string name="callDetailsDurationFormat" msgid="8157706382818184268">"<xliff:g id="MINUTES">%s</xliff:g>분 <xliff:g id="SECONDS">%s</xliff:g>초"</string>
<string name="favoritesFrquentSeparator" msgid="8107518433381283736">"자주 연락하는 사람들의 연락처"</string>
<string name="add_contact_dlg_title" msgid="2896685845822146494">"연락처 추가"</string>
<string name="add_contact_dlg_message_fmt" msgid="7986472669444326576">"\'<xliff:g id="EMAIL">%s</xliff:g>\'을(를) 주소록에 추가하겠습니까?"</string>
@@ -217,18 +219,24 @@
<string name="scanning_sdcard_failed_title" msgid="3506782007953167180">"SD 카드 스캔 실패"</string>
<string name="scanning_sdcard_failed_message" msgid="3761992500690182922">"SD 카드 스캔 실패(이유: \'<xliff:g id="FAIL_REASON">%s</xliff:g>\')"</string>
<string name="fail_reason_io_error" msgid="5922864781066136340">"I/O 오류"</string>
+ <string name="fail_reason_low_memory_during_import" msgid="7514918659342886381">"메모리가 부족합니다. 파일이 너무 크기 때문일 수 있습니다."</string>
<string name="fail_reason_vcard_parse_error" msgid="1201233722762680214">"예기치 못한 이유로 인해 vCard를 구문분석하지 못했습니다."</string>
<string name="fail_reason_vcard_not_supported_error" msgid="655208100451286027">"올바른 형식처럼 보이지만 현재 구현 환경에서는 VCard를 지원하지 않기 때문에 VCard 구문 분석에 실패했습니다."</string>
<string name="fail_reason_no_vcard_file" msgid="6376516175882881595">"SD 카드에 VCard 파일이 없습니다."</string>
+ <string name="fail_reason_failed_to_collect_vcard_meta_info" msgid="4154492282316067754">"지정한 vCard 파일에 대한 메타 정보를 수집하지 못했습니다."</string>
<string name="fail_reason_failed_to_read_files" msgid="3659521123567134029">"하나 이상의 파일을 가져오지 못했습니다(%s)."</string>
<string name="fail_reason_unknown" msgid="999034019513096768">"알 수 없는 오류"</string>
<string name="select_vcard_title" msgid="3968948173786172468">"vCard 파일 선택"</string>
- <string name="progress_shower_message" msgid="5636525578293752526">"<xliff:g id="ACTION">%1$s</xliff:g>"\n"<xliff:g id="FILENAME">%2$s</xliff:g>"</string>
- <string name="reading_vcard_title" msgid="4723433501579653199">"vCard 읽는 중"</string>
- <string name="reading_vcard_message" msgid="6381368920030748743">"vCard 파일 읽는 중"</string>
- <string name="reading_vcard_failed_title" msgid="4923008144735294994">"vCard 데이터를 읽어오지 못했습니다."</string>
- <string name="reading_vcard_contacts" msgid="3066834102042012868">"연락처 <xliff:g id="CURRENT_NUMBER">%1$s</xliff:g>개(총 <xliff:g id="TOTAL_NUMBER">%2$s</xliff:g>개) 내보내는 중"</string>
- <string name="reading_vcard_files" msgid="34180143726972661">"파일 <xliff:g id="TOTAL_NUMBER">%2$s</xliff:g>개 중 <xliff:g id="CURRENT_NUMBER">%1$s</xliff:g>개"</string>
+ <string name="caching_vcard_title" msgid="5009556022082659780">"vCard를 로컬 임시 저장공간에 캐시하는 중"</string>
+ <string name="caching_vcard_message" msgid="2380844718093378900">"가져오기 도구가 vCard를 로컬 임시 저장공간에 캐시하는 중입니다. 곧 실제 가져오기가 시작됩니다."</string>
+ <string name="progress_notifier_message" msgid="8412196553907067659">"<xliff:g id="CURRENT_NUMBER">%s</xliff:g>/<xliff:g id="TOTAL_NUMBER">%s</xliff:g>: <xliff:g id="FILENAME">%s</xliff:g>"</string>
+ <string name="reading_vcard_title" msgid="3324988724123717025">"vCard를 읽는 중"</string>
+ <string name="reading_vcard_failed_title" msgid="2162610359561887043">"vCard 데이터를 읽지 못했습니다."</string>
+ <string name="reading_vcard_canceled_title" msgid="1770608329958463131">"vCard 데이터 읽기가 취소되었습니다."</string>
+ <string name="importing_vcard_finished_title" msgid="3213257229785702182">"vCard 가져오기 완료됨"</string>
+ <string name="vcard_importer_start_message" msgid="5520549352987753320">"vCard 가져오기 도구가 시작되었습니다."</string>
+ <string name="vcard_importer_will_start_message" msgid="2055145402481822160">"잠시 후에 vCard 가져오기 도구가 vCard를 가져옵니다."</string>
+ <string name="percentage" msgid="34897865327092209">"%s%%"</string>
<string name="confirm_export_title" msgid="7648747763127442983">"내보내기 확인"</string>
<string name="confirm_export_message" msgid="3875683519257829750">"\'<xliff:g id="VCARD_FILENAME">%s</xliff:g>\'(으)로 연락처 목록을 내보내시겠습니까?"</string>
<string name="exporting_contact_failed_title" msgid="585823094820602526">"연락처 데이터를 내보내지 못했습니다."</string>
@@ -236,6 +244,8 @@
<string name="fail_reason_no_exportable_contact" msgid="4919714086648344495">"내보낼 수 있는 연락처가 없습니다."</string>
<string name="fail_reason_too_many_vcard" msgid="7084146295639672658">"SD 카드에 vCard 파일이 너무 많습니다."</string>
<string name="fail_reason_too_long_filename" msgid="1915716071321839166">"필수 파일 이름이 너무 깁니다(\'<xliff:g id="FILENAME">%s</xliff:g>\')."</string>
+ <string name="vcard_exporter_start_message" msgid="6497218576519292369">"vCard 내보내기 도구가 시작되었습니다."</string>
+ <string name="exporting_vcard_finished_title" msgid="1268425436375550862">"vCard 내보내기 완료됨"</string>
<string name="exporting_contact_list_title" msgid="9072240631534457415">"연락처 데이터 내보내기"</string>
<string name="exporting_contact_list_message" msgid="5640326540405486055">"연락처 데이터를 \'<xliff:g id="FILE_NAME">%s</xliff:g>\'(으)로 내보내는 중"</string>
<string name="fail_reason_could_not_initialize_exporter" msgid="4943708332700987376">"내보내기를 초기화하지 못했습니다. \'<xliff:g id="EXACT_REASON">%s</xliff:g>\'"</string>
@@ -243,8 +253,8 @@
<string name="composer_failed_to_get_database_infomation" msgid="3723109558155169053">"데이터베이스 정보를 가져오지 못했습니다."</string>
<string name="composer_has_no_exportable_contact" msgid="754734132189369094">"내보낼 수 있는 연락처가 없습니다. 휴대전화에 실제로 연락처가 있다면 일부 데이터 제공업체에서 모든 연락처를 휴대전화 외부로 내보내지 못하도록 금지했을 수 있습니다."</string>
<string name="composer_not_initialized" msgid="8041534450748388843">"vCard 작성기가 바르게 초기화되지 않았습니다."</string>
- <string name="fail_reason_could_not_open_file" msgid="4013520943128739511">"\'<xliff:g id="FILE_NAME">%1$s</xliff:g>\'을(를) 열 수 없습니다. <xliff:g id="EXACT_REASON">%2$s</xliff:g>"</string>
- <string name="exporting_contact_list_progress" msgid="560522409559101193">"연락처 <xliff:g id="CURRENT_NUMBER">%1$s</xliff:g>개(총 <xliff:g id="TOTAL_NUMBER">%2$s</xliff:g>개) 내보내는 중"</string>
+ <string name="fail_reason_could_not_open_file" msgid="4013520943128739511">"\'<xliff:g id="FILE_NAME">%s</xliff:g>\'을(를) 열 수 없습니다. <xliff:g id="EXACT_REASON">%s</xliff:g>"</string>
+ <string name="exporting_contact_list_progress" msgid="560522409559101193">"연락처 <xliff:g id="CURRENT_NUMBER">%s</xliff:g>개(총 <xliff:g id="TOTAL_NUMBER">%s</xliff:g>개) 내보내는 중"</string>
<string name="search_settings_description" msgid="2675223022992445813">"연락처 명단"</string>
<string name="add_2sec_pause" msgid="9214012315201040129">"2초 간 일시 정지 추가"</string>
<string name="add_wait" msgid="3360818652790319634">"대기 시간 추가"</string>
@@ -254,6 +264,7 @@
<string name="quickcontact_missing_app" msgid="4600366393134289038">"이 작업을 처리하는 애플리케이션을 찾을 수 없습니다."</string>
<string name="quickcontact_remember_choice" msgid="5964536411579749424">"이 선택사항 저장"</string>
<string name="quickcontact_missing_name" msgid="5590266114306996632">"알 수 없음"</string>
+ <string name="quickcontact_no_data" msgid="2098000859125253675">"데이터가 없습니다."</string>
<string name="menu_accounts" msgid="8499114602017077970">"계정"</string>
<string name="menu_import_export" msgid="3765725645491577190">"가져오기/내보내기"</string>
<string name="dialog_import_export" msgid="4771877268244096596">"연락처 가져오기/내보내기"</string>
@@ -359,6 +370,7 @@
<string name="name_phonetic_family" msgid="462095502140180305">"성(소리나는 대로)"</string>
<string name="account_type_format" msgid="718948015590343010">"<xliff:g id="SOURCE">%1$s</xliff:g> 연락처"</string>
<string name="from_account_format" msgid="687567483928582084">"출처: <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
+ <string name="account_type_and_name" msgid="2876220035411512934">"<xliff:g id="SOURCE_1">%2$s</xliff:g>에서 가져온 <xliff:g id="SOURCE_0">%1$s</xliff:g> 연락처"</string>
<string name="use_photo_as_primary" msgid="8807110122951157246">"사진 사용"</string>
<string name="contact_read_only" msgid="1203216914575723978">"<xliff:g id="SOURCE">%1$s</xliff:g> 연락처 정보는 이 기기에서 수정할 수 없습니다."</string>
<string name="no_contact_details" msgid="6754415338321837001">"연락처에 대한 추가 정보가 없습니다."</string>
@@ -374,9 +386,42 @@
<string name="pick_photo" msgid="448886509158039462">"갤러리에서 사진 선택"</string>
<string name="locale_change_in_progress" msgid="1124266507671178413">"변경된 언어를 반영하도록 연락처 목록을 업데이트하는 중입니다."\n\n"잠시 기다려 주세요."</string>
<string name="upgrade_in_progress" msgid="7530893673211750223">"연락처 목록을 업데이트하고 있습니다."\n\n"잠시 기다려 주세요..."</string>
- <string name="upgrade_out_of_memory" msgid="492151063059824962">"연락처를 업그레이드하는 중입니다. "\n\n"업그레이드 프로세스에는 약 <xliff:g id="SIZE_IN_MEGABYTES">%d</xliff:g>Mb의 휴대전화 내부 저장공간이 필요합니다."\n\n"다음 옵션 중 하나를 선택하세요."</string>
+ <string name="upgrade_out_of_memory" msgid="6713914687111678777">"주소록을 업그레이드하는 중입니다. "\n\n"업그레이드 프로세스에는 약 <xliff:g id="SIZE_IN_MEGABYTES">%d</xliff:g>MB의 휴대전화 내부 저장공간이 필요합니다."\n\n"다음 옵션 중 하나를 선택하세요."</string>
<string name="upgrade_out_of_memory_uninstall" msgid="1721798828992091432">"일부 애플리케이션 제거"</string>
<string name="upgrade_out_of_memory_retry" msgid="8431289830472724609">"업그레이드 다시 시도"</string>
<string name="search_results_for" msgid="8705490885073188513">"<xliff:g id="QUERY">%s</xliff:g>에 대한 검색결과"</string>
<string name="search_results_searching" msgid="7755623475227227314">"검색 중..."</string>
+ <string name="adding_recipients" msgid="5123647646892106631">"로드 중..."</string>
+ <string name="menu_display_selected" msgid="6470001164297969034">"선택한 항목 표시"</string>
+ <string name="menu_display_all" msgid="8887488642609786198">"모두 표시"</string>
+ <string name="menu_select_all" msgid="621719255150713545">"모두 선택"</string>
+ <string name="menu_select_none" msgid="7093222469852132345">"모두 선택취소"</string>
+ <plurals name="multiple_picker_title">
+ <item quantity="one" msgid="4761009734586319101">"수신자 1명이 선택됨"</item>
+ <item quantity="other" msgid="4608837420986126229">"수신자 <xliff:g id="COUNT">%d</xliff:g>명이 선택됨"</item>
+ </plurals>
+ <string name="unknown_contacts_separator" msgid="2490675836664397214">"알 수 없는 연락처"</string>
+ <string name="no_contacts_selected" msgid="5877803471037324613">"선택한 연락처가 없습니다."</string>
+ <string name="add_field" msgid="5257149039253569615">"정보 추가"</string>
+ <string name="contact_status_update_attribution" msgid="752179367353018597">"출처: <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
+ <string name="contact_status_update_attribution_with_date" msgid="7358045508107825068">"<xliff:g id="DATE">%1$s</xliff:g>(출처: <xliff:g id="SOURCE">%2$s</xliff:g>)"</string>
+ <string name="description_edit" msgid="1601490950771217014">"수정"</string>
+ <string name="description_star" msgid="2605854427360036550">"즐겨찾기"</string>
+ <string name="edit_contact" msgid="7529281274005689512">"연락처 수정"</string>
+ <plurals name="merge_info">
+ <item quantity="one" msgid="148365587896371969">"병합되지 않았습니다."</item>
+ <item quantity="other" msgid="425683718017380845">"<xliff:g id="COUNT">%0$d</xliff:g>개 출처에서 병합했습니다."</item>
+ </plurals>
+ <string name="edit_delete_rawcontact" msgid="5054644778227879148">"삭제"</string>
+ <string name="edit_error_saving" msgid="2153949392245240166">"저장하는 중에 오류가 발생했습니다."</string>
+ <string name="edit_structured_editor_button" msgid="3830078388142808106">"..."</string>
+ <string name="local_invisible_directory" msgid="6046691709127661065">"기타"</string>
+ <!-- no translation found for aggregation_suggestion_title:one (7676930148670768645) -->
+ <!-- no translation found for aggregation_suggestion_title:other (3744098467672876675) -->
+ <!-- no translation found for aggregation_suggestion_joined_title (5342738708069602301) -->
+ <skip />
+ <!-- no translation found for aggregation_suggestion_join_button (7248325487963514260) -->
+ <skip />
+ <!-- no translation found for aggregation_suggestion_view_button (8232061564083962522) -->
+ <skip />
</resources>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index ea95eca..5e07f3c 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -31,6 +31,8 @@
<string name="viewContactTitle" msgid="7989394521836644384">"Egenskaper for kontakt"</string>
<string name="viewContactDesription" msgid="214186610887547860">"Se på kontakt"</string>
<string name="editContactDescription" msgid="2947202828256214947">"Rediger kontakt"</string>
+ <string name="edit_email_caption" msgid="2401084399676154272">"E-postadresse:"</string>
+ <string name="edit_email_type_caption" msgid="66331218163770698">"Type: "</string>
<string name="insertContactDescription" msgid="4709878105452681987">"Opprett kontakt"</string>
<string name="searchHint" msgid="8482945356247760701">"Søk i kontakter"</string>
<string name="menu_search" msgid="9147752853603483719">"Søk"</string>
@@ -123,13 +125,13 @@
</plurals>
<string name="listTotalAllContactsZero" msgid="6811347506748072822">"Ingen synlige kontakter"</string>
<plurals name="listFoundAllContacts">
- <item quantity="one" msgid="2830107332033967280">"Fant 1 kontakt"</item>
- <item quantity="other" msgid="7752927996850263152">"Fant <xliff:g id="COUNT">%d</xliff:g> kontakter"</item>
+ <item quantity="one" msgid="5517063038754171134">"1 funnet"</item>
+ <item quantity="other" msgid="3852668542926965042">"<xliff:g id="COUNT">%d</xliff:g> funnet"</item>
</plurals>
- <string name="listFoundAllContactsZero" msgid="5554368784319460828">"Fant ingen kontakter"</string>
+ <string name="listFoundAllContactsZero" msgid="777952841930508289">"Ikke funnet"</string>
<plurals name="searchFoundContacts">
- <item quantity="one" msgid="916648718690661777">"1 kontakt"</item>
- <item quantity="other" msgid="5660384247071761844">"<xliff:g id="COUNT">%d</xliff:g> kontakter"</item>
+ <item quantity="one" msgid="4826918429708286628">"1 funnet"</item>
+ <item quantity="other" msgid="7988132539476575389">"<xliff:g id="COUNT">%d</xliff:g> funnet"</item>
</plurals>
<string name="contactsIconLabel" msgid="7666609097606552806">"Kontakter"</string>
<string name="contactsFavoritesLabel" msgid="8417039765586853670">"Favoritter"</string>
@@ -159,10 +161,10 @@
<string name="simContacts_title" msgid="27341688347689769">"Kontakter på SIM-kort"</string>
<string name="noContactsHelpTextWithSyncForCreateShortcut" msgid="801504710275614594">"Du har ingen kontakter å vise. (Hvis du nettopp har lagt til en konto, kan det ta noen minutter å synkronisere kontaktene.)"</string>
<string name="noContactsHelpTextForCreateShortcut" msgid="3081286388667108335">"Du har ingen kontakter å vise."</string>
- <string name="noContactsHelpText" msgid="6788487368878712350">"Du har ingen kontakter å vise. "\n\n"Slik legger du til en kontakt: Trykk på "<font fgcolor="#ffffffff"><b>"Meny"</b></font>" og trykk deretter på:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Kontoer"</b></font>" for å legge til eller konfigurere en konto med kontakter som kan synkroniseres til telefonen"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Ny kontakt"</b></font>" for å opprette en ny kontakt"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importér/eksportér"</b></font>\n</li></string>
- <string name="noContactsHelpTextWithSync" msgid="3734101165712848179">"Du har ingen kontakter å vise. (Hvis du nylig la til en konto, kan det ta noen minutter å synkronisere kontaktene.)"\n\n"Slik legger du til kontakter: Trykk på "<font fgcolor="#ffffffff"><b>"Meny"</b></font>" og trykk deretter på: "\n" "\n<li><font fgcolor="#ffffffff"><b>"Kontoer "</b></font>" for å legge til eller konfigurere en konto med kontakter som kan synkroniseres til telefonen"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Visningsalternativer"</b></font>" for å endre hvilke kontakter som vises"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Ny kontakt"</b></font>" for å opprette en ny kontakt"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importér/Eksportér"</b></font>\n</li></string>
- <string name="noContactsNoSimHelpText" msgid="6553845386917463292">"Du har ingen kontakter å vise. "\n\n"Slik legger du til en kontakt: Trykk på "<font fgcolor="#ffffffff"><b>"Meny"</b></font>" og trykk deretter på:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Kontoer"</b></font>" for å legge til eller konfigurere en konto med kontakter som kan synkroniseres til telefonen"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Ny kontakt"</b></font>" for å opprette en ny kontakt"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importér/eksportér"</b></font>\n</li></string>
- <string name="noContactsNoSimHelpTextWithSync" msgid="1122296298361373488">"Du har ingen kontakter å vise. (Hvis du nylig la til en konto, kan det ta noen minutter å synkronisere kontaktene.)"\n\n"Slik legger du til kontakter: Trykk på "<font fgcolor="#ffffffff"><b>"Meny"</b></font>" og trykk deretter på: "\n" "\n<li><font fgcolor="#ffffffff"><b>"Kontoer "</b></font>" for å legge til eller konfigurere en konto med kontakter som kan synkroniseres til telefonen"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Visningsalternativer"</b></font>" for å endre hvilke kontakter som vises"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Ny kontakt"</b></font>" for å opprette en ny kontakt"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importér/Eksportér"</b></font>\n</li></string>
+ <!-- syntax error in translation for noContactsHelpText (7633826236417884130) org.xmlpull.v1.XmlPullParserException: expected: /li read: font (position:END_TAG </font>@1:275 in java.io.StringReader@5456a499) -->
+ <string name="noContactsHelpTextWithSync" msgid="3017521127042216243">"Du har ingen kontakter å vise. (Hvis du nylig la til en konto, kan det ta noen minutter å synkronisere kontaktene.)"\n\n"Slik legger du til kontakter: Trykk på "<font fgcolor="#ffffffff"><b>"Meny"</b></font>" og trykk deretter på: "\n" "\n<li><font fgcolor="#ffffffff"><b>"Kontoer "</b></font>" for å legge til eller konfigurere en konto med kontakter som kan synkroniseres til telefonen"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Visningsalternativer"</b></font>" for å endre hvilke kontakter som vises"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Ny kontakt"</b></font>" for å opprette en ny kontakt"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importér/Eksportér"</b></font>" for å importere kontakter fra SIM- eller SD-kort"\n</li></string>
+ <!-- syntax error in translation for noContactsNoSimHelpText (467658807711582876) org.xmlpull.v1.XmlPullParserException: expected: /li read: font (position:END_TAG </font>@1:279 in java.io.StringReader@16c9ba38) -->
+ <string name="noContactsNoSimHelpTextWithSync" msgid="9040060730467973050">"Du har ingen kontakter å vise. (Hvis du nylig la til en konto, kan det ta noen minutter å synkronisere kontaktene.)"\n\n"Slik legger du til kontakter: Trykk på "<font fgcolor="#ffffffff"><b>"Meny"</b></font>" og trykk deretter på: "\n" "\n<li><font fgcolor="#ffffffff"><b>"Kontoer "</b></font>" for å legge til eller konfigurere en konto med kontakter som kan synkroniseres til telefonen"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Visningsalternativer"</b></font>" for å endre hvilke kontakter som vises"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Ny kontakt"</b></font>" for å opprette en ny kontakt"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importér/eksportér"</b></font>" for å importere kontakter fra SD-kortet"\n</li></string>
<string name="noFavoritesHelpText" msgid="3744655776704833277">"Du har ingen favoritter."\n\n"Slik legger du til en kontakt i favorittlisten:"\n\n" "<li>"Trykk på fanen "<b>"Kontakter"</b>" "\n</li>" "\n<li>"Trykk på kontakten du vil legge til i favoritter"\n</li>" "\n<li>"Trykk på stjernen ved siden av kontaktnavnet"\n</li></string>
<string name="liveFolder_all_label" msgid="5961411940473276616">"Alle kontakter"</string>
<string name="liveFolder_favorites_label" msgid="2674341514070517105">"Med stjerne"</string>
@@ -179,7 +181,7 @@
<string name="callBack" msgid="5498224409038809224">"Ring tilbake"</string>
<string name="callAgain" msgid="3197312117049874778">"Ring på nytt"</string>
<string name="returnCall" msgid="8171961914203617813">"Ring tilbake"</string>
- <string name="callDetailsDurationFormat" msgid="8157706382818184268">"<xliff:g id="MINUTES">%1$s</xliff:g> min <xliff:g id="SECONDS">%2$s</xliff:g> sek"</string>
+ <string name="callDetailsDurationFormat" msgid="8157706382818184268">"<xliff:g id="MINUTES">%s</xliff:g> min <xliff:g id="SECONDS">%s</xliff:g> sek"</string>
<string name="favoritesFrquentSeparator" msgid="8107518433381283736">"Ofte kontaktet"</string>
<string name="add_contact_dlg_title" msgid="2896685845822146494">"Legg til kontakt"</string>
<string name="add_contact_dlg_message_fmt" msgid="7986472669444326576">"Legg til «<xliff:g id="EMAIL">%s</xliff:g>» som kontakt?"</string>
@@ -217,18 +219,24 @@
<string name="scanning_sdcard_failed_title" msgid="3506782007953167180">"Kunne ikke scanne minnekort"</string>
<string name="scanning_sdcard_failed_message" msgid="3761992500690182922">"Kunne ikke skanne minnekort (grunn: «<xliff:g id="FAIL_REASON">%s</xliff:g>»)"</string>
<string name="fail_reason_io_error" msgid="5922864781066136340">"Inn/ut-feil (årsak: «FAIL_REASON»)"</string>
+ <string name="fail_reason_low_memory_during_import" msgid="7514918659342886381">"Ikke tilstrekkelig minne (filen kan være for stor)"</string>
<string name="fail_reason_vcard_parse_error" msgid="1201233722762680214">"Kan ikke analysere VCard av uventet årsak"</string>
<string name="fail_reason_vcard_not_supported_error" msgid="655208100451286027">"Kan ikke analysere VCard selv om det ser ut til å være i riktig format, fordi den aktuelle implementeringen ikke støtter det."</string>
<string name="fail_reason_no_vcard_file" msgid="6376516175882881595">"Fant ingen VCard-filer på minnekortet"</string>
+ <string name="fail_reason_failed_to_collect_vcard_meta_info" msgid="4154492282316067754">"Kan ikke hente inn metainformasjon for de(n) aktuelle vCard-filen(e)."</string>
<string name="fail_reason_failed_to_read_files" msgid="3659521123567134029">"Importeringen av én eller flere filer mislyktes (%s)"</string>
<string name="fail_reason_unknown" msgid="999034019513096768">"Ukjent feil"</string>
<string name="select_vcard_title" msgid="3968948173786172468">"Velg VCard-fil"</string>
- <string name="progress_shower_message" msgid="5636525578293752526">"<xliff:g id="ACTION">%1$s</xliff:g>"\n"<xliff:g id="FILENAME">%2$s</xliff:g>"</string>
- <string name="reading_vcard_title" msgid="4723433501579653199">"Leser VCard"</string>
- <string name="reading_vcard_message" msgid="6381368920030748743">"Leser VCard-fil(er)"</string>
- <string name="reading_vcard_failed_title" msgid="4923008144735294994">"Kunne ikke lese VCard-data"</string>
- <string name="reading_vcard_contacts" msgid="3066834102042012868">"<xliff:g id="CURRENT_NUMBER">%1$s</xliff:g> av <xliff:g id="TOTAL_NUMBER">%2$s</xliff:g> kontakter"</string>
- <string name="reading_vcard_files" msgid="34180143726972661">"<xliff:g id="CURRENT_NUMBER">%1$s</xliff:g> av <xliff:g id="TOTAL_NUMBER">%2$s</xliff:g> filer"</string>
+ <string name="caching_vcard_title" msgid="5009556022082659780">"Bufrer vCard-fil(er) til lokal midlertidig lagring"</string>
+ <string name="caching_vcard_message" msgid="2380844718093378900">"Importprogrammet bufrer vCard-filen(e) til lokal midlertidig lagring. Importen av selve filen starter snart."</string>
+ <string name="progress_notifier_message" msgid="8412196553907067659">"<xliff:g id="CURRENT_NUMBER">%s</xliff:g>/<xliff:g id="TOTAL_NUMBER">%s</xliff:g>: <xliff:g id="FILENAME">%s</xliff:g>"</string>
+ <string name="reading_vcard_title" msgid="3324988724123717025">"Leser vCard-fil(er)"</string>
+ <string name="reading_vcard_failed_title" msgid="2162610359561887043">"Kan ikke lese vCard-data"</string>
+ <string name="reading_vcard_canceled_title" msgid="1770608329958463131">"Lesing av vCard-data ble avbrutt"</string>
+ <string name="importing_vcard_finished_title" msgid="3213257229785702182">"Import av vCard-fil fullført"</string>
+ <string name="vcard_importer_start_message" msgid="5520549352987753320">"Importprogrammet for vCard er startet."</string>
+ <string name="vcard_importer_will_start_message" msgid="2055145402481822160">"Importprogrammet for vCard importerer vCard-filen etter kort tid."</string>
+ <string name="percentage" msgid="34897865327092209">"%s%%"</string>
<string name="confirm_export_title" msgid="7648747763127442983">"Eksportbekreftelse"</string>
<string name="confirm_export_message" msgid="3875683519257829750">"Vil du eksportere kontaktlisten din til «<xliff:g id="VCARD_FILENAME">%s</xliff:g>»?"</string>
<string name="exporting_contact_failed_title" msgid="585823094820602526">"Kunne ikke eksportere kontaktlisten"</string>
@@ -236,6 +244,8 @@
<string name="fail_reason_no_exportable_contact" msgid="4919714086648344495">"Det finnes ingen eksporterbar kontakt."</string>
<string name="fail_reason_too_many_vcard" msgid="7084146295639672658">"For mange VCard-datafiler på minnekortet"</string>
<string name="fail_reason_too_long_filename" msgid="1915716071321839166">"For langt filnavn kreves («<xliff:g id="FILENAME">%s</xliff:g>»)"</string>
+ <string name="vcard_exporter_start_message" msgid="6497218576519292369">"Eksportprogrammet for vCard er startet."</string>
+ <string name="exporting_vcard_finished_title" msgid="1268425436375550862">"Eksport av vCard-fil fullført"</string>
<string name="exporting_contact_list_title" msgid="9072240631534457415">"Eksporterer kontaktdata"</string>
<string name="exporting_contact_list_message" msgid="5640326540405486055">"Eksporterer kontaktdata til «<xliff:g id="FILE_NAME">%s</xliff:g>»"</string>
<string name="fail_reason_could_not_initialize_exporter" msgid="4943708332700987376">"Kunne ikke starte eksporteringen: «<xliff:g id="EXACT_REASON">%s</xliff:g>»"</string>
@@ -243,8 +253,8 @@
<string name="composer_failed_to_get_database_infomation" msgid="3723109558155169053">"Klarte ikke å hente databaseinformasjon"</string>
<string name="composer_has_no_exportable_contact" msgid="754734132189369094">"Det finnes ingen eksporterbare kontakter. Hvis du har kontakter på telefonen, kan disse i enkelte tilfeller ikke eksporteres. Dette skyldes i så fall at dataleverandøren ikke tillater det."</string>
<string name="composer_not_initialized" msgid="8041534450748388843">"Objektet for vCard-redigering er ikke riktig initialisert"</string>
- <string name="fail_reason_could_not_open_file" msgid="4013520943128739511">"Kunne ikke åpne «<xliff:g id="FILE_NAME">%1$s</xliff:g>»: <xliff:g id="EXACT_REASON">%2$s</xliff:g>"</string>
- <string name="exporting_contact_list_progress" msgid="560522409559101193">"<xliff:g id="CURRENT_NUMBER">%1$s</xliff:g> av <xliff:g id="TOTAL_NUMBER">%2$s</xliff:g> kontakter"</string>
+ <string name="fail_reason_could_not_open_file" msgid="4013520943128739511">"Kunne ikke åpne «<xliff:g id="FILE_NAME">%s</xliff:g>»: <xliff:g id="EXACT_REASON">%s</xliff:g>"</string>
+ <string name="exporting_contact_list_progress" msgid="560522409559101193">"<xliff:g id="CURRENT_NUMBER">%s</xliff:g> av <xliff:g id="TOTAL_NUMBER">%s</xliff:g> kontakter"</string>
<string name="search_settings_description" msgid="2675223022992445813">"Navn på kontakter"</string>
<string name="add_2sec_pause" msgid="9214012315201040129">"Legg til pause på 2 sek."</string>
<string name="add_wait" msgid="3360818652790319634">"Legg til Vent"</string>
@@ -254,6 +264,7 @@
<string name="quickcontact_missing_app" msgid="4600366393134289038">"Ingen programmer som kan utføre denne handlingen ble funnet"</string>
<string name="quickcontact_remember_choice" msgid="5964536411579749424">"Husk dette valget"</string>
<string name="quickcontact_missing_name" msgid="5590266114306996632">"Ukjent"</string>
+ <string name="quickcontact_no_data" msgid="2098000859125253675">"Ingen data"</string>
<string name="menu_accounts" msgid="8499114602017077970">"Kontoer"</string>
<string name="menu_import_export" msgid="3765725645491577190">"Import/eksport"</string>
<string name="dialog_import_export" msgid="4771877268244096596">"Importér/eksportér kontakter"</string>
@@ -359,6 +370,7 @@
<string name="name_phonetic_family" msgid="462095502140180305">"Fonetisk etternavn"</string>
<string name="account_type_format" msgid="718948015590343010">"<xliff:g id="SOURCE">%1$s</xliff:g>-kontakt"</string>
<string name="from_account_format" msgid="687567483928582084">"fra <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
+ <string name="account_type_and_name" msgid="2876220035411512934">"<xliff:g id="SOURCE_0">%1$s</xliff:g> kontakt fra <xliff:g id="SOURCE_1">%2$s</xliff:g>"</string>
<string name="use_photo_as_primary" msgid="8807110122951157246">"Bruk dette bildet"</string>
<string name="contact_read_only" msgid="1203216914575723978">"<xliff:g id="SOURCE">%1$s</xliff:g>-kontaktinformasjon kan ikke redigeres på denne enheten."</string>
<string name="no_contact_details" msgid="6754415338321837001">"Ingen utfyllende informasjon for denne kontakten"</string>
@@ -374,9 +386,42 @@
<string name="pick_photo" msgid="448886509158039462">"Velg bilde fra galleriet"</string>
<string name="locale_change_in_progress" msgid="1124266507671178413">"Kontaktlisten er oppdatert med nytt språk."\n\n"Vent litt ..."</string>
<string name="upgrade_in_progress" msgid="7530893673211750223">"Kontaktlisten oppdateres."\n\n"Vent litt ..."</string>
- <string name="upgrade_out_of_memory" msgid="492151063059824962">"Oppgradering av kontakter pågår. "\n\n"Oppgraderingsprosessen krever cirka <xliff:g id="SIZE_IN_MEGABYTES">%d</xliff:g> MB internt minne på telefonen."\n\n"Velg ett av følgende alternativer:"</string>
+ <string name="upgrade_out_of_memory" msgid="6713914687111678777">"Oppgradering av kontakter pågår. "\n\n"Oppgraderingsprosessen krever cirka <xliff:g id="SIZE_IN_MEGABYTES">%d</xliff:g> MB internt minne på telefonen."\n\n"Velg ett av følgende alternativer:"</string>
<string name="upgrade_out_of_memory_uninstall" msgid="1721798828992091432">"Avinstaller noen programmer"</string>
<string name="upgrade_out_of_memory_retry" msgid="8431289830472724609">"Prøv å oppgradere på nytt"</string>
<string name="search_results_for" msgid="8705490885073188513">"Søkeresultater for <xliff:g id="QUERY">%s</xliff:g>"</string>
<string name="search_results_searching" msgid="7755623475227227314">"Søker ..."</string>
+ <string name="adding_recipients" msgid="5123647646892106631">"Laster inn ..."</string>
+ <string name="menu_display_selected" msgid="6470001164297969034">"Vis valgte"</string>
+ <string name="menu_display_all" msgid="8887488642609786198">"Vis alle"</string>
+ <string name="menu_select_all" msgid="621719255150713545">"Marker alle"</string>
+ <string name="menu_select_none" msgid="7093222469852132345">"Fjern alle markeringer"</string>
+ <plurals name="multiple_picker_title">
+ <item quantity="one" msgid="4761009734586319101">"Én mottager valgt"</item>
+ <item quantity="other" msgid="4608837420986126229">"<xliff:g id="COUNT">%d</xliff:g> mottagere valgt"</item>
+ </plurals>
+ <string name="unknown_contacts_separator" msgid="2490675836664397214">"Ukjente kontakter"</string>
+ <string name="no_contacts_selected" msgid="5877803471037324613">"Ingen kontakter valgt."</string>
+ <string name="add_field" msgid="5257149039253569615">"Legg til informasjon"</string>
+ <string name="contact_status_update_attribution" msgid="752179367353018597">"via <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
+ <string name="contact_status_update_attribution_with_date" msgid="7358045508107825068">"<xliff:g id="DATE">%1$s</xliff:g> via <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
+ <string name="description_edit" msgid="1601490950771217014">"rediger"</string>
+ <string name="description_star" msgid="2605854427360036550">"favoritt"</string>
+ <string name="edit_contact" msgid="7529281274005689512">"Rediger kontakt"</string>
+ <plurals name="merge_info">
+ <item quantity="one" msgid="148365587896371969">"ikke slått sammen"</item>
+ <item quantity="other" msgid="425683718017380845">"sammenslått fra <xliff:g id="COUNT">%0$d</xliff:g> kilder"</item>
+ </plurals>
+ <string name="edit_delete_rawcontact" msgid="5054644778227879148">"Slett"</string>
+ <string name="edit_error_saving" msgid="2153949392245240166">"Feil ved lagring"</string>
+ <string name="edit_structured_editor_button" msgid="3830078388142808106">"..."</string>
+ <string name="local_invisible_directory" msgid="6046691709127661065">"Andre"</string>
+ <!-- no translation found for aggregation_suggestion_title:one (7676930148670768645) -->
+ <!-- no translation found for aggregation_suggestion_title:other (3744098467672876675) -->
+ <!-- no translation found for aggregation_suggestion_joined_title (5342738708069602301) -->
+ <skip />
+ <!-- no translation found for aggregation_suggestion_join_button (7248325487963514260) -->
+ <skip />
+ <!-- no translation found for aggregation_suggestion_view_button (8232061564083962522) -->
+ <skip />
</resources>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index b2b08d1..b86c6c1 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -31,6 +31,8 @@
<string name="viewContactTitle" msgid="7989394521836644384">"Details van contact"</string>
<string name="viewContactDesription" msgid="214186610887547860">"Contact weergeven"</string>
<string name="editContactDescription" msgid="2947202828256214947">"Contact bewerken"</string>
+ <string name="edit_email_caption" msgid="2401084399676154272">"E-mailadres:"</string>
+ <string name="edit_email_type_caption" msgid="66331218163770698">"Type:"</string>
<string name="insertContactDescription" msgid="4709878105452681987">"Contact maken"</string>
<string name="searchHint" msgid="8482945356247760701">"Contacten zoeken"</string>
<string name="menu_search" msgid="9147752853603483719">"Zoeken"</string>
@@ -123,13 +125,13 @@
</plurals>
<string name="listTotalAllContactsZero" msgid="6811347506748072822">"Geen zichtbare contacten"</string>
<plurals name="listFoundAllContacts">
- <item quantity="one" msgid="2830107332033967280">"1 contact gevonden"</item>
- <item quantity="other" msgid="7752927996850263152">"<xliff:g id="COUNT">%d</xliff:g> contacten gevonden"</item>
+ <item quantity="one" msgid="5517063038754171134">"1 gevonden"</item>
+ <item quantity="other" msgid="3852668542926965042">"<xliff:g id="COUNT">%d</xliff:g> gevonden"</item>
</plurals>
- <string name="listFoundAllContactsZero" msgid="5554368784319460828">"Contact niet gevonden"</string>
+ <string name="listFoundAllContactsZero" msgid="777952841930508289">"Niet gevonden"</string>
<plurals name="searchFoundContacts">
- <item quantity="one" msgid="916648718690661777">"1 contact"</item>
- <item quantity="other" msgid="5660384247071761844">"<xliff:g id="COUNT">%d</xliff:g> contacten"</item>
+ <item quantity="one" msgid="4826918429708286628">"1 gevonden"</item>
+ <item quantity="other" msgid="7988132539476575389">"<xliff:g id="COUNT">%d</xliff:g> gevonden"</item>
</plurals>
<string name="contactsIconLabel" msgid="7666609097606552806">"Contact"</string>
<string name="contactsFavoritesLabel" msgid="8417039765586853670">"Favoriet"</string>
@@ -159,10 +161,10 @@
<string name="simContacts_title" msgid="27341688347689769">"Contacten op SIM-kaart"</string>
<string name="noContactsHelpTextWithSyncForCreateShortcut" msgid="801504710275614594">"U heeft geen contacten om weer te geven. Als u zojuist een account heeft toegevoegd, kan het enkele minuten duren voordat de contacten zijn gesynchroniseerd."</string>
<string name="noContactsHelpTextForCreateShortcut" msgid="3081286388667108335">"U heeft geen contacten om weer te geven."</string>
- <string name="noContactsHelpText" msgid="6788487368878712350">"U heeft geen contacten om weer te geven."\n\n"Als u contacten wilt toevoegen, drukt u op "<font fgcolor="#ffffffff"><b>"\'Menu\'"</b></font>" en raakt u de volgende opties aan:"\n" "\n<li><font fgcolor="#ffffffff"><b>"\'Accounts\'"</b></font>" om een account toe te voegen of een account te configureren met contacten die u kunt synchroniseren met de telefoon"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"\'Nieuw contact\'"</b></font>" om een geheel nieuw contact te maken"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"\'Importeren/exporteren\'"</b></font>\n</li></string>
- <string name="noContactsHelpTextWithSync" msgid="3734101165712848179">"U heeft geen contacten om weer te geven. (Als u net een account heeft toegevoegd, kan het enkele minuten duren voordat de contacten zijn gesynchroniseerd.)"\n\n"Als u contacten wilt toevoegen, drukt u op "<font fgcolor="#ffffffff"><b>"\'Menu\'"</b></font>" en raakt u de volgende opties aan:"\n" "\n<li><font fgcolor="#ffffffff"><b>"\'Accounts\'"</b></font>" om een account toe te voegen of een account te configureren met contacten die u kunt synchroniseren met de telefoon"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"\'Weergaveopties\'"</b></font>" om de zichtbaarheid van contacten te wijzigen"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"\'Nieuw contact\'"</b></font>" om een geheel nieuw contact te maken"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"\'Importeren/exporteren\'"</b></font>\n</li></string>
- <string name="noContactsNoSimHelpText" msgid="6553845386917463292">"U heeft geen contacten om weer te geven."\n\n"Als u contacten wilt toevoegen, drukt u op "<font fgcolor="#ffffffff"><b>"\'Menu\'"</b></font>" en raakt u de volgende opties aan:"\n" "\n<li><font fgcolor="#ffffffff"><b>"\'Account "</b></font>" om een account toe te voegen of een account te configureren met contacten die u kunt synchroniseren met de telefoon"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"\'Nieuw contact\'"</b></font>" om een geheel nieuw contact te maken"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"\'Importeren/exporteren\'"</b></font>\n</li></string>
- <string name="noContactsNoSimHelpTextWithSync" msgid="1122296298361373488">"U heeft geen contacten om weer te geven. (Als u net een account heeft toegevoegd, kan het enkele minuten duren voordat de contacten zijn gesynchroniseerd.)"\n\n"Als u contacten wilt toevoegen, drukt u op "<font fgcolor="#ffffffff"><b>"\'Menu\'"</b></font>" en raakt u de volgende opties aan:"\n" "\n<li><font fgcolor="#ffffffff"><b>"\'Accounts\'"</b></font>" om een account toe te voegen of een account te configureren met contacten die u kunt synchroniseren met de telefoon"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"\'Weergaveopties\'"</b></font>" om de zichtbaarheid van contacten te wijzigen"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"\'Nieuw contact\'"</b></font>" om een geheel nieuw contact te maken"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"\'Importeren/exporteren\'"</b></font>\n</li></string>
+ <string name="noContactsHelpText" msgid="7633826236417884130">"U heeft geen contacten om weer te geven."\n\n"Als u contacten wilt toevoegen, drukt u op "<font fgcolor="#ffffffff"><b>"\'Menu\'"</b></font>" en raakt u de volgende opties aan:"\n" "\n<li><font fgcolor="#ffffffff"><b>"\'Accounts\'"</b></font>" om een account toe te voegen of te configureren met contacten die u kunt synchroniseren met de telefoon"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"\'Nieuw contact\'"</b></font>" om een geheel nieuw contact te maken"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"\'Importeren/exporteren\'"</b></font>" om contacten te importeren van uw SIM- of SD-kaart"\n</li></string>
+ <string name="noContactsHelpTextWithSync" msgid="3017521127042216243">"U heeft geen contacten om weer te geven. (Als u net een account heeft toegevoegd, kan het enkele minuten duren voordat de contacten zijn gesynchroniseerd.)"\n\n"Als u contacten wilt toevoegen, drukt u op "<font fgcolor="#ffffffff"><b>"\'Menu\'"</b></font>" en raakt u de volgende opties aan:"\n" "\n<li><font fgcolor="#ffffffff"><b>"\'Accounts\'"</b></font>" om een account toe te voegen of te configureren met contacten die u kunt synchroniseren met de telefoon"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"\'Weergaveopties\'"</b></font>" om de zichtbaarheid van contacten te wijzigen"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"\'Nieuw contact\'"</b></font>" om een geheel nieuw contact toe te voegen"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"\'Importeren/exporteren\'"</b></font>" om contacten van uw SIM- of SD-kaart te importeren"\n</li></string>
+ <string name="noContactsNoSimHelpText" msgid="467658807711582876">"U heeft geen contacten om weer te geven."\n\n"Als u contacten wilt toevoegen, drukt u op "<font fgcolor="#ffffffff"><b>"\'Menu\'"</b></font>" en raakt u de volgende opties aan:"\n" "\n<li><font fgcolor="#ffffffff"><b>"\'Accounts\'"</b></font>" om een account toe te voegen of te configureren met contacten die u kunt synchroniseren met de telefoon"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"\'Nieuw contact\'"</b></font>" om een geheel nieuw contact te maken"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"\'Importeren/exporteren\'"</b></font>" om contacten van uw SD-kaart te importeren"\n</li></string>
+ <string name="noContactsNoSimHelpTextWithSync" msgid="9040060730467973050">"U heeft geen contacten om weer te geven. (Als u net een account heeft toegevoegd, kan het enkele minuten duren voordat de contacten zijn gesynchroniseerd.)"\n\n"Als u contacten wilt toevoegen, drukt u op "<font fgcolor="#ffffffff"><b>"\'Menu\'"</b></font>" en raakt u de volgende opties aan:"\n" "\n<li><font fgcolor="#ffffffff"><b>"\'Accounts\'"</b></font>" om een account toe te voegen of te configureren met contacten die u kunt synchroniseren met de telefoon"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"\'Weergaveopties\'"</b></font>" om de zichtbaarheid van contacten te wijzigen"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"\'Nieuw contact\'"</b></font>" om een geheel nieuw contact te maken"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"\'Importeren/exporteren\'"</b></font>" om contacten van uw SD-kaart te importeren"\n</li></string>
<string name="noFavoritesHelpText" msgid="3744655776704833277">"U heeft geen favorieten."\n\n"U kunt als volgt een contact toevoegen aan uw lijst met favorieten:"\n\n" "<li>"Raak het tabblad \'"<b>"Contacten"</b>"\' aan."\n</li>" "\n<li>"Raak het contact aan dat u wilt toevoegen aan uw favorieten."\n</li>" "\n<li>"Raak de ster aan die wordt weergegeven naast de naam van het contact."\n</li></string>
<string name="liveFolder_all_label" msgid="5961411940473276616">"Alle contacten"</string>
<string name="liveFolder_favorites_label" msgid="2674341514070517105">"Met ster"</string>
@@ -179,7 +181,7 @@
<string name="callBack" msgid="5498224409038809224">"Terugbellen"</string>
<string name="callAgain" msgid="3197312117049874778">"Opnieuw bellen"</string>
<string name="returnCall" msgid="8171961914203617813">"Terugbellen"</string>
- <string name="callDetailsDurationFormat" msgid="8157706382818184268">"<xliff:g id="MINUTES">%1$s</xliff:g> min. <xliff:g id="SECONDS">%2$s</xliff:g> sec."</string>
+ <string name="callDetailsDurationFormat" msgid="8157706382818184268">"<xliff:g id="MINUTES">%s</xliff:g> min. <xliff:g id="SECONDS">%s</xliff:g> sec."</string>
<string name="favoritesFrquentSeparator" msgid="8107518433381283736">"Regelmatig contact"</string>
<string name="add_contact_dlg_title" msgid="2896685845822146494">"Contact toevoegen"</string>
<string name="add_contact_dlg_message_fmt" msgid="7986472669444326576">"Voeg \"<xliff:g id="EMAIL">%s</xliff:g>\" toe aan contactpersonen?"</string>
@@ -217,18 +219,24 @@
<string name="scanning_sdcard_failed_title" msgid="3506782007953167180">"Scannen van SD-kaart is mislukt"</string>
<string name="scanning_sdcard_failed_message" msgid="3761992500690182922">"Scannen van SD-kaart is mislukt: (Reden: \'<xliff:g id="FAIL_REASON">%s</xliff:g>\')"</string>
<string name="fail_reason_io_error" msgid="5922864781066136340">"I/O-fout"</string>
+ <string name="fail_reason_low_memory_during_import" msgid="7514918659342886381">"Onvoldoende geheugen (het bestand is mogelijk te groot)"</string>
<string name="fail_reason_vcard_parse_error" msgid="1201233722762680214">"Parseren van vCard is mislukt om onbekende reden"</string>
<string name="fail_reason_vcard_not_supported_error" msgid="655208100451286027">"Kan vCard niet parseren, ook al lijkt de indeling geldig. vCard wordt niet ondersteund door de huidige implementatie"</string>
<string name="fail_reason_no_vcard_file" msgid="6376516175882881595">"Geen vCard-bestand gevonden op SD-kaart"</string>
+ <string name="fail_reason_failed_to_collect_vcard_meta_info" msgid="4154492282316067754">"Verzamelen van metagegevens van betreffende vCard-bestanden is mislukt."</string>
<string name="fail_reason_failed_to_read_files" msgid="3659521123567134029">"Een of meer bestanden kunnen niet worden geïmporteerd (%s)"</string>
<string name="fail_reason_unknown" msgid="999034019513096768">"Onbekende fout"</string>
<string name="select_vcard_title" msgid="3968948173786172468">"vCard-bestand selecteren"</string>
- <string name="progress_shower_message" msgid="5636525578293752526">"<xliff:g id="ACTION">%1$s</xliff:g>"\n"<xliff:g id="FILENAME">%2$s</xliff:g>"</string>
- <string name="reading_vcard_title" msgid="4723433501579653199">"vCard lezen"</string>
- <string name="reading_vcard_message" msgid="6381368920030748743">"vCard-bestand(en) lezen"</string>
- <string name="reading_vcard_failed_title" msgid="4923008144735294994">"Lezen van vCard-gegevens is mislukt"</string>
- <string name="reading_vcard_contacts" msgid="3066834102042012868">"<xliff:g id="CURRENT_NUMBER">%1$s</xliff:g> van <xliff:g id="TOTAL_NUMBER">%2$s</xliff:g> contacten"</string>
- <string name="reading_vcard_files" msgid="34180143726972661">"<xliff:g id="CURRENT_NUMBER">%1$s</xliff:g> van <xliff:g id="TOTAL_NUMBER">%2$s</xliff:g> bestanden"</string>
+ <string name="caching_vcard_title" msgid="5009556022082659780">"vCard(s) cachen in lokale tijdelijke opslag"</string>
+ <string name="caching_vcard_message" msgid="2380844718093378900">"De importer cacht vCard(s) in de lokale tijdelijke opslag. Daadwerkelijk importeren begint gauw."</string>
+ <string name="progress_notifier_message" msgid="8412196553907067659">"<xliff:g id="CURRENT_NUMBER">%s</xliff:g>/<xliff:g id="TOTAL_NUMBER">%s</xliff:g>: <xliff:g id="FILENAME">%s</xliff:g>"</string>
+ <string name="reading_vcard_title" msgid="3324988724123717025">"vCard(s) lezen"</string>
+ <string name="reading_vcard_failed_title" msgid="2162610359561887043">"Lezen van vCard-gegevens is mislukt"</string>
+ <string name="reading_vcard_canceled_title" msgid="1770608329958463131">"Lezen van vCard-gegevens is geannuleerd"</string>
+ <string name="importing_vcard_finished_title" msgid="3213257229785702182">"Importeren van vCard voltooid"</string>
+ <string name="vcard_importer_start_message" msgid="5520549352987753320">"vCard-importer gestart."</string>
+ <string name="vcard_importer_will_start_message" msgid="2055145402481822160">"Na enige tijd wordt de vCard geïmporteerd door de vCard-importer."</string>
+ <string name="percentage" msgid="34897865327092209">"%s%%"</string>
<string name="confirm_export_title" msgid="7648747763127442983">"Exporteren bevestigen"</string>
<string name="confirm_export_message" msgid="3875683519257829750">"Weet u zeker dat u uw lijst met contacten wilt exporteren naar \'<xliff:g id="VCARD_FILENAME">%s</xliff:g>\'?"</string>
<string name="exporting_contact_failed_title" msgid="585823094820602526">"Exporteren van contactgegevens mislukt"</string>
@@ -236,6 +244,8 @@
<string name="fail_reason_no_exportable_contact" msgid="4919714086648344495">"Er is geen contact dat geëxporteerd kan worden"</string>
<string name="fail_reason_too_many_vcard" msgid="7084146295639672658">"Te veel vCard-gegevens op de SD-kaart"</string>
<string name="fail_reason_too_long_filename" msgid="1915716071321839166">"Vereiste bestandsnaam is te lang (\'<xliff:g id="FILENAME">%s</xliff:g>\')"</string>
+ <string name="vcard_exporter_start_message" msgid="6497218576519292369">"vCard-exporter gestart."</string>
+ <string name="exporting_vcard_finished_title" msgid="1268425436375550862">"Exporteren van vCard voltooid"</string>
<string name="exporting_contact_list_title" msgid="9072240631534457415">"Contactgegevens exporteren"</string>
<string name="exporting_contact_list_message" msgid="5640326540405486055">"Contactgegevens exporteren naar \'<xliff:g id="FILE_NAME">%s</xliff:g>\'"</string>
<string name="fail_reason_could_not_initialize_exporter" msgid="4943708332700987376">"Kan het exportprogramma niet initialiseren: \'<xliff:g id="EXACT_REASON">%s</xliff:g>\'"</string>
@@ -243,8 +253,8 @@
<string name="composer_failed_to_get_database_infomation" msgid="3723109558155169053">"Databasegegevens ophalen mislukt"</string>
<string name="composer_has_no_exportable_contact" msgid="754734132189369094">"Er is geen contact dat kan worden geëxporteerd. Als er contacten op uw telefoon staan, kan het exporteren van alle contacten vanaf uw telefoon mogelijk worden geblokkeerd door een gegevensprovider."</string>
<string name="composer_not_initialized" msgid="8041534450748388843">"De vCard-editor is niet juist geïnitialiseerd"</string>
- <string name="fail_reason_could_not_open_file" msgid="4013520943128739511">"Kan \'<xliff:g id="FILE_NAME">%1$s</xliff:g>\' niet openen: <xliff:g id="EXACT_REASON">%2$s</xliff:g>"</string>
- <string name="exporting_contact_list_progress" msgid="560522409559101193">"<xliff:g id="CURRENT_NUMBER">%1$s</xliff:g> van <xliff:g id="TOTAL_NUMBER">%2$s</xliff:g> contacten"</string>
+ <string name="fail_reason_could_not_open_file" msgid="4013520943128739511">"Kan \'<xliff:g id="FILE_NAME">%s</xliff:g>\' niet openen: <xliff:g id="EXACT_REASON">%s</xliff:g>"</string>
+ <string name="exporting_contact_list_progress" msgid="560522409559101193">"<xliff:g id="CURRENT_NUMBER">%s</xliff:g> van <xliff:g id="TOTAL_NUMBER">%s</xliff:g> contacten"</string>
<string name="search_settings_description" msgid="2675223022992445813">"Namen van uw contacten"</string>
<string name="add_2sec_pause" msgid="9214012315201040129">"Pauze van 2 seconden toevoegen"</string>
<string name="add_wait" msgid="3360818652790319634">"Wachten toevoegen"</string>
@@ -254,6 +264,7 @@
<string name="quickcontact_missing_app" msgid="4600366393134289038">"Er is geen toepassing gevonden om deze actie uit te voeren"</string>
<string name="quickcontact_remember_choice" msgid="5964536411579749424">"Deze keuze onthouden"</string>
<string name="quickcontact_missing_name" msgid="5590266114306996632">"Onbekend"</string>
+ <string name="quickcontact_no_data" msgid="2098000859125253675">"Geen gegevens"</string>
<string name="menu_accounts" msgid="8499114602017077970">"Accounts"</string>
<string name="menu_import_export" msgid="3765725645491577190">"Importeren/exporteren"</string>
<string name="dialog_import_export" msgid="4771877268244096596">"Contacten importeren/exporteren"</string>
@@ -359,6 +370,7 @@
<string name="name_phonetic_family" msgid="462095502140180305">"Fonetische achternaam"</string>
<string name="account_type_format" msgid="718948015590343010">"<xliff:g id="SOURCE">%1$s</xliff:g> contact"</string>
<string name="from_account_format" msgid="687567483928582084">"van <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
+ <string name="account_type_and_name" msgid="2876220035411512934">"<xliff:g id="SOURCE_0">%1$s</xliff:g> contact uit <xliff:g id="SOURCE_1">%2$s</xliff:g>"</string>
<string name="use_photo_as_primary" msgid="8807110122951157246">"Deze foto gebruiken"</string>
<string name="contact_read_only" msgid="1203216914575723978">"Contactgegevens van <xliff:g id="SOURCE">%1$s</xliff:g> kunnen niet worden bewerkt op dit apparaat."</string>
<string name="no_contact_details" msgid="6754415338321837001">"Geen extra gegevens voor dit contact"</string>
@@ -374,9 +386,42 @@
<string name="pick_photo" msgid="448886509158039462">"Foto selecteren in Galerij"</string>
<string name="locale_change_in_progress" msgid="1124266507671178413">"Lijst met contacten wordt bijgewerkt om de gewijzigde taal te weerspiegelen."\n\n"Een ogenblik geduld..."</string>
<string name="upgrade_in_progress" msgid="7530893673211750223">"Lijst met contacten wordt bijgewerkt."\n\n"Een ogenblik geduld..."</string>
- <string name="upgrade_out_of_memory" msgid="492151063059824962">"Contacten worden bijgewerkt. "\n\n"Voor het upgradeproces is ongeveer <xliff:g id="SIZE_IN_MEGABYTES">%d</xliff:g> MB interne telefoonopslag vereist."\n\n"Kies een van de volgende opties:"</string>
+ <string name="upgrade_out_of_memory" msgid="6713914687111678777">"Contacten worden bijgewerkt. "\n\n"Voor het upgradeproces is ongeveer <xliff:g id="SIZE_IN_MEGABYTES">%d</xliff:g> MB interne telefoonopslag vereist."\n\n"Kies een van de volgende opties:"</string>
<string name="upgrade_out_of_memory_uninstall" msgid="1721798828992091432">"Verwijder enkele toepassingen"</string>
<string name="upgrade_out_of_memory_retry" msgid="8431289830472724609">"Upgrade opnieuw proberen"</string>
<string name="search_results_for" msgid="8705490885073188513">"Zoekresultaten voor: <xliff:g id="QUERY">%s</xliff:g>"</string>
<string name="search_results_searching" msgid="7755623475227227314">"Zoeken..."</string>
+ <string name="adding_recipients" msgid="5123647646892106631">"Laden…"</string>
+ <string name="menu_display_selected" msgid="6470001164297969034">"Selectie weergeven"</string>
+ <string name="menu_display_all" msgid="8887488642609786198">"Alles weergeven"</string>
+ <string name="menu_select_all" msgid="621719255150713545">"Alles selecteren"</string>
+ <string name="menu_select_none" msgid="7093222469852132345">"Selectie ongedaan maken"</string>
+ <plurals name="multiple_picker_title">
+ <item quantity="one" msgid="4761009734586319101">"1 ontvanger geselecteerd"</item>
+ <item quantity="other" msgid="4608837420986126229">"<xliff:g id="COUNT">%d</xliff:g> ontvangers geselecteerd"</item>
+ </plurals>
+ <string name="unknown_contacts_separator" msgid="2490675836664397214">"Onbekende contacten"</string>
+ <string name="no_contacts_selected" msgid="5877803471037324613">"Geen contacten geselecteerd."</string>
+ <string name="add_field" msgid="5257149039253569615">"Informatie toevoegen"</string>
+ <string name="contact_status_update_attribution" msgid="752179367353018597">"via <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
+ <string name="contact_status_update_attribution_with_date" msgid="7358045508107825068">"<xliff:g id="DATE">%1$s</xliff:g> via <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
+ <string name="description_edit" msgid="1601490950771217014">"bewerken"</string>
+ <string name="description_star" msgid="2605854427360036550">"favoriet"</string>
+ <string name="edit_contact" msgid="7529281274005689512">"Contact bewerken"</string>
+ <plurals name="merge_info">
+ <item quantity="one" msgid="148365587896371969">"niet samengevoegd"</item>
+ <item quantity="other" msgid="425683718017380845">"samengevoegd uit <xliff:g id="COUNT">%0$d</xliff:g> bronnen"</item>
+ </plurals>
+ <string name="edit_delete_rawcontact" msgid="5054644778227879148">"Verwijderen"</string>
+ <string name="edit_error_saving" msgid="2153949392245240166">"Fout bij opslaan"</string>
+ <string name="edit_structured_editor_button" msgid="3830078388142808106">"..."</string>
+ <string name="local_invisible_directory" msgid="6046691709127661065">"Overig"</string>
+ <!-- no translation found for aggregation_suggestion_title:one (7676930148670768645) -->
+ <!-- no translation found for aggregation_suggestion_title:other (3744098467672876675) -->
+ <!-- no translation found for aggregation_suggestion_joined_title (5342738708069602301) -->
+ <skip />
+ <!-- no translation found for aggregation_suggestion_join_button (7248325487963514260) -->
+ <skip />
+ <!-- no translation found for aggregation_suggestion_view_button (8232061564083962522) -->
+ <skip />
</resources>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index ac25187..8f272a8 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -31,6 +31,8 @@
<string name="viewContactTitle" msgid="7989394521836644384">"Szczegóły kontaktu"</string>
<string name="viewContactDesription" msgid="214186610887547860">"Wyświetl kontakt"</string>
<string name="editContactDescription" msgid="2947202828256214947">"Edytuj kontakt"</string>
+ <string name="edit_email_caption" msgid="2401084399676154272">"Adres e-mail:"</string>
+ <string name="edit_email_type_caption" msgid="66331218163770698">"Typ:"</string>
<string name="insertContactDescription" msgid="4709878105452681987">"Utwórz kontakt"</string>
<string name="searchHint" msgid="8482945356247760701">"Przeszukuj kontakty"</string>
<string name="menu_search" msgid="9147752853603483719">"Szukaj"</string>
@@ -123,13 +125,13 @@
</plurals>
<string name="listTotalAllContactsZero" msgid="6811347506748072822">"Brak widocznych kontaktów"</string>
<plurals name="listFoundAllContacts">
- <item quantity="one" msgid="2830107332033967280">"Znaleziono 1 kontakt"</item>
- <item quantity="other" msgid="7752927996850263152">"Znalezionych kontaktów: <xliff:g id="COUNT">%d</xliff:g>"</item>
+ <item quantity="one" msgid="5517063038754171134">"Znaleziono: 1"</item>
+ <item quantity="other" msgid="3852668542926965042">"Znaleziono: <xliff:g id="COUNT">%d</xliff:g>"</item>
</plurals>
- <string name="listFoundAllContactsZero" msgid="5554368784319460828">"Nie znaleziono kontaktu"</string>
+ <string name="listFoundAllContactsZero" msgid="777952841930508289">"Nie znaleziono"</string>
<plurals name="searchFoundContacts">
- <item quantity="one" msgid="916648718690661777">"Liczba kontaktów: 1"</item>
- <item quantity="other" msgid="5660384247071761844">"Liczba kontaktów: <xliff:g id="COUNT">%d</xliff:g>"</item>
+ <item quantity="one" msgid="4826918429708286628">"Znaleziono: 1"</item>
+ <item quantity="other" msgid="7988132539476575389">"Znaleziono: <xliff:g id="COUNT">%d</xliff:g>"</item>
</plurals>
<string name="contactsIconLabel" msgid="7666609097606552806">"Kontakty"</string>
<string name="contactsFavoritesLabel" msgid="8417039765586853670">"Ulubione"</string>
@@ -159,10 +161,10 @@
<string name="simContacts_title" msgid="27341688347689769">"Kontakty z karty SIM"</string>
<string name="noContactsHelpTextWithSyncForCreateShortcut" msgid="801504710275614594">"Nie masz żadnych kontaktów do wyświetlenia (jeśli konto zostało dopiero dodane, synchronizacja kontaktów może potrwać kilka minut)."</string>
<string name="noContactsHelpTextForCreateShortcut" msgid="3081286388667108335">"Nie masz żadnych kontaktów do wyświetlenia."</string>
- <string name="noContactsHelpText" msgid="6788487368878712350">"Nie masz żadnych kontaktów do wyświetlenia."\n\n"Aby dodać kontakty, naciśnij przycisk "<font fgcolor="#ffffffff"><b>"Menu"</b></font>" i dotknij opcji:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Konta"</b></font>", aby dodać lub skonfigurować konto zawierające kontakty, które można synchronizować z telefonem."\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Nowy kontakt"</b></font>", aby utworzyć nowy kontakt od początku."\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importuj/eksportuj"</b></font>\n</li></string>
- <string name="noContactsHelpTextWithSync" msgid="3734101165712848179">"Nie masz żadnych kontaktów do wyświetlenia. (Jeśli przed chwilą dodano konto, synchronizacja kontaktów może potrwać kilka minut)."\n\n"Aby dodać kontakty, naciśnij przycisk "<font fgcolor="#ffffffff"><b>"Menu"</b></font>" i dotknij opcji:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Konta"</b></font>", aby dodać lub skonfigurować konto zawierające kontakty, które można synchronizować z telefonem."\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Opcje wyświetlania"</b></font>", aby zmienić widoczne typy kontaktów."\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Nowy kontakt"</b></font>", aby utworzyć nowy kontakt od początku."\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importuj/eksportuj"</b></font>\n</li></string>
- <string name="noContactsNoSimHelpText" msgid="6553845386917463292">"Nie masz żadnych kontaktów do wyświetlenia."\n\n"Aby dodać kontakty, naciśnij przycisk "<font fgcolor="#ffffffff"><b>"Menu"</b></font>" i dotknij opcji:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Konta"</b></font>", aby dodać lub skonfigurować konto zawierające kontakty, które można synchronizować z telefonem."\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Nowy kontakt"</b></font>", aby utworzyć nowy kontakt od początku."\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importuj/eksportuj"</b></font>\n</li></string>
- <string name="noContactsNoSimHelpTextWithSync" msgid="1122296298361373488">"Nie masz żadnych kontaktów do wyświetlenia. (Jeśli przed chwilą dodano konto, synchronizacja kontaktów może potrwać kilka minut)."\n\n"Aby dodać kontakty, naciśnij przycisk "<font fgcolor="#ffffffff"><b>"Menu"</b></font>" i dotknij opcji:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Konta"</b></font>", aby dodać lub skonfigurować konto zawierające kontakty, które można synchronizować z telefonem."\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Opcje wyświetlania"</b></font>", aby zmienić widoczne typy kontaktów."\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Nowy kontakt"</b></font>", aby utworzyć nowy kontakt od początku."\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importuj/eksportuj"</b></font>\n</li></string>
+ <string name="noContactsHelpText" msgid="7633826236417884130">"Nie masz żadnych kontaktów do wyświetlenia."\n\n"Aby dodać kontakty, naciśnij przycisk "<font fgcolor="#ffffffff"><b>"Menu"</b></font>" i dotknij opcji:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Konta"</b></font>", aby dodać lub skonfigurować konto zawierające kontakty, które można synchronizować z telefonem;"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Nowy kontakt"</b></font>", aby utworzyć nowy kontakt od początku;"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importuj/eksportuj"</b></font>", aby zaimportować kontakty z karty SIM lub SD."\n</li></string>
+ <string name="noContactsHelpTextWithSync" msgid="3017521127042216243">"Nie masz żadnych kontaktów do wyświetlenia. (Jeśli przed chwilą dodano konto, synchronizacja kontaktów może potrwać kilka minut)."\n\n"Aby dodać kontakty, naciśnij przycisk "<font fgcolor="#ffffffff"><b>"Menu"</b></font>" i dotknij opcji:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Konta"</b></font>", aby dodać lub skonfigurować konto zawierające kontakty, które można synchronizować z telefonem;"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Opcje wyświetlania"</b></font>", aby zmienić widoczne typy kontaktów;"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Nowy kontakt"</b></font>", aby utworzyć nowy kontakt od początku;"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importuj/eksportuj"</b></font>", aby zaimportować kontakty z karty SIM lub SD."\n</li></string>
+ <string name="noContactsNoSimHelpText" msgid="467658807711582876">"Nie masz żadnych kontaktów do wyświetlenia."\n\n"Aby dodać kontakty, naciśnij przycisk "<font fgcolor="#ffffffff"><b>"Menu"</b></font>" i dotknij opcji:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Konta"</b></font>", aby dodać lub skonfigurować konto zawierające kontakty, które można synchronizować z telefonem;"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Nowy kontakt"</b></font>", aby utworzyć nowy kontakt od początku;"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importuj/eksportuj"</b></font>", aby zaimportować kontakty z karty SD."\n</li></string>
+ <string name="noContactsNoSimHelpTextWithSync" msgid="9040060730467973050">"Nie masz żadnych kontaktów do wyświetlenia. (Jeśli przed chwilą dodano konto, synchronizacja kontaktów może potrwać kilka minut)."\n\n"Aby dodać kontakty, naciśnij przycisk "<font fgcolor="#ffffffff"><b>"Menu"</b></font>" i dotknij opcji: "\n" "\n<li><font fgcolor="#ffffffff"><b>"Konta"</b></font>", aby dodać lub skonfigurować konto zawierające kontakty, które można synchronizować z telefonem;"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Opcje wyświetlania"</b></font>", aby zmienić widoczne typy kontaktów;"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Nowy kontakt"</b></font>", aby utworzyć nowy kontakt od początku;"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importuj/eksportuj"</b></font>", aby zaimportować kontakty z karty SD."\n</li></string>
<string name="noFavoritesHelpText" msgid="3744655776704833277">"Nie masz żadnych ulubionych kontaktów."\n\n"Aby dodać kontakt do listy ulubionych:"\n\n" "<li>"Dotknij karty "<b>"Kontakty"</b>"."\n</li>" "\n<li>"Dotknij kontaktu, który chcesz dodać do ulubionych."\n</li>" "\n<li>"Dotknij ikony gwiazdki obok nazwy kontaktu."\n</li></string>
<string name="liveFolder_all_label" msgid="5961411940473276616">"Wszystkie kontakty"</string>
<string name="liveFolder_favorites_label" msgid="2674341514070517105">"Oznaczone gwiazdką"</string>
@@ -179,7 +181,7 @@
<string name="callBack" msgid="5498224409038809224">"Oddzwoń"</string>
<string name="callAgain" msgid="3197312117049874778">"Zadzwoń ponownie"</string>
<string name="returnCall" msgid="8171961914203617813">"Połączenie zwrotne"</string>
- <string name="callDetailsDurationFormat" msgid="8157706382818184268">"<xliff:g id="MINUTES">%1$s</xliff:g> min <xliff:g id="SECONDS">%2$s</xliff:g> s"</string>
+ <string name="callDetailsDurationFormat" msgid="8157706382818184268">"<xliff:g id="MINUTES">%s</xliff:g> min <xliff:g id="SECONDS">%s</xliff:g> s"</string>
<string name="favoritesFrquentSeparator" msgid="8107518433381283736">"Częste kontakty"</string>
<string name="add_contact_dlg_title" msgid="2896685845822146494">"Dodaj kontakt"</string>
<string name="add_contact_dlg_message_fmt" msgid="7986472669444326576">"Czy dodać adres „<xliff:g id="EMAIL">%s</xliff:g>” do kontaktów?"</string>
@@ -217,18 +219,24 @@
<string name="scanning_sdcard_failed_title" msgid="3506782007953167180">"Skanowanie karty SD nie powiodło się"</string>
<string name="scanning_sdcard_failed_message" msgid="3761992500690182922">"Skanowanie karty SD nie powiodło się (przyczyna: „<xliff:g id="FAIL_REASON">%s</xliff:g>”)"</string>
<string name="fail_reason_io_error" msgid="5922864781066136340">"Błąd wejścia/wyjścia"</string>
+ <string name="fail_reason_low_memory_during_import" msgid="7514918659342886381">"Niewystarczająca ilość pamięci (plik może być zbyt duży)"</string>
<string name="fail_reason_vcard_parse_error" msgid="1201233722762680214">"Nie można zanalizować pliku vCard z nieoczekiwanego powodu"</string>
<string name="fail_reason_vcard_not_supported_error" msgid="655208100451286027">"Nie można zanalizować pliku vCard, mimo że prawdopodobnie ma on prawidłowy format. Bieżąca wersja go nie obsługuje."</string>
<string name="fail_reason_no_vcard_file" msgid="6376516175882881595">"Na karcie SD nie znaleziono żadnego pliku vCard"</string>
+ <string name="fail_reason_failed_to_collect_vcard_meta_info" msgid="4154492282316067754">"Nie można zebrać metainformacji z podanych plików vCard."</string>
<string name="fail_reason_failed_to_read_files" msgid="3659521123567134029">"Nie można zaimportować co najmniej jednego pliku (%s)."</string>
<string name="fail_reason_unknown" msgid="999034019513096768">"Nieznany błąd"</string>
<string name="select_vcard_title" msgid="3968948173786172468">"Wybierz plik vCard"</string>
- <string name="progress_shower_message" msgid="5636525578293752526">"<xliff:g id="ACTION">%1$s</xliff:g>"\n"<xliff:g id="FILENAME">%2$s</xliff:g>"</string>
- <string name="reading_vcard_title" msgid="4723433501579653199">"Odczytywanie danych vCard"</string>
- <string name="reading_vcard_message" msgid="6381368920030748743">"Odczytywanie plików vCard"</string>
- <string name="reading_vcard_failed_title" msgid="4923008144735294994">"Odczytywanie danych vCard nie powiodło się"</string>
- <string name="reading_vcard_contacts" msgid="3066834102042012868">"Kontakt <xliff:g id="CURRENT_NUMBER">%1$s</xliff:g> z <xliff:g id="TOTAL_NUMBER">%2$s</xliff:g>"</string>
- <string name="reading_vcard_files" msgid="34180143726972661">"plik <xliff:g id="CURRENT_NUMBER">%1$s</xliff:g> z <xliff:g id="TOTAL_NUMBER">%2$s</xliff:g>"</string>
+ <string name="caching_vcard_title" msgid="5009556022082659780">"Buforowanie kart vCard w lokalnym obszarze tymczasowym"</string>
+ <string name="caching_vcard_message" msgid="2380844718093378900">"Program importujący buforuje karty vCard w lokalnym obszarze tymczasowym. Rzeczywisty proces importowania rozpocznie się za chwilę."</string>
+ <string name="progress_notifier_message" msgid="8412196553907067659">"<xliff:g id="CURRENT_NUMBER">%s</xliff:g>/<xliff:g id="TOTAL_NUMBER">%s</xliff:g>: <xliff:g id="FILENAME">%s</xliff:g>"</string>
+ <string name="reading_vcard_title" msgid="3324988724123717025">"Odczytywanie kart vCard"</string>
+ <string name="reading_vcard_failed_title" msgid="2162610359561887043">"Nie można odczytać danych karty vCard"</string>
+ <string name="reading_vcard_canceled_title" msgid="1770608329958463131">"Odczytywanie danych kart vCard zostało anulowane"</string>
+ <string name="importing_vcard_finished_title" msgid="3213257229785702182">"Zakończono importowanie kart vCard"</string>
+ <string name="vcard_importer_start_message" msgid="5520549352987753320">"Uruchomiono program importujący karty vCard."</string>
+ <string name="vcard_importer_will_start_message" msgid="2055145402481822160">"Program importujący karty vCard zaimportuje za chwilę karty vCard."</string>
+ <string name="percentage" msgid="34897865327092209">"%s%%"</string>
<string name="confirm_export_title" msgid="7648747763127442983">"Potwierdź eksport"</string>
<string name="confirm_export_message" msgid="3875683519257829750">"Czy na pewno chcesz wyeksportować listę kontaktów do pliku „<xliff:g id="VCARD_FILENAME">%s</xliff:g>”?"</string>
<string name="exporting_contact_failed_title" msgid="585823094820602526">"Nie można wyeksportować danych kontaktu"</string>
@@ -236,6 +244,8 @@
<string name="fail_reason_no_exportable_contact" msgid="4919714086648344495">"Brak kontaktów, które można wyeksportować"</string>
<string name="fail_reason_too_many_vcard" msgid="7084146295639672658">"Zbyt dużo plików vCard na karcie SD"</string>
<string name="fail_reason_too_long_filename" msgid="1915716071321839166">"Wymagana nazwa pliku jest zbyt długa („<xliff:g id="FILENAME">%s</xliff:g>”)"</string>
+ <string name="vcard_exporter_start_message" msgid="6497218576519292369">"Uruchomiono program eksportujący karty vCard."</string>
+ <string name="exporting_vcard_finished_title" msgid="1268425436375550862">"Zakończono eksportowanie kart vCard"</string>
<string name="exporting_contact_list_title" msgid="9072240631534457415">"Eksportowanie danych kontaktowych"</string>
<string name="exporting_contact_list_message" msgid="5640326540405486055">"Eksportowanie danych kontaktowych do pliku „<xliff:g id="FILE_NAME">%s</xliff:g>”"</string>
<string name="fail_reason_could_not_initialize_exporter" msgid="4943708332700987376">"Nie można zainicjować programu eksportującego: „<xliff:g id="EXACT_REASON">%s</xliff:g>”"</string>
@@ -243,8 +253,8 @@
<string name="composer_failed_to_get_database_infomation" msgid="3723109558155169053">"Nie można pobrać informacji o bazie danych"</string>
<string name="composer_has_no_exportable_contact" msgid="754734132189369094">"Brak kontaktów, które można wyeksportować. Jeśli faktycznie masz kontakty w telefonie, wyeksportowanie któregokolwiek z nich poza telefon może być zabronione przez dostawcę danych."</string>
<string name="composer_not_initialized" msgid="8041534450748388843">"Obiekt tworzenia danych vCard nie został poprawnie zainicjowany"</string>
- <string name="fail_reason_could_not_open_file" msgid="4013520943128739511">"Nie można otworzyć pliku „<xliff:g id="FILE_NAME">%1$s</xliff:g>”: <xliff:g id="EXACT_REASON">%2$s</xliff:g>"</string>
- <string name="exporting_contact_list_progress" msgid="560522409559101193">"Kontakt <xliff:g id="CURRENT_NUMBER">%1$s</xliff:g> z <xliff:g id="TOTAL_NUMBER">%2$s</xliff:g>"</string>
+ <string name="fail_reason_could_not_open_file" msgid="4013520943128739511">"Nie można otworzyć pliku „<xliff:g id="FILE_NAME">%s</xliff:g>”: <xliff:g id="EXACT_REASON">%s</xliff:g>"</string>
+ <string name="exporting_contact_list_progress" msgid="560522409559101193">"Kontakt <xliff:g id="CURRENT_NUMBER">%s</xliff:g> z <xliff:g id="TOTAL_NUMBER">%s</xliff:g>"</string>
<string name="search_settings_description" msgid="2675223022992445813">"Imiona i nazwiska oraz nazwy w Twoich kontaktach"</string>
<string name="add_2sec_pause" msgid="9214012315201040129">"Dodaj 2-sekundową pauzę"</string>
<string name="add_wait" msgid="3360818652790319634">"Dodaj oczekiwanie"</string>
@@ -254,6 +264,7 @@
<string name="quickcontact_missing_app" msgid="4600366393134289038">"Nie znaleziono aplikacji do obsługi tej akcji"</string>
<string name="quickcontact_remember_choice" msgid="5964536411579749424">"Zapamiętaj ten wybór"</string>
<string name="quickcontact_missing_name" msgid="5590266114306996632">"Nieznane"</string>
+ <string name="quickcontact_no_data" msgid="2098000859125253675">"Brak danych"</string>
<string name="menu_accounts" msgid="8499114602017077970">"Konta"</string>
<string name="menu_import_export" msgid="3765725645491577190">"Importuj/eksportuj"</string>
<string name="dialog_import_export" msgid="4771877268244096596">"Importuj/eksportuj kontakty"</string>
@@ -359,6 +370,7 @@
<string name="name_phonetic_family" msgid="462095502140180305">"Nazwisko (fonetycznie)"</string>
<string name="account_type_format" msgid="718948015590343010">"Kontakt <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
<string name="from_account_format" msgid="687567483928582084">"z <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
+ <string name="account_type_and_name" msgid="2876220035411512934">"Kontakt <xliff:g id="SOURCE_0">%1$s</xliff:g> z <xliff:g id="SOURCE_1">%2$s</xliff:g>"</string>
<string name="use_photo_as_primary" msgid="8807110122951157246">"Użyj tego zdjęcia"</string>
<string name="contact_read_only" msgid="1203216914575723978">"W tym urządzeniu nie można edytować informacji kontaktowych <xliff:g id="SOURCE">%1$s</xliff:g>."</string>
<string name="no_contact_details" msgid="6754415338321837001">"Brak dodatkowych informacji dla tego kontaktu"</string>
@@ -374,9 +386,42 @@
<string name="pick_photo" msgid="448886509158039462">"Wybierz zdjęcie z galerii"</string>
<string name="locale_change_in_progress" msgid="1124266507671178413">"Lista kontaktów jest aktualizowana, aby odzwierciedlić zmianę języka."\n\n"Czekaj..."</string>
<string name="upgrade_in_progress" msgid="7530893673211750223">"Lista kontaktów jest aktualizowana."\n\n"Czekaj..."</string>
- <string name="upgrade_out_of_memory" msgid="492151063059824962">"Trwa proces uaktualniania kontaktów. "\n\n"Proces uaktualniania wymaga w przybliżeniu <xliff:g id="SIZE_IN_MEGABYTES">%d</xliff:g> MB wewnętrznej pamięci telefonu."\n\n"Wybierz jedną z następujących opcji:"</string>
+ <string name="upgrade_out_of_memory" msgid="6713914687111678777">"Trwa proces uaktualniania kontaktów. "\n\n"Proces uaktualniania wymaga w przybliżeniu <xliff:g id="SIZE_IN_MEGABYTES">%d</xliff:g> MB wewnętrznej pamięci telefonu."\n\n"Wybierz jedną z następujących opcji:"</string>
<string name="upgrade_out_of_memory_uninstall" msgid="1721798828992091432">"Odinstaluj część aplikacji"</string>
<string name="upgrade_out_of_memory_retry" msgid="8431289830472724609">"Ponów próbę uaktualnienia"</string>
<string name="search_results_for" msgid="8705490885073188513">"Wyniki wyszukiwania dla: <xliff:g id="QUERY">%s</xliff:g>"</string>
<string name="search_results_searching" msgid="7755623475227227314">"Trwa wyszukiwanie..."</string>
+ <string name="adding_recipients" msgid="5123647646892106631">"Wczytywanie…"</string>
+ <string name="menu_display_selected" msgid="6470001164297969034">"Pokaż wybrane"</string>
+ <string name="menu_display_all" msgid="8887488642609786198">"Pokaż wszystkie"</string>
+ <string name="menu_select_all" msgid="621719255150713545">"Wybierz wszystko"</string>
+ <string name="menu_select_none" msgid="7093222469852132345">"Anuluj wybór wszystkich\n"</string>
+ <plurals name="multiple_picker_title">
+ <item quantity="one" msgid="4761009734586319101">"Wybrano 1 odbiorcę"</item>
+ <item quantity="other" msgid="4608837420986126229">"Liczba wybranych odbiorców: <xliff:g id="COUNT">%d</xliff:g>"</item>
+ </plurals>
+ <string name="unknown_contacts_separator" msgid="2490675836664397214">"Nieznane kontakty"</string>
+ <string name="no_contacts_selected" msgid="5877803471037324613">"Nie wybrano kontaktów."</string>
+ <string name="add_field" msgid="5257149039253569615">"Dodaj informacje"</string>
+ <string name="contact_status_update_attribution" msgid="752179367353018597">"za pośrednictwem: <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
+ <string name="contact_status_update_attribution_with_date" msgid="7358045508107825068">"<xliff:g id="DATE">%1$s</xliff:g>, za pośrednictwem: <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
+ <string name="description_edit" msgid="1601490950771217014">"edytuj"</string>
+ <string name="description_star" msgid="2605854427360036550">"ulubione"</string>
+ <string name="edit_contact" msgid="7529281274005689512">"Edytuj kontakt"</string>
+ <plurals name="merge_info">
+ <item quantity="one" msgid="148365587896371969">"nie scalono"</item>
+ <item quantity="other" msgid="425683718017380845">"scalono z <xliff:g id="COUNT">%0$d</xliff:g> źródeł"</item>
+ </plurals>
+ <string name="edit_delete_rawcontact" msgid="5054644778227879148">"Usuń"</string>
+ <string name="edit_error_saving" msgid="2153949392245240166">"Błąd podczas zapisywania"</string>
+ <string name="edit_structured_editor_button" msgid="3830078388142808106">"..."</string>
+ <string name="local_invisible_directory" msgid="6046691709127661065">"Inne"</string>
+ <!-- no translation found for aggregation_suggestion_title:one (7676930148670768645) -->
+ <!-- no translation found for aggregation_suggestion_title:other (3744098467672876675) -->
+ <!-- no translation found for aggregation_suggestion_joined_title (5342738708069602301) -->
+ <skip />
+ <!-- no translation found for aggregation_suggestion_join_button (7248325487963514260) -->
+ <skip />
+ <!-- no translation found for aggregation_suggestion_view_button (8232061564083962522) -->
+ <skip />
</resources>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index b18eaa7..1a9735e 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -31,6 +31,8 @@
<string name="viewContactTitle" msgid="7989394521836644384">"Detalhes de contacto"</string>
<string name="viewContactDesription" msgid="214186610887547860">"Visualizar contacto"</string>
<string name="editContactDescription" msgid="2947202828256214947">"Editar contacto"</string>
+ <string name="edit_email_caption" msgid="2401084399676154272">"Endereço de e-mail:"</string>
+ <string name="edit_email_type_caption" msgid="66331218163770698">"Tipo: "</string>
<string name="insertContactDescription" msgid="4709878105452681987">"Criar contacto"</string>
<string name="searchHint" msgid="8482945356247760701">"Pesquisar contactos"</string>
<string name="menu_search" msgid="9147752853603483719">"Pesquisar"</string>
@@ -123,13 +125,13 @@
</plurals>
<string name="listTotalAllContactsZero" msgid="6811347506748072822">"Sem contactos visíveis"</string>
<plurals name="listFoundAllContacts">
- <item quantity="one" msgid="2830107332033967280">"Foi encontrado 1 contacto"</item>
- <item quantity="other" msgid="7752927996850263152">"Foram encontrados <xliff:g id="COUNT">%d</xliff:g> contactos"</item>
+ <item quantity="one" msgid="5517063038754171134">"1 encontrado"</item>
+ <item quantity="other" msgid="3852668542926965042">"<xliff:g id="COUNT">%d</xliff:g> encontrado(s)"</item>
</plurals>
- <string name="listFoundAllContactsZero" msgid="5554368784319460828">"Contacto não encontrado"</string>
+ <string name="listFoundAllContactsZero" msgid="777952841930508289">"Não encontrado"</string>
<plurals name="searchFoundContacts">
- <item quantity="one" msgid="916648718690661777">"1 contacto"</item>
- <item quantity="other" msgid="5660384247071761844">"<xliff:g id="COUNT">%d</xliff:g> contactos"</item>
+ <item quantity="one" msgid="4826918429708286628">"1 encontrado"</item>
+ <item quantity="other" msgid="7988132539476575389">"<xliff:g id="COUNT">%d</xliff:g> encontrado(s)"</item>
</plurals>
<string name="contactsIconLabel" msgid="7666609097606552806">"Contactos"</string>
<string name="contactsFavoritesLabel" msgid="8417039765586853670">"Favoritos"</string>
@@ -159,10 +161,10 @@
<string name="simContacts_title" msgid="27341688347689769">"Contactos no cartão SIM"</string>
<string name="noContactsHelpTextWithSyncForCreateShortcut" msgid="801504710275614594">"Não existem contactos a apresentar. (Se acabou de adicionar uma conta, pode demorar alguns minutos a sincronizar os contactos.)"</string>
<string name="noContactsHelpTextForCreateShortcut" msgid="3081286388667108335">"Não existem contactos a apresentar."</string>
- <string name="noContactsHelpText" msgid="6788487368878712350">"Não tem contactos para apresentar."\n\n"Para adicionar contactos, prima "<font fgcolor="#ffffffff"><b>"Menu"</b></font>" e toque em:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Contas"</b></font>" para adicionar ou configurar uma conta com contactos que pode sincronizar no telefone"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Novo contacto"</b></font>" para criar um novo contacto de raiz"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importar/Exportar"</b></font>\n</li></string>
- <string name="noContactsHelpTextWithSync" msgid="3734101165712848179">"Não tem contactos para apresentar (se acabou de adicionar uma conta, pode demorar alguns minutos a sincronizar os contactos)."\n\n"Para adicionar contactos, prima "<font fgcolor="#ffffffff"><b>"Menu"</b></font>" e toque em:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Contas"</b></font>" para adicionar ou configurar uma conta com os contactos que pode sincronizar no telefone"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Opções de visualização"</b></font>" para alterar os contactos visíveis"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Novo contacto"</b></font>" para criar uma nova conta de raiz"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importar/Exportar"</b></font>\n</li></string>
- <string name="noContactsNoSimHelpText" msgid="6553845386917463292">"Não tem contactos para apresentar."\n\n"Para adicionar contactos, prima "<font fgcolor="#ffffffff"><b>"Menu"</b></font>" e toque em:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Contas"</b></font>" para adicionar ou configurar uma conta com contactos que pode sincronizar no telefone"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Novo contacto"</b></font>" para criar um novo contacto de raiz"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importar/Exportar"</b></font>\n</li></string>
- <string name="noContactsNoSimHelpTextWithSync" msgid="1122296298361373488">"Não tem contactos para apresentar (se acabou de adicionar uma conta, pode demorar alguns minutos a sincronizar os contactos)."\n\n"Para adicionar contactos, prima "<font fgcolor="#ffffffff"><b>"Menu"</b></font>" e toque em:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Contas"</b></font>" para adicionar ou configurar uma conta com os contactos que pode sincronizar no telefone"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Opções de visualização"</b></font>" para alterar os contactos visíveis"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Novo contacto"</b></font>" para criar uma nova conta de raiz"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importar/Exportar"</b></font>\n</li></string>
+ <string name="noContactsHelpText" msgid="7633826236417884130">"Não tem contactos para apresentar."\n\n"Para adicionar contactos, prima "<font fgcolor="#ffffffff"><b>"Menu"</b></font>" e toque em:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Contas"</b></font>" para adicionar ou configurar uma conta com contactos que pode sincronizar com o telemóvel"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Novo contacto"</b></font>" para criar um novo contacto de raiz"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importar/Exportar"</b></font>" para importar contactos do SIM ou do cartão SD"\n</li></string>
+ <string name="noContactsHelpTextWithSync" msgid="3017521127042216243">"Não tem contactos para apresentar (se acabou de adicionar uma conta, a sincronização dos contactos pode demorar alguns minutos)."\n\n"Para adicionar contactos, prima "<font fgcolor="#ffffffff"><b>"Menu"</b></font>" e toque em:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Contas"</b></font>" para adicionar ou configurar uma conta com contactos que pode sincronizar com o telemóvel"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Opções de visualização"</b></font>" para alterar os contactos visíveis"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Novo contacto"</b></font>" para criar um novo contacto de raiz"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importar/Exportar"</b></font>" para importar contactos do SIM ou do cartão SD"\n</li></string>
+ <string name="noContactsNoSimHelpText" msgid="467658807711582876">"Não tem contactos para apresentar."\n\n"Para adicionar contactos, prima "<font fgcolor="#ffffffff"><b>"Menu"</b></font>" e toque em:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Contas"</b></font>" para adicionar ou configurar uma conta com contactos que pode sincronizar com o telemóvel"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Novo contacto"</b></font>" para criar um novo contacto de raiz"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importar/Exportar"</b></font>" para importar contactos do cartão SD"\n</li></string>
+ <string name="noContactsNoSimHelpTextWithSync" msgid="9040060730467973050">"Não tem contactos para apresentar (se acabou de adicionar uma conta, a sincronização dos contactos pode demorar alguns minutos)."\n\n"Para adicionar contactos, prima "<font fgcolor="#ffffffff"><b>"Menu"</b></font>" e toque em:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Contas"</b></font>" para adicionar ou configurar uma conta com contactos que pode sincronizar com o telemóvel"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Opções de visualização"</b></font>" para alterar os contactos visíveis"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Novo contacto"</b></font>" para criar um novo contacto de raiz"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importar/Exportar"</b></font>" para importar contactos do cartão SD"\n</li></string>
<string name="noFavoritesHelpText" msgid="3744655776704833277">"Não tem favoritos."\n\n"Para adicionar um contacto à sua lista de favoritos:"\n\n" "<li>"Toque no separador "<b>"Contactos"</b>" "\n</li>" "\n<li>"Toque no contacto que pretende adicionar aos favoritos"\n</li>" "\n<li>"Toque na estrela junto ao nome do contacto"\n</li></string>
<string name="liveFolder_all_label" msgid="5961411940473276616">"Todos os contactos"</string>
<string name="liveFolder_favorites_label" msgid="2674341514070517105">"Marcado com estrela"</string>
@@ -179,7 +181,7 @@
<string name="callBack" msgid="5498224409038809224">"Chamada de retorno"</string>
<string name="callAgain" msgid="3197312117049874778">"Ligar novamente"</string>
<string name="returnCall" msgid="8171961914203617813">"Devolver chamada"</string>
- <string name="callDetailsDurationFormat" msgid="8157706382818184268">"<xliff:g id="MINUTES">%1$s</xliff:g> min <xliff:g id="SECONDS">%2$s</xliff:g> seg"</string>
+ <string name="callDetailsDurationFormat" msgid="8157706382818184268">"<xliff:g id="MINUTES">%s</xliff:g> min <xliff:g id="SECONDS">%s</xliff:g> seg"</string>
<string name="favoritesFrquentSeparator" msgid="8107518433381283736">"Contactos frequentes"</string>
<string name="add_contact_dlg_title" msgid="2896685845822146494">"Adicionar contacto"</string>
<string name="add_contact_dlg_message_fmt" msgid="7986472669444326576">"Adicionar \"<xliff:g id="EMAIL">%s</xliff:g>\" aos contactos?"</string>
@@ -217,18 +219,24 @@
<string name="scanning_sdcard_failed_title" msgid="3506782007953167180">"Falha na análise do cartão SD"</string>
<string name="scanning_sdcard_failed_message" msgid="3761992500690182922">"Falha na análise do cartão SD (Motivo: \"<xliff:g id="FAIL_REASON">%s</xliff:g>\")"</string>
<string name="fail_reason_io_error" msgid="5922864781066136340">"Erro de E/S"</string>
+ <string name="fail_reason_low_memory_during_import" msgid="7514918659342886381">"A memória não é suficiente (o ficheiro pode ser demasiado grande)"</string>
<string name="fail_reason_vcard_parse_error" msgid="1201233722762680214">"Falha na análise do vCard por motivo inesperado"</string>
<string name="fail_reason_vcard_not_supported_error" msgid="655208100451286027">"Falha na análise do vCard embora o formato pareça válido, uma vez que não é suportado pela implementação actual"</string>
<string name="fail_reason_no_vcard_file" msgid="6376516175882881595">"Não foi encontrado nenhum ficheiro vCard no cartão SD"</string>
+ <string name="fail_reason_failed_to_collect_vcard_meta_info" msgid="4154492282316067754">"A recolha de meta informações de ficheiro(s) vCard específicado(s) falhou."</string>
<string name="fail_reason_failed_to_read_files" msgid="3659521123567134029">"A importação de um ou vários ficheiros falhou (%s)."</string>
<string name="fail_reason_unknown" msgid="999034019513096768">"Erro desconhecido"</string>
<string name="select_vcard_title" msgid="3968948173786172468">"Seleccionar ficheiro vCard"</string>
- <string name="progress_shower_message" msgid="5636525578293752526">"<xliff:g id="ACTION">%1$s</xliff:g>"\n"<xliff:g id="FILENAME">%2$s</xliff:g>"</string>
- <string name="reading_vcard_title" msgid="4723433501579653199">"A ler vCard"</string>
- <string name="reading_vcard_message" msgid="6381368920030748743">"A ler ficheiro(s) vCard"</string>
- <string name="reading_vcard_failed_title" msgid="4923008144735294994">"Falha na leitura de dados do vCard"</string>
- <string name="reading_vcard_contacts" msgid="3066834102042012868">"<xliff:g id="CURRENT_NUMBER">%1$s</xliff:g> de <xliff:g id="TOTAL_NUMBER">%2$s</xliff:g> contactos"</string>
- <string name="reading_vcard_files" msgid="34180143726972661">"<xliff:g id="CURRENT_NUMBER">%1$s</xliff:g> de <xliff:g id="TOTAL_NUMBER">%2$s</xliff:g> ficheiros"</string>
+ <string name="caching_vcard_title" msgid="5009556022082659780">"A colocar vCard(s) em cache no armazenamento temporário local"</string>
+ <string name="caching_vcard_message" msgid="2380844718093378900">"O importador está a colocar vCard(s) em cache no armazenamento temporário local. A importação efectiva começará brevemente."</string>
+ <string name="progress_notifier_message" msgid="8412196553907067659">"<xliff:g id="CURRENT_NUMBER">%s</xliff:g>/<xliff:g id="TOTAL_NUMBER">%s</xliff:g>: <xliff:g id="FILENAME">%s</xliff:g>"</string>
+ <string name="reading_vcard_title" msgid="3324988724123717025">"A ler vCard(s)"</string>
+ <string name="reading_vcard_failed_title" msgid="2162610359561887043">"A leitura dos dados vCard falhou"</string>
+ <string name="reading_vcard_canceled_title" msgid="1770608329958463131">"A leitura de dados vCard foi cancelada"</string>
+ <string name="importing_vcard_finished_title" msgid="3213257229785702182">"A importação do vCard terminou"</string>
+ <string name="vcard_importer_start_message" msgid="5520549352987753320">"Importador vCard iniciado."</string>
+ <string name="vcard_importer_will_start_message" msgid="2055145402481822160">"O importador vCard importará o vCard dentro de algum tempo."</string>
+ <string name="percentage" msgid="34897865327092209">"%s%%"</string>
<string name="confirm_export_title" msgid="7648747763127442983">"Confirmar exportação"</string>
<string name="confirm_export_message" msgid="3875683519257829750">"Tem a certeza de que pretende exportar a lista de contactos para \"<xliff:g id="VCARD_FILENAME">%s</xliff:g>\"?"</string>
<string name="exporting_contact_failed_title" msgid="585823094820602526">"Falha ao exportar dados de contacto"</string>
@@ -236,6 +244,8 @@
<string name="fail_reason_no_exportable_contact" msgid="4919714086648344495">"Não existe um contacto exportável"</string>
<string name="fail_reason_too_many_vcard" msgid="7084146295639672658">"Demasiados ficheiros vCard no cartão SD"</string>
<string name="fail_reason_too_long_filename" msgid="1915716071321839166">"Nome de ficheiro demasiado longo (\"<xliff:g id="FILENAME">%s</xliff:g>\")"</string>
+ <string name="vcard_exporter_start_message" msgid="6497218576519292369">"Exportador vCard iniciado."</string>
+ <string name="exporting_vcard_finished_title" msgid="1268425436375550862">"A exportação do vCard terminou"</string>
<string name="exporting_contact_list_title" msgid="9072240631534457415">"Exportar dados do contacto"</string>
<string name="exporting_contact_list_message" msgid="5640326540405486055">"Exportar dados do contacto para \"<xliff:g id="FILE_NAME">%s</xliff:g>\""</string>
<string name="fail_reason_could_not_initialize_exporter" msgid="4943708332700987376">"Não foi possível iniciar o exportador: \"<xliff:g id="EXACT_REASON">%s</xliff:g>\""</string>
@@ -243,8 +253,8 @@
<string name="composer_failed_to_get_database_infomation" msgid="3723109558155169053">"Falha na obtenção de informações da base de dados"</string>
<string name="composer_has_no_exportable_contact" msgid="754734132189369094">"Não existe nenhum contacto exportável. Se tiver realmente contactos no telefone, a sua exportação para fora do telefone por parte de alguns fornecedores de dados pode não ser autorizada."</string>
<string name="composer_not_initialized" msgid="8041534450748388843">"O compositor vCard não foi inicializado correctamente"</string>
- <string name="fail_reason_could_not_open_file" msgid="4013520943128739511">"Não foi possível abrir \"<xliff:g id="FILE_NAME">%1$s</xliff:g>\": <xliff:g id="EXACT_REASON">%2$s</xliff:g>"</string>
- <string name="exporting_contact_list_progress" msgid="560522409559101193">"<xliff:g id="CURRENT_NUMBER">%1$s</xliff:g> de <xliff:g id="TOTAL_NUMBER">%2$s</xliff:g> contactos"</string>
+ <string name="fail_reason_could_not_open_file" msgid="4013520943128739511">"Não foi possível abrir \"<xliff:g id="FILE_NAME">%s</xliff:g>\": <xliff:g id="EXACT_REASON">%s</xliff:g>"</string>
+ <string name="exporting_contact_list_progress" msgid="560522409559101193">"<xliff:g id="CURRENT_NUMBER">%s</xliff:g> de <xliff:g id="TOTAL_NUMBER">%s</xliff:g> contactos"</string>
<string name="search_settings_description" msgid="2675223022992445813">"Nomes dos contactos"</string>
<string name="add_2sec_pause" msgid="9214012315201040129">"Adicionar pausa de 2 seg."</string>
<string name="add_wait" msgid="3360818652790319634">"Adicionar espera"</string>
@@ -254,6 +264,7 @@
<string name="quickcontact_missing_app" msgid="4600366393134289038">"Não foram encontradas aplicações para executar esta acção"</string>
<string name="quickcontact_remember_choice" msgid="5964536411579749424">"Memorizar esta escolha"</string>
<string name="quickcontact_missing_name" msgid="5590266114306996632">"Desconhecido"</string>
+ <string name="quickcontact_no_data" msgid="2098000859125253675">"Sem dados"</string>
<string name="menu_accounts" msgid="8499114602017077970">"Contas"</string>
<string name="menu_import_export" msgid="3765725645491577190">"Importar/Exportar"</string>
<string name="dialog_import_export" msgid="4771877268244096596">"Importar/Exportar contactos"</string>
@@ -359,6 +370,7 @@
<string name="name_phonetic_family" msgid="462095502140180305">"Fonética do segundo apelido"</string>
<string name="account_type_format" msgid="718948015590343010">"Contacto de <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
<string name="from_account_format" msgid="687567483928582084">"de <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
+ <string name="account_type_and_name" msgid="2876220035411512934">"contacto <xliff:g id="SOURCE_0">%1$s</xliff:g> de <xliff:g id="SOURCE_1">%2$s</xliff:g>"</string>
<string name="use_photo_as_primary" msgid="8807110122951157246">"Utilizar esta fotografia"</string>
<string name="contact_read_only" msgid="1203216914575723978">"Não é possível editar as informações de contacto do <xliff:g id="SOURCE">%1$s</xliff:g> neste dispositivo."</string>
<string name="no_contact_details" msgid="6754415338321837001">"Não há informações adicionais para este contacto"</string>
@@ -374,9 +386,42 @@
<string name="pick_photo" msgid="448886509158039462">"Seleccionar fotografia da Galeria"</string>
<string name="locale_change_in_progress" msgid="1124266507671178413">"A lista de contactos está a ser actualizada para reflectir a alteração do idioma."\n\n"Aguarde..."</string>
<string name="upgrade_in_progress" msgid="7530893673211750223">"A lista de contactos está a ser actualizada."\n\n"Aguarde..."</string>
- <string name="upgrade_out_of_memory" msgid="492151063059824962">"Os contactos estão no processo de serem actualizados. "\n\n"O processo de actualização requer aproximadamente <xliff:g id="SIZE_IN_MEGABYTES">%d</xliff:g> MB de armazenamento de telefone interno."\n\n"Escolha uma das seguintes opções:"</string>
+ <string name="upgrade_out_of_memory" msgid="6713914687111678777">"Os contactos estão em processo de actualização. "\n\n"O processo de actualização requer aproximadamente <xliff:g id="SIZE_IN_MEGABYTES">%d</xliff:g> MB de armazenamento interno no telemóvel."\n\n"Escolha uma das seguintes opções:"</string>
<string name="upgrade_out_of_memory_uninstall" msgid="1721798828992091432">"Desinstalar algumas aplicações"</string>
<string name="upgrade_out_of_memory_retry" msgid="8431289830472724609">"Repetir actualização"</string>
<string name="search_results_for" msgid="8705490885073188513">"Resultados de pesquisa para: <xliff:g id="QUERY">%s</xliff:g>"</string>
<string name="search_results_searching" msgid="7755623475227227314">"A pesquisar..."</string>
+ <string name="adding_recipients" msgid="5123647646892106631">"A carregar …"</string>
+ <string name="menu_display_selected" msgid="6470001164297969034">"Mostrar seleccionados"</string>
+ <string name="menu_display_all" msgid="8887488642609786198">"Mostrar tudo"</string>
+ <string name="menu_select_all" msgid="621719255150713545">"Seleccionar tudo"</string>
+ <string name="menu_select_none" msgid="7093222469852132345">"Desmarcar tudo"</string>
+ <plurals name="multiple_picker_title">
+ <item quantity="one" msgid="4761009734586319101">"1 destinatário seleccionado"</item>
+ <item quantity="other" msgid="4608837420986126229">"<xliff:g id="COUNT">%d</xliff:g> destinatários seleccionados"</item>
+ </plurals>
+ <string name="unknown_contacts_separator" msgid="2490675836664397214">"Contactos desconhecidos"</string>
+ <string name="no_contacts_selected" msgid="5877803471037324613">"Nenhum contacto seleccionado."</string>
+ <string name="add_field" msgid="5257149039253569615">"Adicionar informações"</string>
+ <string name="contact_status_update_attribution" msgid="752179367353018597">"através do <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
+ <string name="contact_status_update_attribution_with_date" msgid="7358045508107825068">"<xliff:g id="DATE">%1$s</xliff:g> através do <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
+ <string name="description_edit" msgid="1601490950771217014">"editar"</string>
+ <string name="description_star" msgid="2605854427360036550">"favorito"</string>
+ <string name="edit_contact" msgid="7529281274005689512">"Editar contacto"</string>
+ <plurals name="merge_info">
+ <item quantity="one" msgid="148365587896371969">"não intercalado(s)"</item>
+ <item quantity="other" msgid="425683718017380845">"intercalado a partir de <xliff:g id="COUNT">%0$d</xliff:g> origens"</item>
+ </plurals>
+ <string name="edit_delete_rawcontact" msgid="5054644778227879148">"Eliminar"</string>
+ <string name="edit_error_saving" msgid="2153949392245240166">"Erro ao guardar"</string>
+ <string name="edit_structured_editor_button" msgid="3830078388142808106">"..."</string>
+ <string name="local_invisible_directory" msgid="6046691709127661065">"Outro"</string>
+ <!-- no translation found for aggregation_suggestion_title:one (7676930148670768645) -->
+ <!-- no translation found for aggregation_suggestion_title:other (3744098467672876675) -->
+ <!-- no translation found for aggregation_suggestion_joined_title (5342738708069602301) -->
+ <skip />
+ <!-- no translation found for aggregation_suggestion_join_button (7248325487963514260) -->
+ <skip />
+ <!-- no translation found for aggregation_suggestion_view_button (8232061564083962522) -->
+ <skip />
</resources>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index b5c9352..7f68d1b 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -31,6 +31,8 @@
<string name="viewContactTitle" msgid="7989394521836644384">"Detalhes do contato"</string>
<string name="viewContactDesription" msgid="214186610887547860">"Ver contato"</string>
<string name="editContactDescription" msgid="2947202828256214947">"Editar contato"</string>
+ <string name="edit_email_caption" msgid="2401084399676154272">"Endereço de e-mail:"</string>
+ <string name="edit_email_type_caption" msgid="66331218163770698">"Tipo: "</string>
<string name="insertContactDescription" msgid="4709878105452681987">"Criar contato"</string>
<string name="searchHint" msgid="8482945356247760701">"Pesquisar contatos"</string>
<string name="menu_search" msgid="9147752853603483719">"Pesquisa"</string>
@@ -123,13 +125,13 @@
</plurals>
<string name="listTotalAllContactsZero" msgid="6811347506748072822">"Não há contatos visíveis"</string>
<plurals name="listFoundAllContacts">
- <item quantity="one" msgid="2830107332033967280">"1 contato encontrado"</item>
- <item quantity="other" msgid="7752927996850263152">"<xliff:g id="COUNT">%d</xliff:g> contatos encontrados"</item>
+ <item quantity="one" msgid="5517063038754171134">"Um encontrado"</item>
+ <item quantity="other" msgid="3852668542926965042">"<xliff:g id="COUNT">%d</xliff:g> encontrados"</item>
</plurals>
- <string name="listFoundAllContactsZero" msgid="5554368784319460828">"Contato não encontrado"</string>
+ <string name="listFoundAllContactsZero" msgid="777952841930508289">"Não encontrado"</string>
<plurals name="searchFoundContacts">
- <item quantity="one" msgid="916648718690661777">"1 contato"</item>
- <item quantity="other" msgid="5660384247071761844">"<xliff:g id="COUNT">%d</xliff:g> contatos"</item>
+ <item quantity="one" msgid="4826918429708286628">"Um encontrado"</item>
+ <item quantity="other" msgid="7988132539476575389">"<xliff:g id="COUNT">%d</xliff:g> encontrados"</item>
</plurals>
<string name="contactsIconLabel" msgid="7666609097606552806">"Contatos"</string>
<string name="contactsFavoritesLabel" msgid="8417039765586853670">"Favoritos"</string>
@@ -159,10 +161,10 @@
<string name="simContacts_title" msgid="27341688347689769">"Contatos do cartão SIM"</string>
<string name="noContactsHelpTextWithSyncForCreateShortcut" msgid="801504710275614594">"Você não tem contatos para exibir. Se você acabou de adicionar uma conta, pode levar alguns minutos para sincronizar os contatos."</string>
<string name="noContactsHelpTextForCreateShortcut" msgid="3081286388667108335">"Você não tem contatos para exibir."</string>
- <string name="noContactsHelpText" msgid="6788487368878712350">"Você não tem nenhum contato para exibir."\n\n"Para adicionar contatos, pressione "<font fgcolor="#ffffffff"><b>"Menu"</b></font>" e toque em:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Contas"</b></font>" para adicionar ou configurar uma conta com contatos que possam ser sincronizados com o telefone"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Novo contato"</b></font>" para criar um novo contato do início"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importar/Exportar"</b></font>\n</li></string>
- <string name="noContactsHelpTextWithSync" msgid="3734101165712848179">"Você não tem contatos para exibir (se acabou de adicionar uma conta, pode levar alguns minutos para sincronizar os contatos.)"\n\n"Para adicionar contatos, pressione"<font fgcolor="#ffffffff"><b>"Menu"</b></font>" e toque em:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Contas"</b></font>" para adicionar ou configurar uma conta com contatos que possam ser sincronizados com o telefone"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Opções de exibição"</b></font>" para alterar quais contatos ficam visíveis"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Novo contato"</b></font>" para criar um novo contato do início"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importar/Exportar"</b></font>\n</li></string>
- <string name="noContactsNoSimHelpText" msgid="6553845386917463292">"Você não tem nenhum contato para exibir."\n\n"Para adicionar contatos, pressione "<font fgcolor="#ffffffff"><b>"Menu"</b></font>" e toque em:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Contas"</b></font>" para adicionar ou configurar uma conta com contatos que possam ser sincronizados com o telefone"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Novo contato"</b></font>" para criar um novo contato do início"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importar/Exportar"</b></font>\n</li></string>
- <string name="noContactsNoSimHelpTextWithSync" msgid="1122296298361373488">"Você não tem contatos para exibir (se acabou de adicionar uma conta, pode levar alguns minutos para sincronizar os contatos.)"\n\n"Para adicionar contatos, pressione"<font fgcolor="#ffffffff"><b>"Menu"</b></font>" e toque em:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Contas"</b></font>" para adicionar ou configurar uma conta com contatos que possam ser sincronizados com o telefone"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Opções de exibição"</b></font>" para alterar quais contatos ficam visíveis"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Novo contato"</b></font>" para criar um novo contato do início"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importar/Exportar"</b></font>\n</li></string>
+ <string name="noContactsHelpText" msgid="7633826236417884130">"Você não tem contatos para exibir."\n\n"Para adicionar contatos, pressione "<font fgcolor="#ffffffff"><b>"Menu"</b></font>" e toque em:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Contas"</b></font>" para adicionar ou configurar uma conta com contatos que possam ser sincronizados com o telefone"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Novo contato"</b></font>" para criar um novo contato do início"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importar/Exportar"</b></font>" para importar contatos do chip ou cartão SD"\n</li></string>
+ <string name="noContactsHelpTextWithSync" msgid="3017521127042216243">"Você não tem contatos para exibir (caso tenha acabado de adicionar uma conta, pode levar alguns minutos para sincronizar os contatos)."\n\n"Para adicionar contatos, pressione "<font fgcolor="#ffffffff"><b>"Menu"</b></font>" e toque em:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Contas"</b></font>" para adicionar ou configurar uma conta com contatos que possam ser sincronizados com o telefone"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Opções de exibição"</b></font>" para alterar quais contatos ficam visíveis"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Novo contato"</b></font>" para criar um novo contato do início"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importar/Exportar"</b></font>" para importar contatos do chip ou cartão SD"\n</li></string>
+ <string name="noContactsNoSimHelpText" msgid="467658807711582876">"Você não tem contatos para exibir."\n\n"Para adicionar contatos, pressione "<font fgcolor="#ffffffff"><b>"Menu"</b></font>" e toque em:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Contas"</b></font>" para adicionar ou configurar uma conta com contatos que possam ser sincronizados com o telefone"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Novo contato"</b></font>" para criar um novo contato do início"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importar/Exportar"</b></font>" para importar contatos do chip ou cartão SD"\n</li></string>
+ <string name="noContactsNoSimHelpTextWithSync" msgid="9040060730467973050">"Você não tem contatos para exibir (se acabou de adicionar uma conta, pode levar alguns minutos para sincronizar os contatos)."\n\n"Para adicionar contatos, pressione "<font fgcolor="#ffffffff"><b>"Menu"</b></font>" e toque em:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Contas"</b></font>" para adicionar ou configurar uma conta com contatos que possam ser sincronizados com o telefone"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Opções de exibição"</b></font>" para alterar quais contatos ficam visíveis"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Novo contato"</b></font>" para criar um novo contato do início"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importar/Exportar"</b></font>" para importar contatos do chip ou cartão SD"\n</li></string>
<string name="noFavoritesHelpText" msgid="3744655776704833277">"Você não tem nenhum favorito."\n\n"Para adicionar um contato à sua lista de favoritos:"\n\n" "<li>"Toque na guia "<b>"Contatos"</b>\n</li>" "\n<li>"Toque no contato que deseja adicionar aos seus favoritos"\n</li>" "\n<li>"Toque na estrela ao lado do nome do contato"\n</li></string>
<string name="liveFolder_all_label" msgid="5961411940473276616">"Todos os contatos"</string>
<string name="liveFolder_favorites_label" msgid="2674341514070517105">"Com estrela"</string>
@@ -179,7 +181,7 @@
<string name="callBack" msgid="5498224409038809224">"Retornar chamada"</string>
<string name="callAgain" msgid="3197312117049874778">"Chamar novamente"</string>
<string name="returnCall" msgid="8171961914203617813">"Retornar chamada"</string>
- <string name="callDetailsDurationFormat" msgid="8157706382818184268">"<xliff:g id="MINUTES">%1$s</xliff:g> min. <xliff:g id="SECONDS">%2$s</xliff:g> s"</string>
+ <string name="callDetailsDurationFormat" msgid="8157706382818184268">"<xliff:g id="MINUTES">%s</xliff:g> min. <xliff:g id="SECONDS">%s</xliff:g> s"</string>
<string name="favoritesFrquentSeparator" msgid="8107518433381283736">"Chamados frequentemente"</string>
<string name="add_contact_dlg_title" msgid="2896685845822146494">"Adicionar contato"</string>
<string name="add_contact_dlg_message_fmt" msgid="7986472669444326576">"Adicionar \"<xliff:g id="EMAIL">%s</xliff:g>\" aos contatos?"</string>
@@ -217,18 +219,24 @@
<string name="scanning_sdcard_failed_title" msgid="3506782007953167180">"Falha na verificação do cartão SD"</string>
<string name="scanning_sdcard_failed_message" msgid="3761992500690182922">"Falha na verificação do cartão SD (Motivo: \"<xliff:g id="FAIL_REASON">%s</xliff:g>\")"</string>
<string name="fail_reason_io_error" msgid="5922864781066136340">"Erro E/S"</string>
+ <string name="fail_reason_low_memory_during_import" msgid="7514918659342886381">"Memória insuficiente (o arquivo talvez seja muito grande)"</string>
<string name="fail_reason_vcard_parse_error" msgid="1201233722762680214">"Falha ao analisar o vCard por razão inesperada"</string>
<string name="fail_reason_vcard_not_supported_error" msgid="655208100451286027">"Falha ao analisar o vCard. Embora o formato pareça ser válido, a implementação atual não oferece suporte a ele."</string>
<string name="fail_reason_no_vcard_file" msgid="6376516175882881595">"Nenhum arquivo de vCard encontrado no cartão SD"</string>
+ <string name="fail_reason_failed_to_collect_vcard_meta_info" msgid="4154492282316067754">"Falha ao coletar metainformações dos arquivos de vCard fornecidos."</string>
<string name="fail_reason_failed_to_read_files" msgid="3659521123567134029">"Falha na importação de um ou mais arquivos (%s)."</string>
<string name="fail_reason_unknown" msgid="999034019513096768">"Erro desconhecido"</string>
<string name="select_vcard_title" msgid="3968948173786172468">"Selecionar arquivo do vCard"</string>
- <string name="progress_shower_message" msgid="5636525578293752526">"<xliff:g id="ACTION">%1$s</xliff:g>"\n"<xliff:g id="FILENAME">%2$s</xliff:g>"</string>
- <string name="reading_vcard_title" msgid="4723433501579653199">"Lendo vCard"</string>
- <string name="reading_vcard_message" msgid="6381368920030748743">"Lendo os arquivos do vCard"</string>
- <string name="reading_vcard_failed_title" msgid="4923008144735294994">"A leitura dos dados do vCard falhou"</string>
- <string name="reading_vcard_contacts" msgid="3066834102042012868">"<xliff:g id="CURRENT_NUMBER">%1$s</xliff:g> de <xliff:g id="TOTAL_NUMBER">%2$s</xliff:g> contatos"</string>
- <string name="reading_vcard_files" msgid="34180143726972661">"<xliff:g id="CURRENT_NUMBER">%1$s</xliff:g> de <xliff:g id="TOTAL_NUMBER">%2$s</xliff:g> arquivos"</string>
+ <string name="caching_vcard_title" msgid="5009556022082659780">"Armazenando vCard(s) em cache no armazenamento temporário local"</string>
+ <string name="caching_vcard_message" msgid="2380844718093378900">"O importador está armazenando vCard(s) em cache no armazenamento temporário local. A importação real começará em breve."</string>
+ <string name="progress_notifier_message" msgid="8412196553907067659">"<xliff:g id="CURRENT_NUMBER">%s</xliff:g>/<xliff:g id="TOTAL_NUMBER">%s</xliff:g>: <xliff:g id="FILENAME">%s</xliff:g>"</string>
+ <string name="reading_vcard_title" msgid="3324988724123717025">"Lendo vCard(s)"</string>
+ <string name="reading_vcard_failed_title" msgid="2162610359561887043">"Falha ao ler dados de vCard"</string>
+ <string name="reading_vcard_canceled_title" msgid="1770608329958463131">"A leitura dos dados do vCard foi cancelada"</string>
+ <string name="importing_vcard_finished_title" msgid="3213257229785702182">"Importação de vCard concluída"</string>
+ <string name="vcard_importer_start_message" msgid="5520549352987753320">"Importador de vCard iniciado."</string>
+ <string name="vcard_importer_will_start_message" msgid="2055145402481822160">"O importador de vCard importará o vCard depois de alguns instantes."</string>
+ <string name="percentage" msgid="34897865327092209">"%s%%"</string>
<string name="confirm_export_title" msgid="7648747763127442983">"Confirmar exportação"</string>
<string name="confirm_export_message" msgid="3875683519257829750">"Tem certeza de que deseja exportar a sua lista de contatos para \"<xliff:g id="VCARD_FILENAME">%s</xliff:g>\"?"</string>
<string name="exporting_contact_failed_title" msgid="585823094820602526">"Falha ao exportar dados do contato"</string>
@@ -236,6 +244,8 @@
<string name="fail_reason_no_exportable_contact" msgid="4919714086648344495">"Não há contatos exportáveis"</string>
<string name="fail_reason_too_many_vcard" msgid="7084146295639672658">"Muitos arquivos do vCard no cartão SD"</string>
<string name="fail_reason_too_long_filename" msgid="1915716071321839166">"O nome de arquivo exigido é muito longo (\"<xliff:g id="FILENAME">%s</xliff:g>\")"</string>
+ <string name="vcard_exporter_start_message" msgid="6497218576519292369">"Exportador de vCard iniciado."</string>
+ <string name="exporting_vcard_finished_title" msgid="1268425436375550862">"Exportação de vCard concluída"</string>
<string name="exporting_contact_list_title" msgid="9072240631534457415">"Exportando dados do contato"</string>
<string name="exporting_contact_list_message" msgid="5640326540405486055">"Exportando dados do contato para \"<xliff:g id="FILE_NAME">%s</xliff:g>\""</string>
<string name="fail_reason_could_not_initialize_exporter" msgid="4943708332700987376">"Não foi possível iniciar o exportador: \"<xliff:g id="EXACT_REASON">%s</xliff:g>\""</string>
@@ -243,8 +253,8 @@
<string name="composer_failed_to_get_database_infomation" msgid="3723109558155169053">"Falha ao acessar informações do banco de dados"</string>
<string name="composer_has_no_exportable_contact" msgid="754734132189369094">"Não há contatos exportáveis. Se você realmente tiver contatos no seu telefone, talvez a exportação não seja permitida por algum provedor de dados."</string>
<string name="composer_not_initialized" msgid="8041534450748388843">"O vCard não foi iniciado corretamente"</string>
- <string name="fail_reason_could_not_open_file" msgid="4013520943128739511">"Não foi possível abrir \"<xliff:g id="FILE_NAME">%1$s</xliff:g>\": <xliff:g id="EXACT_REASON">%2$s</xliff:g>"</string>
- <string name="exporting_contact_list_progress" msgid="560522409559101193">"<xliff:g id="CURRENT_NUMBER">%1$s</xliff:g> de <xliff:g id="TOTAL_NUMBER">%2$s</xliff:g> contatos"</string>
+ <string name="fail_reason_could_not_open_file" msgid="4013520943128739511">"Não foi possível abrir \"<xliff:g id="FILE_NAME">%s</xliff:g>\": <xliff:g id="EXACT_REASON">%s</xliff:g>"</string>
+ <string name="exporting_contact_list_progress" msgid="560522409559101193">"<xliff:g id="CURRENT_NUMBER">%s</xliff:g> de <xliff:g id="TOTAL_NUMBER">%s</xliff:g> contatos"</string>
<string name="search_settings_description" msgid="2675223022992445813">"Nomes dos seus contatos"</string>
<string name="add_2sec_pause" msgid="9214012315201040129">"Adicionar pausa de 2 segundos"</string>
<string name="add_wait" msgid="3360818652790319634">"Adicionar espera"</string>
@@ -254,6 +264,7 @@
<string name="quickcontact_missing_app" msgid="4600366393134289038">"Nenhum aplicativo foi encontrado para executar esta ação."</string>
<string name="quickcontact_remember_choice" msgid="5964536411579749424">"Lembrar desta escolha"</string>
<string name="quickcontact_missing_name" msgid="5590266114306996632">"Desconhecido"</string>
+ <string name="quickcontact_no_data" msgid="2098000859125253675">"Nenhum dado"</string>
<string name="menu_accounts" msgid="8499114602017077970">"Contas"</string>
<string name="menu_import_export" msgid="3765725645491577190">"Importar/Exportar"</string>
<string name="dialog_import_export" msgid="4771877268244096596">"Importar/Exportar contatos"</string>
@@ -359,6 +370,7 @@
<string name="name_phonetic_family" msgid="462095502140180305">"Sobrenome fonético"</string>
<string name="account_type_format" msgid="718948015590343010">"Contato de <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
<string name="from_account_format" msgid="687567483928582084">"de <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
+ <string name="account_type_and_name" msgid="2876220035411512934">"contato do <xliff:g id="SOURCE_0">%1$s</xliff:g> de <xliff:g id="SOURCE_1">%2$s</xliff:g>"</string>
<string name="use_photo_as_primary" msgid="8807110122951157246">"Usar esta foto"</string>
<string name="contact_read_only" msgid="1203216914575723978">"As informações de contato de <xliff:g id="SOURCE">%1$s</xliff:g> não podem ser editadas neste aparelho."</string>
<string name="no_contact_details" msgid="6754415338321837001">"Não há informações adicionais para este contato"</string>
@@ -374,9 +386,42 @@
<string name="pick_photo" msgid="448886509158039462">"Selecionar foto da Galeria"</string>
<string name="locale_change_in_progress" msgid="1124266507671178413">"A lista de contatos está sendo atualizada para refletir a alteração do idioma."\n\n"Aguarde..."</string>
<string name="upgrade_in_progress" msgid="7530893673211750223">"A lista de contatos está sendo atualizada."\n\n"Aguarde..."</string>
- <string name="upgrade_out_of_memory" msgid="492151063059824962">"Os contatos estão sendo atualizados no momento. "\n\n"O processo de atualização requer aproximadamente <xliff:g id="SIZE_IN_MEGABYTES">%d</xliff:g> MB de armazenamento interno do telefone."\n\n"Escolha uma das seguintes opções:"</string>
+ <string name="upgrade_out_of_memory" msgid="6713914687111678777">"Os contatos estão sendo atualizados no momento. "\n\n"O processo de atualização requer aproximadamente <xliff:g id="SIZE_IN_MEGABYTES">%d</xliff:g> MB de armazenamento interno do telefone."\n\n"Escolha uma das seguintes opções:"</string>
<string name="upgrade_out_of_memory_uninstall" msgid="1721798828992091432">"Desinstalar alguns aplicativos"</string>
<string name="upgrade_out_of_memory_retry" msgid="8431289830472724609">"Tentar atualizar novamente"</string>
<string name="search_results_for" msgid="8705490885073188513">"Resultados da pesquisa para: <xliff:g id="QUERY">%s</xliff:g>"</string>
<string name="search_results_searching" msgid="7755623475227227314">"Pesquisando..."</string>
+ <string name="adding_recipients" msgid="5123647646892106631">"Carregando…"</string>
+ <string name="menu_display_selected" msgid="6470001164297969034">"Mostrar selecionados"</string>
+ <string name="menu_display_all" msgid="8887488642609786198">"Mostrar todos"</string>
+ <string name="menu_select_all" msgid="621719255150713545">"Selecionar todos"</string>
+ <string name="menu_select_none" msgid="7093222469852132345">"Desmarcar todos"</string>
+ <plurals name="multiple_picker_title">
+ <item quantity="one" msgid="4761009734586319101">"1 destinatário selecionado"</item>
+ <item quantity="other" msgid="4608837420986126229">"<xliff:g id="COUNT">%d</xliff:g> destinatários selecionados"</item>
+ </plurals>
+ <string name="unknown_contacts_separator" msgid="2490675836664397214">"Contatos selecionados"</string>
+ <string name="no_contacts_selected" msgid="5877803471037324613">"Nenhum contato selecionado."</string>
+ <string name="add_field" msgid="5257149039253569615">"Adicionar informações"</string>
+ <string name="contact_status_update_attribution" msgid="752179367353018597">"por meio de <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
+ <string name="contact_status_update_attribution_with_date" msgid="7358045508107825068">"<xliff:g id="DATE">%1$s</xliff:g> via <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
+ <string name="description_edit" msgid="1601490950771217014">"editar"</string>
+ <string name="description_star" msgid="2605854427360036550">"favorito"</string>
+ <string name="edit_contact" msgid="7529281274005689512">"Editar contato "</string>
+ <plurals name="merge_info">
+ <item quantity="one" msgid="148365587896371969">"não mesclado"</item>
+ <item quantity="other" msgid="425683718017380845">"mesclado a partir de <xliff:g id="COUNT">%0$d</xliff:g> origens"</item>
+ </plurals>
+ <string name="edit_delete_rawcontact" msgid="5054644778227879148">"Excluir"</string>
+ <string name="edit_error_saving" msgid="2153949392245240166">"Erro ao salvar"</string>
+ <string name="edit_structured_editor_button" msgid="3830078388142808106">"..."</string>
+ <string name="local_invisible_directory" msgid="6046691709127661065">"Outros"</string>
+ <!-- no translation found for aggregation_suggestion_title:one (7676930148670768645) -->
+ <!-- no translation found for aggregation_suggestion_title:other (3744098467672876675) -->
+ <!-- no translation found for aggregation_suggestion_joined_title (5342738708069602301) -->
+ <skip />
+ <!-- no translation found for aggregation_suggestion_join_button (7248325487963514260) -->
+ <skip />
+ <!-- no translation found for aggregation_suggestion_view_button (8232061564083962522) -->
+ <skip />
</resources>
diff --git a/res/values-rm/strings.xml b/res/values-rm/strings.xml
new file mode 100644
index 0000000..17d5c9f
--- /dev/null
+++ b/res/values-rm/strings.xml
@@ -0,0 +1,762 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2006 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for sharedUserLabel (7965035505237135241) -->
+ <skip />
+ <string name="contactsList" msgid="8661624236494819731">"Contacts"</string>
+ <!-- no translation found for launcherDialer (8636288196618486553) -->
+ <skip />
+ <!-- no translation found for shortcutContact (749243779392912958) -->
+ <skip />
+ <!-- no translation found for shortcutDialContact (746622101599186779) -->
+ <skip />
+ <!-- no translation found for shortcutMessageContact (2460337253595976198) -->
+ <skip />
+ <!-- no translation found for shortcutActivityTitle (6642877210643565436) -->
+ <skip />
+ <!-- no translation found for callShortcutActivityTitle (6065749861423648991) -->
+ <skip />
+ <!-- no translation found for messageShortcutActivityTitle (3084542316620335911) -->
+ <skip />
+ <!-- no translation found for starredList (4817256136413959463) -->
+ <skip />
+ <!-- no translation found for frequentList (7154768136473953056) -->
+ <skip />
+ <!-- no translation found for strequentList (5640192862059373511) -->
+ <skip />
+ <!-- no translation found for viewContactTitle (7989394521836644384) -->
+ <skip />
+ <!-- no translation found for viewContactDesription (214186610887547860) -->
+ <skip />
+ <!-- no translation found for editContactDescription (2947202828256214947) -->
+ <skip />
+ <!-- no translation found for edit_email_caption (2401084399676154272) -->
+ <skip />
+ <!-- no translation found for edit_email_type_caption (66331218163770698) -->
+ <skip />
+ <!-- no translation found for insertContactDescription (4709878105452681987) -->
+ <skip />
+ <!-- no translation found for searchHint (8482945356247760701) -->
+ <skip />
+ <!-- no translation found for menu_search (9147752853603483719) -->
+ <skip />
+ <!-- no translation found for menu_newContact (1209922412763274638) -->
+ <skip />
+ <!-- no translation found for menu_viewContact (2795575601596468581) -->
+ <skip />
+ <!-- no translation found for menu_callNumber (5142851348489387516) -->
+ <skip />
+ <!-- no translation found for menu_addStar (2908478235715404876) -->
+ <skip />
+ <!-- no translation found for menu_removeStar (5844227078364227030) -->
+ <skip />
+ <!-- no translation found for menu_editContact (3452858480713561396) -->
+ <skip />
+ <!-- no translation found for menu_deleteContact (1916555454274101750) -->
+ <skip />
+ <!-- no translation found for menu_call (3992595586042260618) -->
+ <skip />
+ <!-- no translation found for menu_sendSMS (5535886767547006515) -->
+ <skip />
+ <!-- no translation found for menu_sendEmail (7293508859242926187) -->
+ <skip />
+ <!-- no translation found for menu_viewAddress (1814744325763202024) -->
+ <skip />
+ <!-- no translation found for menu_makeDefaultNumber (4838759253316649534) -->
+ <skip />
+ <!-- no translation found for menu_makeDefaultEmail (2599044610375789994) -->
+ <skip />
+ <!-- no translation found for menu_splitAggregate (8368636463748691868) -->
+ <skip />
+ <!-- no translation found for contactsSplitMessage (5253490235863170269) -->
+ <skip />
+ <!-- no translation found for splitConfirmation_title (6716467920283502570) -->
+ <skip />
+ <!-- no translation found for splitConfirmation (1150797297503944823) -->
+ <skip />
+ <!-- no translation found for menu_joinAggregate (5027981918265667970) -->
+ <skip />
+ <!-- no translation found for titleJoinContactDataWith (7684875775798635354) -->
+ <skip />
+ <!-- no translation found for blurbJoinContactDataWith (995870557595050304) -->
+ <skip />
+ <!-- no translation found for showAllContactsJoinItem (2189695051430392383) -->
+ <skip />
+ <!-- no translation found for separatorJoinAggregateSuggestions (2831414448851313345) -->
+ <skip />
+ <!-- no translation found for separatorJoinAggregateAll (7939932265026181043) -->
+ <skip />
+ <!-- no translation found for contactsJoinedMessage (7208148163607047389) -->
+ <skip />
+ <!-- no translation found for menu_contactOptions (1957061455705020617) -->
+ <skip />
+ <!-- no translation found for contactOptionsTitle (8259347644090700915) -->
+ <skip />
+ <!-- no translation found for deleteConfirmation_title (6394309508930335204) -->
+ <skip />
+ <!-- no translation found for readOnlyContactWarning (1390849295342594265) -->
+ <skip />
+ <!-- no translation found for readOnlyContactDeleteConfirmation (2137170726670196909) -->
+ <skip />
+ <!-- no translation found for multipleContactDeleteConfirmation (938900978442960800) -->
+ <skip />
+ <!-- no translation found for deleteConfirmation (811706994761610640) -->
+ <skip />
+ <!-- no translation found for menu_done (796017761764190697) -->
+ <skip />
+ <!-- no translation found for menu_doNotSave (2174577548513895144) -->
+ <skip />
+ <!-- no translation found for editContact_title_edit (7678695190666836093) -->
+ <skip />
+ <!-- no translation found for editContact_title_insert (9125600232291405757) -->
+ <skip />
+ <!-- no translation found for label_phonetic_name (2288082649573927286) -->
+ <skip />
+ <!-- no translation found for label_notes (8337354953278341042) -->
+ <skip />
+ <!-- no translation found for label_ringtone (8833166825330686244) -->
+ <skip />
+ <!-- no translation found for ghostData_name (6490954238641157585) -->
+ <skip />
+ <!-- no translation found for ghostData_phonetic_name (7852749081984070902) -->
+ <skip />
+ <!-- no translation found for ghostData_company (5414421120553765775) -->
+ <skip />
+ <string name="ghostData_title" msgid="7496735200318496110">"Titel"</string>
+ <!-- no translation found for invalidContactMessage (5816991830260044593) -->
+ <skip />
+ <!-- no translation found for pickerNewContactHeader (7750705279843568147) -->
+ <skip />
+ <!-- no translation found for selectLabel (4255424123394910733) -->
+ <skip />
+ <!-- no translation found for phoneLabelsGroup (6468091477851199285) -->
+ <skip />
+ <!-- no translation found for emailLabelsGroup (8389931313045344406) -->
+ <skip />
+ <!-- no translation found for imLabelsGroup (3898238486262614027) -->
+ <skip />
+ <!-- no translation found for postalLabelsGroup (1618078212734693682) -->
+ <skip />
+ <!-- no translation found for otherLabels:0 (8287841928119937597) -->
+ <!-- no translation found for otherLabels:1 (7196592230748086755) -->
+ <!-- no translation found for photoPickerNotFoundText (431331662154342581) -->
+ <skip />
+ <!-- no translation found for attachToContact (8820530304406066714) -->
+ <skip />
+ <!-- no translation found for customLabelPickerTitle (1081475101983255212) -->
+ <skip />
+ <!-- no translation found for menu_displayGroup (5655505437727616553) -->
+ <skip />
+ <!-- no translation found for displayGroups (2278964020773993336) -->
+ <skip />
+ <!-- no translation found for send_to_voicemail_checkbox (9001686764070676353) -->
+ <skip />
+ <string name="default_ringtone" msgid="9099988849649827972">"Standard"</string>
+ <!-- no translation found for changePicture (2943329047610967714) -->
+ <skip />
+ <!-- no translation found for removePicture (3041230993155966350) -->
+ <skip />
+ <!-- no translation found for noContacts (8579310973261953559) -->
+ <skip />
+ <!-- no translation found for noMatchingContacts (4266283206853990471) -->
+ <skip />
+ <!-- no translation found for noContactsWithPhoneNumbers (1605457050218824269) -->
+ <skip />
+ <!-- no translation found for showFilterPhones (4184858075465653970) -->
+ <skip />
+ <!-- no translation found for showFilterPhonesDescrip (6644443248815191067) -->
+ <skip />
+ <!-- no translation found for headerContactGroups (2426134991932503843) -->
+ <skip />
+ <!-- no translation found for groupDescrip:other (3507881585720628389) -->
+ <!-- no translation found for groupDescripPhones:other (3816047547470490208) -->
+ <!-- no translation found for savingContact (4075751076741924939) -->
+ <skip />
+ <!-- no translation found for savingDisplayGroups (2133152192716475939) -->
+ <skip />
+ <!-- no translation found for contactSavedToast (7152589189385441091) -->
+ <skip />
+ <!-- no translation found for contactSavedErrorToast (9189098776225004666) -->
+ <skip />
+ <!-- no translation found for listTotalPhoneContacts:one (8721111084815668845) -->
+ <!-- no translation found for listTotalPhoneContacts:other (6133262880804110289) -->
+ <!-- no translation found for listTotalPhoneContactsZero (2756295259674938869) -->
+ <skip />
+ <!-- no translation found for listTotalAllContacts:one (1096068709488455155) -->
+ <!-- no translation found for listTotalAllContacts:other (2865867557378939630) -->
+ <!-- no translation found for listTotalAllContactsZero (6811347506748072822) -->
+ <skip />
+ <!-- no translation found for listFoundAllContacts:one (5517063038754171134) -->
+ <!-- no translation found for listFoundAllContacts:other (3852668542926965042) -->
+ <!-- no translation found for listFoundAllContactsZero (777952841930508289) -->
+ <skip />
+ <!-- no translation found for searchFoundContacts:one (4826918429708286628) -->
+ <!-- no translation found for searchFoundContacts:other (7988132539476575389) -->
+ <!-- no translation found for contactsIconLabel (7666609097606552806) -->
+ <skip />
+ <!-- no translation found for contactsFavoritesLabel (8417039765586853670) -->
+ <skip />
+ <!-- no translation found for dialerIconLabel (6500826552823403796) -->
+ <skip />
+ <!-- no translation found for recentCallsIconLabel (1419116422359067949) -->
+ <skip />
+ <!-- no translation found for liveFolderAll (4789010460767506206) -->
+ <skip />
+ <!-- no translation found for liveFolderFavorites (3100957542927222282) -->
+ <skip />
+ <!-- no translation found for liveFolderPhone (3739376066610926780) -->
+ <skip />
+ <!-- no translation found for menu_sendTextMessage (6937343460284499306) -->
+ <skip />
+ <!-- no translation found for recentCalls_callNumber (1756372533999226126) -->
+ <skip />
+ <!-- no translation found for recentCalls_editNumberBeforeCall (7756171675833267857) -->
+ <skip />
+ <!-- no translation found for recentCalls_addToContact (1429899535546487008) -->
+ <skip />
+ <!-- no translation found for recentCalls_removeFromRecentList (401662244636511330) -->
+ <skip />
+ <!-- no translation found for recentCalls_deleteAll (6352364392762163704) -->
+ <skip />
+ <!-- no translation found for recentCalls_empty (247053222448663107) -->
+ <skip />
+ <!-- no translation found for clearCallLogConfirmation_title (718072843006222703) -->
+ <skip />
+ <!-- no translation found for clearCallLogConfirmation (7625927669136267636) -->
+ <skip />
+ <string name="imei" msgid="3045126336951684285">"IMEI"</string>
+ <!-- no translation found for meid (6210568493746275750) -->
+ <skip />
+ <!-- no translation found for voicemail (3851469869202611441) -->
+ <skip />
+ <!-- no translation found for unknown (740067747858270469) -->
+ <skip />
+ <!-- no translation found for private_num (6374339738119166953) -->
+ <skip />
+ <!-- no translation found for payphone (4864313342828942922) -->
+ <skip />
+ <!-- no translation found for dialerKeyboardHintText (5401660096579787344) -->
+ <skip />
+ <!-- no translation found for dialerDialpadHintText (5824490365898349041) -->
+ <skip />
+ <!-- no translation found for simContacts_emptyLoading (6700035985448642408) -->
+ <skip />
+ <!-- no translation found for simContacts_title (27341688347689769) -->
+ <skip />
+ <!-- no translation found for noContactsHelpTextWithSyncForCreateShortcut (801504710275614594) -->
+ <skip />
+ <!-- no translation found for noContactsHelpTextForCreateShortcut (3081286388667108335) -->
+ <skip />
+ <!-- no translation found for noContactsHelpText (7633826236417884130) -->
+ <skip />
+ <!-- no translation found for noContactsHelpTextWithSync (3017521127042216243) -->
+ <skip />
+ <!-- no translation found for noContactsNoSimHelpText (467658807711582876) -->
+ <skip />
+ <!-- no translation found for noContactsNoSimHelpTextWithSync (9040060730467973050) -->
+ <skip />
+ <!-- no translation found for noFavoritesHelpText (3744655776704833277) -->
+ <skip />
+ <!-- no translation found for liveFolder_all_label (5961411940473276616) -->
+ <skip />
+ <!-- no translation found for liveFolder_favorites_label (2674341514070517105) -->
+ <skip />
+ <!-- no translation found for liveFolder_phones_label (1709786878793436245) -->
+ <skip />
+ <!-- no translation found for dialer_useDtmfDialpad (1707548397435075040) -->
+ <skip />
+ <!-- no translation found for dialer_returnToInCallScreen (3719386377550913067) -->
+ <skip />
+ <!-- no translation found for dialer_addAnotherCall (4205688819890074468) -->
+ <skip />
+ <!-- no translation found for callDetailTitle (5340227785196217938) -->
+ <skip />
+ <!-- no translation found for toast_call_detail_error (7200975244804730096) -->
+ <skip />
+ <!-- no translation found for type_incoming (6502076603836088532) -->
+ <skip />
+ <!-- no translation found for type_outgoing (343108709599392641) -->
+ <skip />
+ <!-- no translation found for type_missed (2720502601640509542) -->
+ <skip />
+ <!-- no translation found for actionIncomingCall (6028930669817038600) -->
+ <skip />
+ <!-- no translation found for callBack (5498224409038809224) -->
+ <skip />
+ <!-- no translation found for callAgain (3197312117049874778) -->
+ <skip />
+ <!-- no translation found for returnCall (8171961914203617813) -->
+ <skip />
+ <!-- no translation found for callDetailsDurationFormat (8157706382818184268) -->
+ <skip />
+ <!-- no translation found for favoritesFrquentSeparator (8107518433381283736) -->
+ <skip />
+ <!-- no translation found for add_contact_dlg_title (2896685845822146494) -->
+ <skip />
+ <!-- no translation found for add_contact_dlg_message_fmt (7986472669444326576) -->
+ <skip />
+ <!-- no translation found for all_tab_label (4003124364397916826) -->
+ <skip />
+ <string name="description_image_button_one" msgid="1740638037139856139">"in"</string>
+ <string name="description_image_button_two" msgid="5882638439003731308">"dus"</string>
+ <string name="description_image_button_three" msgid="8709731759376015180">"trais"</string>
+ <string name="description_image_button_four" msgid="3530239685642246130">"quatter"</string>
+ <!-- no translation found for description_image_button_five (1182465427501188413) -->
+ <skip />
+ <!-- no translation found for description_image_button_six (2093656269261415475) -->
+ <skip />
+ <!-- no translation found for description_image_button_seven (2450357020447676481) -->
+ <skip />
+ <string name="description_image_button_eight" msgid="6969435115163287801">"otg"</string>
+ <!-- no translation found for description_image_button_nine (7857248695662558323) -->
+ <skip />
+ <!-- no translation found for description_image_button_star (3365919907520767866) -->
+ <skip />
+ <string name="description_image_button_zero" msgid="4133108949401820710">"nulla"</string>
+ <!-- no translation found for description_image_button_pound (3039765597595889230) -->
+ <skip />
+ <!-- no translation found for description_voicemail_button (3402506823655455591) -->
+ <skip />
+ <!-- no translation found for description_dial_button (1274091017188142646) -->
+ <skip />
+ <!-- no translation found for description_delete_button (6263102114033407382) -->
+ <skip />
+ <!-- no translation found for description_digits_edittext (8760207516497016437) -->
+ <skip />
+ <!-- no translation found for description_contact_photo (3387458082667894062) -->
+ <skip />
+ <!-- no translation found for description_minus_button (387136707700230172) -->
+ <skip />
+ <!-- no translation found for description_plus_button (515164827856229880) -->
+ <skip />
+ <!-- no translation found for no_sdcard_title (5911758680339949273) -->
+ <skip />
+ <!-- no translation found for no_sdcard_message (6019391476490445358) -->
+ <skip />
+ <!-- no translation found for searching_vcard_title (4970508055399376813) -->
+ <skip />
+ <!-- no translation found for import_from_sim (3859272228033941659) -->
+ <skip />
+ <!-- no translation found for import_from_sdcard (8550360976693202816) -->
+ <skip />
+ <!-- no translation found for export_to_sdcard (2597105442616166277) -->
+ <skip />
+ <!-- no translation found for share_visible_contacts (890150378880783797) -->
+ <skip />
+ <!-- no translation found for import_one_vcard_string (9059163467020328433) -->
+ <skip />
+ <!-- no translation found for import_multiple_vcard_string (3810226492811062392) -->
+ <skip />
+ <!-- no translation found for import_all_vcard_string (5518136113853448474) -->
+ <skip />
+ <!-- no translation found for searching_vcard_message (6917522333561434546) -->
+ <skip />
+ <!-- no translation found for scanning_sdcard_failed_title (3506782007953167180) -->
+ <skip />
+ <!-- no translation found for scanning_sdcard_failed_message (3761992500690182922) -->
+ <skip />
+ <!-- no translation found for fail_reason_io_error (5922864781066136340) -->
+ <skip />
+ <!-- no translation found for fail_reason_low_memory_during_import (7514918659342886381) -->
+ <skip />
+ <!-- no translation found for fail_reason_vcard_parse_error (1201233722762680214) -->
+ <skip />
+ <!-- no translation found for fail_reason_vcard_not_supported_error (655208100451286027) -->
+ <skip />
+ <!-- no translation found for fail_reason_no_vcard_file (6376516175882881595) -->
+ <skip />
+ <!-- no translation found for fail_reason_failed_to_collect_vcard_meta_info (4154492282316067754) -->
+ <skip />
+ <!-- no translation found for fail_reason_failed_to_read_files (3659521123567134029) -->
+ <skip />
+ <!-- no translation found for fail_reason_unknown (999034019513096768) -->
+ <skip />
+ <!-- no translation found for select_vcard_title (3968948173786172468) -->
+ <skip />
+ <!-- no translation found for caching_vcard_title (5009556022082659780) -->
+ <skip />
+ <!-- no translation found for caching_vcard_message (2380844718093378900) -->
+ <skip />
+ <!-- no translation found for progress_notifier_message (2311011466908220528) -->
+ <skip />
+ <!-- no translation found for importing_vcard_description (4245275224298571351) -->
+ <skip />
+ <!-- no translation found for reading_vcard_failed_title (2162610359561887043) -->
+ <skip />
+ <!-- no translation found for reading_vcard_canceled_title (1770608329958463131) -->
+ <skip />
+ <!-- no translation found for importing_vcard_finished_title (3213257229785702182) -->
+ <skip />
+ <!-- no translation found for vcard_importer_start_message (5520549352987753320) -->
+ <skip />
+ <!-- no translation found for vcard_importer_will_start_message (2055145402481822160) -->
+ <skip />
+ <!-- no translation found for percentage (34897865327092209) -->
+ <skip />
+ <!-- no translation found for confirm_export_title (7648747763127442983) -->
+ <skip />
+ <!-- no translation found for confirm_export_message (3875683519257829750) -->
+ <skip />
+ <!-- no translation found for exporting_contact_failed_title (585823094820602526) -->
+ <skip />
+ <!-- no translation found for exporting_contact_failed_message (4151348002470298092) -->
+ <skip />
+ <!-- no translation found for fail_reason_no_exportable_contact (4919714086648344495) -->
+ <skip />
+ <!-- no translation found for fail_reason_too_many_vcard (7084146295639672658) -->
+ <skip />
+ <!-- no translation found for fail_reason_too_long_filename (1915716071321839166) -->
+ <skip />
+ <!-- no translation found for vcard_exporter_start_message (6497218576519292369) -->
+ <skip />
+ <!-- no translation found for exporting_vcard_finished_title (1268425436375550862) -->
+ <skip />
+ <!-- no translation found for exporting_contact_list_title (9072240631534457415) -->
+ <skip />
+ <!-- no translation found for exporting_contact_list_message (5640326540405486055) -->
+ <skip />
+ <!-- no translation found for fail_reason_could_not_initialize_exporter (4943708332700987376) -->
+ <skip />
+ <!-- no translation found for fail_reason_error_occurred_during_export (2151165129433831202) -->
+ <skip />
+ <!-- no translation found for composer_failed_to_get_database_infomation (3723109558155169053) -->
+ <skip />
+ <!-- no translation found for composer_has_no_exportable_contact (754734132189369094) -->
+ <skip />
+ <!-- no translation found for composer_not_initialized (8041534450748388843) -->
+ <skip />
+ <!-- no translation found for fail_reason_could_not_open_file (4013520943128739511) -->
+ <skip />
+ <!-- no translation found for exporting_contact_list_progress (560522409559101193) -->
+ <skip />
+ <!-- no translation found for search_settings_description (2675223022992445813) -->
+ <skip />
+ <!-- no translation found for add_2sec_pause (9214012315201040129) -->
+ <skip />
+ <!-- no translation found for add_wait (3360818652790319634) -->
+ <skip />
+ <!-- no translation found for call_disambig_title (1911302597959335178) -->
+ <skip />
+ <!-- no translation found for sms_disambig_title (4675399294513152364) -->
+ <skip />
+ <!-- no translation found for make_primary (5829291915305113983) -->
+ <skip />
+ <!-- no translation found for quickcontact_missing_app (4600366393134289038) -->
+ <skip />
+ <!-- no translation found for quickcontact_remember_choice (5964536411579749424) -->
+ <skip />
+ <!-- no translation found for quickcontact_missing_name (5590266114306996632) -->
+ <skip />
+ <!-- no translation found for quickcontact_no_data (2098000859125253675) -->
+ <skip />
+ <!-- no translation found for menu_accounts (8499114602017077970) -->
+ <skip />
+ <!-- no translation found for menu_import_export (3765725645491577190) -->
+ <skip />
+ <!-- no translation found for dialog_import_export (4771877268244096596) -->
+ <skip />
+ <!-- no translation found for menu_share (943789700636542260) -->
+ <skip />
+ <!-- no translation found for share_via (563121028023030093) -->
+ <skip />
+ <!-- no translation found for share_error (4374508848981697170) -->
+ <skip />
+ <string name="nameLabelsGroup" msgid="2034640839640477827">"Num"</string>
+ <!-- no translation found for nicknameLabelsGroup (2891682101053358010) -->
+ <skip />
+ <!-- no translation found for organizationLabelsGroup (2478611760751832035) -->
+ <skip />
+ <!-- no translation found for websiteLabelsGroup (4202998982804009261) -->
+ <skip />
+ <!-- no translation found for eventLabelsGroup (8069912895912714412) -->
+ <skip />
+ <!-- no translation found for type_short_home (7770424864090605384) -->
+ <skip />
+ <!-- no translation found for type_short_mobile (1655473281466676216) -->
+ <skip />
+ <!-- no translation found for type_short_work (4925330752504537861) -->
+ <skip />
+ <!-- no translation found for type_short_pager (2613818970827594238) -->
+ <skip />
+ <!-- no translation found for type_short_other (5669407180177236769) -->
+ <skip />
+ <!-- no translation found for edit_read_only (8158629550655830981) -->
+ <skip />
+ <!-- no translation found for edit_secondary_collapse (5371618426594477103) -->
+ <skip />
+ <!-- no translation found for dialog_primary_name (5521591005692614833) -->
+ <skip />
+ <!-- no translation found for dialog_new_contact_account (9044704073286262197) -->
+ <skip />
+ <!-- no translation found for menu_sync_remove (3266725887008450161) -->
+ <skip />
+ <!-- no translation found for dialog_sync_add (8267045393119375803) -->
+ <skip />
+ <!-- no translation found for display_more_groups (2682547080423434170) -->
+ <skip />
+ <!-- no translation found for display_ungrouped (4602580795576261158) -->
+ <skip />
+ <!-- no translation found for display_all_contacts (6846131371214707956) -->
+ <skip />
+ <!-- no translation found for display_warn_remove_ungrouped (2314043155909167610) -->
+ <skip />
+ <!-- no translation found for account_phone (4025734638492419713) -->
+ <skip />
+ <!-- no translation found for call_custom (7756571794763171802) -->
+ <skip />
+ <!-- no translation found for call_home (1990519474420545392) -->
+ <skip />
+ <!-- no translation found for call_mobile (7502236805487609178) -->
+ <skip />
+ <!-- no translation found for call_work (5328785911463744028) -->
+ <skip />
+ <!-- no translation found for call_fax_work (7467763592359059243) -->
+ <skip />
+ <!-- no translation found for call_fax_home (8342175628887571876) -->
+ <skip />
+ <!-- no translation found for call_pager (9003902812293983281) -->
+ <skip />
+ <!-- no translation found for call_other (8563753966926932052) -->
+ <skip />
+ <!-- no translation found for call_callback (1910165691349426858) -->
+ <skip />
+ <!-- no translation found for call_car (3280537320306436445) -->
+ <skip />
+ <!-- no translation found for call_company_main (6105120947138711257) -->
+ <skip />
+ <!-- no translation found for call_isdn (1541590690193403411) -->
+ <skip />
+ <!-- no translation found for call_main (6082900571803441339) -->
+ <skip />
+ <!-- no translation found for call_other_fax (5745314124619636674) -->
+ <skip />
+ <!-- no translation found for call_radio (8296755876398357063) -->
+ <skip />
+ <!-- no translation found for call_telex (2223170774548648114) -->
+ <skip />
+ <!-- no translation found for call_tty_tdd (8951266948204379604) -->
+ <skip />
+ <!-- no translation found for call_work_mobile (8707874281430105394) -->
+ <skip />
+ <!-- no translation found for call_work_pager (3419348514157949008) -->
+ <skip />
+ <!-- no translation found for call_assistant (2141641383068514308) -->
+ <skip />
+ <!-- no translation found for call_mms (6274041545876221437) -->
+ <skip />
+ <!-- no translation found for sms_custom (5932736853732191825) -->
+ <skip />
+ <!-- no translation found for sms_home (7524332261493162995) -->
+ <skip />
+ <!-- no translation found for sms_mobile (5200107250451030769) -->
+ <skip />
+ <!-- no translation found for sms_work (2269624156655267740) -->
+ <skip />
+ <!-- no translation found for sms_fax_work (8028189067816907075) -->
+ <skip />
+ <!-- no translation found for sms_fax_home (9204042076306809634) -->
+ <skip />
+ <!-- no translation found for sms_pager (7730404569637015192) -->
+ <skip />
+ <!-- no translation found for sms_other (806127844607642331) -->
+ <skip />
+ <!-- no translation found for sms_callback (5004824430094288752) -->
+ <skip />
+ <!-- no translation found for sms_car (7444227058437359641) -->
+ <skip />
+ <!-- no translation found for sms_company_main (118970873419678087) -->
+ <skip />
+ <!-- no translation found for sms_isdn (8153785037515047845) -->
+ <skip />
+ <!-- no translation found for sms_main (8621625784504541679) -->
+ <skip />
+ <!-- no translation found for sms_other_fax (3888842199855843152) -->
+ <skip />
+ <!-- no translation found for sms_radio (3329166673433967820) -->
+ <skip />
+ <!-- no translation found for sms_telex (9034802430065267848) -->
+ <skip />
+ <!-- no translation found for sms_tty_tdd (6782284969132531532) -->
+ <skip />
+ <!-- no translation found for sms_work_mobile (2459939960512702560) -->
+ <skip />
+ <!-- no translation found for sms_work_pager (5566924423316960597) -->
+ <skip />
+ <!-- no translation found for sms_assistant (2773424339923116234) -->
+ <skip />
+ <!-- no translation found for sms_mms (4069352461380762677) -->
+ <skip />
+ <!-- no translation found for email_home (8573740658148184279) -->
+ <skip />
+ <!-- no translation found for email_mobile (2042889209787989814) -->
+ <skip />
+ <!-- no translation found for email_work (2807430017302722689) -->
+ <skip />
+ <!-- no translation found for email_other (3454004077967657109) -->
+ <skip />
+ <!-- no translation found for email_custom (7548003991586214105) -->
+ <skip />
+ <!-- no translation found for email (5668400997660065897) -->
+ <skip />
+ <!-- no translation found for map_home (1243547733423343982) -->
+ <skip />
+ <!-- no translation found for map_work (1360474076921878088) -->
+ <skip />
+ <!-- no translation found for map_other (3817820803587012641) -->
+ <skip />
+ <!-- no translation found for map_custom (6184363799976265281) -->
+ <skip />
+ <!-- no translation found for chat_aim (2588492205291249142) -->
+ <skip />
+ <!-- no translation found for chat_msn (8041633440091073484) -->
+ <skip />
+ <!-- no translation found for chat_yahoo (6629211142719943666) -->
+ <skip />
+ <!-- no translation found for chat_skype (1210045020427480566) -->
+ <skip />
+ <!-- no translation found for chat_qq (4294637812847719693) -->
+ <skip />
+ <!-- no translation found for chat_gtalk (981575737258117697) -->
+ <skip />
+ <!-- no translation found for chat_icq (8438405386153745775) -->
+ <skip />
+ <!-- no translation found for chat_jabber (7561444230307829609) -->
+ <skip />
+ <!-- no translation found for chat (9025361898797412245) -->
+ <skip />
+ <!-- no translation found for postal_street (8133143961580058972) -->
+ <skip />
+ <!-- no translation found for postal_pobox (4431938829180269821) -->
+ <skip />
+ <!-- no translation found for postal_neighborhood (1450783874558956739) -->
+ <skip />
+ <string name="postal_city" msgid="6597491300084895548">"Citad"</string>
+ <!-- no translation found for postal_region (6045263193478437672) -->
+ <skip />
+ <!-- no translation found for postal_postcode (572136414136673751) -->
+ <skip />
+ <!-- no translation found for postal_country (7638264508416368690) -->
+ <skip />
+ <!-- no translation found for name_given (1687286314106019813) -->
+ <skip />
+ <!-- no translation found for name_family (3416695586119999058) -->
+ <skip />
+ <!-- no translation found for name_prefix (59756378548779822) -->
+ <skip />
+ <!-- no translation found for name_middle (8467433655992690326) -->
+ <skip />
+ <!-- no translation found for name_suffix (3855278445375651441) -->
+ <skip />
+ <!-- no translation found for name_phonetic_given (6853570431394449191) -->
+ <skip />
+ <!-- no translation found for name_phonetic_middle (8643721493320405200) -->
+ <skip />
+ <!-- no translation found for name_phonetic_family (462095502140180305) -->
+ <skip />
+ <!-- no translation found for account_type_format (718948015590343010) -->
+ <skip />
+ <!-- no translation found for from_account_format (687567483928582084) -->
+ <skip />
+ <!-- no translation found for account_type_and_name (2876220035411512934) -->
+ <skip />
+ <!-- no translation found for use_photo_as_primary (8807110122951157246) -->
+ <skip />
+ <!-- no translation found for contact_read_only (1203216914575723978) -->
+ <skip />
+ <!-- no translation found for no_contact_details (6754415338321837001) -->
+ <skip />
+ <!-- no translation found for display_options_sort_list_by (6080091755852211076) -->
+ <skip />
+ <!-- no translation found for display_options_sort_by_given_name (184916793466387067) -->
+ <skip />
+ <string name="display_options_sort_by_family_name" msgid="7857986975275712622">"Num da famiglia"</string>
+ <!-- no translation found for display_options_view_names_as (18022868169627979) -->
+ <skip />
+ <!-- no translation found for display_options_view_given_name_first (6968288511197363292) -->
+ <skip />
+ <!-- no translation found for display_options_view_family_name_first (1447288164951453714) -->
+ <skip />
+ <!-- no translation found for search_bar_hint (1012756309632856553) -->
+ <skip />
+ <!-- no translation found for search_for_all_contacts (6644963335787294131) -->
+ <skip />
+ <!-- no translation found for take_photo (7496128293167402354) -->
+ <skip />
+ <!-- no translation found for pick_photo (448886509158039462) -->
+ <skip />
+ <!-- no translation found for locale_change_in_progress (1124266507671178413) -->
+ <skip />
+ <!-- no translation found for upgrade_in_progress (7530893673211750223) -->
+ <skip />
+ <!-- no translation found for upgrade_out_of_memory (6713914687111678777) -->
+ <skip />
+ <!-- no translation found for upgrade_out_of_memory_uninstall (1721798828992091432) -->
+ <skip />
+ <!-- no translation found for upgrade_out_of_memory_retry (8431289830472724609) -->
+ <skip />
+ <!-- no translation found for search_results_for (8705490885073188513) -->
+ <skip />
+ <!-- no translation found for search_results_searching (7755623475227227314) -->
+ <skip />
+ <!-- no translation found for adding_recipients (5123647646892106631) -->
+ <skip />
+ <!-- no translation found for menu_display_selected (6470001164297969034) -->
+ <skip />
+ <!-- no translation found for menu_display_all (8887488642609786198) -->
+ <skip />
+ <!-- no translation found for menu_select_all (621719255150713545) -->
+ <skip />
+ <!-- no translation found for menu_select_none (7093222469852132345) -->
+ <skip />
+ <!-- no translation found for multiple_picker_title:one (4761009734586319101) -->
+ <!-- no translation found for multiple_picker_title:other (4608837420986126229) -->
+ <!-- no translation found for unknown_contacts_separator (2490675836664397214) -->
+ <skip />
+ <!-- no translation found for no_contacts_selected (5877803471037324613) -->
+ <skip />
+ <!-- no translation found for add_field (5257149039253569615) -->
+ <skip />
+ <!-- no translation found for contact_status_update_attribution (752179367353018597) -->
+ <skip />
+ <!-- no translation found for contact_status_update_attribution_with_date (7358045508107825068) -->
+ <skip />
+ <!-- no translation found for description_edit (1601490950771217014) -->
+ <skip />
+ <!-- no translation found for description_star (2605854427360036550) -->
+ <skip />
+ <!-- no translation found for edit_contact (7529281274005689512) -->
+ <skip />
+ <!-- no translation found for merge_info:one (148365587896371969) -->
+ <!-- no translation found for merge_info:other (425683718017380845) -->
+ <!-- no translation found for edit_delete_rawcontact (5054644778227879148) -->
+ <skip />
+ <!-- no translation found for edit_error_saving (2153949392245240166) -->
+ <skip />
+ <!-- no translation found for edit_structured_editor_button (3830078388142808106) -->
+ <skip />
+ <!-- no translation found for local_invisible_directory (6046691709127661065) -->
+ <skip />
+ <!-- no translation found for aggregation_suggestion_title:one (7676930148670768645) -->
+ <!-- no translation found for aggregation_suggestion_title:other (3744098467672876675) -->
+ <!-- no translation found for aggregation_suggestion_joined_title (5342738708069602301) -->
+ <skip />
+ <!-- no translation found for aggregation_suggestion_join_button (7248325487963514260) -->
+ <skip />
+ <!-- no translation found for aggregation_suggestion_view_button (8232061564083962522) -->
+ <skip />
+</resources>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index c4b3756..94a19f7 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -31,6 +31,8 @@
<string name="viewContactTitle" msgid="7989394521836644384">"Сведения о контакте"</string>
<string name="viewContactDesription" msgid="214186610887547860">"Просмотреть контакт"</string>
<string name="editContactDescription" msgid="2947202828256214947">"Изменить контакт"</string>
+ <string name="edit_email_caption" msgid="2401084399676154272">"Адрес электронной почты:"</string>
+ <string name="edit_email_type_caption" msgid="66331218163770698">"Тип:"</string>
<string name="insertContactDescription" msgid="4709878105452681987">"Создать контакт"</string>
<string name="searchHint" msgid="8482945356247760701">"Поиск контактов"</string>
<string name="menu_search" msgid="9147752853603483719">"Поиск"</string>
@@ -123,13 +125,13 @@
</plurals>
<string name="listTotalAllContactsZero" msgid="6811347506748072822">"Видимые контакты отсутствуют."</string>
<plurals name="listFoundAllContacts">
- <item quantity="one" msgid="2830107332033967280">"Найден 1 контакт"</item>
- <item quantity="other" msgid="7752927996850263152">"Найдено контактов: <xliff:g id="COUNT">%d</xliff:g>"</item>
+ <item quantity="one" msgid="5517063038754171134">"Найдено: 1"</item>
+ <item quantity="other" msgid="3852668542926965042">"Найдено: <xliff:g id="COUNT">%d</xliff:g>"</item>
</plurals>
- <string name="listFoundAllContactsZero" msgid="5554368784319460828">"Контакт не найден."</string>
+ <string name="listFoundAllContactsZero" msgid="777952841930508289">"Не найдено"</string>
<plurals name="searchFoundContacts">
- <item quantity="one" msgid="916648718690661777">"1 контакт"</item>
- <item quantity="other" msgid="5660384247071761844">"Контактов: <xliff:g id="COUNT">%d</xliff:g>"</item>
+ <item quantity="one" msgid="4826918429708286628">"Найдено: 1"</item>
+ <item quantity="other" msgid="7988132539476575389">"Найдено: <xliff:g id="COUNT">%d</xliff:g>"</item>
</plurals>
<string name="contactsIconLabel" msgid="7666609097606552806">"Контакты"</string>
<string name="contactsFavoritesLabel" msgid="8417039765586853670">"Избранное"</string>
@@ -159,10 +161,10 @@
<string name="simContacts_title" msgid="27341688347689769">"Контакты на SIM-карте"</string>
<string name="noContactsHelpTextWithSyncForCreateShortcut" msgid="801504710275614594">"Отсутствуют контакты для отображения. (Если аккаунт был только что добавлен, потребуется несколько минут для синхронизации контактов.)"</string>
<string name="noContactsHelpTextForCreateShortcut" msgid="3081286388667108335">"Отсутствуют контакты для отображения."</string>
- <string name="noContactsHelpText" msgid="6788487368878712350">"У вас нет контактов."\n\n"Чтобы добавить контакты, нажмите "<font fgcolor="#ffffffff"><b>"Меню"</b></font>" и выберите:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Аккаунты"</b></font>", чтобы добавить или настроить аккаунт с контактами, которые можно будет синхронизировать с телефоном;"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Новый контакт"</b></font>", чтобы создать контакт \"с нуля\";"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Импорт/Экспорт."</b></font>\n</li></string>
- <string name="noContactsHelpTextWithSync" msgid="3734101165712848179">"У вас нет контактов. (Если вы только что добавили аккаунт, синхронизация контактов может занять несколько минут.)"\n\n"Чтобы добавить контакты, нажмите "<font fgcolor="#ffffffff"><b>"Меню"</b></font>" и выберите:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Аккаунты"</b></font>", чтобы добавить или настроить аккаунт с контактами, которые можно будет синхронизировать с телефоном;"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Варианты отображения"</b></font>", чтобы выбрать, какие контакты будут видны; "\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Новый контакт"</b></font>", чтобы создать контакт \"с нуля\";"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Импорт/Экспорт."</b></font>\n</li></string>
- <string name="noContactsNoSimHelpText" msgid="6553845386917463292">"У вас нет контактов."\n\n"Чтобы добавить контакты, нажмите "<font fgcolor="#ffffffff"><b>"Меню"</b></font>" и выберите:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Аккаунты"</b></font>", чтобы добавить или настроить аккаунт с контактами, которые можно будет синхронизировать с телефоном;"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Новый контакт"</b></font>", чтобы создать контакт \"с нуля\";"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Импорт/Экспорт."</b></font>\n</li></string>
- <string name="noContactsNoSimHelpTextWithSync" msgid="1122296298361373488">"У вас нет контактов. (Если вы только что добавили аккаунт, синхронизация контактов может занять несколько минут.)"\n\n"Чтобы добавить контакты, нажмите "<font fgcolor="#ffffffff"><b>"Меню"</b></font>" и выберите:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Аккаунты"</b></font>", чтобы добавить или настроить аккаунт с контактами, которые можно будет синхронизировать с телефоном;"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Варианты отображения"</b></font>", чтобы выбрать, какие контакты будут видны; "\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Новый контакт"</b></font>", чтобы создать контакт \"с нуля\";"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Импорт/Экспорт."</b></font>\n</li></string>
+ <string name="noContactsHelpText" msgid="7633826236417884130">"У вас нет контактов."\n\n"Чтобы добавить контакты, нажмите "<font fgcolor="#ffffffff"><b>"Меню"</b></font>" и выберите:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Аккаунты,"</b></font>" чтобы добавить или настроить аккаунт с контактами, которые можно будет синхронизировать с телефоном;"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Новый контакт,"</b></font>" чтобы создать новый контакт;"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Импорт/Экспорт,"</b></font>" чтобы импортировать контакты с SIM-карты или SD-карты."\n</li></string>
+ <string name="noContactsHelpTextWithSync" msgid="3017521127042216243">"У вас нет контактов (если вы добавили аккаунт только что, подожите несколько минут, пока идет их синхронизация с телефоном). "\n\n"Чтобы добавить контакты, нажмите "<font fgcolor="#ffffffff"><b>"Меню"</b></font>" и выберите:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Аккаунты"</b></font>", чтобы добавить или настроить аккаунт с контактами, которые можно будет синхронизировать с телефоном;"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Варианты отображения"</b></font>", чтобы выбрать, какие контакты будут видны; "\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Новый контакт"</b></font>", чтобы создать новый контакт;"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Импорт/Экспорт"</b></font>", чтобы импортировать контакты с SIM-карты или SD-карты."\n</li></string>
+ <string name="noContactsNoSimHelpText" msgid="467658807711582876">"У вас нет контактов."\n\n"Чтобы добавить контакты, нажмите "<font fgcolor="#ffffffff"><b>"Меню"</b></font>" и выберите:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Аккаунты"</b></font>", чтобы добавить или настроить аккаунт с контактами, которые можно будет синхронизировать с телефоном;"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Новый контакт"</b></font>", чтобы создать новый контакт;"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Импорт/Экспорт"</b></font>", чтобы импортировать контакты с SD-карты."\n</li></string>
+ <string name="noContactsNoSimHelpTextWithSync" msgid="9040060730467973050">"У вас нет контактов (если вы добавили аккаунт только что, подожите несколько минут, пока идет их синхронизация с телефоном). "\n\n"Чтобы добавить контакты, нажмите "<font fgcolor="#ffffffff"><b>"Меню"</b></font>" и выберите:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Аккаунты"</b></font>", чтобы добавить или настроить аккаунт с контактами, которые можно будет синхронизировать с телефоном;"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Варианты отображения"</b></font>", чтобы выбрать, какие контакты будут видны; "\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Новый контакт"</b></font>", чтобы создать новый контакт;"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Импорт/Экспорт"</b></font>", чтобы импортировать контакты с SD-карты."\n</li></string>
<string name="noFavoritesHelpText" msgid="3744655776704833277">"У вас нет избранных контактов."\n\n"Чтобы сделать контакт избранным,"\n\n" "<li>"нажмите вкладку "<b>"Контакты"</b>";"\n</li>" "\n<li>"нажмите контакт, который нужно сделать избранным;"\n</li>" "\n<li>"нажмите звездочку возле имени контакта."\n</li></string>
<string name="liveFolder_all_label" msgid="5961411940473276616">"Все контакты"</string>
<string name="liveFolder_favorites_label" msgid="2674341514070517105">"Помеченные"</string>
@@ -179,7 +181,7 @@
<string name="callBack" msgid="5498224409038809224">"Перезвонить"</string>
<string name="callAgain" msgid="3197312117049874778">"Позвонить снова"</string>
<string name="returnCall" msgid="8171961914203617813">"Перезвонить"</string>
- <string name="callDetailsDurationFormat" msgid="8157706382818184268">"<xliff:g id="MINUTES">%1$s</xliff:g>:<xliff:g id="SECONDS">%2$s</xliff:g>"</string>
+ <string name="callDetailsDurationFormat" msgid="8157706382818184268">"<xliff:g id="MINUTES">%s</xliff:g>:<xliff:g id="SECONDS">%s</xliff:g>"</string>
<string name="favoritesFrquentSeparator" msgid="8107518433381283736">"Часто набираемые"</string>
<string name="add_contact_dlg_title" msgid="2896685845822146494">"Добавить контакт"</string>
<string name="add_contact_dlg_message_fmt" msgid="7986472669444326576">"Добавить в контакты <xliff:g id="EMAIL">%s</xliff:g>?"</string>
@@ -217,18 +219,24 @@
<string name="scanning_sdcard_failed_title" msgid="3506782007953167180">"Не удалось сканировать SD-карту."</string>
<string name="scanning_sdcard_failed_message" msgid="3761992500690182922">"Не удалось просканировать SD-карту (причина: \"<xliff:g id="FAIL_REASON">%s</xliff:g>\")"</string>
<string name="fail_reason_io_error" msgid="5922864781066136340">"Ошибка ввода-вывода"</string>
+ <string name="fail_reason_low_memory_during_import" msgid="7514918659342886381">"Недостаточно памяти (возможно, файл слишком велик)"</string>
<string name="fail_reason_vcard_parse_error" msgid="1201233722762680214">"Не удалось обработать vCard по неизвестной причине."</string>
<string name="fail_reason_vcard_not_supported_error" msgid="655208100451286027">"Не удалось обработать vCard. Файл правильного формата, но не поддерживается в текущей реализации."</string>
<string name="fail_reason_no_vcard_file" msgid="6376516175882881595">"На SD-карте не найдены файлы VCard"</string>
+ <string name="fail_reason_failed_to_collect_vcard_meta_info" msgid="4154492282316067754">"Не удалось собрать метаинформацию файлов VCard."</string>
<string name="fail_reason_failed_to_read_files" msgid="3659521123567134029">"Не удалось импортировать один или несколько файлов (%s)."</string>
<string name="fail_reason_unknown" msgid="999034019513096768">"Неизвестная ошибка"</string>
<string name="select_vcard_title" msgid="3968948173786172468">"Выберите файл vCard"</string>
- <string name="progress_shower_message" msgid="5636525578293752526">"<xliff:g id="ACTION">%1$s</xliff:g>"\n"<xliff:g id="FILENAME">%2$s</xliff:g>"</string>
- <string name="reading_vcard_title" msgid="4723433501579653199">"Чтение vCard"</string>
- <string name="reading_vcard_message" msgid="6381368920030748743">"Чтение файлов VCard"</string>
- <string name="reading_vcard_failed_title" msgid="4923008144735294994">"Не удается считать данные vCard"</string>
- <string name="reading_vcard_contacts" msgid="3066834102042012868">"Контакт <xliff:g id="CURRENT_NUMBER">%1$s</xliff:g> из <xliff:g id="TOTAL_NUMBER">%2$s</xliff:g>"</string>
- <string name="reading_vcard_files" msgid="34180143726972661">"Файл <xliff:g id="CURRENT_NUMBER">%1$s</xliff:g> из <xliff:g id="TOTAL_NUMBER">%2$s</xliff:g>"</string>
+ <string name="caching_vcard_title" msgid="5009556022082659780">"Кэширование файлов vCard в локальное временное хранилище"</string>
+ <string name="caching_vcard_message" msgid="2380844718093378900">"Импортер кэширует файлы vCard в локальное временное хранилище. Фактический импорт начнется в ближайшее время."</string>
+ <string name="progress_notifier_message" msgid="8412196553907067659">"<xliff:g id="CURRENT_NUMBER">%s</xliff:g>/<xliff:g id="TOTAL_NUMBER">%s</xliff:g>: <xliff:g id="FILENAME">%s</xliff:g>"</string>
+ <string name="reading_vcard_title" msgid="3324988724123717025">"Чтение файлов vCard"</string>
+ <string name="reading_vcard_failed_title" msgid="2162610359561887043">"Не удалось прочитать данные из файла vCard"</string>
+ <string name="reading_vcard_canceled_title" msgid="1770608329958463131">"Чтение данных vCard отменено"</string>
+ <string name="importing_vcard_finished_title" msgid="3213257229785702182">"Импорт файлов vCard завершен"</string>
+ <string name="vcard_importer_start_message" msgid="5520549352987753320">"Импорт файлов vCard запущен."</string>
+ <string name="vcard_importer_will_start_message" msgid="2055145402481822160">"Импортер vCard начнет передачу файлов в ближайшее время."</string>
+ <string name="percentage" msgid="34897865327092209">"%s%%"</string>
<string name="confirm_export_title" msgid="7648747763127442983">"Подтверждение·экспорта"</string>
<string name="confirm_export_message" msgid="3875683519257829750">"Вы действительно хотите экспортировать контакты в файл <xliff:g id="VCARD_FILENAME">%s</xliff:g>?"</string>
<string name="exporting_contact_failed_title" msgid="585823094820602526">"Не удалось экспортировать данные контакта."</string>
@@ -236,6 +244,8 @@
<string name="fail_reason_no_exportable_contact" msgid="4919714086648344495">"Нет контактов, которые можно экспортировать."</string>
<string name="fail_reason_too_many_vcard" msgid="7084146295639672658">"Слишком много файлов VCard на SD-карте."</string>
<string name="fail_reason_too_long_filename" msgid="1915716071321839166">"Название файла слишком длинное (<xliff:g id="FILENAME">%s</xliff:g>)."</string>
+ <string name="vcard_exporter_start_message" msgid="6497218576519292369">"Экспорт файлов vCard запущен."</string>
+ <string name="exporting_vcard_finished_title" msgid="1268425436375550862">"Экспорт файлов vCard завершен"</string>
<string name="exporting_contact_list_title" msgid="9072240631534457415">"Экспорт данных контакта"</string>
<string name="exporting_contact_list_message" msgid="5640326540405486055">"Экспорт данных контакта в \"<xliff:g id="FILE_NAME">%s</xliff:g>\""</string>
<string name="fail_reason_could_not_initialize_exporter" msgid="4943708332700987376">"Не удается запустить функцию экспорта: \"<xliff:g id="EXACT_REASON">%s</xliff:g>\""</string>
@@ -243,8 +253,8 @@
<string name="composer_failed_to_get_database_infomation" msgid="3723109558155169053">"Не удалось получить информацию из базы данных."</string>
<string name="composer_has_no_exportable_contact" msgid="754734132189369094">"Нет контактов для экспорта. Если в вашем телефоне имеются контакты, возможно они защищены от экспорта оператором услуг."</string>
<string name="composer_not_initialized" msgid="8041534450748388843">"Редактор vCard запущен некорректно"</string>
- <string name="fail_reason_could_not_open_file" msgid="4013520943128739511">"Не удается открыть \"<xliff:g id="FILE_NAME">%1$s</xliff:g>\": <xliff:g id="EXACT_REASON">%2$s</xliff:g>"</string>
- <string name="exporting_contact_list_progress" msgid="560522409559101193">"Контакт <xliff:g id="CURRENT_NUMBER">%1$s</xliff:g> из <xliff:g id="TOTAL_NUMBER">%2$s</xliff:g>"</string>
+ <string name="fail_reason_could_not_open_file" msgid="4013520943128739511">"Не удается открыть \"<xliff:g id="FILE_NAME">%s</xliff:g>\": <xliff:g id="EXACT_REASON">%s</xliff:g>"</string>
+ <string name="exporting_contact_list_progress" msgid="560522409559101193">"Контакт <xliff:g id="CURRENT_NUMBER">%s</xliff:g> из <xliff:g id="TOTAL_NUMBER">%s</xliff:g>"</string>
<string name="search_settings_description" msgid="2675223022992445813">"Имена контактов"</string>
<string name="add_2sec_pause" msgid="9214012315201040129">"Добавить двухсекундную паузу"</string>
<string name="add_wait" msgid="3360818652790319634">"Добавить паузу"</string>
@@ -254,6 +264,7 @@
<string name="quickcontact_missing_app" msgid="4600366393134289038">"Отсутствует приложение для обработки этого действия"</string>
<string name="quickcontact_remember_choice" msgid="5964536411579749424">"Запомнить выбранное"</string>
<string name="quickcontact_missing_name" msgid="5590266114306996632">"Неизвестно"</string>
+ <string name="quickcontact_no_data" msgid="2098000859125253675">"Нет данных"</string>
<string name="menu_accounts" msgid="8499114602017077970">"Аккаунты"</string>
<string name="menu_import_export" msgid="3765725645491577190">"Импорт/Экспорт"</string>
<string name="dialog_import_export" msgid="4771877268244096596">"Импорт и экспорт контактов"</string>
@@ -359,6 +370,7 @@
<string name="name_phonetic_family" msgid="462095502140180305">"Фамилия (транскрипция)"</string>
<string name="account_type_format" msgid="718948015590343010">"Контакт <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
<string name="from_account_format" msgid="687567483928582084">"из <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
+ <string name="account_type_and_name" msgid="2876220035411512934">"Контакт <xliff:g id="SOURCE_0">%1$s</xliff:g> из источника <xliff:g id="SOURCE_1">%2$s</xliff:g>"</string>
<string name="use_photo_as_primary" msgid="8807110122951157246">"Использовать эту фотографию"</string>
<string name="contact_read_only" msgid="1203216914575723978">"Невозможно изменить контактную информацию <xliff:g id="SOURCE">%1$s</xliff:g> на этом устройстве."</string>
<string name="no_contact_details" msgid="6754415338321837001">"Отсутствует дополнительная информация об этом контакте."</string>
@@ -374,9 +386,42 @@
<string name="pick_photo" msgid="448886509158039462">"Выбрать фото из галереи"</string>
<string name="locale_change_in_progress" msgid="1124266507671178413">"Выполняется обновление списка контактов, чтобы изменение языка вступило в силу."\n\n"Подождите..."</string>
<string name="upgrade_in_progress" msgid="7530893673211750223">"Обновляется список контактов."\n\n"Подождите..."</string>
- <string name="upgrade_out_of_memory" msgid="492151063059824962">"Контакты обновляются. "\n\n" Для обновления требуется около <xliff:g id="SIZE_IN_MEGABYTES">%d</xliff:g> МБ внутренней памяти телефона."\n\n"Выберите один из следующих вариантов:"</string>
+ <string name="upgrade_out_of_memory" msgid="6713914687111678777">"Контакты обновляются. "\n\n" Для обновления требуется около <xliff:g id="SIZE_IN_MEGABYTES">%d</xliff:g> МБ внутренней памяти телефона."\n\n"Выберите один из следующих вариантов:"</string>
<string name="upgrade_out_of_memory_uninstall" msgid="1721798828992091432">"Удалить некоторые приложения"</string>
<string name="upgrade_out_of_memory_retry" msgid="8431289830472724609">"Повторить попытку обновления"</string>
<string name="search_results_for" msgid="8705490885073188513">"Результаты поиска для: <xliff:g id="QUERY">%s</xliff:g>"</string>
<string name="search_results_searching" msgid="7755623475227227314">"Поиск..."</string>
+ <string name="adding_recipients" msgid="5123647646892106631">"Загрузка…"</string>
+ <string name="menu_display_selected" msgid="6470001164297969034">"Показать выбранные"</string>
+ <string name="menu_display_all" msgid="8887488642609786198">"Показать все"</string>
+ <string name="menu_select_all" msgid="621719255150713545">"Выбрать все"</string>
+ <string name="menu_select_none" msgid="7093222469852132345">"Снять все выделения"</string>
+ <plurals name="multiple_picker_title">
+ <item quantity="one" msgid="4761009734586319101">"Выбран 1 получатель"</item>
+ <item quantity="other" msgid="4608837420986126229">"Выбрано получателей: <xliff:g id="COUNT">%d</xliff:g>"</item>
+ </plurals>
+ <string name="unknown_contacts_separator" msgid="2490675836664397214">"Неизвестные контакты"</string>
+ <string name="no_contacts_selected" msgid="5877803471037324613">"Контакты не выбраны."</string>
+ <string name="add_field" msgid="5257149039253569615">"Добавить информацию"</string>
+ <string name="contact_status_update_attribution" msgid="752179367353018597">"с помощью <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
+ <string name="contact_status_update_attribution_with_date" msgid="7358045508107825068">"<xliff:g id="DATE">%1$s</xliff:g> с помощью <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
+ <string name="description_edit" msgid="1601490950771217014">"изменить"</string>
+ <string name="description_star" msgid="2605854427360036550">"избранное"</string>
+ <string name="edit_contact" msgid="7529281274005689512">"Изменить контакт"</string>
+ <plurals name="merge_info">
+ <item quantity="one" msgid="148365587896371969">"не объединен"</item>
+ <item quantity="other" msgid="425683718017380845">"объединено из нескольких источников (<xliff:g id="COUNT">%0$d</xliff:g>)"</item>
+ </plurals>
+ <string name="edit_delete_rawcontact" msgid="5054644778227879148">"Удалить"</string>
+ <string name="edit_error_saving" msgid="2153949392245240166">"Ошибка при сохранении"</string>
+ <string name="edit_structured_editor_button" msgid="3830078388142808106">"..."</string>
+ <string name="local_invisible_directory" msgid="6046691709127661065">"Другое"</string>
+ <!-- no translation found for aggregation_suggestion_title:one (7676930148670768645) -->
+ <!-- no translation found for aggregation_suggestion_title:other (3744098467672876675) -->
+ <!-- no translation found for aggregation_suggestion_joined_title (5342738708069602301) -->
+ <skip />
+ <!-- no translation found for aggregation_suggestion_join_button (7248325487963514260) -->
+ <skip />
+ <!-- no translation found for aggregation_suggestion_view_button (8232061564083962522) -->
+ <skip />
</resources>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index dace27a..3ca77b0 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -31,6 +31,8 @@
<string name="viewContactTitle" msgid="7989394521836644384">"Kontaktuppgifter"</string>
<string name="viewContactDesription" msgid="214186610887547860">"Visa kontakt"</string>
<string name="editContactDescription" msgid="2947202828256214947">"Redigera kontakt"</string>
+ <string name="edit_email_caption" msgid="2401084399676154272">"E-postadress:"</string>
+ <string name="edit_email_type_caption" msgid="66331218163770698">"Typ:"</string>
<string name="insertContactDescription" msgid="4709878105452681987">"Skapa kontakt"</string>
<string name="searchHint" msgid="8482945356247760701">"Sök efter kontakter"</string>
<string name="menu_search" msgid="9147752853603483719">"Sök"</string>
@@ -123,13 +125,13 @@
</plurals>
<string name="listTotalAllContactsZero" msgid="6811347506748072822">"Det finns inga synliga kontakter"</string>
<plurals name="listFoundAllContacts">
- <item quantity="one" msgid="2830107332033967280">"En kontakt hittades"</item>
- <item quantity="other" msgid="7752927996850263152">"<xliff:g id="COUNT">%d</xliff:g> kontakter hittades"</item>
+ <item quantity="one" msgid="5517063038754171134">"1 hittades"</item>
+ <item quantity="other" msgid="3852668542926965042">"<xliff:g id="COUNT">%d</xliff:g> hittades"</item>
</plurals>
- <string name="listFoundAllContactsZero" msgid="5554368784319460828">"Kontakten hittades inte"</string>
+ <string name="listFoundAllContactsZero" msgid="777952841930508289">"Hittades inte"</string>
<plurals name="searchFoundContacts">
- <item quantity="one" msgid="916648718690661777">"1 kontakt"</item>
- <item quantity="other" msgid="5660384247071761844">"<xliff:g id="COUNT">%d</xliff:g> kontakter"</item>
+ <item quantity="one" msgid="4826918429708286628">"1 hittades"</item>
+ <item quantity="other" msgid="7988132539476575389">"<xliff:g id="COUNT">%d</xliff:g> hittades"</item>
</plurals>
<string name="contactsIconLabel" msgid="7666609097606552806">"Kontakter"</string>
<string name="contactsFavoritesLabel" msgid="8417039765586853670">"Favoriter"</string>
@@ -159,10 +161,10 @@
<string name="simContacts_title" msgid="27341688347689769">"Kontakter från SIM-kort"</string>
<string name="noContactsHelpTextWithSyncForCreateShortcut" msgid="801504710275614594">"Du har inga kontakter att visa. (Om du nyss lade till ett konto kan det ta några minuter innan kontakterna synkroniserats.)"</string>
<string name="noContactsHelpTextForCreateShortcut" msgid="3081286388667108335">"Du har inga kontakter att visa."</string>
- <string name="noContactsHelpText" msgid="6788487368878712350">"Det finns inga kontakter att visa."\n\n"Om du vill lägga till kontakter trycker du på "<font fgcolor="#ffffffff"><b>"Meny"</b></font>" och sedan på:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Konton"</b></font>" om du vill lägga till eller konfigurera ett konto med kontakter som kan synkroniseras till telefonen"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Ny kontakt"</b></font>" om du vill skapa en ny kontakt från grunden"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importera/exportera"</b></font>\n</li></string>
- <string name="noContactsHelpTextWithSync" msgid="3734101165712848179">"Det finns inga kontakter att visa (om du nyss har lagt till ett konto kan det ta några minuter att synkronisera kontakter)."\n\n"Om du vill lägga till kontakter trycker du på "<font fgcolor="#ffffffff"><b>"Meny"</b></font>" och sedan på:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Konton"</b></font>" om du vill lägga till eller konfigurera ett konto med kontakter som kan synkroniseras till telefonen"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Visningsalternativ"</b></font>" om du vill ändra vilka kontakter som är synliga"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Ny kontakt"</b></font>" om du vill skapa en ny kontakt från grunden"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importera/exportera"</b></font>\n</li></string>
- <string name="noContactsNoSimHelpText" msgid="6553845386917463292">"Det finns inga kontakter att visa."\n\n"Om du vill lägga till kontakter trycker du på "<font fgcolor="#ffffffff"><b>"Meny"</b></font>" och sedan på:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Konton"</b></font>" om du vill lägga till eller konfigurera ett konto med kontakter som kan synkroniseras till telefonen"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Ny kontakt"</b></font>" om du vill skapa en ny kontakt från grunden"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importera/exportera"</b></font>\n</li></string>
- <string name="noContactsNoSimHelpTextWithSync" msgid="1122296298361373488">"Det finns inga kontakter att visa (om du nyss har lagt till ett konto kan det ta några minuter att synkronisera kontakter)."\n\n"Om du vill lägga till kontakter trycker du på "<font fgcolor="#ffffffff"><b>"Meny"</b></font>" och sedan på:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Konton"</b></font>" om du vill lägga till eller konfigurera ett konto med kontakter som kan synkroniseras till telefonen"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Visningsalternativ"</b></font>" om du vill ändra vilka kontakter som är synliga"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Ny kontakt"</b></font>" om du vill skapa en ny kontakt från grunden"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importera/exportera"</b></font>\n</li></string>
+ <string name="noContactsHelpText" msgid="7633826236417884130">"Det finns inga kontakter att visa."\n\n"Om du vill lägga till kontakter trycker du på "<font fgcolor="#ffffffff"><b>"Meny"</b></font>" och sedan på:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Konton"</b></font>" om du vill lägga till eller konfigurera ett konto med kontakter som kan synkroniseras till telefonen"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Ny kontakt"</b></font>" om du vill skapa en ny kontakt från grunden"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importera/exportera"</b></font>" om du vill importera kontakter från SIM- eller SD-kortet"\n</li></string>
+ <string name="noContactsHelpTextWithSync" msgid="3017521127042216243">"Det finns inga kontakter att visa (om du nyss har lagt till ett konto kan det ta några minuter att synkronisera kontakter)."\n\n"Om du vill lägga till kontakter trycker du på "<font fgcolor="#ffffffff"><b>"Meny"</b></font>" och sedan på:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Konton"</b></font>" om du vill lägga till eller konfigurera ett konto med kontakter som kan synkroniseras till telefonen"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Visningsalternativ"</b></font>" om du vill ändra vilka kontakter som är synliga"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Ny kontakt"</b></font>" om du vill skapa en ny kontakt från grunden"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importera/exportera"</b></font>" om du vill importera kontakter från SIM-kortet eller SD-kortet"\n</li></string>
+ <string name="noContactsNoSimHelpText" msgid="467658807711582876">"Det finns inga kontakter att visa."\n\n"Om du vill lägga till kontakter trycker du på "<font fgcolor="#ffffffff"><b>"Meny"</b></font>" och sedan på:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Konton"</b></font>" om du vill lägga till eller konfigurera ett konto med kontakter som kan synkroniseras till telefonen"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Ny kontakt"</b></font>" om du vill skapa en ny kontakt från grunden"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importera/exportera"</b></font>" om du vill importera kontakter från SD-kortet"\n</li></string>
+ <string name="noContactsNoSimHelpTextWithSync" msgid="9040060730467973050">"Det finns inga kontakter att visa (om du nyss har lagt till ett konto kan det ta några minuter att synkronisera kontakter)."\n\n"Om du vill lägga till kontakter trycker du på "<font fgcolor="#ffffffff"><b>"Meny"</b></font>" och sedan på:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Konton"</b></font>" om du vill lägga till eller konfigurera ett konto med kontakter som kan synkroniseras till telefonen"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Visningsalternativ"</b></font>" om du vill ändra vilka kontakter som är synliga"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Ny kontakt"</b></font>" om du vill skapa en ny kontakt från grunden"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importera/exportera"</b></font>" om du vill importera kontakter från SD-kortet"\n</li></string>
<string name="noFavoritesHelpText" msgid="3744655776704833277">"Du har inte några favoriter."\n\n"Så här lägger du till kontakter i favoritlistan:"\n\n" "<li>"Tryck på fliken "<b>"Kontakter"</b>\n</li>" "\n<li>"Tryck på den kontakt du vill lägga till i favoriterna"\n</li>" "\n<li>"Tryck på stjärnan bredvid kontaktens namn"\n</li></string>
<string name="liveFolder_all_label" msgid="5961411940473276616">"Alla kontakter"</string>
<string name="liveFolder_favorites_label" msgid="2674341514070517105">"Stjärnmärkt"</string>
@@ -179,7 +181,7 @@
<string name="callBack" msgid="5498224409038809224">"Ring upp"</string>
<string name="callAgain" msgid="3197312117049874778">"Ring igen"</string>
<string name="returnCall" msgid="8171961914203617813">"Ring upp"</string>
- <string name="callDetailsDurationFormat" msgid="8157706382818184268">"<xliff:g id="MINUTES">%1$s</xliff:g> minuter <xliff:g id="SECONDS">%2$s</xliff:g> sekunder"</string>
+ <string name="callDetailsDurationFormat" msgid="8157706382818184268">"<xliff:g id="MINUTES">%s</xliff:g> minuter <xliff:g id="SECONDS">%s</xliff:g> sekunder"</string>
<string name="favoritesFrquentSeparator" msgid="8107518433381283736">"Ofta kontaktade"</string>
<string name="add_contact_dlg_title" msgid="2896685845822146494">"Lägg till kontakt"</string>
<string name="add_contact_dlg_message_fmt" msgid="7986472669444326576">"Vill du lägga till <xliff:g id="EMAIL">%s</xliff:g> i Kontakter?"</string>
@@ -217,18 +219,24 @@
<string name="scanning_sdcard_failed_title" msgid="3506782007953167180">"Det gick inte att läsa SD-kort"</string>
<string name="scanning_sdcard_failed_message" msgid="3761992500690182922">"Det gick inte att skanna SD-kortet (orsak: <xliff:g id="FAIL_REASON">%s</xliff:g>)"</string>
<string name="fail_reason_io_error" msgid="5922864781066136340">"I/O-fel"</string>
+ <string name="fail_reason_low_memory_during_import" msgid="7514918659342886381">"Det finns för lite minne (filen kan vara för stor)"</string>
<string name="fail_reason_vcard_parse_error" msgid="1201233722762680214">"Det gick inte att analysera vCard av okänd anledning"</string>
<string name="fail_reason_vcard_not_supported_error" msgid="655208100451286027">"Det gick inte att analysera vCard trots att formatet verkar vara giltigt. Den aktuella implementeringen stöder inte det."</string>
<string name="fail_reason_no_vcard_file" msgid="6376516175882881595">"Ingen vCard-fil hittades på SD-kortet"</string>
+ <string name="fail_reason_failed_to_collect_vcard_meta_info" msgid="4154492282316067754">"Det går inte att samla in metainformation för den eller de givna vCard-filerna."</string>
<string name="fail_reason_failed_to_read_files" msgid="3659521123567134029">"En eller flera filer kunde inte importeras: (%s)."</string>
<string name="fail_reason_unknown" msgid="999034019513096768">"Okänt fel"</string>
<string name="select_vcard_title" msgid="3968948173786172468">"Välj vCard-fil"</string>
- <string name="progress_shower_message" msgid="5636525578293752526">"<xliff:g id="ACTION">%1$s</xliff:g>"\n"<xliff:g id="FILENAME">%2$s</xliff:g>"</string>
- <string name="reading_vcard_title" msgid="4723433501579653199">"Läser vCard"</string>
- <string name="reading_vcard_message" msgid="6381368920030748743">"Läser vCard-filer"</string>
- <string name="reading_vcard_failed_title" msgid="4923008144735294994">"Det gick inte att läsa vCard-data"</string>
- <string name="reading_vcard_contacts" msgid="3066834102042012868">"<xliff:g id="CURRENT_NUMBER">%1$s</xliff:g> av <xliff:g id="TOTAL_NUMBER">%2$s</xliff:g> kontakter"</string>
- <string name="reading_vcard_files" msgid="34180143726972661">"<xliff:g id="CURRENT_NUMBER">%1$s</xliff:g> av <xliff:g id="TOTAL_NUMBER">%2$s</xliff:g> filer"</string>
+ <string name="caching_vcard_title" msgid="5009556022082659780">"Cachelagrar vCard i lokalt tillfälligt utrymme"</string>
+ <string name="caching_vcard_message" msgid="2380844718093378900">"Importfunktionen cachelagrar vCard-fil(er) i lokalt tillfälligt utrymme. Den faktiska importen börjar snart."</string>
+ <string name="progress_notifier_message" msgid="8412196553907067659">"<xliff:g id="CURRENT_NUMBER">%s</xliff:g>/<xliff:g id="TOTAL_NUMBER">%s</xliff:g>: <xliff:g id="FILENAME">%s</xliff:g>"</string>
+ <string name="reading_vcard_title" msgid="3324988724123717025">"Läser vCard"</string>
+ <string name="reading_vcard_failed_title" msgid="2162610359561887043">"Det går inte att läsa vCard-data"</string>
+ <string name="reading_vcard_canceled_title" msgid="1770608329958463131">"Läsning av vCard-data avbröts"</string>
+ <string name="importing_vcard_finished_title" msgid="3213257229785702182">"vCard-importen har slutförts"</string>
+ <string name="vcard_importer_start_message" msgid="5520549352987753320">"vCard-import har startat."</string>
+ <string name="vcard_importer_will_start_message" msgid="2055145402481822160">"Importfunktionen för vCard importerar vCard-filen efter en stund."</string>
+ <string name="percentage" msgid="34897865327092209">"%s%%"</string>
<string name="confirm_export_title" msgid="7648747763127442983">"Bekräfta export"</string>
<string name="confirm_export_message" msgid="3875683519257829750">"Vill du exportera kontaktlistan till \"<xliff:g id="VCARD_FILENAME">%s</xliff:g>\"?"</string>
<string name="exporting_contact_failed_title" msgid="585823094820602526">"Det gick inte att exportera kontaktdata"</string>
@@ -236,6 +244,8 @@
<string name="fail_reason_no_exportable_contact" msgid="4919714086648344495">"Det finns ingen kontakt som kan exporteras"</string>
<string name="fail_reason_too_many_vcard" msgid="7084146295639672658">"För många vCard-filer på SD-kortet"</string>
<string name="fail_reason_too_long_filename" msgid="1915716071321839166">"Det obligatoriska filnamnet är för långt (\"<xliff:g id="FILENAME">%s</xliff:g>\")"</string>
+ <string name="vcard_exporter_start_message" msgid="6497218576519292369">"vCard-export har startat."</string>
+ <string name="exporting_vcard_finished_title" msgid="1268425436375550862">"vCard-exporten har slutförts"</string>
<string name="exporting_contact_list_title" msgid="9072240631534457415">"Exporterar kontaktuppgifter"</string>
<string name="exporting_contact_list_message" msgid="5640326540405486055">"Exporterar kontaktuppgifter till: <xliff:g id="FILE_NAME">%s</xliff:g>"</string>
<string name="fail_reason_could_not_initialize_exporter" msgid="4943708332700987376">"Det gick inte att starta exportverktyget: <xliff:g id="EXACT_REASON">%s</xliff:g>"</string>
@@ -243,8 +253,8 @@
<string name="composer_failed_to_get_database_infomation" msgid="3723109558155169053">"Det gick inte att hämta databasinformation"</string>
<string name="composer_has_no_exportable_contact" msgid="754734132189369094">"Det finns ingen kontakt som kan exporteras. Om du vet att du har kontakter i din telefon kan problemet vara att en leverantör förhindrar att kontakterna exporteras från telefonen."</string>
<string name="composer_not_initialized" msgid="8041534450748388843">"vCard-kompositören är inte korrekt initierad"</string>
- <string name="fail_reason_could_not_open_file" msgid="4013520943128739511">"Det gick inte att öppna <xliff:g id="FILE_NAME">%1$s</xliff:g>: <xliff:g id="EXACT_REASON">%2$s</xliff:g>"</string>
- <string name="exporting_contact_list_progress" msgid="560522409559101193">"<xliff:g id="CURRENT_NUMBER">%1$s</xliff:g> av <xliff:g id="TOTAL_NUMBER">%2$s</xliff:g> kontakter"</string>
+ <string name="fail_reason_could_not_open_file" msgid="4013520943128739511">"Det gick inte att öppna <xliff:g id="FILE_NAME">%s</xliff:g>: <xliff:g id="EXACT_REASON">%s</xliff:g>"</string>
+ <string name="exporting_contact_list_progress" msgid="560522409559101193">"<xliff:g id="CURRENT_NUMBER">%s</xliff:g> av <xliff:g id="TOTAL_NUMBER">%s</xliff:g> kontakter"</string>
<string name="search_settings_description" msgid="2675223022992445813">"Dina kontakters namn"</string>
<string name="add_2sec_pause" msgid="9214012315201040129">"Lägg till en paus på 2 sek."</string>
<string name="add_wait" msgid="3360818652790319634">"Lägg till väntetid"</string>
@@ -254,6 +264,7 @@
<string name="quickcontact_missing_app" msgid="4600366393134289038">"Inget program som kan hantera åtgärden hittades"</string>
<string name="quickcontact_remember_choice" msgid="5964536411579749424">"Kom ihåg det här valet"</string>
<string name="quickcontact_missing_name" msgid="5590266114306996632">"Okänd"</string>
+ <string name="quickcontact_no_data" msgid="2098000859125253675">"Inga data"</string>
<string name="menu_accounts" msgid="8499114602017077970">"Konton"</string>
<string name="menu_import_export" msgid="3765725645491577190">"Importera/exportera"</string>
<string name="dialog_import_export" msgid="4771877268244096596">"Importera/exportera kontakter"</string>
@@ -359,6 +370,7 @@
<string name="name_phonetic_family" msgid="462095502140180305">"Fonetiskt efternamn"</string>
<string name="account_type_format" msgid="718948015590343010">"<xliff:g id="SOURCE">%1$s</xliff:g>-kontakt"</string>
<string name="from_account_format" msgid="687567483928582084">"från <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
+ <string name="account_type_and_name" msgid="2876220035411512934">"<xliff:g id="SOURCE_0">%1$s</xliff:g> kontakt från <xliff:g id="SOURCE_1">%2$s</xliff:g>"</string>
<string name="use_photo_as_primary" msgid="8807110122951157246">"Använd det här fotot"</string>
<string name="contact_read_only" msgid="1203216914575723978">"Kontaktinformation för <xliff:g id="SOURCE">%1$s</xliff:g> kan inte redigeras i den här enheten."</string>
<string name="no_contact_details" msgid="6754415338321837001">"Det finns ingen mer information för kontakten"</string>
@@ -374,9 +386,42 @@
<string name="pick_photo" msgid="448886509158039462">"Välj ett foto från galleriet"</string>
<string name="locale_change_in_progress" msgid="1124266507671178413">"Kontaktlistan uppdateras så att språkändringen visas."\n\n"Vänta..."</string>
<string name="upgrade_in_progress" msgid="7530893673211750223">"Kontaktlistan uppdateras."\n\n"Vänta..."</string>
- <string name="upgrade_out_of_memory" msgid="492151063059824962">"Kontakter håller på att uppgraderas. "\n\n"Uppgraderingen kräver ungefär <xliff:g id="SIZE_IN_MEGABYTES">%d</xliff:g>MB av telefonens internminne."\n\n"Välj något av följande alternativ:"</string>
+ <string name="upgrade_out_of_memory" msgid="6713914687111678777">"Kontakter håller på att uppgraderas. "\n\n"Uppgraderingen kräver ungefär <xliff:g id="SIZE_IN_MEGABYTES">%d</xliff:g>MB av telefonens internminne."\n\n"Välj något av följande alternativ:"</string>
<string name="upgrade_out_of_memory_uninstall" msgid="1721798828992091432">"Avinstallera några program"</string>
<string name="upgrade_out_of_memory_retry" msgid="8431289830472724609">"Försök uppgradera igen"</string>
<string name="search_results_for" msgid="8705490885073188513">"Sökresultat för: <xliff:g id="QUERY">%s</xliff:g>"</string>
<string name="search_results_searching" msgid="7755623475227227314">"Söker..."</string>
+ <string name="adding_recipients" msgid="5123647646892106631">"Läser in …"</string>
+ <string name="menu_display_selected" msgid="6470001164297969034">"Visa markerade"</string>
+ <string name="menu_display_all" msgid="8887488642609786198">"Visa alla"</string>
+ <string name="menu_select_all" msgid="621719255150713545">"Markera alla"</string>
+ <string name="menu_select_none" msgid="7093222469852132345">"Avmarkera alla"</string>
+ <plurals name="multiple_picker_title">
+ <item quantity="one" msgid="4761009734586319101">"1 mottagare har markerats"</item>
+ <item quantity="other" msgid="4608837420986126229">"<xliff:g id="COUNT">%d</xliff:g> mottagare har markerats"</item>
+ </plurals>
+ <string name="unknown_contacts_separator" msgid="2490675836664397214">"Okända kontakter"</string>
+ <string name="no_contacts_selected" msgid="5877803471037324613">"Inga kontakter har markerats."</string>
+ <string name="add_field" msgid="5257149039253569615">"Lägg till information"</string>
+ <string name="contact_status_update_attribution" msgid="752179367353018597">"via <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
+ <string name="contact_status_update_attribution_with_date" msgid="7358045508107825068">"<xliff:g id="DATE">%1$s</xliff:g> via <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
+ <string name="description_edit" msgid="1601490950771217014">"redigera"</string>
+ <string name="description_star" msgid="2605854427360036550">"favorit"</string>
+ <string name="edit_contact" msgid="7529281274005689512">"Redigera kontakt"</string>
+ <plurals name="merge_info">
+ <item quantity="one" msgid="148365587896371969">"inte kombinerade"</item>
+ <item quantity="other" msgid="425683718017380845">"kombinerade från <xliff:g id="COUNT">%0$d</xliff:g> källor"</item>
+ </plurals>
+ <string name="edit_delete_rawcontact" msgid="5054644778227879148">"Ta bort"</string>
+ <string name="edit_error_saving" msgid="2153949392245240166">"Det gick inte att spara"</string>
+ <string name="edit_structured_editor_button" msgid="3830078388142808106">"..."</string>
+ <string name="local_invisible_directory" msgid="6046691709127661065">"Övrigt"</string>
+ <!-- no translation found for aggregation_suggestion_title:one (7676930148670768645) -->
+ <!-- no translation found for aggregation_suggestion_title:other (3744098467672876675) -->
+ <!-- no translation found for aggregation_suggestion_joined_title (5342738708069602301) -->
+ <skip />
+ <!-- no translation found for aggregation_suggestion_join_button (7248325487963514260) -->
+ <skip />
+ <!-- no translation found for aggregation_suggestion_view_button (8232061564083962522) -->
+ <skip />
</resources>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index f169128..eb22281 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -31,6 +31,8 @@
<string name="viewContactTitle" msgid="7989394521836644384">"Kişi ayrıntıları"</string>
<string name="viewContactDesription" msgid="214186610887547860">"Kişiyi görüntüle"</string>
<string name="editContactDescription" msgid="2947202828256214947">"Kişiyi düzenle"</string>
+ <string name="edit_email_caption" msgid="2401084399676154272">"E-posta adresi:"</string>
+ <string name="edit_email_type_caption" msgid="66331218163770698">"Tür: "</string>
<string name="insertContactDescription" msgid="4709878105452681987">"Kişi oluştur"</string>
<string name="searchHint" msgid="8482945356247760701">"Kişileri ara"</string>
<string name="menu_search" msgid="9147752853603483719">"Ara"</string>
@@ -123,13 +125,13 @@
</plurals>
<string name="listTotalAllContactsZero" msgid="6811347506748072822">"Görülebilir kişi yok"</string>
<plurals name="listFoundAllContacts">
- <item quantity="one" msgid="2830107332033967280">"1 kişi bulundu"</item>
- <item quantity="other" msgid="7752927996850263152">"<xliff:g id="COUNT">%d</xliff:g> kişi bulundu"</item>
+ <item quantity="one" msgid="5517063038754171134">"1 kişi bulundu"</item>
+ <item quantity="other" msgid="3852668542926965042">"<xliff:g id="COUNT">%d</xliff:g> kişi bulundu"</item>
</plurals>
- <string name="listFoundAllContactsZero" msgid="5554368784319460828">"Kişi bulunamadı"</string>
+ <string name="listFoundAllContactsZero" msgid="777952841930508289">"Bulunamadı"</string>
<plurals name="searchFoundContacts">
- <item quantity="one" msgid="916648718690661777">"1 kişi"</item>
- <item quantity="other" msgid="5660384247071761844">"<xliff:g id="COUNT">%d</xliff:g> kişi"</item>
+ <item quantity="one" msgid="4826918429708286628">"1 kişi bulundu"</item>
+ <item quantity="other" msgid="7988132539476575389">"<xliff:g id="COUNT">%d</xliff:g> kişi bulundu"</item>
</plurals>
<string name="contactsIconLabel" msgid="7666609097606552806">"Kişiler"</string>
<string name="contactsFavoritesLabel" msgid="8417039765586853670">"Sık Kullanılanlar"</string>
@@ -159,10 +161,10 @@
<string name="simContacts_title" msgid="27341688347689769">"SIM kart kişileri"</string>
<string name="noContactsHelpTextWithSyncForCreateShortcut" msgid="801504710275614594">"Görüntülenecek kişiniz yok. (Hesabı yeni eklediyseniz, kişilerin senkronize edilmesi birkaç dakika sürebilir.)"</string>
<string name="noContactsHelpTextForCreateShortcut" msgid="3081286388667108335">"Görüntülenecek kişiniz yok."</string>
- <string name="noContactsHelpText" msgid="6788487368878712350">"Görüntülenecek hiçbir kişiniz yok."\n\n"Kişi eklemek için, "<font fgcolor="#ffffffff"><b>"Menü"</b></font>"\'ye basın ve şunlara dokunun:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Hesaplar"</b></font>" (telefon ile senkronize edebileceğiniz kişilere sahip olan bir hesap eklemek veya yapılandırmak için)"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Yeni kişi"</b></font>" (en baştan yeni bir kişi eklemek için)"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"İçe/Dışa Aktar"</b></font>\n</li></string>
- <string name="noContactsHelpTextWithSync" msgid="3734101165712848179">"Görüntülenecek hiçbir kişiniz yok. (Kısa süre önce bir hesap eklediyseniz, kişileri senkronize etmek birkaç dakika sürebilir.)"\n\n"Kişi eklemek için, "<font fgcolor="#ffffffff"><b>"Menü"</b></font>"\'ye basın ve şunlara dokunun:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Hesaplar"</b></font>" (telefon ile senkronize edebileceğiniz kişilere sahip olan bir hesap eklemek veya yapılandırmak için)"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Seçenekleri görüntüle"</b></font>" (görülebilir kişileri değiştirmek için)"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Yeni kişi"</b></font>" (en baştan yeni bir kişi oluşturmak için)"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"İçe/Dışa Aktar"</b></font>\n</li></string>
- <string name="noContactsNoSimHelpText" msgid="6553845386917463292">"Görüntülenecek hiçbir kişiniz yok."\n\n"Kişi eklemek için, "<font fgcolor="#ffffffff"><b>"Menü"</b></font>"\'ye basın ve şunlara dokunun:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Hesaplar"</b></font>" (telefon ile senkronize edebileceğiniz kişilere sahip olan bir hesap eklemek veya yapılandırmak için)"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Yeni kişi"</b></font>" (en baştan yeni bir kişi eklemek için)"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"İçe/Dışa Aktar"</b></font>\n</li></string>
- <string name="noContactsNoSimHelpTextWithSync" msgid="1122296298361373488">"Görüntülenecek hiçbir kişiniz yok. (Kısa süre önce bir hesap eklediyseniz, kişileri senkronize etmek birkaç dakika sürebilir.)"\n\n"Kişi eklemek için, "<font fgcolor="#ffffffff"><b>"Menü"</b></font>"\'ye basın ve şunlara dokunun:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Hesaplar"</b></font>" (telefon ile senkronize edebileceğiniz kişilere sahip olan bir hesap eklemek veya yapılandırmak için)"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Seçenekleri görüntüle"</b></font>" (görülebilir kişileri değiştirmek için)"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Yeni kişi"</b></font>" (en baştan yeni bir kişi oluşturmak için)"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"İçe/Dışa Aktar"</b></font>\n</li></string>
+ <string name="noContactsHelpText" msgid="7633826236417884130">"Görüntülenecek kişiniz yok."\n\n"Kişi eklemek için, "<font fgcolor="#ffffffff"><b>"Menü"</b></font>"\'ye basın ve şunlara dokunun:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Hesaplar"</b></font>" (telefon ile senkronize edebileceğiniz kişilere sahip bir hesap eklemek veya yapılandırmak için)"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Yeni kişi"</b></font>" (en baştan yeni bir kişi eklemek için)"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"İçe/Dışa Aktar"</b></font>\n</li>"(SIM veya SD kartınızdaki kişileri aktarmak için)"</string>
+ <string name="noContactsHelpTextWithSync" msgid="3017521127042216243">"Görüntülenecek kişiniz yok. (Kısa bir süre önce hesap eklediyseniz, kişileri senkronize etmek birkaç dakika sürebilir.)"\n\n"Kişi eklemek için, "<font fgcolor="#ffffffff"><b>"Menü"</b></font>"\'ye basın ve şunlara dokunun:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Hesaplar"</b></font>" (telefon ile senkronize edebileceğiniz kişilere sahip olan bir hesap eklemek veya yapılandırmak için"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Seçenekleri görüntüle"</b></font>" (görülebilir kişileri değiştirmek için)"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Yeni kişi"</b></font>" (en baştan yeni bir kişi oluşturmak için)"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"İçe/Dışa Aktar"</b></font>" (SIM veya SD kartınızdaki kişileri aktarmak için)"\n</li></string>
+ <string name="noContactsNoSimHelpText" msgid="467658807711582876">"Görüntülenecek kişiniz yok."\n\n"Kişi eklemek için, "<font fgcolor="#ffffffff"><b>"Menü"</b></font>"\'ye basın ve şunlara dokunun:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Hesaplar"</b></font>" (telefon ile senkronize edebileceğiniz kişilere sahip olan bir hesap eklemek veya yapılandırmak için)"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Yeni kişi"</b></font>" (en baştan yeni bir kişi eklemek için)"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"İçe/Dışa Aktar"</b></font>" (kişileri SD kartınızdan içe aktarmak için)"\n</li></string>
+ <string name="noContactsNoSimHelpTextWithSync" msgid="9040060730467973050">"Görüntülenecek kişiniz yok. (Kısa süre önce bir hesap eklediyseniz kişileri senkronize etmek birkaç dakika sürebilir.)"\n\n"Kişi eklemek için, "<font fgcolor="#ffffffff"><b>"Menü"</b></font>"\'ye basın ve şunlara dokunun:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Hesaplar"</b></font>" (telefon ile senkronize edebileceğiniz kişilere sahip bir hesap eklemek veya yapılandırmak için)"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Seçenekleri görüntüle"</b></font>" (görülebilir kişileri değiştirmek için)"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Yeni kişi"</b></font>" (en baştan yeni bir kişi oluşturmak için)"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"İçe/Dışa Aktar"</b></font>\n</li>"(SIM veya SD kartınızdaki kişileri içe aktarmak için)"</string>
<string name="noFavoritesHelpText" msgid="3744655776704833277">"Favoriniz bulunmuyor."\n\n"Favoriler listenize kişi eklemek için:"\n\n" "<li><b>"Kişiler"</b>" sekmesine dokunun"\n</li>" "\n<li>"Favorilerinize eklemek istediğiniz kişiye dokunun"\n</li>" "\n<li>"Kişi adının yanındaki yıldız simgesine dokunun"\n</li></string>
<string name="liveFolder_all_label" msgid="5961411940473276616">"Tüm kişiler"</string>
<string name="liveFolder_favorites_label" msgid="2674341514070517105">"Yıldızlı"</string>
@@ -179,7 +181,7 @@
<string name="callBack" msgid="5498224409038809224">"Geri ara"</string>
<string name="callAgain" msgid="3197312117049874778">"Tekrar çağrı yap"</string>
<string name="returnCall" msgid="8171961914203617813">"Geri ara"</string>
- <string name="callDetailsDurationFormat" msgid="8157706382818184268">"<xliff:g id="MINUTES">%1$s</xliff:g> dak <xliff:g id="SECONDS">%2$s</xliff:g> sn"</string>
+ <string name="callDetailsDurationFormat" msgid="8157706382818184268">"<xliff:g id="MINUTES">%s</xliff:g> dak <xliff:g id="SECONDS">%s</xliff:g> sn"</string>
<string name="favoritesFrquentSeparator" msgid="8107518433381283736">"Sık iletişim kurulanlar"</string>
<string name="add_contact_dlg_title" msgid="2896685845822146494">"Kişi ekle"</string>
<string name="add_contact_dlg_message_fmt" msgid="7986472669444326576">"\"<xliff:g id="EMAIL">%s</xliff:g>\" adresi kişilere eklensin mi?"</string>
@@ -217,18 +219,24 @@
<string name="scanning_sdcard_failed_title" msgid="3506782007953167180">"SD kart taranamadı"</string>
<string name="scanning_sdcard_failed_message" msgid="3761992500690182922">"SD kart taraması başarısız oldu (Neden: \"<xliff:g id="FAIL_REASON">%s</xliff:g>\")"</string>
<string name="fail_reason_io_error" msgid="5922864781066136340">"G/Ç Hatası"</string>
+ <string name="fail_reason_low_memory_during_import" msgid="7514918659342886381">"Bellek yetersiz (dosya çok büyük olabilir)"</string>
<string name="fail_reason_vcard_parse_error" msgid="1201233722762680214">"Beklenmedik bir nedenden dolayı vCard ayrıştırılamadı"</string>
<string name="fail_reason_vcard_not_supported_error" msgid="655208100451286027">"Geçerli biçimde görünmesine rağmen vCard ayrıştırılamadı, çünkü şu anki uygulama vCard\'ı desteklemiyor"</string>
<string name="fail_reason_no_vcard_file" msgid="6376516175882881595">"SD kartta hiçbir vCard dosyası bulunamadı"</string>
+ <string name="fail_reason_failed_to_collect_vcard_meta_info" msgid="4154492282316067754">"Belirtilen vCard dosyalarının meta bilgileri toplanamıyor."</string>
<string name="fail_reason_failed_to_read_files" msgid="3659521123567134029">"İçe aktarılamayan bir veya birden fazla dosya (%s)."</string>
<string name="fail_reason_unknown" msgid="999034019513096768">"Bilinmeyen hata"</string>
<string name="select_vcard_title" msgid="3968948173786172468">"vCard dosyasını seç"</string>
- <string name="progress_shower_message" msgid="5636525578293752526">"<xliff:g id="ACTION">%1$s</xliff:g>"\n"<xliff:g id="FILENAME">%2$s</xliff:g>"</string>
- <string name="reading_vcard_title" msgid="4723433501579653199">"vCard okunuyor"</string>
- <string name="reading_vcard_message" msgid="6381368920030748743">"vCard dosyaları okunuyor"</string>
- <string name="reading_vcard_failed_title" msgid="4923008144735294994">"vCard verileri okunamadı"</string>
- <string name="reading_vcard_contacts" msgid="3066834102042012868">"Toplam <xliff:g id="TOTAL_NUMBER">%2$s</xliff:g> kişiden <xliff:g id="CURRENT_NUMBER">%1$s</xliff:g> kişi"</string>
- <string name="reading_vcard_files" msgid="34180143726972661">"toplam <xliff:g id="TOTAL_NUMBER">%2$s</xliff:g> dosyadan <xliff:g id="CURRENT_NUMBER">%1$s</xliff:g>. dosya"</string>
+ <string name="caching_vcard_title" msgid="5009556022082659780">"vCard\'lar yerel geçici depolamada önbelleğe kaydediliyor"</string>
+ <string name="caching_vcard_message" msgid="2380844718093378900">"İçe aktarma aracı vCard\'ları yerel geçici depolama alanına önbelleğe kaydediyor. Asıl içe aktarma işlemi kısa süre içinde başlayacak."</string>
+ <string name="progress_notifier_message" msgid="8412196553907067659">"<xliff:g id="CURRENT_NUMBER">%s</xliff:g>/<xliff:g id="TOTAL_NUMBER">%s</xliff:g>: <xliff:g id="FILENAME">%s</xliff:g>"</string>
+ <string name="reading_vcard_title" msgid="3324988724123717025">"vCard\'lar Okunuyor"</string>
+ <string name="reading_vcard_failed_title" msgid="2162610359561887043">"vCard verileri Okunamadı"</string>
+ <string name="reading_vcard_canceled_title" msgid="1770608329958463131">"vCard verilerini okuma işlemi iptal edildi."</string>
+ <string name="importing_vcard_finished_title" msgid="3213257229785702182">"vCard içe aktarma işlemi tamamlandı"</string>
+ <string name="vcard_importer_start_message" msgid="5520549352987753320">"vCard içe aktarma aracı başlatıldı."</string>
+ <string name="vcard_importer_will_start_message" msgid="2055145402481822160">"vCard içe aktarma aracı vCard\'ı bir süre sonra içe aktaracaktır."</string>
+ <string name="percentage" msgid="34897865327092209">"%s%%"</string>
<string name="confirm_export_title" msgid="7648747763127442983">"Dışa aktarımı onayla"</string>
<string name="confirm_export_message" msgid="3875683519257829750">"Kişi listenizi \"<xliff:g id="VCARD_FILENAME">%s</xliff:g>\" için dışa aktarmak istediğinizden emin misiniz?"</string>
<string name="exporting_contact_failed_title" msgid="585823094820602526">"Kişi verileri dışa aktarılamadı"</string>
@@ -236,6 +244,8 @@
<string name="fail_reason_no_exportable_contact" msgid="4919714086648344495">"Dışa aktarılabilen hiçbir kişi yok"</string>
<string name="fail_reason_too_many_vcard" msgid="7084146295639672658">"SD kartta çok fazla vCard dosyası var"</string>
<string name="fail_reason_too_long_filename" msgid="1915716071321839166">"Gereken dosya adı çok uzun (\"<xliff:g id="FILENAME">%s</xliff:g>\")"</string>
+ <string name="vcard_exporter_start_message" msgid="6497218576519292369">"vCard dışa aktarma aracı başlatıldı."</string>
+ <string name="exporting_vcard_finished_title" msgid="1268425436375550862">"vCard dışa aktarma işlemi tamamlandı"</string>
<string name="exporting_contact_list_title" msgid="9072240631534457415">"Kişi verileri dışa aktarılıyor"</string>
<string name="exporting_contact_list_message" msgid="5640326540405486055">"Kişi verileri \"<xliff:g id="FILE_NAME">%s</xliff:g>\" dosyasına dışa aktarılıyor"</string>
<string name="fail_reason_could_not_initialize_exporter" msgid="4943708332700987376">"Dışa aktarıcı başlatılamadı: \"<xliff:g id="EXACT_REASON">%s</xliff:g>\""</string>
@@ -243,8 +253,8 @@
<string name="composer_failed_to_get_database_infomation" msgid="3723109558155169053">"Veri tabanı bilgileri alınamadı"</string>
<string name="composer_has_no_exportable_contact" msgid="754734132189369094">"Dışa aktarılabilir kişi yok. Telefonunuzda aslında kişi kayıtları varsa, veri sağlayıcısı tarafından tüm kişilerin telefon dışına aktarılması yasaklanmış olabilir."</string>
<string name="composer_not_initialized" msgid="8041534450748388843">"vCard oluşturucu doğru bir şekilde başlatılmamış"</string>
- <string name="fail_reason_could_not_open_file" msgid="4013520943128739511">"\"<xliff:g id="FILE_NAME">%1$s</xliff:g>\" dosyası açılamadı: <xliff:g id="EXACT_REASON">%2$s</xliff:g>"</string>
- <string name="exporting_contact_list_progress" msgid="560522409559101193">"Toplam <xliff:g id="TOTAL_NUMBER">%2$s</xliff:g> kişiden <xliff:g id="CURRENT_NUMBER">%1$s</xliff:g> kişi"</string>
+ <string name="fail_reason_could_not_open_file" msgid="4013520943128739511">"\"<xliff:g id="FILE_NAME">%s</xliff:g>\" dosyası açılamadı: <xliff:g id="EXACT_REASON">%s</xliff:g>"</string>
+ <string name="exporting_contact_list_progress" msgid="560522409559101193">"Toplam <xliff:g id="TOTAL_NUMBER">%s</xliff:g> kişiden <xliff:g id="CURRENT_NUMBER">%s</xliff:g> kişi"</string>
<string name="search_settings_description" msgid="2675223022992445813">"Kişilerinizin adları"</string>
<string name="add_2sec_pause" msgid="9214012315201040129">"2 saniyelik duraklama ekle"</string>
<string name="add_wait" msgid="3360818652790319634">"Bekleme ekle"</string>
@@ -254,6 +264,7 @@
<string name="quickcontact_missing_app" msgid="4600366393134289038">"Bu işlemi gerçekleştirecek bir uygulama bulunamadı"</string>
<string name="quickcontact_remember_choice" msgid="5964536411579749424">"Bu tercihi anımsa"</string>
<string name="quickcontact_missing_name" msgid="5590266114306996632">"Bilinmiyor"</string>
+ <string name="quickcontact_no_data" msgid="2098000859125253675">"Veri yok"</string>
<string name="menu_accounts" msgid="8499114602017077970">"Hesaplar"</string>
<string name="menu_import_export" msgid="3765725645491577190">"İçe/Dışa Aktar"</string>
<string name="dialog_import_export" msgid="4771877268244096596">"Kişileri içe/dışa aktar"</string>
@@ -359,6 +370,7 @@
<string name="name_phonetic_family" msgid="462095502140180305">"Fonetik soyadı"</string>
<string name="account_type_format" msgid="718948015590343010">"<xliff:g id="SOURCE">%1$s</xliff:g> kişi"</string>
<string name="from_account_format" msgid="687567483928582084">"kaynak: <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
+ <string name="account_type_and_name" msgid="2876220035411512934">"<xliff:g id="SOURCE_1">%2$s</xliff:g> kaynağına ait <xliff:g id="SOURCE_0">%1$s</xliff:g> kişisi"</string>
<string name="use_photo_as_primary" msgid="8807110122951157246">"Bu fotoğrafı kullan"</string>
<string name="contact_read_only" msgid="1203216914575723978">"<xliff:g id="SOURCE">%1$s</xliff:g> kişi bilgileri bu cihazda düzenlenemez."</string>
<string name="no_contact_details" msgid="6754415338321837001">"Bu kişi için ek bilgi yok"</string>
@@ -374,9 +386,42 @@
<string name="pick_photo" msgid="448886509158039462">"Galeri\'den fotoğraf seç"</string>
<string name="locale_change_in_progress" msgid="1124266507671178413">"Kişi listesi, dil değişikliğini yansıtmak üzere güncelleniyor."\n\n"Lütfen bekleyin..."</string>
<string name="upgrade_in_progress" msgid="7530893673211750223">"Kişi listesi güncelleniyor."\n\n"Lütfen bekleyin..."</string>
- <string name="upgrade_out_of_memory" msgid="492151063059824962">"Kişiler yeni sürüme geçiriliyor. "\n\n"Yeni sürüme geçirme işlemi yaklaşık <xliff:g id="SIZE_IN_MEGABYTES">%d</xliff:g>Mb dahili telefon depolama alanı gerektirir."\n\n"Aşağıdaki seçeneklerden birini belirleyin:"</string>
+ <string name="upgrade_out_of_memory" msgid="6713914687111678777">"Kişiler yeni sürüme geçiriliyor. "\n\n"Yeni sürüme geçirme işlemi yaklaşık <xliff:g id="SIZE_IN_MEGABYTES">%d</xliff:g> MB dahili telefon depolama alanı gerektirir."\n\n"Aşağıdaki seçeneklerden birini belirleyin:"</string>
<string name="upgrade_out_of_memory_uninstall" msgid="1721798828992091432">"Bazı uygulamaları kaldır"</string>
<string name="upgrade_out_of_memory_retry" msgid="8431289830472724609">"Yeni sürüme geçmeyi tekrar dene"</string>
<string name="search_results_for" msgid="8705490885073188513">"Şunun için arama sonuçları: <xliff:g id="QUERY">%s</xliff:g>"</string>
<string name="search_results_searching" msgid="7755623475227227314">"Aranıyor..."</string>
+ <string name="adding_recipients" msgid="5123647646892106631">"Yükleniyor …"</string>
+ <string name="menu_display_selected" msgid="6470001164297969034">"Seçileni göster"</string>
+ <string name="menu_display_all" msgid="8887488642609786198">"Tümünü göster\n"</string>
+ <string name="menu_select_all" msgid="621719255150713545">"Tümünü seç"</string>
+ <string name="menu_select_none" msgid="7093222469852132345">"Tümünün seçimini kaldır"</string>
+ <plurals name="multiple_picker_title">
+ <item quantity="one" msgid="4761009734586319101">"1 alıcı seçildi"</item>
+ <item quantity="other" msgid="4608837420986126229">"<xliff:g id="COUNT">%d</xliff:g> alıcı seçildi"</item>
+ </plurals>
+ <string name="unknown_contacts_separator" msgid="2490675836664397214">"Bilinmeyen kişiler"</string>
+ <string name="no_contacts_selected" msgid="5877803471037324613">"Seçili kişi yok."</string>
+ <string name="add_field" msgid="5257149039253569615">"Bilgi ekle"</string>
+ <string name="contact_status_update_attribution" msgid="752179367353018597">"<xliff:g id="SOURCE">%1$s</xliff:g> aracılığıyla"</string>
+ <string name="contact_status_update_attribution_with_date" msgid="7358045508107825068">"<xliff:g id="SOURCE">%2$s</xliff:g> üzerinden şu saatte: <xliff:g id="DATE">%1$s</xliff:g>"</string>
+ <string name="description_edit" msgid="1601490950771217014">"düzenle"</string>
+ <string name="description_star" msgid="2605854427360036550">"favori"</string>
+ <string name="edit_contact" msgid="7529281274005689512">"Kişiyi düzenle"</string>
+ <plurals name="merge_info">
+ <item quantity="one" msgid="148365587896371969">"birleştirilmemiş"</item>
+ <item quantity="other" msgid="425683718017380845">"<xliff:g id="COUNT">%0$d</xliff:g> kaynaktan birleştirildi"</item>
+ </plurals>
+ <string name="edit_delete_rawcontact" msgid="5054644778227879148">"Sil"</string>
+ <string name="edit_error_saving" msgid="2153949392245240166">"Kaydetme hatası"</string>
+ <string name="edit_structured_editor_button" msgid="3830078388142808106">"..."</string>
+ <string name="local_invisible_directory" msgid="6046691709127661065">"Diğer"</string>
+ <!-- no translation found for aggregation_suggestion_title:one (7676930148670768645) -->
+ <!-- no translation found for aggregation_suggestion_title:other (3744098467672876675) -->
+ <!-- no translation found for aggregation_suggestion_joined_title (5342738708069602301) -->
+ <skip />
+ <!-- no translation found for aggregation_suggestion_join_button (7248325487963514260) -->
+ <skip />
+ <!-- no translation found for aggregation_suggestion_view_button (8232061564083962522) -->
+ <skip />
</resources>
diff --git a/res/layout/item_editor_field.xml b/res/values-xlarge/dimens.xml
similarity index 73%
copy from res/layout/item_editor_field.xml
copy to res/values-xlarge/dimens.xml
index 9ad8689..e99b257 100644
--- a/res/layout/item_editor_field.xml
+++ b/res/values-xlarge/dimens.xml
@@ -1,12 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2008 The Android Open Source Project
+<!-- 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.
@@ -14,7 +14,7 @@
limitations under the License.
-->
-<EditText
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content" />
+<resources>
+ <!-- Size of the text in the aizy visual scroll control -->
+ <dimen name="aizy_text_size">17dip</dimen>
+</resources>
diff --git a/res/values-xlarge/styles.xml b/res/values-xlarge/styles.xml
new file mode 100644
index 0000000..cc8682f
--- /dev/null
+++ b/res/values-xlarge/styles.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<resources>
+ <style name="ContactBrowserTheme" parent="@android:Theme.WithActionBar">
+ </style>
+ <style name="ContactPickerTheme" parent="@android:Theme.Dialog">
+ </style>
+ <style name="ContactsPreferencesTheme" parent="@android:Theme.Dialog">
+ </style>
+ <style name="TallTitleBarTheme" parent="@android:Theme.Dialog">
+ <item name="android:windowContentOverlay">@null</item>
+ </style>
+</resources>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index ecdfee5..46f6bfe 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -31,6 +31,8 @@
<string name="viewContactTitle" msgid="7989394521836644384">"联系人详情"</string>
<string name="viewContactDesription" msgid="214186610887547860">"查看联系人"</string>
<string name="editContactDescription" msgid="2947202828256214947">"编辑联系人"</string>
+ <string name="edit_email_caption" msgid="2401084399676154272">"电子邮件地址:"</string>
+ <string name="edit_email_type_caption" msgid="66331218163770698">"类型:"</string>
<string name="insertContactDescription" msgid="4709878105452681987">"创建联系人"</string>
<string name="searchHint" msgid="8482945356247760701">"搜索联系人"</string>
<string name="menu_search" msgid="9147752853603483719">"搜索"</string>
@@ -123,13 +125,13 @@
</plurals>
<string name="listTotalAllContactsZero" msgid="6811347506748072822">"未显示任何联系人"</string>
<plurals name="listFoundAllContacts">
- <item quantity="one" msgid="2830107332033967280">"找到 1 位联系人"</item>
- <item quantity="other" msgid="7752927996850263152">"找到 <xliff:g id="COUNT">%d</xliff:g> 位联系人"</item>
+ <item quantity="one" msgid="5517063038754171134">"找到 1 个联系人"</item>
+ <item quantity="other" msgid="3852668542926965042">"找到 <xliff:g id="COUNT">%d</xliff:g> 个联系人"</item>
</plurals>
- <string name="listFoundAllContactsZero" msgid="5554368784319460828">"未找到联系人"</string>
+ <string name="listFoundAllContactsZero" msgid="777952841930508289">"找不到"</string>
<plurals name="searchFoundContacts">
- <item quantity="one" msgid="916648718690661777">"1 位联系人"</item>
- <item quantity="other" msgid="5660384247071761844">"<xliff:g id="COUNT">%d</xliff:g> 位联系人"</item>
+ <item quantity="one" msgid="4826918429708286628">"找到 1 个联系人"</item>
+ <item quantity="other" msgid="7988132539476575389">"找到 <xliff:g id="COUNT">%d</xliff:g> 个联系人"</item>
</plurals>
<string name="contactsIconLabel" msgid="7666609097606552806">"通讯录"</string>
<string name="contactsFavoritesLabel" msgid="8417039765586853670">"收藏"</string>
@@ -159,10 +161,10 @@
<string name="simContacts_title" msgid="27341688347689769">"SIM 卡联系人"</string>
<string name="noContactsHelpTextWithSyncForCreateShortcut" msgid="801504710275614594">"没有任何联系人可供显示。(如果您刚刚添加了一个帐户,则系统需要几分钟的时间同步联系人。)"</string>
<string name="noContactsHelpTextForCreateShortcut" msgid="3081286388667108335">"没有任何联系人可供显示。"</string>
- <string name="noContactsHelpText" msgid="6788487368878712350">"您没有可显示的联系人。"\n\n"要添加联系人,请按"<font fgcolor="#ffffffff"><b>"菜单"</b></font>",然后触摸:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Google 帐户"</b></font>"以添加或配置可同步到手机的联系人的帐户"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"新建联系人"</b></font>"以从头开始创建新联系人"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"导入/导出"</b></font>\n</li></string>
- <string name="noContactsHelpTextWithSync" msgid="3734101165712848179">"您没有可显示的联系人。(如果您刚刚添加了一个帐户,则需要几分钟时间同步联系人。)"\n\n"要添加联系人,请按"<font fgcolor="#ffffffff"><b>"菜单"</b></font>",然后触摸:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Google 帐户"</b></font>"以添加或配置可同步到手机的联系人的帐户"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"显示选项"</b></font>"以更改可见的联系人"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"新建联系人"</b></font>"以从头开始创建新联系人"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"导入/导出"</b></font>\n</li></string>
- <string name="noContactsNoSimHelpText" msgid="6553845386917463292">"您没有可显示的联系人。"\n\n"要添加联系人,请按"<font fgcolor="#ffffffff"><b>"菜单"</b></font>",然后触摸:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Google 帐户"</b></font>"以添加或配置可同步到手机的联系人的帐户"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"新建联系人"</b></font>"以从头开始创建新联系人"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"导入/导出"</b></font>\n</li></string>
- <string name="noContactsNoSimHelpTextWithSync" msgid="1122296298361373488">"您没有可显示的联系人。(如果您刚刚添加了一个帐户,则需要几分钟时间同步联系人。)"\n\n"要添加联系人,请按"<font fgcolor="#ffffffff"><b>"菜单"</b></font>",然后触摸:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Google 帐户"</b></font>"以添加或配置可同步到手机的联系人的帐户"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"显示选项"</b></font>"以更改可见的联系人"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"新建联系人"</b></font>"以从头开始创建新联系人"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"导入/导出"</b></font>\n</li></string>
+ <string name="noContactsHelpText" msgid="7633826236417884130">"您没有可显示的联系人。"\n\n"要添加联系人,请按"<font fgcolor="#ffffffff"><b>"菜单"</b></font>",然后触摸以下按钮执行操作:"\n\n<li><font fgcolor="#ffffffff"><b>"Google 帐户"</b></font>"以添加或配置帐户,您可以将该帐户中包含的联系人同步到手机"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"新建联系人"</b></font>"以从头开始创建新联系人"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"导入/导出"</b></font>"以导入 SIM 卡或 SD 卡上的联系人"\n</li></string>
+ <string name="noContactsHelpTextWithSync" msgid="3017521127042216243">"您没有可显示的联系人。(如果您刚刚添加了一个帐户,则需要几分钟时间同步联系人。)"\n\n"要添加联系人,请按"<font fgcolor="#ffffffff"><b>"菜单"</b></font>",然后触摸:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Google 帐户"</b></font>"以添加或配置帐户,您可以将该帐户中包含的联系人同步到手机"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"显示选项"</b></font>"以更改可见的联系人"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"新建联系人"</b></font>"以从头开始创建新联系人"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"导入/导出"</b></font>"以导入 SIM 卡或 SD 卡上的联系人"\n</li></string>
+ <string name="noContactsNoSimHelpText" msgid="467658807711582876">"您没有可显示的联系人。"\n\n"要添加联系人,请按"<font fgcolor="#ffffffff"><b>"菜单"</b></font>",然后触摸:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Google 帐户"</b></font>"以添加或配置帐户,您可以将该帐户中包含的联系人同步到手机"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"新建联系人"</b></font>"以从头开始创建新联系人"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"导入/导出"</b></font>"以导入 SIM 卡或 SD 卡上的联系人"\n</li></string>
+ <string name="noContactsNoSimHelpTextWithSync" msgid="9040060730467973050">"您没有可显示的联系人。(如果您刚刚添加了一个帐户,则需要几分钟时间同步联系人。)"\n\n"要添加联系人,请按"<font fgcolor="#ffffffff"><b>"菜单"</b></font>",然后触摸:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Google 帐户"</b></font>"以添加或配置帐户,您可以将该帐户中包含的联系人同步到手机"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"显示选项"</b></font>"以更改可见的联系人"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"新建联系人"</b></font>"以从头开始创建新联系人"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"导入/导出"</b></font>"以导入 SD 卡上的联系人"\n</li></string>
<string name="noFavoritesHelpText" msgid="3744655776704833277">"您未收藏任何联系人。"\n\n"要向您的收藏列表添加联系人,请执行以下操作:"\n\n" "<li>"触摸"<b>"联系人"</b>"标签"\n</li>\n<li>"触摸您要加入收藏的联系人"\n</li>\n<li>"触摸联系人姓名旁边的星标"\n</li></string>
<string name="liveFolder_all_label" msgid="5961411940473276616">"所有联系人"</string>
<string name="liveFolder_favorites_label" msgid="2674341514070517105">"已加星标的内容"</string>
@@ -179,7 +181,7 @@
<string name="callBack" msgid="5498224409038809224">"回电"</string>
<string name="callAgain" msgid="3197312117049874778">"重拨"</string>
<string name="returnCall" msgid="8171961914203617813">"回拨"</string>
- <string name="callDetailsDurationFormat" msgid="8157706382818184268">"<xliff:g id="MINUTES">%1$s</xliff:g> 分 <xliff:g id="SECONDS">%2$s</xliff:g> 秒"</string>
+ <string name="callDetailsDurationFormat" msgid="8157706382818184268">"<xliff:g id="MINUTES">%s</xliff:g> 分 <xliff:g id="SECONDS">%s</xliff:g> 秒"</string>
<string name="favoritesFrquentSeparator" msgid="8107518433381283736">"常用联系人"</string>
<string name="add_contact_dlg_title" msgid="2896685845822146494">"添加联系人"</string>
<string name="add_contact_dlg_message_fmt" msgid="7986472669444326576">"将“<xliff:g id="EMAIL">%s</xliff:g>”添加到联系人?"</string>
@@ -217,18 +219,24 @@
<string name="scanning_sdcard_failed_title" msgid="3506782007953167180">"扫描 SD 卡失败"</string>
<string name="scanning_sdcard_failed_message" msgid="3761992500690182922">"扫描 SD 卡失败(原因:“<xliff:g id="FAIL_REASON">%s</xliff:g>”)"</string>
<string name="fail_reason_io_error" msgid="5922864781066136340">"I/O 错误"</string>
+ <string name="fail_reason_low_memory_during_import" msgid="7514918659342886381">"存储器空间不足(文件可能过大)"</string>
<string name="fail_reason_vcard_parse_error" msgid="1201233722762680214">"由于意外原因而无法解析 vCard"</string>
<string name="fail_reason_vcard_not_supported_error" msgid="655208100451286027">"虽然 vCard 似乎使用了有效的格式,但系统无法对其进行解析,因为当前的实现方案不支持该格式。"</string>
<string name="fail_reason_no_vcard_file" msgid="6376516175882881595">"未在 SD 卡上找到 vCard 文件"</string>
+ <string name="fail_reason_failed_to_collect_vcard_meta_info" msgid="4154492282316067754">"无法收集指定的 vCard 文件的元信息。"</string>
<string name="fail_reason_failed_to_read_files" msgid="3659521123567134029">"一个或多个文件导入失败 (%s)。"</string>
<string name="fail_reason_unknown" msgid="999034019513096768">"未知错误"</string>
<string name="select_vcard_title" msgid="3968948173786172468">"选择 vCard 文件"</string>
- <string name="progress_shower_message" msgid="5636525578293752526">"<xliff:g id="ACTION">%1$s</xliff:g>"\n"<xliff:g id="FILENAME">%2$s</xliff:g>"</string>
- <string name="reading_vcard_title" msgid="4723433501579653199">"正在读取 vCard"</string>
- <string name="reading_vcard_message" msgid="6381368920030748743">"正在读取 vCard 文件"</string>
- <string name="reading_vcard_failed_title" msgid="4923008144735294994">"读取 vCard 数据失败"</string>
- <string name="reading_vcard_contacts" msgid="3066834102042012868">"第 <xliff:g id="CURRENT_NUMBER">%1$s</xliff:g> 个联系人(共 <xliff:g id="TOTAL_NUMBER">%2$s</xliff:g> 个)"</string>
- <string name="reading_vcard_files" msgid="34180143726972661">"第 <xliff:g id="CURRENT_NUMBER">%1$s</xliff:g> 个,共 <xliff:g id="TOTAL_NUMBER">%2$s</xliff:g> 个文件"</string>
+ <string name="caching_vcard_title" msgid="5009556022082659780">"正在将 vCard 缓存到本地临时存储"</string>
+ <string name="caching_vcard_message" msgid="2380844718093378900">"导入程序正在将 vCard 缓存到本地临时存储。很快将会开始真正的导入。"</string>
+ <string name="progress_notifier_message" msgid="8412196553907067659">"<xliff:g id="CURRENT_NUMBER">%s</xliff:g>/<xliff:g id="TOTAL_NUMBER">%s</xliff:g>:<xliff:g id="FILENAME">%s</xliff:g>"</string>
+ <string name="reading_vcard_title" msgid="3324988724123717025">"正在读取 vCard"</string>
+ <string name="reading_vcard_failed_title" msgid="2162610359561887043">"无法读取 vCard 数据"</string>
+ <string name="reading_vcard_canceled_title" msgid="1770608329958463131">"已取消读取 vCard 数据"</string>
+ <string name="importing_vcard_finished_title" msgid="3213257229785702182">"导入 vCard 已完成"</string>
+ <string name="vcard_importer_start_message" msgid="5520549352987753320">"vCard 导入程序已启动。"</string>
+ <string name="vcard_importer_will_start_message" msgid="2055145402481822160">"vCard 导入程序会在稍后导入该 vCard。"</string>
+ <string name="percentage" msgid="34897865327092209">"%s%%"</string>
<string name="confirm_export_title" msgid="7648747763127442983">"确认导出"</string>
<string name="confirm_export_message" msgid="3875683519257829750">"确定要将您的联系人列表导出到“<xliff:g id="VCARD_FILENAME">%s</xliff:g>”中吗?"</string>
<string name="exporting_contact_failed_title" msgid="585823094820602526">"导出联系人数据失败"</string>
@@ -236,6 +244,8 @@
<string name="fail_reason_no_exportable_contact" msgid="4919714086648344495">"没有可导出的联系人"</string>
<string name="fail_reason_too_many_vcard" msgid="7084146295639672658">"SD 卡中的 vCard 文件太多"</string>
<string name="fail_reason_too_long_filename" msgid="1915716071321839166">"所需文件名太长(“<xliff:g id="FILENAME">%s</xliff:g>”)"</string>
+ <string name="vcard_exporter_start_message" msgid="6497218576519292369">"vCard 导出程序已启动。"</string>
+ <string name="exporting_vcard_finished_title" msgid="1268425436375550862">"导出 vCard 已完成"</string>
<string name="exporting_contact_list_title" msgid="9072240631534457415">"正在导出联系人数据"</string>
<string name="exporting_contact_list_message" msgid="5640326540405486055">"正在向“<xliff:g id="FILE_NAME">%s</xliff:g>”导出联系人数据"</string>
<string name="fail_reason_could_not_initialize_exporter" msgid="4943708332700987376">"无法初始化导出程序:“<xliff:g id="EXACT_REASON">%s</xliff:g>”"</string>
@@ -243,8 +253,8 @@
<string name="composer_failed_to_get_database_infomation" msgid="3723109558155169053">"获取数据库信息失败"</string>
<string name="composer_has_no_exportable_contact" msgid="754734132189369094">"没有可导出的联系人。如果您的手机中确实有联系人,则可能是某个数据提供程序禁止您从手机中导出任何联系人。"</string>
<string name="composer_not_initialized" msgid="8041534450748388843">"vCard 制作程序未正确初始化"</string>
- <string name="fail_reason_could_not_open_file" msgid="4013520943128739511">"无法打开“<xliff:g id="FILE_NAME">%1$s</xliff:g>”:<xliff:g id="EXACT_REASON">%2$s</xliff:g>"</string>
- <string name="exporting_contact_list_progress" msgid="560522409559101193">"第 <xliff:g id="CURRENT_NUMBER">%1$s</xliff:g> 个联系人(共 <xliff:g id="TOTAL_NUMBER">%2$s</xliff:g> 个)"</string>
+ <string name="fail_reason_could_not_open_file" msgid="4013520943128739511">"无法打开“<xliff:g id="FILE_NAME">%s</xliff:g>”:<xliff:g id="EXACT_REASON">%s</xliff:g>"</string>
+ <string name="exporting_contact_list_progress" msgid="560522409559101193">"第 <xliff:g id="CURRENT_NUMBER">%s</xliff:g> 个联系人(共 <xliff:g id="TOTAL_NUMBER">%s</xliff:g> 个)"</string>
<string name="search_settings_description" msgid="2675223022992445813">"联系人姓名"</string>
<string name="add_2sec_pause" msgid="9214012315201040129">"暂停时间延长 2 秒"</string>
<string name="add_wait" msgid="3360818652790319634">"延长等待时间"</string>
@@ -254,6 +264,7 @@
<string name="quickcontact_missing_app" msgid="4600366393134289038">"未找到可处理此操作的应用程序"</string>
<string name="quickcontact_remember_choice" msgid="5964536411579749424">"记住此选择"</string>
<string name="quickcontact_missing_name" msgid="5590266114306996632">"未知"</string>
+ <string name="quickcontact_no_data" msgid="2098000859125253675">"无数据"</string>
<string name="menu_accounts" msgid="8499114602017077970">"帐户"</string>
<string name="menu_import_export" msgid="3765725645491577190">"导入/导出"</string>
<string name="dialog_import_export" msgid="4771877268244096596">"导入/导出联系人"</string>
@@ -359,6 +370,7 @@
<string name="name_phonetic_family" msgid="462095502140180305">"姓氏拼音"</string>
<string name="account_type_format" msgid="718948015590343010">"<xliff:g id="SOURCE">%1$s</xliff:g> 联系人"</string>
<string name="from_account_format" msgid="687567483928582084">"来自 <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
+ <string name="account_type_and_name" msgid="2876220035411512934">"来自 <xliff:g id="SOURCE_1">%2$s</xliff:g> 的 <xliff:g id="SOURCE_0">%1$s</xliff:g> 联系人"</string>
<string name="use_photo_as_primary" msgid="8807110122951157246">"使用此照片"</string>
<string name="contact_read_only" msgid="1203216914575723978">"<xliff:g id="SOURCE">%1$s</xliff:g> 联系人信息在此设备上不可编辑。"</string>
<string name="no_contact_details" msgid="6754415338321837001">"无此联系人的其他信息"</string>
@@ -374,9 +386,42 @@
<string name="pick_photo" msgid="448886509158039462">"从图库中选择照片"</string>
<string name="locale_change_in_progress" msgid="1124266507671178413">"正在更新联系人列表,以反映语言的变更。"\n\n"请稍候..."</string>
<string name="upgrade_in_progress" msgid="7530893673211750223">"正在更新联系人列表。"\n\n"请稍候..."</string>
- <string name="upgrade_out_of_memory" msgid="492151063059824962">"正在升级联系人。"\n\n"升级过程需要约 <xliff:g id="SIZE_IN_MEGABYTES">%d</xliff:g> Mb 的手机内存。"\n\n"请选择以下选项之一:"</string>
+ <string name="upgrade_out_of_memory" msgid="6713914687111678777">"正在升级联系人。"\n\n"升级过程需要约 <xliff:g id="SIZE_IN_MEGABYTES">%d</xliff:g> MB 的手机内存。"\n\n"请选择以下选项之一:"</string>
<string name="upgrade_out_of_memory_uninstall" msgid="1721798828992091432">"卸载一些应用程序"</string>
<string name="upgrade_out_of_memory_retry" msgid="8431289830472724609">"重新尝试升级"</string>
<string name="search_results_for" msgid="8705490885073188513">"“<xliff:g id="QUERY">%s</xliff:g>”的搜索结果"</string>
<string name="search_results_searching" msgid="7755623475227227314">"正在搜索..."</string>
+ <string name="adding_recipients" msgid="5123647646892106631">"正在载入..."</string>
+ <string name="menu_display_selected" msgid="6470001164297969034">"已选收件人"</string>
+ <string name="menu_display_all" msgid="8887488642609786198">"全部收件人"</string>
+ <string name="menu_select_all" msgid="621719255150713545">"全选"</string>
+ <string name="menu_select_none" msgid="7093222469852132345">"取消全选"</string>
+ <plurals name="multiple_picker_title">
+ <item quantity="one" msgid="4761009734586319101">"已选择 1 个收件人"</item>
+ <item quantity="other" msgid="4608837420986126229">"已选择 <xliff:g id="COUNT">%d</xliff:g> 个收件人"</item>
+ </plurals>
+ <string name="unknown_contacts_separator" msgid="2490675836664397214">"未知联系人"</string>
+ <string name="no_contacts_selected" msgid="5877803471037324613">"未选择联系人。"</string>
+ <string name="add_field" msgid="5257149039253569615">"添加信息"</string>
+ <string name="contact_status_update_attribution" msgid="752179367353018597">"来源:<xliff:g id="SOURCE">%1$s</xliff:g>"</string>
+ <string name="contact_status_update_attribution_with_date" msgid="7358045508107825068">"时间:<xliff:g id="DATE">%1$s</xliff:g>,来源:<xliff:g id="SOURCE">%2$s</xliff:g>"</string>
+ <string name="description_edit" msgid="1601490950771217014">"编辑"</string>
+ <string name="description_star" msgid="2605854427360036550">"收藏"</string>
+ <string name="edit_contact" msgid="7529281274005689512">"编辑联系人"</string>
+ <plurals name="merge_info">
+ <item quantity="one" msgid="148365587896371969">"未合并"</item>
+ <item quantity="other" msgid="425683718017380845">"合并自 <xliff:g id="COUNT">%0$d</xliff:g> 个来源"</item>
+ </plurals>
+ <string name="edit_delete_rawcontact" msgid="5054644778227879148">"删除"</string>
+ <string name="edit_error_saving" msgid="2153949392245240166">"保存出错"</string>
+ <string name="edit_structured_editor_button" msgid="3830078388142808106">"..."</string>
+ <string name="local_invisible_directory" msgid="6046691709127661065">"其他"</string>
+ <!-- no translation found for aggregation_suggestion_title:one (7676930148670768645) -->
+ <!-- no translation found for aggregation_suggestion_title:other (3744098467672876675) -->
+ <!-- no translation found for aggregation_suggestion_joined_title (5342738708069602301) -->
+ <skip />
+ <!-- no translation found for aggregation_suggestion_join_button (7248325487963514260) -->
+ <skip />
+ <!-- no translation found for aggregation_suggestion_view_button (8232061564083962522) -->
+ <skip />
</resources>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index 4427eaf..a6bbd81 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -31,6 +31,8 @@
<string name="viewContactTitle" msgid="7989394521836644384">"聯絡人詳細資料"</string>
<string name="viewContactDesription" msgid="214186610887547860">"檢視聯絡人"</string>
<string name="editContactDescription" msgid="2947202828256214947">"編輯聯絡人"</string>
+ <string name="edit_email_caption" msgid="2401084399676154272">"電子郵件地址:"</string>
+ <string name="edit_email_type_caption" msgid="66331218163770698">"類型:"</string>
<string name="insertContactDescription" msgid="4709878105452681987">"建立聯絡人"</string>
<string name="searchHint" msgid="8482945356247760701">"搜尋聯絡人"</string>
<string name="menu_search" msgid="9147752853603483719">"搜尋"</string>
@@ -123,13 +125,13 @@
</plurals>
<string name="listTotalAllContactsZero" msgid="6811347506748072822">"沒有可顯示的聯絡人"</string>
<plurals name="listFoundAllContacts">
- <item quantity="one" msgid="2830107332033967280">"已找到 1 位聯絡人"</item>
- <item quantity="other" msgid="7752927996850263152">"已找到 <xliff:g id="COUNT">%d</xliff:g> 位聯絡人"</item>
+ <item quantity="one" msgid="5517063038754171134">"找到 1 位聯絡人"</item>
+ <item quantity="other" msgid="3852668542926965042">"找到 <xliff:g id="COUNT">%d</xliff:g> 位聯絡人"</item>
</plurals>
- <string name="listFoundAllContactsZero" msgid="5554368784319460828">"找不到聯絡人"</string>
+ <string name="listFoundAllContactsZero" msgid="777952841930508289">"找不到"</string>
<plurals name="searchFoundContacts">
- <item quantity="one" msgid="916648718690661777">"1 位聯絡人"</item>
- <item quantity="other" msgid="5660384247071761844">"<xliff:g id="COUNT">%d</xliff:g> 位聯絡人"</item>
+ <item quantity="one" msgid="4826918429708286628">"找到 1 位聯絡人"</item>
+ <item quantity="other" msgid="7988132539476575389">"找到 <xliff:g id="COUNT">%d</xliff:g> 位聯絡人"</item>
</plurals>
<string name="contactsIconLabel" msgid="7666609097606552806">"聯絡人"</string>
<string name="contactsFavoritesLabel" msgid="8417039765586853670">"我的最愛"</string>
@@ -159,10 +161,10 @@
<string name="simContacts_title" msgid="27341688347689769">"SIM 卡聯絡人"</string>
<string name="noContactsHelpTextWithSyncForCreateShortcut" msgid="801504710275614594">"您沒有任何聯絡人可供顯示 (如果您剛剛才新增帳戶,系統需要幾分鐘的時間才能同步聯絡人資訊)。"</string>
<string name="noContactsHelpTextForCreateShortcut" msgid="3081286388667108335">"您沒有任何聯絡人可供顯示。"</string>
- <string name="noContactsHelpText" msgid="6788487368878712350">"您沒有任何聯絡人。"\n\n"如要新增聯絡人,請按下 ["<font fgcolor="#ffffffff">"選單"<b></b></font>"] 並輕觸:"\n" "\n<li>"["<font fgcolor="#ffffffff">"帳戶"<b></b></font>" 來新增或設定您可以同步處理手機資料的聯絡人帳戶"\n</li>" "\n<li>"["<font fgcolor="#ffffffff">"新增聯絡人"<b></b></font>"] 從頭開始建立聯絡人"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"匯入/匯出"</b></font>\n</li></string>
- <string name="noContactsHelpTextWithSync" msgid="3734101165712848179">"您沒有任何聯絡人 (如果您剛剛才新增帳戶,系統需要幾分鐘的時間才能同步聯絡人資訊)。"\n\n"如要新增聯絡人,請按下 ["<font fgcolor="#ffffffff">"選單"</font><b></b>"] 並輕觸:"\n\n"["<li><font fgcolor="#ffffffff">"帳戶"<b></b></font>"] 來新增或設定您可以同步處理手機資料的聯絡人帳戶"\n</li>" "\n<li>"["<font fgcolor="#ffffffff">"顯示選項"<b></b></font>"] 來變更聯絡人的顯示狀態"\n</li>" "\n<li>"["<font fgcolor="#ffffffff">"新增聯絡人"<b></b></font>"] 從頭開始建立聯絡人"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"匯入/匯出"</b></font>\n</li></string>
- <string name="noContactsNoSimHelpText" msgid="6553845386917463292">"您沒有任何聯絡人。"\n\n"如要新增聯絡人,請按下 ["<font fgcolor="#ffffffff">"選單"<b></b></font>"] 並輕觸:"\n" "\n<li>"["<font fgcolor="#ffffffff">"帳戶"<b></b></font>" 來新增或設定您可以同步處理手機資料的聯絡人帳戶"\n</li>" "\n<li>"["<font fgcolor="#ffffffff">"新增聯絡人"<b></b></font>"] 從頭開始建立聯絡人"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"匯入/匯出"</b></font>\n</li></string>
- <string name="noContactsNoSimHelpTextWithSync" msgid="1122296298361373488">"您沒有任何聯絡人 (如果您剛剛才新增帳戶,系統需要幾分鐘的時間才能同步聯絡人資訊)。"\n\n"如要新增聯絡人,請按下 ["<font fgcolor="#ffffffff">"選單"</font><b></b>"] 並輕觸:"\n\n"["<li><font fgcolor="#ffffffff">"帳戶"<b></b></font>"] 來新增或設定您可以同步處理手機資料的聯絡人帳戶"\n</li>" "\n<li>"["<font fgcolor="#ffffffff">"顯示選項"<b></b></font>"] 來變更聯絡人的顯示狀態"\n</li>" "\n<li>"["<font fgcolor="#ffffffff">"新增聯絡人"<b></b></font>"] 從頭開始建立聯絡人"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"匯入/匯出"</b></font>\n</li></string>
+ <string name="noContactsHelpText" msgid="7633826236417884130">"您沒有任何聯絡人可以顯示。"\n\n"如要新增聯絡人,請按下 [選單] 並輕觸:"\n\n"[帳戶] 以新增或設定含有聯絡人的帳戶,您可以將其聯絡人資料同步傳送到手機"\n" "\n"[新增聯絡人] 以從頭開始建立聯絡人"\n" "\n"[匯入/匯出] 以從 SIM 卡或 SD 卡匯入聯絡人"<font fgcolor="#ffffffff"><b></b></font><li><font fgcolor="#ffffffff"><b></b></font></li><li><font fgcolor="#ffffffff"><b></b></font></li><li><font fgcolor="#ffffffff"><b></b></font>\n</li></string>
+ <string name="noContactsHelpTextWithSync" msgid="3017521127042216243">"您沒有任何聯絡人 (如果您剛剛才新增帳戶,系統需要幾分鐘的時間才能同步處理聯絡人資訊)。"\n\n"如要新增聯絡人,請按下 [選單] 並輕觸:"\n\n"[帳戶] 以新增或設定含有聯絡人的帳戶,您可以將其聯絡人資料同步傳送到手機"\n" "\n"[顯示選項] 以變更聯絡人的顯示狀態"\n" "\n"[新增聯絡人] 以從頭開始建立聯絡人"\n" "\n"[匯入/匯出] 以從 SIM 卡或 SD 卡匯入聯絡人"<font fgcolor="#ffffffff"><b></b></font><li><font fgcolor="#ffffffff"><b></b></font></li><li><font fgcolor="#ffffffff"><b></b></font></li><li><font fgcolor="#ffffffff"><b></b></font></li><li><font fgcolor="#ffffffff"><b></b></font>\n</li></string>
+ <string name="noContactsNoSimHelpText" msgid="467658807711582876">"您沒有任何聯絡人。"\n\n"如要新增聯絡人,請按下 [選單] 並輕觸:"\n" "\n"[帳戶] 以新增或設定含有聯絡人的帳戶,您可以將其聯絡人資料同步傳送到手機帳戶"\n" "\n"[新增聯絡人] 從頭開始建立聯絡人"\n" "\n"[匯入/匯出] 從 SD 卡匯入聯絡人"<font fgcolor="#ffffffff"><b></b></font><li><font fgcolor="#ffffffff"><b></b></font></li><li><font fgcolor="#ffffffff"><b></b></font></li><li><font fgcolor="#ffffffff"><b></b></font>\n</li></string>
+ <string name="noContactsNoSimHelpTextWithSync" msgid="9040060730467973050">"您沒有任何聯絡人 (如果您剛剛才新增帳戶,系統需要幾分鐘的時間才能同步處理聯絡人資訊)。"\n\n"如要新增聯絡人,請按下 [選單] 並輕觸:"\n\n"[帳戶] 以新增或設定含有聯絡人的帳戶,您可以將其聯絡人資料同步傳送到手機"\n" "\n"[顯示選項] 以變更聯絡人的顯示狀態"\n" "\n"[新增聯絡人] 以從頭開始建立聯絡人"\n" "\n"[匯入/匯出] 以從 SD 卡匯入聯絡人"<font fgcolor="#ffffffff"><b></b></font><li><font fgcolor="#ffffffff"><b></b></font></li><li><font fgcolor="#ffffffff"><b></b></font></li><li><font fgcolor="#ffffffff"><b></b></font></li><li><font fgcolor="#ffffffff"><b></b></font>\n</li></string>
<string name="noFavoritesHelpText" msgid="3744655776704833277">"您沒有任何最愛。"\n\n"如要將聯絡人新增至最愛清單:"\n\n<li>"輕觸 [聯絡人"<b></b>"] 標籤"\n</li>" "\n<li>"輕觸要新增至最愛的聯絡人"\n</li>" "\n<li>"輕觸聯絡人名稱旁邊的星號圖示"\n</li></string>
<string name="liveFolder_all_label" msgid="5961411940473276616">"所有聯絡人"</string>
<string name="liveFolder_favorites_label" msgid="2674341514070517105">"已加星號"</string>
@@ -179,7 +181,7 @@
<string name="callBack" msgid="5498224409038809224">"回播電話"</string>
<string name="callAgain" msgid="3197312117049874778">"重撥"</string>
<string name="returnCall" msgid="8171961914203617813">"回電"</string>
- <string name="callDetailsDurationFormat" msgid="8157706382818184268">"<xliff:g id="MINUTES">%1$s</xliff:g> 分 <xliff:g id="SECONDS">%2$s</xliff:g> 秒"</string>
+ <string name="callDetailsDurationFormat" msgid="8157706382818184268">"<xliff:g id="MINUTES">%s</xliff:g> 分 <xliff:g id="SECONDS">%s</xliff:g> 秒"</string>
<string name="favoritesFrquentSeparator" msgid="8107518433381283736">"常用聯絡人"</string>
<string name="add_contact_dlg_title" msgid="2896685845822146494">"新增聯絡人"</string>
<string name="add_contact_dlg_message_fmt" msgid="7986472669444326576">"要將「<xliff:g id="EMAIL">%s</xliff:g>」新增為聯絡人嗎?"</string>
@@ -217,18 +219,24 @@
<string name="scanning_sdcard_failed_title" msgid="3506782007953167180">"無法掃描 SD 卡"</string>
<string name="scanning_sdcard_failed_message" msgid="3761992500690182922">"無法掃描 SD 卡 (原因:「<xliff:g id="FAIL_REASON">%s</xliff:g>」)"</string>
<string name="fail_reason_io_error" msgid="5922864781066136340">"I/O 錯誤"</string>
+ <string name="fail_reason_low_memory_during_import" msgid="7514918659342886381">"記憶體不足 (檔案可能過大)"</string>
<string name="fail_reason_vcard_parse_error" msgid="1201233722762680214">"因未預期原因,無法剖析 VCard"</string>
<string name="fail_reason_vcard_not_supported_error" msgid="655208100451286027">"VCard 格式正確,但目前的實作系統不支援此格式,因此無法剖析"</string>
<string name="fail_reason_no_vcard_file" msgid="6376516175882881595">"SD 卡上沒有 vCard 檔案"</string>
+ <string name="fail_reason_failed_to_collect_vcard_meta_info" msgid="4154492282316067754">"無法從指定的 vCard 檔案收集中繼資料。"</string>
<string name="fail_reason_failed_to_read_files" msgid="3659521123567134029">"無法匯入一或多個檔案 (%s)。"</string>
<string name="fail_reason_unknown" msgid="999034019513096768">"未知的錯誤"</string>
<string name="select_vcard_title" msgid="3968948173786172468">"選取 VCard 檔案"</string>
- <string name="progress_shower_message" msgid="5636525578293752526">"<xliff:g id="ACTION">%1$s</xliff:g>"\n"<xliff:g id="FILENAME">%2$s</xliff:g>"</string>
- <string name="reading_vcard_title" msgid="4723433501579653199">"正在讀取 VCard"</string>
- <string name="reading_vcard_message" msgid="6381368920030748743">"正在讀取 VCard 檔案"</string>
- <string name="reading_vcard_failed_title" msgid="4923008144735294994">"無法讀取 VCard 資料"</string>
- <string name="reading_vcard_contacts" msgid="3066834102042012868">"第 <xliff:g id="CURRENT_NUMBER">%1$s</xliff:g> 位聯絡人,共 <xliff:g id="TOTAL_NUMBER">%2$s</xliff:g> 位"</string>
- <string name="reading_vcard_files" msgid="34180143726972661">"第 <xliff:g id="CURRENT_NUMBER">%1$s</xliff:g> 個檔案,共 <xliff:g id="TOTAL_NUMBER">%2$s</xliff:g> 個"</string>
+ <string name="caching_vcard_title" msgid="5009556022082659780">"正在將 vCard 資料快取至本機暫存空間"</string>
+ <string name="caching_vcard_message" msgid="2380844718093378900">"匯入工具正在將 vCard 的資料快取至本機暫存空間,隨即將啟動實際的匯入作業。"</string>
+ <string name="progress_notifier_message" msgid="8412196553907067659">"<xliff:g id="CURRENT_NUMBER">%s</xliff:g>/<xliff:g id="TOTAL_NUMBER">%s</xliff:g>:<xliff:g id="FILENAME">%s</xliff:g>"</string>
+ <string name="reading_vcard_title" msgid="3324988724123717025">"正在讀取 vCard"</string>
+ <string name="reading_vcard_failed_title" msgid="2162610359561887043">"無法讀取 vCard 資料"</string>
+ <string name="reading_vcard_canceled_title" msgid="1770608329958463131">"已取消讀取 vCard 資料"</string>
+ <string name="importing_vcard_finished_title" msgid="3213257229785702182">"已結束 vCard 匯入作業"</string>
+ <string name="vcard_importer_start_message" msgid="5520549352987753320">"vCard 匯入工具已啟動。"</string>
+ <string name="vcard_importer_will_start_message" msgid="2055145402481822160">"vCard 匯入工具將於稍後匯入該 vCard。"</string>
+ <string name="percentage" msgid="34897865327092209">"%s%%"</string>
<string name="confirm_export_title" msgid="7648747763127442983">"確認匯出"</string>
<string name="confirm_export_message" msgid="3875683519257829750">"確定要將聯絡人清單匯出至「<xliff:g id="VCARD_FILENAME">%s</xliff:g>」嗎?"</string>
<string name="exporting_contact_failed_title" msgid="585823094820602526">"無法匯出聯絡人資料"</string>
@@ -236,6 +244,8 @@
<string name="fail_reason_no_exportable_contact" msgid="4919714086648344495">"沒有可匯出的聯絡人"</string>
<string name="fail_reason_too_many_vcard" msgid="7084146295639672658">"SD 卡中的 VCard 檔案過多"</string>
<string name="fail_reason_too_long_filename" msgid="1915716071321839166">"要求的檔案名稱過長 (「<xliff:g id="FILENAME">%s</xliff:g>」)"</string>
+ <string name="vcard_exporter_start_message" msgid="6497218576519292369">"vCard 匯出工具已啟動。"</string>
+ <string name="exporting_vcard_finished_title" msgid="1268425436375550862">"已結束 vCard 匯出作業"</string>
<string name="exporting_contact_list_title" msgid="9072240631534457415">"正在匯出聯絡人資料"</string>
<string name="exporting_contact_list_message" msgid="5640326540405486055">"正在將聯絡人資料匯出至「<xliff:g id="FILE_NAME">%s</xliff:g>」"</string>
<string name="fail_reason_could_not_initialize_exporter" msgid="4943708332700987376">"無法初始化匯出程式:「<xliff:g id="EXACT_REASON">%s</xliff:g>」"</string>
@@ -243,8 +253,8 @@
<string name="composer_failed_to_get_database_infomation" msgid="3723109558155169053">"無法取得資料庫資訊"</string>
<string name="composer_has_no_exportable_contact" msgid="754734132189369094">"沒有可匯出的聯絡人。如果您的手機中確實存有聯絡人資料,這可能是因為某些資料提供者禁止您將所有聯絡人匯出手機。"</string>
<string name="composer_not_initialized" msgid="8041534450748388843">"VCard 編輯器並未正確初始化"</string>
- <string name="fail_reason_could_not_open_file" msgid="4013520943128739511">"無法開啟「<xliff:g id="FILE_NAME">%1$s</xliff:g>」:<xliff:g id="EXACT_REASON">%2$s</xliff:g>"</string>
- <string name="exporting_contact_list_progress" msgid="560522409559101193">"第 <xliff:g id="CURRENT_NUMBER">%1$s</xliff:g> 位聯絡人,共 <xliff:g id="TOTAL_NUMBER">%2$s</xliff:g> 位"</string>
+ <string name="fail_reason_could_not_open_file" msgid="4013520943128739511">"無法開啟「<xliff:g id="FILE_NAME">%s</xliff:g>」:<xliff:g id="EXACT_REASON">%s</xliff:g>"</string>
+ <string name="exporting_contact_list_progress" msgid="560522409559101193">"第 <xliff:g id="CURRENT_NUMBER">%s</xliff:g> 位聯絡人,共 <xliff:g id="TOTAL_NUMBER">%s</xliff:g> 位"</string>
<string name="search_settings_description" msgid="2675223022992445813">"您的聯絡人姓名"</string>
<string name="add_2sec_pause" msgid="9214012315201040129">"新增 2 秒暫停功能"</string>
<string name="add_wait" msgid="3360818652790319634">"新增插播功能"</string>
@@ -254,6 +264,7 @@
<string name="quickcontact_missing_app" msgid="4600366393134289038">"找不到可以處理這個動作的應用程式"</string>
<string name="quickcontact_remember_choice" msgid="5964536411579749424">"記住這個選擇"</string>
<string name="quickcontact_missing_name" msgid="5590266114306996632">"不明"</string>
+ <string name="quickcontact_no_data" msgid="2098000859125253675">"無資料"</string>
<string name="menu_accounts" msgid="8499114602017077970">"帳戶"</string>
<string name="menu_import_export" msgid="3765725645491577190">"匯入/匯出"</string>
<string name="dialog_import_export" msgid="4771877268244096596">"匯入/匯出聯絡人"</string>
@@ -359,6 +370,7 @@
<string name="name_phonetic_family" msgid="462095502140180305">"姓氏 (拼音)"</string>
<string name="account_type_format" msgid="718948015590343010">"<xliff:g id="SOURCE">%1$s</xliff:g> 聯絡人"</string>
<string name="from_account_format" msgid="687567483928582084">"來自 <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
+ <string name="account_type_and_name" msgid="2876220035411512934">"來自 <xliff:g id="SOURCE_1">%2$s</xliff:g> 的 <xliff:g id="SOURCE_0">%1$s</xliff:g> 聯絡人"</string>
<string name="use_photo_as_primary" msgid="8807110122951157246">"使用此相片"</string>
<string name="contact_read_only" msgid="1203216914575723978">"無法在此裝置編輯 <xliff:g id="SOURCE">%1$s</xliff:g> 的聯絡人資訊"</string>
<string name="no_contact_details" msgid="6754415338321837001">"沒有此聯絡人的其他資訊"</string>
@@ -374,9 +386,42 @@
<string name="pick_photo" msgid="448886509158039462">"從圖片庫選取相片"</string>
<string name="locale_change_in_progress" msgid="1124266507671178413">"正在更新聯絡人清單以反映語言變更。"\n\n"請稍候..."</string>
<string name="upgrade_in_progress" msgid="7530893673211750223">"正在更新聯絡人清單。"\n\n"請稍候..."</string>
- <string name="upgrade_out_of_memory" msgid="492151063059824962">"正在進行聯絡人升級。"\n\n"升級程序需要大約 <xliff:g id="SIZE_IN_MEGABYTES">%d</xliff:g> MB 的手機內部儲存空間。"\n\n"請選擇下列其中一個選項:"</string>
+ <string name="upgrade_out_of_memory" msgid="6713914687111678777">"正在進行聯絡人升級。"\n\n"升級程序需要大約 <xliff:g id="SIZE_IN_MEGABYTES">%d</xliff:g> MB 的手機內部儲存空間。"\n\n"請選擇下列其中一個選項:"</string>
<string name="upgrade_out_of_memory_uninstall" msgid="1721798828992091432">"解除安裝一些應用程式"</string>
<string name="upgrade_out_of_memory_retry" msgid="8431289830472724609">"重試升級"</string>
<string name="search_results_for" msgid="8705490885073188513">"此項目之搜尋結果:<xliff:g id="QUERY">%s</xliff:g>"</string>
<string name="search_results_searching" msgid="7755623475227227314">"搜尋中..."</string>
+ <string name="adding_recipients" msgid="5123647646892106631">"載入中..."</string>
+ <string name="menu_display_selected" msgid="6470001164297969034">"顯示已選取的項目"</string>
+ <string name="menu_display_all" msgid="8887488642609786198">"全部顯示"</string>
+ <string name="menu_select_all" msgid="621719255150713545">"全選"</string>
+ <string name="menu_select_none" msgid="7093222469852132345">"全部取消選取"</string>
+ <plurals name="multiple_picker_title">
+ <item quantity="one" msgid="4761009734586319101">"已選取 1 位收件者"</item>
+ <item quantity="other" msgid="4608837420986126229">"已選取 <xliff:g id="COUNT">%d</xliff:g> 位收件者"</item>
+ </plurals>
+ <string name="unknown_contacts_separator" msgid="2490675836664397214">"不明聯絡人"</string>
+ <string name="no_contacts_selected" msgid="5877803471037324613">"未選取任何聯絡人。"</string>
+ <string name="add_field" msgid="5257149039253569615">"新增資訊"</string>
+ <string name="contact_status_update_attribution" msgid="752179367353018597">"透過 <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
+ <string name="contact_status_update_attribution_with_date" msgid="7358045508107825068">"<xliff:g id="DATE">%1$s</xliff:g> (透過 <xliff:g id="SOURCE">%2$s</xliff:g>)"</string>
+ <string name="description_edit" msgid="1601490950771217014">"編輯"</string>
+ <string name="description_star" msgid="2605854427360036550">"我的最愛"</string>
+ <string name="edit_contact" msgid="7529281274005689512">"編輯聯絡人"</string>
+ <plurals name="merge_info">
+ <item quantity="one" msgid="148365587896371969">"未合併"</item>
+ <item quantity="other" msgid="425683718017380845">"從 <xliff:g id="COUNT">%0$d</xliff:g> 個來源合併"</item>
+ </plurals>
+ <string name="edit_delete_rawcontact" msgid="5054644778227879148">"刪除"</string>
+ <string name="edit_error_saving" msgid="2153949392245240166">"儲存時發生錯誤"</string>
+ <string name="edit_structured_editor_button" msgid="3830078388142808106">"..."</string>
+ <string name="local_invisible_directory" msgid="6046691709127661065">"其他"</string>
+ <!-- no translation found for aggregation_suggestion_title:one (7676930148670768645) -->
+ <!-- no translation found for aggregation_suggestion_title:other (3744098467672876675) -->
+ <!-- no translation found for aggregation_suggestion_joined_title (5342738708069602301) -->
+ <skip />
+ <!-- no translation found for aggregation_suggestion_join_button (7248325487963514260) -->
+ <skip />
+ <!-- no translation found for aggregation_suggestion_view_button (8232061564083962522) -->
+ <skip />
</resources>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 5f39532..c475148 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -26,4 +26,10 @@
<color name="pinned_header_background">#ff202020</color>
<color name="translucent_search_background">#cc000000</color>
+
+ <!-- Color used in the Aizy visual scroll control for empty section -->
+ <color name="aizy_empty_section">#ff666666</color>
+
+ <!-- Color used in the Aizy visual scroll control for non-empty sections -->
+ <color name="aizy_non_empty_section">#ffcccccc</color>
</resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 4a7a743..b8b06ff 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -39,4 +39,14 @@
<dimen name="list_item_vertical_divider_margin">5dip</dimen>
<dimen name="list_item_presence_icon_margin">5dip</dimen>
<dimen name="list_item_header_text_width">56dip</dimen>
+ <dimen name="list_item_header_chip_width">4dip</dimen>
+ <dimen name="list_item_header_chip_right_margin">4dip</dimen>
+ <dimen name="list_item_header_checkbox_margin">5dip</dimen>
+
+ <dimen name="aggregation_suggestion_icon_size">40dip</dimen>
+
+ <!-- Size of the text in the aizy visual scroll control -->
+ <dimen name="aizy_text_size">13dip</dimen>
+ <dimen name="aizy_preview_width">130dip</dimen>
+ <dimen name="aizy_preview_height">115dip</dimen>
</resources>
diff --git a/res/values/donottranslate_config.xml b/res/values/donottranslate_config.xml
index f1c6951..cad2a63 100644
--- a/res/values/donottranslate_config.xml
+++ b/res/values/donottranslate_config.xml
@@ -53,7 +53,7 @@
<string name="config_export_vcard_type" translatable="false">default</string>
<!-- Directory in which exported VCard file is stored -->
- <string name="config_export_dir" translatable="false">/sdcard</string>
+ <string name="config_export_dir" translatable="false">/mnt/sdcard</string>
<!-- Prefix of exported VCard file -->
<string name="config_export_file_prefix" translatable="false"></string>
diff --git a/res/values/ids.xml b/res/values/ids.xml
index ceb10f8..b1ed87f 100644
--- a/res/values/ids.xml
+++ b/res/values/ids.xml
@@ -14,32 +14,44 @@
limitations under the License.
-->
<resources>
- <!-- The EditText for entries in the EditContactActivity -->
+ <!-- The EditText for entries in the ContactEditFragment -->
<item type="id" name="data"/>
<item type="id" name="header_phones"/>
<item type="id" name="dialog_sync_add"/>
<item type="id" name="dialog_import_export"/>
- <!-- For ImportVCardActivity -->
+ <!-- For vcard.ImportVCardActivity -->
<item type="id" name="dialog_searching_vcard"/>
<item type="id" name="dialog_sdcard_not_found"/>
<item type="id" name="dialog_vcard_not_found"/>
<item type="id" name="dialog_select_import_type"/>
<item type="id" name="dialog_select_one_vcard"/>
<item type="id" name="dialog_select_multiple_vcard"/>
- <item type="id" name="dialog_reading_vcard"/>
+ <item type="id" name="dialog_cache_vcard"/>
<item type="id" name="dialog_io_exception"/>
<item type="id" name="dialog_error_with_message"/>
- <!-- For ContactsListActivity -->
- <item type="id" name="dialog_delete_contact_confirmation"/>
- <item type="id" name="dialog_readonly_contact_hide_confirmation"/>
- <item type="id" name="dialog_multiple_contact_delete_confirmation"/>
- <item type="id" name="dialog_readonly_contact_delete_confirmation"/>
+ <!-- For vcard.CancelImportActivity -->
+ <item type="id" name="dialog_cancel_import_confirmation"/>
- <!-- For ExportVCard -->
+ <!-- For ContactDeletionInteraction -->
+ <item type="id" name="dialog_delete_contact_confirmation"/>
+
+ <!-- For ImportExportInteraction -->
+ <item type="id" name="dialog_import_export_options"/>
+
+ <!-- For ExportVCardActivity -->
<item type="id" name="dialog_export_confirmation"/>
<item type="id" name="dialog_exporting_vcard"/>
<item type="id" name="dialog_fail_to_export_with_reason"/>
+ <!-- For PhoneNumberInteraction -->
+ <item type="id" name="dialog_phone_number_call_disambiguation"/>
+
+ <!-- For PhoneNumberMessageSendInteraction -->
+ <item type="id" name="dialog_phone_number_message_disambiguation"/>
+
+ <!-- Dialog Manager Ids -->
+ <item type="id" name="dialog_manager_id_1"/>
+ <item type="id" name="dialog_manager_id_2"/>
</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 2a426fe..2458d80 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -68,6 +68,12 @@
editing a contact. This string represents the built in way to edit the contact. -->
<string name="editContactDescription">Edit contact</string>
+ <!-- The caption in the edit-email screen -->
+ <string name="edit_email_caption">Email address:</string>
+
+ <!-- The caption of the custom type -->
+ <string name="edit_email_type_caption">Type:</string>
+
<!-- The description presented to the user in the Intent choose when there are multiple activities that allow
creating a new contact. This string represents the built in way to create the contact. -->
<string name="insertContactDescription">Create contact</string>
@@ -329,17 +335,17 @@
<!-- Displayed at the top of the contacts showing the total number of contacts found when "Only contacts with phones" not selected -->
<plurals name="listFoundAllContacts">
- <item quantity="one">Found 1 contact</item>
- <item quantity="other">Found <xliff:g id="count">%d</xliff:g> contacts</item>
+ <item quantity="one">1 found</item>
+ <item quantity="other"><xliff:g id="count">%d</xliff:g> found</item>
</plurals>
<!-- Displayed at the top of the contacts showing the zero total number of contacts found when "Only contacts with phones" not selected -->
- <string name="listFoundAllContactsZero">Contact not found</string>
+ <string name="listFoundAllContactsZero">Not found</string>
<!-- Displayed at the top of the contacts showing the total number of contacts found when typing search query -->
<plurals name="searchFoundContacts">
- <item quantity="one">1 contact</item>
- <item quantity="other"><xliff:g id="count">%d</xliff:g> contacts</item>
+ <item quantity="one">1 found</item>
+ <item quantity="other"><xliff:g id="count">%d</xliff:g> found</item>
</plurals>
<!-- The description text for the contacts tab. Space is limited for this string, so the shorter the better -->
@@ -428,7 +434,7 @@
<string name="noContactsHelpText">"You don't have any contacts to display.\n\nTo add contacts, press <font fgcolor="#ffffffff"><b>Menu</b></font> and touch:\n
\n<li><font fgcolor="#ffffffff"><b>Accounts</b></font> to add or configure an account with contacts you can sync to the phone\n</li>
\n<li><font fgcolor="#ffffffff"><b>New contact</b></font> to create a new contact from scratch\n</li>
- \n<li><font fgcolor="#ffffffff"><b>Import/Export</b></font>\n</li>"
+ \n<li><font fgcolor="#ffffffff"><b>Import/Export</b></font> to import contacts from your SIM or SD card\n</li>"
</string>
<!-- Displayed full screen when the user has no contacts and they are displaying the My Contacts group, and contact syncing is enabled -->
@@ -436,14 +442,14 @@
\n<li><font fgcolor="#ffffffff"><b>Accounts</b></font> to add or configure an account with contacts you can sync to the phone\n</li>
\n<li><font fgcolor="#ffffffff"><b>Display options</b></font> to change which contacts are visible\n</li>
\n<li><font fgcolor="#ffffffff"><b>New contact</b></font> to create a new contact from scratch\n</li>
- \n<li><font fgcolor="#ffffffff"><b>Import/Export</b></font>\n</li>"
+ \n<li><font fgcolor="#ffffffff"><b>Import/Export</b></font> to import contacts from your SIM or SD card\n</li>"
</string>
<!-- Displayed full screen when the user has no contacts and they are displaying the My Contacts group, and contact syncing is disabled, and there is no sim card (cdma)-->
<string name="noContactsNoSimHelpText">"You don't have any contacts to display.\n\nTo add contacts, press <font fgcolor="#ffffffff"><b>Menu</b></font> and touch:\n
\n<li><font fgcolor="#ffffffff"><b>Accounts</b></font> to add or configure an account with contacts you can sync to the phone\n</li>
\n<li><font fgcolor="#ffffffff"><b>New contact</b></font> to create a new contact from scratch\n</li>
- \n<li><font fgcolor="#ffffffff"><b>Import/Export</b></font>\n</li>"
+ \n<li><font fgcolor="#ffffffff"><b>Import/Export</b></font> to import contacts from your SD card\n</li>"
</string>
<!-- Displayed full screen when the user has no contacts and they are displaying the My Contacts group, and contact syncing is enabled, and there is no sim card (cdma) -->
@@ -451,7 +457,7 @@
\n<li><font fgcolor="#ffffffff"><b>Accounts</b></font> to add or configure an account with contacts you can sync to the phone\n</li>
\n<li><font fgcolor="#ffffffff"><b>Display options</b></font> to change which contacts are visible\n</li>
\n<li><font fgcolor="#ffffffff"><b>New contact</b></font> to create a new contact from scratch\n</li>
- \n<li><font fgcolor="#ffffffff"><b>Import/Export</b></font>\n</li>"
+ \n<li><font fgcolor="#ffffffff"><b>Import/Export</b></font> to import contacts from your SD card\n</li>"
</string>
<!-- Displayed full screen when the user has no favorites and they are displaying the favorites tab -->
@@ -516,7 +522,7 @@
<string name="returnCall">Return call</string>
<!-- A nicely formatted call duration displayed when viewing call details. For example "42 mins 28 secs" -->
- <string name="callDetailsDurationFormat"><xliff:g id="minutes" example="42">%1$s</xliff:g> mins <xliff:g id="seconds" example="28">%2$s</xliff:g> secs</string>
+ <string name="callDetailsDurationFormat"><xliff:g id="minutes" example="42">%s</xliff:g> mins <xliff:g id="seconds" example="28">%s</xliff:g> secs</string>
<!-- A list separator for the Favorites tab indicating that items below it are frequently contacted contacts rather than starred contacts -->
<string name="favoritesFrquentSeparator">Frequently contacted</string>
@@ -662,7 +668,7 @@
<!-- Dialog message shown when SDcard does not exist -->
<string name="no_sdcard_message">No SD card detected</string>
- <!-- Dialog title shown when searching VCard data from SD Card -->
+ <!-- Dialog title shown when searching vCard data from SD Card -->
<string name="searching_vcard_title">Searching for vCard</string>
<!-- Action string for selecting SIM for importing contacts -->
@@ -692,13 +698,13 @@
than one vCard files available in the system. -->
<string name="import_all_vcard_string">Import all vCard files</string>
- <!-- Dialog message shown when searching VCard data from SD Card -->
+ <!-- Dialog message shown when searching vCard data from SD Card -->
<string name="searching_vcard_message">Searching for vCard data on SD card</string>
- <!-- Dialog title shown when scanning VCard data failed. -->
+ <!-- Dialog title shown when scanning vCard data failed. -->
<string name="scanning_sdcard_failed_title">Scanning SD card failed</string>
- <!-- Dialog message shown when searching VCard data failed.
+ <!-- Dialog message shown when searching vCard data failed.
An exact reason for the failure should -->
<string name="scanning_sdcard_failed_message">Scanning SD card failed (Reason: \"<xliff:g id="fail_reason">%s</xliff:g>\")</string>
@@ -706,6 +712,10 @@
emitted some I/O error. Exact reason will be appended by the system. -->
<string name="fail_reason_io_error">I/O Error</string>
+ <!-- Failure reason show when Contacts app (especially vCard importer) encountered
+ low memory problem and could not proceed its import procedure. -->
+ <string name="fail_reason_low_memory_during_import">Memory is insufficient (the file may be too large)</string>
+
<!-- The failed reason shown when vCard parser was not able to be parsed by the current vCard
implementation. This might happen even when the input vCard is completely valid, though
we believe it is rather rare in the actual world. -->
@@ -721,6 +731,9 @@
(with extension ".vcf" in SDCard.) -->
<string name="fail_reason_no_vcard_file">No vCard file found on the SD card</string>
+ <!-- Fail reason shown when vCard importer failed to look over meta information stored in vCard file(s). -->
+ <string name="fail_reason_failed_to_collect_vcard_meta_info">Failed to collect meta information of given vCard file(s).</string>
+
<!-- The failed reason shown when the import of some of vCard files failed during multiple vCard
files import. It includes the case where all files were failed to be imported. -->
<string name="fail_reason_failed_to_read_files">One or more files failed to be imported (%s).</string>
@@ -728,28 +741,47 @@
<!-- The failed reason which should not be shown but it may in some buggy condition. -->
<string name="fail_reason_unknown">Unknown error</string>
- <!-- Dialog title shown when a user is asked to select VCard file -->
+ <!-- Dialog title shown when a user is asked to select vCard file -->
<string name="select_vcard_title">Select vCard file</string>
- <!-- The message shown while reading a vCard file/entry. The first argument is like
- "Reading VCard" or "Reading VCard files" and the second is the display name of the current
- data being parsed -->
- <string name="progress_shower_message"><xliff:g id="action" example="Reading VCard">%1$s</xliff:g>\n<xliff:g id="filename" example="foo.vcf">%2$s</xliff:g></string>
+ <!-- The title shown when vCard importer is caching files to be imported into local temporary
+ data storage. -->
+ <string name="caching_vcard_title">Caching vCard(s) to local temporary storage</string>
- <!-- Dialog title shown when reading VCard data -->
- <string name="reading_vcard_title">Reading vCard</string>
+ <!-- The message shown when vCard importer is caching files to be imported into local temporary
+ data storage. -->
+ <string name="caching_vcard_message">Importer is caching vCard(s) to local temporary storage. Actual import will start soon.</string>
- <!-- Dialog message shown when reading a VCard file -->
- <string name="reading_vcard_message">Reading vCard file(s)</string>
+ <!-- The message shown while importing vCard(s).
+ First argument is current index of contacts to be imported.
+ Second argument is the total number of contacts.
+ Third argument is the name of a contact which is being read. -->
+ <string name="progress_notifier_message">Importing <xliff:g id="current_number">%s</xliff:g>/<xliff:g id="total_number">%s</xliff:g>: <xliff:g id="name" example="Joe Due">%s</xliff:g></string>
- <!-- Dialog title shown when reading VCard data failed -->
- <string name="reading_vcard_failed_title">Reading of vCard data has failed</string>
+ <!-- Description shown when importing vCard data.
+ The argument is the name of a contact which is being read. -->
+ <string name="importing_vcard_description">Importing <xliff:g id="name" example="Joe Due">%s</xliff:g></string>
- <!-- Message while reading one vCard file "(current number) of (total number) contacts" The order of "current number" and "total number" cannot be changed (like "total: (total number), current: (current number)")-->
- <string name="reading_vcard_contacts"><xliff:g id="current_number">%1$s</xliff:g> of <xliff:g id="total_number">%2$s</xliff:g> contacts</string>
+ <!-- Dialog title shown when reading vCard data failed -->
+ <string name="reading_vcard_failed_title">Failed to Read vCard data</string>
- <!-- Message while reading multiple vCard files "(current number) of (total number) files" The order of "current number" and "total number" cannot be changed (like "total: (total number), current: (current number)")-->
- <string name="reading_vcard_files"><xliff:g id="current_number">%1$s</xliff:g> of <xliff:g id="total_number">%2$s</xliff:g> files</string>
+ <!-- The title shown when reading vCard is canceled (probably by a user) -->
+ <string name="reading_vcard_canceled_title">Reading vCard data was canceled</string>
+
+ <!-- The title shown when reading vCard finished -->
+ <string name="importing_vcard_finished_title">Finished importing vCard</string>
+
+ <!-- The title shown when importing vCard is canceled (probably by a user) -->
+ <string name="importing_vcard_canceled_title">Reading vCard data was canceled</string>
+
+ <!-- The message shown when vCard importer started running. -->
+ <string name="vcard_importer_start_message">vCard importer started.</string>
+
+ <!-- The message shown when additional vCard to be imported is given during the import for others -->
+ <string name="vcard_importer_will_start_message">vCard importer will import the vCard after a while.</string>
+
+ <!-- The percentage, used for expressing the progress of vCard import/export. -->
+ <string name="percentage">%s%%</string>
<!-- Dialog title shown when a user confirms whether he/she export Contact data -->
<string name="confirm_export_title">Confirm export</string>
@@ -777,6 +809,12 @@
mention it here. -->
<string name="fail_reason_too_long_filename">Required filename is too long (\"<xliff:g id="filename">%s</xliff:g>\")</string>
+ <!-- The message shown when vCard importer started running. -->
+ <string name="vcard_exporter_start_message">vCard exporter started.</string>
+
+ <!-- The title shown when reading vCard is canceled (probably by a user) -->
+ <string name="exporting_vcard_finished_title">Finished exporting vCard</string>
+
<!-- Dialog title shown when the application is exporting contact data outside -->
<string name="exporting_contact_list_title">Exporting contact data</string>
@@ -807,10 +845,18 @@
<!-- The failed reason shown when vCard importer/exporter could not open the file
specified by a user. The file name should be in the message. -->
- <string name="fail_reason_could_not_open_file">Could not open \"<xliff:g id="file_name">%1$s</xliff:g>\": <xliff:g id="exact_reason">%2$s</xliff:g></string>
+ <string name="fail_reason_could_not_open_file">Could not open \"<xliff:g id="file_name">%s</xliff:g>\": <xliff:g id="exact_reason">%s</xliff:g></string>
<!-- Message in progress bar while exporting contact list to a file "(current number) of (total number) contacts" The order of "current number" and "total number" cannot be changed (like "total: (total number), current: (current number)")-->
- <string name="exporting_contact_list_progress"><xliff:g id="current_number">%1$s</xliff:g> of <xliff:g id="total_number">%2$s</xliff:g> contacts</string>
+ <string name="exporting_contact_list_progress"><xliff:g id="current_number">%s</xliff:g> of <xliff:g id="total_number">%s</xliff:g> contacts</string>
+
+ <!-- Title shown in a Dialog confirming a user's cancel request toward existing vCard import. -->
+ <string name="cancel_import_confirmation_title">Canceling import vCard</string>
+
+ <!-- Message shown in a Dialog confirming a user's cancel request toward existing vCard import.
+ The argument is the Uri for the vCard import the user wants to cancel.
+ -->
+ <string name="cancel_import_confirmation_message">Are you sure to cancel importing vCard?</string>
<!-- The string used to describe Contacts as a searchable item within system search settings. -->
<string name="search_settings_description">Names of your contacts</string>
@@ -1042,6 +1088,8 @@
<!-- Generic action string for starting an IM chat -->
<string name="chat">Chat</string>
+ <!-- Field title for the full postal address of a contact [CHAR LIMIT=64]-->
+ <string name="postal_address">Address</string>
<!-- Field title for the street of a structured postal address of a contact -->
<string name="postal_street">Street</string>
<!-- Field title for the PO box of a structured postal address of a contact -->
@@ -1057,6 +1105,8 @@
<!-- Field title for the country of a structured postal address of a contact -->
<string name="postal_country">Country</string>
+ <!-- Field title for the full name of a contact [CHAR LIMIT=64]-->
+ <string name="full_name">Name</string>
<!-- Field title for the given name of a contact -->
<string name="name_given">Given name</string>
<!-- Field title for the family name of a contact -->
@@ -1080,6 +1130,9 @@
<!-- String describing which account a contact came from when editing it -->
<string name="from_account_format">from <xliff:g id="source" example="user@gmail.com">%1$s</xliff:g></string>
+ <!-- String describing both account type and account name -->
+ <string name="account_type_and_name"><xliff:g id="source" example="Gmail">%1$s</xliff:g> contact from <xliff:g id="source" example="user@gmail.com">%2$s</xliff:g></string>
+
<!-- Checkbox asking the user if they want to display a particular photo for a contact -->
<string name="use_photo_as_primary">Use this photo</string>
@@ -1141,4 +1194,102 @@
<!-- Title shown in the search result activity of contacts app while searching -->
<string name="search_results_searching">Searching...</string>
+
+ <!-- Message of progress dialog for multiple picker -->
+ <string name="adding_recipients">"Loading \u2026"</string>
+
+ <!-- Label to display only selection in multiple picker -->
+ <string name="menu_display_selected">"Show selected"</string>
+
+ <!-- Label to display all recipients in multiple picker -->
+ <string name="menu_display_all">"Show all"</string>
+
+ <!-- Label to select all contacts in multiple picker -->
+ <string name="menu_select_all">"Select all"</string>
+
+ <!-- Label to clear all selection in multiple picker -->
+ <string name="menu_select_none">"Unselect all"</string>
+
+ <!-- Label to display how many selected in multiple picker -->
+ <plurals name="multiple_picker_title">
+ <!-- number of selected recipients is one -->
+ <item quantity="one">"1 recipient selected"</item>
+ <!-- number of selected recipients is not equal to one -->
+ <item quantity="other"><xliff:g id="count">%d</xliff:g>" recipients selected"</item>
+ </plurals>
+
+ <!-- Separator label to display unknown recipients in multiple picker -->
+ <string name="unknown_contacts_separator">"Unknown contacts"</string>
+
+ <!-- The text displayed when the contacts list is empty while displaying only selected contacts in multiple picker -->
+ <string name="no_contacts_selected">"No contacts selected."</string>
+
+ <!-- The add field button shown in the editor under each editable Raw Contact -->
+ <string name="add_field">Add information</string>
+
+ <!-- Attbution of a contact status update, when the time of update is unknown -->
+ <string name="contact_status_update_attribution">via <xliff:g id="source" example="Google Talk">%1$s</xliff:g></string>
+
+ <!-- Attbution of a contact status update, when the time of update is known -->
+ <string name="contact_status_update_attribution_with_date"><xliff:g id="date" example="3 hours ago">%1$s</xliff:g> via <xliff:g id="source" example="Google Talk">%2$s</xliff:g></string>
+
+ <!-- String describing the Edit push button
+
+ Used by AccessibilityService to announce the purpose of the view.
+ -->
+ <string name="description_edit">edit</string>
+
+ <!-- String describing the Star/Favorite checkbox
+
+ Used by AccessibilityService to announce the purpose of the view.
+ -->
+ <string name="description_star">favorite</string>
+
+ <!-- The title of the Edit-Contact screen -->
+ <string name="edit_contact">Edit contact</string>
+
+ <!-- Shows how many contacts have been merged. The value 1 is not shown but should be translated
+ anyway if we change our mind later -->
+ <plurals name="merge_info">
+ <item quantity="one">not merged</item>
+ <item quantity="other">merged from <xliff:g id="count">%0$d</xliff:g> sources</item>
+ </plurals>
+
+ <!-- Command to delete a RawContact (a Fragment of a Contact) -->
+ <string name="edit_delete_rawcontact">Delete</string>
+
+ <!-- Shown in a Toast to indicate an error while trying to save the Data -->
+ <string name="edit_error_saving">Error saving</string>
+
+ <!-- Text in the editor to show the structured editor -->
+ <string name="edit_structured_editor_button">...</string>
+
+ <!-- The name of the invisible local contact directory -->
+ <string name="local_invisible_directory">Other</string>
+
+ <!-- The heading of the contact aggregation suggestions section in Contact editor. [CHAR LIMIT=128]-->
+ <plurals name="aggregation_suggestion_title">
+ <item quantity="one">Similar contact</item>
+ <item quantity="other">Similar contacts</item>
+ </plurals>
+
+ <!-- The heading of the aggregation suggestion section of the Contact editor
+ indicating the contact that will be joined with the current contact. [CHAR LIMIT=128]-->
+ <string name="aggregation_suggestion_joined_title">Joined contact:</string>
+
+ <!-- The button next to a contact aggregation suggestion in Contact editor. [CHAR LIMIT=12]-->
+ <string name="aggregation_suggestion_join_button">Join</string>
+
+ <!-- The button next to the message about multiple contact aggregation suggestions in Contact editor. [CHAR LIMIT=12]-->
+ <string name="aggregation_suggestion_view_button">View</string>
+
+ <!-- Primary alphabet of this language. Each of these characters always has its own section in
+ the visual scroll control next to the contact list. These letters must be uppercase.
+ While there is no hard limit on the number of characters, there should not be more than
+ 40. If the language requires more, this string should instead be empty so that the
+ visual scroll control adapts to the contents.
+ This text must only contain letters that can appear as sections, otherwise they would
+ only be empty.
+ Translations require extensive QA! -->
+ <string name="visualScrollerAlphabet">A;B;C;D;E;F;G;H;I;J;K;L;M;N;O;P;Q;R;S;T;U;V;W;X;Y;Z</string>
</resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index ad4f4f6..b7df91f 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -24,6 +24,10 @@
<item name="android:windowAnimationStyle">@style/ContactsSearchAnimation</item>
</style>
+ <style name="EmptyButton">
+ <item name="android:background">@drawable/btn_circle</item>
+ </style>
+
<style name="MinusButton">
<item name="android:background">@drawable/btn_circle</item>
<item name="android:src">@drawable/ic_btn_round_minus</item>
@@ -100,4 +104,13 @@
<style name="DummyAnimation">
<item name="android:windowExitAnimation">@anim/dummy_animation</item>
</style>
+
+ <style name="ContactBrowserTheme" parent="@android:Theme">
+ </style>
+
+ <style name="ContactPickerTheme" parent="@android:Theme">
+ </style>
+
+ <style name="ContactsPreferencesTheme" parent="@android:Theme">
+ </style>
</resources>
diff --git a/src/com/android/contacts/CallContactActivity.java b/src/com/android/contacts/CallContactActivity.java
new file mode 100644
index 0000000..76f0892
--- /dev/null
+++ b/src/com/android/contacts/CallContactActivity.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2009 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;
+
+import com.android.contacts.interactions.PhoneNumberInteraction;
+
+import android.app.Activity;
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnDismissListener;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.ContactsContract.Contacts;
+
+/**
+ * An interstitial activity used when the user selects a QSB search suggestion using
+ * a call button.
+ */
+public class CallContactActivity extends Activity implements OnDismissListener {
+
+ private PhoneNumberInteraction mPhoneNumberInteraction;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mPhoneNumberInteraction = new PhoneNumberInteraction(this, false, this);
+
+ Uri contactUri = getIntent().getData();
+ if (contactUri == null) {
+ finish();
+ }
+
+ // If we are being invoked with a saved state, rely on Activity to restore it
+ if (savedInstanceState != null) {
+ return;
+ }
+
+ if (Contacts.CONTENT_ITEM_TYPE.equals(getContentResolver().getType(contactUri))) {
+ mPhoneNumberInteraction.startInteraction(contactUri);
+ } else {
+ startActivity(new Intent(Intent.ACTION_CALL_PRIVILEGED, contactUri));
+ finish();
+ }
+ }
+
+ public void onDismiss(DialogInterface dialog) {
+ if (!isChangingConfigurations()) {
+ finish();
+ }
+ }
+
+ @Override
+ protected Dialog onCreateDialog(int id, Bundle args) {
+ return mPhoneNumberInteraction.onCreateDialog(id, args);
+ }
+
+ @Override
+ protected void onPrepareDialog(int id, Dialog dialog, Bundle args) {
+ mPhoneNumberInteraction.onPrepareDialog(id, dialog, args);
+ }
+}
diff --git a/src/com/android/contacts/Collapser.java b/src/com/android/contacts/Collapser.java
index 3872dfd..d072dce 100644
--- a/src/com/android/contacts/Collapser.java
+++ b/src/com/android/contacts/Collapser.java
@@ -16,7 +16,6 @@
package com.android.contacts;
-import java.util.HashMap;
import java.util.Iterator;
import java.util.ArrayList;
@@ -44,7 +43,7 @@
/**
* Collapses a list of Collapsible items into a list of collapsed items. Items are collapsed
- * if {@link Collapsible#shouldCollapseWith(Object) return strue, and are collapsed
+ * if {@link Collapsible#shouldCollapseWith(Object)} returns strue, and are collapsed
* through the {@Link Collapsible#collapseWith(Object)} function implemented by the data item.
*
* @param list ArrayList of Objects of type <T extends Collapsible<T>> to be collapsed.
diff --git a/src/com/android/contacts/ContactEntryAdapter.java b/src/com/android/contacts/ContactEntryAdapter.java
deleted file mode 100644
index 34ee505..0000000
--- a/src/com/android/contacts/ContactEntryAdapter.java
+++ /dev/null
@@ -1,262 +0,0 @@
-/*
- * Copyright (C) 2007 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;
-
-import android.content.Context;
-import android.net.Uri;
-import android.os.Parcel;
-import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.Data;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.BaseAdapter;
-
-import java.util.ArrayList;
-
-public abstract class ContactEntryAdapter<E extends ContactEntryAdapter.Entry>
- extends BaseAdapter {
-
- protected ArrayList<ArrayList<E>> mSections;
- protected LayoutInflater mInflater;
- protected Context mContext;
- protected boolean mSeparators;
-
- /**
- * Base class for adapter entries.
- */
- public static class Entry {
- public int type = -1;
- public String label;
- public String data;
- public Uri uri;
- public long id = 0;
- public long contactId;
- public int maxLines = 1;
- public String mimetype;
-
- /**
- * Helper for making subclasses parcelable.
- */
- protected void writeToParcel(Parcel p) {
- p.writeInt(type);
- p.writeString(label);
- p.writeString(data);
- p.writeParcelable(uri, 0);
- p.writeLong(id);
- p.writeInt(maxLines);
- p.writeString(mimetype);
- }
-
- /**
- * Helper for making subclasses parcelable.
- */
- protected void readFromParcel(Parcel p) {
- final ClassLoader loader = getClass().getClassLoader();
- type = p.readInt();
- label = p.readString();
- data = p.readString();
- uri = p.readParcelable(loader);
- id = p.readLong();
- maxLines = p.readInt();
- mimetype = p.readString();
- }
- }
-
- ContactEntryAdapter(Context context, ArrayList<ArrayList<E>> sections, boolean separators) {
- mContext = context;
- mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- mSections = sections;
- mSeparators = separators;
- }
-
- /**
- * Resets the section data.
- *
- * @param sections the section data
- */
- public final void setSections(ArrayList<ArrayList<E>> sections, boolean separators) {
- mSections = sections;
- mSeparators = separators;
- notifyDataSetChanged();
- }
-
- /**
- * Resets the section data and returns the position of the given entry.
- *
- * @param sections the section data
- * @param entry the entry to return the position for
- * @return the position of entry, or -1 if it isn't found
- */
- public final int setSections(ArrayList<ArrayList<E>> sections, E entry) {
- mSections = sections;
- notifyDataSetChanged();
-
- int numSections = mSections.size();
- int position = 0;
- for (int i = 0; i < numSections; i++) {
- ArrayList<E> section = mSections.get(i);
- int sectionSize = section.size();
- for (int j = 0; j < sectionSize; j++) {
- E e = section.get(j);
- if (e.equals(entry)) {
- position += j;
- return position;
- }
- }
- position += sectionSize;
- }
- return -1;
- }
-
- /**
- * @see android.widget.ListAdapter#getCount()
- */
- public final int getCount() {
- return countEntries(mSections, mSeparators);
- }
-
- /**
- * @see android.widget.ListAdapter#hasSeparators()
- */
- @Override
- public final boolean areAllItemsEnabled() {
- return mSeparators == false;
- }
-
- /**
- * @see android.widget.ListAdapter#isSeparator(int)
- */
- @Override
- public final boolean isEnabled(int position) {
- if (!mSeparators) {
- return true;
- }
-
- int numSections = mSections.size();
- for (int i = 0; i < numSections; i++) {
- ArrayList<E> section = mSections.get(i);
- int sectionSize = section.size();
- if (sectionSize == 1) {
- // The section only contains a separator and nothing else, skip it
- continue;
- }
- if (position == 0) {
- // The first item in a section is always the separator
- return false;
- }
- position -= sectionSize;
- }
- return true;
- }
-
- /**
- * @see android.widget.ListAdapter#getItem(int)
- */
- public final Object getItem(int position) {
- return getEntry(mSections, position, mSeparators);
- }
-
- /**
- * Get the entry for the given position.
- *
- * @param sections the list of sections
- * @param position the position for the desired entry
- * @return the ContactEntry for the given position
- */
- public final static <T extends Entry> T getEntry(ArrayList<ArrayList<T>> sections,
- int position, boolean separators) {
- int numSections = sections.size();
- for (int i = 0; i < numSections; i++) {
- ArrayList<T> section = sections.get(i);
- int sectionSize = section.size();
- if (separators && sectionSize == 1) {
- // The section only contains a separator and nothing else, skip it
- continue;
- }
- if (position < section.size()) {
- return section.get(position);
- }
- position -= section.size();
- }
- return null;
- }
-
- /**
- * Get the count of entries in all sections
- *
- * @param sections the list of sections
- * @return the count of entries in all sections
- */
- public static <T extends Entry> int countEntries(ArrayList<ArrayList<T>> sections,
- boolean separators) {
- int count = 0;
- int numSections = sections.size();
- for (int i = 0; i < numSections; i++) {
- ArrayList<T> section = sections.get(i);
- int sectionSize = section.size();
- if (separators && sectionSize == 1) {
- // The section only contains a separator and nothing else, skip it
- continue;
- }
- count += sections.get(i).size();
- }
- return count;
- }
-
- /**
- * @see android.widget.ListAdapter#getItemId(int)
- */
- public final long getItemId(int position) {
- Entry entry = getEntry(mSections, position, mSeparators);
- if (entry != null) {
- return entry.id;
- } else {
- return -1;
- }
- }
-
- /**
- * @see android.widget.ListAdapter#getView(int, View, ViewGroup)
- */
- public View getView(int position, View convertView, ViewGroup parent) {
- View v;
- if (convertView == null) {
- v = newView(position, parent);
- } else {
- v = convertView;
- }
- bindView(v, getEntry(mSections, position, mSeparators));
- return v;
- }
-
- /**
- * Create a new view for an entry.
- *
- * @parent the parent ViewGroup
- * @return the newly created view
- */
- protected abstract View newView(int position, ViewGroup parent);
-
- /**
- * Binds the data from an entry to a view.
- *
- * @param view the view to display the entry in
- * @param entry the data to bind
- */
- protected abstract void bindView(View view, E entry);
-}
diff --git a/src/com/android/contacts/ContactEntryListView.java b/src/com/android/contacts/ContactEntryListView.java
new file mode 100644
index 0000000..21ff25e
--- /dev/null
+++ b/src/com/android/contacts/ContactEntryListView.java
@@ -0,0 +1,87 @@
+/*
+ * 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;
+
+import com.android.contacts.list.ContactEntryListAdapter;
+import com.android.contacts.widget.PinnedHeaderListView;
+import com.android.contacts.widget.TextHighlightingAnimation;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.AbsListView;
+import android.widget.ListAdapter;
+
+/**
+ * A custom list view for a list of contacts or contact-related entries. It handles
+ * animation of names on scroll.
+ */
+public class ContactEntryListView extends PinnedHeaderListView {
+
+ private static final int TEXT_HIGHLIGHTING_ANIMATION_DURATION = 350;
+
+ private final TextHighlightingAnimation mHighlightingAnimation =
+ new ContactNameHighlightingAnimation(this, TEXT_HIGHLIGHTING_ANIMATION_DURATION);
+
+ private boolean mHighlightNamesWhenScrolling;
+
+ public ContactEntryListView(Context context) {
+ this(context, null);
+ }
+
+ public ContactEntryListView(Context context, AttributeSet attrs) {
+ this(context, attrs, com.android.internal.R.attr.listViewStyle);
+ }
+
+ public ContactEntryListView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ setPinnedHeaderBackgroundColor(
+ context.getResources().getColor(R.color.pinned_header_background));
+ }
+
+ public TextHighlightingAnimation getTextHighlightingAnimation() {
+ return mHighlightingAnimation;
+ }
+
+ public boolean getHighlightNamesWhenScrolling() {
+ return mHighlightNamesWhenScrolling;
+ }
+
+ public void setHighlightNamesWhenScrolling(boolean flag) {
+ mHighlightNamesWhenScrolling = flag;
+ }
+
+ @Override
+ public void setAdapter(ListAdapter adapter) {
+ super.setAdapter(adapter);
+ if (adapter instanceof ContactEntryListAdapter) {
+ ((ContactEntryListAdapter)adapter)
+ .setTextWithHighlightingFactory(mHighlightingAnimation);
+ }
+ }
+
+ @Override
+ public void onScrollStateChanged(AbsListView view, int scrollState) {
+ super.onScrollStateChanged(view, scrollState);
+ if (mHighlightNamesWhenScrolling) {
+ if (scrollState != OnScrollListener.SCROLL_STATE_IDLE) {
+ mHighlightingAnimation.startHighlighting();
+ } else {
+ mHighlightingAnimation.stopHighlighting();
+ }
+ }
+ }
+}
diff --git a/src/com/android/contacts/ContactListEmptyView.java b/src/com/android/contacts/ContactListEmptyView.java
new file mode 100644
index 0000000..40d5152
--- /dev/null
+++ b/src/com/android/contacts/ContactListEmptyView.java
@@ -0,0 +1,113 @@
+/*
+ * 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;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.IContentService;
+import android.os.RemoteException;
+import android.provider.ContactsContract;
+import android.telephony.TelephonyManager;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.widget.ScrollView;
+import android.widget.TextView;
+
+/**
+ * Displays a message when there is nothing to display in a contact list.
+ */
+public class ContactListEmptyView extends ScrollView {
+
+ private static final String TAG = "ContactListEmptyView";
+
+ public ContactListEmptyView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public void hide() {
+ TextView empty = (TextView) findViewById(R.id.emptyText);
+ empty.setVisibility(GONE);
+ }
+
+ public void show(boolean searchMode, boolean displayOnlyPhones,
+ boolean isFavoritesMode, boolean isQueryMode, boolean isShortcutAction,
+ boolean isMultipleSelectionEnabled, boolean showSelectedOnly) {
+ if (searchMode) {
+ return;
+ }
+
+ TextView empty = (TextView) findViewById(R.id.emptyText);
+ Context context = getContext();
+ if (displayOnlyPhones) {
+ empty.setText(context.getText(R.string.noContactsWithPhoneNumbers));
+ } else if (isFavoritesMode) {
+ empty.setText(context.getText(R.string.noFavoritesHelpText));
+ } else if (isQueryMode) {
+ empty.setText(context.getText(R.string.noMatchingContacts));
+ } if (isMultipleSelectionEnabled) {
+ if (showSelectedOnly) {
+ empty.setText(context.getText(R.string.no_contacts_selected));
+ } else {
+ empty.setText(context.getText(R.string.noContactsWithPhoneNumbers));
+ }
+ } else {
+ TelephonyManager telephonyManager =
+ (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+ boolean hasSim = telephonyManager.hasIccCard();
+ if (isSyncActive()) {
+ if (isShortcutAction) {
+ // Help text is the same no matter whether there is SIM or not.
+ empty.setText(
+ context.getText(R.string.noContactsHelpTextWithSyncForCreateShortcut));
+ } else if (hasSim) {
+ empty.setText(context.getText(R.string.noContactsHelpTextWithSync));
+ } else {
+ empty.setText(context.getText(R.string.noContactsNoSimHelpTextWithSync));
+ }
+ } else {
+ if (isShortcutAction) {
+ // Help text is the same no matter whether there is SIM or not.
+ empty.setText(context.getText(R.string.noContactsHelpTextForCreateShortcut));
+ } else if (hasSim) {
+ empty.setText(context.getText(R.string.noContactsHelpText));
+ } else {
+ empty.setText(context.getText(R.string.noContactsNoSimHelpText));
+ }
+ }
+ }
+ empty.setVisibility(VISIBLE);
+ }
+
+ private boolean isSyncActive() {
+ Account[] accounts = AccountManager.get(getContext()).getAccounts();
+ if (accounts != null && accounts.length > 0) {
+ IContentService contentService = ContentResolver.getContentService();
+ for (Account account : accounts) {
+ try {
+ if (contentService.isSyncActive(account, ContactsContract.AUTHORITY)) {
+ return true;
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Could not get the sync status");
+ }
+ }
+ }
+ return false;
+ }
+}
diff --git a/src/com/android/contacts/ContactNameHighlightingAnimation.java b/src/com/android/contacts/ContactNameHighlightingAnimation.java
new file mode 100644
index 0000000..68664b3
--- /dev/null
+++ b/src/com/android/contacts/ContactNameHighlightingAnimation.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2007 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;
+
+import com.android.contacts.list.ContactListItemView;
+import com.android.contacts.widget.TextHighlightingAnimation;
+
+import android.view.View;
+import android.widget.ListView;
+
+/**
+ * A {@link TextHighlightingAnimation} that redraws just the contact display name in a
+ * list item.
+ */
+public class ContactNameHighlightingAnimation extends TextHighlightingAnimation {
+ private final ListView mListView;
+
+ public ContactNameHighlightingAnimation(ListView listView, int duration) {
+ super(duration);
+ this.mListView = listView;
+ }
+
+ /**
+ * Redraws all visible items of the list corresponding to contacts
+ */
+ @Override
+ protected void invalidate() {
+ int childCount = mListView.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View itemView = mListView.getChildAt(i);
+ if (itemView instanceof ContactListItemView) {
+ final ContactListItemView view = (ContactListItemView)itemView;
+ view.getNameTextView().invalidate();
+ }
+ }
+ }
+
+ @Override
+ protected void onAnimationStarted() {
+ mListView.setScrollingCacheEnabled(false);
+ }
+
+ @Override
+ protected void onAnimationEnded() {
+ mListView.setScrollingCacheEnabled(true);
+ }
+}
diff --git a/src/com/android/contacts/ContactsListActivity.java b/src/com/android/contacts/ContactsListActivity.java
deleted file mode 100644
index 0d2c7eb..0000000
--- a/src/com/android/contacts/ContactsListActivity.java
+++ /dev/null
@@ -1,3585 +0,0 @@
-/*
- * Copyright (C) 2007 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;
-
-import com.android.contacts.TextHighlightingAnimation.TextWithHighlighting;
-import com.android.contacts.model.ContactsSource;
-import com.android.contacts.model.Sources;
-import com.android.contacts.ui.ContactsPreferences;
-import com.android.contacts.ui.ContactsPreferencesActivity;
-import com.android.contacts.ui.ContactsPreferencesActivity.Prefs;
-import com.android.contacts.util.AccountSelectionUtil;
-import com.android.contacts.util.Constants;
-
-import android.accounts.Account;
-import android.accounts.AccountManager;
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.app.ListActivity;
-import android.app.SearchManager;
-import android.content.AsyncQueryHandler;
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.IContentService;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.UriMatcher;
-import android.content.res.ColorStateList;
-import android.content.res.Resources;
-import android.database.CharArrayBuffer;
-import android.database.ContentObserver;
-import android.database.Cursor;
-import android.database.MatrixCursor;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Rect;
-import android.graphics.Typeface;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.net.Uri.Builder;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Parcelable;
-import android.os.RemoteException;
-import android.preference.PreferenceManager;
-import android.provider.ContactsContract;
-import android.provider.Settings;
-import android.provider.Contacts.ContactMethods;
-import android.provider.Contacts.People;
-import android.provider.Contacts.PeopleColumns;
-import android.provider.Contacts.Phones;
-import android.provider.ContactsContract.ContactCounts;
-import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.Data;
-import android.provider.ContactsContract.Intents;
-import android.provider.ContactsContract.ProviderStatus;
-import android.provider.ContactsContract.RawContacts;
-import android.provider.ContactsContract.SearchSnippetColumns;
-import android.provider.ContactsContract.CommonDataKinds.Email;
-import android.provider.ContactsContract.CommonDataKinds.Nickname;
-import android.provider.ContactsContract.CommonDataKinds.Organization;
-import android.provider.ContactsContract.CommonDataKinds.Phone;
-import android.provider.ContactsContract.CommonDataKinds.Photo;
-import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
-import android.provider.ContactsContract.Contacts.AggregationSuggestions;
-import android.provider.ContactsContract.Intents.Insert;
-import android.provider.ContactsContract.Intents.UI;
-import android.telephony.TelephonyManager;
-import android.text.Editable;
-import android.text.Html;
-import android.text.TextUtils;
-import android.text.TextWatcher;
-import android.util.Log;
-import android.view.ContextMenu;
-import android.view.ContextThemeWrapper;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ContextMenu.ContextMenuInfo;
-import android.view.View.OnClickListener;
-import android.view.View.OnFocusChangeListener;
-import android.view.View.OnTouchListener;
-import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputMethodManager;
-import android.widget.AbsListView;
-import android.widget.AdapterView;
-import android.widget.ArrayAdapter;
-import android.widget.Button;
-import android.widget.CursorAdapter;
-import android.widget.Filter;
-import android.widget.ImageView;
-import android.widget.ListView;
-import android.widget.QuickContactBadge;
-import android.widget.SectionIndexer;
-import android.widget.TextView;
-import android.widget.Toast;
-import android.widget.AbsListView.OnScrollListener;
-
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Random;
-
-/**
- * Displays a list of contacts. Usually is embedded into the ContactsActivity.
- */
-@SuppressWarnings("deprecation")
-public class ContactsListActivity extends ListActivity implements View.OnCreateContextMenuListener,
- View.OnClickListener, View.OnKeyListener, TextWatcher, TextView.OnEditorActionListener,
- OnFocusChangeListener, OnTouchListener {
-
- public static class JoinContactActivity extends ContactsListActivity {
-
- }
-
- public static class ContactsSearchActivity extends ContactsListActivity {
-
- }
-
- private static final String TAG = "ContactsListActivity";
-
- private static final boolean ENABLE_ACTION_ICON_OVERLAYS = true;
-
- private static final String LIST_STATE_KEY = "liststate";
- private static final String SHORTCUT_ACTION_KEY = "shortcutAction";
-
- static final int MENU_ITEM_VIEW_CONTACT = 1;
- static final int MENU_ITEM_CALL = 2;
- static final int MENU_ITEM_EDIT_BEFORE_CALL = 3;
- static final int MENU_ITEM_SEND_SMS = 4;
- static final int MENU_ITEM_SEND_IM = 5;
- static final int MENU_ITEM_EDIT = 6;
- static final int MENU_ITEM_DELETE = 7;
- static final int MENU_ITEM_TOGGLE_STAR = 8;
-
- private static final int SUBACTIVITY_NEW_CONTACT = 1;
- private static final int SUBACTIVITY_VIEW_CONTACT = 2;
- private static final int SUBACTIVITY_DISPLAY_GROUP = 3;
- private static final int SUBACTIVITY_SEARCH = 4;
- private static final int SUBACTIVITY_FILTER = 5;
-
- private static final int TEXT_HIGHLIGHTING_ANIMATION_DURATION = 350;
-
- /**
- * The action for the join contact activity.
- * <p>
- * Input: extra field {@link #EXTRA_AGGREGATE_ID} is the aggregate ID.
- *
- * TODO: move to {@link ContactsContract}.
- */
- public static final String JOIN_AGGREGATE =
- "com.android.contacts.action.JOIN_AGGREGATE";
-
- /**
- * Used with {@link #JOIN_AGGREGATE} to give it the target for aggregation.
- * <p>
- * Type: LONG
- */
- public static final String EXTRA_AGGREGATE_ID =
- "com.android.contacts.action.AGGREGATE_ID";
-
- /**
- * Used with {@link #JOIN_AGGREGATE} to give it the name of the aggregation target.
- * <p>
- * Type: STRING
- */
- @Deprecated
- public static final String EXTRA_AGGREGATE_NAME =
- "com.android.contacts.action.AGGREGATE_NAME";
-
- public static final String AUTHORITIES_FILTER_KEY = "authorities";
-
- private static final Uri CONTACTS_CONTENT_URI_WITH_LETTER_COUNTS =
- buildSectionIndexerUri(Contacts.CONTENT_URI);
-
- /** Mask for picker mode */
- static final int MODE_MASK_PICKER = 0x80000000;
- /** Mask for no presence mode */
- static final int MODE_MASK_NO_PRESENCE = 0x40000000;
- /** Mask for enabling list filtering */
- static final int MODE_MASK_NO_FILTER = 0x20000000;
- /** Mask for having a "create new contact" header in the list */
- static final int MODE_MASK_CREATE_NEW = 0x10000000;
- /** Mask for showing photos in the list */
- static final int MODE_MASK_SHOW_PHOTOS = 0x08000000;
- /** Mask for hiding additional information e.g. primary phone number in the list */
- static final int MODE_MASK_NO_DATA = 0x04000000;
- /** Mask for showing a call button in the list */
- static final int MODE_MASK_SHOW_CALL_BUTTON = 0x02000000;
- /** Mask to disable quickcontact (images will show as normal images) */
- static final int MODE_MASK_DISABLE_QUIKCCONTACT = 0x01000000;
- /** Mask to show the total number of contacts at the top */
- static final int MODE_MASK_SHOW_NUMBER_OF_CONTACTS = 0x00800000;
-
- /** Unknown mode */
- static final int MODE_UNKNOWN = 0;
- /** Default mode */
- static final int MODE_DEFAULT = 4 | MODE_MASK_SHOW_PHOTOS | MODE_MASK_SHOW_NUMBER_OF_CONTACTS;
- /** Custom mode */
- static final int MODE_CUSTOM = 8;
- /** Show all starred contacts */
- static final int MODE_STARRED = 20 | MODE_MASK_SHOW_PHOTOS;
- /** Show frequently contacted contacts */
- static final int MODE_FREQUENT = 30 | MODE_MASK_SHOW_PHOTOS;
- /** Show starred and the frequent */
- static final int MODE_STREQUENT = 35 | MODE_MASK_SHOW_PHOTOS | MODE_MASK_SHOW_CALL_BUTTON;
- /** Show all contacts and pick them when clicking */
- static final int MODE_PICK_CONTACT = 40 | MODE_MASK_PICKER | MODE_MASK_SHOW_PHOTOS
- | MODE_MASK_DISABLE_QUIKCCONTACT;
- /** Show all contacts as well as the option to create a new one */
- static final int MODE_PICK_OR_CREATE_CONTACT = 42 | MODE_MASK_PICKER | MODE_MASK_CREATE_NEW
- | MODE_MASK_SHOW_PHOTOS | MODE_MASK_DISABLE_QUIKCCONTACT;
- /** Show all people through the legacy provider and pick them when clicking */
- static final int MODE_LEGACY_PICK_PERSON = 43 | MODE_MASK_PICKER
- | MODE_MASK_DISABLE_QUIKCCONTACT;
- /** Show all people through the legacy provider as well as the option to create a new one */
- static final int MODE_LEGACY_PICK_OR_CREATE_PERSON = 44 | MODE_MASK_PICKER
- | MODE_MASK_CREATE_NEW | MODE_MASK_DISABLE_QUIKCCONTACT;
- /** Show all contacts and pick them when clicking, and allow creating a new contact */
- static final int MODE_INSERT_OR_EDIT_CONTACT = 45 | MODE_MASK_PICKER | MODE_MASK_CREATE_NEW
- | MODE_MASK_SHOW_PHOTOS | MODE_MASK_DISABLE_QUIKCCONTACT;
- /** Show all phone numbers and pick them when clicking */
- static final int MODE_PICK_PHONE = 50 | MODE_MASK_PICKER | MODE_MASK_NO_PRESENCE;
- /** Show all phone numbers through the legacy provider and pick them when clicking */
- static final int MODE_LEGACY_PICK_PHONE =
- 51 | MODE_MASK_PICKER | MODE_MASK_NO_PRESENCE | MODE_MASK_NO_FILTER;
- /** Show all postal addresses and pick them when clicking */
- static final int MODE_PICK_POSTAL =
- 55 | MODE_MASK_PICKER | MODE_MASK_NO_PRESENCE | MODE_MASK_NO_FILTER;
- /** Show all postal addresses and pick them when clicking */
- static final int MODE_LEGACY_PICK_POSTAL =
- 56 | MODE_MASK_PICKER | MODE_MASK_NO_PRESENCE | MODE_MASK_NO_FILTER;
- static final int MODE_GROUP = 57 | MODE_MASK_SHOW_PHOTOS;
- /** Run a search query */
- static final int MODE_QUERY = 60 | MODE_MASK_SHOW_PHOTOS | MODE_MASK_NO_FILTER
- | MODE_MASK_SHOW_NUMBER_OF_CONTACTS;
- /** Run a search query in PICK mode, but that still launches to VIEW */
- static final int MODE_QUERY_PICK_TO_VIEW = 65 | MODE_MASK_SHOW_PHOTOS | MODE_MASK_PICKER
- | MODE_MASK_SHOW_NUMBER_OF_CONTACTS;
-
- /** Show join suggestions followed by an A-Z list */
- static final int MODE_JOIN_CONTACT = 70 | MODE_MASK_PICKER | MODE_MASK_NO_PRESENCE
- | MODE_MASK_NO_DATA | MODE_MASK_SHOW_PHOTOS | MODE_MASK_DISABLE_QUIKCCONTACT;
-
- /** Run a search query in a PICK mode */
- static final int MODE_QUERY_PICK = 75 | MODE_MASK_SHOW_PHOTOS | MODE_MASK_NO_FILTER
- | MODE_MASK_PICKER | MODE_MASK_DISABLE_QUIKCCONTACT | MODE_MASK_SHOW_NUMBER_OF_CONTACTS;
-
- /** Run a search query in a PICK_PHONE mode */
- static final int MODE_QUERY_PICK_PHONE = 80 | MODE_MASK_NO_FILTER | MODE_MASK_PICKER
- | MODE_MASK_SHOW_NUMBER_OF_CONTACTS;
-
- /** Run a search query in PICK mode, but that still launches to EDIT */
- static final int MODE_QUERY_PICK_TO_EDIT = 85 | MODE_MASK_NO_FILTER | MODE_MASK_SHOW_PHOTOS
- | MODE_MASK_PICKER | MODE_MASK_SHOW_NUMBER_OF_CONTACTS;
-
- /**
- * An action used to do perform search while in a contact picker. It is initiated
- * by the ContactListActivity itself.
- */
- private static final String ACTION_SEARCH_INTERNAL = "com.android.contacts.INTERNAL_SEARCH";
-
- /** Maximum number of suggestions shown for joining aggregates */
- static final int MAX_SUGGESTIONS = 4;
-
- static final String[] CONTACTS_SUMMARY_PROJECTION = new String[] {
- Contacts._ID, // 0
- Contacts.DISPLAY_NAME_PRIMARY, // 1
- Contacts.DISPLAY_NAME_ALTERNATIVE, // 2
- Contacts.SORT_KEY_PRIMARY, // 3
- Contacts.STARRED, // 4
- Contacts.TIMES_CONTACTED, // 5
- Contacts.CONTACT_PRESENCE, // 6
- Contacts.PHOTO_ID, // 7
- Contacts.LOOKUP_KEY, // 8
- Contacts.PHONETIC_NAME, // 9
- Contacts.HAS_PHONE_NUMBER, // 10
- };
- static final String[] CONTACTS_SUMMARY_PROJECTION_FROM_EMAIL = new String[] {
- Contacts._ID, // 0
- Contacts.DISPLAY_NAME_PRIMARY, // 1
- Contacts.DISPLAY_NAME_ALTERNATIVE, // 2
- Contacts.SORT_KEY_PRIMARY, // 3
- Contacts.STARRED, // 4
- Contacts.TIMES_CONTACTED, // 5
- Contacts.CONTACT_PRESENCE, // 6
- Contacts.PHOTO_ID, // 7
- Contacts.LOOKUP_KEY, // 8
- Contacts.PHONETIC_NAME, // 9
- // email lookup doesn't included HAS_PHONE_NUMBER in projection
- };
-
- static final String[] CONTACTS_SUMMARY_FILTER_PROJECTION = new String[] {
- Contacts._ID, // 0
- Contacts.DISPLAY_NAME_PRIMARY, // 1
- Contacts.DISPLAY_NAME_ALTERNATIVE, // 2
- Contacts.SORT_KEY_PRIMARY, // 3
- Contacts.STARRED, // 4
- Contacts.TIMES_CONTACTED, // 5
- Contacts.CONTACT_PRESENCE, // 6
- Contacts.PHOTO_ID, // 7
- Contacts.LOOKUP_KEY, // 8
- Contacts.PHONETIC_NAME, // 9
- Contacts.HAS_PHONE_NUMBER, // 10
- SearchSnippetColumns.SNIPPET_MIMETYPE, // 11
- SearchSnippetColumns.SNIPPET_DATA1, // 12
- SearchSnippetColumns.SNIPPET_DATA4, // 13
- };
-
- static final String[] LEGACY_PEOPLE_PROJECTION = new String[] {
- People._ID, // 0
- People.DISPLAY_NAME, // 1
- People.DISPLAY_NAME, // 2
- People.DISPLAY_NAME, // 3
- People.STARRED, // 4
- PeopleColumns.TIMES_CONTACTED, // 5
- People.PRESENCE_STATUS, // 6
- };
- static final int SUMMARY_ID_COLUMN_INDEX = 0;
- static final int SUMMARY_DISPLAY_NAME_PRIMARY_COLUMN_INDEX = 1;
- static final int SUMMARY_DISPLAY_NAME_ALTERNATIVE_COLUMN_INDEX = 2;
- static final int SUMMARY_SORT_KEY_PRIMARY_COLUMN_INDEX = 3;
- static final int SUMMARY_STARRED_COLUMN_INDEX = 4;
- static final int SUMMARY_TIMES_CONTACTED_COLUMN_INDEX = 5;
- static final int SUMMARY_PRESENCE_STATUS_COLUMN_INDEX = 6;
- static final int SUMMARY_PHOTO_ID_COLUMN_INDEX = 7;
- static final int SUMMARY_LOOKUP_KEY_COLUMN_INDEX = 8;
- static final int SUMMARY_PHONETIC_NAME_COLUMN_INDEX = 9;
- static final int SUMMARY_HAS_PHONE_COLUMN_INDEX = 10;
- static final int SUMMARY_SNIPPET_MIMETYPE_COLUMN_INDEX = 11;
- static final int SUMMARY_SNIPPET_DATA1_COLUMN_INDEX = 12;
- static final int SUMMARY_SNIPPET_DATA4_COLUMN_INDEX = 13;
-
- static final String[] PHONES_PROJECTION = new String[] {
- Phone._ID, //0
- Phone.TYPE, //1
- Phone.LABEL, //2
- Phone.NUMBER, //3
- Phone.DISPLAY_NAME, // 4
- Phone.CONTACT_ID, // 5
- };
- static final String[] LEGACY_PHONES_PROJECTION = new String[] {
- Phones._ID, //0
- Phones.TYPE, //1
- Phones.LABEL, //2
- Phones.NUMBER, //3
- People.DISPLAY_NAME, // 4
- };
- static final int PHONE_ID_COLUMN_INDEX = 0;
- static final int PHONE_TYPE_COLUMN_INDEX = 1;
- static final int PHONE_LABEL_COLUMN_INDEX = 2;
- static final int PHONE_NUMBER_COLUMN_INDEX = 3;
- static final int PHONE_DISPLAY_NAME_COLUMN_INDEX = 4;
- static final int PHONE_CONTACT_ID_COLUMN_INDEX = 5;
-
- static final String[] POSTALS_PROJECTION = new String[] {
- StructuredPostal._ID, //0
- StructuredPostal.TYPE, //1
- StructuredPostal.LABEL, //2
- StructuredPostal.DATA, //3
- StructuredPostal.DISPLAY_NAME, // 4
- };
- static final String[] LEGACY_POSTALS_PROJECTION = new String[] {
- ContactMethods._ID, //0
- ContactMethods.TYPE, //1
- ContactMethods.LABEL, //2
- ContactMethods.DATA, //3
- People.DISPLAY_NAME, // 4
- };
- static final String[] RAW_CONTACTS_PROJECTION = new String[] {
- RawContacts._ID, //0
- RawContacts.CONTACT_ID, //1
- RawContacts.ACCOUNT_TYPE, //2
- };
-
- static final int POSTAL_ID_COLUMN_INDEX = 0;
- static final int POSTAL_TYPE_COLUMN_INDEX = 1;
- static final int POSTAL_LABEL_COLUMN_INDEX = 2;
- static final int POSTAL_ADDRESS_COLUMN_INDEX = 3;
- static final int POSTAL_DISPLAY_NAME_COLUMN_INDEX = 4;
-
- private static final int QUERY_TOKEN = 42;
-
- static final String KEY_PICKER_MODE = "picker_mode";
-
- private ContactItemListAdapter mAdapter;
-
- int mMode = MODE_DEFAULT;
-
- private QueryHandler mQueryHandler;
- private boolean mJustCreated;
- private boolean mSyncEnabled;
- Uri mSelectedContactUri;
-
-// private boolean mDisplayAll;
- private boolean mDisplayOnlyPhones;
-
- private Uri mGroupUri;
-
- private long mQueryAggregateId;
-
- private ArrayList<Long> mWritableRawContactIds = new ArrayList<Long>();
- private int mWritableSourcesCnt;
- private int mReadOnlySourcesCnt;
-
- /**
- * Used to keep track of the scroll state of the list.
- */
- private Parcelable mListState = null;
-
- private String mShortcutAction;
-
- /**
- * Internal query type when in mode {@link #MODE_QUERY_PICK_TO_VIEW}.
- */
- private int mQueryMode = QUERY_MODE_NONE;
-
- private static final int QUERY_MODE_NONE = -1;
- private static final int QUERY_MODE_MAILTO = 1;
- private static final int QUERY_MODE_TEL = 2;
-
- private int mProviderStatus = ProviderStatus.STATUS_NORMAL;
-
- private boolean mSearchMode;
- private boolean mSearchResultsMode;
- private boolean mShowNumberOfContacts;
-
- private boolean mShowSearchSnippets;
- private boolean mSearchInitiated;
-
- private String mInitialFilter;
-
- private static final String CLAUSE_ONLY_VISIBLE = Contacts.IN_VISIBLE_GROUP + "=1";
- private static final String CLAUSE_ONLY_PHONES = Contacts.HAS_PHONE_NUMBER + "=1";
-
- /**
- * In the {@link #MODE_JOIN_CONTACT} determines whether we display a list item with the label
- * "Show all contacts" or actually show all contacts
- */
- private boolean mJoinModeShowAllContacts;
-
- /**
- * The ID of the special item described above.
- */
- private static final long JOIN_MODE_SHOW_ALL_CONTACTS_ID = -2;
-
- // Uri matcher for contact id
- private static final int CONTACTS_ID = 1001;
- private static final UriMatcher sContactsIdMatcher;
-
- private ContactPhotoLoader mPhotoLoader;
-
- final String[] sLookupProjection = new String[] {
- Contacts.LOOKUP_KEY
- };
-
- static {
- sContactsIdMatcher = new UriMatcher(UriMatcher.NO_MATCH);
- sContactsIdMatcher.addURI(ContactsContract.AUTHORITY, "contacts/#", CONTACTS_ID);
- }
-
- private class DeleteClickListener implements DialogInterface.OnClickListener {
- public void onClick(DialogInterface dialog, int which) {
- if (mSelectedContactUri != null) {
- getContentResolver().delete(mSelectedContactUri, null, null);
- }
- }
- }
-
- /**
- * A {@link TextHighlightingAnimation} that redraws just the contact display name in a
- * list item.
- */
- private static class NameHighlightingAnimation extends TextHighlightingAnimation {
- private final ListView mListView;
-
- private NameHighlightingAnimation(ListView listView, int duration) {
- super(duration);
- this.mListView = listView;
- }
-
- /**
- * Redraws all visible items of the list corresponding to contacts
- */
- @Override
- protected void invalidate() {
- int childCount = mListView.getChildCount();
- for (int i = 0; i < childCount; i++) {
- View itemView = mListView.getChildAt(i);
- if (itemView instanceof ContactListItemView) {
- final ContactListItemView view = (ContactListItemView)itemView;
- view.getNameTextView().invalidate();
- }
- }
- }
-
- @Override
- protected void onAnimationStarted() {
- mListView.setScrollingCacheEnabled(false);
- }
-
- @Override
- protected void onAnimationEnded() {
- mListView.setScrollingCacheEnabled(true);
- }
- }
-
- // The size of a home screen shortcut icon.
- private int mIconSize;
- private ContactsPreferences mContactsPrefs;
- private int mDisplayOrder;
- private int mSortOrder;
- private boolean mHighlightWhenScrolling;
- private TextHighlightingAnimation mHighlightingAnimation;
- private SearchEditText mSearchEditText;
-
- /**
- * An approximation of the background color of the pinned header. This color
- * is used when the pinned header is being pushed up. At that point the header
- * "fades away". Rather than computing a faded bitmap based on the 9-patch
- * normally used for the background, we will use a solid color, which will
- * provide better performance and reduced complexity.
- */
- private int mPinnedHeaderBackgroundColor;
-
- private ContentObserver mProviderStatusObserver = new ContentObserver(new Handler()) {
-
- @Override
- public void onChange(boolean selfChange) {
- checkProviderState(true);
- }
- };
-
- @Override
- protected void onCreate(Bundle icicle) {
- super.onCreate(icicle);
-
- mIconSize = getResources().getDimensionPixelSize(android.R.dimen.app_icon_size);
- mContactsPrefs = new ContactsPreferences(this);
- mPhotoLoader = new ContactPhotoLoader(this, R.drawable.ic_contact_list_picture);
-
- // Resolve the intent
- final Intent intent = getIntent();
-
- // Allow the title to be set to a custom String using an extra on the intent
- String title = intent.getStringExtra(UI.TITLE_EXTRA_KEY);
- if (title != null) {
- setTitle(title);
- }
-
- String action = intent.getAction();
- String component = intent.getComponent().getClassName();
-
- // When we get a FILTER_CONTACTS_ACTION, it represents search in the context
- // of some other action. Let's retrieve the original action to provide proper
- // context for the search queries.
- if (UI.FILTER_CONTACTS_ACTION.equals(action)) {
- mSearchMode = true;
- mShowSearchSnippets = true;
- Bundle extras = intent.getExtras();
- if (extras != null) {
- mInitialFilter = extras.getString(UI.FILTER_TEXT_EXTRA_KEY);
- String originalAction =
- extras.getString(ContactsSearchManager.ORIGINAL_ACTION_EXTRA_KEY);
- if (originalAction != null) {
- action = originalAction;
- }
- String originalComponent =
- extras.getString(ContactsSearchManager.ORIGINAL_COMPONENT_EXTRA_KEY);
- if (originalComponent != null) {
- component = originalComponent;
- }
- } else {
- mInitialFilter = null;
- }
- }
-
- Log.i(TAG, "Called with action: " + action);
- mMode = MODE_UNKNOWN;
- if (UI.LIST_DEFAULT.equals(action) || UI.FILTER_CONTACTS_ACTION.equals(action)) {
- mMode = MODE_DEFAULT;
- // When mDefaultMode is true the mode is set in onResume(), since the preferneces
- // activity may change it whenever this activity isn't running
- } else if (UI.LIST_GROUP_ACTION.equals(action)) {
- mMode = MODE_GROUP;
- String groupName = intent.getStringExtra(UI.GROUP_NAME_EXTRA_KEY);
- if (TextUtils.isEmpty(groupName)) {
- finish();
- return;
- }
- buildUserGroupUri(groupName);
- } else if (UI.LIST_ALL_CONTACTS_ACTION.equals(action)) {
- mMode = MODE_CUSTOM;
- mDisplayOnlyPhones = false;
- } else if (UI.LIST_STARRED_ACTION.equals(action)) {
- mMode = mSearchMode ? MODE_DEFAULT : MODE_STARRED;
- } else if (UI.LIST_FREQUENT_ACTION.equals(action)) {
- mMode = mSearchMode ? MODE_DEFAULT : MODE_FREQUENT;
- } else if (UI.LIST_STREQUENT_ACTION.equals(action)) {
- mMode = mSearchMode ? MODE_DEFAULT : MODE_STREQUENT;
- } else if (UI.LIST_CONTACTS_WITH_PHONES_ACTION.equals(action)) {
- mMode = MODE_CUSTOM;
- mDisplayOnlyPhones = true;
- } else if (Intent.ACTION_PICK.equals(action)) {
- // XXX These should be showing the data from the URI given in
- // the Intent.
- final String type = intent.resolveType(this);
- if (Contacts.CONTENT_TYPE.equals(type)) {
- mMode = MODE_PICK_CONTACT;
- } else if (People.CONTENT_TYPE.equals(type)) {
- mMode = MODE_LEGACY_PICK_PERSON;
- } else if (Phone.CONTENT_TYPE.equals(type)) {
- mMode = MODE_PICK_PHONE;
- } else if (Phones.CONTENT_TYPE.equals(type)) {
- mMode = MODE_LEGACY_PICK_PHONE;
- } else if (StructuredPostal.CONTENT_TYPE.equals(type)) {
- mMode = MODE_PICK_POSTAL;
- } else if (ContactMethods.CONTENT_POSTAL_TYPE.equals(type)) {
- mMode = MODE_LEGACY_PICK_POSTAL;
- }
- } else if (Intent.ACTION_CREATE_SHORTCUT.equals(action)) {
- if (component.equals("alias.DialShortcut")) {
- mMode = MODE_PICK_PHONE;
- mShortcutAction = Intent.ACTION_CALL;
- mShowSearchSnippets = false;
- setTitle(R.string.callShortcutActivityTitle);
- } else if (component.equals("alias.MessageShortcut")) {
- mMode = MODE_PICK_PHONE;
- mShortcutAction = Intent.ACTION_SENDTO;
- mShowSearchSnippets = false;
- setTitle(R.string.messageShortcutActivityTitle);
- } else if (mSearchMode) {
- mMode = MODE_PICK_CONTACT;
- mShortcutAction = Intent.ACTION_VIEW;
- setTitle(R.string.shortcutActivityTitle);
- } else {
- mMode = MODE_PICK_OR_CREATE_CONTACT;
- mShortcutAction = Intent.ACTION_VIEW;
- setTitle(R.string.shortcutActivityTitle);
- }
- } else if (Intent.ACTION_GET_CONTENT.equals(action)) {
- final String type = intent.resolveType(this);
- if (Contacts.CONTENT_ITEM_TYPE.equals(type)) {
- if (mSearchMode) {
- mMode = MODE_PICK_CONTACT;
- } else {
- mMode = MODE_PICK_OR_CREATE_CONTACT;
- }
- } else if (Phone.CONTENT_ITEM_TYPE.equals(type)) {
- mMode = MODE_PICK_PHONE;
- } else if (Phones.CONTENT_ITEM_TYPE.equals(type)) {
- mMode = MODE_LEGACY_PICK_PHONE;
- } else if (StructuredPostal.CONTENT_ITEM_TYPE.equals(type)) {
- mMode = MODE_PICK_POSTAL;
- } else if (ContactMethods.CONTENT_POSTAL_ITEM_TYPE.equals(type)) {
- mMode = MODE_LEGACY_PICK_POSTAL;
- } else if (People.CONTENT_ITEM_TYPE.equals(type)) {
- if (mSearchMode) {
- mMode = MODE_LEGACY_PICK_PERSON;
- } else {
- mMode = MODE_LEGACY_PICK_OR_CREATE_PERSON;
- }
- }
-
- } else if (Intent.ACTION_INSERT_OR_EDIT.equals(action)) {
- mMode = MODE_INSERT_OR_EDIT_CONTACT;
- } else if (Intent.ACTION_SEARCH.equals(action)) {
- // See if the suggestion was clicked with a search action key (call button)
- if ("call".equals(intent.getStringExtra(SearchManager.ACTION_MSG))) {
- String query = intent.getStringExtra(SearchManager.QUERY);
- if (!TextUtils.isEmpty(query)) {
- Intent newIntent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
- Uri.fromParts("tel", query, null));
- startActivity(newIntent);
- }
- finish();
- return;
- }
-
- // See if search request has extras to specify query
- if (intent.hasExtra(Insert.EMAIL)) {
- mMode = MODE_QUERY_PICK_TO_VIEW;
- mQueryMode = QUERY_MODE_MAILTO;
- mInitialFilter = intent.getStringExtra(Insert.EMAIL);
- } else if (intent.hasExtra(Insert.PHONE)) {
- mMode = MODE_QUERY_PICK_TO_VIEW;
- mQueryMode = QUERY_MODE_TEL;
- mInitialFilter = intent.getStringExtra(Insert.PHONE);
- } else {
- // Otherwise handle the more normal search case
- mMode = MODE_QUERY;
- mShowSearchSnippets = true;
- mInitialFilter = getIntent().getStringExtra(SearchManager.QUERY);
- }
- mSearchResultsMode = true;
- } else if (ACTION_SEARCH_INTERNAL.equals(action)) {
- String originalAction = null;
- Bundle extras = intent.getExtras();
- if (extras != null) {
- originalAction = extras.getString(ContactsSearchManager.ORIGINAL_ACTION_EXTRA_KEY);
- }
- mShortcutAction = intent.getStringExtra(SHORTCUT_ACTION_KEY);
-
- if (Intent.ACTION_INSERT_OR_EDIT.equals(originalAction)) {
- mMode = MODE_QUERY_PICK_TO_EDIT;
- mShowSearchSnippets = true;
- mInitialFilter = getIntent().getStringExtra(SearchManager.QUERY);
- } else if (mShortcutAction != null && intent.hasExtra(Insert.PHONE)) {
- mMode = MODE_QUERY_PICK_PHONE;
- mQueryMode = QUERY_MODE_TEL;
- mInitialFilter = intent.getStringExtra(Insert.PHONE);
- } else {
- mMode = MODE_QUERY_PICK;
- mQueryMode = QUERY_MODE_NONE;
- mShowSearchSnippets = true;
- mInitialFilter = getIntent().getStringExtra(SearchManager.QUERY);
- }
- mSearchResultsMode = true;
- // Since this is the filter activity it receives all intents
- // dispatched from the SearchManager for security reasons
- // so we need to re-dispatch from here to the intended target.
- } else if (Intents.SEARCH_SUGGESTION_CLICKED.equals(action)) {
- Uri data = intent.getData();
- Uri telUri = null;
- if (sContactsIdMatcher.match(data) == CONTACTS_ID) {
- long contactId = Long.valueOf(data.getLastPathSegment());
- final Cursor cursor = queryPhoneNumbers(contactId);
- if (cursor != null) {
- if (cursor.getCount() == 1 && cursor.moveToFirst()) {
- int phoneNumberIndex = cursor.getColumnIndex(Phone.NUMBER);
- String phoneNumber = cursor.getString(phoneNumberIndex);
- telUri = Uri.parse("tel:" + phoneNumber);
- }
- cursor.close();
- }
- }
- // See if the suggestion was clicked with a search action key (call button)
- Intent newIntent;
- if ("call".equals(intent.getStringExtra(SearchManager.ACTION_MSG)) && telUri != null) {
- newIntent = new Intent(Intent.ACTION_CALL_PRIVILEGED, telUri);
- } else {
- newIntent = new Intent(Intent.ACTION_VIEW, data);
- }
- startActivity(newIntent);
- finish();
- return;
- } else if (Intents.SEARCH_SUGGESTION_DIAL_NUMBER_CLICKED.equals(action)) {
- Intent newIntent = new Intent(Intent.ACTION_CALL_PRIVILEGED, intent.getData());
- startActivity(newIntent);
- finish();
- return;
- } else if (Intents.SEARCH_SUGGESTION_CREATE_CONTACT_CLICKED.equals(action)) {
- // TODO actually support this in EditContactActivity.
- String number = intent.getData().getSchemeSpecificPart();
- Intent newIntent = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI);
- newIntent.putExtra(Intents.Insert.PHONE, number);
- startActivity(newIntent);
- finish();
- return;
- }
-
- if (JOIN_AGGREGATE.equals(action)) {
- if (mSearchMode) {
- mMode = MODE_PICK_CONTACT;
- } else {
- mMode = MODE_JOIN_CONTACT;
- mQueryAggregateId = intent.getLongExtra(EXTRA_AGGREGATE_ID, -1);
- if (mQueryAggregateId == -1) {
- Log.e(TAG, "Intent " + action + " is missing required extra: "
- + EXTRA_AGGREGATE_ID);
- setResult(RESULT_CANCELED);
- finish();
- }
- }
- }
-
- if (mMode == MODE_UNKNOWN) {
- mMode = MODE_DEFAULT;
- }
-
- if (((mMode & MODE_MASK_SHOW_NUMBER_OF_CONTACTS) != 0 || mSearchMode)
- && !mSearchResultsMode) {
- mShowNumberOfContacts = true;
- }
-
- if (mMode == MODE_JOIN_CONTACT) {
- setContentView(R.layout.contacts_list_content_join);
- TextView blurbView = (TextView)findViewById(R.id.join_contact_blurb);
-
- String blurb = getString(R.string.blurbJoinContactDataWith,
- getContactDisplayName(mQueryAggregateId));
- blurbView.setText(blurb);
- mJoinModeShowAllContacts = true;
- } else if (mSearchMode) {
- setContentView(R.layout.contacts_search_content);
- } else if (mSearchResultsMode) {
- setContentView(R.layout.contacts_list_search_results);
- TextView titleText = (TextView)findViewById(R.id.search_results_for);
- titleText.setText(Html.fromHtml(getString(R.string.search_results_for,
- "<b>" + mInitialFilter + "</b>")));
- } else {
- setContentView(R.layout.contacts_list_content);
- }
-
- setupListView();
- if (mSearchMode) {
- setupSearchView();
- }
-
- mQueryHandler = new QueryHandler(this);
- mJustCreated = true;
-
- mSyncEnabled = true;
- }
-
- /**
- * Register an observer for provider status changes - we will need to
- * reflect them in the UI.
- */
- private void registerProviderStatusObserver() {
- getContentResolver().registerContentObserver(ProviderStatus.CONTENT_URI,
- false, mProviderStatusObserver);
- }
-
- /**
- * Register an observer for provider status changes - we will need to
- * reflect them in the UI.
- */
- private void unregisterProviderStatusObserver() {
- getContentResolver().unregisterContentObserver(mProviderStatusObserver);
- }
-
- private void setupListView() {
- final ListView list = getListView();
- final LayoutInflater inflater = getLayoutInflater();
-
- mHighlightingAnimation =
- new NameHighlightingAnimation(list, TEXT_HIGHLIGHTING_ANIMATION_DURATION);
-
- // Tell list view to not show dividers. We'll do it ourself so that we can *not* show
- // them when an A-Z headers is visible.
- list.setDividerHeight(0);
- list.setOnCreateContextMenuListener(this);
-
- mAdapter = new ContactItemListAdapter(this);
- setListAdapter(mAdapter);
-
- if (list instanceof PinnedHeaderListView && mAdapter.getDisplaySectionHeadersEnabled()) {
- mPinnedHeaderBackgroundColor =
- getResources().getColor(R.color.pinned_header_background);
- PinnedHeaderListView pinnedHeaderList = (PinnedHeaderListView)list;
- View pinnedHeader = inflater.inflate(R.layout.list_section, list, false);
- pinnedHeaderList.setPinnedHeaderView(pinnedHeader);
- }
-
- list.setOnScrollListener(mAdapter);
- list.setOnKeyListener(this);
- list.setOnFocusChangeListener(this);
- list.setOnTouchListener(this);
-
- // We manually save/restore the listview state
- list.setSaveEnabled(false);
- }
-
- /**
- * Configures search UI.
- */
- private void setupSearchView() {
- mSearchEditText = (SearchEditText)findViewById(R.id.search_src_text);
- mSearchEditText.addTextChangedListener(this);
- mSearchEditText.setOnEditorActionListener(this);
- mSearchEditText.setText(mInitialFilter);
- }
-
- private String getContactDisplayName(long contactId) {
- String contactName = null;
- Cursor c = getContentResolver().query(
- ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId),
- new String[] {Contacts.DISPLAY_NAME}, null, null, null);
- try {
- if (c != null && c.moveToFirst()) {
- contactName = c.getString(0);
- }
- } finally {
- if (c != null) {
- c.close();
- }
- }
-
- if (contactName == null) {
- contactName = "";
- }
-
- return contactName;
- }
-
- private int getSummaryDisplayNameColumnIndex() {
- if (mDisplayOrder == ContactsContract.Preferences.DISPLAY_ORDER_PRIMARY) {
- return SUMMARY_DISPLAY_NAME_PRIMARY_COLUMN_INDEX;
- } else {
- return SUMMARY_DISPLAY_NAME_ALTERNATIVE_COLUMN_INDEX;
- }
- }
-
- /** {@inheritDoc} */
- public void onClick(View v) {
- int id = v.getId();
- switch (id) {
- // TODO a better way of identifying the button
- case android.R.id.button1: {
- final int position = (Integer)v.getTag();
- Cursor c = mAdapter.getCursor();
- if (c != null) {
- c.moveToPosition(position);
- callContact(c);
- }
- break;
- }
- }
- }
-
- private void setEmptyText() {
- if (mMode == MODE_JOIN_CONTACT || mSearchMode) {
- return;
- }
-
- TextView empty = (TextView) findViewById(R.id.emptyText);
- if (mDisplayOnlyPhones) {
- empty.setText(getText(R.string.noContactsWithPhoneNumbers));
- } else if (mMode == MODE_STREQUENT || mMode == MODE_STARRED) {
- empty.setText(getText(R.string.noFavoritesHelpText));
- } else if (mMode == MODE_QUERY || mMode == MODE_QUERY_PICK
- || mMode == MODE_QUERY_PICK_PHONE || mMode == MODE_QUERY_PICK_TO_VIEW
- || mMode == MODE_QUERY_PICK_TO_EDIT) {
- empty.setText(getText(R.string.noMatchingContacts));
- } else {
- boolean hasSim = ((TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE))
- .hasIccCard();
- boolean createShortcut = Intent.ACTION_CREATE_SHORTCUT.equals(getIntent().getAction());
- if (isSyncActive()) {
- if (createShortcut) {
- // Help text is the same no matter whether there is SIM or not.
- empty.setText(getText(R.string.noContactsHelpTextWithSyncForCreateShortcut));
- } else if (hasSim) {
- empty.setText(getText(R.string.noContactsHelpTextWithSync));
- } else {
- empty.setText(getText(R.string.noContactsNoSimHelpTextWithSync));
- }
- } else {
- if (createShortcut) {
- // Help text is the same no matter whether there is SIM or not.
- empty.setText(getText(R.string.noContactsHelpTextForCreateShortcut));
- } else if (hasSim) {
- empty.setText(getText(R.string.noContactsHelpText));
- } else {
- empty.setText(getText(R.string.noContactsNoSimHelpText));
- }
- }
- }
- }
-
- private boolean isSyncActive() {
- Account[] accounts = AccountManager.get(this).getAccounts();
- if (accounts != null && accounts.length > 0) {
- IContentService contentService = ContentResolver.getContentService();
- for (Account account : accounts) {
- try {
- if (contentService.isSyncActive(account, ContactsContract.AUTHORITY)) {
- return true;
- }
- } catch (RemoteException e) {
- Log.e(TAG, "Could not get the sync status");
- }
- }
- }
- return false;
- }
-
- private void buildUserGroupUri(String group) {
- mGroupUri = Uri.withAppendedPath(Contacts.CONTENT_GROUP_URI, group);
- }
-
- /**
- * Sets the mode when the request is for "default"
- */
- private void setDefaultMode() {
- // Load the preferences
- SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
-
- mDisplayOnlyPhones = prefs.getBoolean(Prefs.DISPLAY_ONLY_PHONES,
- Prefs.DISPLAY_ONLY_PHONES_DEFAULT);
- }
-
- @Override
- protected void onDestroy() {
- super.onDestroy();
- mPhotoLoader.stop();
- }
-
- @Override
- protected void onPause() {
- super.onPause();
- unregisterProviderStatusObserver();
- }
-
- @Override
- protected void onResume() {
- super.onResume();
-
- registerProviderStatusObserver();
- mPhotoLoader.resume();
-
- Activity parent = getParent();
-
- // Do this before setting the filter. The filter thread relies
- // on some state that is initialized in setDefaultMode
- if (mMode == MODE_DEFAULT) {
- // If we're in default mode we need to possibly reset the mode due to a change
- // in the preferences activity while we weren't running
- setDefaultMode();
- }
-
- // See if we were invoked with a filter
- if (mSearchMode) {
- mSearchEditText.requestFocus();
- }
-
- if (!mSearchMode && !checkProviderState(mJustCreated)) {
- return;
- }
-
- if (mJustCreated) {
- // We need to start a query here the first time the activity is launched, as long
- // as we aren't doing a filter.
- startQuery();
- }
- mJustCreated = false;
- mSearchInitiated = false;
- }
-
- /**
- * Obtains the contacts provider status and configures the UI accordingly.
- *
- * @param loadData true if the method needs to start a query when the
- * provider is in the normal state
- * @return true if the provider status is normal
- */
- private boolean checkProviderState(boolean loadData) {
- View importFailureView = findViewById(R.id.import_failure);
- if (importFailureView == null) {
- return true;
- }
-
- TextView messageView = (TextView) findViewById(R.id.emptyText);
-
- // This query can be performed on the UI thread because
- // the API explicitly allows such use.
- Cursor cursor = getContentResolver().query(ProviderStatus.CONTENT_URI, new String[] {
- ProviderStatus.STATUS, ProviderStatus.DATA1
- }, null, null, null);
- try {
- if (cursor.moveToFirst()) {
- int status = cursor.getInt(0);
- if (status != mProviderStatus) {
- mProviderStatus = status;
- switch (status) {
- case ProviderStatus.STATUS_NORMAL:
- mAdapter.notifyDataSetInvalidated();
- if (loadData) {
- startQuery();
- }
- break;
-
- case ProviderStatus.STATUS_CHANGING_LOCALE:
- messageView.setText(R.string.locale_change_in_progress);
- mAdapter.changeCursor(null);
- mAdapter.notifyDataSetInvalidated();
- break;
-
- case ProviderStatus.STATUS_UPGRADING:
- messageView.setText(R.string.upgrade_in_progress);
- mAdapter.changeCursor(null);
- mAdapter.notifyDataSetInvalidated();
- break;
-
- case ProviderStatus.STATUS_UPGRADE_OUT_OF_MEMORY:
- long size = cursor.getLong(1);
- String message = getResources().getString(
- R.string.upgrade_out_of_memory, new Object[] {size});
- messageView.setText(message);
- configureImportFailureView(importFailureView);
- mAdapter.changeCursor(null);
- mAdapter.notifyDataSetInvalidated();
- break;
- }
- }
- }
- } finally {
- cursor.close();
- }
-
- importFailureView.setVisibility(
- mProviderStatus == ProviderStatus.STATUS_UPGRADE_OUT_OF_MEMORY
- ? View.VISIBLE
- : View.GONE);
- return mProviderStatus == ProviderStatus.STATUS_NORMAL;
- }
-
- private void configureImportFailureView(View importFailureView) {
-
- OnClickListener listener = new OnClickListener(){
-
- public void onClick(View v) {
- switch(v.getId()) {
- case R.id.import_failure_uninstall_apps: {
- startActivity(new Intent(Settings.ACTION_MANAGE_APPLICATIONS_SETTINGS));
- break;
- }
- case R.id.import_failure_retry_upgrade: {
- // Send a provider status update, which will trigger a retry
- ContentValues values = new ContentValues();
- values.put(ProviderStatus.STATUS, ProviderStatus.STATUS_UPGRADING);
- getContentResolver().update(ProviderStatus.CONTENT_URI, values, null, null);
- break;
- }
- }
- }};
-
- Button uninstallApps = (Button) findViewById(R.id.import_failure_uninstall_apps);
- uninstallApps.setOnClickListener(listener);
-
- Button retryUpgrade = (Button) findViewById(R.id.import_failure_retry_upgrade);
- retryUpgrade.setOnClickListener(listener);
- }
-
- private String getTextFilter() {
- if (mSearchEditText != null) {
- return mSearchEditText.getText().toString();
- }
- return null;
- }
-
- @Override
- protected void onRestart() {
- super.onRestart();
-
- if (!checkProviderState(false)) {
- return;
- }
-
- // The cursor was killed off in onStop(), so we need to get a new one here
- // We do not perform the query if a filter is set on the list because the
- // filter will cause the query to happen anyway
- if (TextUtils.isEmpty(getTextFilter())) {
- startQuery();
- } else {
- // Run the filtered query on the adapter
- mAdapter.onContentChanged();
- }
- }
-
- @Override
- protected void onSaveInstanceState(Bundle icicle) {
- super.onSaveInstanceState(icicle);
- // Save list state in the bundle so we can restore it after the QueryHandler has run
- if (mList != null) {
- icicle.putParcelable(LIST_STATE_KEY, mList.onSaveInstanceState());
- }
- }
-
- @Override
- protected void onRestoreInstanceState(Bundle icicle) {
- super.onRestoreInstanceState(icicle);
- // Retrieve list state. This will be applied after the QueryHandler has run
- mListState = icicle.getParcelable(LIST_STATE_KEY);
- }
-
- @Override
- protected void onStop() {
- super.onStop();
-
- mAdapter.setSuggestionsCursor(null);
- mAdapter.changeCursor(null);
-
- if (mMode == MODE_QUERY) {
- // Make sure the search box is closed
- SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
- searchManager.stopSearch();
- }
- }
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- super.onCreateOptionsMenu(menu);
-
- // If Contacts was invoked by another Activity simply as a way of
- // picking a contact, don't show the options menu
- if ((mMode & MODE_MASK_PICKER) == MODE_MASK_PICKER) {
- return false;
- }
-
- MenuInflater inflater = getMenuInflater();
- inflater.inflate(R.menu.list, menu);
- return true;
- }
-
- @Override
- public boolean onPrepareOptionsMenu(Menu menu) {
- final boolean defaultMode = (mMode == MODE_DEFAULT);
- menu.findItem(R.id.menu_display_groups).setVisible(defaultMode);
- return true;
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case R.id.menu_display_groups: {
- final Intent intent = new Intent(this, ContactsPreferencesActivity.class);
- startActivityForResult(intent, SUBACTIVITY_DISPLAY_GROUP);
- return true;
- }
- case R.id.menu_search: {
- onSearchRequested();
- return true;
- }
- case R.id.menu_add: {
- final Intent intent = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI);
- startActivity(intent);
- return true;
- }
- case R.id.menu_import_export: {
- displayImportExportDialog();
- return true;
- }
- case R.id.menu_accounts: {
- final Intent intent = new Intent(Settings.ACTION_SYNC_SETTINGS);
- intent.putExtra(AUTHORITIES_FILTER_KEY, new String[] {
- ContactsContract.AUTHORITY
- });
- startActivity(intent);
- return true;
- }
- }
- return false;
- }
-
- @Override
- public void startSearch(String initialQuery, boolean selectInitialQuery, Bundle appSearchData,
- boolean globalSearch) {
- if (mProviderStatus != ProviderStatus.STATUS_NORMAL) {
- return;
- }
-
- if (globalSearch) {
- super.startSearch(initialQuery, selectInitialQuery, appSearchData, globalSearch);
- } else {
- if (!mSearchMode && (mMode & MODE_MASK_NO_FILTER) == 0) {
- if ((mMode & MODE_MASK_PICKER) != 0) {
- ContactsSearchManager.startSearchForResult(this, initialQuery,
- SUBACTIVITY_FILTER);
- } else {
- ContactsSearchManager.startSearch(this, initialQuery);
- }
- }
- }
- }
-
- /**
- * Performs filtering of the list based on the search query entered in the
- * search text edit.
- */
- protected void onSearchTextChanged() {
- // Set the proper empty string
- setEmptyText();
-
- Filter filter = mAdapter.getFilter();
- filter.filter(getTextFilter());
- }
-
- /**
- * Starts a new activity that will run a search query and display search results.
- */
- private void doSearch() {
- String query = getTextFilter();
- if (TextUtils.isEmpty(query)) {
- return;
- }
-
- Intent intent = new Intent(this, SearchResultsActivity.class);
- Intent originalIntent = getIntent();
- Bundle originalExtras = originalIntent.getExtras();
- if (originalExtras != null) {
- intent.putExtras(originalExtras);
- }
-
- intent.putExtra(SearchManager.QUERY, query);
- if ((mMode & MODE_MASK_PICKER) != 0) {
- intent.setAction(ACTION_SEARCH_INTERNAL);
- intent.putExtra(SHORTCUT_ACTION_KEY, mShortcutAction);
- if (mShortcutAction != null) {
- if (Intent.ACTION_CALL.equals(mShortcutAction)
- || Intent.ACTION_SENDTO.equals(mShortcutAction)) {
- intent.putExtra(Insert.PHONE, query);
- }
- } else {
- switch (mQueryMode) {
- case QUERY_MODE_MAILTO:
- intent.putExtra(Insert.EMAIL, query);
- break;
- case QUERY_MODE_TEL:
- intent.putExtra(Insert.PHONE, query);
- break;
- }
- }
- startActivityForResult(intent, SUBACTIVITY_SEARCH);
- } else {
- intent.setAction(Intent.ACTION_SEARCH);
- startActivity(intent);
- }
- }
-
- @Override
- protected Dialog onCreateDialog(int id, Bundle bundle) {
- switch (id) {
- case R.string.import_from_sim:
- case R.string.import_from_sdcard: {
- return AccountSelectionUtil.getSelectAccountDialog(this, id);
- }
- case R.id.dialog_sdcard_not_found: {
- return new AlertDialog.Builder(this)
- .setTitle(R.string.no_sdcard_title)
- .setIcon(android.R.drawable.ic_dialog_alert)
- .setMessage(R.string.no_sdcard_message)
- .setPositiveButton(android.R.string.ok, null).create();
- }
- case R.id.dialog_delete_contact_confirmation: {
- return new AlertDialog.Builder(this)
- .setTitle(R.string.deleteConfirmation_title)
- .setIcon(android.R.drawable.ic_dialog_alert)
- .setMessage(R.string.deleteConfirmation)
- .setNegativeButton(android.R.string.cancel, null)
- .setPositiveButton(android.R.string.ok,
- new DeleteClickListener()).create();
- }
- case R.id.dialog_readonly_contact_hide_confirmation: {
- return new AlertDialog.Builder(this)
- .setTitle(R.string.deleteConfirmation_title)
- .setIcon(android.R.drawable.ic_dialog_alert)
- .setMessage(R.string.readOnlyContactWarning)
- .setNegativeButton(android.R.string.cancel, null)
- .setPositiveButton(android.R.string.ok,
- new DeleteClickListener()).create();
- }
- case R.id.dialog_readonly_contact_delete_confirmation: {
- return new AlertDialog.Builder(this)
- .setTitle(R.string.deleteConfirmation_title)
- .setIcon(android.R.drawable.ic_dialog_alert)
- .setMessage(R.string.readOnlyContactDeleteConfirmation)
- .setNegativeButton(android.R.string.cancel, null)
- .setPositiveButton(android.R.string.ok,
- new DeleteClickListener()).create();
- }
- case R.id.dialog_multiple_contact_delete_confirmation: {
- return new AlertDialog.Builder(this)
- .setTitle(R.string.deleteConfirmation_title)
- .setIcon(android.R.drawable.ic_dialog_alert)
- .setMessage(R.string.multipleContactDeleteConfirmation)
- .setNegativeButton(android.R.string.cancel, null)
- .setPositiveButton(android.R.string.ok,
- new DeleteClickListener()).create();
- }
- }
- return super.onCreateDialog(id, bundle);
- }
-
- /**
- * Create a {@link Dialog} that allows the user to pick from a bulk import
- * or bulk export task across all contacts.
- */
- private void displayImportExportDialog() {
- // Wrap our context to inflate list items using correct theme
- final Context dialogContext = new ContextThemeWrapper(this, android.R.style.Theme_Light);
- final Resources res = dialogContext.getResources();
- final LayoutInflater dialogInflater = (LayoutInflater)dialogContext
- .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-
- // Adapter that shows a list of string resources
- final ArrayAdapter<Integer> adapter = new ArrayAdapter<Integer>(this,
- android.R.layout.simple_list_item_1) {
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- if (convertView == null) {
- convertView = dialogInflater.inflate(android.R.layout.simple_list_item_1,
- parent, false);
- }
-
- final int resId = this.getItem(position);
- ((TextView)convertView).setText(resId);
- return convertView;
- }
- };
-
- if (TelephonyManager.getDefault().hasIccCard()) {
- adapter.add(R.string.import_from_sim);
- }
- if (res.getBoolean(R.bool.config_allow_import_from_sdcard)) {
- adapter.add(R.string.import_from_sdcard);
- }
- if (res.getBoolean(R.bool.config_allow_export_to_sdcard)) {
- adapter.add(R.string.export_to_sdcard);
- }
- if (res.getBoolean(R.bool.config_allow_share_visible_contacts)) {
- adapter.add(R.string.share_visible_contacts);
- }
-
- final DialogInterface.OnClickListener clickListener =
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- dialog.dismiss();
-
- final int resId = adapter.getItem(which);
- switch (resId) {
- case R.string.import_from_sim:
- case R.string.import_from_sdcard: {
- handleImportRequest(resId);
- break;
- }
- case R.string.export_to_sdcard: {
- Context context = ContactsListActivity.this;
- Intent exportIntent = new Intent(context, ExportVCardActivity.class);
- context.startActivity(exportIntent);
- break;
- }
- case R.string.share_visible_contacts: {
- doShareVisibleContacts();
- break;
- }
- default: {
- Log.e(TAG, "Unexpected resource: " +
- getResources().getResourceEntryName(resId));
- }
- }
- }
- };
-
- new AlertDialog.Builder(this)
- .setTitle(R.string.dialog_import_export)
- .setNegativeButton(android.R.string.cancel, null)
- .setSingleChoiceItems(adapter, -1, clickListener)
- .show();
- }
-
- private void doShareVisibleContacts() {
- final Cursor cursor = getContentResolver().query(Contacts.CONTENT_URI,
- sLookupProjection, getContactSelection(), null, null);
- try {
- if (!cursor.moveToFirst()) {
- Toast.makeText(this, R.string.share_error, Toast.LENGTH_SHORT).show();
- return;
- }
-
- StringBuilder uriListBuilder = new StringBuilder();
- int index = 0;
- for (;!cursor.isAfterLast(); cursor.moveToNext()) {
- if (index != 0)
- uriListBuilder.append(':');
- uriListBuilder.append(cursor.getString(0));
- index++;
- }
- Uri uri = Uri.withAppendedPath(
- Contacts.CONTENT_MULTI_VCARD_URI,
- Uri.encode(uriListBuilder.toString()));
-
- final Intent intent = new Intent(Intent.ACTION_SEND);
- intent.setType(Contacts.CONTENT_VCARD_TYPE);
- intent.putExtra(Intent.EXTRA_STREAM, uri);
- startActivity(intent);
- } finally {
- cursor.close();
- }
- }
-
- private void handleImportRequest(int resId) {
- // There's three possibilities:
- // - more than one accounts -> ask the user
- // - just one account -> use the account without asking the user
- // - no account -> use phone-local storage without asking the user
- final Sources sources = Sources.getInstance(this);
- final List<Account> accountList = sources.getAccounts(true);
- final int size = accountList.size();
- if (size > 1) {
- showDialog(resId);
- return;
- }
-
- AccountSelectionUtil.doImport(this, resId, (size == 1 ? accountList.get(0) : null));
- }
-
- @Override
- protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- switch (requestCode) {
- case SUBACTIVITY_NEW_CONTACT:
- if (resultCode == RESULT_OK) {
- returnPickerResult(null, data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME),
- data.getData());
- }
- break;
-
- case SUBACTIVITY_VIEW_CONTACT:
- if (resultCode == RESULT_OK) {
- mAdapter.notifyDataSetChanged();
- }
- break;
-
- case SUBACTIVITY_DISPLAY_GROUP:
- // Mark as just created so we re-run the view query
- mJustCreated = true;
- break;
-
- case SUBACTIVITY_FILTER:
- case SUBACTIVITY_SEARCH:
- // Pass through results of filter or search UI
- if (resultCode == RESULT_OK) {
- setResult(RESULT_OK, data);
- finish();
- }
- break;
- }
- }
-
- @Override
- public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) {
- // If Contacts was invoked by another Activity simply as a way of
- // picking a contact, don't show the context menu
- if ((mMode & MODE_MASK_PICKER) == MODE_MASK_PICKER) {
- return;
- }
-
- AdapterView.AdapterContextMenuInfo info;
- try {
- info = (AdapterView.AdapterContextMenuInfo) menuInfo;
- } catch (ClassCastException e) {
- Log.e(TAG, "bad menuInfo", e);
- return;
- }
-
- Cursor cursor = (Cursor) getListAdapter().getItem(info.position);
- if (cursor == null) {
- // For some reason the requested item isn't available, do nothing
- return;
- }
- long id = info.id;
- Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, id);
- long rawContactId = ContactsUtils.queryForRawContactId(getContentResolver(), id);
- Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
-
- // Setup the menu header
- menu.setHeaderTitle(cursor.getString(getSummaryDisplayNameColumnIndex()));
-
- // View contact details
- menu.add(0, MENU_ITEM_VIEW_CONTACT, 0, R.string.menu_viewContact)
- .setIntent(new Intent(Intent.ACTION_VIEW, contactUri));
-
- if (cursor.getInt(SUMMARY_HAS_PHONE_COLUMN_INDEX) != 0) {
- // Calling contact
- menu.add(0, MENU_ITEM_CALL, 0,
- getString(R.string.menu_call));
- // Send SMS item
- menu.add(0, MENU_ITEM_SEND_SMS, 0, getString(R.string.menu_sendSMS));
- }
-
- // Star toggling
- int starState = cursor.getInt(SUMMARY_STARRED_COLUMN_INDEX);
- if (starState == 0) {
- menu.add(0, MENU_ITEM_TOGGLE_STAR, 0, R.string.menu_addStar);
- } else {
- menu.add(0, MENU_ITEM_TOGGLE_STAR, 0, R.string.menu_removeStar);
- }
-
- // Contact editing
- menu.add(0, MENU_ITEM_EDIT, 0, R.string.menu_editContact)
- .setIntent(new Intent(Intent.ACTION_EDIT, rawContactUri));
- menu.add(0, MENU_ITEM_DELETE, 0, R.string.menu_deleteContact);
- }
-
- @Override
- public boolean onContextItemSelected(MenuItem item) {
- AdapterView.AdapterContextMenuInfo info;
- try {
- info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
- } catch (ClassCastException e) {
- Log.e(TAG, "bad menuInfo", e);
- return false;
- }
-
- Cursor cursor = (Cursor) getListAdapter().getItem(info.position);
-
- switch (item.getItemId()) {
- case MENU_ITEM_TOGGLE_STAR: {
- // Toggle the star
- ContentValues values = new ContentValues(1);
- values.put(Contacts.STARRED, cursor.getInt(SUMMARY_STARRED_COLUMN_INDEX) == 0 ? 1 : 0);
- final Uri selectedUri = this.getContactUri(info.position);
- getContentResolver().update(selectedUri, values, null, null);
- return true;
- }
-
- case MENU_ITEM_CALL: {
- callContact(cursor);
- return true;
- }
-
- case MENU_ITEM_SEND_SMS: {
- smsContact(cursor);
- return true;
- }
-
- case MENU_ITEM_DELETE: {
- doContactDelete(getContactUri(info.position));
- return true;
- }
- }
-
- return super.onContextItemSelected(item);
- }
-
- /**
- * Event handler for the use case where the user starts typing without
- * bringing up the search UI first.
- */
- public boolean onKey(View v, int keyCode, KeyEvent event) {
- if (!mSearchMode && (mMode & MODE_MASK_NO_FILTER) == 0 && !mSearchInitiated) {
- int unicodeChar = event.getUnicodeChar();
- if (unicodeChar != 0) {
- mSearchInitiated = true;
- startSearch(new String(new int[]{unicodeChar}, 0, 1), false, null, false);
- return true;
- }
- }
- return false;
- }
-
- /**
- * Event handler for search UI.
- */
- public void afterTextChanged(Editable s) {
- onSearchTextChanged();
- }
-
- public void beforeTextChanged(CharSequence s, int start, int count, int after) {
- }
-
- public void onTextChanged(CharSequence s, int start, int before, int count) {
- }
-
- /**
- * Event handler for search UI.
- */
- public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
- if (actionId == EditorInfo.IME_ACTION_DONE) {
- hideSoftKeyboard();
- if (TextUtils.isEmpty(getTextFilter())) {
- finish();
- }
- return true;
- }
- return false;
- }
-
- @Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- switch (keyCode) {
- case KeyEvent.KEYCODE_CALL: {
- if (callSelection()) {
- return true;
- }
- break;
- }
-
- case KeyEvent.KEYCODE_DEL: {
- if (deleteSelection()) {
- return true;
- }
- break;
- }
- }
-
- return super.onKeyDown(keyCode, event);
- }
-
- private boolean deleteSelection() {
- if ((mMode & MODE_MASK_PICKER) != 0) {
- return false;
- }
-
- final int position = getListView().getSelectedItemPosition();
- if (position != ListView.INVALID_POSITION) {
- Uri contactUri = getContactUri(position);
- if (contactUri != null) {
- doContactDelete(contactUri);
- return true;
- }
- }
- return false;
- }
-
- /**
- * Prompt the user before deleting the given {@link Contacts} entry.
- */
- protected void doContactDelete(Uri contactUri) {
- mReadOnlySourcesCnt = 0;
- mWritableSourcesCnt = 0;
- mWritableRawContactIds.clear();
-
- Sources sources = Sources.getInstance(ContactsListActivity.this);
- Cursor c = getContentResolver().query(RawContacts.CONTENT_URI, RAW_CONTACTS_PROJECTION,
- RawContacts.CONTACT_ID + "=" + ContentUris.parseId(contactUri), null,
- null);
- if (c != null) {
- try {
- while (c.moveToNext()) {
- final String accountType = c.getString(2);
- final long rawContactId = c.getLong(0);
- ContactsSource contactsSource = sources.getInflatedSource(accountType,
- ContactsSource.LEVEL_SUMMARY);
- if (contactsSource != null && contactsSource.readOnly) {
- mReadOnlySourcesCnt += 1;
- } else {
- mWritableSourcesCnt += 1;
- mWritableRawContactIds.add(rawContactId);
- }
- }
- } finally {
- c.close();
- }
- }
-
- mSelectedContactUri = contactUri;
- if (mReadOnlySourcesCnt > 0 && mWritableSourcesCnt > 0) {
- showDialog(R.id.dialog_readonly_contact_delete_confirmation);
- } else if (mReadOnlySourcesCnt > 0 && mWritableSourcesCnt == 0) {
- showDialog(R.id.dialog_readonly_contact_hide_confirmation);
- } else if (mReadOnlySourcesCnt == 0 && mWritableSourcesCnt > 1) {
- showDialog(R.id.dialog_multiple_contact_delete_confirmation);
- } else {
- showDialog(R.id.dialog_delete_contact_confirmation);
- }
- }
-
- /**
- * Dismisses the soft keyboard when the list takes focus.
- */
- public void onFocusChange(View view, boolean hasFocus) {
- if (view == getListView() && hasFocus) {
- hideSoftKeyboard();
- }
- }
-
- /**
- * Dismisses the soft keyboard when the list takes focus.
- */
- public boolean onTouch(View view, MotionEvent event) {
- if (view == getListView()) {
- hideSoftKeyboard();
- }
- return false;
- }
-
- /**
- * Dismisses the search UI along with the keyboard if the filter text is empty.
- */
- public boolean onKeyPreIme(int keyCode, KeyEvent event) {
- if (mSearchMode && keyCode == KeyEvent.KEYCODE_BACK && TextUtils.isEmpty(getTextFilter())) {
- hideSoftKeyboard();
- onBackPressed();
- return true;
- }
- return false;
- }
-
- @Override
- protected void onListItemClick(ListView l, View v, int position, long id) {
- hideSoftKeyboard();
-
- if (mSearchMode && mAdapter.isSearchAllContactsItemPosition(position)) {
- doSearch();
- } else if (mMode == MODE_INSERT_OR_EDIT_CONTACT || mMode == MODE_QUERY_PICK_TO_EDIT) {
- Intent intent;
- if (position == 0 && !mSearchMode && mMode != MODE_QUERY_PICK_TO_EDIT) {
- intent = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI);
- } else {
- intent = new Intent(Intent.ACTION_EDIT, getSelectedUri(position));
- }
- intent.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
- Bundle extras = getIntent().getExtras();
- if (extras != null) {
- intent.putExtras(extras);
- }
- intent.putExtra(KEY_PICKER_MODE, (mMode & MODE_MASK_PICKER) == MODE_MASK_PICKER);
-
- startActivity(intent);
- finish();
- } else if ((mMode & MODE_MASK_CREATE_NEW) == MODE_MASK_CREATE_NEW
- && position == 0) {
- Intent newContact = new Intent(Intents.Insert.ACTION, Contacts.CONTENT_URI);
- startActivityForResult(newContact, SUBACTIVITY_NEW_CONTACT);
- } else if (mMode == MODE_JOIN_CONTACT && id == JOIN_MODE_SHOW_ALL_CONTACTS_ID) {
- mJoinModeShowAllContacts = false;
- startQuery();
- } else if (id > 0) {
- final Uri uri = getSelectedUri(position);
- if ((mMode & MODE_MASK_PICKER) == 0) {
- final Intent intent = new Intent(Intent.ACTION_VIEW, uri);
- startActivityForResult(intent, SUBACTIVITY_VIEW_CONTACT);
- } else if (mMode == MODE_JOIN_CONTACT) {
- returnPickerResult(null, null, uri);
- } else if (mMode == MODE_QUERY_PICK_TO_VIEW) {
- // Started with query that should launch to view contact
- final Intent intent = new Intent(Intent.ACTION_VIEW, uri);
- startActivity(intent);
- finish();
- } else if (mMode == MODE_PICK_PHONE || mMode == MODE_QUERY_PICK_PHONE) {
- Cursor c = (Cursor) mAdapter.getItem(position);
- returnPickerResult(c, c.getString(PHONE_DISPLAY_NAME_COLUMN_INDEX), uri);
- } else if ((mMode & MODE_MASK_PICKER) != 0) {
- Cursor c = (Cursor) mAdapter.getItem(position);
- returnPickerResult(c, c.getString(getSummaryDisplayNameColumnIndex()), uri);
- } else if (mMode == MODE_PICK_POSTAL
- || mMode == MODE_LEGACY_PICK_POSTAL
- || mMode == MODE_LEGACY_PICK_PHONE) {
- returnPickerResult(null, null, uri);
- }
- } else {
- signalError();
- }
- }
-
- private void hideSoftKeyboard() {
- // Hide soft keyboard, if visible
- InputMethodManager inputMethodManager = (InputMethodManager)
- getSystemService(Context.INPUT_METHOD_SERVICE);
- inputMethodManager.hideSoftInputFromWindow(mList.getWindowToken(), 0);
- }
-
- /**
- * @param selectedUri In most cases, this should be a lookup {@link Uri}, possibly
- * generated through {@link Contacts#getLookupUri(long, String)}.
- */
- private void returnPickerResult(Cursor c, String name, Uri selectedUri) {
- final Intent intent = new Intent();
-
- if (mShortcutAction != null) {
- Intent shortcutIntent;
- if (Intent.ACTION_VIEW.equals(mShortcutAction)) {
- // This is a simple shortcut to view a contact.
- shortcutIntent = new Intent(ContactsContract.QuickContact.ACTION_QUICK_CONTACT);
- shortcutIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
- Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
-
- shortcutIntent.setData(selectedUri);
- shortcutIntent.putExtra(ContactsContract.QuickContact.EXTRA_MODE,
- ContactsContract.QuickContact.MODE_LARGE);
- shortcutIntent.putExtra(ContactsContract.QuickContact.EXTRA_EXCLUDE_MIMES,
- (String[]) null);
-
- final Bitmap icon = framePhoto(loadContactPhoto(selectedUri, null));
- if (icon != null) {
- intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, scaleToAppIconSize(icon));
- } else {
- intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
- Intent.ShortcutIconResource.fromContext(this,
- R.drawable.ic_launcher_shortcut_contact));
- }
- } else {
- // This is a direct dial or sms shortcut.
- String number = c.getString(PHONE_NUMBER_COLUMN_INDEX);
- int type = c.getInt(PHONE_TYPE_COLUMN_INDEX);
- String scheme;
- int resid;
- if (Intent.ACTION_CALL.equals(mShortcutAction)) {
- scheme = Constants.SCHEME_TEL;
- resid = R.drawable.badge_action_call;
- } else {
- scheme = Constants.SCHEME_SMSTO;
- resid = R.drawable.badge_action_sms;
- }
-
- // Make the URI a direct tel: URI so that it will always continue to work
- Uri phoneUri = Uri.fromParts(scheme, number, null);
- shortcutIntent = new Intent(mShortcutAction, phoneUri);
-
- intent.putExtra(Intent.EXTRA_SHORTCUT_ICON,
- generatePhoneNumberIcon(selectedUri, type, resid));
- }
- shortcutIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
- intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
- intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, name);
- setResult(RESULT_OK, intent);
- } else {
- intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, name);
- setResult(RESULT_OK, intent.setData(selectedUri));
- }
- finish();
- }
-
- private Bitmap framePhoto(Bitmap photo) {
- final Resources r = getResources();
- final Drawable frame = r.getDrawable(com.android.internal.R.drawable.quickcontact_badge);
-
- final int width = r.getDimensionPixelSize(R.dimen.contact_shortcut_frame_width);
- final int height = r.getDimensionPixelSize(R.dimen.contact_shortcut_frame_height);
-
- frame.setBounds(0, 0, width, height);
-
- final Rect padding = new Rect();
- frame.getPadding(padding);
-
- final Rect source = new Rect(0, 0, photo.getWidth(), photo.getHeight());
- final Rect destination = new Rect(padding.left, padding.top,
- width - padding.right, height - padding.bottom);
-
- final int d = Math.max(width, height);
- final Bitmap b = Bitmap.createBitmap(d, d, Bitmap.Config.ARGB_8888);
- final Canvas c = new Canvas(b);
-
- c.translate((d - width) / 2.0f, (d - height) / 2.0f);
- frame.draw(c);
- c.drawBitmap(photo, source, destination, new Paint(Paint.FILTER_BITMAP_FLAG));
-
- return b;
- }
-
- /**
- * Generates a phone number shortcut icon. Adds an overlay describing the type of the phone
- * number, and if there is a photo also adds the call action icon.
- *
- * @param lookupUri The person the phone number belongs to
- * @param type The type of the phone number
- * @param actionResId The ID for the action resource
- * @return The bitmap for the icon
- */
- private Bitmap generatePhoneNumberIcon(Uri lookupUri, int type, int actionResId) {
- final Resources r = getResources();
- boolean drawPhoneOverlay = true;
- final float scaleDensity = getResources().getDisplayMetrics().scaledDensity;
-
- Bitmap photo = loadContactPhoto(lookupUri, null);
- if (photo == null) {
- // If there isn't a photo use the generic phone action icon instead
- Bitmap phoneIcon = getPhoneActionIcon(r, actionResId);
- if (phoneIcon != null) {
- photo = phoneIcon;
- drawPhoneOverlay = false;
- } else {
- return null;
- }
- }
-
- // Setup the drawing classes
- Bitmap icon = createShortcutBitmap();
- Canvas canvas = new Canvas(icon);
-
- // Copy in the photo
- Paint photoPaint = new Paint();
- photoPaint.setDither(true);
- photoPaint.setFilterBitmap(true);
- Rect src = new Rect(0,0, photo.getWidth(),photo.getHeight());
- Rect dst = new Rect(0,0, mIconSize, mIconSize);
- canvas.drawBitmap(photo, src, dst, photoPaint);
-
- // Create an overlay for the phone number type
- String overlay = null;
- switch (type) {
- case Phone.TYPE_HOME:
- overlay = getString(R.string.type_short_home);
- break;
-
- case Phone.TYPE_MOBILE:
- overlay = getString(R.string.type_short_mobile);
- break;
-
- case Phone.TYPE_WORK:
- overlay = getString(R.string.type_short_work);
- break;
-
- case Phone.TYPE_PAGER:
- overlay = getString(R.string.type_short_pager);
- break;
-
- case Phone.TYPE_OTHER:
- overlay = getString(R.string.type_short_other);
- break;
- }
- if (overlay != null) {
- Paint textPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG);
- textPaint.setTextSize(20.0f * scaleDensity);
- textPaint.setTypeface(Typeface.DEFAULT_BOLD);
- textPaint.setColor(r.getColor(R.color.textColorIconOverlay));
- textPaint.setShadowLayer(3f, 1, 1, r.getColor(R.color.textColorIconOverlayShadow));
- canvas.drawText(overlay, 2 * scaleDensity, 16 * scaleDensity, textPaint);
- }
-
- // Draw the phone action icon as an overlay
- if (ENABLE_ACTION_ICON_OVERLAYS && drawPhoneOverlay) {
- Bitmap phoneIcon = getPhoneActionIcon(r, actionResId);
- if (phoneIcon != null) {
- src.set(0, 0, phoneIcon.getWidth(), phoneIcon.getHeight());
- int iconWidth = icon.getWidth();
- dst.set(iconWidth - ((int) (20 * scaleDensity)), -1,
- iconWidth, ((int) (19 * scaleDensity)));
- canvas.drawBitmap(phoneIcon, src, dst, photoPaint);
- }
- }
-
- return icon;
- }
-
- private Bitmap scaleToAppIconSize(Bitmap photo) {
- // Setup the drawing classes
- Bitmap icon = createShortcutBitmap();
- Canvas canvas = new Canvas(icon);
-
- // Copy in the photo
- Paint photoPaint = new Paint();
- photoPaint.setDither(true);
- photoPaint.setFilterBitmap(true);
- Rect src = new Rect(0,0, photo.getWidth(),photo.getHeight());
- Rect dst = new Rect(0,0, mIconSize, mIconSize);
- canvas.drawBitmap(photo, src, dst, photoPaint);
-
- return icon;
- }
-
- private Bitmap createShortcutBitmap() {
- return Bitmap.createBitmap(mIconSize, mIconSize, Bitmap.Config.ARGB_8888);
- }
-
- /**
- * Returns the icon for the phone call action.
- *
- * @param r The resources to load the icon from
- * @param resId The resource ID to load
- * @return the icon for the phone call action
- */
- private Bitmap getPhoneActionIcon(Resources r, int resId) {
- Drawable phoneIcon = r.getDrawable(resId);
- if (phoneIcon instanceof BitmapDrawable) {
- BitmapDrawable bd = (BitmapDrawable) phoneIcon;
- return bd.getBitmap();
- } else {
- return null;
- }
- }
-
- private Uri getUriToQuery() {
- switch(mMode) {
- case MODE_JOIN_CONTACT:
- return getJoinSuggestionsUri(null);
- case MODE_FREQUENT:
- case MODE_STARRED:
- return Contacts.CONTENT_URI;
-
- case MODE_DEFAULT:
- case MODE_CUSTOM:
- case MODE_INSERT_OR_EDIT_CONTACT:
- case MODE_PICK_CONTACT:
- case MODE_PICK_OR_CREATE_CONTACT:{
- return CONTACTS_CONTENT_URI_WITH_LETTER_COUNTS;
- }
- case MODE_STREQUENT: {
- return Contacts.CONTENT_STREQUENT_URI;
- }
- case MODE_LEGACY_PICK_PERSON:
- case MODE_LEGACY_PICK_OR_CREATE_PERSON: {
- return People.CONTENT_URI;
- }
- case MODE_PICK_PHONE: {
- return buildSectionIndexerUri(Phone.CONTENT_URI);
- }
- case MODE_LEGACY_PICK_PHONE: {
- return Phones.CONTENT_URI;
- }
- case MODE_PICK_POSTAL: {
- return buildSectionIndexerUri(StructuredPostal.CONTENT_URI);
- }
- case MODE_LEGACY_PICK_POSTAL: {
- return ContactMethods.CONTENT_URI;
- }
- case MODE_QUERY_PICK_TO_VIEW: {
- if (mQueryMode == QUERY_MODE_MAILTO) {
- return Uri.withAppendedPath(Email.CONTENT_FILTER_URI,
- Uri.encode(mInitialFilter));
- } else if (mQueryMode == QUERY_MODE_TEL) {
- return Uri.withAppendedPath(Phone.CONTENT_FILTER_URI,
- Uri.encode(mInitialFilter));
- }
- return CONTACTS_CONTENT_URI_WITH_LETTER_COUNTS;
- }
- case MODE_QUERY:
- case MODE_QUERY_PICK:
- case MODE_QUERY_PICK_TO_EDIT: {
- return getContactFilterUri(mInitialFilter);
- }
- case MODE_QUERY_PICK_PHONE: {
- return Uri.withAppendedPath(Phone.CONTENT_FILTER_URI,
- Uri.encode(mInitialFilter));
- }
- case MODE_GROUP: {
- return mGroupUri;
- }
- default: {
- throw new IllegalStateException("Can't generate URI: Unsupported Mode.");
- }
- }
- }
-
- /**
- * Build the {@link Contacts#CONTENT_LOOKUP_URI} for the given
- * {@link ListView} position, using {@link #mAdapter}.
- */
- private Uri getContactUri(int position) {
- if (position == ListView.INVALID_POSITION) {
- throw new IllegalArgumentException("Position not in list bounds");
- }
-
- final Cursor cursor = (Cursor)mAdapter.getItem(position);
- if (cursor == null) {
- return null;
- }
-
- switch(mMode) {
- case MODE_LEGACY_PICK_PERSON:
- case MODE_LEGACY_PICK_OR_CREATE_PERSON: {
- final long personId = cursor.getLong(SUMMARY_ID_COLUMN_INDEX);
- return ContentUris.withAppendedId(People.CONTENT_URI, personId);
- }
-
- default: {
- // Build and return soft, lookup reference
- final long contactId = cursor.getLong(SUMMARY_ID_COLUMN_INDEX);
- final String lookupKey = cursor.getString(SUMMARY_LOOKUP_KEY_COLUMN_INDEX);
- return Contacts.getLookupUri(contactId, lookupKey);
- }
- }
- }
-
- /**
- * Build the {@link Uri} for the given {@link ListView} position, which can
- * be used as result when in {@link #MODE_MASK_PICKER} mode.
- */
- private Uri getSelectedUri(int position) {
- if (position == ListView.INVALID_POSITION) {
- throw new IllegalArgumentException("Position not in list bounds");
- }
-
- final long id = mAdapter.getItemId(position);
- switch(mMode) {
- case MODE_LEGACY_PICK_PERSON:
- case MODE_LEGACY_PICK_OR_CREATE_PERSON: {
- return ContentUris.withAppendedId(People.CONTENT_URI, id);
- }
- case MODE_PICK_PHONE:
- case MODE_QUERY_PICK_PHONE: {
- return ContentUris.withAppendedId(Data.CONTENT_URI, id);
- }
- case MODE_LEGACY_PICK_PHONE: {
- return ContentUris.withAppendedId(Phones.CONTENT_URI, id);
- }
- case MODE_PICK_POSTAL: {
- return ContentUris.withAppendedId(Data.CONTENT_URI, id);
- }
- case MODE_LEGACY_PICK_POSTAL: {
- return ContentUris.withAppendedId(ContactMethods.CONTENT_URI, id);
- }
- default: {
- return getContactUri(position);
- }
- }
- }
-
- String[] getProjectionForQuery() {
- switch(mMode) {
- case MODE_JOIN_CONTACT:
- case MODE_STREQUENT:
- case MODE_FREQUENT:
- case MODE_STARRED:
- case MODE_DEFAULT:
- case MODE_CUSTOM:
- case MODE_INSERT_OR_EDIT_CONTACT:
- case MODE_GROUP:
- case MODE_PICK_CONTACT:
- case MODE_PICK_OR_CREATE_CONTACT: {
- return mSearchMode
- ? CONTACTS_SUMMARY_FILTER_PROJECTION
- : CONTACTS_SUMMARY_PROJECTION;
- }
- case MODE_QUERY:
- case MODE_QUERY_PICK:
- case MODE_QUERY_PICK_TO_EDIT: {
- return CONTACTS_SUMMARY_FILTER_PROJECTION;
- }
- case MODE_LEGACY_PICK_PERSON:
- case MODE_LEGACY_PICK_OR_CREATE_PERSON: {
- return LEGACY_PEOPLE_PROJECTION ;
- }
- case MODE_QUERY_PICK_PHONE:
- case MODE_PICK_PHONE: {
- return PHONES_PROJECTION;
- }
- case MODE_LEGACY_PICK_PHONE: {
- return LEGACY_PHONES_PROJECTION;
- }
- case MODE_PICK_POSTAL: {
- return POSTALS_PROJECTION;
- }
- case MODE_LEGACY_PICK_POSTAL: {
- return LEGACY_POSTALS_PROJECTION;
- }
- case MODE_QUERY_PICK_TO_VIEW: {
- if (mQueryMode == QUERY_MODE_MAILTO) {
- return CONTACTS_SUMMARY_PROJECTION_FROM_EMAIL;
- } else if (mQueryMode == QUERY_MODE_TEL) {
- return PHONES_PROJECTION;
- }
- break;
- }
- }
-
- // Default to normal aggregate projection
- return CONTACTS_SUMMARY_PROJECTION;
- }
-
- private Bitmap loadContactPhoto(Uri selectedUri, BitmapFactory.Options options) {
- Uri contactUri = null;
- if (Contacts.CONTENT_ITEM_TYPE.equals(getContentResolver().getType(selectedUri))) {
- // TODO we should have a "photo" directory under the lookup URI itself
- contactUri = Contacts.lookupContact(getContentResolver(), selectedUri);
- } else {
-
- Cursor cursor = getContentResolver().query(selectedUri,
- new String[] { Data.CONTACT_ID }, null, null, null);
- try {
- if (cursor != null && cursor.moveToFirst()) {
- final long contactId = cursor.getLong(0);
- contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
- }
- } finally {
- if (cursor != null) cursor.close();
- }
- }
-
- Cursor cursor = null;
- Bitmap bm = null;
-
- try {
- Uri photoUri = Uri.withAppendedPath(contactUri, Contacts.Photo.CONTENT_DIRECTORY);
- cursor = getContentResolver().query(photoUri, new String[] {Photo.PHOTO},
- null, null, null);
- if (cursor != null && cursor.moveToFirst()) {
- bm = ContactsUtils.loadContactPhoto(cursor, 0, options);
- }
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
-
- if (bm == null) {
- final int[] fallbacks = {
- R.drawable.ic_contact_picture,
- R.drawable.ic_contact_picture_2,
- R.drawable.ic_contact_picture_3
- };
- bm = BitmapFactory.decodeResource(getResources(),
- fallbacks[new Random().nextInt(fallbacks.length)]);
- }
-
- return bm;
- }
-
- /**
- * Return the selection arguments for a default query based on the
- * {@link #mDisplayOnlyPhones} flag.
- */
- private String getContactSelection() {
- if (mDisplayOnlyPhones) {
- return CLAUSE_ONLY_VISIBLE + " AND " + CLAUSE_ONLY_PHONES;
- } else {
- return CLAUSE_ONLY_VISIBLE;
- }
- }
-
- private Uri getContactFilterUri(String filter) {
- Uri baseUri;
- if (!TextUtils.isEmpty(filter)) {
- baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, Uri.encode(filter));
- } else {
- baseUri = Contacts.CONTENT_URI;
- }
-
- if (mAdapter.getDisplaySectionHeadersEnabled()) {
- return buildSectionIndexerUri(baseUri);
- } else {
- return baseUri;
- }
- }
-
- private Uri getPeopleFilterUri(String filter) {
- if (!TextUtils.isEmpty(filter)) {
- return Uri.withAppendedPath(People.CONTENT_FILTER_URI, Uri.encode(filter));
- } else {
- return People.CONTENT_URI;
- }
- }
-
- private static Uri buildSectionIndexerUri(Uri uri) {
- return uri.buildUpon()
- .appendQueryParameter(ContactCounts.ADDRESS_BOOK_INDEX_EXTRAS, "true").build();
- }
-
- private Uri getJoinSuggestionsUri(String filter) {
- Builder builder = Contacts.CONTENT_URI.buildUpon();
- builder.appendEncodedPath(String.valueOf(mQueryAggregateId));
- builder.appendEncodedPath(AggregationSuggestions.CONTENT_DIRECTORY);
- if (!TextUtils.isEmpty(filter)) {
- builder.appendEncodedPath(Uri.encode(filter));
- }
- builder.appendQueryParameter("limit", String.valueOf(MAX_SUGGESTIONS));
- return builder.build();
- }
-
- private String getSortOrder(String[] projectionType) {
- if (mSortOrder == ContactsContract.Preferences.SORT_ORDER_PRIMARY) {
- return Contacts.SORT_KEY_PRIMARY;
- } else {
- return Contacts.SORT_KEY_ALTERNATIVE;
- }
- }
-
- void startQuery() {
- // Set the proper empty string
- setEmptyText();
-
- if (mSearchResultsMode) {
- TextView foundContactsText = (TextView)findViewById(R.id.search_results_found);
- foundContactsText.setText(R.string.search_results_searching);
- }
-
- mAdapter.setLoading(true);
-
- // Cancel any pending queries
- mQueryHandler.cancelOperation(QUERY_TOKEN);
- mQueryHandler.setLoadingJoinSuggestions(false);
-
- mSortOrder = mContactsPrefs.getSortOrder();
- mDisplayOrder = mContactsPrefs.getDisplayOrder();
-
- // When sort order and display order contradict each other, we want to
- // highlight the part of the name used for sorting.
- mHighlightWhenScrolling = false;
- if (mSortOrder == ContactsContract.Preferences.SORT_ORDER_PRIMARY &&
- mDisplayOrder == ContactsContract.Preferences.DISPLAY_ORDER_ALTERNATIVE) {
- mHighlightWhenScrolling = true;
- } else if (mSortOrder == ContactsContract.Preferences.SORT_ORDER_ALTERNATIVE &&
- mDisplayOrder == ContactsContract.Preferences.DISPLAY_ORDER_PRIMARY) {
- mHighlightWhenScrolling = true;
- }
-
- String[] projection = getProjectionForQuery();
- if (mSearchMode && TextUtils.isEmpty(getTextFilter())) {
- mAdapter.changeCursor(new MatrixCursor(projection));
- return;
- }
-
- String callingPackage = getCallingPackage();
- Uri uri = getUriToQuery();
- if (!TextUtils.isEmpty(callingPackage)) {
- uri = uri.buildUpon()
- .appendQueryParameter(ContactsContract.REQUESTING_PACKAGE_PARAM_KEY,
- callingPackage)
- .build();
- }
-
- // Kick off the new query
- switch (mMode) {
- case MODE_GROUP:
- case MODE_DEFAULT:
- case MODE_CUSTOM:
- case MODE_PICK_CONTACT:
- case MODE_PICK_OR_CREATE_CONTACT:
- case MODE_INSERT_OR_EDIT_CONTACT:
- mQueryHandler.startQuery(QUERY_TOKEN, null, uri, projection, getContactSelection(),
- null, getSortOrder(projection));
- break;
-
- case MODE_LEGACY_PICK_PERSON:
- case MODE_LEGACY_PICK_OR_CREATE_PERSON: {
- mQueryHandler.startQuery(QUERY_TOKEN, null, uri, projection, null, null,
- People.DISPLAY_NAME);
- break;
- }
- case MODE_PICK_POSTAL:
- case MODE_QUERY:
- case MODE_QUERY_PICK:
- case MODE_QUERY_PICK_PHONE:
- case MODE_QUERY_PICK_TO_VIEW:
- case MODE_QUERY_PICK_TO_EDIT: {
- mQueryHandler.startQuery(QUERY_TOKEN, null, uri, projection, null, null,
- getSortOrder(projection));
- break;
- }
-
- case MODE_STARRED:
- mQueryHandler.startQuery(QUERY_TOKEN, null, uri,
- projection, Contacts.STARRED + "=1", null,
- getSortOrder(projection));
- break;
-
- case MODE_FREQUENT:
- mQueryHandler.startQuery(QUERY_TOKEN, null, uri,
- projection,
- Contacts.TIMES_CONTACTED + " > 0", null,
- Contacts.TIMES_CONTACTED + " DESC, "
- + getSortOrder(projection));
- break;
-
- case MODE_STREQUENT:
- mQueryHandler.startQuery(QUERY_TOKEN, null, uri, projection, null, null, null);
- break;
-
- case MODE_PICK_PHONE:
- mQueryHandler.startQuery(QUERY_TOKEN, null, uri,
- projection, CLAUSE_ONLY_VISIBLE, null, getSortOrder(projection));
- break;
-
- case MODE_LEGACY_PICK_PHONE:
- mQueryHandler.startQuery(QUERY_TOKEN, null, uri,
- projection, null, null, Phones.DISPLAY_NAME);
- break;
-
- case MODE_LEGACY_PICK_POSTAL:
- mQueryHandler.startQuery(QUERY_TOKEN, null, uri,
- projection,
- ContactMethods.KIND + "=" + android.provider.Contacts.KIND_POSTAL, null,
- ContactMethods.DISPLAY_NAME);
- break;
-
- case MODE_JOIN_CONTACT:
- mQueryHandler.setLoadingJoinSuggestions(true);
- mQueryHandler.startQuery(QUERY_TOKEN, null, uri, projection,
- null, null, null);
- break;
- }
- }
-
- /**
- * Called from a background thread to do the filter and return the resulting cursor.
- *
- * @param filter the text that was entered to filter on
- * @return a cursor with the results of the filter
- */
- Cursor doFilter(String filter) {
- String[] projection = getProjectionForQuery();
- if (mSearchMode && TextUtils.isEmpty(getTextFilter())) {
- return new MatrixCursor(projection);
- }
-
- final ContentResolver resolver = getContentResolver();
- switch (mMode) {
- case MODE_DEFAULT:
- case MODE_CUSTOM:
- case MODE_PICK_CONTACT:
- case MODE_PICK_OR_CREATE_CONTACT:
- case MODE_INSERT_OR_EDIT_CONTACT: {
- return resolver.query(getContactFilterUri(filter), projection,
- getContactSelection(), null, getSortOrder(projection));
- }
-
- case MODE_LEGACY_PICK_PERSON:
- case MODE_LEGACY_PICK_OR_CREATE_PERSON: {
- return resolver.query(getPeopleFilterUri(filter), projection, null, null,
- People.DISPLAY_NAME);
- }
-
- case MODE_STARRED: {
- return resolver.query(getContactFilterUri(filter), projection,
- Contacts.STARRED + "=1", null,
- getSortOrder(projection));
- }
-
- case MODE_FREQUENT: {
- return resolver.query(getContactFilterUri(filter), projection,
- Contacts.TIMES_CONTACTED + " > 0", null,
- Contacts.TIMES_CONTACTED + " DESC, "
- + getSortOrder(projection));
- }
-
- case MODE_STREQUENT: {
- Uri uri;
- if (!TextUtils.isEmpty(filter)) {
- uri = Uri.withAppendedPath(Contacts.CONTENT_STREQUENT_FILTER_URI,
- Uri.encode(filter));
- } else {
- uri = Contacts.CONTENT_STREQUENT_URI;
- }
- return resolver.query(uri, projection, null, null, null);
- }
-
- case MODE_PICK_PHONE: {
- Uri uri = getUriToQuery();
- if (!TextUtils.isEmpty(filter)) {
- uri = Uri.withAppendedPath(Phone.CONTENT_FILTER_URI, Uri.encode(filter));
- }
- return resolver.query(uri, projection, CLAUSE_ONLY_VISIBLE, null,
- getSortOrder(projection));
- }
-
- case MODE_LEGACY_PICK_PHONE: {
- //TODO: Support filtering here (bug 2092503)
- break;
- }
-
- case MODE_JOIN_CONTACT: {
-
- // We are on a background thread. Run queries one after the other synchronously
- Cursor cursor = resolver.query(getJoinSuggestionsUri(filter), projection, null,
- null, null);
- mAdapter.setSuggestionsCursor(cursor);
- mJoinModeShowAllContacts = false;
- return resolver.query(getContactFilterUri(filter), projection,
- Contacts._ID + " != " + mQueryAggregateId + " AND " + CLAUSE_ONLY_VISIBLE,
- null, getSortOrder(projection));
- }
- }
- throw new UnsupportedOperationException("filtering not allowed in mode " + mMode);
- }
-
- private Cursor getShowAllContactsLabelCursor(String[] projection) {
- MatrixCursor matrixCursor = new MatrixCursor(projection);
- Object[] row = new Object[projection.length];
- // The only columns we care about is the id
- row[SUMMARY_ID_COLUMN_INDEX] = JOIN_MODE_SHOW_ALL_CONTACTS_ID;
- matrixCursor.addRow(row);
- return matrixCursor;
- }
-
- /**
- * Calls the currently selected list item.
- * @return true if the call was initiated, false otherwise
- */
- boolean callSelection() {
- ListView list = getListView();
- if (list.hasFocus()) {
- Cursor cursor = (Cursor) list.getSelectedItem();
- return callContact(cursor);
- }
- return false;
- }
-
- boolean callContact(Cursor cursor) {
- return callOrSmsContact(cursor, false /*call*/);
- }
-
- boolean smsContact(Cursor cursor) {
- return callOrSmsContact(cursor, true /*sms*/);
- }
-
- /**
- * Calls the contact which the cursor is point to.
- * @return true if the call was initiated, false otherwise
- */
- boolean callOrSmsContact(Cursor cursor, boolean sendSms) {
- if (cursor == null) {
- return false;
- }
-
- switch (mMode) {
- case MODE_PICK_PHONE:
- case MODE_LEGACY_PICK_PHONE:
- case MODE_QUERY_PICK_PHONE: {
- String phone = cursor.getString(PHONE_NUMBER_COLUMN_INDEX);
- if (sendSms) {
- ContactsUtils.initiateSms(this, phone);
- } else {
- ContactsUtils.initiateCall(this, phone);
- }
- return true;
- }
-
- case MODE_PICK_POSTAL:
- case MODE_LEGACY_PICK_POSTAL: {
- return false;
- }
-
- default: {
-
- boolean hasPhone = cursor.getInt(SUMMARY_HAS_PHONE_COLUMN_INDEX) != 0;
- if (!hasPhone) {
- // There is no phone number.
- signalError();
- return false;
- }
-
- String phone = null;
- Cursor phonesCursor = null;
- phonesCursor = queryPhoneNumbers(cursor.getLong(SUMMARY_ID_COLUMN_INDEX));
- if (phonesCursor == null || phonesCursor.getCount() == 0) {
- // No valid number
- signalError();
- return false;
- } else if (phonesCursor.getCount() == 1) {
- // only one number, call it.
- phone = phonesCursor.getString(phonesCursor.getColumnIndex(Phone.NUMBER));
- } else {
- phonesCursor.moveToPosition(-1);
- while (phonesCursor.moveToNext()) {
- if (phonesCursor.getInt(phonesCursor.
- getColumnIndex(Phone.IS_SUPER_PRIMARY)) != 0) {
- // Found super primary, call it.
- phone = phonesCursor.
- getString(phonesCursor.getColumnIndex(Phone.NUMBER));
- break;
- }
- }
- }
-
- if (phone == null) {
- // Display dialog to choose a number to call.
- PhoneDisambigDialog phoneDialog = new PhoneDisambigDialog(
- this, phonesCursor, sendSms);
- phoneDialog.show();
- } else {
- if (sendSms) {
- ContactsUtils.initiateSms(this, phone);
- } else {
- ContactsUtils.initiateCall(this, phone);
- }
- }
- }
- }
- return true;
- }
-
- private Cursor queryPhoneNumbers(long contactId) {
- Uri baseUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
- Uri dataUri = Uri.withAppendedPath(baseUri, Contacts.Data.CONTENT_DIRECTORY);
-
- Cursor c = getContentResolver().query(dataUri,
- new String[] {Phone._ID, Phone.NUMBER, Phone.IS_SUPER_PRIMARY,
- RawContacts.ACCOUNT_TYPE, Phone.TYPE, Phone.LABEL},
- Data.MIMETYPE + "=?", new String[] {Phone.CONTENT_ITEM_TYPE}, null);
- if (c != null && c.moveToFirst()) {
- return c;
- }
- return null;
- }
-
- // TODO: fix PluralRules to handle zero correctly and use Resources.getQuantityText directly
- protected String getQuantityText(int count, int zeroResourceId, int pluralResourceId) {
- if (count == 0) {
- return getString(zeroResourceId);
- } else {
- String format = getResources().getQuantityText(pluralResourceId, count).toString();
- return String.format(format, count);
- }
- }
-
- /**
- * Signal an error to the user.
- */
- void signalError() {
- //TODO play an error beep or something...
- }
-
- Cursor getItemForView(View view) {
- ListView listView = getListView();
- int index = listView.getPositionForView(view);
- if (index < 0) {
- return null;
- }
- return (Cursor) listView.getAdapter().getItem(index);
- }
-
- private static class QueryHandler extends AsyncQueryHandler {
- protected final WeakReference<ContactsListActivity> mActivity;
- protected boolean mLoadingJoinSuggestions = false;
-
- public QueryHandler(Context context) {
- super(context.getContentResolver());
- mActivity = new WeakReference<ContactsListActivity>((ContactsListActivity) context);
- }
-
- public void setLoadingJoinSuggestions(boolean flag) {
- mLoadingJoinSuggestions = flag;
- }
-
- @Override
- protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
- final ContactsListActivity activity = mActivity.get();
- if (activity != null && !activity.isFinishing()) {
-
- // Whenever we get a suggestions cursor, we need to immediately kick off
- // another query for the complete list of contacts
- if (cursor != null && mLoadingJoinSuggestions) {
- mLoadingJoinSuggestions = false;
- if (cursor.getCount() > 0) {
- activity.mAdapter.setSuggestionsCursor(cursor);
- } else {
- cursor.close();
- activity.mAdapter.setSuggestionsCursor(null);
- }
-
- if (activity.mAdapter.mSuggestionsCursorCount == 0
- || !activity.mJoinModeShowAllContacts) {
- startQuery(QUERY_TOKEN, null, activity.getContactFilterUri(
- activity.getTextFilter()),
- CONTACTS_SUMMARY_PROJECTION,
- Contacts._ID + " != " + activity.mQueryAggregateId
- + " AND " + CLAUSE_ONLY_VISIBLE, null,
- activity.getSortOrder(CONTACTS_SUMMARY_PROJECTION));
- return;
- }
-
- cursor = activity.getShowAllContactsLabelCursor(CONTACTS_SUMMARY_PROJECTION);
- }
-
- activity.mAdapter.changeCursor(cursor);
-
- // Now that the cursor is populated again, it's possible to restore the list state
- if (activity.mListState != null) {
- activity.mList.onRestoreInstanceState(activity.mListState);
- activity.mListState = null;
- }
- } else {
- if (cursor != null) {
- cursor.close();
- }
- }
- }
- }
-
- final static class ContactListItemCache {
- public CharArrayBuffer nameBuffer = new CharArrayBuffer(128);
- public CharArrayBuffer dataBuffer = new CharArrayBuffer(128);
- public CharArrayBuffer highlightedTextBuffer = new CharArrayBuffer(128);
- public TextWithHighlighting textWithHighlighting;
- public CharArrayBuffer phoneticNameBuffer = new CharArrayBuffer(128);
- }
-
- final static class PinnedHeaderCache {
- public TextView titleView;
- public ColorStateList textColor;
- public Drawable background;
- }
-
- private final class ContactItemListAdapter extends CursorAdapter
- implements SectionIndexer, OnScrollListener, PinnedHeaderListView.PinnedHeaderAdapter {
- private SectionIndexer mIndexer;
- private boolean mLoading = true;
- private CharSequence mUnknownNameText;
- private boolean mDisplayPhotos = false;
- private boolean mDisplayCallButton = false;
- private boolean mDisplayAdditionalData = true;
- private int mFrequentSeparatorPos = ListView.INVALID_POSITION;
- private boolean mDisplaySectionHeaders = true;
- private Cursor mSuggestionsCursor;
- private int mSuggestionsCursorCount;
-
- public ContactItemListAdapter(Context context) {
- super(context, null, false);
-
- mUnknownNameText = context.getText(android.R.string.unknownName);
- switch (mMode) {
- case MODE_LEGACY_PICK_POSTAL:
- case MODE_PICK_POSTAL:
- case MODE_LEGACY_PICK_PHONE:
- case MODE_PICK_PHONE:
- case MODE_STREQUENT:
- case MODE_FREQUENT:
- mDisplaySectionHeaders = false;
- break;
- }
-
- if (mSearchMode) {
- mDisplaySectionHeaders = false;
- }
-
- // Do not display the second line of text if in a specific SEARCH query mode, usually for
- // matching a specific E-mail or phone number. Any contact details
- // shown would be identical, and columns might not even be present
- // in the returned cursor.
- if (mMode != MODE_QUERY_PICK_PHONE && mQueryMode != QUERY_MODE_NONE) {
- mDisplayAdditionalData = false;
- }
-
- if ((mMode & MODE_MASK_NO_DATA) == MODE_MASK_NO_DATA) {
- mDisplayAdditionalData = false;
- }
-
- if ((mMode & MODE_MASK_SHOW_CALL_BUTTON) == MODE_MASK_SHOW_CALL_BUTTON) {
- mDisplayCallButton = true;
- }
-
- if ((mMode & MODE_MASK_SHOW_PHOTOS) == MODE_MASK_SHOW_PHOTOS) {
- mDisplayPhotos = true;
- }
- }
-
- public boolean getDisplaySectionHeadersEnabled() {
- return mDisplaySectionHeaders;
- }
-
- public void setSuggestionsCursor(Cursor cursor) {
- if (mSuggestionsCursor != null) {
- mSuggestionsCursor.close();
- }
- mSuggestionsCursor = cursor;
- mSuggestionsCursorCount = cursor == null ? 0 : cursor.getCount();
- }
-
- /**
- * Callback on the UI thread when the content observer on the backing cursor fires.
- * Instead of calling requery we need to do an async query so that the requery doesn't
- * block the UI thread for a long time.
- */
- @Override
- protected void onContentChanged() {
- CharSequence constraint = getTextFilter();
- if (!TextUtils.isEmpty(constraint)) {
- // Reset the filter state then start an async filter operation
- Filter filter = getFilter();
- filter.filter(constraint);
- } else {
- // Start an async query
- startQuery();
- }
- }
-
- public void setLoading(boolean loading) {
- mLoading = loading;
- }
-
- @Override
- public boolean isEmpty() {
- if (mProviderStatus != ProviderStatus.STATUS_NORMAL) {
- return true;
- }
-
- if (mSearchMode) {
- return TextUtils.isEmpty(getTextFilter());
- } else if ((mMode & MODE_MASK_CREATE_NEW) == MODE_MASK_CREATE_NEW) {
- // This mode mask adds a header and we always want it to show up, even
- // if the list is empty, so always claim the list is not empty.
- return false;
- } else {
- if (mCursor == null || mLoading) {
- // We don't want the empty state to show when loading.
- return false;
- } else {
- return super.isEmpty();
- }
- }
- }
-
- @Override
- public int getItemViewType(int position) {
- if (position == 0 && (mShowNumberOfContacts || (mMode & MODE_MASK_CREATE_NEW) != 0)) {
- return IGNORE_ITEM_VIEW_TYPE;
- }
-
- if (isShowAllContactsItemPosition(position)) {
- return IGNORE_ITEM_VIEW_TYPE;
- }
-
- if (isSearchAllContactsItemPosition(position)) {
- return IGNORE_ITEM_VIEW_TYPE;
- }
-
- if (getSeparatorId(position) != 0) {
- // We don't want the separator view to be recycled.
- return IGNORE_ITEM_VIEW_TYPE;
- }
-
- return super.getItemViewType(position);
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- if (!mDataValid) {
- throw new IllegalStateException(
- "this should only be called when the cursor is valid");
- }
-
- // handle the total contacts item
- if (position == 0 && mShowNumberOfContacts) {
- return getTotalContactCountView(parent);
- }
-
- if (position == 0 && (mMode & MODE_MASK_CREATE_NEW) != 0) {
- // Add the header for creating a new contact
- return getLayoutInflater().inflate(R.layout.create_new_contact, parent, false);
- }
-
- if (isShowAllContactsItemPosition(position)) {
- return getLayoutInflater().
- inflate(R.layout.contacts_list_show_all_item, parent, false);
- }
-
- if (isSearchAllContactsItemPosition(position)) {
- return getLayoutInflater().
- inflate(R.layout.contacts_list_search_all_item, parent, false);
- }
-
- // Handle the separator specially
- int separatorId = getSeparatorId(position);
- if (separatorId != 0) {
- TextView view = (TextView) getLayoutInflater().
- inflate(R.layout.list_separator, parent, false);
- view.setText(separatorId);
- return view;
- }
-
- boolean showingSuggestion;
- Cursor cursor;
- if (mSuggestionsCursorCount != 0 && position < mSuggestionsCursorCount + 2) {
- showingSuggestion = true;
- cursor = mSuggestionsCursor;
- } else {
- showingSuggestion = false;
- cursor = mCursor;
- }
-
- int realPosition = getRealPosition(position);
- if (!cursor.moveToPosition(realPosition)) {
- throw new IllegalStateException("couldn't move cursor to position " + position);
- }
-
- boolean newView;
- View v;
- if (convertView == null || convertView.getTag() == null) {
- newView = true;
- v = newView(mContext, cursor, parent);
- } else {
- newView = false;
- v = convertView;
- }
- bindView(v, mContext, cursor);
- bindSectionHeader(v, realPosition, mDisplaySectionHeaders && !showingSuggestion);
- return v;
- }
-
- private View getTotalContactCountView(ViewGroup parent) {
- final LayoutInflater inflater = getLayoutInflater();
- View view = inflater.inflate(R.layout.total_contacts, parent, false);
-
- TextView totalContacts = (TextView) view.findViewById(R.id.totalContactsText);
-
- String text;
- int count = getRealCount();
-
- if (mSearchMode && !TextUtils.isEmpty(getTextFilter())) {
- text = getQuantityText(count, R.string.listFoundAllContactsZero,
- R.plurals.searchFoundContacts);
- } else {
- if (mDisplayOnlyPhones) {
- text = getQuantityText(count, R.string.listTotalPhoneContactsZero,
- R.plurals.listTotalPhoneContacts);
- } else {
- text = getQuantityText(count, R.string.listTotalAllContactsZero,
- R.plurals.listTotalAllContacts);
- }
- }
- totalContacts.setText(text);
- return view;
- }
-
- private boolean isShowAllContactsItemPosition(int position) {
- return mMode == MODE_JOIN_CONTACT && mJoinModeShowAllContacts
- && mSuggestionsCursorCount != 0 && position == mSuggestionsCursorCount + 2;
- }
-
- private boolean isSearchAllContactsItemPosition(int position) {
- return mSearchMode && position == getCount() - 1;
- }
-
- private int getSeparatorId(int position) {
- int separatorId = 0;
- if (position == mFrequentSeparatorPos) {
- separatorId = R.string.favoritesFrquentSeparator;
- }
- if (mSuggestionsCursorCount != 0) {
- if (position == 0) {
- separatorId = R.string.separatorJoinAggregateSuggestions;
- } else if (position == mSuggestionsCursorCount + 1) {
- separatorId = R.string.separatorJoinAggregateAll;
- }
- }
- return separatorId;
- }
-
- @Override
- public View newView(Context context, Cursor cursor, ViewGroup parent) {
- final ContactListItemView view = new ContactListItemView(context, null);
- view.setOnCallButtonClickListener(ContactsListActivity.this);
- view.setTag(new ContactListItemCache());
- return view;
- }
-
- @Override
- public void bindView(View itemView, Context context, Cursor cursor) {
- final ContactListItemView view = (ContactListItemView)itemView;
- final ContactListItemCache cache = (ContactListItemCache) view.getTag();
-
- int typeColumnIndex;
- int dataColumnIndex;
- int labelColumnIndex;
- int defaultType;
- int nameColumnIndex;
- int phoneticNameColumnIndex;
- boolean displayAdditionalData = mDisplayAdditionalData;
- boolean highlightingEnabled = false;
- switch(mMode) {
- case MODE_PICK_PHONE:
- case MODE_LEGACY_PICK_PHONE:
- case MODE_QUERY_PICK_PHONE: {
- nameColumnIndex = PHONE_DISPLAY_NAME_COLUMN_INDEX;
- phoneticNameColumnIndex = -1;
- dataColumnIndex = PHONE_NUMBER_COLUMN_INDEX;
- typeColumnIndex = PHONE_TYPE_COLUMN_INDEX;
- labelColumnIndex = PHONE_LABEL_COLUMN_INDEX;
- defaultType = Phone.TYPE_HOME;
- break;
- }
- case MODE_PICK_POSTAL:
- case MODE_LEGACY_PICK_POSTAL: {
- nameColumnIndex = POSTAL_DISPLAY_NAME_COLUMN_INDEX;
- phoneticNameColumnIndex = -1;
- dataColumnIndex = POSTAL_ADDRESS_COLUMN_INDEX;
- typeColumnIndex = POSTAL_TYPE_COLUMN_INDEX;
- labelColumnIndex = POSTAL_LABEL_COLUMN_INDEX;
- defaultType = StructuredPostal.TYPE_HOME;
- break;
- }
- default: {
- nameColumnIndex = getSummaryDisplayNameColumnIndex();
- if (mMode == MODE_LEGACY_PICK_PERSON
- || mMode == MODE_LEGACY_PICK_OR_CREATE_PERSON) {
- phoneticNameColumnIndex = -1;
- } else {
- phoneticNameColumnIndex = SUMMARY_PHONETIC_NAME_COLUMN_INDEX;
- }
- dataColumnIndex = -1;
- typeColumnIndex = -1;
- labelColumnIndex = -1;
- defaultType = Phone.TYPE_HOME;
- displayAdditionalData = false;
- highlightingEnabled = mHighlightWhenScrolling && mMode != MODE_STREQUENT;
- }
- }
-
- // Set the name
- cursor.copyStringToBuffer(nameColumnIndex, cache.nameBuffer);
- TextView nameView = view.getNameTextView();
- int size = cache.nameBuffer.sizeCopied;
- if (size != 0) {
- if (highlightingEnabled) {
- if (cache.textWithHighlighting == null) {
- cache.textWithHighlighting =
- mHighlightingAnimation.createTextWithHighlighting();
- }
- buildDisplayNameWithHighlighting(nameView, cursor, cache.nameBuffer,
- cache.highlightedTextBuffer, cache.textWithHighlighting);
- } else {
- nameView.setText(cache.nameBuffer.data, 0, size);
- }
- } else {
- nameView.setText(mUnknownNameText);
- }
-
- boolean hasPhone = cursor.getColumnCount() >= SUMMARY_HAS_PHONE_COLUMN_INDEX
- && cursor.getInt(SUMMARY_HAS_PHONE_COLUMN_INDEX) != 0;
-
- // Make the call button visible if requested.
- if (mDisplayCallButton && hasPhone) {
- int pos = cursor.getPosition();
- view.showCallButton(android.R.id.button1, pos);
- } else {
- view.hideCallButton();
- }
-
- // Set the photo, if requested
- if (mDisplayPhotos) {
- boolean useQuickContact = (mMode & MODE_MASK_DISABLE_QUIKCCONTACT) == 0;
-
- long photoId = 0;
- if (!cursor.isNull(SUMMARY_PHOTO_ID_COLUMN_INDEX)) {
- photoId = cursor.getLong(SUMMARY_PHOTO_ID_COLUMN_INDEX);
- }
-
- ImageView viewToUse;
- if (useQuickContact) {
- // Build soft lookup reference
- final long contactId = cursor.getLong(SUMMARY_ID_COLUMN_INDEX);
- final String lookupKey = cursor.getString(SUMMARY_LOOKUP_KEY_COLUMN_INDEX);
- QuickContactBadge quickContact = view.getQuickContact();
- quickContact.assignContactUri(Contacts.getLookupUri(contactId, lookupKey));
- viewToUse = quickContact;
- } else {
- viewToUse = view.getPhotoView();
- }
-
- final int position = cursor.getPosition();
- mPhotoLoader.loadPhoto(viewToUse, photoId);
- }
-
- if ((mMode & MODE_MASK_NO_PRESENCE) == 0) {
- // Set the proper icon (star or presence or nothing)
- int serverStatus;
- if (!cursor.isNull(SUMMARY_PRESENCE_STATUS_COLUMN_INDEX)) {
- serverStatus = cursor.getInt(SUMMARY_PRESENCE_STATUS_COLUMN_INDEX);
- Drawable icon = ContactPresenceIconUtil.getPresenceIcon(mContext, serverStatus);
- if (icon != null) {
- view.setPresence(icon);
- } else {
- view.setPresence(null);
- }
- } else {
- view.setPresence(null);
- }
- } else {
- view.setPresence(null);
- }
-
- if (mShowSearchSnippets) {
- boolean showSnippet = false;
- String snippetMimeType = cursor.getString(SUMMARY_SNIPPET_MIMETYPE_COLUMN_INDEX);
- if (Email.CONTENT_ITEM_TYPE.equals(snippetMimeType)) {
- String email = cursor.getString(SUMMARY_SNIPPET_DATA1_COLUMN_INDEX);
- if (!TextUtils.isEmpty(email)) {
- view.setSnippet(email);
- showSnippet = true;
- }
- } else if (Organization.CONTENT_ITEM_TYPE.equals(snippetMimeType)) {
- String company = cursor.getString(SUMMARY_SNIPPET_DATA1_COLUMN_INDEX);
- String title = cursor.getString(SUMMARY_SNIPPET_DATA4_COLUMN_INDEX);
- if (!TextUtils.isEmpty(company)) {
- if (!TextUtils.isEmpty(title)) {
- view.setSnippet(company + " / " + title);
- } else {
- view.setSnippet(company);
- }
- showSnippet = true;
- } else if (!TextUtils.isEmpty(title)) {
- view.setSnippet(title);
- showSnippet = true;
- }
- } else if (Nickname.CONTENT_ITEM_TYPE.equals(snippetMimeType)) {
- String nickname = cursor.getString(SUMMARY_SNIPPET_DATA1_COLUMN_INDEX);
- if (!TextUtils.isEmpty(nickname)) {
- view.setSnippet(nickname);
- showSnippet = true;
- }
- }
-
- if (!showSnippet) {
- view.setSnippet(null);
- }
- }
-
- if (!displayAdditionalData) {
- if (phoneticNameColumnIndex != -1) {
-
- // Set the name
- cursor.copyStringToBuffer(phoneticNameColumnIndex, cache.phoneticNameBuffer);
- int phoneticNameSize = cache.phoneticNameBuffer.sizeCopied;
- if (phoneticNameSize != 0) {
- view.setLabel(cache.phoneticNameBuffer.data, phoneticNameSize);
- } else {
- view.setLabel(null);
- }
- } else {
- view.setLabel(null);
- }
- return;
- }
-
- // Set the data.
- cursor.copyStringToBuffer(dataColumnIndex, cache.dataBuffer);
-
- size = cache.dataBuffer.sizeCopied;
- view.setData(cache.dataBuffer.data, size);
-
- // Set the label.
- if (!cursor.isNull(typeColumnIndex)) {
- final int type = cursor.getInt(typeColumnIndex);
- final String label = cursor.getString(labelColumnIndex);
-
- if (mMode == MODE_LEGACY_PICK_POSTAL || mMode == MODE_PICK_POSTAL) {
- // TODO cache
- view.setLabel(StructuredPostal.getTypeLabel(context.getResources(), type,
- label));
- } else {
- // TODO cache
- view.setLabel(Phone.getTypeLabel(context.getResources(), type, label));
- }
- } else {
- view.setLabel(null);
- }
- }
-
- /**
- * Computes the span of the display name that has highlighted parts and configures
- * the display name text view accordingly.
- */
- private void buildDisplayNameWithHighlighting(TextView textView, Cursor cursor,
- CharArrayBuffer buffer1, CharArrayBuffer buffer2,
- TextWithHighlighting textWithHighlighting) {
- int oppositeDisplayOrderColumnIndex;
- if (mDisplayOrder == ContactsContract.Preferences.DISPLAY_ORDER_PRIMARY) {
- oppositeDisplayOrderColumnIndex = SUMMARY_DISPLAY_NAME_ALTERNATIVE_COLUMN_INDEX;
- } else {
- oppositeDisplayOrderColumnIndex = SUMMARY_DISPLAY_NAME_PRIMARY_COLUMN_INDEX;
- }
- cursor.copyStringToBuffer(oppositeDisplayOrderColumnIndex, buffer2);
-
- textWithHighlighting.setText(buffer1, buffer2);
- textView.setText(textWithHighlighting);
- }
-
- private void bindSectionHeader(View itemView, int position, boolean displaySectionHeaders) {
- final ContactListItemView view = (ContactListItemView)itemView;
- final ContactListItemCache cache = (ContactListItemCache) view.getTag();
- if (!displaySectionHeaders) {
- view.setSectionHeader(null);
- view.setDividerVisible(true);
- } else {
- final int section = getSectionForPosition(position);
- if (getPositionForSection(section) == position) {
- String title = (String)mIndexer.getSections()[section];
- view.setSectionHeader(title);
- } else {
- view.setDividerVisible(false);
- view.setSectionHeader(null);
- }
-
- // move the divider for the last item in a section
- if (getPositionForSection(section + 1) - 1 == position) {
- view.setDividerVisible(false);
- } else {
- view.setDividerVisible(true);
- }
- }
- }
-
- @Override
- public void changeCursor(Cursor cursor) {
- if (cursor != null) {
- setLoading(false);
- }
-
- // Get the split between starred and frequent items, if the mode is strequent
- mFrequentSeparatorPos = ListView.INVALID_POSITION;
- int cursorCount = 0;
- if (cursor != null && (cursorCount = cursor.getCount()) > 0
- && mMode == MODE_STREQUENT) {
- cursor.move(-1);
- for (int i = 0; cursor.moveToNext(); i++) {
- int starred = cursor.getInt(SUMMARY_STARRED_COLUMN_INDEX);
- if (starred == 0) {
- if (i > 0) {
- // Only add the separator when there are starred items present
- mFrequentSeparatorPos = i;
- }
- break;
- }
- }
- }
-
- if (cursor != null && mSearchResultsMode) {
- TextView foundContactsText = (TextView)findViewById(R.id.search_results_found);
- String text = getQuantityText(cursor.getCount(), R.string.listFoundAllContactsZero,
- R.plurals.listFoundAllContacts);
- foundContactsText.setText(text);
- }
-
- super.changeCursor(cursor);
- // Update the indexer for the fast scroll widget
- updateIndexer(cursor);
- }
-
- private void updateIndexer(Cursor cursor) {
- if (cursor == null) {
- mIndexer = null;
- return;
- }
-
- Bundle bundle = cursor.getExtras();
- if (bundle.containsKey(ContactCounts.EXTRA_ADDRESS_BOOK_INDEX_TITLES)) {
- String sections[] =
- bundle.getStringArray(ContactCounts.EXTRA_ADDRESS_BOOK_INDEX_TITLES);
- int counts[] = bundle.getIntArray(ContactCounts.EXTRA_ADDRESS_BOOK_INDEX_COUNTS);
- mIndexer = new ContactsSectionIndexer(sections, counts);
- } else {
- mIndexer = null;
- }
- }
-
- /**
- * Run the query on a helper thread. Beware that this code does not run
- * on the main UI thread!
- */
- @Override
- public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
- return doFilter(constraint.toString());
- }
-
- public Object [] getSections() {
- if (mIndexer == null) {
- return new String[] { " " };
- } else {
- return mIndexer.getSections();
- }
- }
-
- public int getPositionForSection(int sectionIndex) {
- if (mIndexer == null) {
- return -1;
- }
-
- return mIndexer.getPositionForSection(sectionIndex);
- }
-
- public int getSectionForPosition(int position) {
- if (mIndexer == null) {
- return -1;
- }
-
- return mIndexer.getSectionForPosition(position);
- }
-
- @Override
- public boolean areAllItemsEnabled() {
- return mMode != MODE_STARRED
- && !mShowNumberOfContacts
- && mSuggestionsCursorCount == 0;
- }
-
- @Override
- public boolean isEnabled(int position) {
- if (mShowNumberOfContacts) {
- if (position == 0) {
- return false;
- }
- position--;
- }
-
- if (mSuggestionsCursorCount > 0) {
- return position != 0 && position != mSuggestionsCursorCount + 1;
- }
- return position != mFrequentSeparatorPos;
- }
-
- @Override
- public int getCount() {
- if (!mDataValid) {
- return 0;
- }
- int superCount = super.getCount();
-
- if (mShowNumberOfContacts && (mSearchMode || superCount > 0)) {
- // We don't want to count this header if it's the only thing visible, so that
- // the empty text will display.
- superCount++;
- }
-
- if (mSearchMode) {
- // Last element in the list is the "Find
- superCount++;
- }
-
- // We do not show the "Create New" button in Search mode
- if ((mMode & MODE_MASK_CREATE_NEW) != 0 && !mSearchMode) {
- // Count the "Create new contact" line
- superCount++;
- }
-
- if (mSuggestionsCursorCount != 0) {
- // When showing suggestions, we have 2 additional list items: the "Suggestions"
- // and "All contacts" headers.
- return mSuggestionsCursorCount + superCount + 2;
- }
- else if (mFrequentSeparatorPos != ListView.INVALID_POSITION) {
- // When showing strequent list, we have an additional list item - the separator.
- return superCount + 1;
- } else {
- return superCount;
- }
- }
-
- /**
- * Gets the actual count of contacts and excludes all the headers.
- */
- public int getRealCount() {
- return super.getCount();
- }
-
- private int getRealPosition(int pos) {
- if (mShowNumberOfContacts) {
- pos--;
- }
-
- if ((mMode & MODE_MASK_CREATE_NEW) != 0 && !mSearchMode) {
- return pos - 1;
- } else if (mSuggestionsCursorCount != 0) {
- // When showing suggestions, we have 2 additional list items: the "Suggestions"
- // and "All contacts" separators.
- if (pos < mSuggestionsCursorCount + 2) {
- // We are in the upper partition (Suggestions). Adjusting for the "Suggestions"
- // separator.
- return pos - 1;
- } else {
- // We are in the lower partition (All contacts). Adjusting for the size
- // of the upper partition plus the two separators.
- return pos - mSuggestionsCursorCount - 2;
- }
- } else if (mFrequentSeparatorPos == ListView.INVALID_POSITION) {
- // No separator, identity map
- return pos;
- } else if (pos <= mFrequentSeparatorPos) {
- // Before or at the separator, identity map
- return pos;
- } else {
- // After the separator, remove 1 from the pos to get the real underlying pos
- return pos - 1;
- }
- }
-
- @Override
- public Object getItem(int pos) {
- if (mSuggestionsCursorCount != 0 && pos <= mSuggestionsCursorCount) {
- mSuggestionsCursor.moveToPosition(getRealPosition(pos));
- return mSuggestionsCursor;
- } else if (isSearchAllContactsItemPosition(pos)){
- return null;
- } else {
- int realPosition = getRealPosition(pos);
- if (realPosition < 0) {
- return null;
- }
- return super.getItem(realPosition);
- }
- }
-
- @Override
- public long getItemId(int pos) {
- if (mSuggestionsCursorCount != 0 && pos < mSuggestionsCursorCount + 2) {
- if (mSuggestionsCursor.moveToPosition(pos - 1)) {
- return mSuggestionsCursor.getLong(mRowIDColumn);
- } else {
- return 0;
- }
- } else if (isSearchAllContactsItemPosition(pos)) {
- return 0;
- }
- int realPosition = getRealPosition(pos);
- if (realPosition < 0) {
- return 0;
- }
- return super.getItemId(realPosition);
- }
-
- public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
- int totalItemCount) {
- if (view instanceof PinnedHeaderListView) {
- ((PinnedHeaderListView)view).configureHeaderView(firstVisibleItem);
- }
- }
-
- public void onScrollStateChanged(AbsListView view, int scrollState) {
- if (mHighlightWhenScrolling) {
- if (scrollState != OnScrollListener.SCROLL_STATE_IDLE) {
- mHighlightingAnimation.startHighlighting();
- } else {
- mHighlightingAnimation.stopHighlighting();
- }
- }
-
- if (scrollState == OnScrollListener.SCROLL_STATE_FLING) {
- mPhotoLoader.pause();
- } else if (mDisplayPhotos) {
- mPhotoLoader.resume();
- }
- }
-
- /**
- * Computes the state of the pinned header. It can be invisible, fully
- * visible or partially pushed up out of the view.
- */
- public int getPinnedHeaderState(int position) {
- if (mIndexer == null || mCursor == null || mCursor.getCount() == 0) {
- return PINNED_HEADER_GONE;
- }
-
- int realPosition = getRealPosition(position);
- if (realPosition < 0) {
- return PINNED_HEADER_GONE;
- }
-
- // The header should get pushed up if the top item shown
- // is the last item in a section for a particular letter.
- int section = getSectionForPosition(realPosition);
- int nextSectionPosition = getPositionForSection(section + 1);
- if (nextSectionPosition != -1 && realPosition == nextSectionPosition - 1) {
- return PINNED_HEADER_PUSHED_UP;
- }
-
- return PINNED_HEADER_VISIBLE;
- }
-
- /**
- * Configures the pinned header by setting the appropriate text label
- * and also adjusting color if necessary. The color needs to be
- * adjusted when the pinned header is being pushed up from the view.
- */
- public void configurePinnedHeader(View header, int position, int alpha) {
- PinnedHeaderCache cache = (PinnedHeaderCache)header.getTag();
- if (cache == null) {
- cache = new PinnedHeaderCache();
- cache.titleView = (TextView)header.findViewById(R.id.header_text);
- cache.textColor = cache.titleView.getTextColors();
- cache.background = header.getBackground();
- header.setTag(cache);
- }
-
- int realPosition = getRealPosition(position);
- int section = getSectionForPosition(realPosition);
-
- String title = (String)mIndexer.getSections()[section];
- cache.titleView.setText(title);
-
- if (alpha == 255) {
- // Opaque: use the default background, and the original text color
- header.setBackgroundDrawable(cache.background);
- cache.titleView.setTextColor(cache.textColor);
- } else {
- // Faded: use a solid color approximation of the background, and
- // a translucent text color
- header.setBackgroundColor(Color.rgb(
- Color.red(mPinnedHeaderBackgroundColor) * alpha / 255,
- Color.green(mPinnedHeaderBackgroundColor) * alpha / 255,
- Color.blue(mPinnedHeaderBackgroundColor) * alpha / 255));
-
- int textColor = cache.textColor.getDefaultColor();
- cache.titleView.setTextColor(Color.argb(alpha,
- Color.red(textColor), Color.green(textColor), Color.blue(textColor)));
- }
- }
- }
-}
diff --git a/src/com/android/contacts/ContactsSearchManager.java b/src/com/android/contacts/ContactsSearchManager.java
index d65e079..edb2016 100644
--- a/src/com/android/contacts/ContactsSearchManager.java
+++ b/src/com/android/contacts/ContactsSearchManager.java
@@ -16,6 +16,8 @@
package com.android.contacts;
+import com.android.contacts.list.ContactsRequest;
+
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
@@ -31,27 +33,28 @@
* An extra that provides context for search UI and defines the scope for
* the search queries.
*/
- public static final String ORIGINAL_ACTION_EXTRA_KEY = "originalAction";
-
- /**
- * An extra that provides context for search UI and defines the scope for
- * the search queries.
- */
- public static final String ORIGINAL_COMPONENT_EXTRA_KEY = "originalComponent";
+ public static final String ORIGINAL_REQUEST_KEY = "originalRequest";
/**
* Starts the contact list activity in the search mode.
*/
public static void startSearch(Activity context, String initialQuery) {
- context.startActivity(buildIntent(context, initialQuery));
+ context.startActivity(buildIntent(context, initialQuery, null));
}
public static void startSearchForResult(Activity context, String initialQuery,
- int requestCode) {
- context.startActivityForResult(buildIntent(context, initialQuery), requestCode);
+ int requestCode, ContactsRequest originalRequest) {
+ context.startActivityForResult(
+ buildIntent(context, initialQuery, originalRequest), requestCode);
}
- private static Intent buildIntent(Activity context, String initialQuery) {
+ public static void startSearch(Activity context, String initialQuery,
+ ContactsRequest originalRequest) {
+ context.startActivity(buildIntent(context, initialQuery, originalRequest));
+ }
+
+ private static Intent buildIntent(
+ Activity context, String initialQuery, ContactsRequest originalRequest) {
Intent intent = new Intent();
intent.setData(ContactsContract.Contacts.CONTENT_URI);
intent.setAction(UI.FILTER_CONTACTS_ACTION);
@@ -62,8 +65,9 @@
intent.putExtras(originalExtras);
}
intent.putExtra(UI.FILTER_TEXT_EXTRA_KEY, initialQuery);
- intent.putExtra(ORIGINAL_ACTION_EXTRA_KEY, originalIntent.getAction());
- intent.putExtra(ORIGINAL_COMPONENT_EXTRA_KEY, originalIntent.getComponent().getClassName());
+ if (originalRequest != null) {
+ intent.putExtra(ORIGINAL_REQUEST_KEY, originalRequest);
+ }
return intent;
}
}
diff --git a/src/com/android/contacts/ContactsUtils.java b/src/com/android/contacts/ContactsUtils.java
index 94dabba..2bc2721 100644
--- a/src/com/android/contacts/ContactsUtils.java
+++ b/src/com/android/contacts/ContactsUtils.java
@@ -16,7 +16,6 @@
package com.android.contacts;
-
import com.android.contacts.model.ContactsSource;
import com.android.contacts.util.Constants;
@@ -29,6 +28,8 @@
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.Drawable;
+import android.location.Country;
+import android.location.CountryDetector;
import android.net.Uri;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Data;
@@ -236,33 +237,104 @@
return null;
}
+ public static final class ImActions {
+ private final Intent mPrimaryIntent;
+ private final Intent mSecondaryIntent;
+ private final int mPrimaryActionIcon;
+ private final int mSecondaryActionIcon;
+
+ private ImActions(Intent primaryIntent, Intent secondaryIntent, int primaryActionIcon,
+ int secondaryActionIcon) {
+ mPrimaryIntent = primaryIntent;
+ mSecondaryIntent = secondaryIntent;
+ mPrimaryActionIcon = primaryActionIcon;
+ mSecondaryActionIcon = secondaryActionIcon;
+ }
+
+ public Intent getPrimaryIntent() {
+ return mPrimaryIntent;
+ }
+
+ public Intent getSecondaryIntent() {
+ return mSecondaryIntent;
+ }
+
+ public int getPrimaryActionIcon() {
+ return mPrimaryActionIcon;
+ }
+
+ public int getSecondaryActionIcon() {
+ return mSecondaryActionIcon;
+ }
+ }
+
/**
* Build {@link Intent} to launch an action for the given {@link Im} or
- * {@link Email} row. Returns null when missing protocol or data.
+ * {@link Email} row. If the result is non-null, it either contains one or two Intents
+ * (e.g. [Text, Videochat] or just [Text])
+ * Returns null when missing protocol or data.
*/
- public static Intent buildImIntent(ContentValues values) {
+ public static ImActions buildImActions(ContentValues values) {
final boolean isEmail = Email.CONTENT_ITEM_TYPE.equals(values.getAsString(Data.MIMETYPE));
if (!isEmail && !isProtocolValid(values)) {
return null;
}
+ final String data = values.getAsString(isEmail ? Email.DATA : Im.DATA);
+ if (TextUtils.isEmpty(data)) return null;
+
final int protocol = isEmail ? Im.PROTOCOL_GOOGLE_TALK : values.getAsInteger(Im.PROTOCOL);
- String host = values.getAsString(Im.CUSTOM_PROTOCOL);
- String data = values.getAsString(isEmail ? Email.DATA : Im.DATA);
- if (protocol != Im.PROTOCOL_CUSTOM) {
- // Try bringing in a well-known host for specific protocols
- host = ContactsUtils.lookupProviderNameFromId(protocol);
- }
-
- if (!TextUtils.isEmpty(host) && !TextUtils.isEmpty(data)) {
- final String authority = host.toLowerCase();
- final Uri imUri = new Uri.Builder().scheme(Constants.SCHEME_IMTO).authority(
- authority).appendPath(data).build();
- return new Intent(Intent.ACTION_SENDTO, imUri);
+ if (protocol == Im.PROTOCOL_GOOGLE_TALK) {
+ final Integer chatCapabilityObj = values.getAsInteger(Im.CHAT_CAPABILITY);
+ final int chatCapability = chatCapabilityObj == null ? 0 : chatCapabilityObj;
+ if ((chatCapability & Im.CAPABILITY_HAS_CAMERA) != 0) {
+ // Allow Video chat and Texting
+ return new ImActions(
+ new Intent(Intent.ACTION_SENDTO, Uri.parse("xmpp:" + data + "?message")),
+ new Intent(Intent.ACTION_SENDTO, Uri.parse("xmpp:" + data + "?call")),
+ android.R.drawable.sym_action_chat,
+ R.drawable.sym_action_videochat
+ );
+ } else if ((chatCapability & Im.CAPABILITY_HAS_VOICE) != 0) {
+ // Allow Talking and Texting
+ return new ImActions(
+ new Intent(Intent.ACTION_SENDTO, Uri.parse("xmpp:" + data + "?message")),
+ new Intent(Intent.ACTION_SENDTO, Uri.parse("xmpp:" + data + "?call")),
+ android.R.drawable.sym_action_chat,
+ R.drawable.sym_action_audiochat
+ );
+ } else {
+ return new ImActions(
+ new Intent(Intent.ACTION_SENDTO, Uri.parse("xmpp:" + data + "?message")),
+ null,
+ android.R.drawable.sym_action_chat,
+ -1
+ );
+ }
} else {
- return null;
+ // Build an IM Intent
+ String host = values.getAsString(Im.CUSTOM_PROTOCOL);
+
+ if (protocol != Im.PROTOCOL_CUSTOM) {
+ // Try bringing in a well-known host for specific protocols
+ host = ContactsUtils.lookupProviderNameFromId(protocol);
+ }
+
+ if (!TextUtils.isEmpty(host)) {
+ final String authority = host.toLowerCase();
+ final Uri imUri = new Uri.Builder().scheme(Constants.SCHEME_IMTO).authority(
+ authority).appendPath(data).build();
+ return new ImActions(
+ new Intent(Intent.ACTION_SENDTO, imUri),
+ null,
+ android.R.drawable.sym_action_chat,
+ -1
+ );
+ } else {
+ return null;
+ }
}
}
@@ -490,4 +562,15 @@
}
return TextUtils.equals(a.getAction(), b.getAction());
}
+
+ /**
+ * @return The ISO 3166-1 two letters country code of the country the user
+ * is in.
+ */
+ public static final String getCurrentCountryIso(Context context) {
+ CountryDetector detector =
+ (CountryDetector) context.getSystemService(Context.COUNTRY_DETECTOR);
+ Country country = detector.detectCountry();
+ return country.getCountryIso();
+ }
}
diff --git a/src/com/android/contacts/DialtactsActivity.java b/src/com/android/contacts/DialtactsActivity.java
index afb8606..4fdd775 100644
--- a/src/com/android/contacts/DialtactsActivity.java
+++ b/src/com/android/contacts/DialtactsActivity.java
@@ -16,6 +16,8 @@
package com.android.contacts;
+import com.android.contacts.activities.ContactsFrontDoor;
+import com.android.contacts.activities.ContactBrowserActivity;
import com.android.internal.telephony.ITelephony;
import android.app.Activity;
@@ -101,7 +103,7 @@
setCurrentTab(intent);
- if (intent.getAction().equals(UI.FILTER_CONTACTS_ACTION)
+ if (UI.FILTER_CONTACTS_ACTION.equals(intent.getAction())
&& icicle == null) {
setupFilterText(intent);
}
@@ -156,7 +158,7 @@
private void setupContactsTab() {
Intent intent = new Intent(UI.LIST_DEFAULT);
- intent.setClass(this, ContactsListActivity.class);
+ intent.setClass(this, ContactBrowserActivity.class);
mTabHost.addTab(mTabHost.newTabSpec("contacts")
.setIndicator(getText(R.string.contactsIconLabel),
@@ -166,7 +168,7 @@
private void setupFavoritesTab() {
Intent intent = new Intent(UI.LIST_STREQUENT_ACTION);
- intent.setClass(this, ContactsListActivity.class);
+ intent.setClass(this, ContactBrowserActivity.class);
mTabHost.addTab(mTabHost.newTabSpec("favorites")
.setIndicator(getString(R.string.contactsFavoritesLabel),
@@ -228,25 +230,24 @@
final int savedTabIndex = mLastManuallySelectedTab;
// Choose the tab based on the inbound intent
- String componentName = intent.getComponent().getClassName();
- if (getClass().getName().equals(componentName)) {
- if (recentCallsRequest) {
- mTabHost.setCurrentTab(TAB_INDEX_CALL_LOG);
- } else {
- mTabHost.setCurrentTab(TAB_INDEX_DIALER);
- }
- } else if (FAVORITES_ENTRY_COMPONENT.equals(componentName)) {
- mTabHost.setCurrentTab(TAB_INDEX_FAVORITES);
- } else if (CONTACTS_LAUNCH_ACTIVITY.equals(componentName)) {
- mTabHost.setCurrentTab(mLastManuallySelectedTab);
+ if (intent.getBooleanExtra(ContactsFrontDoor.EXTRA_FRONT_DOOR, false)) {
+ // Launched through the contacts front door, set the proper contacts tab
+ setContactsTab();
} else {
- SharedPreferences prefs = getSharedPreferences(PREFS_DIALTACTS, MODE_PRIVATE);
- boolean favoritesAsContacts = prefs.getBoolean(PREF_FAVORITES_AS_CONTACTS,
- PREF_FAVORITES_AS_CONTACTS_DEFAULT);
- if (favoritesAsContacts) {
+ // Not launched through the front door, look at the component to determine the tab
+ String componentName = intent.getComponent().getClassName();
+ if (getClass().getName().equals(componentName)) {
+ if (recentCallsRequest) {
+ mTabHost.setCurrentTab(TAB_INDEX_CALL_LOG);
+ } else {
+ mTabHost.setCurrentTab(TAB_INDEX_DIALER);
+ }
+ } else if (FAVORITES_ENTRY_COMPONENT.equals(componentName)) {
mTabHost.setCurrentTab(TAB_INDEX_FAVORITES);
+ } else if (CONTACTS_LAUNCH_ACTIVITY.equals(componentName)) {
+ mTabHost.setCurrentTab(mLastManuallySelectedTab);
} else {
- mTabHost.setCurrentTab(TAB_INDEX_CONTACTS);
+ setContactsTab();
}
}
@@ -258,13 +259,24 @@
intent.putExtra(EXTRA_IGNORE_STATE, false);
}
+ private void setContactsTab() {
+ SharedPreferences prefs = getSharedPreferences(PREFS_DIALTACTS, MODE_PRIVATE);
+ boolean favoritesAsContacts = prefs.getBoolean(PREF_FAVORITES_AS_CONTACTS,
+ PREF_FAVORITES_AS_CONTACTS_DEFAULT);
+ if (favoritesAsContacts) {
+ mTabHost.setCurrentTab(TAB_INDEX_FAVORITES);
+ } else {
+ mTabHost.setCurrentTab(TAB_INDEX_CONTACTS);
+ }
+ }
+
@Override
public void onNewIntent(Intent newIntent) {
setIntent(newIntent);
fixIntent(newIntent);
setCurrentTab(newIntent);
final String action = newIntent.getAction();
- if (action.equals(UI.FILTER_CONTACTS_ACTION)) {
+ if (UI.FILTER_CONTACTS_ACTION.equals(action)) {
setupFilterText(newIntent);
} else if (isDialIntent(newIntent)) {
setupDialUri(newIntent);
diff --git a/src/com/android/contacts/ImportVCardActivity.java b/src/com/android/contacts/ImportVCardActivity.java
deleted file mode 100644
index 0a324fe..0000000
--- a/src/com/android/contacts/ImportVCardActivity.java
+++ /dev/null
@@ -1,984 +0,0 @@
-/*
- * Copyright (C) 2009 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;
-
-import android.accounts.Account;
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.app.ProgressDialog;
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.DialogInterface.OnCancelListener;
-import android.content.DialogInterface.OnClickListener;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Environment;
-import android.os.Handler;
-import android.os.PowerManager;
-import android.pim.vcard.VCardConfig;
-import android.pim.vcard.VCardEntryCommitter;
-import android.pim.vcard.VCardEntryConstructor;
-import android.pim.vcard.VCardEntryCounter;
-import android.pim.vcard.VCardInterpreter;
-import android.pim.vcard.VCardInterpreterCollection;
-import android.pim.vcard.VCardParser_V21;
-import android.pim.vcard.VCardParser_V30;
-import android.pim.vcard.VCardSourceDetector;
-import android.pim.vcard.exception.VCardException;
-import android.pim.vcard.exception.VCardNestedException;
-import android.pim.vcard.exception.VCardNotSupportedException;
-import android.pim.vcard.exception.VCardVersionException;
-import android.provider.ContactsContract.RawContacts;
-import android.text.SpannableStringBuilder;
-import android.text.Spanned;
-import android.text.TextUtils;
-import android.text.style.RelativeSizeSpan;
-import android.util.Log;
-
-import com.android.contacts.model.Sources;
-import com.android.contacts.util.AccountSelectionUtil;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Date;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Locale;
-import java.util.Set;
-import java.util.Vector;
-
-class VCardFile {
- private String mName;
- private String mCanonicalPath;
- private long mLastModified;
-
- public VCardFile(String name, String canonicalPath, long lastModified) {
- mName = name;
- mCanonicalPath = canonicalPath;
- mLastModified = lastModified;
- }
-
- public String getName() {
- return mName;
- }
-
- public String getCanonicalPath() {
- return mCanonicalPath;
- }
-
- public long getLastModified() {
- return mLastModified;
- }
-}
-
-/**
- * Class for importing vCard. Several user interaction will be required while reading
- * (selecting a file, waiting a moment, etc.)
- *
- * Note that this Activity assumes that the instance is a "one-shot Activity", which will be
- * finished (with the method {@link Activity#finish()}) after the import and never reuse
- * any Dialog in the instance. So this code is careless about the management around managed
- * dialogs stuffs (like how onCreateDialog() is used).
- */
-public class ImportVCardActivity extends Activity {
- private static final String LOG_TAG = "ImportVCardActivity";
- private static final boolean DO_PERFORMANCE_PROFILE = false;
-
- // Run on the UI thread. Must not be null except after onDestroy().
- private Handler mHandler = new Handler();
-
- private AccountSelectionUtil.AccountSelectedListener mAccountSelectionListener;
- private Account mAccount;
-
- private ProgressDialog mProgressDialogForScanVCard;
-
- private List<VCardFile> mAllVCardFileList;
- private VCardScanThread mVCardScanThread;
- private VCardReadThread mVCardReadThread;
- private ProgressDialog mProgressDialogForReadVCard;
-
- private String mErrorMessage;
-
- private boolean mNeedReview = false;
-
- // Runs on the UI thread.
- private class DialogDisplayer implements Runnable {
- private final int mResId;
- public DialogDisplayer(int resId) {
- mResId = resId;
- }
- public DialogDisplayer(String errorMessage) {
- mResId = R.id.dialog_error_with_message;
- mErrorMessage = errorMessage;
- }
- public void run() {
- showDialog(mResId);
- }
- }
-
- private class CancelListener
- implements DialogInterface.OnClickListener, DialogInterface.OnCancelListener {
- public void onClick(DialogInterface dialog, int which) {
- finish();
- }
-
- public void onCancel(DialogInterface dialog) {
- finish();
- }
- }
-
- private CancelListener mCancelListener = new CancelListener();
-
- private class VCardReadThread extends Thread
- implements DialogInterface.OnCancelListener {
- private ContentResolver mResolver;
- private VCardParser_V21 mVCardParser;
- private boolean mCanceled;
- private PowerManager.WakeLock mWakeLock;
- private Uri mUri;
- private File mTempFile;
-
- private List<VCardFile> mSelectedVCardFileList;
- private List<String> mErrorFileNameList;
-
- public VCardReadThread(Uri uri) {
- mUri = uri;
- init();
- }
-
- public VCardReadThread(final List<VCardFile> selectedVCardFileList) {
- mSelectedVCardFileList = selectedVCardFileList;
- mErrorFileNameList = new ArrayList<String>();
- init();
- }
-
- private void init() {
- Context context = ImportVCardActivity.this;
- mResolver = context.getContentResolver();
- PowerManager powerManager = (PowerManager)context.getSystemService(
- Context.POWER_SERVICE);
- mWakeLock = powerManager.newWakeLock(
- PowerManager.SCREEN_DIM_WAKE_LOCK |
- PowerManager.ON_AFTER_RELEASE, LOG_TAG);
- }
-
- @Override
- public void finalize() {
- if (mWakeLock != null && mWakeLock.isHeld()) {
- mWakeLock.release();
- }
- }
-
- @Override
- public void run() {
- boolean shouldCallFinish = true;
- mWakeLock.acquire();
- Uri createdUri = null;
- mTempFile = null;
- // Some malicious vCard data may make this thread broken
- // (e.g. OutOfMemoryError).
- // Even in such cases, some should be done.
- try {
- if (mUri != null) { // Read one vCard expressed by mUri
- final Uri targetUri = mUri;
- mProgressDialogForReadVCard.setProgressNumberFormat("");
- mProgressDialogForReadVCard.setProgress(0);
-
- // Count the number of VCard entries
- mProgressDialogForReadVCard.setIndeterminate(true);
- long start;
- if (DO_PERFORMANCE_PROFILE) {
- start = System.currentTimeMillis();
- }
- VCardEntryCounter counter = new VCardEntryCounter();
- VCardSourceDetector detector = new VCardSourceDetector();
- VCardInterpreterCollection builderCollection = new VCardInterpreterCollection(
- Arrays.asList(counter, detector));
- boolean result;
- try {
- result = readOneVCardFile(targetUri,
- VCardConfig.DEFAULT_CHARSET, builderCollection, null, true, null);
- } catch (VCardNestedException e) {
- try {
- // Assume that VCardSourceDetector was able to detect the source.
- // Try again with the detector.
- result = readOneVCardFile(targetUri,
- VCardConfig.DEFAULT_CHARSET, counter, detector, false, null);
- } catch (VCardNestedException e2) {
- result = false;
- Log.e(LOG_TAG, "Must not reach here. " + e2);
- }
- }
- if (DO_PERFORMANCE_PROFILE) {
- long time = System.currentTimeMillis() - start;
- Log.d(LOG_TAG, "time for counting the number of vCard entries: " +
- time + " ms");
- }
- if (!result) {
- shouldCallFinish = false;
- return;
- }
-
- mProgressDialogForReadVCard.setProgressNumberFormat(
- getString(R.string.reading_vcard_contacts));
- mProgressDialogForReadVCard.setIndeterminate(false);
- mProgressDialogForReadVCard.setMax(counter.getCount());
- String charset = detector.getEstimatedCharset();
- createdUri = doActuallyReadOneVCard(targetUri, null, charset, true, detector,
- mErrorFileNameList);
- } else { // Read multiple files.
- mProgressDialogForReadVCard.setProgressNumberFormat(
- getString(R.string.reading_vcard_files));
- mProgressDialogForReadVCard.setMax(mSelectedVCardFileList.size());
- mProgressDialogForReadVCard.setProgress(0);
-
- for (VCardFile vcardFile : mSelectedVCardFileList) {
- if (mCanceled) {
- return;
- }
- // TODO: detect scheme!
- final Uri targetUri =
- Uri.parse("file://" + vcardFile.getCanonicalPath());
-
- VCardSourceDetector detector = new VCardSourceDetector();
- try {
- if (!readOneVCardFile(targetUri, VCardConfig.DEFAULT_CHARSET,
- detector, null, true, mErrorFileNameList)) {
- continue;
- }
- } catch (VCardNestedException e) {
- // Assume that VCardSourceDetector was able to detect the source.
- }
- String charset = detector.getEstimatedCharset();
- doActuallyReadOneVCard(targetUri, mAccount,
- charset, false, detector, mErrorFileNameList);
- mProgressDialogForReadVCard.incrementProgressBy(1);
- }
- }
- } finally {
- mWakeLock.release();
- mProgressDialogForReadVCard.dismiss();
- if (mTempFile != null) {
- if (!mTempFile.delete()) {
- Log.w(LOG_TAG, "Failed to delete a cache file.");
- }
- mTempFile = null;
- }
- // finish() is called via mCancelListener, which is used in DialogDisplayer.
- if (shouldCallFinish && !isFinishing()) {
- if (mErrorFileNameList == null || mErrorFileNameList.isEmpty()) {
- finish();
- if (mNeedReview) {
- mNeedReview = false;
- Log.v("importVCardActivity", "Prepare to review the imported contact");
-
- if (createdUri != null) {
- // get contact_id of this raw_contact
- final long rawContactId = ContentUris.parseId(createdUri);
- Uri contactUri = RawContacts.getContactLookupUri(
- getContentResolver(), ContentUris.withAppendedId(
- RawContacts.CONTENT_URI, rawContactId));
-
- Intent viewIntent = new Intent(Intent.ACTION_VIEW, contactUri);
- startActivity(viewIntent);
- }
- }
- } else {
- StringBuilder builder = new StringBuilder();
- boolean first = true;
- for (String fileName : mErrorFileNameList) {
- if (first) {
- first = false;
- } else {
- builder.append(", ");
- }
- builder.append(fileName);
- }
-
- runOnUIThread(new DialogDisplayer(
- getString(R.string.fail_reason_failed_to_read_files,
- builder.toString())));
- }
- }
- }
- }
-
- private Uri doActuallyReadOneVCard(Uri uri, Account account,
- String charset, boolean showEntryParseProgress,
- VCardSourceDetector detector, List<String> errorFileNameList) {
- final Context context = ImportVCardActivity.this;
- VCardEntryConstructor builder;
- final String currentLanguage = Locale.getDefault().getLanguage();
- int vcardType = VCardConfig.getVCardTypeFromString(
- context.getString(R.string.config_import_vcard_type));
- if (charset != null) {
- builder = new VCardEntryConstructor(charset, charset, false, vcardType, mAccount);
- } else {
- charset = VCardConfig.DEFAULT_CHARSET;
- builder = new VCardEntryConstructor(null, null, false, vcardType, mAccount);
- }
- VCardEntryCommitter committer = new VCardEntryCommitter(mResolver);
- builder.addEntryHandler(committer);
- if (showEntryParseProgress) {
- builder.addEntryHandler(new ProgressShower(mProgressDialogForReadVCard,
- context.getString(R.string.reading_vcard_message),
- ImportVCardActivity.this,
- mHandler));
- }
-
- try {
- if (!readOneVCardFile(uri, charset, builder, detector, false, null)) {
- return null;
- }
- } catch (VCardNestedException e) {
- Log.e(LOG_TAG, "Never reach here.");
- }
- final ArrayList<Uri> createdUris = committer.getCreatedUris();
- return (createdUris == null || createdUris.size() != 1) ? null : createdUris.get(0);
- }
-
- private boolean readOneVCardFile(Uri uri, String charset,
- VCardInterpreter builder, VCardSourceDetector detector,
- boolean throwNestedException, List<String> errorFileNameList)
- throws VCardNestedException {
- InputStream is;
- try {
- is = mResolver.openInputStream(uri);
- mVCardParser = new VCardParser_V21(detector);
-
- try {
- mVCardParser.parse(is, charset, builder, mCanceled);
- } catch (VCardVersionException e1) {
- try {
- is.close();
- } catch (IOException e) {
- }
- if (builder instanceof VCardEntryConstructor) {
- // Let the object clean up internal temporal objects,
- ((VCardEntryConstructor)builder).clear();
- }
- is = mResolver.openInputStream(uri);
-
- try {
- mVCardParser = new VCardParser_V30();
- mVCardParser.parse(is, charset, builder, mCanceled);
- } catch (VCardVersionException e2) {
- throw new VCardException("vCard with unspported version.");
- }
- } finally {
- if (is != null) {
- try {
- is.close();
- } catch (IOException e) {
- }
- }
- }
- } catch (IOException e) {
- Log.e(LOG_TAG, "IOException was emitted: " + e.getMessage());
-
- mProgressDialogForReadVCard.dismiss();
-
- if (errorFileNameList != null) {
- errorFileNameList.add(uri.toString());
- } else {
- runOnUIThread(new DialogDisplayer(
- getString(R.string.fail_reason_io_error) +
- ": " + e.getLocalizedMessage()));
- }
- return false;
- } catch (VCardNotSupportedException e) {
- if ((e instanceof VCardNestedException) && throwNestedException) {
- throw (VCardNestedException)e;
- }
- if (errorFileNameList != null) {
- errorFileNameList.add(uri.toString());
- } else {
- runOnUIThread(new DialogDisplayer(
- getString(R.string.fail_reason_vcard_not_supported_error) +
- " (" + e.getMessage() + ")"));
- }
- return false;
- } catch (VCardException e) {
- if (errorFileNameList != null) {
- errorFileNameList.add(uri.toString());
- } else {
- runOnUIThread(new DialogDisplayer(
- getString(R.string.fail_reason_vcard_parse_error) +
- " (" + e.getMessage() + ")"));
- }
- return false;
- }
- return true;
- }
-
- public void cancel() {
- mCanceled = true;
- if (mVCardParser != null) {
- mVCardParser.cancel();
- }
- }
-
- public void onCancel(DialogInterface dialog) {
- cancel();
- }
- }
-
- private class ImportTypeSelectedListener implements
- DialogInterface.OnClickListener {
- public static final int IMPORT_ONE = 0;
- public static final int IMPORT_MULTIPLE = 1;
- public static final int IMPORT_ALL = 2;
- public static final int IMPORT_TYPE_SIZE = 3;
-
- private int mCurrentIndex;
-
- public void onClick(DialogInterface dialog, int which) {
- if (which == DialogInterface.BUTTON_POSITIVE) {
- switch (mCurrentIndex) {
- case IMPORT_ALL:
- importMultipleVCardFromSDCard(mAllVCardFileList);
- break;
- case IMPORT_MULTIPLE:
- showDialog(R.id.dialog_select_multiple_vcard);
- break;
- default:
- showDialog(R.id.dialog_select_one_vcard);
- break;
- }
- } else if (which == DialogInterface.BUTTON_NEGATIVE) {
- finish();
- } else {
- mCurrentIndex = which;
- }
- }
- }
-
- private class VCardSelectedListener implements
- DialogInterface.OnClickListener, DialogInterface.OnMultiChoiceClickListener {
- private int mCurrentIndex;
- private Set<Integer> mSelectedIndexSet;
-
- public VCardSelectedListener(boolean multipleSelect) {
- mCurrentIndex = 0;
- if (multipleSelect) {
- mSelectedIndexSet = new HashSet<Integer>();
- }
- }
-
- public void onClick(DialogInterface dialog, int which) {
- if (which == DialogInterface.BUTTON_POSITIVE) {
- if (mSelectedIndexSet != null) {
- List<VCardFile> selectedVCardFileList = new ArrayList<VCardFile>();
- int size = mAllVCardFileList.size();
- // We'd like to sort the files by its index, so we do not use Set iterator.
- for (int i = 0; i < size; i++) {
- if (mSelectedIndexSet.contains(i)) {
- selectedVCardFileList.add(mAllVCardFileList.get(i));
- }
- }
- importMultipleVCardFromSDCard(selectedVCardFileList);
- } else {
- String canonicalPath = mAllVCardFileList.get(mCurrentIndex).getCanonicalPath();
- final Uri uri = Uri.parse("file://" + canonicalPath);
- importOneVCardFromSDCard(uri);
- }
- } else if (which == DialogInterface.BUTTON_NEGATIVE) {
- finish();
- } else {
- // Some file is selected.
- mCurrentIndex = which;
- if (mSelectedIndexSet != null) {
- if (mSelectedIndexSet.contains(which)) {
- mSelectedIndexSet.remove(which);
- } else {
- mSelectedIndexSet.add(which);
- }
- }
- }
- }
-
- public void onClick(DialogInterface dialog, int which, boolean isChecked) {
- if (mSelectedIndexSet == null || (mSelectedIndexSet.contains(which) == isChecked)) {
- Log.e(LOG_TAG, String.format("Inconsist state in index %d (%s)", which,
- mAllVCardFileList.get(which).getCanonicalPath()));
- } else {
- onClick(dialog, which);
- }
- }
- }
-
- /**
- * Thread scanning VCard from SDCard. After scanning, the dialog which lets a user select
- * a vCard file is shown. After the choice, VCardReadThread starts running.
- */
- private class VCardScanThread extends Thread implements OnCancelListener, OnClickListener {
- private boolean mCanceled;
- private boolean mGotIOException;
- private File mRootDirectory;
-
- // To avoid recursive link.
- private Set<String> mCheckedPaths;
- private PowerManager.WakeLock mWakeLock;
-
- private class CanceledException extends Exception {
- }
-
- public VCardScanThread(File sdcardDirectory) {
- mCanceled = false;
- mGotIOException = false;
- mRootDirectory = sdcardDirectory;
- mCheckedPaths = new HashSet<String>();
- PowerManager powerManager = (PowerManager)ImportVCardActivity.this.getSystemService(
- Context.POWER_SERVICE);
- mWakeLock = powerManager.newWakeLock(
- PowerManager.SCREEN_DIM_WAKE_LOCK |
- PowerManager.ON_AFTER_RELEASE, LOG_TAG);
- }
-
- @Override
- public void run() {
- mAllVCardFileList = new Vector<VCardFile>();
- try {
- mWakeLock.acquire();
- getVCardFileRecursively(mRootDirectory);
- } catch (CanceledException e) {
- mCanceled = true;
- } catch (IOException e) {
- mGotIOException = true;
- } finally {
- mWakeLock.release();
- }
-
- if (mCanceled) {
- mAllVCardFileList = null;
- }
-
- mProgressDialogForScanVCard.dismiss();
- mProgressDialogForScanVCard = null;
-
- if (mGotIOException) {
- runOnUIThread(new DialogDisplayer(R.id.dialog_io_exception));
- } else if (mCanceled) {
- finish();
- } else {
- int size = mAllVCardFileList.size();
- final Context context = ImportVCardActivity.this;
- if (size == 0) {
- runOnUIThread(new DialogDisplayer(R.id.dialog_vcard_not_found));
- } else {
- startVCardSelectAndImport();
- }
- }
- }
-
- private void getVCardFileRecursively(File directory)
- throws CanceledException, IOException {
- if (mCanceled) {
- throw new CanceledException();
- }
-
- // e.g. secured directory may return null toward listFiles().
- final File[] files = directory.listFiles();
- if (files == null) {
- Log.w(LOG_TAG, "listFiles() returned null (directory: " + directory + ")");
- return;
- }
- for (File file : directory.listFiles()) {
- if (mCanceled) {
- throw new CanceledException();
- }
- String canonicalPath = file.getCanonicalPath();
- if (mCheckedPaths.contains(canonicalPath)) {
- continue;
- }
-
- mCheckedPaths.add(canonicalPath);
-
- if (file.isDirectory()) {
- getVCardFileRecursively(file);
- } else if (canonicalPath.toLowerCase().endsWith(".vcf") &&
- file.canRead()){
- String fileName = file.getName();
- VCardFile vcardFile = new VCardFile(
- fileName, canonicalPath, file.lastModified());
- mAllVCardFileList.add(vcardFile);
- }
- }
- }
-
- public void onCancel(DialogInterface dialog) {
- mCanceled = true;
- }
-
- public void onClick(DialogInterface dialog, int which) {
- if (which == DialogInterface.BUTTON_NEGATIVE) {
- mCanceled = true;
- }
- }
- }
-
- private void startVCardSelectAndImport() {
- int size = mAllVCardFileList.size();
- if (getResources().getBoolean(R.bool.config_import_all_vcard_from_sdcard_automatically)) {
- importMultipleVCardFromSDCard(mAllVCardFileList);
- } else if (size == 1) {
- String canonicalPath = mAllVCardFileList.get(0).getCanonicalPath();
- Uri uri = Uri.parse("file://" + canonicalPath);
- importOneVCardFromSDCard(uri);
- } else if (getResources().getBoolean(R.bool.config_allow_users_select_all_vcard_import)) {
- runOnUIThread(new DialogDisplayer(R.id.dialog_select_import_type));
- } else {
- runOnUIThread(new DialogDisplayer(R.id.dialog_select_one_vcard));
- }
- }
-
- private void importMultipleVCardFromSDCard(final List<VCardFile> selectedVCardFileList) {
- runOnUIThread(new Runnable() {
- public void run() {
- mVCardReadThread = new VCardReadThread(selectedVCardFileList);
- showDialog(R.id.dialog_reading_vcard);
- }
- });
- }
-
- private void importOneVCardFromSDCard(final Uri uri) {
- runOnUIThread(new Runnable() {
- public void run() {
- mVCardReadThread = new VCardReadThread(uri);
- showDialog(R.id.dialog_reading_vcard);
- }
- });
- }
-
- private Dialog getSelectImportTypeDialog() {
- DialogInterface.OnClickListener listener =
- new ImportTypeSelectedListener();
- AlertDialog.Builder builder = new AlertDialog.Builder(this)
- .setTitle(R.string.select_vcard_title)
- .setPositiveButton(android.R.string.ok, listener)
- .setOnCancelListener(mCancelListener)
- .setNegativeButton(android.R.string.cancel, mCancelListener);
-
- String[] items = new String[ImportTypeSelectedListener.IMPORT_TYPE_SIZE];
- items[ImportTypeSelectedListener.IMPORT_ONE] =
- getString(R.string.import_one_vcard_string);
- items[ImportTypeSelectedListener.IMPORT_MULTIPLE] =
- getString(R.string.import_multiple_vcard_string);
- items[ImportTypeSelectedListener.IMPORT_ALL] =
- getString(R.string.import_all_vcard_string);
- builder.setSingleChoiceItems(items, ImportTypeSelectedListener.IMPORT_ONE, listener);
- return builder.create();
- }
-
- private Dialog getVCardFileSelectDialog(boolean multipleSelect) {
- int size = mAllVCardFileList.size();
- VCardSelectedListener listener = new VCardSelectedListener(multipleSelect);
- AlertDialog.Builder builder =
- new AlertDialog.Builder(this)
- .setTitle(R.string.select_vcard_title)
- .setPositiveButton(android.R.string.ok, listener)
- .setOnCancelListener(mCancelListener)
- .setNegativeButton(android.R.string.cancel, mCancelListener);
-
- CharSequence[] items = new CharSequence[size];
- DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
- for (int i = 0; i < size; i++) {
- VCardFile vcardFile = mAllVCardFileList.get(i);
- SpannableStringBuilder stringBuilder = new SpannableStringBuilder();
- stringBuilder.append(vcardFile.getName());
- stringBuilder.append('\n');
- int indexToBeSpanned = stringBuilder.length();
- // Smaller date text looks better, since each file name becomes easier to read.
- // The value set to RelativeSizeSpan is arbitrary. You can change it to any other
- // value (but the value bigger than 1.0f would not make nice appearance :)
- stringBuilder.append(
- "(" + dateFormat.format(new Date(vcardFile.getLastModified())) + ")");
- stringBuilder.setSpan(
- new RelativeSizeSpan(0.7f), indexToBeSpanned, stringBuilder.length(),
- Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
- items[i] = stringBuilder;
- }
- if (multipleSelect) {
- builder.setMultiChoiceItems(items, (boolean[])null, listener);
- } else {
- builder.setSingleChoiceItems(items, 0, listener);
- }
- return builder.create();
- }
-
- @Override
- protected void onCreate(Bundle bundle) {
- super.onCreate(bundle);
-
- final Intent intent = getIntent();
- if (intent != null) {
- final String accountName = intent.getStringExtra("account_name");
- final String accountType = intent.getStringExtra("account_type");
- if (!TextUtils.isEmpty(accountName) && !TextUtils.isEmpty(accountType)) {
- mAccount = new Account(accountName, accountType);
- }
- } else {
- Log.e(LOG_TAG, "intent does not exist");
- }
-
- // The caller often does not know account information at all, so we show the UI instead.
- if (mAccount == null) {
- // There's three possibilities:
- // - more than one accounts -> ask the user
- // - just one account -> use the account without asking the user
- // - no account -> use phone-local storage without asking the user
- final Sources sources = Sources.getInstance(this);
- final List<Account> accountList = sources.getAccounts(true);
- final int size = accountList.size();
- if (size > 1) {
- final int resId = R.string.import_from_sdcard;
- mAccountSelectionListener =
- new AccountSelectionUtil.AccountSelectedListener(
- this, accountList, resId) {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- dialog.dismiss();
- mAccount = mAccountList.get(which);
- // Instead of using Intent mechanism, call the relevant private method,
- // to avoid throwing an Intent to itself again.
- startImport();
- }
- };
- showDialog(resId);
- return;
- } else {
- mAccount = size > 0 ? accountList.get(0) : null;
- }
- }
-
- startImport();
- }
-
- private void startImport() {
- Intent intent = getIntent();
- final String action = intent.getAction();
- final Uri uri = intent.getData();
- Log.v(LOG_TAG, "action = " + action + " ; path = " + uri);
- if (Intent.ACTION_VIEW.equals(action)) {
- // Import the file directly and then go to EDIT screen
- mNeedReview = true;
- }
-
- if (uri != null) {
- importOneVCardFromSDCard(uri);
- } else {
- doScanExternalStorageAndImportVCard();
- }
- }
-
- @Override
- protected Dialog onCreateDialog(int resId) {
- switch (resId) {
- case R.string.import_from_sdcard: {
- if (mAccountSelectionListener == null) {
- throw new NullPointerException(
- "mAccountSelectionListener must not be null.");
- }
- return AccountSelectionUtil.getSelectAccountDialog(this, resId,
- mAccountSelectionListener,
- new CancelListener());
- }
- case R.id.dialog_searching_vcard: {
- if (mProgressDialogForScanVCard == null) {
- String title = getString(R.string.searching_vcard_title);
- String message = getString(R.string.searching_vcard_message);
- mProgressDialogForScanVCard =
- ProgressDialog.show(this, title, message, true, false);
- mProgressDialogForScanVCard.setOnCancelListener(mVCardScanThread);
- mVCardScanThread.start();
- }
- return mProgressDialogForScanVCard;
- }
- case R.id.dialog_sdcard_not_found: {
- AlertDialog.Builder builder = new AlertDialog.Builder(this)
- .setTitle(R.string.no_sdcard_title)
- .setIcon(android.R.drawable.ic_dialog_alert)
- .setMessage(R.string.no_sdcard_message)
- .setOnCancelListener(mCancelListener)
- .setPositiveButton(android.R.string.ok, mCancelListener);
- return builder.create();
- }
- case R.id.dialog_vcard_not_found: {
- String message = (getString(R.string.scanning_sdcard_failed_message,
- getString(R.string.fail_reason_no_vcard_file)));
- AlertDialog.Builder builder = new AlertDialog.Builder(this)
- .setTitle(R.string.scanning_sdcard_failed_title)
- .setMessage(message)
- .setOnCancelListener(mCancelListener)
- .setPositiveButton(android.R.string.ok, mCancelListener);
- return builder.create();
- }
- case R.id.dialog_select_import_type: {
- return getSelectImportTypeDialog();
- }
- case R.id.dialog_select_multiple_vcard: {
- return getVCardFileSelectDialog(true);
- }
- case R.id.dialog_select_one_vcard: {
- return getVCardFileSelectDialog(false);
- }
- case R.id.dialog_reading_vcard: {
- if (mProgressDialogForReadVCard == null) {
- String title = getString(R.string.reading_vcard_title);
- String message = getString(R.string.reading_vcard_message);
- mProgressDialogForReadVCard = new ProgressDialog(this);
- mProgressDialogForReadVCard.setTitle(title);
- mProgressDialogForReadVCard.setMessage(message);
- mProgressDialogForReadVCard.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
- mProgressDialogForReadVCard.setOnCancelListener(mVCardReadThread);
- mVCardReadThread.start();
- }
- return mProgressDialogForReadVCard;
- }
- case R.id.dialog_io_exception: {
- String message = (getString(R.string.scanning_sdcard_failed_message,
- getString(R.string.fail_reason_io_error)));
- AlertDialog.Builder builder = new AlertDialog.Builder(this)
- .setTitle(R.string.scanning_sdcard_failed_title)
- .setIcon(android.R.drawable.ic_dialog_alert)
- .setMessage(message)
- .setOnCancelListener(mCancelListener)
- .setPositiveButton(android.R.string.ok, mCancelListener);
- return builder.create();
- }
- case R.id.dialog_error_with_message: {
- String message = mErrorMessage;
- if (TextUtils.isEmpty(message)) {
- Log.e(LOG_TAG, "Error message is null while it must not.");
- message = getString(R.string.fail_reason_unknown);
- }
- AlertDialog.Builder builder = new AlertDialog.Builder(this)
- .setTitle(getString(R.string.reading_vcard_failed_title))
- .setIcon(android.R.drawable.ic_dialog_alert)
- .setMessage(message)
- .setOnCancelListener(mCancelListener)
- .setPositiveButton(android.R.string.ok, mCancelListener);
- return builder.create();
- }
- }
-
- return super.onCreateDialog(resId);
- }
-
- @Override
- protected void onPause() {
- super.onPause();
- if (mVCardReadThread != null) {
- // The Activity is no longer visible. Stop the thread.
- mVCardReadThread.cancel();
- mVCardReadThread = null;
- }
-
- // ImportVCardActivity should not be persistent. In other words, if there's some
- // event calling onPause(), this Activity should finish its work and give the main
- // screen back to the caller Activity.
- if (!isFinishing()) {
- finish();
- }
- }
-
- @Override
- protected void onDestroy() {
- // The code assumes the handler runs on the UI thread. If not,
- // clearing the message queue is not enough, one would have to
- // make sure that the handler does not run any callback when
- // this activity isFinishing().
-
- // Need to make sure any worker thread is done before we flush and
- // nullify the message handler.
- if (mVCardReadThread != null) {
- Log.w(LOG_TAG, "VCardReadThread exists while this Activity is now being killed!");
- mVCardReadThread.cancel();
- int attempts = 0;
- while (mVCardReadThread.isAlive() && attempts < 10) {
- try {
- Thread.currentThread().sleep(20);
- } catch (InterruptedException ie) {
- // Keep on going until max attempts is reached.
- }
- attempts++;
- }
- if (mVCardReadThread.isAlive()) {
- // Find out why the thread did not exit in a timely
- // fashion. Last resort: increase the sleep duration
- // and/or the number of attempts.
- Log.e(LOG_TAG, "VCardReadThread is still alive after max attempts.");
- }
- mVCardReadThread = null;
- }
-
- // Callbacks messages have what == 0.
- if (mHandler.hasMessages(0)) {
- mHandler.removeMessages(0);
- }
-
- mHandler = null; // Prevents memory leaks by breaking any circular dependency.
- super.onDestroy();
- }
-
- /**
- * Tries to run a given Runnable object when the UI thread can. Ignore it otherwise
- */
- private void runOnUIThread(Runnable runnable) {
- if (mHandler == null) {
- Log.w(LOG_TAG, "Handler object is null. No dialog is shown.");
- } else {
- mHandler.post(runnable);
- }
- }
-
- @Override
- public void finalize() {
- // TODO: This should not be needed. Throw exception instead.
- if (mVCardReadThread != null) {
- // Not sure this procedure is really needed, but just in case...
- Log.e(LOG_TAG, "VCardReadThread exists while this Activity is now being killed!");
- mVCardReadThread.cancel();
- mVCardReadThread = null;
- }
- }
-
- /**
- * Scans vCard in external storage (typically SDCard) and tries to import it.
- * - When there's no SDCard available, an error dialog is shown.
- * - When multiple vCard files are available, asks a user to select one.
- */
- private void doScanExternalStorageAndImportVCard() {
- // TODO: should use getExternalStorageState().
- final File file = Environment.getExternalStorageDirectory();
- if (!file.exists() || !file.isDirectory() || !file.canRead()) {
- showDialog(R.id.dialog_sdcard_not_found);
- } else {
- mVCardScanThread = new VCardScanThread(file);
- showDialog(R.id.dialog_searching_vcard);
- }
- }
-}
diff --git a/src/com/android/contacts/JoinContactActivity.java b/src/com/android/contacts/JoinContactActivity.java
new file mode 100644
index 0000000..ea48763
--- /dev/null
+++ b/src/com/android/contacts/JoinContactActivity.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2009 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;
+
+
+import com.android.contacts.list.ContactEntryListFragment;
+import com.android.contacts.list.JoinContactListFragment;
+import com.android.contacts.list.OnContactPickerActionListener;
+
+import android.app.Activity;
+import android.app.FragmentTransaction;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.ContactsContract;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+
+/**
+ * An activity that shows a list of contacts that can be joined with the target contact.
+ */
+public class JoinContactActivity extends Activity {
+
+ private static final String TAG = "JoinContactActivity";
+
+ /**
+ * The action for the join contact activity.
+ * <p>
+ * Input: extra field {@link #EXTRA_TARGET_CONTACT_ID} is the aggregate ID.
+ * TODO: move to {@link ContactsContract}.
+ */
+ public static final String JOIN_CONTACT = "com.android.contacts.action.JOIN_CONTACT";
+
+ /**
+ * Used with {@link #JOIN_CONTACT} to give it the target for aggregation.
+ * <p>
+ * Type: LONG
+ */
+ public static final String EXTRA_TARGET_CONTACT_ID = "com.android.contacts.action.CONTACT_ID";
+
+ private long mTargetContactId;
+
+ private JoinContactListFragment mListFragment;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Intent intent = getIntent();
+ mTargetContactId = intent.getLongExtra(EXTRA_TARGET_CONTACT_ID, -1);
+ if (mTargetContactId == -1) {
+ Log.e(TAG, "Intent " + intent.getAction() + " is missing required extra: "
+ + EXTRA_TARGET_CONTACT_ID);
+ setResult(RESULT_CANCELED);
+ finish();
+ return;
+ }
+
+ mListFragment = new JoinContactListFragment();
+ mListFragment.setTargetContactId(mTargetContactId);
+ mListFragment.setOnContactPickerActionListener(new OnContactPickerActionListener() {
+ public void onPickContactAction(Uri contactUri) {
+ Intent intent = new Intent(null, contactUri);
+ setResult(RESULT_OK, intent);
+ finish();
+ }
+
+ public void onSearchAllContactsAction(String string) {
+ }
+
+ public void onShortcutIntentCreated(Intent intent) {
+ }
+
+ public void onCreateNewContactAction() {
+ }
+ });
+
+ FragmentTransaction transaction = getFragmentManager().openTransaction();
+ transaction.add(android.R.id.content, mListFragment);
+ transaction.commit();
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ MenuInflater inflater = getMenuInflater();
+ inflater.inflate(R.menu.search, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.menu_search: {
+ onSearchRequested();
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void startSearch(String initialQuery, boolean selectInitialQuery, Bundle appSearchData,
+ boolean globalSearch) {
+ if (globalSearch) {
+ super.startSearch(initialQuery, selectInitialQuery, appSearchData, globalSearch);
+ } else {
+ mListFragment.startSearch(initialQuery);
+ }
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (requestCode == ContactEntryListFragment.ACTIVITY_REQUEST_CODE_PICKER
+ && resultCode == RESULT_OK) {
+ mListFragment.onPickerResult(data);
+ }
+ }
+}
diff --git a/src/com/android/contacts/MultiplePhonePickerActivity.java b/src/com/android/contacts/MultiplePhonePickerActivity.java
new file mode 100644
index 0000000..572b4de
--- /dev/null
+++ b/src/com/android/contacts/MultiplePhonePickerActivity.java
@@ -0,0 +1,226 @@
+/*
+ * 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;
+
+import com.android.contacts.list.MultiplePhonePickerFragment;
+import com.android.contacts.list.OnMultiplePhoneNumberPickerActionListener;
+
+import android.app.Activity;
+import android.app.FragmentTransaction;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.provider.ContactsContract.Intents;
+import android.view.Menu;
+import android.view.MenuInflater;
+
+/**
+ * Displays of phone numbers and allows selection of multiple numbers.
+ */
+public class MultiplePhonePickerActivity extends Activity {
+
+ /**
+ * Display only selected recipients or not in MODE_PICK_MULTIPLE_PHONES mode
+ */
+ private boolean mShowSelectedOnly = false;
+
+ private MultiplePhonePickerFragment mListFragment;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mListFragment = new MultiplePhonePickerFragment();
+ mListFragment.setOnMultiplePhoneNumberPickerActionListener(
+ new OnMultiplePhoneNumberPickerActionListener() {
+
+ public void onPhoneNumbersSelectedAction(Uri[] dataUris) {
+ returnActivityResult(dataUris);
+ }
+
+ public void onFinishAction() {
+ finish();
+ }
+ });
+
+ Parcelable[] extras = getIntent().getParcelableArrayExtra(Intents.EXTRA_PHONE_URIS);
+ mListFragment.setSelectedUris(extras);
+ FragmentTransaction transaction = getFragmentManager().openTransaction();
+ transaction.add(android.R.id.content, mListFragment);
+ transaction.commit();
+ }
+
+ @Override
+ public void onBackPressed() {
+ returnActivityResult(mListFragment.getSelectedUris());
+ super.onBackPressed();
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle icicle) {
+ super.onSaveInstanceState(icicle);
+ mListFragment.onSaveInstanceState(icicle);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ super.onCreateOptionsMenu(menu);
+
+ final MenuInflater inflater = getMenuInflater();
+ inflater.inflate(R.menu.pick, menu);
+ return true;
+ }
+
+ /*
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ if (mShowSelectedOnly) {
+ menu.findItem(R.id.menu_display_selected).setVisible(false);
+ menu.findItem(R.id.menu_display_all).setVisible(true);
+ menu.findItem(R.id.menu_select_all).setVisible(false);
+ menu.findItem(R.id.menu_select_none).setVisible(false);
+ return true;
+ }
+ menu.findItem(R.id.menu_display_all).setVisible(false);
+ menu.findItem(R.id.menu_display_selected).setVisible(true);
+ if (mUserSelection.isAllSelected()) {
+ menu.findItem(R.id.menu_select_all).setVisible(false);
+ menu.findItem(R.id.menu_select_none).setVisible(true);
+ } else {
+ menu.findItem(R.id.menu_select_all).setVisible(true);
+ menu.findItem(R.id.menu_select_none).setVisible(false);
+ }
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.menu_select_all: {
+ mUserSelection.setAllPhonesSelected(true);
+ checkAll(true);
+ updateWidgets(true);
+ return true;
+ }
+ case R.id.menu_select_none: {
+ mUserSelection.setAllPhonesSelected(false);
+ checkAll(false);
+ updateWidgets(true);
+ return true;
+ }
+ case R.id.menu_display_selected: {
+ mShowSelectedOnly = true;
+ startQuery();
+ return true;
+ }
+ case R.id.menu_display_all: {
+ mShowSelectedOnly = false;
+ startQuery();
+ return true;
+ }
+ }
+ return super.onOptionsItemSelected(item);
+ }
+*/
+ @Override
+ public void startSearch(String initialQuery, boolean selectInitialQuery, Bundle appSearchData,
+ boolean globalSearch) {
+ // TODO
+// if (mProviderStatus != ProviderStatus.STATUS_NORMAL) {
+// return;
+// }
+//
+// if (globalSearch) {
+// super.startSearch(initialQuery, selectInitialQuery, appSearchData, globalSearch);
+// } else {
+// if (!mSearchMode && (mMode & MODE_MASK_NO_FILTER) == 0) {
+// if ((mMode & MODE_MASK_PICKER) != 0) {
+// Bundle extras = getIntent().getExtras();
+// if (extras == null) {
+// extras = new Bundle();
+// }
+// mUserSelection.fillSelectionForSearchMode(extras);
+// ContactsSearchManager.startSearchForResult(this, initialQuery,
+// SUBACTIVITY_FILTER, extras);
+// } else {
+// ContactsSearchManager.startSearch(this, initialQuery);
+// }
+// }
+// }
+ }
+
+// @Override
+// protected void startQuery(Uri uri, String[] projection) {
+// // Filter unknown phone numbers first.
+// mPhoneNumberAdapter.doFilter(null, mShowSelectedOnly);
+// if (mShowSelectedOnly) {
+// StringBuilder idSetBuilder = new StringBuilder();
+// Iterator<Long> itr = mUserSelection.getSelectedPhonIds();
+// if (itr.hasNext()) {
+// idSetBuilder.append(Long.toString(itr.next()));
+// }
+// while (itr.hasNext()) {
+// idSetBuilder.append(',');
+// idSetBuilder.append(Long.toString(itr.next()));
+// }
+// String whereClause = Phone._ID + " IN (" + idSetBuilder.toString() + ")";
+// mQueryHandler.startQuery(QUERY_TOKEN, null, uri, projection, whereClause, null,
+// getSortOrder(projection));
+// } else {
+// mQueryHandler.startQuery(QUERY_TOKEN, null, uri,
+// projection, CLAUSE_ONLY_VISIBLE, null, getSortOrder(projection));
+// }
+// }
+
+// @Override
+// public Cursor doFilter(String filter) {
+// String[] projection = getProjectionForQuery();
+// if (mSearchMode && TextUtils.isEmpty(mListFragment.getQueryString())) {
+// return new MatrixCursor(projection);
+// }
+//
+// final ContentResolver resolver = getContentResolver();
+// // Filter phone numbers as well.
+// mPhoneNumberAdapter.doFilter(filter, mShowSelectedOnly);
+//
+// Uri uri = getUriToQuery();
+// if (!TextUtils.isEmpty(filter)) {
+// uri = Uri.withAppendedPath(Phone.CONTENT_FILTER_URI, Uri.encode(filter));
+// }
+// return resolver.query(uri, projection, CLAUSE_ONLY_VISIBLE, null, getSortOrder(projection));
+// }
+
+ public void returnActivityResult(Uri[] dataUris) {
+ Intent intent = new Intent();
+ intent.putExtra(Intents.EXTRA_PHONE_URIS, dataUris);
+ setResult(RESULT_OK, intent);
+ finish();
+ }
+
+ private void checkAll(boolean checked) {
+ // TODO fix this. It should iterate over the cursor rather than the views in the list.
+ /*
+ final ListView listView = getListView();
+ int childCount = listView.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final ContactListItemView child = (ContactListItemView)listView.getChildAt(i);
+ child.getCheckBoxView().setChecked(checked);
+ }
+ */
+ }
+}
diff --git a/src/com/android/contacts/PhoneDisambigDialog.java b/src/com/android/contacts/PhoneDisambigDialog.java
deleted file mode 100644
index d8cb14e..0000000
--- a/src/com/android/contacts/PhoneDisambigDialog.java
+++ /dev/null
@@ -1,227 +0,0 @@
-/*
- * Copyright (C) 2009 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;
-
-import com.android.contacts.Collapser.Collapsible;
-import com.android.contacts.model.ContactsSource;
-import com.android.contacts.model.Sources;
-import com.android.contacts.model.ContactsSource.DataKind;
-import com.android.contacts.model.ContactsSource.StringInflater;
-
-import android.app.AlertDialog;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.database.Cursor;
-import android.provider.ContactsContract.Data;
-import android.provider.ContactsContract.RawContacts;
-import android.provider.ContactsContract.CommonDataKinds.Phone;
-import android.telephony.PhoneNumberUtils;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ArrayAdapter;
-import android.widget.CheckBox;
-import android.widget.CompoundButton;
-import android.widget.ListAdapter;
-import android.widget.TextView;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Class used for displaying a dialog with a list of phone numbers of which
- * one will be chosen to make a call or initiate an sms message.
- */
-public class PhoneDisambigDialog implements DialogInterface.OnClickListener,
- DialogInterface.OnDismissListener, CompoundButton.OnCheckedChangeListener{
-
- private boolean mMakePrimary = false;
- private Context mContext;
- private AlertDialog mDialog;
- private boolean mSendSms;
- private Cursor mPhonesCursor;
- private ListAdapter mPhonesAdapter;
- private ArrayList<PhoneItem> mPhoneItemList;
-
- public PhoneDisambigDialog(Context context, Cursor phonesCursor) {
- this(context, phonesCursor, false /*make call*/);
- }
-
- public PhoneDisambigDialog(Context context, Cursor phonesCursor, boolean sendSms) {
- mContext = context;
- mSendSms = sendSms;
- mPhonesCursor = phonesCursor;
-
- mPhoneItemList = makePhoneItemsList(phonesCursor);
- Collapser.collapseList(mPhoneItemList);
-
- mPhonesAdapter = new PhonesAdapter(mContext, mPhoneItemList, mSendSms);
-
- LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
- Context.LAYOUT_INFLATER_SERVICE);
- View setPrimaryView = inflater.
- inflate(R.layout.set_primary_checkbox, null);
- ((CheckBox) setPrimaryView.findViewById(R.id.setPrimary)).
- setOnCheckedChangeListener(this);
-
- // Need to show disambig dialogue.
- AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(mContext).
- setAdapter(mPhonesAdapter, this).
- setTitle(sendSms ?
- R.string.sms_disambig_title : R.string.call_disambig_title).
- setView(setPrimaryView);
-
- mDialog = dialogBuilder.create();
- }
-
- /**
- * Show the dialog.
- */
- public void show() {
- if (mPhoneItemList.size() == 1) {
- // If there is only one after collapse, just select it, and close;
- onClick(mDialog, 0);
- return;
- }
- mDialog.show();
- }
-
- public void onClick(DialogInterface dialog, int which) {
- if (mPhoneItemList.size() > which && which >= 0) {
- PhoneItem phoneItem = mPhoneItemList.get(which);
- long id = phoneItem.id;
- String phone = phoneItem.phoneNumber;
-
- if (mMakePrimary) {
- ContentValues values = new ContentValues(1);
- values.put(Data.IS_SUPER_PRIMARY, 1);
- mContext.getContentResolver().update(ContentUris.
- withAppendedId(Data.CONTENT_URI, id), values, null, null);
- }
-
- if (mSendSms) {
- ContactsUtils.initiateSms(mContext, phone);
- } else {
- ContactsUtils.initiateCall(mContext, phone);
- }
- } else {
- dialog.dismiss();
- }
- }
-
- public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
- mMakePrimary = isChecked;
- }
-
- public void onDismiss(DialogInterface dialog) {
- mPhonesCursor.close();
- }
-
- private static class PhonesAdapter extends ArrayAdapter<PhoneItem> {
- private final boolean sendSms;
- private final Sources mSources;
-
- public PhonesAdapter(Context context, List<PhoneItem> objects, boolean sendSms) {
- super(context, R.layout.phone_disambig_item,
- android.R.id.text2, objects);
- this.sendSms = sendSms;
- mSources = Sources.getInstance(context);
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- View view = super.getView(position, convertView, parent);
-
- PhoneItem item = getItem(position);
- ContactsSource source = mSources.getInflatedSource(item.accountType,
- ContactsSource.LEVEL_SUMMARY);
-
- // Obtain a string representation of the phone type specific to the
- // ContactSource associated with that phone number
- TextView typeView = (TextView)view.findViewById(android.R.id.text1);
- DataKind kind = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE);
- if (kind != null) {
- ContentValues values = new ContentValues();
- values.put(Phone.TYPE, item.type);
- values.put(Phone.LABEL, item.label);
- StringInflater header = sendSms ? kind.actionAltHeader : kind.actionHeader;
- typeView.setText(header.inflateUsing(getContext(), values));
- } else {
- typeView.setText(R.string.call_other);
- }
- return view;
- }
- }
-
- private class PhoneItem implements Collapsible<PhoneItem> {
-
- final long id;
- final String phoneNumber;
- final String accountType;
- final long type;
- final String label;
-
- public PhoneItem(long id, String phoneNumber, String accountType, int type, String label) {
- this.id = id;
- this.phoneNumber = (phoneNumber != null ? phoneNumber : "");
- this.accountType = accountType;
- this.type = type;
- this.label = label;
- }
-
- public boolean collapseWith(PhoneItem phoneItem) {
- if (!shouldCollapseWith(phoneItem)) {
- return false;
- }
- // Just keep the number and id we already have.
- return true;
- }
-
- public boolean shouldCollapseWith(PhoneItem phoneItem) {
- if (PhoneNumberUtils.compare(PhoneDisambigDialog.this.mContext,
- phoneNumber, phoneItem.phoneNumber)) {
- return true;
- }
- return false;
- }
-
- @Override
- public String toString() {
- return phoneNumber;
- }
- }
-
- private ArrayList<PhoneItem> makePhoneItemsList(Cursor phonesCursor) {
- ArrayList<PhoneItem> phoneList = new ArrayList<PhoneItem>();
-
- phonesCursor.moveToPosition(-1);
- while (phonesCursor.moveToNext()) {
- long id = phonesCursor.getLong(phonesCursor.getColumnIndex(Data._ID));
- String phone = phonesCursor.getString(phonesCursor.getColumnIndex(Phone.NUMBER));
- String accountType =
- phonesCursor.getString(phonesCursor.getColumnIndex(RawContacts.ACCOUNT_TYPE));
- int type = phonesCursor.getInt(phonesCursor.getColumnIndex(Phone.TYPE));
- String label = phonesCursor.getString(phonesCursor.getColumnIndex(Phone.LABEL));
-
- phoneList.add(new PhoneItem(id, phone, accountType, type, label));
- }
-
- return phoneList;
- }
-}
diff --git a/src/com/android/contacts/PinnedHeaderListView.java b/src/com/android/contacts/PinnedHeaderListView.java
deleted file mode 100644
index 9d1391b..0000000
--- a/src/com/android/contacts/PinnedHeaderListView.java
+++ /dev/null
@@ -1,182 +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;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.ListAdapter;
-import android.widget.ListView;
-
-/**
- * A ListView that maintains a header pinned at the top of the list. The
- * pinned header can be pushed up and dissolved as needed.
- */
-public class PinnedHeaderListView extends ListView {
-
- /**
- * Adapter interface. The list adapter must implement this interface.
- */
- public interface PinnedHeaderAdapter {
-
- /**
- * Pinned header state: don't show the header.
- */
- public static final int PINNED_HEADER_GONE = 0;
-
- /**
- * Pinned header state: show the header at the top of the list.
- */
- public static final int PINNED_HEADER_VISIBLE = 1;
-
- /**
- * Pinned header state: show the header. If the header extends beyond
- * the bottom of the first shown element, push it up and clip.
- */
- public static final int PINNED_HEADER_PUSHED_UP = 2;
-
- /**
- * Computes the desired state of the pinned header for the given
- * position of the first visible list item. Allowed return values are
- * {@link #PINNED_HEADER_GONE}, {@link #PINNED_HEADER_VISIBLE} or
- * {@link #PINNED_HEADER_PUSHED_UP}.
- */
- int getPinnedHeaderState(int position);
-
- /**
- * Configures the pinned header view to match the first visible list item.
- *
- * @param header pinned header view.
- * @param position position of the first visible list item.
- * @param alpha fading of the header view, between 0 and 255.
- */
- void configurePinnedHeader(View header, int position, int alpha);
- }
-
- private static final int MAX_ALPHA = 255;
-
- private PinnedHeaderAdapter mAdapter;
- private View mHeaderView;
- private boolean mHeaderViewVisible;
-
- private int mHeaderViewWidth;
-
- private int mHeaderViewHeight;
-
- public PinnedHeaderListView(Context context) {
- super(context);
- }
-
- public PinnedHeaderListView(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- public PinnedHeaderListView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- }
-
- public void setPinnedHeaderView(View view) {
- mHeaderView = view;
-
- // Disable vertical fading when the pinned header is present
- // TODO change ListView to allow separate measures for top and bottom fading edge;
- // in this particular case we would like to disable the top, but not the bottom edge.
- if (mHeaderView != null) {
- setFadingEdgeLength(0);
- }
- requestLayout();
- }
-
- @Override
- public void setAdapter(ListAdapter adapter) {
- super.setAdapter(adapter);
- mAdapter = (PinnedHeaderAdapter)adapter;
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- if (mHeaderView != null) {
- measureChild(mHeaderView, widthMeasureSpec, heightMeasureSpec);
- mHeaderViewWidth = mHeaderView.getMeasuredWidth();
- mHeaderViewHeight = mHeaderView.getMeasuredHeight();
- }
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
- if (mHeaderView != null) {
- mHeaderView.layout(0, 0, mHeaderViewWidth, mHeaderViewHeight);
- configureHeaderView(getFirstVisiblePosition());
- }
- }
-
- public void configureHeaderView(int position) {
- if (mHeaderView == null) {
- return;
- }
-
- int state = mAdapter.getPinnedHeaderState(position);
- switch (state) {
- case PinnedHeaderAdapter.PINNED_HEADER_GONE: {
- mHeaderViewVisible = false;
- break;
- }
-
- case PinnedHeaderAdapter.PINNED_HEADER_VISIBLE: {
- mAdapter.configurePinnedHeader(mHeaderView, position, MAX_ALPHA);
- if (mHeaderView.getTop() != 0) {
- mHeaderView.layout(0, 0, mHeaderViewWidth, mHeaderViewHeight);
- }
- mHeaderViewVisible = true;
- break;
- }
-
- case PinnedHeaderAdapter.PINNED_HEADER_PUSHED_UP: {
- View firstView = getChildAt(0);
- int bottom = firstView.getBottom();
- int itemHeight = firstView.getHeight();
- int headerHeight = mHeaderView.getHeight();
- int y;
- int alpha;
- if (bottom < headerHeight) {
- y = (bottom - headerHeight);
- alpha = MAX_ALPHA * (headerHeight + y) / headerHeight;
- } else {
- y = 0;
- alpha = MAX_ALPHA;
- }
- mAdapter.configurePinnedHeader(mHeaderView, position, alpha);
- if (mHeaderView.getTop() != y) {
- mHeaderView.layout(0, y, mHeaderViewWidth, mHeaderViewHeight + y);
- }
- mHeaderViewVisible = true;
- break;
- }
- }
- }
-
- @Override
- protected void dispatchDraw(Canvas canvas) {
- super.dispatchDraw(canvas);
- if (mHeaderViewVisible) {
- drawChild(canvas, mHeaderView, getDrawingTime());
- }
- }
-}
diff --git a/src/com/android/contacts/ProgressShower.java b/src/com/android/contacts/ProgressShower.java
deleted file mode 100644
index a5ad2a2..0000000
--- a/src/com/android/contacts/ProgressShower.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright (C) 2009 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;
-
-import android.app.ProgressDialog;
-import android.content.Context;
-import android.os.Handler;
-import android.pim.vcard.VCardEntry;
-import android.pim.vcard.VCardEntryHandler;
-import android.pim.vcard.VCardConfig;
-import android.util.Log;
-
-public class ProgressShower implements VCardEntryHandler {
- public static final String LOG_TAG = "vcard.ProgressShower";
-
- private final Context mContext;
- private final Handler mHandler;
- private final ProgressDialog mProgressDialog;
- private final String mProgressMessage;
-
- private long mTime;
-
- private class ShowProgressRunnable implements Runnable {
- private VCardEntry mContact;
-
- public ShowProgressRunnable(VCardEntry contact) {
- mContact = contact;
- }
-
- public void run() {
- mProgressDialog.setMessage( mProgressMessage + "\n" +
- mContact.getDisplayName());
- mProgressDialog.incrementProgressBy(1);
- }
- }
-
- public ProgressShower(ProgressDialog progressDialog,
- String progressMessage,
- Context context,
- Handler handler) {
- mContext = context;
- mHandler = handler;
- mProgressDialog = progressDialog;
- mProgressMessage = progressMessage;
- }
-
- public void onStart() {
- }
-
- public void onEntryCreated(VCardEntry contactStruct) {
- long start = System.currentTimeMillis();
-
- if (!contactStruct.isIgnorable()) {
- if (mProgressDialog != null && mProgressMessage != null) {
- if (mHandler != null) {
- mHandler.post(new ShowProgressRunnable(contactStruct));
- } else {
- mProgressDialog.setMessage(mContext.getString(R.string.progress_shower_message,
- mProgressMessage,
- contactStruct.getDisplayName()));
- }
- }
- }
-
- mTime += System.currentTimeMillis() - start;
- }
-
- public void onEnd() {
- if (VCardConfig.showPerformanceLog()) {
- Log.d(LOG_TAG,
- String.format("Time to progress a dialog: %d ms", mTime));
- }
- }
-}
diff --git a/src/com/android/contacts/ScrollingTabWidget.java b/src/com/android/contacts/ScrollingTabWidget.java
deleted file mode 100644
index b45abe4..0000000
--- a/src/com/android/contacts/ScrollingTabWidget.java
+++ /dev/null
@@ -1,418 +0,0 @@
-/*
- * Copyright (C) 2009 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;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
-import android.view.View.OnClickListener;
-import android.view.View.OnFocusChangeListener;
-import android.widget.HorizontalScrollView;
-import android.widget.ImageView;
-import android.widget.RelativeLayout;
-
-/*
- * Tab widget that can contain more tabs than can fit on screen at once and scroll over them.
- */
-public class ScrollingTabWidget extends RelativeLayout
- implements OnClickListener, ViewTreeObserver.OnGlobalFocusChangeListener,
- OnFocusChangeListener {
-
- private static final String TAG = "ScrollingTabWidget";
-
- private OnTabSelectionChangedListener mSelectionChangedListener;
- private int mSelectedTab = 0;
- private ImageView mLeftArrowView;
- private ImageView mRightArrowView;
- private HorizontalScrollView mTabsScrollWrapper;
- private TabStripView mTabsView;
- private LayoutInflater mInflater;
-
- // Keeps track of the left most visible tab.
- private int mLeftMostVisibleTabIndex = 0;
-
- public ScrollingTabWidget(Context context) {
- this(context, null);
- }
-
- public ScrollingTabWidget(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public ScrollingTabWidget(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs);
-
- mInflater = (LayoutInflater) mContext.getSystemService(
- Context.LAYOUT_INFLATER_SERVICE);
-
- setFocusable(true);
- setOnFocusChangeListener(this);
- if (!hasFocus()) {
- setDescendantFocusability(FOCUS_BLOCK_DESCENDANTS);
- }
-
- mLeftArrowView = (ImageView) mInflater.inflate(R.layout.tab_left_arrow, this, false);
- mLeftArrowView.setOnClickListener(this);
- mRightArrowView = (ImageView) mInflater.inflate(R.layout.tab_right_arrow, this, false);
- mRightArrowView.setOnClickListener(this);
- mTabsScrollWrapper = (HorizontalScrollView) mInflater.inflate(
- R.layout.tab_layout, this, false);
- mTabsView = (TabStripView) mTabsScrollWrapper.findViewById(android.R.id.tabs);
- View accountNameView = mInflater.inflate(R.layout.tab_account_name, this, false);
-
- mLeftArrowView.setVisibility(View.INVISIBLE);
- mRightArrowView.setVisibility(View.INVISIBLE);
-
- addView(mTabsScrollWrapper);
- addView(mLeftArrowView);
- addView(mRightArrowView);
- addView(accountNameView);
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- final ViewTreeObserver treeObserver = getViewTreeObserver();
- if (treeObserver != null) {
- treeObserver.addOnGlobalFocusChangeListener(this);
- }
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- final ViewTreeObserver treeObserver = getViewTreeObserver();
- if (treeObserver != null) {
- treeObserver.removeOnGlobalFocusChangeListener(this);
- }
- }
-
- protected void updateArrowVisibility() {
- int scrollViewLeftEdge = mTabsScrollWrapper.getScrollX();
- int tabsViewLeftEdge = mTabsView.getLeft();
- int scrollViewRightEdge = scrollViewLeftEdge + mTabsScrollWrapper.getWidth();
- int tabsViewRightEdge = mTabsView.getRight();
-
- int rightArrowCurrentVisibility = mRightArrowView.getVisibility();
- if (scrollViewRightEdge == tabsViewRightEdge
- && rightArrowCurrentVisibility == View.VISIBLE) {
- mRightArrowView.setVisibility(View.INVISIBLE);
- } else if (scrollViewRightEdge < tabsViewRightEdge
- && rightArrowCurrentVisibility != View.VISIBLE) {
- mRightArrowView.setVisibility(View.VISIBLE);
- }
-
- int leftArrowCurrentVisibility = mLeftArrowView.getVisibility();
- if (scrollViewLeftEdge == tabsViewLeftEdge
- && leftArrowCurrentVisibility == View.VISIBLE) {
- mLeftArrowView.setVisibility(View.INVISIBLE);
- } else if (scrollViewLeftEdge > tabsViewLeftEdge
- && leftArrowCurrentVisibility != View.VISIBLE) {
- mLeftArrowView.setVisibility(View.VISIBLE);
- }
- }
-
- /**
- * Returns the tab indicator view at the given index.
- *
- * @param index the zero-based index of the tab indicator view to return
- * @return the tab indicator view at the given index
- */
- public View getChildTabViewAt(int index) {
- return mTabsView.getChildAt(index);
- }
-
- /**
- * Returns the number of tab indicator views.
- *
- * @return the number of tab indicator views.
- */
- public int getTabCount() {
- return mTabsView.getChildCount();
- }
-
- /**
- * Returns the {@link ViewGroup} that actually contains the tabs. This is where the tab
- * views should be attached to when being inflated.
- */
- public ViewGroup getTabParent() {
- return mTabsView;
- }
-
- public void removeAllTabs() {
- mTabsView.removeAllViews();
- }
-
- @Override
- public void dispatchDraw(Canvas canvas) {
- updateArrowVisibility();
- super.dispatchDraw(canvas);
- }
-
- /**
- * Sets the current tab.
- * This method is used to bring a tab to the front of the Widget,
- * and is used to post to the rest of the UI that a different tab
- * has been brought to the foreground.
- *
- * Note, this is separate from the traditional "focus" that is
- * employed from the view logic.
- *
- * For instance, if we have a list in a tabbed view, a user may be
- * navigating up and down the list, moving the UI focus (orange
- * highlighting) through the list items. The cursor movement does
- * not effect the "selected" tab though, because what is being
- * scrolled through is all on the same tab. The selected tab only
- * changes when we navigate between tabs (moving from the list view
- * to the next tabbed view, in this example).
- *
- * To move both the focus AND the selected tab at once, please use
- * {@link #focusCurrentTab}. Normally, the view logic takes care of
- * adjusting the focus, so unless you're circumventing the UI,
- * you'll probably just focus your interest here.
- *
- * @param index The tab that you want to indicate as the selected
- * tab (tab brought to the front of the widget)
- *
- * @see #focusCurrentTab
- */
- public void setCurrentTab(int index) {
- if (index < 0 || index >= getTabCount()) {
- return;
- }
-
- if (mSelectedTab < getTabCount()) {
- mTabsView.setSelected(mSelectedTab, false);
- }
- mSelectedTab = index;
- mTabsView.setSelected(mSelectedTab, true);
- }
-
- /**
- * Return index of the currently selected tab.
- */
- public int getCurrentTab() {
- return mSelectedTab;
- }
-
- /**
- * Sets the current tab and focuses the UI on it.
- * This method makes sure that the focused tab matches the selected
- * tab, normally at {@link #setCurrentTab}. Normally this would not
- * be an issue if we go through the UI, since the UI is responsible
- * for calling TabWidget.onFocusChanged(), but in the case where we
- * are selecting the tab programmatically, we'll need to make sure
- * focus keeps up.
- *
- * @param index The tab that you want focused (highlighted in orange)
- * and selected (tab brought to the front of the widget)
- *
- * @see #setCurrentTab
- */
- public void focusCurrentTab(int index) {
- if (index < 0 || index >= getTabCount()) {
- return;
- }
-
- setCurrentTab(index);
- getChildTabViewAt(index).requestFocus();
-
- }
-
- /**
- * Adds a tab to the list of tabs. The tab's indicator view is specified
- * by a layout id. InflateException will be thrown if there is a problem
- * inflating.
- *
- * @param layoutResId The layout id to be inflated to make the tab indicator.
- */
- public void addTab(int layoutResId) {
- addTab(mInflater.inflate(layoutResId, mTabsView, false));
- }
-
- /**
- * Adds a tab to the list of tabs. The tab's indicator view must be provided.
- *
- * @param child
- */
- public void addTab(View child) {
- if (child == null) {
- return;
- }
-
- if (child.getLayoutParams() == null) {
- final LayoutParams lp = new LayoutParams(
- ViewGroup.LayoutParams.WRAP_CONTENT,
- ViewGroup.LayoutParams.WRAP_CONTENT);
- lp.setMargins(0, 0, 0, 0);
- child.setLayoutParams(lp);
- }
-
- // Ensure you can navigate to the tab with the keyboard, and you can touch it
- child.setFocusable(true);
- child.setClickable(true);
- child.setOnClickListener(new TabClickListener());
- child.setOnFocusChangeListener(this);
-
- mTabsView.addView(child);
- }
-
- /**
- * Provides a way for ViewContactActivity and EditContactActivity to be notified that the
- * user clicked on a tab indicator.
- */
- public void setTabSelectionListener(OnTabSelectionChangedListener listener) {
- mSelectionChangedListener = listener;
- }
-
- public void onGlobalFocusChanged(View oldFocus, View newFocus) {
- if (isTab(oldFocus) && !isTab(newFocus)) {
- onLoseFocus();
- }
- }
-
- public void onFocusChange(View v, boolean hasFocus) {
- if (v == this && hasFocus) {
- onObtainFocus();
- return;
- }
-
- if (hasFocus) {
- for (int i = 0; i < getTabCount(); i++) {
- if (getChildTabViewAt(i) == v) {
- setCurrentTab(i);
- mSelectionChangedListener.onTabSelectionChanged(i, false);
- break;
- }
- }
- }
- }
-
- /**
- * Called when the {@link ScrollingTabWidget} gets focus. Here the
- * widget decides which of it's tabs should have focus.
- */
- protected void onObtainFocus() {
- // Setting this flag, allows the children of this View to obtain focus.
- setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
- // Assign focus to the last selected tab.
- focusCurrentTab(mSelectedTab);
- mSelectionChangedListener.onTabSelectionChanged(mSelectedTab, false);
- }
-
- /**
- * Called when the focus has left the {@link ScrollingTabWidget} or its
- * descendants. At this time we want the children of this view to be marked
- * as un-focusable, so that next time focus is moved to the widget, the widget
- * gets control, and can assign focus where it wants.
- */
- protected void onLoseFocus() {
- // Setting this flag will effectively make the tabs unfocusable. This will
- // be toggled when the widget obtains focus again.
- setDescendantFocusability(FOCUS_BLOCK_DESCENDANTS);
- }
-
- public boolean isTab(View v) {
- for (int i = 0; i < getTabCount(); i++) {
- if (getChildTabViewAt(i) == v) {
- return true;
- }
- }
- return false;
- }
-
- private class TabClickListener implements OnClickListener {
- public void onClick(View v) {
- for (int i = 0; i < getTabCount(); i++) {
- if (getChildTabViewAt(i) == v) {
- setCurrentTab(i);
- mSelectionChangedListener.onTabSelectionChanged(i, true);
- break;
- }
- }
- }
- }
-
- public interface OnTabSelectionChangedListener {
- /**
- * Informs the tab widget host which tab was selected. It also indicates
- * if the tab was clicked/pressed or just focused into.
- *
- * @param tabIndex index of the tab that was selected
- * @param clicked whether the selection changed due to a touch/click
- * or due to focus entering the tab through navigation. Pass true
- * if it was due to a press/click and false otherwise.
- */
- void onTabSelectionChanged(int tabIndex, boolean clicked);
- }
-
- public void onClick(View v) {
- updateLeftMostVisible();
- if (v == mRightArrowView && (mLeftMostVisibleTabIndex + 1 < getTabCount())) {
- tabScroll(true /* right */);
- } else if (v == mLeftArrowView && mLeftMostVisibleTabIndex > 0) {
- tabScroll(false /* left */);
- }
- }
-
- /*
- * Updates our record of the left most visible tab. We keep track of this explicitly
- * on arrow clicks, but need to re-calibrate after focus navigation.
- */
- protected void updateLeftMostVisible() {
- int viewableLeftEdge = mTabsScrollWrapper.getScrollX();
-
- if (mLeftArrowView.getVisibility() == View.VISIBLE) {
- viewableLeftEdge += mLeftArrowView.getWidth();
- }
-
- for (int i = 0; i < getTabCount(); i++) {
- View tab = getChildTabViewAt(i);
- int tabLeftEdge = tab.getLeft();
- if (tabLeftEdge >= viewableLeftEdge) {
- mLeftMostVisibleTabIndex = i;
- break;
- }
- }
- }
-
- /**
- * Scrolls the tabs by exactly one tab width.
- *
- * @param directionRight if true, scroll to the right, if false, scroll to the left.
- */
- protected void tabScroll(boolean directionRight) {
- int scrollWidth = 0;
- View newLeftMostVisibleTab = null;
- if (directionRight) {
- newLeftMostVisibleTab = getChildTabViewAt(++mLeftMostVisibleTabIndex);
- } else {
- newLeftMostVisibleTab = getChildTabViewAt(--mLeftMostVisibleTabIndex);
- }
-
- scrollWidth = newLeftMostVisibleTab.getLeft() - mTabsScrollWrapper.getScrollX();
- if (mLeftMostVisibleTabIndex > 0) {
- scrollWidth -= mLeftArrowView.getWidth();
- }
- mTabsScrollWrapper.smoothScrollBy(scrollWidth, 0);
- }
-
-}
diff --git a/src/com/android/contacts/SearchEditText.java b/src/com/android/contacts/SearchEditText.java
deleted file mode 100644
index 7683f23..0000000
--- a/src/com/android/contacts/SearchEditText.java
+++ /dev/null
@@ -1,69 +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;
-
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.view.KeyEvent;
-import android.widget.EditText;
-
-/**
- * A custom text editor that helps automatically dismiss the activity along with the soft
- * keyboard.
- */
-public class SearchEditText extends EditText {
-
- private boolean mMagnifyingGlassShown = true;
- private Drawable mMagnifyingGlass;
-
- public SearchEditText(Context context, AttributeSet attrs) {
- super(context, attrs);
- mMagnifyingGlass = getCompoundDrawables()[2];
- }
-
- /**
- * Conditionally shows a magnifying glass icon on the right side of the text field
- * when the text it empty.
- */
- @Override
- public boolean onPreDraw() {
- boolean emptyText = TextUtils.isEmpty(getText());
- if (mMagnifyingGlassShown != emptyText) {
- mMagnifyingGlassShown = emptyText;
- if (mMagnifyingGlassShown) {
- setCompoundDrawables(null, null, mMagnifyingGlass, null);
- } else {
- setCompoundDrawables(null, null, null, null);
- }
- return false;
- }
- return super.onPreDraw();
- }
-
- /**
- * Forwards the onKeyPreIme call to the view's activity.
- */
- @Override
- public boolean onKeyPreIme(int keyCode, KeyEvent event) {
- if (((ContactsListActivity)getContext()).onKeyPreIme(keyCode, event)) {
- return true;
- }
- return super.onKeyPreIme(keyCode, event);
- }
-}
diff --git a/src/com/android/contacts/TwelveKeyDialer.java b/src/com/android/contacts/TwelveKeyDialer.java
index 53c59f3..b9a65ae 100644
--- a/src/com/android/contacts/TwelveKeyDialer.java
+++ b/src/com/android/contacts/TwelveKeyDialer.java
@@ -268,7 +268,8 @@
}
protected void maybeAddNumberFormatting() {
- mDigits.addTextChangedListener(new PhoneNumberFormattingTextWatcher());
+ mDigits.addTextChangedListener(
+ new PhoneNumberFormattingTextWatcher(ContactsUtils.getCurrentCountryIso(this)));
}
/**
@@ -617,25 +618,6 @@
public boolean onKeyUp(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_CALL: {
- // TODO: In dialButtonPressed we do some of these
- // tests again. We should try to consolidate them in
- // one place.
- if (!phoneIsCdma() && mIsAddCallMode && isDigitsEmpty()) {
- // For CDMA phones, we always call
- // dialButtonPressed() because we may need to send
- // an empty flash command to the network.
- // Otherwise, if we are adding a call from the
- // InCallScreen and the phone number entered is
- // empty, we just close the dialer to expose the
- // InCallScreen under it.
- finish();
- }
-
- // If we're CDMA, regardless of where we are adding a call from (either
- // InCallScreen or Dialtacts), the user may need to send an empty
- // flash command to the network. So let's call dialButtonPressed() regardless
- // and dialButtonPressed will handle this functionality for us.
- // otherwise, we place the call.
dialButtonPressed();
return true;
}
@@ -774,52 +756,55 @@
}
void callVoicemail() {
- Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
- Uri.fromParts("voicemail", EMPTY_NUMBER, null));
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- startActivity(intent);
- mDigits.getText().clear();
+ startActivity(newVoicemailIntent());
+ mDigits.getText().clear(); // TODO: Fix bug 1745781
finish();
}
+ /**
+ * In most cases, when the dial button is pressed, there is a
+ * number in digits area. Pack it in the intent, start the
+ * outgoing call broadcast as a separate task and finish this
+ * activity.
+ *
+ * When there is no digit and the phone is CDMA and off hook,
+ * we're sending a blank flash for CDMA. CDMA networks use Flash
+ * messages when special processing needs to be done, mainly for
+ * 3-way or call waiting scenarios. Presumably, here we're in a
+ * special 3-way scenario where the network needs a blank flash
+ * before being able to add the new participant. (This is not the
+ * case with all 3-way calls, just certain CDMA infrastructures.)
+ *
+ * Otherwise, there is no digit, display the last dialed
+ * number. Don't finish since the user may want to edit it. The
+ * user needs to press the dial button again, to dial it (general
+ * case described above).
+ */
void dialButtonPressed() {
- final String number = mDigits.getText().toString();
- boolean sendEmptyFlash = false;
- Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED);
-
- if (isDigitsEmpty()) { // There is no number entered.
+ if (isDigitsEmpty()) { // No number entered.
if (phoneIsCdma() && phoneIsOffhook()) {
- // On CDMA phones, if we're already on a call, pressing
- // the Dial button without entering any digits means "send
- // an empty flash."
- intent.setData(Uri.fromParts("tel", EMPTY_NUMBER, null));
- intent.putExtra(EXTRA_SEND_EMPTY_FLASH, true);
- sendEmptyFlash = true;
- } else if (!TextUtils.isEmpty(mLastNumberDialed)) {
- // Otherwise, pressing the Dial button without entering
- // any digits means "recall the last number dialed".
- mDigits.setText(mLastNumberDialed);
- return;
+ // This is really CDMA specific. On GSM is it possible
+ // to be off hook and wanted to add a 3rd party using
+ // the redial feature.
+ startActivity(newFlashIntent());
} else {
- // Rare case: there's no "last number dialed". There's
- // nothing useful for the Dial button to do in this case.
- playTone(ToneGenerator.TONE_PROP_NACK);
- return;
+ if (!TextUtils.isEmpty(mLastNumberDialed)) {
+ mDigits.setText(mLastNumberDialed);
+ } else {
+ // There's no "last number dialed" or the
+ // background query is still running. There's
+ // nothing useful for the Dial button to do in
+ // this case. Note: with a soft dial button, this
+ // can never happens since the dial button is
+ // disabled under these conditons.
+ playTone(ToneGenerator.TONE_PROP_NACK);
+ }
}
- } else { // There is a number.
- intent.setData(Uri.fromParts("tel", number, null));
- }
+ } else {
+ final String number = mDigits.getText().toString();
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- startActivity(intent);
- mDigits.getText().clear();
-
- // Don't finish TwelveKeyDialer yet if we're sending a blank flash for CDMA. CDMA
- // networks use Flash messages when special processing needs to be done, mainly for
- // 3-way or call waiting scenarios. Presumably, here we're in a special 3-way scenario
- // where the network needs a blank flash before being able to add the new participant.
- // (This is not the case with all 3-way calls, just certain CDMA infrastructures.)
- if (!sendEmptyFlash) {
+ startActivity(newDialNumberIntent(number));
+ mDigits.getText().clear(); // TODO: Fix bug 1745781
finish();
}
}
@@ -1267,4 +1252,25 @@
ContactsSearchManager.startSearch(this, initialQuery);
}
}
+
+ // Helpers for the call intents.
+ private Intent newVoicemailIntent() {
+ final Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
+ Uri.fromParts("voicemail", EMPTY_NUMBER, null));
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ return intent;
+ }
+
+ private Intent newFlashIntent() {
+ final Intent intent = newDialNumberIntent(EMPTY_NUMBER);
+ intent.putExtra(EXTRA_SEND_EMPTY_FLASH, true);
+ return intent;
+ }
+
+ private Intent newDialNumberIntent(String number) {
+ final Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
+ Uri.fromParts("tel", number, null));
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ return intent;
+ }
}
diff --git a/src/com/android/contacts/ViewContactActivity.java b/src/com/android/contacts/ViewContactActivity.java
deleted file mode 100644
index a4a0892..0000000
--- a/src/com/android/contacts/ViewContactActivity.java
+++ /dev/null
@@ -1,1363 +0,0 @@
-/*
- * Copyright (C) 2007 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;
-import com.android.contacts.Collapser.Collapsible;
-import com.android.contacts.model.ContactsSource;
-import com.android.contacts.model.Sources;
-import com.android.contacts.model.ContactsSource.DataKind;
-import com.android.contacts.ui.EditContactActivity;
-import com.android.contacts.util.Constants;
-import com.android.contacts.util.DataStatus;
-import com.android.contacts.util.NotifyingAsyncQueryHandler;
-import com.android.internal.telephony.ITelephony;
-import com.android.internal.widget.ContactHeaderWidget;
-import com.google.android.collect.Lists;
-import com.google.android.collect.Maps;
-
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.content.ActivityNotFoundException;
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Entity;
-import android.content.EntityIterator;
-import android.content.Intent;
-import android.content.Entity.NamedContentValues;
-import android.content.res.Resources;
-import android.database.ContentObserver;
-import android.database.Cursor;
-import android.graphics.drawable.Drawable;
-import android.net.ParseException;
-import android.net.Uri;
-import android.net.WebAddress;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.provider.ContactsContract;
-import android.provider.ContactsContract.AggregationExceptions;
-import android.provider.ContactsContract.CommonDataKinds;
-import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.Data;
-import android.provider.ContactsContract.DisplayNameSources;
-import android.provider.ContactsContract.RawContacts;
-import android.provider.ContactsContract.RawContactsEntity;
-import android.provider.ContactsContract.StatusUpdates;
-import android.provider.ContactsContract.CommonDataKinds.Email;
-import android.provider.ContactsContract.CommonDataKinds.Im;
-import android.provider.ContactsContract.CommonDataKinds.Nickname;
-import android.provider.ContactsContract.CommonDataKinds.Note;
-import android.provider.ContactsContract.CommonDataKinds.Organization;
-import android.provider.ContactsContract.CommonDataKinds.Phone;
-import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
-import android.provider.ContactsContract.CommonDataKinds.Website;
-import android.telephony.PhoneNumberUtils;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.ContextMenu;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.Window;
-import android.view.ContextMenu.ContextMenuInfo;
-import android.widget.AdapterView;
-import android.widget.ImageView;
-import android.widget.ListView;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-
-/**
- * Displays the details of a specific contact.
- */
-public class ViewContactActivity extends Activity
- implements View.OnCreateContextMenuListener, DialogInterface.OnClickListener,
- AdapterView.OnItemClickListener, NotifyingAsyncQueryHandler.AsyncQueryListener {
- private static final String TAG = "ViewContact";
-
- private static final boolean SHOW_SEPARATORS = false;
-
- private static final int DIALOG_CONFIRM_DELETE = 1;
- private static final int DIALOG_CONFIRM_READONLY_DELETE = 2;
- private static final int DIALOG_CONFIRM_MULTIPLE_DELETE = 3;
- private static final int DIALOG_CONFIRM_READONLY_HIDE = 4;
-
- private static final int REQUEST_JOIN_CONTACT = 1;
- private static final int REQUEST_EDIT_CONTACT = 2;
-
- public static final int MENU_ITEM_MAKE_DEFAULT = 3;
-
- protected Uri mLookupUri;
- private ContentResolver mResolver;
- private ViewAdapter mAdapter;
- private int mNumPhoneNumbers = 0;
-
- /**
- * A list of distinct contact IDs included in the current contact.
- */
- private ArrayList<Long> mRawContactIds = new ArrayList<Long>();
-
- /* package */ ArrayList<ViewEntry> mPhoneEntries = new ArrayList<ViewEntry>();
- /* package */ ArrayList<ViewEntry> mSmsEntries = new ArrayList<ViewEntry>();
- /* package */ ArrayList<ViewEntry> mEmailEntries = new ArrayList<ViewEntry>();
- /* package */ ArrayList<ViewEntry> mPostalEntries = new ArrayList<ViewEntry>();
- /* package */ ArrayList<ViewEntry> mImEntries = new ArrayList<ViewEntry>();
- /* package */ ArrayList<ViewEntry> mNicknameEntries = new ArrayList<ViewEntry>();
- /* package */ ArrayList<ViewEntry> mOrganizationEntries = new ArrayList<ViewEntry>();
- /* package */ ArrayList<ViewEntry> mGroupEntries = new ArrayList<ViewEntry>();
- /* package */ ArrayList<ViewEntry> mOtherEntries = new ArrayList<ViewEntry>();
- /* package */ ArrayList<ArrayList<ViewEntry>> mSections = new ArrayList<ArrayList<ViewEntry>>();
-
- private Cursor mCursor;
-
- protected ContactHeaderWidget mContactHeaderWidget;
- private NotifyingAsyncQueryHandler mHandler;
-
- protected LayoutInflater mInflater;
-
- protected int mReadOnlySourcesCnt;
- protected int mWritableSourcesCnt;
- protected boolean mAllRestricted;
-
- protected Uri mPrimaryPhoneUri = null;
-
- protected ArrayList<Long> mWritableRawContactIds = new ArrayList<Long>();
-
- private static final int TOKEN_ENTITIES = 0;
- private static final int TOKEN_STATUSES = 1;
-
- private boolean mHasEntities = false;
- private boolean mHasStatuses = false;
-
- private long mNameRawContactId = -1;
- private int mDisplayNameSource = DisplayNameSources.UNDEFINED;
-
- private ArrayList<Entity> mEntities = Lists.newArrayList();
- private HashMap<Long, DataStatus> mStatuses = Maps.newHashMap();
-
- /**
- * The view shown if the detail list is empty.
- * We set this to the list view when first bind the adapter, so that it won't be shown while
- * we're loading data.
- */
- private View mEmptyView;
-
- private ContentObserver mObserver = new ContentObserver(new Handler()) {
- @Override
- public boolean deliverSelfNotifications() {
- return true;
- }
-
- @Override
- public void onChange(boolean selfChange) {
- if (mCursor != null && !mCursor.isClosed()) {
- startEntityQuery();
- }
- }
- };
-
- public void onClick(DialogInterface dialog, int which) {
- closeCursor();
- getContentResolver().delete(mLookupUri, null, null);
- finish();
- }
-
- private ListView mListView;
- private boolean mShowSmsLinksForAllPhones;
-
- @Override
- protected void onCreate(Bundle icicle) {
- super.onCreate(icicle);
-
- final Intent intent = getIntent();
- Uri data = intent.getData();
- String authority = data.getAuthority();
- if (ContactsContract.AUTHORITY.equals(authority)) {
- mLookupUri = data;
- } else if (android.provider.Contacts.AUTHORITY.equals(authority)) {
- final long rawContactId = ContentUris.parseId(data);
- mLookupUri = RawContacts.getContactLookupUri(getContentResolver(),
- ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId));
-
- }
- mInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-
- requestWindowFeature(Window.FEATURE_NO_TITLE);
- setContentView(R.layout.contact_card_layout);
-
- mContactHeaderWidget = (ContactHeaderWidget) findViewById(R.id.contact_header_widget);
- mContactHeaderWidget.showStar(true);
- mContactHeaderWidget.setExcludeMimes(new String[] {
- Contacts.CONTENT_ITEM_TYPE
- });
-
- mHandler = new NotifyingAsyncQueryHandler(this, this);
-
- mListView = (ListView) findViewById(R.id.contact_data);
- mListView.setOnCreateContextMenuListener(this);
- mListView.setScrollBarStyle(ListView.SCROLLBARS_OUTSIDE_OVERLAY);
- mListView.setOnItemClickListener(this);
- // Don't set it to mListView yet. We do so later when we bind the adapter.
- mEmptyView = findViewById(android.R.id.empty);
-
- mResolver = getContentResolver();
-
- // Build the list of sections. The order they're added to mSections dictates the
- // order they are displayed in the list.
- mSections.add(mPhoneEntries);
- mSections.add(mSmsEntries);
- mSections.add(mEmailEntries);
- mSections.add(mImEntries);
- mSections.add(mPostalEntries);
- mSections.add(mNicknameEntries);
- mSections.add(mOrganizationEntries);
- mSections.add(mGroupEntries);
- mSections.add(mOtherEntries);
-
- //TODO Read this value from a preference
- mShowSmsLinksForAllPhones = true;
- }
-
- @Override
- protected void onResume() {
- super.onResume();
- startEntityQuery();
- }
-
- @Override
- protected void onPause() {
- super.onPause();
- closeCursor();
- }
-
- @Override
- protected void onDestroy() {
- super.onDestroy();
- closeCursor();
- }
-
- @Override
- protected Dialog onCreateDialog(int id) {
- switch (id) {
- case DIALOG_CONFIRM_DELETE:
- return new AlertDialog.Builder(this)
- .setTitle(R.string.deleteConfirmation_title)
- .setIcon(android.R.drawable.ic_dialog_alert)
- .setMessage(R.string.deleteConfirmation)
- .setNegativeButton(android.R.string.cancel, null)
- .setPositiveButton(android.R.string.ok, this)
- .setCancelable(false)
- .create();
- case DIALOG_CONFIRM_READONLY_DELETE:
- return new AlertDialog.Builder(this)
- .setTitle(R.string.deleteConfirmation_title)
- .setIcon(android.R.drawable.ic_dialog_alert)
- .setMessage(R.string.readOnlyContactDeleteConfirmation)
- .setNegativeButton(android.R.string.cancel, null)
- .setPositiveButton(android.R.string.ok, this)
- .setCancelable(false)
- .create();
- case DIALOG_CONFIRM_MULTIPLE_DELETE:
- return new AlertDialog.Builder(this)
- .setTitle(R.string.deleteConfirmation_title)
- .setIcon(android.R.drawable.ic_dialog_alert)
- .setMessage(R.string.multipleContactDeleteConfirmation)
- .setNegativeButton(android.R.string.cancel, null)
- .setPositiveButton(android.R.string.ok, this)
- .setCancelable(false)
- .create();
- case DIALOG_CONFIRM_READONLY_HIDE: {
- return new AlertDialog.Builder(this)
- .setTitle(R.string.deleteConfirmation_title)
- .setIcon(android.R.drawable.ic_dialog_alert)
- .setMessage(R.string.readOnlyContactWarning)
- .setPositiveButton(android.R.string.ok, this)
- .create();
- }
-
- }
- return null;
- }
-
- /** {@inheritDoc} */
- public void onQueryComplete(int token, Object cookie, final Cursor cursor) {
- if (token == TOKEN_STATUSES) {
- try {
- // Read available social rows and consider binding
- readStatuses(cursor);
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- considerBindData();
- return;
- }
-
- // One would think we could just iterate over the Cursor
- // directly here, as the result set should be small, and we've
- // already run the query in an AsyncTask, but a lot of ANRs
- // were being reported in this code nonetheless. See bug
- // 2539603 for details. The real bug which makes this result
- // set huge and CPU-heavy may be elsewhere.
- // TODO: if we keep this async, perhaps the entity iteration
- // should also be original AsyncTask, rather than ping-ponging
- // between threads like this.
- final ArrayList<Entity> oldEntities = mEntities;
- (new AsyncTask<Void, Void, ArrayList<Entity>>() {
- @Override
- protected ArrayList<Entity> doInBackground(Void... params) {
- ArrayList<Entity> newEntities = new ArrayList<Entity>(cursor.getCount());
- EntityIterator iterator = RawContacts.newEntityIterator(cursor);
- try {
- while (iterator.hasNext()) {
- Entity entity = iterator.next();
- newEntities.add(entity);
- }
- } finally {
- iterator.close();
- }
- return newEntities;
- }
-
- @Override
- protected void onPostExecute(ArrayList<Entity> newEntities) {
- if (newEntities == null) {
- // There was an error loading.
- return;
- }
- synchronized (ViewContactActivity.this) {
- if (mEntities != oldEntities) {
- // Multiple async tasks were in flight and we
- // lost the race.
- return;
- }
- mEntities = newEntities;
- mHasEntities = true;
- }
- considerBindData();
- }
- }).execute();
- }
-
- private long getRefreshedContactId() {
- Uri freshContactUri = Contacts.lookupContact(getContentResolver(), mLookupUri);
- if (freshContactUri != null) {
- return ContentUris.parseId(freshContactUri);
- }
- return -1;
- }
-
- /**
- * Read from the given {@link Cursor} and build a set of {@link DataStatus}
- * objects to match any valid statuses found.
- */
- private synchronized void readStatuses(Cursor cursor) {
- mStatuses.clear();
-
- // Walk found statuses, creating internal row for each
- while (cursor.moveToNext()) {
- final DataStatus status = new DataStatus(cursor);
- final long dataId = cursor.getLong(StatusQuery._ID);
- mStatuses.put(dataId, status);
- }
-
- mHasStatuses = true;
- }
-
- private static Cursor setupContactCursor(ContentResolver resolver, Uri lookupUri) {
- if (lookupUri == null) {
- return null;
- }
- final List<String> segments = lookupUri.getPathSegments();
- if (segments.size() != 4) {
- return null;
- }
-
- // Contains an Id.
- final long uriContactId = Long.parseLong(segments.get(3));
- final String uriLookupKey = Uri.encode(segments.get(2));
- final Uri dataUri = Uri.withAppendedPath(
- ContentUris.withAppendedId(Contacts.CONTENT_URI, uriContactId),
- Contacts.Data.CONTENT_DIRECTORY);
-
- // This cursor has several purposes:
- // - Fetch NAME_RAW_CONTACT_ID and DISPLAY_NAME_SOURCE
- // - Fetch the lookup-key to ensure we are looking at the right record
- // - Watcher for change events
- Cursor cursor = resolver.query(dataUri,
- new String[] {
- Contacts.NAME_RAW_CONTACT_ID,
- Contacts.DISPLAY_NAME_SOURCE,
- Contacts.LOOKUP_KEY
- }, null, null, null);
-
- if (cursor.moveToFirst()) {
- String lookupKey =
- cursor.getString(cursor.getColumnIndex(Contacts.LOOKUP_KEY));
- if (!lookupKey.equals(uriLookupKey)) {
- // ID and lookup key do not match
- cursor.close();
- return null;
- }
- return cursor;
- } else {
- cursor.close();
- return null;
- }
- }
-
- private synchronized void startEntityQuery() {
- closeCursor();
-
- // Interprete mLookupUri
- mCursor = setupContactCursor(mResolver, mLookupUri);
-
- // If mCursor is null now we did not succeed in using the Uri's Id (or it didn't contain
- // a Uri). Instead we now have to use the lookup key to find the record
- if (mCursor == null) {
- mLookupUri = Contacts.getLookupUri(getContentResolver(), mLookupUri);
- mCursor = setupContactCursor(mResolver, mLookupUri);
- }
-
- // If mCursor is still null, we were unsuccessful in finding the record
- if (mCursor == null) {
- mNameRawContactId = -1;
- mDisplayNameSource = DisplayNameSources.UNDEFINED;
- // TODO either figure out a way to prevent a flash of black background or
- // use some other UI than a toast
- Toast.makeText(this, R.string.invalidContactMessage, Toast.LENGTH_SHORT).show();
- Log.e(TAG, "invalid contact uri: " + mLookupUri);
- finish();
- return;
- }
-
- final long contactId = ContentUris.parseId(mLookupUri);
-
- mNameRawContactId =
- mCursor.getLong(mCursor.getColumnIndex(Contacts.NAME_RAW_CONTACT_ID));
- mDisplayNameSource =
- mCursor.getInt(mCursor.getColumnIndex(Contacts.DISPLAY_NAME_SOURCE));
-
- mCursor.registerContentObserver(mObserver);
-
- // Clear flags and start queries to data and status
- mHasEntities = false;
- mHasStatuses = false;
-
- mHandler.startQuery(TOKEN_ENTITIES, null, RawContactsEntity.CONTENT_URI, null,
- RawContacts.CONTACT_ID + "=?", new String[] {
- String.valueOf(contactId)
- }, null);
- final Uri dataUri = Uri.withAppendedPath(
- ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId),
- Contacts.Data.CONTENT_DIRECTORY);
- mHandler.startQuery(TOKEN_STATUSES, null, dataUri, StatusQuery.PROJECTION,
- StatusUpdates.PRESENCE + " IS NOT NULL OR " + StatusUpdates.STATUS
- + " IS NOT NULL", null, null);
-
- mContactHeaderWidget.bindFromContactLookupUri(mLookupUri);
- }
-
- private void closeCursor() {
- if (mCursor != null) {
- mCursor.unregisterContentObserver(mObserver);
- mCursor.close();
- mCursor = null;
- }
- }
-
- /**
- * Consider binding views after any of several background queries has
- * completed. We check internal flags and only bind when all data has
- * arrived.
- */
- private void considerBindData() {
- if (mHasEntities && mHasStatuses) {
- bindData();
- }
- }
-
- private void bindData() {
-
- // Build up the contact entries
- buildEntries();
-
- // Collapse similar data items in select sections.
- Collapser.collapseList(mPhoneEntries);
- Collapser.collapseList(mSmsEntries);
- Collapser.collapseList(mEmailEntries);
- Collapser.collapseList(mPostalEntries);
- Collapser.collapseList(mImEntries);
-
- if (mAdapter == null) {
- mAdapter = new ViewAdapter(this, mSections);
- mListView.setAdapter(mAdapter);
- } else {
- mAdapter.setSections(mSections, SHOW_SEPARATORS);
- }
- mListView.setEmptyView(mEmptyView);
- }
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- super.onCreateOptionsMenu(menu);
-
- final MenuInflater inflater = getMenuInflater();
- inflater.inflate(R.menu.view, menu);
- return true;
- }
-
- @Override
- public boolean onPrepareOptionsMenu(Menu menu) {
- super.onPrepareOptionsMenu(menu);
-
- // Only allow edit when we have at least one raw_contact id
- final boolean hasRawContact = (mRawContactIds.size() > 0);
- menu.findItem(R.id.menu_edit).setEnabled(hasRawContact);
-
- // Only allow share when unrestricted contacts available
- menu.findItem(R.id.menu_share).setEnabled(!mAllRestricted);
-
- return true;
- }
-
- @Override
- public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) {
- AdapterView.AdapterContextMenuInfo info;
- try {
- info = (AdapterView.AdapterContextMenuInfo) menuInfo;
- } catch (ClassCastException e) {
- Log.e(TAG, "bad menuInfo", e);
- return;
- }
-
- // This can be null sometimes, don't crash...
- if (info == null) {
- Log.e(TAG, "bad menuInfo");
- return;
- }
-
- ViewEntry entry = ContactEntryAdapter.getEntry(mSections, info.position, SHOW_SEPARATORS);
- menu.setHeaderTitle(R.string.contactOptionsTitle);
- if (entry.mimetype.equals(CommonDataKinds.Phone.CONTENT_ITEM_TYPE)) {
- menu.add(0, 0, 0, R.string.menu_call).setIntent(entry.intent);
- menu.add(0, 0, 0, R.string.menu_sendSMS).setIntent(entry.secondaryIntent);
- if (!entry.isPrimary) {
- menu.add(0, MENU_ITEM_MAKE_DEFAULT, 0, R.string.menu_makeDefaultNumber);
- }
- } else if (entry.mimetype.equals(CommonDataKinds.Email.CONTENT_ITEM_TYPE)) {
- menu.add(0, 0, 0, R.string.menu_sendEmail).setIntent(entry.intent);
- if (!entry.isPrimary) {
- menu.add(0, MENU_ITEM_MAKE_DEFAULT, 0, R.string.menu_makeDefaultEmail);
- }
- } else if (entry.mimetype.equals(CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE)) {
- menu.add(0, 0, 0, R.string.menu_viewAddress).setIntent(entry.intent);
- }
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case R.id.menu_edit: {
- Long rawContactIdToEdit = null;
- if (mRawContactIds.size() > 0) {
- rawContactIdToEdit = mRawContactIds.get(0);
- } else {
- // There is no rawContact to edit.
- break;
- }
- Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI,
- rawContactIdToEdit);
- startActivityForResult(new Intent(Intent.ACTION_EDIT, rawContactUri),
- REQUEST_EDIT_CONTACT);
- break;
- }
- case R.id.menu_delete: {
- // Get confirmation
- if (mReadOnlySourcesCnt > 0 & mWritableSourcesCnt > 0) {
- showDialog(DIALOG_CONFIRM_READONLY_DELETE);
- } else if (mReadOnlySourcesCnt > 0 && mWritableSourcesCnt == 0) {
- showDialog(DIALOG_CONFIRM_READONLY_HIDE);
- } else if (mReadOnlySourcesCnt == 0 && mWritableSourcesCnt > 1) {
- showDialog(DIALOG_CONFIRM_MULTIPLE_DELETE);
- } else {
- showDialog(DIALOG_CONFIRM_DELETE);
- }
- return true;
- }
- case R.id.menu_join: {
- showJoinAggregateActivity();
- return true;
- }
- case R.id.menu_options: {
- showOptionsActivity();
- return true;
- }
- case R.id.menu_share: {
- if (mAllRestricted) return false;
-
- // TODO: Keep around actual LOOKUP_KEY, or formalize method of extracting
- final String lookupKey = Uri.encode(mLookupUri.getPathSegments().get(2));
- final Uri shareUri = Uri.withAppendedPath(Contacts.CONTENT_VCARD_URI, lookupKey);
-
- final Intent intent = new Intent(Intent.ACTION_SEND);
- intent.setType(Contacts.CONTENT_VCARD_TYPE);
- intent.putExtra(Intent.EXTRA_STREAM, shareUri);
-
- // Launch chooser to share contact via
- final CharSequence chooseTitle = getText(R.string.share_via);
- final Intent chooseIntent = Intent.createChooser(intent, chooseTitle);
-
- try {
- startActivity(chooseIntent);
- } catch (ActivityNotFoundException ex) {
- Toast.makeText(this, R.string.share_error, Toast.LENGTH_SHORT).show();
- }
- return true;
- }
- }
- return super.onOptionsItemSelected(item);
- }
-
- @Override
- public boolean onContextItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case MENU_ITEM_MAKE_DEFAULT: {
- if (makeItemDefault(item)) {
- return true;
- }
- break;
- }
- }
-
- return super.onContextItemSelected(item);
- }
-
- private boolean makeItemDefault(MenuItem item) {
- ViewEntry entry = getViewEntryForMenuItem(item);
- if (entry == null) {
- return false;
- }
-
- // Update the primary values in the data record.
- ContentValues values = new ContentValues(1);
- values.put(Data.IS_SUPER_PRIMARY, 1);
- getContentResolver().update(ContentUris.withAppendedId(Data.CONTENT_URI, entry.id),
- values, null, null);
- startEntityQuery();
- return true;
- }
-
- /**
- * Shows a list of aggregates that can be joined into the currently viewed aggregate.
- */
- public void showJoinAggregateActivity() {
- long freshId = getRefreshedContactId();
- if (freshId > 0) {
- String displayName = null;
- if (mCursor.moveToFirst()) {
- displayName = mCursor.getString(0);
- }
- Intent intent = new Intent(ContactsListActivity.JOIN_AGGREGATE);
- intent.putExtra(ContactsListActivity.EXTRA_AGGREGATE_ID, freshId);
- if (displayName != null) {
- intent.putExtra(ContactsListActivity.EXTRA_AGGREGATE_NAME, displayName);
- }
- startActivityForResult(intent, REQUEST_JOIN_CONTACT);
- }
- }
-
- @Override
- protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
- if (requestCode == REQUEST_JOIN_CONTACT) {
- if (resultCode == RESULT_OK && intent != null) {
- final long contactId = ContentUris.parseId(intent.getData());
- joinAggregate(contactId);
- }
- } else if (requestCode == REQUEST_EDIT_CONTACT) {
- if (resultCode == EditContactActivity.RESULT_CLOSE_VIEW_ACTIVITY) {
- finish();
- } else if (resultCode == Activity.RESULT_OK) {
- mLookupUri = intent.getData();
- if (mLookupUri == null) {
- finish();
- }
- }
- }
- }
-
- private void joinAggregate(final long contactId) {
- Cursor c = mResolver.query(RawContacts.CONTENT_URI, new String[] {RawContacts._ID},
- RawContacts.CONTACT_ID + "=" + contactId, null, null);
-
- try {
- while(c.moveToNext()) {
- long rawContactId = c.getLong(0);
- setAggregationException(rawContactId, AggregationExceptions.TYPE_KEEP_TOGETHER);
- }
- } finally {
- c.close();
- }
-
- Toast.makeText(this, R.string.contactsJoinedMessage, Toast.LENGTH_LONG).show();
- startEntityQuery();
- }
-
- /**
- * Given a contact ID sets an aggregation exception to either join the contact with the
- * current aggregate or split off.
- */
- protected void setAggregationException(long rawContactId, int exceptionType) {
- ContentValues values = new ContentValues(3);
- for (long aRawContactId : mRawContactIds) {
- if (aRawContactId != rawContactId) {
- values.put(AggregationExceptions.RAW_CONTACT_ID1, aRawContactId);
- values.put(AggregationExceptions.RAW_CONTACT_ID2, rawContactId);
- values.put(AggregationExceptions.TYPE, exceptionType);
- mResolver.update(AggregationExceptions.CONTENT_URI, values, null, null);
- }
- }
- }
-
- private void showOptionsActivity() {
- final Intent intent = new Intent(this, ContactOptionsActivity.class);
- intent.setData(mLookupUri);
- startActivity(intent);
- }
-
- private ViewEntry getViewEntryForMenuItem(MenuItem item) {
- AdapterView.AdapterContextMenuInfo info;
- try {
- info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
- } catch (ClassCastException e) {
- Log.e(TAG, "bad menuInfo", e);
- return null;
- }
-
- return ContactEntryAdapter.getEntry(mSections, info.position, SHOW_SEPARATORS);
- }
-
- @Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- switch (keyCode) {
- case KeyEvent.KEYCODE_CALL: {
- try {
- ITelephony phone = ITelephony.Stub.asInterface(
- ServiceManager.checkService("phone"));
- if (phone != null && !phone.isIdle()) {
- // Skip out and let the key be handled at a higher level
- break;
- }
- } catch (RemoteException re) {
- // Fall through and try to call the contact
- }
-
- int index = mListView.getSelectedItemPosition();
- if (index != -1) {
- ViewEntry entry = ViewAdapter.getEntry(mSections, index, SHOW_SEPARATORS);
- if (entry != null && entry.intent != null &&
- entry.intent.getAction() == Intent.ACTION_CALL_PRIVILEGED) {
- startActivity(entry.intent);
- return true;
- }
- } else if (mPrimaryPhoneUri != null) {
- // There isn't anything selected, call the default number
- final Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
- mPrimaryPhoneUri);
- startActivity(intent);
- return true;
- }
- return false;
- }
-
- case KeyEvent.KEYCODE_DEL: {
- if (mReadOnlySourcesCnt > 0 & mWritableSourcesCnt > 0) {
- showDialog(DIALOG_CONFIRM_READONLY_DELETE);
- } else if (mReadOnlySourcesCnt > 0 && mWritableSourcesCnt == 0) {
- showDialog(DIALOG_CONFIRM_READONLY_HIDE);
- } else if (mReadOnlySourcesCnt == 0 && mWritableSourcesCnt > 1) {
- showDialog(DIALOG_CONFIRM_MULTIPLE_DELETE);
- } else {
- showDialog(DIALOG_CONFIRM_DELETE);
- }
- return true;
- }
- }
-
- return super.onKeyDown(keyCode, event);
- }
-
- public void onItemClick(AdapterView parent, View v, int position, long id) {
- ViewEntry entry = ViewAdapter.getEntry(mSections, position, SHOW_SEPARATORS);
- if (entry != null) {
- Intent intent = entry.intent;
- if (intent != null) {
- try {
- startActivity(intent);
- } catch (ActivityNotFoundException e) {
- Log.e(TAG, "No activity found for intent: " + intent);
- signalError();
- }
- } else {
- signalError();
- }
- } else {
- signalError();
- }
- }
-
- /**
- * Signal an error to the user via a beep, or some other method.
- */
- private void signalError() {
- //TODO: implement this when we have the sonification APIs
- }
-
- /**
- * Build up the entries to display on the screen.
- *
- * @param personCursor the URI for the contact being displayed
- */
- private final void buildEntries() {
- // Clear out the old entries
- final int numSections = mSections.size();
- for (int i = 0; i < numSections; i++) {
- mSections.get(i).clear();
- }
-
- mRawContactIds.clear();
-
- mReadOnlySourcesCnt = 0;
- mWritableSourcesCnt = 0;
- mAllRestricted = true;
- mPrimaryPhoneUri = null;
-
- mWritableRawContactIds.clear();
-
- final Context context = this;
- final Sources sources = Sources.getInstance(context);
-
- // Build up method entries
- if (mLookupUri != null) {
- for (Entity entity: mEntities) {
- final ContentValues entValues = entity.getEntityValues();
- final String accountType = entValues.getAsString(RawContacts.ACCOUNT_TYPE);
- final long rawContactId = entValues.getAsLong(RawContacts._ID);
-
- // Mark when this contact has any unrestricted components
- final boolean isRestricted = entValues.getAsInteger(RawContacts.IS_RESTRICTED) != 0;
- if (!isRestricted) mAllRestricted = false;
-
- if (!mRawContactIds.contains(rawContactId)) {
- mRawContactIds.add(rawContactId);
- }
- ContactsSource contactsSource = sources.getInflatedSource(accountType,
- ContactsSource.LEVEL_SUMMARY);
- if (contactsSource != null && contactsSource.readOnly) {
- mReadOnlySourcesCnt += 1;
- } else {
- mWritableSourcesCnt += 1;
- mWritableRawContactIds.add(rawContactId);
- }
-
-
- for (NamedContentValues subValue : entity.getSubValues()) {
- final ContentValues entryValues = subValue.values;
- entryValues.put(Data.RAW_CONTACT_ID, rawContactId);
-
- final long dataId = entryValues.getAsLong(Data._ID);
- final String mimeType = entryValues.getAsString(Data.MIMETYPE);
- if (mimeType == null) continue;
-
- final DataKind kind = sources.getKindOrFallback(accountType, mimeType, this,
- ContactsSource.LEVEL_MIMETYPES);
- if (kind == null) continue;
-
- final ViewEntry entry = ViewEntry.fromValues(context, mimeType, kind,
- rawContactId, dataId, entryValues);
-
- final boolean hasData = !TextUtils.isEmpty(entry.data);
- final boolean isSuperPrimary = entryValues.getAsInteger(
- Data.IS_SUPER_PRIMARY) != 0;
-
- if (Phone.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
- // Build phone entries
- mNumPhoneNumbers++;
-
- entry.intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
- Uri.fromParts(Constants.SCHEME_TEL, entry.data, null));
- entry.secondaryIntent = new Intent(Intent.ACTION_SENDTO,
- Uri.fromParts(Constants.SCHEME_SMSTO, entry.data, null));
-
- // Remember super-primary phone
- if (isSuperPrimary) mPrimaryPhoneUri = entry.uri;
-
- entry.isPrimary = isSuperPrimary;
- mPhoneEntries.add(entry);
-
- if (entry.type == CommonDataKinds.Phone.TYPE_MOBILE
- || mShowSmsLinksForAllPhones) {
- // Add an SMS entry
- if (kind.iconAltRes > 0) {
- entry.secondaryActionIcon = kind.iconAltRes;
- }
- }
- } else if (Email.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
- // Build email entries
- entry.intent = new Intent(Intent.ACTION_SENDTO,
- Uri.fromParts(Constants.SCHEME_MAILTO, entry.data, null));
- entry.isPrimary = isSuperPrimary;
- mEmailEntries.add(entry);
-
- // When Email rows have status, create additional Im row
- final DataStatus status = mStatuses.get(entry.id);
- if (status != null) {
- final String imMime = Im.CONTENT_ITEM_TYPE;
- final DataKind imKind = sources.getKindOrFallback(accountType,
- imMime, this, ContactsSource.LEVEL_MIMETYPES);
- final ViewEntry imEntry = ViewEntry.fromValues(context,
- imMime, imKind, rawContactId, dataId, entryValues);
- imEntry.intent = ContactsUtils.buildImIntent(entryValues);
- imEntry.applyStatus(status, false);
- mImEntries.add(imEntry);
- }
- } else if (StructuredPostal.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
- // Build postal entries
- entry.maxLines = 4;
- entry.intent = new Intent(Intent.ACTION_VIEW, entry.uri);
- mPostalEntries.add(entry);
- } else if (Im.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
- // Build IM entries
- entry.intent = ContactsUtils.buildImIntent(entryValues);
- if (TextUtils.isEmpty(entry.label)) {
- entry.label = getString(R.string.chat).toLowerCase();
- }
-
- // Apply presence and status details when available
- final DataStatus status = mStatuses.get(entry.id);
- if (status != null) {
- entry.applyStatus(status, false);
- }
- mImEntries.add(entry);
- } else if (Organization.CONTENT_ITEM_TYPE.equals(mimeType) &&
- (hasData || !TextUtils.isEmpty(entry.label))) {
- // Build organization entries
- final boolean isNameRawContact = (mNameRawContactId == rawContactId);
-
- final boolean duplicatesTitle =
- isNameRawContact
- && mDisplayNameSource == DisplayNameSources.ORGANIZATION
- && (!hasData || TextUtils.isEmpty(entry.label));
-
- if (!duplicatesTitle) {
- entry.uri = null;
-
- if (TextUtils.isEmpty(entry.label)) {
- entry.label = entry.data;
- entry.data = "";
- }
-
- mOrganizationEntries.add(entry);
- }
- } else if (Nickname.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
- // Build nickname entries
- final boolean isNameRawContact = (mNameRawContactId == rawContactId);
-
- final boolean duplicatesTitle =
- isNameRawContact
- && mDisplayNameSource == DisplayNameSources.NICKNAME;
-
- if (!duplicatesTitle) {
- entry.uri = null;
- mNicknameEntries.add(entry);
- }
- } else if (Note.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
- // Build note entries
- entry.uri = null;
- entry.maxLines = 100;
- mOtherEntries.add(entry);
- } else if (Website.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
- // Build note entries
- entry.uri = null;
- entry.maxLines = 10;
- try {
- WebAddress webAddress = new WebAddress(entry.data);
- entry.intent = new Intent(Intent.ACTION_VIEW,
- Uri.parse(webAddress.toString()));
- } catch (ParseException e) {
- Log.e(TAG, "Couldn't parse website: " + entry.data);
- }
- mOtherEntries.add(entry);
- } else {
- // Handle showing custom rows
- entry.intent = new Intent(Intent.ACTION_VIEW, entry.uri);
-
- // Use social summary when requested by external source
- final DataStatus status = mStatuses.get(entry.id);
- final boolean hasSocial = kind.actionBodySocial && status != null;
- if (hasSocial) {
- entry.applyStatus(status, true);
- }
-
- if (hasSocial || hasData) {
- mOtherEntries.add(entry);
- }
- }
- }
- }
- }
- }
-
- static String buildActionString(DataKind kind, ContentValues values, boolean lowerCase,
- Context context) {
- if (kind.actionHeader == null) {
- return null;
- }
- CharSequence actionHeader = kind.actionHeader.inflateUsing(context, values);
- if (actionHeader == null) {
- return null;
- }
- return lowerCase ? actionHeader.toString().toLowerCase() : actionHeader.toString();
- }
-
- static String buildDataString(DataKind kind, ContentValues values, Context context) {
- if (kind.actionBody == null) {
- return null;
- }
- CharSequence actionBody = kind.actionBody.inflateUsing(context, values);
- return actionBody == null ? null : actionBody.toString();
- }
-
- /**
- * A basic structure with the data for a contact entry in the list.
- */
- static class ViewEntry extends ContactEntryAdapter.Entry implements Collapsible<ViewEntry> {
- public Context context = null;
- public String resPackageName = null;
- public int actionIcon = -1;
- public boolean isPrimary = false;
- public int secondaryActionIcon = -1;
- public Intent intent;
- public Intent secondaryIntent = null;
- public int maxLabelLines = 1;
- public ArrayList<Long> ids = new ArrayList<Long>();
- public int collapseCount = 0;
-
- public int presence = -1;
-
- public CharSequence footerLine = null;
-
- private ViewEntry() {
- }
-
- /**
- * Build new {@link ViewEntry} and populate from the given values.
- */
- public static ViewEntry fromValues(Context context, String mimeType, DataKind kind,
- long rawContactId, long dataId, ContentValues values) {
- final ViewEntry entry = new ViewEntry();
- entry.context = context;
- entry.contactId = rawContactId;
- entry.id = dataId;
- entry.uri = ContentUris.withAppendedId(Data.CONTENT_URI, entry.id);
- entry.mimetype = mimeType;
- entry.label = buildActionString(kind, values, false, context);
- entry.data = buildDataString(kind, values, context);
-
- if (kind.typeColumn != null && values.containsKey(kind.typeColumn)) {
- entry.type = values.getAsInteger(kind.typeColumn);
- }
- if (kind.iconRes > 0) {
- entry.resPackageName = kind.resPackageName;
- entry.actionIcon = kind.iconRes;
- }
-
- return entry;
- }
-
- /**
- * Apply given {@link DataStatus} values over this {@link ViewEntry}
- *
- * @param fillData When true, the given status replaces {@link #data}
- * and {@link #footerLine}. Otherwise only {@link #presence}
- * is updated.
- */
- public ViewEntry applyStatus(DataStatus status, boolean fillData) {
- presence = status.getPresence();
- if (fillData && status.isValid()) {
- this.data = status.getStatus().toString();
- this.footerLine = status.getTimestampLabel(context);
- }
-
- return this;
- }
-
- public boolean collapseWith(ViewEntry entry) {
- // assert equal collapse keys
- if (!shouldCollapseWith(entry)) {
- return false;
- }
-
- // Choose the label associated with the highest type precedence.
- if (TypePrecedence.getTypePrecedence(mimetype, type)
- > TypePrecedence.getTypePrecedence(entry.mimetype, entry.type)) {
- type = entry.type;
- label = entry.label;
- }
-
- // Choose the max of the maxLines and maxLabelLines values.
- maxLines = Math.max(maxLines, entry.maxLines);
- maxLabelLines = Math.max(maxLabelLines, entry.maxLabelLines);
-
- // Choose the presence with the highest precedence.
- if (StatusUpdates.getPresencePrecedence(presence)
- < StatusUpdates.getPresencePrecedence(entry.presence)) {
- presence = entry.presence;
- }
-
- // If any of the collapsed entries are primary make the whole thing primary.
- isPrimary = entry.isPrimary ? true : isPrimary;
-
- // uri, and contactdId, shouldn't make a difference. Just keep the original.
-
- // Keep track of all the ids that have been collapsed with this one.
- ids.add(entry.id);
- collapseCount++;
- return true;
- }
-
- public boolean shouldCollapseWith(ViewEntry entry) {
- if (entry == null) {
- return false;
- }
-
- if (!ContactsUtils.shouldCollapse(context, mimetype, data, entry.mimetype,
- entry.data)) {
- return false;
- }
-
- if (!TextUtils.equals(mimetype, entry.mimetype)
- || !ContactsUtils.areIntentActionEqual(intent, entry.intent)
- || !ContactsUtils.areIntentActionEqual(secondaryIntent, entry.secondaryIntent)
- || actionIcon != entry.actionIcon) {
- return false;
- }
-
- return true;
- }
- }
-
- /** Cache of the children views of a row */
- static class ViewCache {
- public TextView label;
- public TextView data;
- public TextView footer;
- public ImageView actionIcon;
- public ImageView presenceIcon;
- public ImageView primaryIcon;
- public ImageView secondaryActionButton;
- public View secondaryActionDivider;
-
- // Need to keep track of this too
- ViewEntry entry;
- }
-
- private final class ViewAdapter extends ContactEntryAdapter<ViewEntry>
- implements View.OnClickListener {
-
-
- ViewAdapter(Context context, ArrayList<ArrayList<ViewEntry>> sections) {
- super(context, sections, SHOW_SEPARATORS);
- }
-
- public void onClick(View v) {
- Intent intent = (Intent) v.getTag();
- startActivity(intent);
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- ViewEntry entry = getEntry(mSections, position, false);
- View v;
-
- ViewCache views;
-
- // Check to see if we can reuse convertView
- if (convertView != null) {
- v = convertView;
- views = (ViewCache) v.getTag();
- } else {
- // Create a new view if needed
- v = mInflater.inflate(R.layout.list_item_text_icons, parent, false);
-
- // Cache the children
- views = new ViewCache();
- views.label = (TextView) v.findViewById(android.R.id.text1);
- views.data = (TextView) v.findViewById(android.R.id.text2);
- views.footer = (TextView) v.findViewById(R.id.footer);
- views.actionIcon = (ImageView) v.findViewById(R.id.action_icon);
- views.primaryIcon = (ImageView) v.findViewById(R.id.primary_icon);
- views.presenceIcon = (ImageView) v.findViewById(R.id.presence_icon);
- views.secondaryActionButton = (ImageView) v.findViewById(
- R.id.secondary_action_button);
- views.secondaryActionButton.setOnClickListener(this);
- views.secondaryActionDivider = v.findViewById(R.id.divider);
- v.setTag(views);
- }
-
- // Update the entry in the view cache
- views.entry = entry;
-
- // Bind the data to the view
- bindView(v, entry);
- return v;
- }
-
- @Override
- protected View newView(int position, ViewGroup parent) {
- // getView() handles this
- throw new UnsupportedOperationException();
- }
-
- @Override
- protected void bindView(View view, ViewEntry entry) {
- final Resources resources = mContext.getResources();
- ViewCache views = (ViewCache) view.getTag();
-
- // Set the label
- TextView label = views.label;
- setMaxLines(label, entry.maxLabelLines);
- label.setText(entry.label);
-
- // Set the data
- TextView data = views.data;
- if (data != null) {
- if (entry.mimetype.equals(Phone.CONTENT_ITEM_TYPE)
- || entry.mimetype.equals(Constants.MIME_SMS_ADDRESS)) {
- data.setText(PhoneNumberUtils.formatNumber(entry.data));
- } else {
- data.setText(entry.data);
- }
- setMaxLines(data, entry.maxLines);
- }
-
- // Set the footer
- if (!TextUtils.isEmpty(entry.footerLine)) {
- views.footer.setText(entry.footerLine);
- views.footer.setVisibility(View.VISIBLE);
- } else {
- views.footer.setVisibility(View.GONE);
- }
-
- // Set the primary icon
- views.primaryIcon.setVisibility(entry.isPrimary ? View.VISIBLE : View.GONE);
-
- // Set the action icon
- ImageView action = views.actionIcon;
- if (entry.actionIcon != -1) {
- Drawable actionIcon;
- if (entry.resPackageName != null) {
- // Load external resources through PackageManager
- actionIcon = mContext.getPackageManager().getDrawable(entry.resPackageName,
- entry.actionIcon, null);
- } else {
- actionIcon = resources.getDrawable(entry.actionIcon);
- }
- action.setImageDrawable(actionIcon);
- action.setVisibility(View.VISIBLE);
- } else {
- // Things should still line up as if there was an icon, so make it invisible
- action.setVisibility(View.INVISIBLE);
- }
-
- // Set the presence icon
- Drawable presenceIcon = ContactPresenceIconUtil.getPresenceIcon(
- mContext, entry.presence);
- ImageView presenceIconView = views.presenceIcon;
- if (presenceIcon != null) {
- presenceIconView.setImageDrawable(presenceIcon);
- presenceIconView.setVisibility(View.VISIBLE);
- } else {
- presenceIconView.setVisibility(View.GONE);
- }
-
- // Set the secondary action button
- ImageView secondaryActionView = views.secondaryActionButton;
- Drawable secondaryActionIcon = null;
- if (entry.secondaryActionIcon != -1) {
- secondaryActionIcon = resources.getDrawable(entry.secondaryActionIcon);
- }
- if (entry.secondaryIntent != null && secondaryActionIcon != null) {
- secondaryActionView.setImageDrawable(secondaryActionIcon);
- secondaryActionView.setTag(entry.secondaryIntent);
- secondaryActionView.setVisibility(View.VISIBLE);
- views.secondaryActionDivider.setVisibility(View.VISIBLE);
- } else {
- secondaryActionView.setVisibility(View.GONE);
- views.secondaryActionDivider.setVisibility(View.GONE);
- }
- }
-
- private void setMaxLines(TextView textView, int maxLines) {
- if (maxLines == 1) {
- textView.setSingleLine(true);
- textView.setEllipsize(TextUtils.TruncateAt.END);
- } else {
- textView.setSingleLine(false);
- textView.setMaxLines(maxLines);
- textView.setEllipsize(null);
- }
- }
- }
-
- private interface StatusQuery {
- final String[] PROJECTION = new String[] {
- Data._ID,
- Data.STATUS,
- Data.STATUS_RES_PACKAGE,
- Data.STATUS_ICON,
- Data.STATUS_LABEL,
- Data.STATUS_TIMESTAMP,
- Data.PRESENCE,
- };
-
- final int _ID = 0;
- }
-
- @Override
- public void startSearch(String initialQuery, boolean selectInitialQuery, Bundle appSearchData,
- boolean globalSearch) {
- if (globalSearch) {
- super.startSearch(initialQuery, selectInitialQuery, appSearchData, globalSearch);
- } else {
- ContactsSearchManager.startSearch(this, initialQuery);
- }
- }
-}
diff --git a/src/com/android/contacts/activities/ActionBarAdapter.java b/src/com/android/contacts/activities/ActionBarAdapter.java
new file mode 100644
index 0000000..3dfbfa5
--- /dev/null
+++ b/src/com/android/contacts/activities/ActionBarAdapter.java
@@ -0,0 +1,255 @@
+/*
+ * 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.activities;
+
+import com.android.contacts.R;
+import com.android.contacts.list.ContactsRequest;
+import com.android.contacts.widget.SearchEditText;
+import com.android.contacts.widget.SearchEditText.OnFilterTextListener;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.ImageView;
+import android.widget.ToggleButton;
+
+import java.util.HashMap;
+
+/**
+ * Adapter for the action bar at the top of the Contacts activity.
+ */
+public class ActionBarAdapter implements OnFilterTextListener, OnClickListener {
+
+ public interface Listener {
+ void onAction();
+ }
+
+ private static final String EXTRA_KEY_DEFAULT_MODE = "navBar.defaultMode";
+ private static final String EXTRA_KEY_MODE = "navBar.mode";
+ private static final String EXTRA_KEY_QUERY = "navBar.query";
+
+ private static final String KEY_MODE_CONTACTS = "mode_contacts";
+ private static final String KEY_MODE_FAVORITES = "mode_favorites";
+ private static final String KEY_MODE_SEARCH = "mode_search";
+
+ private int mMode = ContactBrowserMode.MODE_CONTACTS;
+ private int mDefaultMode = ContactBrowserMode.MODE_CONTACTS;
+ private String mQueryString;
+ private HashMap<Integer, Bundle> mSavedStateByMode = new HashMap<Integer, Bundle>();
+
+
+ private SearchEditText mSearchEditText;
+ private View mNavigationBar;
+
+ private final Context mContext;
+
+ private Listener mListener;
+
+ private ToggleButton mContactsButton;
+ private ToggleButton mFavoritesButton;
+ private ToggleButton mSearchButton;
+ private ImageView mCancelSearchButton;
+
+ public ActionBarAdapter(Context context) {
+ mContext = context;
+ }
+
+ public void onCreate(Bundle savedState, ContactsRequest request) {
+ mDefaultMode = -1;
+ mMode = -1;
+ mQueryString = null;
+ if (savedState != null) {
+ mDefaultMode = savedState.getInt(EXTRA_KEY_DEFAULT_MODE, -1);
+ mMode = savedState.getInt(EXTRA_KEY_MODE, -1);
+ mQueryString = savedState.getString(EXTRA_KEY_QUERY);
+ restoreSavedState(savedState, ContactBrowserMode.MODE_CONTACTS, KEY_MODE_CONTACTS);
+ restoreSavedState(savedState, ContactBrowserMode.MODE_FAVORITES, KEY_MODE_FAVORITES);
+ restoreSavedState(savedState, ContactBrowserMode.MODE_SEARCH, KEY_MODE_SEARCH);
+ }
+
+ int actionCode = request.getActionCode();
+ if (mDefaultMode == -1) {
+ mDefaultMode = actionCode == ContactsRequest.ACTION_DEFAULT
+ ? ContactBrowserMode.MODE_CONTACTS
+ : ContactBrowserMode.MODE_FAVORITES;
+ }
+ if (mMode == -1) {
+ mMode = request.isSearchMode() ? ContactBrowserMode.MODE_SEARCH : mDefaultMode;
+ }
+ if (mQueryString == null) {
+ mQueryString = request.getQueryString();
+ }
+ }
+
+ public void setListener(Listener listener) {
+ mListener = listener;
+ }
+
+ public View onCreateView(LayoutInflater inflater) {
+ mNavigationBar = inflater.inflate(R.layout.navigation_bar, null);
+ mSearchEditText = (SearchEditText)mNavigationBar.findViewById(R.id.search_src_text);
+ mSearchEditText.setMaginfyingGlassEnabled(false);
+ mSearchEditText.setOnFilterTextListener(this);
+ mSearchEditText.setText(mQueryString);
+ mContactsButton = (ToggleButton)mNavigationBar.findViewById(R.id.nav_contacts);
+ mContactsButton.setOnClickListener(this);
+ mFavoritesButton = (ToggleButton)mNavigationBar.findViewById(R.id.nav_favorites);
+ mFavoritesButton.setOnClickListener(this);
+ mSearchButton = (ToggleButton)mNavigationBar.findViewById(R.id.nav_search);
+ mSearchButton.setOnClickListener(this);
+ mCancelSearchButton = (ImageView)mNavigationBar.findViewById(R.id.nav_cancel_search);
+ mCancelSearchButton.setOnClickListener(this);
+ update();
+
+ return mNavigationBar;
+ }
+
+ public int getMode() {
+ return mMode;
+ }
+
+ public void setMode(int mode) {
+ mMode = mode;
+ update();
+ if (mListener != null) {
+ mListener.onAction();
+ }
+ }
+
+ public int getDefaultMode() {
+ return mDefaultMode;
+ }
+
+ public void setDefaultMode(int defaultMode) {
+ mDefaultMode = defaultMode;
+ }
+
+ public String getQueryString() {
+ return mQueryString;
+ }
+
+ public void setQueryString(String query) {
+ mQueryString = query;
+ mSearchEditText.setText(query);
+ }
+
+ public void update() {
+ switch(mMode) {
+ case ContactBrowserMode.MODE_CONTACTS:
+ mContactsButton.setChecked(true);
+ mFavoritesButton.setChecked(false);
+ mSearchButton.setChecked(false);
+ mSearchButton.setVisibility(View.VISIBLE);
+ mSearchEditText.setVisibility(View.GONE);
+ mCancelSearchButton.setVisibility(View.GONE);
+ break;
+ case ContactBrowserMode.MODE_FAVORITES:
+ mContactsButton.setChecked(false);
+ mFavoritesButton.setChecked(true);
+ mSearchButton.setChecked(false);
+ mSearchButton.setVisibility(View.VISIBLE);
+ mSearchEditText.setVisibility(View.GONE);
+ mCancelSearchButton.setVisibility(View.GONE);
+ break;
+ case ContactBrowserMode.MODE_SEARCH:
+ mContactsButton.setChecked(false);
+ mFavoritesButton.setChecked(false);
+ mSearchButton.setVisibility(View.GONE);
+ mSearchEditText.setVisibility(View.VISIBLE);
+ mSearchEditText.requestFocus();
+ InputMethodManager inputMethodManager =
+ (InputMethodManager)mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
+ inputMethodManager.showSoftInput(mSearchEditText, 0);
+ mCancelSearchButton.setVisibility(View.VISIBLE);
+ break;
+ }
+ }
+
+ public void toggleSearchMode() {
+ setMode(mMode == ContactBrowserMode.MODE_SEARCH
+ ? mDefaultMode
+ : ContactBrowserMode.MODE_SEARCH);
+ }
+
+ @Override
+ public void onClick(View view) {
+ if (view == mSearchButton) {
+ setMode(ContactBrowserMode.MODE_SEARCH);
+ } else if (view == mContactsButton) {
+ setMode(ContactBrowserMode.MODE_CONTACTS);
+ setDefaultMode(ContactBrowserMode.MODE_CONTACTS);
+ } else if (view == mFavoritesButton) {
+ setMode(ContactBrowserMode.MODE_FAVORITES);
+ setDefaultMode(ContactBrowserMode.MODE_FAVORITES);
+ } else { // mCancelSearchButton
+ setMode(mDefaultMode);
+ }
+ }
+
+ @Override
+ public void onFilterChange(String queryString) {
+ mQueryString = queryString;
+ if (mListener != null) {
+ mListener.onAction();
+ }
+ }
+
+ @Override
+ public void onCancelSearch() {
+ setMode(mDefaultMode);
+ }
+
+ public void saveStateForMode(int mode, Bundle state) {
+ mSavedStateByMode.put(mode, state);
+ }
+
+ public Bundle getSavedStateForMode(int mode) {
+ return mSavedStateByMode.get(mode);
+ }
+
+ public void clearSavedState(int mode) {
+ mSavedStateByMode.remove(mode);
+ }
+
+ public void onSaveInstanceState(Bundle outState) {
+ outState.putInt(EXTRA_KEY_DEFAULT_MODE, mDefaultMode);
+ outState.putInt(EXTRA_KEY_MODE, mMode);
+ outState.putString(EXTRA_KEY_QUERY, mQueryString);
+ saveInstanceState(outState, ContactBrowserMode.MODE_CONTACTS, KEY_MODE_CONTACTS);
+ saveInstanceState(outState, ContactBrowserMode.MODE_FAVORITES, KEY_MODE_FAVORITES);
+ saveInstanceState(outState, ContactBrowserMode.MODE_SEARCH, KEY_MODE_SEARCH);
+ }
+
+ private void saveInstanceState(Bundle outState, int mode, String key) {
+ Bundle state = mSavedStateByMode.get(mode);
+ if (state != null) {
+ outState.putParcelable(key, state);
+ }
+ }
+
+ private void restoreSavedState(Bundle savedState, int mode, String key) {
+ Bundle bundle = savedState.getParcelable(key);
+ if (bundle == null) {
+ mSavedStateByMode.remove(mode);
+ } else {
+ mSavedStateByMode.put(mode, bundle);
+ }
+ }
+}
diff --git a/src/com/android/contacts/activities/ContactBrowserActivity.java b/src/com/android/contacts/activities/ContactBrowserActivity.java
new file mode 100644
index 0000000..09009b4
--- /dev/null
+++ b/src/com/android/contacts/activities/ContactBrowserActivity.java
@@ -0,0 +1,834 @@
+/*
+ * 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.activities;
+
+import com.android.contacts.R;
+import com.android.contacts.interactions.ContactDeletionInteraction;
+import com.android.contacts.interactions.ImportExportInteraction;
+import com.android.contacts.interactions.PhoneNumberInteraction;
+import com.android.contacts.list.ContactBrowseListContextMenuAdapter;
+import com.android.contacts.list.ContactBrowseListFragment;
+import com.android.contacts.list.ContactEntryListFragment;
+import com.android.contacts.list.ContactsIntentResolver;
+import com.android.contacts.list.ContactsRequest;
+import com.android.contacts.list.DefaultContactBrowseListFragment;
+import com.android.contacts.list.OnContactBrowserActionListener;
+import com.android.contacts.list.StrequentContactListFragment;
+import com.android.contacts.ui.ContactsPreferencesActivity;
+import com.android.contacts.util.DialogManager;
+import com.android.contacts.views.detail.ContactDetailFragment;
+import com.android.contacts.views.detail.ContactNoneFragment;
+import com.android.contacts.views.editor.ContactEditorFragment;
+import com.android.contacts.widget.ContextMenuAdapter;
+
+import android.app.ActionBar;
+import android.app.Activity;
+import android.app.Dialog;
+import android.app.Fragment;
+import android.content.ActivityNotFoundException;
+import android.content.ContentValues;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.Contacts;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.Window;
+import android.widget.Toast;
+
+/**
+ * Displays a list to browse contacts. For xlarge screens, this also displays a detail-pane on
+ * the right
+ */
+public class ContactBrowserActivity extends Activity
+ implements View.OnCreateContextMenuListener, ActionBarAdapter.Listener,
+ DialogManager.DialogShowingViewActivity {
+
+ private static final String TAG = "ContactBrowserActivity";
+
+ private static final String KEY_MODE = "mode";
+
+ private static final int SUBACTIVITY_NEW_CONTACT = 2;
+ private static final int SUBACTIVITY_DISPLAY_GROUP = 3;
+
+ private DialogManager mDialogManager = new DialogManager(this);
+
+ private ContactsIntentResolver mIntentResolver;
+ private ContactsRequest mRequest;
+
+ private boolean mHasActionBar;
+ private ActionBarAdapter mActionBarAdapter;
+
+ /**
+ * Contact browser mode, see {@link ContactBrowserMode}.
+ */
+ private int mMode = -1;
+
+ private ContactBrowseListFragment mListFragment;
+ private ContactNoneFragment mEmptyFragment;
+
+ private boolean mContactContentDisplayed;
+ private ContactDetailFragment mDetailFragment;
+ private DetailFragmentListener mDetailFragmentListener = new DetailFragmentListener();
+
+ private ContactEditorFragment mEditorFragment;
+ private EditorFragmentListener mEditorFragmentListener = new EditorFragmentListener();
+
+ private PhoneNumberInteraction mPhoneNumberCallInteraction;
+ private PhoneNumberInteraction mSendTextMessageInteraction;
+ private ContactDeletionInteraction mContactDeletionInteraction;
+ private ImportExportInteraction mImportExportInteraction;
+
+ private boolean mSearchInitiated;
+
+ public ContactBrowserActivity() {
+ mIntentResolver = new ContactsIntentResolver(this);
+ }
+
+ @Override
+ public void onAttachFragment(Fragment fragment) {
+ if (fragment instanceof ContactBrowseListFragment) {
+ mListFragment = (ContactBrowseListFragment)fragment;
+ mListFragment.setOnContactListActionListener(new ContactBrowserActionListener());
+ } else if (fragment instanceof ContactNoneFragment) {
+ mEmptyFragment = (ContactNoneFragment)fragment;
+ } else if (fragment instanceof ContactDetailFragment) {
+ mDetailFragment = (ContactDetailFragment)fragment;
+ mDetailFragment.setListener(mDetailFragmentListener);
+ } else if (fragment instanceof ContactEditorFragment) {
+ mEditorFragment = (ContactEditorFragment)fragment;
+ mEditorFragment.setListener(mEditorFragmentListener);
+ }
+ }
+
+ @Override
+ protected void onCreate(Bundle savedState) {
+ super.onCreate(savedState);
+
+ if (savedState != null) {
+ mMode = savedState.getInt(KEY_MODE);
+ }
+
+ // Extract relevant information from the intent
+ mRequest = mIntentResolver.resolveIntent(getIntent());
+ if (!mRequest.isValid()) {
+ setResult(RESULT_CANCELED);
+ finish();
+ return;
+ }
+
+ Intent redirect = mRequest.getRedirectIntent();
+ if (redirect != null) {
+ // Need to start a different activity
+ startActivity(redirect);
+ finish();
+ return;
+ }
+
+ setTitle(mRequest.getActivityTitle());
+ setContentView(R.layout.contact_browser);
+
+ mHasActionBar = getWindow().hasFeature(Window.FEATURE_ACTION_BAR);
+ mContactContentDisplayed = findViewById(R.id.detail_container) != null;
+
+ if (mHasActionBar) {
+ mActionBarAdapter = new ActionBarAdapter(this);
+ mActionBarAdapter.onCreate(savedState, mRequest);
+ mActionBarAdapter.setListener(this);
+
+ ActionBar actionBar = getActionBar();
+ View navBarView = mActionBarAdapter.onCreateView(getLayoutInflater());
+ actionBar.setCustomNavigationMode(navBarView);
+ }
+
+ configureListFragment();
+
+ if (mContactContentDisplayed) {
+ setupContactDetailFragment(mListFragment.getSelectedContactUri());
+ }
+ }
+
+ private void configureListFragment() {
+ int mode = -1;
+ if (mHasActionBar) {
+ mode = mActionBarAdapter.getMode();
+ if (mode == ContactBrowserMode.MODE_SEARCH
+ && TextUtils.isEmpty(mActionBarAdapter.getQueryString())) {
+ mode = mActionBarAdapter.getDefaultMode();
+ }
+ } else {
+ int actionCode = mRequest.getActionCode();
+ if (actionCode == ContactsRequest.ACTION_FREQUENT ||
+ actionCode == ContactsRequest.ACTION_STARRED ||
+ actionCode == ContactsRequest.ACTION_STREQUENT) {
+ mode = ContactBrowserMode.MODE_FAVORITES;
+ } else {
+ mode = ContactBrowserMode.MODE_CONTACTS;
+ }
+ }
+
+ boolean replaceList = (mode != mMode);
+ if (replaceList) {
+ closeListFragment();
+ mMode = mode;
+ switch (mMode) {
+ case ContactBrowserMode.MODE_CONTACTS: {
+ mListFragment = createListFragment(ContactsRequest.ACTION_DEFAULT);
+ break;
+ }
+ case ContactBrowserMode.MODE_FAVORITES: {
+ int favoritesAction = mRequest.getActionCode();
+ if (favoritesAction == ContactsRequest.ACTION_DEFAULT) {
+ favoritesAction = ContactsRequest.ACTION_STREQUENT;
+ }
+ mListFragment = createListFragment(favoritesAction);
+ break;
+ }
+ case ContactBrowserMode.MODE_SEARCH: {
+ mListFragment = createContactSearchFragment();
+ break;
+ }
+ }
+ }
+
+ if (mHasActionBar) {
+ Bundle savedStateForMode = mActionBarAdapter.getSavedStateForMode(mMode);
+ if (savedStateForMode != null) {
+ mListFragment.restoreSavedState(savedStateForMode);
+ mActionBarAdapter.clearSavedState(mMode);
+ }
+ if (mMode == ContactBrowserMode.MODE_SEARCH) {
+ mListFragment.setQueryString(mActionBarAdapter.getQueryString());
+ }
+ }
+
+ if (replaceList) {
+ getFragmentManager().openTransaction()
+ .replace(R.id.list_container, mListFragment)
+ .commit();
+ }
+ }
+
+ private void closeListFragment() {
+ if (mListFragment != null) {
+ mListFragment.setOnContactListActionListener(null);
+
+ if (mHasActionBar) {
+ Bundle state = new Bundle();
+ mListFragment.onSaveInstanceState(state);
+ mActionBarAdapter.saveStateForMode(mMode, state);
+ }
+
+ mListFragment = null;
+ }
+ }
+
+ private void setupContactDetailFragment(Uri contactLookupUri) {
+
+ // If we are already editing this URI - just continue editing
+ if (mEditorFragment != null && contactLookupUri != null
+ && contactLookupUri.equals(mEditorFragment.getLookupUri())) {
+ return;
+ }
+
+ if (mDetailFragment != null && contactLookupUri != null
+ && contactLookupUri.equals(mDetailFragment.getUri())) {
+ return;
+ }
+
+ // No editor here
+ closeEditorFragment(true);
+
+ if (contactLookupUri != null) {
+ // Already showing? Nothing to do
+ if (mDetailFragment != null) {
+ mDetailFragment.loadUri(contactLookupUri);
+ return;
+ }
+
+ closeEmptyFragment();
+
+ mDetailFragment = new ContactDetailFragment();
+ mDetailFragment.loadUri(contactLookupUri);
+
+ // Nothing showing yet? Create (this happens during Activity-Startup)
+ getFragmentManager().openTransaction()
+ .replace(R.id.detail_container, mDetailFragment)
+ .commit();
+ } else {
+ closeDetailFragment();
+
+ mEmptyFragment = new ContactNoneFragment();
+ getFragmentManager().openTransaction()
+ .replace(R.id.detail_container, mEmptyFragment)
+ .commit();
+ }
+ }
+
+ private void setupContactEditorFragment(Uri contactLookupUri) {
+ // No detail view here
+ closeDetailFragment();
+ closeEmptyFragment();
+
+ // Already showing? Nothing to do
+ if (mEditorFragment != null) return;
+
+ mEditorFragment = new ContactEditorFragment();
+ mEditorFragment.load(Intent.ACTION_EDIT, contactLookupUri,
+ Contacts.CONTENT_ITEM_TYPE, new Bundle());
+
+ // Nothing showing yet? Create (this happens during Activity-Startup)
+ getFragmentManager().openTransaction()
+ .replace(R.id.detail_container, mEditorFragment)
+ .commit();
+ }
+
+ private void closeDetailFragment() {
+ if (mDetailFragment != null) {
+ mDetailFragment.setListener(null);
+ mDetailFragment = null;
+ }
+ }
+
+ /**
+ * Closes the editor, if it is currently open
+ * @param save Whether the changes should be saved. This should always be true, unless
+ * this is called from a Revert/Undo button
+ */
+ private void closeEditorFragment(boolean save) {
+ Log.d(TAG, "closeEditorFragment(" + save + ")");
+
+ if (mEditorFragment != null) {
+ // Remove the listener before saving, because it will be used to forward the onClose
+ // after save
+ mEditorFragment.setListener(null);
+ if (save) mEditorFragment.save(true);
+ mEditorFragment = null;
+ }
+ }
+
+ private void closeEmptyFragment() {
+ mEmptyFragment = null;
+ }
+
+ /**
+ * Handler for action bar actions.
+ */
+ @Override
+ public void onAction() {
+ configureListFragment();
+ setupContactDetailFragment(mListFragment.getSelectedContactUri());
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ Log.d(TAG, "onStop");
+
+ // If anything was left unsaved, save it now but keep the editor open.
+ // Don't do this if we are only rotating the screen
+ if (mEditorFragment != null && !isChangingConfigurations()) {
+ mEditorFragment.save(false);
+ }
+ }
+
+ /**
+ * Creates the list fragment for the specified mode.
+ */
+ private ContactBrowseListFragment createListFragment(int actionCode) {
+ switch (actionCode) {
+ case ContactsRequest.ACTION_DEFAULT: {
+ DefaultContactBrowseListFragment fragment = new DefaultContactBrowseListFragment();
+ fragment.setContactsRequest(mRequest);
+ fragment.setOnContactListActionListener(new ContactBrowserActionListener());
+ fragment.setDisplayWithPhonesOnlyOption(mRequest.getDisplayWithPhonesOnlyOption());
+ fragment.setVisibleContactsRestrictionEnabled(mRequest.getDisplayOnlyVisible());
+ fragment.setContextMenuAdapter(new ContactBrowseListContextMenuAdapter(fragment));
+ fragment.setSearchMode(mRequest.isSearchMode());
+ fragment.setQueryString(mRequest.getQueryString());
+ fragment.setDirectorySearchEnabled(
+ mRequest.isSearchMode() && mRequest.isDirectorySearchEnabled());
+ fragment.setAizyEnabled(!mRequest.isSearchMode());
+ fragment.setSelectionVisible(mContactContentDisplayed);
+ return fragment;
+ }
+
+ case ContactsRequest.ACTION_GROUP: {
+ throw new UnsupportedOperationException("Not yet implemented");
+ }
+
+ case ContactsRequest.ACTION_STARRED: {
+ StrequentContactListFragment fragment = new StrequentContactListFragment();
+ fragment.setOnContactListActionListener(new ContactBrowserActionListener());
+ fragment.setFrequentlyContactedContactsIncluded(false);
+ fragment.setStarredContactsIncluded(true);
+ fragment.setSelectionVisible(mContactContentDisplayed);
+ return fragment;
+ }
+
+ case ContactsRequest.ACTION_FREQUENT: {
+ StrequentContactListFragment fragment = new StrequentContactListFragment();
+ fragment.setOnContactListActionListener(new ContactBrowserActionListener());
+ fragment.setFrequentlyContactedContactsIncluded(true);
+ fragment.setStarredContactsIncluded(false);
+ fragment.setSelectionVisible(mContactContentDisplayed);
+ return fragment;
+ }
+
+ case ContactsRequest.ACTION_STREQUENT: {
+ StrequentContactListFragment fragment = new StrequentContactListFragment();
+ fragment.setOnContactListActionListener(new ContactBrowserActionListener());
+ fragment.setFrequentlyContactedContactsIncluded(true);
+ fragment.setStarredContactsIncluded(true);
+ fragment.setSelectionVisible(mContactContentDisplayed);
+ return fragment;
+ }
+
+ default:
+ throw new IllegalStateException("Invalid action code: " + actionCode);
+ }
+ }
+
+ private ContactBrowseListFragment createContactSearchFragment() {
+ DefaultContactBrowseListFragment fragment = new DefaultContactBrowseListFragment();
+ fragment.setOnContactListActionListener(new ContactBrowserActionListener());
+ fragment.setDisplayWithPhonesOnlyOption(ContactsRequest.DISPLAY_ONLY_WITH_PHONES_DISABLED);
+ fragment.setVisibleContactsRestrictionEnabled(true);
+ fragment.setContextMenuAdapter(new ContactBrowseListContextMenuAdapter(fragment));
+ fragment.setSearchMode(true);
+ fragment.setDirectorySearchEnabled(true);
+ fragment.setAizyEnabled(false);
+ fragment.setSelectionVisible(true);
+ return fragment;
+ }
+
+ private final class ContactBrowserActionListener implements OnContactBrowserActionListener {
+ public void onViewContactAction(Uri contactLookupUri, boolean force) {
+ if (mContactContentDisplayed) {
+ if (force) closeEditorFragment(true);
+ mListFragment.setSelectedContactUri(contactLookupUri);
+ setupContactDetailFragment(contactLookupUri);
+ } else {
+ startActivity(new Intent(Intent.ACTION_VIEW, contactLookupUri));
+ }
+ }
+
+ public void onCreateNewContactAction() {
+ Intent intent = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI);
+ Bundle extras = getIntent().getExtras();
+ if (extras != null) {
+ intent.putExtras(extras);
+ }
+ startActivity(intent);
+ }
+
+ public void onEditContactAction(Uri contactLookupUri) {
+ if (mContactContentDisplayed) {
+ closeEditorFragment(true);
+ mListFragment.setSelectedContactUri(contactLookupUri);
+ setupContactEditorFragment(contactLookupUri);
+ } else {
+ Intent intent = new Intent(Intent.ACTION_EDIT, contactLookupUri);
+ Bundle extras = getIntent().getExtras();
+ if (extras != null) {
+ intent.putExtras(extras);
+ }
+ startActivity(intent);
+ }
+ }
+
+ public void onAddToFavoritesAction(Uri contactUri) {
+ ContentValues values = new ContentValues(1);
+ values.put(Contacts.STARRED, 1);
+ getContentResolver().update(contactUri, values, null, null);
+ }
+
+ public void onRemoveFromFavoritesAction(Uri contactUri) {
+ ContentValues values = new ContentValues(1);
+ values.put(Contacts.STARRED, 0);
+ getContentResolver().update(contactUri, values, null, null);
+ }
+
+ public void onCallContactAction(Uri contactUri) {
+ getPhoneNumberCallInteraction().startInteraction(contactUri);
+ }
+
+ public void onSmsContactAction(Uri contactUri) {
+ getSendTextMessageInteraction().startInteraction(contactUri);
+ }
+
+ public void onDeleteContactAction(Uri contactUri) {
+ getContactDeletionInteraction().deleteContact(contactUri);
+ }
+
+ public void onFinishAction() {
+ onBackPressed();
+ }
+ }
+
+ private class DetailFragmentListener implements ContactDetailFragment.Listener {
+ @Override
+ public void onContactNotFound() {
+ setupContactDetailFragment(null);
+ }
+
+ @Override
+ public void onEditRequested(Uri contactLookupUri) {
+ setupContactEditorFragment(contactLookupUri);
+ }
+
+ @Override
+ public void onItemClicked(Intent intent) {
+ try {
+ startActivity(intent);
+ } catch (ActivityNotFoundException e) {
+ Log.e(TAG, "No activity found for intent: " + intent);
+ }
+ }
+
+ @Override
+ public void onDeleteRequested(Uri contactLookupUri) {
+ getContactDeletionInteraction().deleteContact(contactLookupUri);
+ }
+ }
+
+ private class EditorFragmentListener implements ContactEditorFragment.Listener {
+ @Override
+ public void onReverted() {
+ final Uri uri = mEditorFragment.getLookupUri();
+ closeEditorFragment(false);
+ setupContactDetailFragment(uri);
+ }
+
+ @Override
+ public void onSaveFinished(int resultCode, Intent resultIntent) {
+ Log.d(TAG, "onSaveFinished(" + resultCode + "," + resultIntent + ")");
+ // it is already saved, so no need to save again here
+ final Uri uri = mEditorFragment.getLookupUri();
+ closeEditorFragment(false);
+ setupContactDetailFragment(uri);
+ }
+
+ @Override
+ public void onAggregationChangeFinished(Uri newLookupUri) {
+ // We have already saved. Close the editor so that we can open again with the
+ // new contact
+ Log.d(TAG, "onAggregationChangeFinished(" + newLookupUri + ")");
+ closeEditorFragment(false);
+ mListFragment.setSelectedContactUri(newLookupUri);
+ setupContactDetailFragment(newLookupUri);
+ }
+
+ @Override
+ public void onAccountSelectorAborted() {
+ Toast.makeText(ContactBrowserActivity.this, "closeBecauseAccountSelectorAborted",
+ Toast.LENGTH_LONG).show();
+ }
+
+ @Override
+ public void onContactNotFound() {
+ setupContactDetailFragment(null);
+ }
+
+ @Override
+ public void setTitleTo(int resourceId) {
+ }
+
+ @Override
+ public void onDeleteRequested(Uri contactLookupUri) {
+ getContactDeletionInteraction().deleteContact(contactLookupUri);
+ }
+ }
+
+ public void startActivityAndForwardResult(final Intent intent) {
+ intent.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
+
+ // Forward extras to the new activity
+ Bundle extras = getIntent().getExtras();
+ if (extras != null) {
+ intent.putExtras(extras);
+ }
+ startActivity(intent);
+ finish();
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ super.onCreateOptionsMenu(menu);
+
+ MenuInflater inflater = getMenuInflater();
+ if (mHasActionBar) {
+ inflater.inflate(R.menu.actions, menu);
+ return true;
+ } else if (mRequest.getActionCode() == ContactsRequest.ACTION_DEFAULT ||
+ mRequest.getActionCode() == ContactsRequest.ACTION_STREQUENT) {
+ inflater.inflate(R.menu.list, menu);
+ return true;
+ } else if (!mListFragment.isSearchMode()) {
+ inflater.inflate(R.menu.search, menu);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ MenuItem displayGroups = menu.findItem(R.id.menu_display_groups);
+ if (displayGroups != null) {
+ displayGroups.setVisible(
+ mRequest.getActionCode() == ContactsRequest.ACTION_DEFAULT);
+ }
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.menu_display_groups: {
+ final Intent intent = new Intent(this, ContactsPreferencesActivity.class);
+ startActivityForResult(intent, SUBACTIVITY_DISPLAY_GROUP);
+ return true;
+ }
+ case R.id.menu_search: {
+ onSearchRequested();
+ return true;
+ }
+ case R.id.menu_add: {
+ final Intent intent = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI);
+ startActivityForResult(intent, SUBACTIVITY_NEW_CONTACT);
+ return true;
+ }
+ case R.id.menu_import_export: {
+ getImportExportInteraction().startInteraction();
+ return true;
+ }
+ case R.id.menu_accounts: {
+ final Intent intent = new Intent(Settings.ACTION_SYNC_SETTINGS);
+ intent.putExtra(Settings.EXTRA_AUTHORITIES, new String[] {
+ ContactsContract.AUTHORITY
+ });
+ startActivity(intent);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void startSearch(String initialQuery, boolean selectInitialQuery, Bundle appSearchData,
+ boolean globalSearch) {
+// TODO
+// if (mProviderStatus != ProviderStatus.STATUS_NORMAL) {
+// return;
+// }
+
+ if (globalSearch) {
+ super.startSearch(initialQuery, selectInitialQuery, appSearchData, globalSearch);
+ } else {
+ mListFragment.startSearch(initialQuery);
+ }
+ }
+
+ @Override
+ protected Dialog onCreateDialog(int id, Bundle bundle) {
+ if (DialogManager.isManagedId(id)) return mDialogManager.onCreateDialog(id, bundle);
+
+ Dialog dialog = getContactDeletionInteraction().onCreateDialog(id, bundle);
+ if (dialog != null) return dialog;
+
+ dialog = getPhoneNumberCallInteraction().onCreateDialog(id, bundle);
+ if (dialog != null) return dialog;
+
+ dialog = getSendTextMessageInteraction().onCreateDialog(id, bundle);
+ if (dialog != null) return dialog;
+
+ dialog = getImportExportInteraction().onCreateDialog(id, bundle);
+ if (dialog != null) return dialog;
+
+ return super.onCreateDialog(id, bundle);
+ }
+
+ @Override
+ protected void onPrepareDialog(int id, Dialog dialog, Bundle bundle) {
+ if (getContactDeletionInteraction().onPrepareDialog(id, dialog, bundle)) {
+ return;
+ }
+
+ if (getPhoneNumberCallInteraction().onPrepareDialog(id, dialog, bundle)) {
+ return;
+ }
+
+ if (getSendTextMessageInteraction().onPrepareDialog(id, dialog, bundle)) {
+ return;
+ }
+
+ super.onPrepareDialog(id, dialog, bundle);
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ switch (requestCode) {
+ case SUBACTIVITY_NEW_CONTACT: {
+ if (resultCode == RESULT_OK && mContactContentDisplayed) {
+ final Uri newContactUri = data.getData();
+ mListFragment.setSelectedContactUri(newContactUri);
+ setupContactDetailFragment(newContactUri);
+ }
+ break;
+ }
+
+ case SUBACTIVITY_DISPLAY_GROUP:
+ // TODO: Force the ListFragment to reload its setting and update (don't lookup
+ // directories here)
+ break;
+
+ // TODO: Using the new startActivityWithResultFromFragment API this should not be needed
+ // anymore
+ case ContactEntryListFragment.ACTIVITY_REQUEST_CODE_PICKER:
+ if (resultCode == RESULT_OK) {
+ mListFragment.onPickerResult(data);
+ }
+
+// TODO fix or remove multipicker code
+// else if (resultCode == RESULT_CANCELED && mMode == MODE_PICK_MULTIPLE_PHONES) {
+// // Finish the activity if the sub activity was canceled as back key is used
+// // to confirm user selection in MODE_PICK_MULTIPLE_PHONES.
+// finish();
+// }
+// break;
+ }
+ }
+
+ @Override
+ public boolean onContextItemSelected(MenuItem item) {
+ ContextMenuAdapter menuAdapter = mListFragment.getContextMenuAdapter();
+ if (menuAdapter != null) {
+ return menuAdapter.onContextItemSelected(item);
+ }
+
+ return super.onContextItemSelected(item);
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ // TODO move to the fragment
+ switch (keyCode) {
+// case KeyEvent.KEYCODE_CALL: {
+// if (callSelection()) {
+// return true;
+// }
+// break;
+// }
+
+ case KeyEvent.KEYCODE_DEL: {
+ if (deleteSelection()) {
+ return true;
+ }
+ break;
+ }
+ default: {
+ // Bring up the search UI if the user starts typing
+ final int unicodeChar = event.getUnicodeChar();
+
+ if (unicodeChar != 0) {
+ String query = new String(new int[]{ unicodeChar }, 0, 1);
+ if (mHasActionBar) {
+ if (mActionBarAdapter.getMode() != ContactBrowserMode.MODE_SEARCH) {
+ mActionBarAdapter.setQueryString(query);
+ mActionBarAdapter.setMode(ContactBrowserMode.MODE_SEARCH);
+ return true;
+ }
+ } else if (!mRequest.isSearchMode()) {
+ if (!mSearchInitiated) {
+ mSearchInitiated = true;
+ startSearch(query, false, null, false);
+ return true;
+ }
+ }
+ }
+ }
+ }
+
+ return super.onKeyDown(keyCode, event);
+ }
+
+ private boolean deleteSelection() {
+ // TODO move to the fragment
+// if (mActionCode == ContactsRequest.ACTION_DEFAULT) {
+// final int position = mListView.getSelectedItemPosition();
+// if (position != ListView.INVALID_POSITION) {
+// Uri contactUri = getContactUri(position);
+// if (contactUri != null) {
+// doContactDelete(contactUri);
+// return true;
+// }
+// }
+// }
+ return false;
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putInt(KEY_MODE, mMode);
+ if (mActionBarAdapter != null) {
+ mActionBarAdapter.onSaveInstanceState(outState);
+ }
+ }
+
+ private PhoneNumberInteraction getPhoneNumberCallInteraction() {
+ if (mPhoneNumberCallInteraction == null) {
+ mPhoneNumberCallInteraction = new PhoneNumberInteraction(this, false, null);
+ }
+ return mPhoneNumberCallInteraction;
+ }
+
+ private PhoneNumberInteraction getSendTextMessageInteraction() {
+ if (mSendTextMessageInteraction == null) {
+ mSendTextMessageInteraction = new PhoneNumberInteraction(this, true, null);
+ }
+ return mSendTextMessageInteraction;
+ }
+
+ private ContactDeletionInteraction getContactDeletionInteraction() {
+ if (mContactDeletionInteraction == null) {
+ mContactDeletionInteraction = new ContactDeletionInteraction();
+ mContactDeletionInteraction.attachToActivity(this);
+ }
+ return mContactDeletionInteraction;
+ }
+
+ private ImportExportInteraction getImportExportInteraction() {
+ if (mImportExportInteraction == null) {
+ mImportExportInteraction = new ImportExportInteraction(this);
+ }
+ return mImportExportInteraction;
+ }
+
+ @Override
+ public DialogManager getDialogManager() {
+ return mDialogManager;
+ }
+}
diff --git a/src/com/android/contacts/SearchResultsActivity.java b/src/com/android/contacts/activities/ContactBrowserMode.java
similarity index 64%
rename from src/com/android/contacts/SearchResultsActivity.java
rename to src/com/android/contacts/activities/ContactBrowserMode.java
index 09f0014..c135c1d 100644
--- a/src/com/android/contacts/SearchResultsActivity.java
+++ b/src/com/android/contacts/activities/ContactBrowserMode.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2007 The Android Open Source Project
+ * 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.
@@ -13,11 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.contacts;
+
+package com.android.contacts.activities;
/**
- * The activity that displays the list of contact search results. We need a separate
- * class because it uses a different theme from {@link ContactsListActivity}.
+ * Contact browser mode constants.
*/
-public class SearchResultsActivity extends ContactsListActivity {
+public class ContactBrowserMode {
+
+ public static final int MODE_CONTACTS = 0;
+ public static final int MODE_FAVORITES = 1;
+ public static final int MODE_SEARCH = 2;
+
}
diff --git a/src/com/android/contacts/activities/ContactDetailActivity.java b/src/com/android/contacts/activities/ContactDetailActivity.java
new file mode 100644
index 0000000..58c5312
--- /dev/null
+++ b/src/com/android/contacts/activities/ContactDetailActivity.java
@@ -0,0 +1,131 @@
+/*
+ * 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.activities;
+
+import com.android.contacts.ContactsSearchManager;
+import com.android.contacts.R;
+import com.android.contacts.interactions.ContactDeletionInteraction;
+import com.android.contacts.views.detail.ContactDetailFragment;
+
+import android.app.Activity;
+import android.app.Dialog;
+import android.content.ActivityNotFoundException;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.MenuItem;
+
+public class ContactDetailActivity extends Activity {
+ private static final String TAG = "ContactDetailActivity";
+
+ private ContactDetailFragment mFragment;
+ private ContactDeletionInteraction mContactDeletionInteraction;
+
+ @Override
+ public void onCreate(Bundle savedState) {
+ super.onCreate(savedState);
+
+ setContentView(R.layout.contact_detail_activity);
+
+ mFragment = (ContactDetailFragment) getFragmentManager().findFragmentById(
+ R.id.contact_detail_fragment);
+ mFragment.setListener(mFragmentListener);
+ mFragment.loadUri(getIntent().getData());
+
+ Log.i(TAG, getIntent().getData().toString());
+ }
+
+ @Override
+ protected Dialog onCreateDialog(int id, Bundle args) {
+ final Dialog deletionDialog = getContactDeletionInteraction().onCreateDialog(id, args);
+ if (deletionDialog != null) return deletionDialog;
+
+ // Nobody knows about the Dialog
+ Log.w(TAG, "Unknown dialog requested, id: " + id + ", args: " + args);
+ return null;
+ }
+
+ @Override
+ protected void onPrepareDialog(int id, Dialog dialog, Bundle args) {
+ if (getContactDeletionInteraction().onPrepareDialog(id, dialog, args)) {
+ return;
+ }
+ }
+
+ @Override
+ public boolean onContextItemSelected(MenuItem item) {
+ // TODO: This is too hardwired.
+ if (mFragment.onContextItemSelected(item)) return true;
+
+ return super.onContextItemSelected(item);
+ }
+
+ @Override
+ public void startSearch(String initialQuery, boolean selectInitialQuery, Bundle appSearchData,
+ boolean globalSearch) {
+ if (globalSearch) {
+ super.startSearch(initialQuery, selectInitialQuery, appSearchData, globalSearch);
+ } else {
+ ContactsSearchManager.startSearch(this, initialQuery);
+ }
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ // TODO: This is too hardwired.
+ if (mFragment.onKeyDown(keyCode, event)) return true;
+
+ return super.onKeyDown(keyCode, event);
+ }
+
+ private ContactDeletionInteraction getContactDeletionInteraction() {
+ if (mContactDeletionInteraction == null) {
+ mContactDeletionInteraction = new ContactDeletionInteraction();
+ mContactDeletionInteraction.attachToActivity(this);
+ }
+ return mContactDeletionInteraction;
+ }
+
+ private final ContactDetailFragment.Listener mFragmentListener =
+ new ContactDetailFragment.Listener() {
+ @Override
+ public void onContactNotFound() {
+ finish();
+ }
+
+ @Override
+ public void onEditRequested(Uri lookupUri) {
+ startActivity(new Intent(Intent.ACTION_EDIT, lookupUri));
+ }
+
+ @Override
+ public void onItemClicked(Intent intent) {
+ try {
+ startActivity(intent);
+ } catch (ActivityNotFoundException e) {
+ Log.e(TAG, "No activity found for intent: " + intent);
+ }
+ }
+
+ @Override
+ public void onDeleteRequested(Uri lookupUri) {
+ getContactDeletionInteraction().deleteContact(lookupUri);
+ }
+ };
+}
diff --git a/src/com/android/contacts/activities/ContactEditorActivity.java b/src/com/android/contacts/activities/ContactEditorActivity.java
new file mode 100644
index 0000000..85aa82e
--- /dev/null
+++ b/src/com/android/contacts/activities/ContactEditorActivity.java
@@ -0,0 +1,148 @@
+/*
+ * 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.activities;
+
+import com.android.contacts.ContactsSearchManager;
+import com.android.contacts.R;
+import com.android.contacts.interactions.ContactDeletionInteraction;
+import com.android.contacts.util.DialogManager;
+import com.android.contacts.views.editor.ContactEditorFragment;
+
+import android.app.Activity;
+import android.app.Dialog;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+
+public class ContactEditorActivity extends Activity implements
+ DialogManager.DialogShowingViewActivity {
+ private static final String TAG = "ContactEditorActivity";
+
+ private ContactEditorFragment mFragment;
+ private ContactDeletionInteraction mContactDeletionInteraction;
+ private Button mDoneButton;
+ private Button mRevertButton;
+
+ private DialogManager mDialogManager = new DialogManager(this);
+
+ @Override
+ public void onCreate(Bundle savedState) {
+ super.onCreate(savedState);
+
+ setContentView(R.layout.contact_editor_activity);
+
+ mFragment = (ContactEditorFragment) getFragmentManager().findFragmentById(
+ R.id.contact_editor_fragment);
+ mFragment.setListener(mFragmentListener);
+ mFragment.load(getIntent().getAction(), getIntent().getData(),
+ getIntent().resolveType(getContentResolver()), getIntent().getExtras());
+
+ // Depending on the use-case, this activity has Done and Revert buttons or not.
+ mDoneButton = (Button) findViewById(R.id.done);
+ mRevertButton = (Button) findViewById(R.id.revert);
+ if (mDoneButton != null) mDoneButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mFragment.save(true);
+ }
+ });
+ if (mRevertButton != null) mRevertButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ finish();
+ }
+ });
+
+ Log.i(TAG, getIntent().getData().toString());
+ }
+
+ @Override
+ protected Dialog onCreateDialog(int id, Bundle args) {
+ if (DialogManager.isManagedId(id)) return mDialogManager.onCreateDialog(id, args);
+
+ // Nobody knows about the Dialog
+ Log.w(TAG, "Unknown dialog requested, id: " + id + ", args: " + args);
+ return null;
+ }
+
+ @Override
+ public void startSearch(String initialQuery, boolean selectInitialQuery, Bundle appSearchData,
+ boolean globalSearch) {
+ if (globalSearch) {
+ super.startSearch(initialQuery, selectInitialQuery, appSearchData, globalSearch);
+ } else {
+ ContactsSearchManager.startSearch(this, initialQuery);
+ }
+ }
+
+ private ContactDeletionInteraction getContactDeletionInteraction() {
+ if (mContactDeletionInteraction == null) {
+ mContactDeletionInteraction = new ContactDeletionInteraction();
+ mContactDeletionInteraction.attachToActivity(this);
+ }
+ return mContactDeletionInteraction;
+ }
+
+ private final ContactEditorFragment.Listener mFragmentListener =
+ new ContactEditorFragment.Listener() {
+ @Override
+ public void onReverted() {
+ finish();
+ }
+
+ @Override
+ public void onSaveFinished(int resultCode, Intent resultIntent) {
+ setResult(resultCode, resultIntent);
+ finish();
+ }
+
+ @Override
+ public void onAggregationChangeFinished(Uri newLookupUri) {
+ finish();
+ }
+
+ @Override
+ public void onAccountSelectorAborted() {
+ finish();
+ }
+
+ @Override
+ public void onContactNotFound() {
+ setResult(Activity.RESULT_CANCELED, null);
+ finish();
+ }
+
+ @Override
+ public void setTitleTo(int resourceId) {
+ setTitle(resourceId);
+ }
+
+ @Override
+ public void onDeleteRequested(Uri lookupUri) {
+ getContactDeletionInteraction().deleteContact(lookupUri);
+ }
+ };
+
+ @Override
+ public DialogManager getDialogManager() {
+ return mDialogManager;
+ }
+}
diff --git a/src/com/android/contacts/activities/ContactSearchActivity.java b/src/com/android/contacts/activities/ContactSearchActivity.java
new file mode 100644
index 0000000..cac3e1e
--- /dev/null
+++ b/src/com/android/contacts/activities/ContactSearchActivity.java
@@ -0,0 +1,177 @@
+/*
+ * 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.activities;
+
+import com.android.contacts.R;
+import com.android.contacts.interactions.ContactDeletionInteraction;
+import com.android.contacts.interactions.PhoneNumberInteraction;
+import com.android.contacts.list.ContactBrowseListContextMenuAdapter;
+import com.android.contacts.list.ContactBrowseListFragment;
+import com.android.contacts.list.ContactsIntentResolver;
+import com.android.contacts.list.ContactsRequest;
+import com.android.contacts.list.DefaultContactBrowseListFragment;
+import com.android.contacts.list.OnContactBrowserActionListener;
+import com.android.contacts.widget.SearchEditText;
+import com.android.contacts.widget.SearchEditText.OnFilterTextListener;
+
+import android.app.Activity;
+import android.content.ContentValues;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.ContactsContract.Contacts;
+
+public class ContactSearchActivity extends Activity {
+
+ private ContactsIntentResolver mIntentResolver;
+ private ContactsRequest mRequest;
+ private ContactBrowseListFragment mListFragment;
+ private PhoneNumberInteraction mPhoneNumberCallInteraction;
+ private PhoneNumberInteraction mSendTextMessageInteraction;
+ private ContactDeletionInteraction mContactDeletionInteraction;
+ private SearchEditText mSearchEditText;
+
+ public ContactSearchActivity() {
+ mIntentResolver = new ContactsIntentResolver(this);
+ }
+
+ @Override
+ protected void onCreate(Bundle savedState) {
+ super.onCreate(savedState);
+
+ // Extract relevant information from the intent
+ mRequest = mIntentResolver.resolveIntent(getIntent());
+ if (!mRequest.isValid()) {
+ setResult(RESULT_CANCELED);
+ finish();
+ return;
+ }
+
+ setTitle(mRequest.getActivityTitle());
+
+ setContentView(R.layout.contacts_search_content);
+
+ if (mListFragment == null) {
+ mListFragment = createContactSearchFragment();
+ getFragmentManager().openTransaction()
+ .replace(R.id.list_container, mListFragment)
+ .commit();
+ }
+
+ mSearchEditText = (SearchEditText)findViewById(R.id.search_src_text);
+ mSearchEditText.setText(mRequest.getQueryString());
+ mSearchEditText.setOnFilterTextListener(new OnFilterTextListener() {
+ public void onFilterChange(String queryString) {
+ mListFragment.setQueryString(queryString);
+ }
+
+ public void onCancelSearch() {
+ finish();
+ }
+ });
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ mSearchEditText.requestFocus();
+ }
+
+
+ private ContactBrowseListFragment createContactSearchFragment() {
+ DefaultContactBrowseListFragment fragment = new DefaultContactBrowseListFragment();
+ fragment.setOnContactListActionListener(new ContactBrowserActionListener());
+ fragment.setDisplayWithPhonesOnlyOption(ContactsRequest.DISPLAY_ONLY_WITH_PHONES_DISABLED);
+ fragment.setVisibleContactsRestrictionEnabled(true);
+ fragment.setContextMenuAdapter(new ContactBrowseListContextMenuAdapter(fragment));
+ fragment.setSearchMode(true);
+ fragment.setDirectorySearchEnabled(true);
+ fragment.setAizyEnabled(false);
+ fragment.setSelectionVisible(true);
+ fragment.setEditMode(mRequest.getActionCode() ==
+ ContactsRequest.ACTION_INSERT_OR_EDIT_CONTACT);
+ return fragment;
+ }
+
+ private final class ContactBrowserActionListener implements OnContactBrowserActionListener {
+ public void onViewContactAction(Uri contactLookupUri, boolean force) {
+ startActivity(new Intent(Intent.ACTION_VIEW, contactLookupUri));
+ }
+
+ public void onCreateNewContactAction() {
+ }
+
+ public void onEditContactAction(Uri contactLookupUri) {
+ Intent intent = new Intent(Intent.ACTION_EDIT, contactLookupUri);
+ Bundle extras = getIntent().getExtras();
+ if (extras != null) {
+ intent.putExtras(extras);
+ }
+ startActivity(intent);
+ }
+
+ public void onAddToFavoritesAction(Uri contactUri) {
+ ContentValues values = new ContentValues(1);
+ values.put(Contacts.STARRED, 1);
+ getContentResolver().update(contactUri, values, null, null);
+ }
+
+ public void onRemoveFromFavoritesAction(Uri contactUri) {
+ ContentValues values = new ContentValues(1);
+ values.put(Contacts.STARRED, 0);
+ getContentResolver().update(contactUri, values, null, null);
+ }
+
+ public void onCallContactAction(Uri contactUri) {
+ getPhoneNumberCallInteraction().startInteraction(contactUri);
+ }
+
+ public void onSmsContactAction(Uri contactUri) {
+ getSendTextMessageInteraction().startInteraction(contactUri);
+ }
+
+ public void onDeleteContactAction(Uri contactUri) {
+ getContactDeletionInteraction().deleteContact(contactUri);
+ }
+
+ public void onFinishAction() {
+ onBackPressed();
+ }
+ }
+
+ private PhoneNumberInteraction getPhoneNumberCallInteraction() {
+ if (mPhoneNumberCallInteraction == null) {
+ mPhoneNumberCallInteraction = new PhoneNumberInteraction(this, false, null);
+ }
+ return mPhoneNumberCallInteraction;
+ }
+
+ private PhoneNumberInteraction getSendTextMessageInteraction() {
+ if (mSendTextMessageInteraction == null) {
+ mSendTextMessageInteraction = new PhoneNumberInteraction(this, true, null);
+ }
+ return mSendTextMessageInteraction;
+ }
+
+ private ContactDeletionInteraction getContactDeletionInteraction() {
+ if (mContactDeletionInteraction == null) {
+ mContactDeletionInteraction = new ContactDeletionInteraction();
+ mContactDeletionInteraction.attachToActivity(this);
+ }
+ return mContactDeletionInteraction;
+ }
+}
diff --git a/src/com/android/contacts/activities/ContactSelectionActivity.java b/src/com/android/contacts/activities/ContactSelectionActivity.java
new file mode 100644
index 0000000..66d06d2
--- /dev/null
+++ b/src/com/android/contacts/activities/ContactSelectionActivity.java
@@ -0,0 +1,473 @@
+/*
+ * Copyright (C) 2007 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.activities;
+
+import com.android.contacts.R;
+import com.android.contacts.list.ContactEntryListFragment;
+import com.android.contacts.list.ContactPickerFragment;
+import com.android.contacts.list.ContactsIntentResolver;
+import com.android.contacts.list.ContactsRequest;
+import com.android.contacts.list.DefaultContactBrowseListFragment;
+import com.android.contacts.list.OnContactBrowserActionListener;
+import com.android.contacts.list.OnContactPickerActionListener;
+import com.android.contacts.list.OnPhoneNumberPickerActionListener;
+import com.android.contacts.list.OnPostalAddressPickerActionListener;
+import com.android.contacts.list.PhoneNumberPickerFragment;
+import com.android.contacts.list.PostalAddressPickerFragment;
+import com.android.contacts.widget.ContextMenuAdapter;
+import com.android.contacts.widget.SearchEditText;
+import com.android.contacts.widget.SearchEditText.OnFilterTextListener;
+
+import android.app.Activity;
+import android.app.FragmentTransaction;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.ContactsContract.Contacts;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+
+/**
+ * Displays a list of contacts (or phone numbers or postal addresses) for the
+ * purposes of selecting one.
+ */
+public class ContactSelectionActivity extends Activity implements View.OnCreateContextMenuListener {
+
+ private static final String TAG = "ContactSelectionActivity";
+
+ private static final int SUBACTIVITY_NEW_CONTACT = 1;
+ private static final int SUBACTIVITY_VIEW_CONTACT = 2;
+ private static final int SUBACTIVITY_DISPLAY_GROUP = 3;
+ private static final int SUBACTIVITY_SEARCH = 4;
+
+ private ContactsIntentResolver mIntentResolver;
+ protected ContactEntryListFragment<?> mListFragment;
+
+ private int mActionCode;
+
+ private boolean mSearchInitiated;
+
+ private ContactsRequest mRequest;
+ private SearchEditText mSearchEditText;
+
+ public ContactSelectionActivity() {
+ mIntentResolver = new ContactsIntentResolver(this);
+ }
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ // Extract relevant information from the intent
+ mRequest = mIntentResolver.resolveIntent(getIntent());
+ if (!mRequest.isValid()) {
+ setResult(RESULT_CANCELED);
+ finish();
+ return;
+ }
+
+ Intent redirect = mRequest.getRedirectIntent();
+ if (redirect != null) {
+ // Need to start a different activity
+ startActivity(redirect);
+ finish();
+ return;
+ }
+
+ setTitle(mRequest.getActivityTitle());
+
+ onCreateFragment();
+
+ int listFragmentContainerId;
+ if (mRequest.isSearchMode()) {
+ setContentView(R.layout.contacts_search_content);
+ listFragmentContainerId = R.id.list_container;
+ setupSearchUI();
+ } else {
+ listFragmentContainerId = android.R.id.content;
+ }
+
+ FragmentTransaction transaction = getFragmentManager().openTransaction();
+ transaction.add(listFragmentContainerId, mListFragment);
+ transaction.commit();
+ }
+
+ private void setupSearchUI() {
+ mSearchEditText = (SearchEditText)findViewById(R.id.search_src_text);
+ mSearchEditText.setText(mRequest.getQueryString());
+ mSearchEditText.setOnFilterTextListener(new OnFilterTextListener() {
+ public void onFilterChange(String queryString) {
+ mListFragment.setQueryString(queryString);
+ }
+
+ public void onCancelSearch() {
+ finish();
+ }
+ });
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ if (mRequest.isSearchMode()) {
+ mSearchEditText.requestFocus();
+ }
+ }
+
+ /**
+ * Creates the fragment based on the current request.
+ */
+ private void onCreateFragment() {
+ mActionCode = mRequest.getActionCode();
+ switch (mActionCode) {
+ case ContactsRequest.ACTION_INSERT_OR_EDIT_CONTACT: {
+ DefaultContactBrowseListFragment fragment = new DefaultContactBrowseListFragment();
+ fragment.setOnContactListActionListener(new ContactBrowserActionListener());
+ fragment.setEditMode(true);
+ fragment.setCreateContactEnabled(true);
+ fragment.setDisplayWithPhonesOnlyOption(mRequest.getDisplayWithPhonesOnlyOption());
+ fragment.setVisibleContactsRestrictionEnabled(mRequest.getDisplayOnlyVisible());
+ fragment.setSearchMode(mRequest.isSearchMode());
+ fragment.setQueryString(mRequest.getQueryString());
+ fragment.setDirectorySearchEnabled(false);
+ mListFragment = fragment;
+ break;
+ }
+
+ case ContactsRequest.ACTION_PICK_CONTACT: {
+ ContactPickerFragment fragment = new ContactPickerFragment();
+ fragment.setOnContactPickerActionListener(new ContactPickerActionListener());
+ fragment.setLegacyCompatibilityMode(mRequest.isLegacyCompatibilityMode());
+ fragment.setSearchMode(mRequest.isSearchMode());
+ mListFragment = fragment;
+ break;
+ }
+
+ case ContactsRequest.ACTION_PICK_OR_CREATE_CONTACT: {
+ ContactPickerFragment fragment = new ContactPickerFragment();
+ fragment.setOnContactPickerActionListener(new ContactPickerActionListener());
+ fragment.setCreateContactEnabled(!mRequest.isSearchMode());
+ fragment.setLegacyCompatibilityMode(mRequest.isLegacyCompatibilityMode());
+ mListFragment = fragment;
+ break;
+ }
+
+ case ContactsRequest.ACTION_CREATE_SHORTCUT_CONTACT: {
+ ContactPickerFragment fragment = new ContactPickerFragment();
+ fragment.setOnContactPickerActionListener(new ContactPickerActionListener());
+ fragment.setCreateContactEnabled(!mRequest.isSearchMode());
+ fragment.setLegacyCompatibilityMode(mRequest.isLegacyCompatibilityMode());
+ fragment.setSearchMode(mRequest.isSearchMode());
+ fragment.setQueryString(mRequest.getQueryString());
+ fragment.setDirectorySearchEnabled(mRequest.isDirectorySearchEnabled());
+ fragment.setShortcutRequested(true);
+ mListFragment = fragment;
+ break;
+ }
+
+ case ContactsRequest.ACTION_PICK_PHONE: {
+ PhoneNumberPickerFragment fragment = new PhoneNumberPickerFragment();
+ fragment.setOnPhoneNumberPickerActionListener(
+ new PhoneNumberPickerActionListener());
+ fragment.setLegacyCompatibilityMode(mRequest.isLegacyCompatibilityMode());
+ mListFragment = fragment;
+ break;
+ }
+
+ case ContactsRequest.ACTION_CREATE_SHORTCUT_CALL: {
+ PhoneNumberPickerFragment fragment = new PhoneNumberPickerFragment();
+ fragment.setOnPhoneNumberPickerActionListener(
+ new PhoneNumberPickerActionListener());
+ fragment.setLegacyCompatibilityMode(mRequest.isLegacyCompatibilityMode());
+ fragment.setShortcutAction(Intent.ACTION_CALL);
+ fragment.setSearchMode(mRequest.isSearchMode());
+
+ mListFragment = fragment;
+ break;
+ }
+
+ case ContactsRequest.ACTION_CREATE_SHORTCUT_SMS: {
+ PhoneNumberPickerFragment fragment = new PhoneNumberPickerFragment();
+ fragment.setOnPhoneNumberPickerActionListener(
+ new PhoneNumberPickerActionListener());
+ fragment.setLegacyCompatibilityMode(mRequest.isLegacyCompatibilityMode());
+ fragment.setShortcutAction(Intent.ACTION_SENDTO);
+ fragment.setSearchMode(mRequest.isSearchMode());
+
+ mListFragment = fragment;
+ break;
+ }
+
+ case ContactsRequest.ACTION_PICK_POSTAL: {
+ PostalAddressPickerFragment fragment = new PostalAddressPickerFragment();
+ fragment.setOnPostalAddressPickerActionListener(
+ new PostalAddressPickerActionListener());
+ fragment.setLegacyCompatibilityMode(mRequest.isLegacyCompatibilityMode());
+ mListFragment = fragment;
+ break;
+ }
+
+ default:
+ throw new IllegalStateException("Invalid action code: " + mActionCode);
+ }
+ mListFragment.setContactsRequest(mRequest);
+ }
+
+ private final class ContactBrowserActionListener implements OnContactBrowserActionListener {
+ public void onViewContactAction(Uri contactLookupUri, boolean force) {
+ throw new UnsupportedOperationException();
+ }
+
+ public void onCreateNewContactAction() {
+ Intent intent = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI);
+ Bundle extras = getIntent().getExtras();
+ if (extras != null) {
+ intent.putExtras(extras);
+ }
+ startActivity(intent);
+ }
+
+ public void onEditContactAction(Uri contactLookupUri) {
+ Intent intent = new Intent(Intent.ACTION_EDIT, contactLookupUri);
+ Bundle extras = getIntent().getExtras();
+ if (extras != null) {
+ intent.putExtras(extras);
+ }
+ startActivity(intent);
+ }
+
+ public void onAddToFavoritesAction(Uri contactUri) {
+ throw new UnsupportedOperationException();
+ }
+
+ public void onRemoveFromFavoritesAction(Uri contactUri) {
+ throw new UnsupportedOperationException();
+ }
+
+ public void onCallContactAction(Uri contactUri) {
+ throw new UnsupportedOperationException();
+ }
+
+ public void onSmsContactAction(Uri contactUri) {
+ throw new UnsupportedOperationException();
+ }
+
+ public void onDeleteContactAction(Uri contactUri) {
+ throw new UnsupportedOperationException();
+ }
+
+ public void onFinishAction() {
+ onBackPressed();
+ }
+ }
+
+ private final class ContactPickerActionListener implements OnContactPickerActionListener {
+ public void onCreateNewContactAction() {
+ Intent intent = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI);
+ startActivityAndForwardResult(intent);
+ }
+
+ public void onPickContactAction(Uri contactUri) {
+ Intent intent = new Intent();
+ setResult(RESULT_OK, intent.setData(contactUri));
+ finish();
+ }
+
+ public void onShortcutIntentCreated(Intent intent) {
+ setResult(RESULT_OK, intent);
+ finish();
+ }
+ }
+
+ private final class PhoneNumberPickerActionListener implements
+ OnPhoneNumberPickerActionListener {
+ public void onPickPhoneNumberAction(Uri dataUri) {
+ Intent intent = new Intent();
+ setResult(RESULT_OK, intent.setData(dataUri));
+ finish();
+ }
+
+ public void onShortcutIntentCreated(Intent intent) {
+ setResult(RESULT_OK, intent);
+ finish();
+ }
+ }
+
+ private final class PostalAddressPickerActionListener implements
+ OnPostalAddressPickerActionListener {
+ public void onPickPostalAddressAction(Uri dataUri) {
+ Intent intent = new Intent();
+ setResult(RESULT_OK, intent.setData(dataUri));
+ finish();
+ }
+ }
+
+ public void startActivityAndForwardResult(final Intent intent) {
+ intent.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
+
+ // Forward extras to the new activity
+ Bundle extras = getIntent().getExtras();
+ if (extras != null) {
+ intent.putExtras(extras);
+ }
+ startActivity(intent);
+ finish();
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ super.onCreateOptionsMenu(menu);
+
+ MenuInflater inflater = getMenuInflater();
+ if (!mListFragment.isSearchMode()) {
+ inflater.inflate(R.menu.search, menu);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.menu_search: {
+ onSearchRequested();
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void startSearch(String initialQuery, boolean selectInitialQuery, Bundle appSearchData,
+ boolean globalSearch) {
+// TODO
+// if (mProviderStatus != ProviderStatus.STATUS_NORMAL) {
+// return;
+// }
+
+ if (globalSearch) {
+ super.startSearch(initialQuery, selectInitialQuery, appSearchData, globalSearch);
+ } else {
+ mListFragment.startSearch(initialQuery);
+ }
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ switch (requestCode) {
+// case SUBACTIVITY_NEW_CONTACT:
+// if (resultCode == RESULT_OK) {
+// returnPickerResult(null, data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME),
+// data.getData());
+// setRe
+// }
+// break;
+
+// case SUBACTIVITY_VIEW_CONTACT:
+// if (resultCode == RESULT_OK) {
+// mAdapter.notifyDataSetChanged();
+// }
+// break;
+//
+// case SUBACTIVITY_DISPLAY_GROUP:
+// // Mark as just created so we re-run the view query
+//// mJustCreated = true;
+// break;
+//
+ case ContactEntryListFragment.ACTIVITY_REQUEST_CODE_PICKER:
+ if (resultCode == RESULT_OK) {
+ mListFragment.onPickerResult(data);
+ }
+
+// TODO fix or remove multipicker code
+// else if (resultCode == RESULT_CANCELED && mMode == MODE_PICK_MULTIPLE_PHONES) {
+// // Finish the activity if the sub activity was canceled as back key is used
+// // to confirm user selection in MODE_PICK_MULTIPLE_PHONES.
+// finish();
+// }
+// break;
+ }
+ }
+
+ @Override
+ public boolean onContextItemSelected(MenuItem item) {
+ ContextMenuAdapter menuAdapter = mListFragment.getContextMenuAdapter();
+ if (menuAdapter != null) {
+ return menuAdapter.onContextItemSelected(item);
+ }
+
+ return super.onContextItemSelected(item);
+ }
+
+ /**
+ * Event handler for the use case where the user starts typing without
+ * bringing up the search UI first.
+ */
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ if (!mSearchInitiated && !mRequest.isSearchMode()) {
+ int unicodeChar = event.getUnicodeChar();
+ if (unicodeChar != 0) {
+ mSearchInitiated = true;
+ startSearch(new String(new int[]{unicodeChar}, 0, 1), false, null, false);
+ return true;
+ }
+ }
+ return super.dispatchKeyEvent(event);
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ // TODO move to the fragment
+ switch (keyCode) {
+// case KeyEvent.KEYCODE_CALL: {
+// if (callSelection()) {
+// return true;
+// }
+// break;
+// }
+
+ case KeyEvent.KEYCODE_DEL: {
+ if (deleteSelection()) {
+ return true;
+ }
+ break;
+ }
+ }
+
+ return super.onKeyDown(keyCode, event);
+ }
+
+ private boolean deleteSelection() {
+ // TODO move to the fragment
+// if (mActionCode == ContactsRequest.ACTION_DEFAULT) {
+// final int position = mListView.getSelectedItemPosition();
+// if (position != ListView.INVALID_POSITION) {
+// Uri contactUri = getContactUri(position);
+// if (contactUri != null) {
+// doContactDelete(contactUri);
+// return true;
+// }
+// }
+// }
+ return false;
+ }
+}
diff --git a/src/com/android/contacts/activities/ContactsFrontDoor.java b/src/com/android/contacts/activities/ContactsFrontDoor.java
new file mode 100644
index 0000000..d8ffd7a
--- /dev/null
+++ b/src/com/android/contacts/activities/ContactsFrontDoor.java
@@ -0,0 +1,52 @@
+/*
+ * 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.activities;
+
+import com.android.contacts.DialtactsActivity;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.os.Bundle;
+
+public class ContactsFrontDoor extends Activity {
+ public static final String EXTRA_FRONT_DOOR = "front_door";
+
+ @Override
+ public void onCreate(Bundle savedState) {
+ super.onCreate(savedState);
+
+ Intent intent = new Intent();
+ intent.setFlags(
+ Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS | Intent.FLAG_ACTIVITY_FORWARD_RESULT);
+ intent.putExtra(EXTRA_FRONT_DOOR, true);
+
+ // The user launched the config based front door, pick the right activity to go to
+ Configuration config = getResources().getConfiguration();
+ int screenLayoutSize = config.screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK;
+ if (screenLayoutSize == Configuration.SCREENLAYOUT_SIZE_XLARGE) {
+ // XL screen, use two pane UI
+ intent.setClass(this, ContactBrowserActivity.class);
+ } else {
+ // Default to the normal dialtacts layout
+ intent.setClass(this, DialtactsActivity.class);
+ }
+
+ startActivity(intent);
+ finish();
+ }
+}
diff --git a/src/com/android/contacts/interactions/ContactDeletionInteraction.java b/src/com/android/contacts/interactions/ContactDeletionInteraction.java
new file mode 100644
index 0000000..5746efc
--- /dev/null
+++ b/src/com/android/contacts/interactions/ContactDeletionInteraction.java
@@ -0,0 +1,201 @@
+/*
+ * 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 com.android.contacts.R;
+import com.android.contacts.model.ContactsSource;
+import com.android.contacts.model.Sources;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.CursorLoader;
+import android.content.DialogInterface;
+import android.content.Loader;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.ContactsContract.RawContacts;
+import android.util.Log;
+
+// TODO: This should respect the lookup element. The Id might by now refer to a different contact
+/**
+ * An interaction invoked to delete a contact.
+ */
+public class ContactDeletionInteraction {
+
+ private static final String TAG = "ContactDeletionInteraction";
+
+ public static final String EXTRA_KEY_CONTACT_URI = "contactUri";
+ public static final String EXTRA_KEY_MESSAGE_ID = "messageId";
+
+ private static final String[] RAW_CONTACTS_PROJECTION = new String[] {
+ RawContacts._ID, //0
+ RawContacts.ACCOUNT_TYPE, //1
+ };
+
+ private final class RawContactLoader extends CursorLoader {
+ private final Uri mContactUri;
+
+ private RawContactLoader(Context context, Uri contactUri) {
+ super(context,
+ RawContacts.CONTENT_URI,
+ RAW_CONTACTS_PROJECTION,
+ RawContacts.CONTACT_ID + "=?",
+ new String[] { String.valueOf(ContentUris.parseId(contactUri)) },
+ null);
+ this.mContactUri = contactUri;
+ }
+
+ @Override
+ public void deliverResult(Cursor data) {
+ if (data == null || data.getCount() == 0) {
+ Log.e(TAG, "No such contact: " + mContactUri);
+ return;
+ }
+
+ showConfirmationDialog(data, mContactUri);
+ }
+ }
+
+ private final class ConfirmationDialogClickListener implements DialogInterface.OnClickListener {
+ private final Uri mContactUri;
+
+ public ConfirmationDialogClickListener(Uri contactUri) {
+ this.mContactUri = contactUri;
+ }
+
+ public void onClick(DialogInterface dialog, int which) {
+ doDeleteContact(mContactUri);
+ }
+ }
+
+ private Context mContext;
+ private CursorLoader mLoader;
+
+ public void attachToActivity(Activity activity) {
+ setContext(activity);
+ }
+
+ /* Visible for testing */
+ void setContext(Context context) {
+ mContext = context;
+ }
+
+ public void deleteContact(Uri contactUri) {
+ if (mLoader != null) {
+ mLoader.destroy();
+ }
+ mLoader = new RawContactLoader(mContext, contactUri);
+ startLoading(mLoader);
+ }
+
+ protected void showConfirmationDialog(Cursor cursor, Uri contactUri) {
+ int writableSourcesCnt = 0;
+ int readOnlySourcesCnt = 0;
+
+ Sources sources = getSources();
+ try {
+ while (cursor.moveToNext()) {
+ final long rawContactId = cursor.getLong(0);
+ final String accountType = cursor.getString(1);
+ ContactsSource contactsSource = sources.getInflatedSource(accountType,
+ ContactsSource.LEVEL_SUMMARY);
+ boolean readonly = contactsSource != null && contactsSource.readOnly;
+ if (readonly) {
+ readOnlySourcesCnt ++;
+ } else {
+ writableSourcesCnt ++;
+ }
+ }
+ } finally {
+ cursor.close();
+ }
+
+ int messageId;
+ if (readOnlySourcesCnt > 0 && writableSourcesCnt > 0) {
+ messageId = R.string.readOnlyContactDeleteConfirmation;
+ } else if (readOnlySourcesCnt > 0 && writableSourcesCnt == 0) {
+ messageId = R.string.readOnlyContactWarning;
+ } else if (readOnlySourcesCnt == 0 && writableSourcesCnt > 1) {
+ messageId = R.string.multipleContactDeleteConfirmation;
+ } else {
+ messageId = R.string.deleteConfirmation;
+ }
+
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(EXTRA_KEY_CONTACT_URI, contactUri);
+ bundle.putInt(EXTRA_KEY_MESSAGE_ID, messageId);
+
+ showDialog(bundle);
+ }
+
+ /**
+ * Creates a delete confirmation dialog and returns it. Returns null if the
+ * id is not for a deletion confirmation.
+ */
+ public Dialog onCreateDialog(int id, Bundle bundle) {
+ if (id != R.id.dialog_delete_contact_confirmation) {
+ return null;
+ }
+
+ return new AlertDialog.Builder(mContext)
+ .setTitle(R.string.deleteConfirmation_title)
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .setMessage(R.string.deleteConfirmation)
+ .setNegativeButton(android.R.string.cancel, null)
+ .setPositiveButton(android.R.string.ok, null)
+ .create();
+ }
+
+ public boolean onPrepareDialog(int id, Dialog dialog, Bundle bundle) {
+ if (id != R.id.dialog_delete_contact_confirmation) {
+ return false;
+ }
+
+ Uri contactUri = bundle.getParcelable(EXTRA_KEY_CONTACT_URI);
+ int messageId = bundle.getInt(EXTRA_KEY_MESSAGE_ID, R.string.deleteConfirmation);
+
+ ((AlertDialog)dialog).setMessage(mContext.getText(messageId));
+ ((AlertDialog)dialog).setButton(DialogInterface.BUTTON_POSITIVE,
+ mContext.getText(android.R.string.ok),
+ new ConfirmationDialogClickListener(contactUri));
+
+ return true;
+ }
+
+ protected void doDeleteContact(Uri contactUri) {
+ mContext.getContentResolver().delete(contactUri, null, null);
+ }
+
+ /* Visible for testing */
+ void startLoading(Loader<Cursor> loader) {
+ loader.startLoading();
+ }
+
+ /* Visible for testing */
+ Sources getSources() {
+ return Sources.getInstance(mContext);
+ }
+
+ /* Visible for testing */
+ void showDialog(Bundle bundle) {
+ ((Activity)mContext).showDialog(R.id.dialog_delete_contact_confirmation, bundle);
+ }
+}
diff --git a/src/com/android/contacts/interactions/ImportExportInteraction.java b/src/com/android/contacts/interactions/ImportExportInteraction.java
new file mode 100644
index 0000000..33df22f
--- /dev/null
+++ b/src/com/android/contacts/interactions/ImportExportInteraction.java
@@ -0,0 +1,219 @@
+/*
+ * 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 com.android.contacts.R;
+import com.android.contacts.model.Sources;
+import com.android.contacts.util.AccountSelectionUtil;
+import com.android.contacts.vcard.ExportVCardActivity;
+
+import android.accounts.Account;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.ContactsContract.Contacts;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+import android.view.ContextThemeWrapper;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import java.util.List;
+
+/**
+ * An interaction invoked to import/export contacts.
+ */
+public class ImportExportInteraction {
+
+ private static final String TAG = "ImportExportInteraction";
+
+ private final String[] LOOKUP_PROJECTION = new String[] {
+ Contacts.LOOKUP_KEY
+ };
+
+ private final Context mContext;
+
+ public ImportExportInteraction(Context context) {
+ this.mContext = context;
+ }
+
+ /**
+ * Creates a {@link Dialog} that allows the user to choose between a bulk import
+ * and bulk export task across all contacts.
+ */
+ public void startInteraction() {
+ showDialog(R.id.dialog_import_export_options, null);
+ }
+
+ public Dialog onCreateDialog(int id, Bundle bundle) {
+ switch (id) {
+ case R.id.dialog_import_export_options: {
+ return createOptionsDialog();
+ }
+ case R.string.import_from_sim:
+ case R.string.import_from_sdcard: {
+ return AccountSelectionUtil.getSelectAccountDialog(mContext, id);
+ }
+ case R.id.dialog_sdcard_not_found: {
+ return new AlertDialog.Builder(mContext)
+ .setTitle(R.string.no_sdcard_title)
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .setMessage(R.string.no_sdcard_message)
+ .setPositiveButton(android.R.string.ok, null).create();
+ }
+ }
+
+ return null;
+ }
+
+ private Dialog createOptionsDialog() {
+ // Wrap our context to inflate list items using the correct theme
+ final Context dialogContext =
+ new ContextThemeWrapper(mContext, android.R.style.Theme_Light);
+ final Resources res = dialogContext.getResources();
+ final LayoutInflater dialogInflater = (LayoutInflater)dialogContext
+ .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+
+ // Adapter that shows a list of string resources
+ final ArrayAdapter<Integer> adapter = new ArrayAdapter<Integer>(mContext,
+ android.R.layout.simple_list_item_1) {
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ if (convertView == null) {
+ convertView = dialogInflater.inflate(android.R.layout.simple_list_item_1,
+ parent, false);
+ }
+
+ final int resId = this.getItem(position);
+ ((TextView)convertView).setText(resId);
+ return convertView;
+ }
+ };
+
+ if (TelephonyManager.getDefault().hasIccCard()) {
+ adapter.add(R.string.import_from_sim);
+ }
+ if (res.getBoolean(R.bool.config_allow_import_from_sdcard)) {
+ adapter.add(R.string.import_from_sdcard);
+ }
+ if (res.getBoolean(R.bool.config_allow_export_to_sdcard)) {
+ adapter.add(R.string.export_to_sdcard);
+ }
+ if (res.getBoolean(R.bool.config_allow_share_visible_contacts)) {
+ adapter.add(R.string.share_visible_contacts);
+ }
+
+ final DialogInterface.OnClickListener clickListener =
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.dismiss();
+
+ final int resId = adapter.getItem(which);
+ switch (resId) {
+ case R.string.import_from_sim:
+ case R.string.import_from_sdcard: {
+ handleImportRequest(resId);
+ break;
+ }
+ case R.string.export_to_sdcard: {
+ Intent exportIntent = new Intent(mContext, ExportVCardActivity.class);
+ mContext.startActivity(exportIntent);
+ break;
+ }
+ case R.string.share_visible_contacts: {
+ doShareVisibleContacts();
+ break;
+ }
+ default: {
+ Log.e(TAG, "Unexpected resource: " +
+ mContext.getResources().getResourceEntryName(resId));
+ }
+ }
+ }
+ };
+
+ return new AlertDialog.Builder(mContext)
+ .setTitle(R.string.dialog_import_export)
+ .setNegativeButton(android.R.string.cancel, null)
+ .setSingleChoiceItems(adapter, -1, clickListener)
+ .create();
+ }
+
+ private void doShareVisibleContacts() {
+
+ // TODO move the query into a loader
+ final Cursor cursor = mContext.getContentResolver().query(Contacts.CONTENT_URI,
+ LOOKUP_PROJECTION, Contacts.IN_VISIBLE_GROUP + "!=0", null, null);
+ try {
+ if (!cursor.moveToFirst()) {
+ Toast.makeText(mContext, R.string.share_error, Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ StringBuilder uriListBuilder = new StringBuilder();
+ int index = 0;
+ while (cursor.moveToNext()) {
+ if (index != 0)
+ uriListBuilder.append(':');
+ uriListBuilder.append(cursor.getString(0));
+ index++;
+ }
+ Uri uri = Uri.withAppendedPath(
+ Contacts.CONTENT_MULTI_VCARD_URI,
+ Uri.encode(uriListBuilder.toString()));
+
+ final Intent intent = new Intent(Intent.ACTION_SEND);
+ intent.setType(Contacts.CONTENT_VCARD_TYPE);
+ intent.putExtra(Intent.EXTRA_STREAM, uri);
+ mContext.startActivity(intent);
+ } finally {
+ cursor.close();
+ }
+ }
+
+ private void handleImportRequest(int resId) {
+ // There are three possibilities:
+ // - more than one accounts -> ask the user
+ // - just one account -> use the account without asking the user
+ // - no account -> use phone-local storage without asking the user
+ final Sources sources = Sources.getInstance(mContext);
+ final List<Account> accountList = sources.getAccounts(true);
+ final int size = accountList.size();
+ if (size > 1) {
+ showDialog(resId, null);
+ return;
+ }
+
+ AccountSelectionUtil.doImport(mContext, resId, (size == 1 ? accountList.get(0) : null));
+ }
+
+ /* Visible for testing */
+ void showDialog(int dialogId, Bundle bundle) {
+ ((Activity)mContext).showDialog(dialogId, bundle);
+ }
+}
diff --git a/src/com/android/contacts/interactions/PhoneNumberInteraction.java b/src/com/android/contacts/interactions/PhoneNumberInteraction.java
new file mode 100644
index 0000000..5cf289e
--- /dev/null
+++ b/src/com/android/contacts/interactions/PhoneNumberInteraction.java
@@ -0,0 +1,365 @@
+/*
+ * 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 com.android.contacts.Collapser;
+import com.android.contacts.Collapser.Collapsible;
+import com.android.contacts.R;
+import com.android.contacts.model.ContactsSource;
+import com.android.contacts.model.ContactsSource.DataKind;
+import com.android.contacts.model.ContactsSource.StringInflater;
+import com.android.contacts.model.Sources;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.CursorLoader;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.content.DialogInterface.OnDismissListener;
+import android.content.Intent;
+import android.content.Loader;
+import android.content.Loader.OnLoadCompleteListener;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.RawContacts;
+import android.telephony.PhoneNumberUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.CheckBox;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+
+/**
+ * Initiates phone calls or a text message.
+ */
+public class PhoneNumberInteraction
+ implements OnLoadCompleteListener<Cursor>, OnClickListener {
+
+ public static final String EXTRA_KEY_ITEMS = "items";
+
+ /**
+ * A model object for capturing a phone number for a given contact.
+ */
+ static class PhoneItem implements Parcelable, Collapsible<PhoneItem> {
+ long id;
+ String phoneNumber;
+ String accountType;
+ long type;
+ String label;
+
+ public static Parcelable.Creator<PhoneItem> CREATOR = new Creator<PhoneItem>() {
+
+ public PhoneItem[] newArray(int size) {
+ return new PhoneItem[size];
+ }
+
+ public PhoneItem createFromParcel(Parcel source) {
+ PhoneItem item = new PhoneItem();
+ item.id = source.readLong();
+ item.phoneNumber = source.readString();
+ item.accountType = source.readString();
+ item.type = source.readLong();
+ item.label = source.readString();
+ return item;
+ }
+ };
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeLong(id);
+ dest.writeString(phoneNumber);
+ dest.writeString(accountType);
+ dest.writeLong(type);
+ dest.writeString(label);
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public boolean collapseWith(PhoneItem phoneItem) {
+ if (!shouldCollapseWith(phoneItem)) {
+ return false;
+ }
+ // Just keep the number and id we already have.
+ return true;
+ }
+
+ public boolean shouldCollapseWith(PhoneItem phoneItem) {
+ if (PhoneNumberUtils.compareStrictly(phoneNumber, phoneItem.phoneNumber)) {
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return phoneNumber;
+ }
+ }
+
+ /**
+ * A list adapter that populates the list of contact's phone numbers.
+ */
+ private class PhoneItemAdapter extends ArrayAdapter<PhoneItem> {
+ private final Sources mSources;
+
+ public PhoneItemAdapter(Context context) {
+ super(context, R.layout.phone_disambig_item, android.R.id.text2);
+ mSources = Sources.getInstance(context);
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ View view = super.getView(position, convertView, parent);
+
+ PhoneItem item = getItem(position);
+ ContactsSource source = mSources.getInflatedSource(item.accountType,
+ ContactsSource.LEVEL_SUMMARY);
+
+ // Obtain a string representation of the phone type specific to the
+ // ContactSource associated with that phone number
+ TextView typeView = (TextView)view.findViewById(android.R.id.text1);
+ DataKind kind = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE);
+ if (kind != null) {
+ ContentValues values = new ContentValues();
+ values.put(Phone.TYPE, item.type);
+ values.put(Phone.LABEL, item.label);
+ StringInflater header = mSendTextMessage ? kind.actionAltHeader : kind.actionHeader;
+ typeView.setText(header.inflateUsing(getContext(), values));
+ } else {
+ typeView.setText(R.string.call_other);
+ }
+ return view;
+ }
+ }
+
+ private static final String[] PHONE_NUMBER_PROJECTION = new String[] {
+ Phone._ID,
+ Phone.NUMBER,
+ Phone.IS_SUPER_PRIMARY,
+ RawContacts.ACCOUNT_TYPE,
+ Phone.TYPE,
+ Phone.LABEL
+ };
+
+ private static final String PHONE_NUMBER_SELECTION = Data.MIMETYPE + "='"
+ + Phone.CONTENT_ITEM_TYPE + "' AND " + Phone.NUMBER + " NOT NULL";
+
+ private final Context mContext;
+ private final OnDismissListener mDismissListener;
+ private final boolean mSendTextMessage;
+
+ private CursorLoader mLoader;
+
+ public PhoneNumberInteraction(Context context, boolean sendTextMessage,
+ DialogInterface.OnDismissListener dismissListener) {
+ mContext = context;
+ mSendTextMessage = sendTextMessage;
+ mDismissListener = dismissListener;
+ }
+
+ private void performAction(String phoneNumber) {
+ Intent intent;
+ if (mSendTextMessage) {
+ intent = new Intent(
+ Intent.ACTION_SENDTO, Uri.fromParts("sms", phoneNumber, null));
+ } else {
+ intent = new Intent(
+ Intent.ACTION_CALL_PRIVILEGED, Uri.fromParts("tel", phoneNumber, null));
+
+ }
+ startActivity(intent);
+ }
+
+ /**
+ * Initiates the interaction. This may result in a phone call or sms message started
+ * or a disambiguation dialog to determine which phone number should be used.
+ */
+ public void startInteraction(Uri contactUri) {
+ if (mLoader != null) {
+ mLoader.destroy();
+ }
+
+ mLoader = new CursorLoader(mContext,
+ Uri.withAppendedPath(contactUri, Contacts.Data.CONTENT_DIRECTORY),
+ PHONE_NUMBER_PROJECTION,
+ PHONE_NUMBER_SELECTION,
+ null,
+ null);
+ mLoader.registerListener(0, this);
+ startLoading(mLoader);
+ }
+
+ @Override
+ public void onLoadComplete(Loader<Cursor> loader, Cursor cursor) {
+ if (cursor == null) {
+ onDismiss();
+ return;
+ }
+
+ ArrayList<PhoneItem> phoneList = new ArrayList<PhoneItem>();
+ String primaryPhone = null;
+ try {
+ while (cursor.moveToNext()) {
+ if (cursor.getInt(cursor.getColumnIndex(Phone.IS_SUPER_PRIMARY)) != 0) {
+ // Found super primary, call it.
+ primaryPhone = cursor.getString(cursor.getColumnIndex(Phone.NUMBER));
+ break;
+ }
+
+ PhoneItem item = new PhoneItem();
+ item.id = cursor.getLong(cursor.getColumnIndex(Data._ID));
+ item.phoneNumber = cursor.getString(cursor.getColumnIndex(Phone.NUMBER));
+ item.accountType =
+ cursor.getString(cursor.getColumnIndex(RawContacts.ACCOUNT_TYPE));
+ item.type = cursor.getInt(cursor.getColumnIndex(Phone.TYPE));
+ item.label = cursor.getString(cursor.getColumnIndex(Phone.LABEL));
+
+ phoneList.add(item);
+ }
+ } finally {
+ cursor.close();
+ }
+
+ if (primaryPhone != null) {
+ performAction(primaryPhone);
+ onDismiss();
+ return;
+ }
+
+ Collapser.collapseList(phoneList);
+
+ if (phoneList.size() == 0) {
+ onDismiss();
+ } else if (phoneList.size() == 1) {
+ onDismiss();
+ performAction(phoneList.get(0).phoneNumber);
+ } else {
+ Bundle bundle = new Bundle();
+ bundle.putParcelableArrayList(EXTRA_KEY_ITEMS, phoneList);
+ showDialog(getDialogId(), bundle);
+ }
+ }
+
+ private void onDismiss() {
+ if (mDismissListener != null) {
+ mDismissListener.onDismiss(null);
+ }
+ }
+
+ private int getDialogId() {
+ return mSendTextMessage
+ ? R.id.dialog_phone_number_message_disambiguation
+ : R.id.dialog_phone_number_call_disambiguation;
+ }
+
+ public Dialog onCreateDialog(int id, Bundle bundle) {
+ if (id != getDialogId()) {
+ return null;
+ }
+
+ LayoutInflater inflater = LayoutInflater.from(mContext);
+ View setPrimaryView = inflater.inflate(R.layout.set_primary_checkbox, null);
+ AlertDialog dialog = new AlertDialog.Builder(mContext)
+ .setAdapter(new PhoneItemAdapter(mContext), this)
+ .setView(setPrimaryView)
+ .setTitle(mSendTextMessage
+ ? R.string.sms_disambig_title
+ : R.string.call_disambig_title)
+ .create();
+ dialog.setOnDismissListener(mDismissListener);
+ return dialog;
+ }
+
+ public boolean onPrepareDialog(int id, Dialog dialog, Bundle bundle) {
+ if (id != getDialogId()) {
+ return false;
+ }
+
+ ArrayList<PhoneItem> phoneList = bundle.getParcelableArrayList(EXTRA_KEY_ITEMS);
+
+ AlertDialog alertDialog = (AlertDialog)dialog;
+ PhoneItemAdapter adapter = (PhoneItemAdapter)alertDialog.getListView().getAdapter();
+ adapter.clear();
+ adapter.addAll(phoneList);
+
+ return true;
+ }
+
+ /**
+ * Handles the user selection in the disambiguation dialog.
+ */
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ AlertDialog alertDialog = (AlertDialog)dialog;
+ PhoneItemAdapter adapter = (PhoneItemAdapter)alertDialog.getListView().getAdapter();
+ PhoneItem phoneItem = adapter.getItem(which);
+ if (phoneItem != null) {
+ long id = phoneItem.id;
+ String phone = phoneItem.phoneNumber;
+
+ CheckBox checkBox = (CheckBox)alertDialog.findViewById(R.id.setPrimary);
+ if (checkBox.isChecked()) {
+ makePrimary(id);
+ }
+
+ performAction(phone);
+ }
+ }
+
+ /**
+ * Makes the selected phone number primary.
+ */
+ void makePrimary(long id) {
+ // TODO use a Saver
+ ContentValues values = new ContentValues(1);
+ values.put(Data.IS_SUPER_PRIMARY, 1);
+ Uri uri = ContentUris.withAppendedId(Data.CONTENT_URI, id);
+ mContext.getContentResolver().update(uri, values, null, null);
+ }
+
+ /* Visible for testing */
+ void showDialog(int dialogId, Bundle bundle) {
+ Activity activity = (Activity)mContext;
+ if (!activity.isFinishing()) {
+ activity.showDialog(dialogId, bundle);
+ }
+ }
+
+ /* Visible for testing */
+ void startActivity(Intent intent) {
+ mContext.startActivity(intent);
+ }
+
+ /* Visible for testing */
+ void startLoading(Loader<Cursor> loader) {
+ loader.startLoading();
+ }
+}
diff --git a/src/com/android/contacts/list/ContactBrowseListContextMenuAdapter.java b/src/com/android/contacts/list/ContactBrowseListContextMenuAdapter.java
new file mode 100644
index 0000000..b288a29
--- /dev/null
+++ b/src/com/android/contacts/list/ContactBrowseListContextMenuAdapter.java
@@ -0,0 +1,144 @@
+/*
+ * 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.list;
+
+import com.android.contacts.R;
+import com.android.contacts.util.PhoneCapabilityTester;
+import com.android.contacts.widget.ContextMenuAdapter;
+
+import android.content.Context;
+import android.net.Uri;
+import android.util.Log;
+import android.view.ContextMenu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.widget.AdapterView;
+
+/**
+ * A contextual menu adapter for the basic contact list.
+ */
+public class ContactBrowseListContextMenuAdapter implements ContextMenuAdapter {
+
+ private static final int MENU_ITEM_VIEW_CONTACT = 1;
+ private static final int MENU_ITEM_CALL = 2;
+ private static final int MENU_ITEM_SEND_SMS = 3;
+ private static final int MENU_ITEM_EDIT = 4;
+ private static final int MENU_ITEM_DELETE = 5;
+ private static final int MENU_ITEM_TOGGLE_STAR = 6;
+
+ private static final String TAG = "LightContactBrowserContextMenuAdapter";
+
+ private final ContactBrowseListFragment mContactListFragment;
+
+ public ContactBrowseListContextMenuAdapter(ContactBrowseListFragment fragment) {
+ this.mContactListFragment = fragment;
+ }
+
+ public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) {
+ AdapterView.AdapterContextMenuInfo info;
+ try {
+ info = (AdapterView.AdapterContextMenuInfo) menuInfo;
+ } catch (ClassCastException e) {
+ Log.wtf(TAG, "Bad menuInfo", e);
+ return;
+ }
+
+ ContactListAdapter adapter = mContactListFragment.getAdapter();
+ int headerViewsCount = mContactListFragment.getListView().getHeaderViewsCount();
+ int position = info.position - headerViewsCount;
+
+ // Setup the menu header
+ menu.setHeaderTitle(adapter.getContactDisplayName(position));
+
+ // View contact details
+ menu.add(0, MENU_ITEM_VIEW_CONTACT, 0, R.string.menu_viewContact);
+
+ if (adapter.getHasPhoneNumber(position)) {
+ final Context context = mContactListFragment.getContext();
+ boolean hasPhoneApp = PhoneCapabilityTester.isPhoneCallIntentRegistered(context);
+ boolean hasSmsApp = PhoneCapabilityTester.isSmsIntentRegistered(context);
+ // Calling contact
+ if (hasPhoneApp) menu.add(0, MENU_ITEM_CALL, 0, R.string.menu_call);
+ // Send SMS item
+ if (hasSmsApp) menu.add(0, MENU_ITEM_SEND_SMS, 0, R.string.menu_sendSMS);
+ }
+
+ // Star toggling
+ if (!adapter.isContactStarred(position)) {
+ menu.add(0, MENU_ITEM_TOGGLE_STAR, 0, R.string.menu_addStar);
+ } else {
+ menu.add(0, MENU_ITEM_TOGGLE_STAR, 0, R.string.menu_removeStar);
+ }
+
+ // Contact editing
+ menu.add(0, MENU_ITEM_EDIT, 0, R.string.menu_editContact);
+ menu.add(0, MENU_ITEM_DELETE, 0, R.string.menu_deleteContact);
+ }
+
+ public boolean onContextItemSelected(MenuItem item) {
+ AdapterView.AdapterContextMenuInfo info;
+ try {
+ info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
+ } catch (ClassCastException e) {
+ Log.wtf(TAG, "Bad menuInfo", e);
+ return false;
+ }
+
+ ContactListAdapter adapter = mContactListFragment.getAdapter();
+ int headerViewsCount = mContactListFragment.getListView().getHeaderViewsCount();
+ int position = info.position - headerViewsCount;
+
+ final Uri contactUri = adapter.getContactUri(position);
+ switch (item.getItemId()) {
+ case MENU_ITEM_VIEW_CONTACT: {
+ mContactListFragment.viewContact(contactUri, true);
+ return true;
+ }
+
+ case MENU_ITEM_TOGGLE_STAR: {
+ if (adapter.isContactStarred(position)) {
+ mContactListFragment.removeFromFavorites(contactUri);
+ } else {
+ mContactListFragment.addToFavorites(contactUri);
+ }
+ return true;
+ }
+
+ case MENU_ITEM_CALL: {
+ mContactListFragment.callContact(contactUri);
+ return true;
+ }
+
+ case MENU_ITEM_SEND_SMS: {
+ mContactListFragment.smsContact(contactUri);
+ return true;
+ }
+
+ case MENU_ITEM_EDIT: {
+ mContactListFragment.editContact(contactUri);
+ return true;
+ }
+
+ case MENU_ITEM_DELETE: {
+ mContactListFragment.deleteContact(contactUri);
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/src/com/android/contacts/list/ContactBrowseListFragment.java b/src/com/android/contacts/list/ContactBrowseListFragment.java
new file mode 100644
index 0000000..c09dc47
--- /dev/null
+++ b/src/com/android/contacts/list/ContactBrowseListFragment.java
@@ -0,0 +1,230 @@
+/*
+ * 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.list;
+
+import com.android.contacts.R;
+
+import android.app.LoaderManager.LoaderCallbacks;
+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.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Directory;
+import android.text.TextUtils;
+
+/**
+ * Fragment containing a contact list used for browsing (as compared to
+ * picking a contact with one of the PICK intents).
+ */
+public abstract class ContactBrowseListFragment extends
+ ContactEntryListFragment<ContactListAdapter> {
+
+ private static final String KEY_SELECTED_URI = "selectedUri";
+
+ private static final int SELECTED_ID_LOADER = -3;
+
+ private Uri mSelectedContactUri;
+ private long mSelectedContactDirectoryId;
+ private String mSelectedContactLookupKey;
+
+ private OnContactBrowserActionListener mListener;
+
+ private LoaderCallbacks<Cursor> mIdLoaderCallbacks = new LoaderCallbacks<Cursor>() {
+
+ @Override
+ public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+ return new CursorLoader(getContext(),
+ mSelectedContactUri,
+ new String[] { Contacts.LOOKUP_KEY },
+ null,
+ null,
+ null);
+ }
+
+ @Override
+ public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+ String lookupKey = null;
+ if (data != null) {
+ if (data.moveToFirst()) {
+ lookupKey = data.getString(0);
+ }
+ }
+ if (!TextUtils.equals(mSelectedContactLookupKey, lookupKey)) {
+ mSelectedContactLookupKey = lookupKey;
+ configureContactSelection();
+ }
+ return;
+ }
+ };
+
+ @Override
+ public void restoreSavedState(Bundle savedState) {
+ super.restoreSavedState(savedState);
+
+ if (savedState == null) {
+ return;
+ }
+
+ mSelectedContactUri = savedState.getParcelable(KEY_SELECTED_URI);
+ parseSelectedContactUri();
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putParcelable(KEY_SELECTED_URI, mSelectedContactUri);
+ }
+
+ @Override
+ public void onStart() {
+ // Refresh the currently selected lookup in case it changed while we were sleeping
+ startLoadingContactLookupKey();
+ super.onStart();
+ }
+
+ protected void startLoadingContactLookupKey() {
+ if (isSelectionVisible() && mSelectedContactUri != null &&
+ (mSelectedContactDirectoryId == Directory.DEFAULT ||
+ mSelectedContactDirectoryId == Directory.LOCAL_INVISIBLE)) {
+ getLoaderManager().restartLoader(SELECTED_ID_LOADER, null, mIdLoaderCallbacks);
+ } else {
+ getLoaderManager().stopLoader(SELECTED_ID_LOADER);
+ }
+ }
+
+ @Override
+ protected void prepareEmptyView() {
+ if (isSearchMode()) {
+ return;
+ } else if (isSyncActive()) {
+ if (hasIccCard()) {
+ setEmptyText(R.string.noContactsHelpTextWithSync);
+ } else {
+ setEmptyText(R.string.noContactsNoSimHelpTextWithSync);
+ }
+ } else {
+ if (hasIccCard()) {
+ setEmptyText(R.string.noContactsHelpText);
+ } else {
+ setEmptyText(R.string.noContactsNoSimHelpText);
+ }
+ }
+ }
+
+ public Uri getSelectedContactUri() {
+ return mSelectedContactUri;
+ }
+
+ public void setSelectedContactUri(Uri uri) {
+ if (mSelectedContactUri == null
+ || (mSelectedContactUri != null && !mSelectedContactUri.equals(uri))) {
+ mSelectedContactUri = uri;
+
+ parseSelectedContactUri();
+
+ // Configure the adapter to show the selection based on the lookup key extracted
+ // from the URI
+ configureAdapter();
+
+ // Also, launch a loader to pick up a new lookup key in case it has changed
+ startLoadingContactLookupKey();
+ }
+ }
+
+ private void parseSelectedContactUri() {
+ if (mSelectedContactUri != null) {
+ if (!mSelectedContactUri.toString()
+ .startsWith(Contacts.CONTENT_LOOKUP_URI.toString())) {
+ throw new IllegalStateException(
+ "Contact list contains a non-lookup URI: " + mSelectedContactUri);
+ }
+
+ String directoryParam =
+ mSelectedContactUri.getQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY);
+ mSelectedContactDirectoryId = TextUtils.isEmpty(directoryParam)
+ ? Directory.DEFAULT
+ : Long.parseLong(directoryParam);
+ mSelectedContactLookupKey =
+ Uri.encode(mSelectedContactUri.getPathSegments().get(2));
+ } else {
+ mSelectedContactDirectoryId = Directory.DEFAULT;
+ mSelectedContactLookupKey = null;
+ }
+ }
+
+ @Override
+ protected void configureAdapter() {
+ super.configureAdapter();
+ configureContactSelection();
+ }
+
+ /**
+ * Configures the adapter with the identity of the currently selected contact.
+ */
+ private void configureContactSelection() {
+ ContactListAdapter adapter = getAdapter();
+ if (adapter == null) {
+ return;
+ }
+
+ adapter.setSelectedContact(mSelectedContactDirectoryId, mSelectedContactLookupKey);
+ }
+
+ public void setOnContactListActionListener(OnContactBrowserActionListener listener) {
+ mListener = listener;
+ }
+
+ public void createNewContact() {
+ mListener.onCreateNewContactAction();
+ }
+
+ public void viewContact(Uri contactUri, boolean finishEditing) {
+ mListener.onViewContactAction(contactUri, finishEditing);
+ }
+
+ public void editContact(Uri contactUri) {
+ mListener.onEditContactAction(contactUri);
+ }
+
+ public void deleteContact(Uri contactUri) {
+ mListener.onDeleteContactAction(contactUri);
+ }
+
+ public void addToFavorites(Uri contactUri) {
+ mListener.onAddToFavoritesAction(contactUri);
+ }
+
+ public void removeFromFavorites(Uri contactUri) {
+ mListener.onRemoveFromFavoritesAction(contactUri);
+ }
+
+ public void callContact(Uri contactUri) {
+ mListener.onCallContactAction(contactUri);
+ }
+
+ public void smsContact(Uri contactUri) {
+ mListener.onSmsContactAction(contactUri);
+ }
+
+ @Override
+ protected void finish() {
+ super.finish();
+ mListener.onFinishAction();
+ }
+}
diff --git a/src/com/android/contacts/list/ContactEntryListAdapter.java b/src/com/android/contacts/list/ContactEntryListAdapter.java
new file mode 100644
index 0000000..ef0807a
--- /dev/null
+++ b/src/com/android/contacts/list/ContactEntryListAdapter.java
@@ -0,0 +1,399 @@
+/*
+ * 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.list;
+
+import com.android.contacts.ContactPhotoLoader;
+import com.android.contacts.ContactsSectionIndexer;
+import com.android.contacts.R;
+import com.android.contacts.widget.IndexerListAdapter;
+import com.android.contacts.widget.TextWithHighlightingFactory;
+
+import android.content.Context;
+import android.content.CursorLoader;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.provider.ContactsContract.ContactCounts;
+import android.provider.ContactsContract.Directory;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import java.util.HashSet;
+
+/**
+ * Common base class for various contact-related lists, e.g. contact list, phone number list
+ * etc.
+ */
+public abstract class ContactEntryListAdapter extends IndexerListAdapter {
+
+ private static final String TAG = "ContactEntryListAdapter";
+
+ /**
+ * The animation is used here to allocate animated name text views.
+ */
+ private TextWithHighlightingFactory mTextWithHighlightingFactory;
+ private int mDisplayOrder;
+ private int mSortOrder;
+ private boolean mNameHighlightingEnabled;
+
+ private boolean mDisplayPhotos;
+ private ContactPhotoLoader mPhotoLoader;
+
+ private String mQueryString;
+ private boolean mSearchMode;
+ private boolean mDirectorySearchEnabled;
+
+ private boolean mLoading = true;
+ private boolean mEmptyListEnabled = true;
+
+ private boolean mSelectionVisible;
+
+ public ContactEntryListAdapter(Context context) {
+ super(context, R.layout.list_section, R.id.header_text);
+ addPartitions();
+ }
+
+ protected void addPartitions() {
+ addPartition(createDefaultDirectoryPartition());
+ }
+
+ protected DirectoryPartition createDefaultDirectoryPartition() {
+ DirectoryPartition partition = new DirectoryPartition(true, true);
+ partition.setDirectoryId(Directory.DEFAULT);
+ partition.setDirectoryType(getContext().getString(R.string.contactsList));
+ partition.setPriorityDirectory(true);
+ return partition;
+ }
+
+ private int getPartitionByDirectoryId(long id) {
+ int count = getPartitionCount();
+ for (int i = 0; i < count; i++) {
+ Partition partition = getPartition(i);
+ if (partition instanceof DirectoryPartition) {
+ if (((DirectoryPartition)partition).getDirectoryId() == id) {
+ return i;
+ }
+ }
+ }
+ return -1;
+ }
+
+ public abstract String getContactDisplayName(int position);
+ public abstract void configureLoader(CursorLoader loader, long directoryId);
+
+ /**
+ * Marks all partitions as "loading"
+ */
+ public void onDataReload() {
+ boolean notify = false;
+ int count = getPartitionCount();
+ for (int i = 0; i < count; i++) {
+ Partition partition = getPartition(i);
+ if (partition instanceof DirectoryPartition) {
+ DirectoryPartition directoryPartition = (DirectoryPartition)partition;
+ if (!directoryPartition.isLoading()) {
+ directoryPartition.setLoading(true);
+ notify = true;
+ }
+ }
+ }
+ if (notify) {
+ notifyDataSetChanged();
+ }
+ }
+
+ public boolean isSearchMode() {
+ return mSearchMode;
+ }
+
+ public void setSearchMode(boolean flag) {
+ mSearchMode = flag;
+ }
+
+ public String getQueryString() {
+ return mQueryString;
+ }
+
+ public void setQueryString(String queryString) {
+ mQueryString = queryString;
+ }
+
+ public boolean isDirectorySearchEnabled() {
+ return mDirectorySearchEnabled;
+ }
+
+ public void setDirectorySearchEnabled(boolean flag) {
+ mDirectorySearchEnabled = flag;
+ }
+
+ public int getContactNameDisplayOrder() {
+ return mDisplayOrder;
+ }
+
+ public void setContactNameDisplayOrder(int displayOrder) {
+ mDisplayOrder = displayOrder;
+ }
+
+ public int getSortOrder() {
+ return mSortOrder;
+ }
+
+ public void setSortOrder(int sortOrder) {
+ mSortOrder = sortOrder;
+ }
+
+ public void setNameHighlightingEnabled(boolean flag) {
+ mNameHighlightingEnabled = flag;
+ }
+
+ public boolean isNameHighlightingEnabled() {
+ return mNameHighlightingEnabled;
+ }
+
+ public void setTextWithHighlightingFactory(TextWithHighlightingFactory factory) {
+ mTextWithHighlightingFactory = factory;
+ }
+
+ protected TextWithHighlightingFactory getTextWithHighlightingFactory() {
+ return mTextWithHighlightingFactory;
+ }
+
+ public void setPhotoLoader(ContactPhotoLoader photoLoader) {
+ mPhotoLoader = photoLoader;
+ }
+
+ protected ContactPhotoLoader getPhotoLoader() {
+ return mPhotoLoader;
+ }
+
+ public boolean getDisplayPhotos() {
+ return mDisplayPhotos;
+ }
+
+ public void setDisplayPhotos(boolean displayPhotos) {
+ mDisplayPhotos = displayPhotos;
+ }
+
+ public boolean isEmptyListEnabled() {
+ return mEmptyListEnabled;
+ }
+
+ public void setEmptyListEnabled(boolean flag) {
+ mEmptyListEnabled = flag;
+ }
+
+ public boolean isSelectionVisible() {
+ return mSelectionVisible;
+ }
+
+ public void setSelectionVisible(boolean flag) {
+ this.mSelectionVisible = flag;
+ }
+
+ public void configureDirectoryLoader(DirectoryListLoader loader) {
+ loader.setDirectorySearchEnabled(mDirectorySearchEnabled);
+ }
+
+ /**
+ * Updates partitions according to the directory meta-data contained in the supplied
+ * cursor. Takes ownership of the cursor and will close it.
+ */
+ public void changeDirectories(Cursor cursor) {
+ HashSet<Long> directoryIds = new HashSet<Long>();
+
+ int idColumnIndex = cursor.getColumnIndex(Directory._ID);
+ int directoryTypeColumnIndex = cursor.getColumnIndex(DirectoryListLoader.DIRECTORY_TYPE);
+ int displayNameColumnIndex = cursor.getColumnIndex(Directory.DISPLAY_NAME);
+
+ // TODO preserve the order of partition to match those of the cursor
+ // Phase I: add new directories
+ cursor.moveToPosition(-1);
+ while (cursor.moveToNext()) {
+ long id = cursor.getLong(idColumnIndex);
+ directoryIds.add(id);
+ if (getPartitionByDirectoryId(id) == -1) {
+ DirectoryPartition partition = new DirectoryPartition(false, true);
+ partition.setDirectoryId(id);
+ partition.setDirectoryType(cursor.getString(directoryTypeColumnIndex));
+ partition.setDisplayName(cursor.getString(displayNameColumnIndex));
+ addPartition(partition);
+ }
+ }
+
+ // Phase II: remove deleted directories
+ int count = getPartitionCount();
+ for (int i = count; --i >= 0; ) {
+ Partition partition = getPartition(i);
+ if (partition instanceof DirectoryPartition) {
+ long id = ((DirectoryPartition)partition).getDirectoryId();
+ if (!directoryIds.contains(id)) {
+ removePartition(i);
+ }
+ }
+ }
+
+ invalidate();
+ notifyDataSetChanged();
+ }
+
+ @Override
+ public void changeCursor(int partitionIndex, Cursor cursor) {
+ if (partitionIndex >= getPartitionCount()) {
+ // There is no partition for this data
+ return;
+ }
+
+ Partition partition = getPartition(partitionIndex);
+ if (partition instanceof DirectoryPartition) {
+ ((DirectoryPartition)partition).setLoading(false);
+ }
+
+ super.changeCursor(partitionIndex, cursor);
+
+ if (isSectionHeaderDisplayEnabled() && partitionIndex == getIndexedPartition()) {
+ updateIndexer(cursor);
+ }
+ }
+
+ public void changeCursor(Cursor cursor) {
+ changeCursor(0, cursor);
+ }
+
+ /**
+ * Updates the indexer, which is used to produce section headers.
+ */
+ private void updateIndexer(Cursor cursor) {
+ if (cursor == null) {
+ setIndexer(null);
+ return;
+ }
+
+ Bundle bundle = cursor.getExtras();
+ if (bundle.containsKey(ContactCounts.EXTRA_ADDRESS_BOOK_INDEX_TITLES)) {
+ String sections[] =
+ bundle.getStringArray(ContactCounts.EXTRA_ADDRESS_BOOK_INDEX_TITLES);
+ int counts[] = bundle.getIntArray(ContactCounts.EXTRA_ADDRESS_BOOK_INDEX_COUNTS);
+ setIndexer(new ContactsSectionIndexer(sections, counts));
+ } else {
+ setIndexer(null);
+ }
+ }
+
+ @Override
+ public int getViewTypeCount() {
+ // We need a separate view type for each item type, plus another one for
+ // each type with header, plus one for "other".
+ return getItemViewTypeCount() * 2 + 1;
+ }
+
+ @Override
+ public int getItemViewType(int partitionIndex, int position) {
+ int type = super.getItemViewType(partitionIndex, position);
+ if (isSectionHeaderDisplayEnabled() && partitionIndex == getIndexedPartition()) {
+ Placement placement = getItemPlacementInSection(position);
+ return placement.firstInSection ? type : getItemViewTypeCount() + type;
+ } else {
+ return type;
+ }
+ }
+
+ @Override
+ public boolean isEmpty() {
+ // TODO
+// if (contactsListActivity.mProviderStatus != ProviderStatus.STATUS_NORMAL) {
+// return true;
+// }
+
+ if (!mEmptyListEnabled) {
+ return false;
+ } else if (isSearchMode()) {
+ return TextUtils.isEmpty(getQueryString());
+ } else if (mLoading) {
+ // We don't want the empty state to show when loading.
+ return false;
+ } else {
+ return super.isEmpty();
+ }
+ }
+
+ /**
+ * Changes visibility parameters for the default directory partition.
+ */
+ public void configureDefaultPartition(boolean showIfEmpty, boolean hasHeader) {
+ int defaultPartitionIndex = -1;
+ int count = getPartitionCount();
+ for (int i = 0; i < count; i++) {
+ Partition partition = getPartition(i);
+ if (partition instanceof DirectoryPartition &&
+ ((DirectoryPartition)partition).getDirectoryId() == Directory.DEFAULT) {
+ defaultPartitionIndex = i;
+ break;
+ }
+ }
+ if (defaultPartitionIndex != -1) {
+ setShowIfEmpty(defaultPartitionIndex, showIfEmpty);
+ setHasHeader(defaultPartitionIndex, hasHeader);
+ }
+ }
+
+ @Override
+ protected View newHeaderView(Context context, int partition, Cursor cursor,
+ ViewGroup parent) {
+ LayoutInflater inflater = LayoutInflater.from(context);
+ return inflater.inflate(R.layout.directory_header, parent, false);
+ }
+
+ @Override
+ protected void bindHeaderView(View view, int partitionIndex, Cursor cursor) {
+ Partition partition = getPartition(partitionIndex);
+ if (!(partition instanceof DirectoryPartition)) {
+ return;
+ }
+
+ DirectoryPartition directoryPartition = (DirectoryPartition)partition;
+ TextView directoryTypeTextView = (TextView)view.findViewById(R.id.directory_type);
+ directoryTypeTextView.setText(directoryPartition.getDirectoryType());
+ TextView displayNameTextView = (TextView)view.findViewById(R.id.display_name);
+ if (!TextUtils.isEmpty(directoryPartition.getDisplayName())) {
+ displayNameTextView.setText(directoryPartition.getDisplayName());
+ displayNameTextView.setVisibility(View.VISIBLE);
+ } else {
+ displayNameTextView.setVisibility(View.GONE);
+ }
+
+ TextView countText = (TextView)view.findViewById(R.id.count);
+ if (directoryPartition.isLoading()) {
+ countText.setText(R.string.search_results_searching);
+ } else {
+ int count = cursor == null ? 0 : cursor.getCount();
+ countText.setText(getQuantityText(count, R.string.listFoundAllContactsZero,
+ R.plurals.searchFoundContacts));
+ }
+ }
+
+ // TODO: fix PluralRules to handle zero correctly and use Resources.getQuantityText directly
+ public String getQuantityText(int count, int zeroResourceId, int pluralResourceId) {
+ if (count == 0) {
+ return getContext().getString(zeroResourceId);
+ } else {
+ String format = getContext().getResources()
+ .getQuantityText(pluralResourceId, count).toString();
+ return String.format(format, count);
+ }
+ }
+}
diff --git a/src/com/android/contacts/list/ContactEntryListFragment.java b/src/com/android/contacts/list/ContactEntryListFragment.java
new file mode 100644
index 0000000..1315922
--- /dev/null
+++ b/src/com/android/contacts/list/ContactEntryListFragment.java
@@ -0,0 +1,988 @@
+/*
+ * 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.list;
+
+import com.android.common.widget.CompositeCursorAdapter.Partition;
+import com.android.contacts.ContactEntryListView;
+import com.android.contacts.ContactListEmptyView;
+import com.android.contacts.ContactPhotoLoader;
+import com.android.contacts.ContactsSearchManager;
+import com.android.contacts.R;
+import com.android.contacts.ui.ContactsPreferences;
+import com.android.contacts.widget.ContextMenuAdapter;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.app.Activity;
+import android.app.Fragment;
+import android.app.LoaderManager;
+import android.app.LoaderManager.LoaderCallbacks;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.CursorLoader;
+import android.content.IContentService;
+import android.content.Intent;
+import android.content.Loader;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.Directory;
+import android.provider.ContactsContract.ProviderStatus;
+import android.provider.Settings;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnFocusChangeListener;
+import android.view.View.OnTouchListener;
+import android.view.ViewGroup;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.AbsListView;
+import android.widget.AbsListView.OnScrollListener;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.Button;
+import android.widget.ListView;
+import android.widget.TextView;
+
+/**
+ * Common base class for various contact-related list fragments.
+ */
+public abstract class ContactEntryListFragment<T extends ContactEntryListAdapter>
+ extends Fragment
+ implements OnItemClickListener, OnScrollListener, OnFocusChangeListener, OnTouchListener,
+ LoaderCallbacks<Cursor> {
+
+ // TODO: Make this protected. This should not be used from the ContactBrowserActivity but
+ // instead use the new startActivityWithResultFromFragment API
+ public static final int ACTIVITY_REQUEST_CODE_PICKER = 1;
+
+ private static final String TAG = "ContactEntryListFragment";
+
+ private static final String KEY_LIST_STATE = "liststate";
+ private static final String KEY_SECTION_HEADER_DISPLAY_ENABLED = "sectionHeaderDisplayEnabled";
+ private static final String KEY_PHOTO_LOADER_ENABLED = "photoLoaderEnabled";
+ private static final String KEY_SEARCH_MODE = "searchMode";
+ private static final String KEY_AIZY_ENABLED = "aizyEnabled";
+ private static final String KEY_QUERY_STRING = "queryString";
+ private static final String KEY_DIRECTORY_SEARCH_ENABLED = "directorySearchEnabled";
+ private static final String KEY_SELECTION_VISIBLE = "selectionVisible";
+ private static final String KEY_REQUEST = "request";
+ private static final String KEY_LEGACY_COMPATIBILITY = "legacyCompatibility";
+
+ private static final String DIRECTORY_ID_ARG_KEY = "directoryId";
+
+ private static final int DIRECTORY_LOADER_ID = -1;
+
+ private static final int DIRECTORY_SEARCH_DELAY_MILLIS = 300;
+ private static final int DIRECTORY_SEARCH_MESSAGE = 1;
+
+ private boolean mSectionHeaderDisplayEnabled;
+ private boolean mPhotoLoaderEnabled;
+ private boolean mSearchMode;
+ private boolean mAizyEnabled;
+ private String mQueryString;
+ private boolean mDirectorySearchEnabled;
+ private boolean mSelectionVisible;
+ private ContactsRequest mRequest;
+ private boolean mLegacyCompatibility;
+
+ private T mAdapter;
+ private View mView;
+ private ListView mListView;
+ private ContactListAizyView mAizy;
+
+ /**
+ * Used for keeping track of the scroll state of the list.
+ */
+ private Parcelable mListState;
+
+ private int mDisplayOrder;
+ private int mSortOrder;
+
+ private ContextMenuAdapter mContextMenuAdapter;
+ private ContactPhotoLoader mPhotoLoader;
+ private ContactListEmptyView mEmptyView;
+ private ProviderStatusLoader mProviderStatusLoader;
+ private ContactsPreferences mContactsPrefs;
+
+ private int mProviderStatus = ProviderStatus.STATUS_NORMAL;
+
+ private boolean mForceLoad;
+ private boolean mLoadDirectoryList;
+
+ /**
+ * Indicates whether we are doing the initial complete load of data (false) or
+ * a refresh caused by a change notification (true)
+ */
+ private boolean mLoadPriorityDirectoriesOnly;
+
+ private Context mContext;
+
+ private LoaderManager mLoaderManager;
+
+ private Handler mDelayedDirectorySearchHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what == DIRECTORY_SEARCH_MESSAGE) {
+ loadDirectoryPartition(msg.arg1, (DirectoryPartition) msg.obj);
+ }
+ }
+ };
+
+ protected abstract View inflateView(LayoutInflater inflater, ViewGroup container);
+ protected abstract T createListAdapter();
+
+ /**
+ * @param position Please note that the position is already adjusted for
+ * header views, so "0" means the first list item below header
+ * views.
+ */
+ protected abstract void onItemClick(int position, long id);
+
+ @Override
+ public void onAttach(Activity activity) {
+ super.onAttach(activity);
+ setContext(activity);
+ setLoaderManager(super.getLoaderManager());
+ }
+
+ /**
+ * Sets a context for the fragment in the unit test environment.
+ */
+ public void setContext(Context context) {
+ mContext = context;
+ configurePhotoLoader();
+ }
+
+ public Context getContext() {
+ return mContext;
+ }
+
+ /**
+ * Overrides a loader manager for use in unit tests.
+ */
+ public void setLoaderManager(LoaderManager loaderManager) {
+ mLoaderManager = loaderManager;
+ }
+
+ @Override
+ public LoaderManager getLoaderManager() {
+ return mLoaderManager;
+ }
+
+ public T getAdapter() {
+ return mAdapter;
+ }
+
+ @Override
+ public View getView() {
+ return mView;
+ }
+
+ public ListView getListView() {
+ return mListView;
+ }
+
+ public ContactListEmptyView getEmptyView() {
+ return mEmptyView;
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putBoolean(KEY_SECTION_HEADER_DISPLAY_ENABLED, mSectionHeaderDisplayEnabled);
+ outState.putBoolean(KEY_PHOTO_LOADER_ENABLED, mPhotoLoaderEnabled);
+ outState.putBoolean(KEY_SEARCH_MODE, mSearchMode);
+ outState.putBoolean(KEY_AIZY_ENABLED, mAizyEnabled);
+ outState.putBoolean(KEY_DIRECTORY_SEARCH_ENABLED, mDirectorySearchEnabled);
+ outState.putBoolean(KEY_SELECTION_VISIBLE, mSelectionVisible);
+ outState.putBoolean(KEY_LEGACY_COMPATIBILITY, mLegacyCompatibility);
+ outState.putString(KEY_QUERY_STRING, mQueryString);
+ outState.putParcelable(KEY_REQUEST, mRequest);
+
+ if (mListView != null) {
+ outState.putParcelable(KEY_LIST_STATE, mListView.onSaveInstanceState());
+ }
+ }
+
+ @Override
+ public void onCreate(Bundle savedState) {
+ super.onCreate(savedState);
+ restoreSavedState(savedState);
+ }
+
+ public void restoreSavedState(Bundle savedState) {
+ if (savedState == null) {
+ return;
+ }
+
+ mSectionHeaderDisplayEnabled = savedState.getBoolean(KEY_SECTION_HEADER_DISPLAY_ENABLED);
+ mPhotoLoaderEnabled = savedState.getBoolean(KEY_PHOTO_LOADER_ENABLED);
+ mSearchMode = savedState.getBoolean(KEY_SEARCH_MODE);
+ mAizyEnabled = savedState.getBoolean(KEY_AIZY_ENABLED);
+ mDirectorySearchEnabled = savedState.getBoolean(KEY_DIRECTORY_SEARCH_ENABLED);
+ mSelectionVisible = savedState.getBoolean(KEY_SELECTION_VISIBLE);
+ mLegacyCompatibility = savedState.getBoolean(KEY_LEGACY_COMPATIBILITY);
+ mQueryString = savedState.getString(KEY_QUERY_STRING);
+ mRequest = savedState.getParcelable(KEY_REQUEST);
+
+ // Retrieve list state. This will be applied in onLoadFinished
+ mListState = savedState.getParcelable(KEY_LIST_STATE);
+ }
+
+ /**
+ * Returns the parsed intent that started the activity hosting this fragment.
+ */
+ public ContactsRequest getContactsRequest() {
+ return mRequest;
+ }
+
+ /**
+ * Sets a parsed intent that started the activity hosting this fragment.
+ */
+ public void setContactsRequest(ContactsRequest request) {
+ mRequest = request;
+ }
+
+ @Override
+ public void onStart() {
+
+ if (mContactsPrefs == null) {
+ mContactsPrefs = new ContactsPreferences(mContext);
+ }
+
+ if (mProviderStatusLoader == null) {
+ mProviderStatusLoader = new ProviderStatusLoader(mContext);
+ }
+
+ loadPreferences(mContactsPrefs);
+
+ if (mListView instanceof ContactEntryListView) {
+ ContactEntryListView listView = (ContactEntryListView)mListView;
+ listView.setHighlightNamesWhenScrolling(isNameHighlighingEnabled());
+ }
+
+ mForceLoad = false;
+ mLoadDirectoryList = true;
+ mLoadPriorityDirectoriesOnly = true;
+
+ startLoading();
+ super.onStart();
+ }
+
+ protected void startLoading() {
+ configureAdapter();
+ int partitionCount = mAdapter.getPartitionCount();
+ for (int i = 0; i < partitionCount; i++) {
+ Partition partition = mAdapter.getPartition(i);
+ if (partition instanceof DirectoryPartition) {
+ DirectoryPartition directoryPartition = (DirectoryPartition)partition;
+ if (mLoadPriorityDirectoriesOnly == directoryPartition.isPriorityDirectory()) {
+ startLoadingDirectoryPartition(i);
+ }
+ } else {
+ getLoaderManager().initLoader(i, null, this);
+ }
+ }
+ }
+
+ @Override
+ public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+ if (id == DIRECTORY_LOADER_ID) {
+ DirectoryListLoader loader = new DirectoryListLoader(mContext);
+ mAdapter.configureDirectoryLoader(loader);
+ return loader;
+ } else {
+ CursorLoader loader = new CursorLoader(mContext, null, null, null, null, null);
+ long directoryId = args != null && args.containsKey(DIRECTORY_ID_ARG_KEY)
+ ? args.getLong(DIRECTORY_ID_ARG_KEY)
+ : Directory.DEFAULT;
+ mAdapter.configureLoader(loader, directoryId);
+ return loader;
+ }
+ }
+
+ private void startLoadingDirectoryPartition(int partitionIndex) {
+ DirectoryPartition partition = (DirectoryPartition)mAdapter.getPartition(partitionIndex);
+ long directoryId = partition.getDirectoryId();
+ if (mForceLoad) {
+ if (directoryId == Directory.DEFAULT) {
+ loadDirectoryPartition(partitionIndex, partition);
+ } else {
+ loadDirectoryPartitionDelayed(partitionIndex, partition);
+ }
+ } else {
+ Bundle args = new Bundle();
+ args.putLong(DIRECTORY_ID_ARG_KEY, directoryId);
+ getLoaderManager().initLoader(partitionIndex, args, this);
+ }
+ }
+
+ /**
+ * Queues up a delayed request to search the specified directory. Since
+ * directory search will likely introduce a lot of network traffic, we want
+ * to wait for a pause in the user's typing before sending a directory request.
+ */
+ private void loadDirectoryPartitionDelayed(int partitionIndex, DirectoryPartition partition) {
+ mDelayedDirectorySearchHandler.removeMessages(DIRECTORY_SEARCH_MESSAGE, partition);
+ Message msg = mDelayedDirectorySearchHandler.obtainMessage(
+ DIRECTORY_SEARCH_MESSAGE, partitionIndex, 0, partition);
+ mDelayedDirectorySearchHandler.sendMessageDelayed(msg, DIRECTORY_SEARCH_DELAY_MILLIS);
+ }
+
+ /**
+ * Loads the directory partition.
+ */
+ protected void loadDirectoryPartition(int partitionIndex, DirectoryPartition partition) {
+ Bundle args = new Bundle();
+ args.putLong(DIRECTORY_ID_ARG_KEY, partition.getDirectoryId());
+ getLoaderManager().restartLoader(partitionIndex, args, this);
+ }
+
+ /**
+ * Cancels all queued directory loading requests.
+ */
+ private void removePendingDirectorySearchRequests() {
+ mDelayedDirectorySearchHandler.removeMessages(DIRECTORY_SEARCH_MESSAGE);
+ }
+
+ @Override
+ public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+ if (!checkProviderStatus(false)) {
+ if (data != null) {
+ data.close();
+ }
+ return;
+ }
+
+ int loaderId = loader.getId();
+ if (loaderId == DIRECTORY_LOADER_ID) {
+ removePendingDirectorySearchRequests();
+ mAdapter.changeDirectories(data);
+ } else {
+ onPartitionLoaded(loaderId, data);
+ }
+
+ if (isDirectorySearchEnabled()) {
+ if (mLoadDirectoryList) {
+ mLoadDirectoryList = false;
+ getLoaderManager().initLoader(DIRECTORY_LOADER_ID, null, this);
+ } else if (mLoadPriorityDirectoriesOnly) {
+ mLoadPriorityDirectoriesOnly = false;
+ startLoading();
+ }
+ }
+
+// TODO fix the empty view
+// if (mEmptyView != null && (data == null || data.getCount() == 0)) {
+// prepareEmptyView();
+// }
+ }
+
+ protected void onPartitionLoaded(int partitionIndex, Cursor data) {
+ mAdapter.changeCursor(partitionIndex, data);
+ showCount(partitionIndex, data);
+ if (partitionIndex == mAdapter.getIndexedPartition()) {
+ mAizy.readFromIndexer(mAdapter.getIndexer());
+ }
+
+ // TODO should probably only restore instance state after all directories are loaded
+ completeRestoreInstanceState();
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ mAdapter.clearPartitions();
+ }
+
+ protected void reloadData() {
+ mAdapter.onDataReload();
+ mLoadPriorityDirectoriesOnly = true;
+ mForceLoad = true;
+ startLoading();
+ }
+
+ /**
+ * Configures the empty view. It is called when we are about to populate
+ * the list with an empty cursor.
+ */
+ protected void prepareEmptyView() {
+ }
+
+ /**
+ * Shows the count of entries included in the list. The default
+ * implementation does nothing.
+ */
+ protected void showCount(int partitionIndex, Cursor data) {
+ }
+
+ /**
+ * Provides logic that dismisses this fragment. The default implementation
+ * does nothing.
+ */
+ protected void finish() {
+ }
+
+ public void setSectionHeaderDisplayEnabled(boolean flag) {
+ mSectionHeaderDisplayEnabled = flag;
+ if (mAdapter != null) {
+ mAdapter.setSectionHeaderDisplayEnabled(flag);
+ }
+ configureAizy();
+ }
+
+ public boolean isSectionHeaderDisplayEnabled() {
+ return mSectionHeaderDisplayEnabled;
+ }
+
+ public void setAizyEnabled(boolean flag) {
+ mAizyEnabled = flag;
+ configureAizy();
+ }
+
+ public boolean isAizyEnabled() {
+ return mAizyEnabled;
+ }
+
+ private void configureAizy() {
+ boolean hasAisy = isAizyEnabled() && isSectionHeaderDisplayEnabled();
+
+ if (mListView != null) {
+ mListView.setFastScrollEnabled(!hasAisy);
+ mListView.setVerticalScrollBarEnabled(!hasAisy);
+ }
+ if (mAizy != null) {
+ if (hasAisy) {
+ mAizy.setVisibility(View.VISIBLE);
+ } else if (isAizyEnabled()) {
+ mAizy.setVisibility(View.INVISIBLE);
+ } else {
+ mAizy.setVisibility(View.GONE);
+ }
+ }
+ }
+
+ public void setPhotoLoaderEnabled(boolean flag) {
+ mPhotoLoaderEnabled = flag;
+ configurePhotoLoader();
+ }
+
+ public boolean isPhotoLoaderEnabled() {
+ return mPhotoLoaderEnabled;
+ }
+
+ /**
+ * Returns true if the list is supposed to visually highlight the selected item.
+ */
+ public boolean isSelectionVisible() {
+ return mSelectionVisible;
+ }
+
+ public void setSelectionVisible(boolean flag) {
+ this.mSelectionVisible = flag;
+ }
+
+ public void setSearchMode(boolean flag) {
+ if (mSearchMode != flag) {
+ mSearchMode = flag;
+ setSectionHeaderDisplayEnabled(!mSearchMode);
+
+ if (mAdapter != null) {
+ mAdapter.clearPartitions();
+ mAdapter.setSearchMode(flag);
+ mAdapter.setPinnedPartitionHeadersEnabled(flag);
+ mAdapter.configureDefaultPartition(flag, flag);
+ reloadData();
+ }
+
+ if (mListView != null) {
+ mListView.setFastScrollEnabled(!flag);
+ }
+ }
+ }
+
+ public boolean isSearchMode() {
+ return mSearchMode;
+ }
+
+ public String getQueryString() {
+ return mQueryString;
+ }
+
+ public void setQueryString(String queryString) {
+ if (!TextUtils.equals(mQueryString, queryString)) {
+ mQueryString = queryString;
+ if (mAdapter != null) {
+ mAdapter.setQueryString(queryString);
+ reloadData();
+ }
+ }
+ }
+
+ public boolean isDirectorySearchEnabled() {
+ return mDirectorySearchEnabled;
+ }
+
+ public void setDirectorySearchEnabled(boolean flag) {
+ mDirectorySearchEnabled = flag;
+ }
+
+ public boolean isLegacyCompatibilityMode() {
+ return mLegacyCompatibility;
+ }
+
+ public void setLegacyCompatibilityMode(boolean flag) {
+ mLegacyCompatibility = flag;
+ }
+
+ protected int getContactNameDisplayOrder() {
+ return mDisplayOrder;
+ }
+
+ protected void setContactNameDisplayOrder(int displayOrder) {
+ mDisplayOrder = displayOrder;
+ if (mAdapter != null) {
+ mAdapter.setContactNameDisplayOrder(displayOrder);
+ }
+ }
+
+ public int getSortOrder() {
+ return mSortOrder;
+ }
+
+ public void setSortOrder(int sortOrder) {
+ mSortOrder = sortOrder;
+ if (mAdapter != null) {
+ mAdapter.setSortOrder(sortOrder);
+ }
+ }
+
+ public void setContextMenuAdapter(ContextMenuAdapter adapter) {
+ mContextMenuAdapter = adapter;
+ if (mListView != null) {
+ mListView.setOnCreateContextMenuListener(adapter);
+ }
+ }
+
+ public ContextMenuAdapter getContextMenuAdapter() {
+ return mContextMenuAdapter;
+ }
+
+ protected void loadPreferences(ContactsPreferences contactsPrefs) {
+ setContactNameDisplayOrder(contactsPrefs.getDisplayOrder());
+ setSortOrder(contactsPrefs.getSortOrder());
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ onCreateView(inflater, container);
+
+ mAdapter = createListAdapter();
+
+ boolean searchMode = isSearchMode();
+ mAdapter.setSearchMode(searchMode);
+ mAdapter.configureDefaultPartition(searchMode, searchMode);
+ mAdapter.setPhotoLoader(mPhotoLoader);
+ mListView.setAdapter(mAdapter);
+
+ return mView;
+ }
+
+ protected void onCreateView(LayoutInflater inflater, ViewGroup container) {
+ mView = inflateView(inflater, container);
+
+ mListView = (ListView)mView.findViewById(android.R.id.list);
+ if (mListView == null) {
+ throw new RuntimeException(
+ "Your content must have a ListView whose id attribute is " +
+ "'android.R.id.list'");
+ }
+
+ View emptyView = mView.findViewById(com.android.internal.R.id.empty);
+ if (emptyView != null) {
+ mListView.setEmptyView(emptyView);
+ if (emptyView instanceof ContactListEmptyView) {
+ mEmptyView = (ContactListEmptyView)emptyView;
+ }
+ }
+
+ mListView.setOnItemClickListener(this);
+ mListView.setOnFocusChangeListener(this);
+ mListView.setOnTouchListener(this);
+ mListView.setFastScrollEnabled(!isSearchMode());
+
+ // Tell list view to not show dividers. We'll do it ourself so that we can *not* show
+ // them when an A-Z headers is visible.
+ mListView.setDividerHeight(0);
+
+ // We manually save/restore the listview state
+ mListView.setSaveEnabled(false);
+
+ if (mContextMenuAdapter != null) {
+ mListView.setOnCreateContextMenuListener(mContextMenuAdapter);
+ }
+
+ mAizy = (ContactListAizyView) mView.findViewById(R.id.contacts_list_aizy);
+ mAizy.setListener(new ContactListAizyView.Listener() {
+ @Override
+ public void onScroll(int position) {
+ mListView.setSelectionFromTop(position + mListView.getHeaderViewsCount(), 0);
+ }
+ });
+
+ configureAizy();
+ configurePhotoLoader();
+ }
+
+ protected void configurePhotoLoader() {
+ if (isPhotoLoaderEnabled() && mContext != null) {
+ if (mPhotoLoader == null) {
+ mPhotoLoader = new ContactPhotoLoader(mContext, R.drawable.ic_contact_list_picture);
+ }
+ if (mListView != null) {
+ mListView.setOnScrollListener(this);
+ }
+ if (mAdapter != null) {
+ mAdapter.setPhotoLoader(mPhotoLoader);
+ }
+ }
+ }
+
+ protected void configureAdapter() {
+ if (mAdapter == null) {
+ return;
+ }
+
+ mAdapter.setQueryString(mQueryString);
+ mAdapter.setDirectorySearchEnabled(mDirectorySearchEnabled);
+ mAdapter.setPinnedPartitionHeadersEnabled(mSearchMode);
+ mAdapter.setContactNameDisplayOrder(mDisplayOrder);
+ mAdapter.setSortOrder(mSortOrder);
+ mAdapter.setNameHighlightingEnabled(isNameHighlighingEnabled());
+ mAdapter.setSectionHeaderDisplayEnabled(mSectionHeaderDisplayEnabled);
+ mAdapter.setSelectionVisible(mSelectionVisible);
+ }
+
+ protected boolean isNameHighlighingEnabled() {
+ // When sort order and display order contradict each other, we want to
+ // highlight the part of the name used for sorting.
+ if (mSortOrder == ContactsContract.Preferences.SORT_ORDER_PRIMARY &&
+ mDisplayOrder == ContactsContract.Preferences.DISPLAY_ORDER_ALTERNATIVE) {
+ return true;
+ } else if (mSortOrder == ContactsContract.Preferences.SORT_ORDER_ALTERNATIVE &&
+ mDisplayOrder == ContactsContract.Preferences.DISPLAY_ORDER_PRIMARY) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
+ int totalItemCount) {
+ }
+
+ public void onScrollStateChanged(AbsListView view, int scrollState) {
+ if (scrollState == OnScrollListener.SCROLL_STATE_FLING) {
+ mPhotoLoader.pause();
+ } else if (isPhotoLoaderEnabled()) {
+ mPhotoLoader.resume();
+ }
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ registerProviderStatusObserver();
+
+ if (isPhotoLoaderEnabled()) {
+ mPhotoLoader.resume();
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ if (isPhotoLoaderEnabled()) {
+ mPhotoLoader.stop();
+ }
+ super.onDestroy();
+ }
+
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ hideSoftKeyboard();
+
+ int adjPosition = position - mListView.getHeaderViewsCount();
+ if (adjPosition >= 0) {
+ onItemClick(adjPosition, id);
+ }
+ }
+
+ private void hideSoftKeyboard() {
+ // Hide soft keyboard, if visible
+ InputMethodManager inputMethodManager = (InputMethodManager)
+ mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
+ inputMethodManager.hideSoftInputFromWindow(mListView.getWindowToken(), 0);
+ }
+
+ /**
+ * Dismisses the soft keyboard when the list takes focus.
+ */
+ public void onFocusChange(View view, boolean hasFocus) {
+ if (view == mListView && hasFocus) {
+ hideSoftKeyboard();
+ }
+ }
+
+ /**
+ * Dismisses the soft keyboard when the list is touched.
+ */
+ public boolean onTouch(View view, MotionEvent event) {
+ if (view == mListView) {
+ hideSoftKeyboard();
+ }
+ return false;
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ removePendingDirectorySearchRequests();
+ unregisterProviderStatusObserver();
+ }
+
+ /**
+ * Dismisses the search UI along with the keyboard if the filter text is empty.
+ */
+ public void onClose() {
+ hideSoftKeyboard();
+ finish();
+ }
+
+ /**
+ * Restore the list state after the adapter is populated.
+ */
+ protected void completeRestoreInstanceState() {
+ if (mListState != null) {
+ mListView.onRestoreInstanceState(mListState);
+ mListState = null;
+ }
+ }
+
+ private ContentObserver mProviderStatusObserver = new ContentObserver(new Handler()) {
+ @Override
+ public void onChange(boolean selfChange) {
+ checkProviderStatus(true);
+ }
+ };
+
+ /**
+ * Register an observer for provider status changes - we will need to
+ * reflect them in the UI.
+ */
+ private void registerProviderStatusObserver() {
+ mContext.getContentResolver().registerContentObserver(ProviderStatus.CONTENT_URI,
+ false, mProviderStatusObserver);
+ }
+
+ /**
+ * Register an observer for provider status changes - we will need to
+ * reflect them in the UI.
+ */
+ private void unregisterProviderStatusObserver() {
+ mContext.getContentResolver().unregisterContentObserver(mProviderStatusObserver);
+ }
+
+ /**
+ * Obtains the contacts provider status and configures the UI accordingly.
+ *
+ * @param loadData true if the method needs to start a query when the
+ * provider is in the normal state
+ * @return true if the provider status is normal
+ */
+ private boolean checkProviderStatus(boolean loadData) {
+ View importFailureView = findViewById(R.id.import_failure);
+ if (importFailureView == null) {
+ return true;
+ }
+
+ // This query can be performed on the UI thread because
+ // the API explicitly allows such use.
+ Cursor cursor = mContext.getContentResolver().query(ProviderStatus.CONTENT_URI,
+ new String[] { ProviderStatus.STATUS, ProviderStatus.DATA1 }, null, null, null);
+ if (cursor != null) {
+ try {
+ if (cursor.moveToFirst()) {
+ int status = cursor.getInt(0);
+ if (status != mProviderStatus) {
+ mProviderStatus = status;
+ switch (status) {
+ case ProviderStatus.STATUS_NORMAL:
+ mAdapter.notifyDataSetInvalidated();
+ if (loadData) {
+ reloadData();
+ }
+ break;
+
+ case ProviderStatus.STATUS_CHANGING_LOCALE:
+ setEmptyText(R.string.locale_change_in_progress);
+ mAdapter.changeCursor(null);
+ mAdapter.notifyDataSetInvalidated();
+ break;
+
+ case ProviderStatus.STATUS_UPGRADING:
+ setEmptyText(R.string.upgrade_in_progress);
+ mAdapter.changeCursor(null);
+ mAdapter.notifyDataSetInvalidated();
+ break;
+
+ case ProviderStatus.STATUS_UPGRADE_OUT_OF_MEMORY:
+ long size = cursor.getLong(1);
+ String message = mContext.getResources().getString(
+ R.string.upgrade_out_of_memory, new Object[] {size});
+ TextView messageView = (TextView) findViewById(R.id.emptyText);
+ messageView.setText(message);
+ messageView.setVisibility(View.VISIBLE);
+ configureImportFailureView(importFailureView);
+ mAdapter.changeCursor(null);
+ mAdapter.notifyDataSetInvalidated();
+ break;
+ }
+ }
+ }
+ } finally {
+ cursor.close();
+ }
+ }
+
+ importFailureView.setVisibility(
+ mProviderStatus == ProviderStatus.STATUS_UPGRADE_OUT_OF_MEMORY
+ ? View.VISIBLE
+ : View.GONE);
+ return mProviderStatus == ProviderStatus.STATUS_NORMAL;
+ }
+
+ private void configureImportFailureView(View importFailureView) {
+
+ OnClickListener listener = new OnClickListener(){
+
+ public void onClick(View v) {
+ switch(v.getId()) {
+ case R.id.import_failure_uninstall_apps: {
+ // TODO break into a separate method
+ startActivity(new Intent(Settings.ACTION_MANAGE_APPLICATIONS_SETTINGS));
+ break;
+ }
+ case R.id.import_failure_retry_upgrade: {
+ // Send a provider status update, which will trigger a retry
+ ContentValues values = new ContentValues();
+ values.put(ProviderStatus.STATUS, ProviderStatus.STATUS_UPGRADING);
+ mContext.getContentResolver().update(ProviderStatus.CONTENT_URI,
+ values, null, null);
+ break;
+ }
+ }
+ }};
+
+ Button uninstallApps = (Button) findViewById(R.id.import_failure_uninstall_apps);
+ uninstallApps.setOnClickListener(listener);
+
+ Button retryUpgrade = (Button) findViewById(R.id.import_failure_retry_upgrade);
+ retryUpgrade.setOnClickListener(listener);
+ }
+
+ private View findViewById(int id) {
+ return mView.findViewById(id);
+ }
+
+ // TODO: fix PluralRules to handle zero correctly and use Resources.getQuantityText directly
+ public String getQuantityText(int count, int zeroResourceId, int pluralResourceId) {
+ if (count == 0) {
+ return mContext.getString(zeroResourceId);
+ } else {
+ String format = mContext.getResources()
+ .getQuantityText(pluralResourceId, count).toString();
+ return String.format(format, count);
+ }
+ }
+
+ protected void setEmptyText(int resourceId) {
+ TextView empty = (TextView) getEmptyView().findViewById(R.id.emptyText);
+ empty.setText(mContext.getText(resourceId));
+ empty.setVisibility(View.VISIBLE);
+ }
+
+ // TODO redesign into an async task or loader
+ protected boolean isSyncActive() {
+ Account[] accounts = AccountManager.get(mContext).getAccounts();
+ if (accounts != null && accounts.length > 0) {
+ IContentService contentService = ContentResolver.getContentService();
+ for (Account account : accounts) {
+ try {
+ if (contentService.isSyncActive(account, ContactsContract.AUTHORITY)) {
+ return true;
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Could not get the sync status");
+ }
+ }
+ }
+ return false;
+ }
+
+ protected boolean hasIccCard() {
+ TelephonyManager telephonyManager =
+ (TelephonyManager)mContext.getSystemService(Context.TELEPHONY_SERVICE);
+ return telephonyManager.hasIccCard();
+ }
+
+ /**
+ * Processes a user request to start search. This may be triggered by the
+ * search key, a menu item or some other user action.
+ */
+ public void startSearch(String initialQuery) {
+ ContactsSearchManager.startSearch(getActivity(), initialQuery, mRequest);
+ }
+
+ /**
+ * Processes a result returned by the contact picker.
+ */
+ public void onPickerResult(Intent data) {
+ throw new UnsupportedOperationException("Picker result handler is not implemented.");
+ }
+
+ // TODO integrate into picker fragments
+// protected Uri buildCallingPackageUri(Uri uri) {
+// String callingPackage = getContext().getCallingPackage();
+// if (!TextUtils.isEmpty(callingPackage)) {
+// uri = uri.buildUpon().appendQueryParameter(
+// ContactsContract.REQUESTING_PACKAGE_PARAM_KEY, callingPackage).build();
+// }
+// }
+}
diff --git a/src/com/android/contacts/list/ContactListAdapter.java b/src/com/android/contacts/list/ContactListAdapter.java
new file mode 100644
index 0000000..a18e016
--- /dev/null
+++ b/src/com/android/contacts/list/ContactListAdapter.java
@@ -0,0 +1,239 @@
+/*
+ * 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.list;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.ContactCounts;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Directory;
+import android.provider.ContactsContract.SearchSnippetColumns;
+import android.text.TextUtils;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ListView;
+import android.widget.QuickContactBadge;
+
+/**
+ * A cursor adapter for the {@link ContactsContract.Contacts#CONTENT_TYPE} content type.
+ */
+public abstract class ContactListAdapter extends ContactEntryListAdapter {
+
+ protected static final String[] PROJECTION = new String[] {
+ Contacts._ID, // 0
+ Contacts.DISPLAY_NAME_PRIMARY, // 1
+ Contacts.DISPLAY_NAME_ALTERNATIVE, // 2
+ Contacts.SORT_KEY_PRIMARY, // 3
+ Contacts.STARRED, // 4
+ Contacts.CONTACT_PRESENCE, // 5
+ Contacts.PHOTO_ID, // 6
+ Contacts.LOOKUP_KEY, // 7
+ Contacts.PHONETIC_NAME, // 8
+ Contacts.HAS_PHONE_NUMBER, // 9
+ };
+
+ protected static final String[] FILTER_PROJECTION = new String[] {
+ Contacts._ID, // 0
+ Contacts.DISPLAY_NAME_PRIMARY, // 1
+ Contacts.DISPLAY_NAME_ALTERNATIVE, // 2
+ Contacts.SORT_KEY_PRIMARY, // 3
+ Contacts.STARRED, // 4
+ Contacts.CONTACT_PRESENCE, // 5
+ Contacts.PHOTO_ID, // 6
+ Contacts.LOOKUP_KEY, // 7
+ Contacts.PHONETIC_NAME, // 8
+ Contacts.HAS_PHONE_NUMBER, // 9
+ SearchSnippetColumns.SNIPPET_MIMETYPE, // 10
+ SearchSnippetColumns.SNIPPET_DATA1, // 11
+ SearchSnippetColumns.SNIPPET_DATA4, // 12
+ };
+
+ protected static final int CONTACT_ID_COLUMN_INDEX = 0;
+ protected static final int CONTACT_DISPLAY_NAME_PRIMARY_COLUMN_INDEX = 1;
+ protected static final int CONTACT_DISPLAY_NAME_ALTERNATIVE_COLUMN_INDEX = 2;
+ protected static final int CONTACT_SORT_KEY_PRIMARY_COLUMN_INDEX = 3;
+ protected static final int CONTACT_STARRED_COLUMN_INDEX = 4;
+ protected static final int CONTACT_PRESENCE_STATUS_COLUMN_INDEX = 5;
+ protected static final int CONTACT_PHOTO_ID_COLUMN_INDEX = 6;
+ protected static final int CONTACT_LOOKUP_KEY_COLUMN_INDEX = 7;
+ protected static final int CONTACT_PHONETIC_NAME_COLUMN_INDEX = 8;
+ protected static final int CONTACT_HAS_PHONE_COLUMN_INDEX = 9;
+ protected static final int CONTACT_SNIPPET_MIMETYPE_COLUMN_INDEX = 10;
+ protected static final int CONTACT_SNIPPET_DATA1_COLUMN_INDEX = 11;
+ protected static final int CONTACT_SNIPPET_DATA4_COLUMN_INDEX = 12;
+
+ private boolean mQuickContactEnabled;
+ private CharSequence mUnknownNameText;
+ private int mDisplayNameColumnIndex;
+ private int mAlternativeDisplayNameColumnIndex;
+
+ private long mSelectedContactDirectoryId;
+ private String mSelectedContactLookupKey;
+
+ public ContactListAdapter(Context context) {
+ super(context);
+
+ mUnknownNameText = context.getText(android.R.string.unknownName);
+ }
+
+ public CharSequence getUnknownNameText() {
+ return mUnknownNameText;
+ }
+
+ public long getSelectedContactDirectoryId() {
+ return mSelectedContactDirectoryId;
+ }
+
+ public String getSelectedContactLookupKey() {
+ return mSelectedContactLookupKey;
+ }
+
+ public void setSelectedContact(long selectedDirectoryId, String lookupKey) {
+ if (mSelectedContactDirectoryId != selectedDirectoryId ||
+ !TextUtils.equals(mSelectedContactLookupKey, lookupKey)) {
+ this.mSelectedContactDirectoryId = selectedDirectoryId;
+ this.mSelectedContactLookupKey = lookupKey;
+ notifyDataSetChanged();
+ }
+ }
+
+ protected static Uri buildSectionIndexerUri(Uri uri) {
+ return uri.buildUpon()
+ .appendQueryParameter(ContactCounts.ADDRESS_BOOK_INDEX_EXTRAS, "true").build();
+ }
+
+ public boolean getHasPhoneNumber(int position) {
+ return ((Cursor)getItem(position)).getInt(CONTACT_HAS_PHONE_COLUMN_INDEX) != 0;
+ }
+
+ public boolean isContactStarred(int position) {
+ return ((Cursor)getItem(position)).getInt(CONTACT_STARRED_COLUMN_INDEX) != 0;
+ }
+
+ @Override
+ public String getContactDisplayName(int position) {
+ return ((Cursor)getItem(position)).getString(mDisplayNameColumnIndex);
+ }
+
+ public boolean isQuickContactEnabled() {
+ return mQuickContactEnabled;
+ }
+
+ public void setQuickContactEnabled(boolean quickContactEnabled) {
+ mQuickContactEnabled = quickContactEnabled;
+ }
+
+ @Override
+ public void setContactNameDisplayOrder(int displayOrder) {
+ super.setContactNameDisplayOrder(displayOrder);
+ if (getContactNameDisplayOrder() == ContactsContract.Preferences.DISPLAY_ORDER_PRIMARY) {
+ mDisplayNameColumnIndex = CONTACT_DISPLAY_NAME_PRIMARY_COLUMN_INDEX;
+ mAlternativeDisplayNameColumnIndex = CONTACT_DISPLAY_NAME_ALTERNATIVE_COLUMN_INDEX;
+ } else {
+ mDisplayNameColumnIndex = CONTACT_DISPLAY_NAME_ALTERNATIVE_COLUMN_INDEX;
+ mAlternativeDisplayNameColumnIndex = CONTACT_DISPLAY_NAME_PRIMARY_COLUMN_INDEX;
+ }
+ }
+
+ /**
+ * Builds the {@link Contacts#CONTENT_LOOKUP_URI} for the given
+ * {@link ListView} position.
+ */
+ public Uri getContactUri(int position) {
+ int partitionIndex = getPartitionForPosition(position);
+ Cursor item = (Cursor)getItem(position);
+ return item != null ? getContactUri(partitionIndex, item) : null;
+ }
+
+ public Uri getContactUri(int partitionIndex, Cursor cursor) {
+ long contactId = cursor.getLong(CONTACT_ID_COLUMN_INDEX);
+ String lookupKey = cursor.getString(CONTACT_LOOKUP_KEY_COLUMN_INDEX);
+ Uri uri = Contacts.getLookupUri(contactId, lookupKey);
+ long directoryId = ((DirectoryPartition)getPartition(partitionIndex)).getDirectoryId();
+ if (directoryId != Directory.DEFAULT && directoryId != Directory.LOCAL_INVISIBLE) {
+ uri = uri.buildUpon().appendQueryParameter(
+ ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(directoryId)).build();
+ }
+ return uri;
+ }
+
+ /**
+ * Returns true if the specified contact is selected in the list. For a
+ * contact to be shown as selected, we need both the directory and and the
+ * lookup key to be the same. We are paying no attention to the contactId,
+ * because it is volatile, especially in the case of directories.
+ */
+ public boolean isSelectedContact(int partitionIndex, Cursor cursor) {
+ long directoryId = ((DirectoryPartition)getPartition(partitionIndex)).getDirectoryId();
+ return getSelectedContactDirectoryId() == directoryId
+ && TextUtils.equals(getSelectedContactLookupKey(),
+ cursor.getString(CONTACT_LOOKUP_KEY_COLUMN_INDEX));
+ }
+
+ @Override
+ protected View newView(Context context, int partition, Cursor cursor, int position,
+ ViewGroup parent) {
+ final ContactListItemView view = new ContactListItemView(context, null);
+ view.setUnknownNameText(mUnknownNameText);
+ view.setTextWithHighlightingFactory(getTextWithHighlightingFactory());
+ return view;
+ }
+
+ protected void bindSectionHeaderAndDivider(ContactListItemView view, int position) {
+ Placement placement = getItemPlacementInSection(position);
+ view.setSectionHeader(placement.firstInSection ? placement.sectionHeader : null);
+ view.setDividerVisible(!placement.lastInSection);
+ }
+
+ protected void bindPhoto(final ContactListItemView view, Cursor cursor) {
+ // Set the photo, if available
+ long photoId = 0;
+ if (!cursor.isNull(CONTACT_PHOTO_ID_COLUMN_INDEX)) {
+ photoId = cursor.getLong(CONTACT_PHOTO_ID_COLUMN_INDEX);
+ }
+
+ getPhotoLoader().loadPhoto(view.getPhotoView(), photoId);
+ }
+
+ protected void bindQuickContact(
+ final ContactListItemView view, int partitionIndex, Cursor cursor) {
+ long photoId = 0;
+ if (!cursor.isNull(CONTACT_PHOTO_ID_COLUMN_INDEX)) {
+ photoId = cursor.getLong(CONTACT_PHOTO_ID_COLUMN_INDEX);
+ }
+
+ QuickContactBadge quickContact = view.getQuickContact();
+ quickContact.assignContactUri(getContactUri(partitionIndex, cursor));
+ getPhotoLoader().loadPhoto(quickContact, photoId);
+ }
+
+ protected void bindName(final ContactListItemView view, Cursor cursor) {
+ view.showDisplayName(cursor, mDisplayNameColumnIndex, isNameHighlightingEnabled(),
+ mAlternativeDisplayNameColumnIndex);
+ view.showPhoneticName(cursor, CONTACT_PHONETIC_NAME_COLUMN_INDEX);
+ }
+
+ protected void bindPresence(final ContactListItemView view, Cursor cursor) {
+ view.showPresence(cursor, CONTACT_PRESENCE_STATUS_COLUMN_INDEX);
+ }
+
+ protected void bindSearchSnippet(final ContactListItemView view, Cursor cursor) {
+ view.showSnippet(cursor, CONTACT_SNIPPET_MIMETYPE_COLUMN_INDEX,
+ CONTACT_SNIPPET_DATA1_COLUMN_INDEX, CONTACT_SNIPPET_DATA4_COLUMN_INDEX);
+ }
+}
diff --git a/src/com/android/contacts/list/ContactListAizyView.java b/src/com/android/contacts/list/ContactListAizyView.java
new file mode 100644
index 0000000..a2fa706
--- /dev/null
+++ b/src/com/android/contacts/list/ContactListAizyView.java
@@ -0,0 +1,337 @@
+/*
+ * 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.list;
+
+import com.android.contacts.R;
+import com.android.contacts.util.PhonebookCollatorFactory;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Paint.Align;
+import android.graphics.Paint.FontMetrics;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.PopupWindow;
+import android.widget.SectionIndexer;
+import android.widget.TextView;
+
+import java.text.Collator;
+import java.util.ArrayList;
+
+/**
+ * A View that displays the sections given by an Indexer and their relative sizes. For
+ * English and similar languages, this is an A to Z list (where only the used letters are
+ * displayed). As the sections are shown in their relative sizes, this View can be used as a
+ * scrollbar.
+ */
+public class ContactListAizyView extends View {
+ private static final String TAG = "ContactListAizyView";
+
+ private static final int PREVIEW_TIME_DELAY_MS = 400;
+
+ private Listener mListener;
+ private PopupWindow mPreviewPopupWindow;
+ private TextView mPreviewPopupTextView;
+
+ private ResourceValues mResourceValues;
+
+ /**
+ * True if the popup window is currently visible.
+ */
+ private boolean mPreviewPopupVisible;
+
+ /**
+ * Time when the user started tapping. This is used to calculate the time delay before fading
+ * in the PopupWindow
+ */
+ private long mPreviewPopupStartTime;
+
+ /**
+ * Needed only inside {@link #onTouchEvent(MotionEvent)} to get the location of touch events.
+ */
+ private int[] mWindowOffset;
+
+ /**
+ * Needed to measure text. Used inside {@link #onDraw(Canvas)}
+ */
+ private final Rect bounds = new Rect();
+
+ /**
+ * Used and cached inside {@link #onDraw(Canvas)}
+ */
+ private FontMetrics mFontMetrics;
+
+ /**
+ * Used and cached inside {@link #onDraw(Canvas)}
+ */
+ private Paint mPaint;
+
+ /**
+ * The list of displayed sections. "Virtual" sections can be empty and therefore don't show
+ * up as regular sections
+ */
+ private final ArrayList<VirtualSection> mVirtualSections = new ArrayList<VirtualSection>();
+
+ public ContactListAizyView(Context context) {
+ super(context);
+ }
+
+ public ContactListAizyView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ public ContactListAizyView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ mResourceValues = new ResourceValues(getResources());
+
+ final LayoutInflater inflater =
+ (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ mPreviewPopupWindow = new PopupWindow(
+ inflater.inflate(R.layout.aizy_popup_window, null, false),
+ (int) mResourceValues.previewWidth, (int) mResourceValues.previewHeight);
+ mPreviewPopupWindow.setAnimationStyle(android.R.style.Animation_Toast);
+ mPreviewPopupTextView =
+ (TextView) mPreviewPopupWindow.getContentView().findViewById(R.id.caption);
+ }
+
+ /**
+ * Sets up the Aizy based on the indexer and completely reads its contents.
+ * This function has to be called everytime the data is changed.
+ */
+ public void readFromIndexer(SectionIndexer indexer) {
+ mVirtualSections.clear();
+ final String alphabetString = getResources().getString(R.string.visualScrollerAlphabet);
+ final String[] alphabet = alphabetString.split(";");
+
+ // We expect to get 10 additional items that the base alphabet
+ mVirtualSections.ensureCapacity(alphabet.length + 10);
+
+ if (indexer != null) {
+ // Add the real sections
+ final Object[] sections = indexer.getSections();
+ for (int sectionIndex = 0; sectionIndex < sections.length; sectionIndex++) {
+ final Object section = sections[sectionIndex];
+ final String caption = section == null ? "" : section.toString();
+ final int position = indexer.getPositionForSection(sectionIndex);
+ mVirtualSections.add(new VirtualSection(caption, sectionIndex, position));
+ }
+ }
+
+ final Collator collator = PhonebookCollatorFactory.getCollator();
+
+ // Add the base alphabet if missing
+ for (String caption : alphabet) {
+ boolean insertAtEnd = true;
+ VirtualSection previousVirtualSection = null;
+ for (int i = 0; i < mVirtualSections.size(); i++) {
+ final VirtualSection virtualSection = mVirtualSections.get(i);
+ final String virtualSectionCaption = virtualSection.getCaption();
+ final int comparison = collator.compare(virtualSectionCaption, caption);
+ if (comparison == 0) {
+ // element is already in the list.
+ insertAtEnd = false;
+ break;
+ }
+ if (comparison > 0) {
+ // we stepped too far. the element belongs before the element at i
+ insertAtEnd = false;
+ final int realSectionPosition = previousVirtualSection == null ? 0
+ : previousVirtualSection.getRealSectionPosition();
+ mVirtualSections.add(i, new VirtualSection(caption, -1, realSectionPosition));
+ break;
+ }
+ previousVirtualSection = virtualSection;
+ }
+ if (insertAtEnd) {
+ final int realSectionPosition = previousVirtualSection == null ? 0
+ : previousVirtualSection.getRealSectionPosition();
+ mVirtualSections.add(new VirtualSection(caption, -1, realSectionPosition));
+ }
+ }
+ invalidate();
+ }
+
+ /**
+ * Sets the Listener that is called everytime the user taps on this control.
+ */
+ public void setListener(Listener listener) {
+ mListener = listener;
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ setMeasuredDimension(resolveSize(0, widthMeasureSpec), resolveSize(0, heightMeasureSpec));
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ if (mPaint == null) {
+ mPaint = new Paint();
+ mPaint.setTextSize(mResourceValues.textSize);
+ mPaint.setAntiAlias(true);
+ mPaint.setTextAlign(Align.CENTER);
+ }
+ if (mFontMetrics == null) {
+ mFontMetrics = mPaint.getFontMetrics();
+ }
+ final float fontHeight = mFontMetrics.descent - mFontMetrics.ascent;
+ final int halfWidth = getWidth() / 2;
+ // Draw
+ float lastVisibleY = Float.NEGATIVE_INFINITY;
+ final float sectionHeight = (float) getHeight() / mVirtualSections.size();
+ for (int i = 0; i < mVirtualSections.size(); i++) {
+ final VirtualSection virtualSection = mVirtualSections.get(i);
+ final String caption = virtualSection.getCaption();
+ if (!virtualSection.isMeasured()) {
+ mPaint.getTextBounds(caption, 0, caption.length(), bounds);
+ virtualSection.setMeasuredSize(-bounds.top);
+ }
+ final float y = i * sectionHeight;
+ if (lastVisibleY + fontHeight < y) {
+ mPaint.setColor(virtualSection.getRealSectionIndex() != -1
+ ? mResourceValues.nonEmptySectionColor : mResourceValues.emptySectionColor);
+
+ canvas.drawText(caption, halfWidth,
+ y + sectionHeight / 2 + virtualSection.getMeasuredSize() / 2, mPaint);
+ lastVisibleY = y;
+ }
+ }
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ if (mWindowOffset == null) {
+ mWindowOffset = new int[2];
+ getLocationInWindow(mWindowOffset);
+ }
+
+ // Scroll the list itself
+ final int boundedY = Math.min(Math.max(0, (int) (event.getY())), getHeight() - 1);
+ final int index = boundedY * mVirtualSections.size() / getHeight();
+ final VirtualSection virtualSection = mVirtualSections.get(index);
+ final int sectionY = index * getHeight() / mVirtualSections.size();
+ mPreviewPopupTextView.setText(virtualSection.getCaption());
+ mPreviewPopupTextView.setTextColor(virtualSection.getRealSectionIndex() != -1
+ ? mResourceValues.nonEmptySectionColor : mResourceValues.emptySectionColor);
+
+ // Draw popup window
+ final int previewX = mWindowOffset[0] + getWidth();
+ final float sectionHeight = (float) getHeight() / mVirtualSections.size();
+ final int previewY = (int) (sectionY + mWindowOffset[1] + (sectionHeight -
+ mPreviewPopupWindow.getHeight()) / 2);
+ final int actionMasked = event.getActionMasked();
+ final boolean fingerIsDown = actionMasked == MotionEvent.ACTION_DOWN;
+ if (fingerIsDown) {
+ mPreviewPopupStartTime = System.currentTimeMillis();
+ }
+ final boolean fingerIsDownOrScrubbing =
+ actionMasked == MotionEvent.ACTION_MOVE || actionMasked == MotionEvent.ACTION_DOWN;
+
+ final boolean previewPopupVisible = fingerIsDownOrScrubbing &&
+ (System.currentTimeMillis() > mPreviewPopupStartTime + PREVIEW_TIME_DELAY_MS);
+
+ if (previewPopupVisible != mPreviewPopupVisible) {
+ if (previewPopupVisible) {
+ mPreviewPopupWindow.showAtLocation(this, Gravity.LEFT | Gravity.TOP,
+ previewX, previewY);
+ } else {
+ mPreviewPopupWindow.dismiss();
+ }
+ mPreviewPopupVisible = previewPopupVisible;
+ } else {
+ mPreviewPopupWindow.update(previewX, previewY, -1, -1);
+ }
+
+ // Perform the actual scrolling
+ if (mListener != null) mListener.onScroll(virtualSection.getRealSectionPosition());
+
+ super.onTouchEvent(event);
+ return true;
+ }
+
+ /**
+ * Reads an provides all values from the resource files
+ */
+ private static class ResourceValues {
+ private final int emptySectionColor;
+ private final int nonEmptySectionColor;
+ private final float textSize;
+ private final float previewWidth;
+ private final float previewHeight;
+
+ private ResourceValues(Resources resources) {
+ emptySectionColor = resources.getColor(R.color.aizy_empty_section);
+ nonEmptySectionColor = resources.getColor(R.color.aizy_non_empty_section);
+ textSize = resources.getDimension(R.dimen.aizy_text_size);
+ previewWidth = resources.getDimension(R.dimen.aizy_preview_width);
+ previewHeight = resources.getDimension(R.dimen.aizy_preview_height);
+ }
+ }
+
+ private static class VirtualSection {
+ private final String mCaption;
+ private final int mRealSectionIndex;
+ private final int mRealSectionPosition;
+ private float mMeasuredSize = Float.NaN;
+
+ public String getCaption() {
+ return mCaption;
+ }
+
+ public int getRealSectionIndex() {
+ return mRealSectionIndex;
+ }
+
+ public int getRealSectionPosition() {
+ return mRealSectionPosition;
+ }
+
+ public boolean isMeasured() {
+ return mMeasuredSize == Float.NaN;
+ }
+
+ public void setMeasuredSize(float value) {
+ mMeasuredSize = value;
+ }
+
+ public float getMeasuredSize() {
+ return mMeasuredSize;
+ }
+
+ public VirtualSection(String caption, int realSectionIndex, int realSectionPosition) {
+ mCaption = caption;
+ mRealSectionIndex = realSectionIndex;
+ mRealSectionPosition = realSectionPosition;
+ }
+ }
+
+ public interface Listener {
+ void onScroll(int position);
+ }
+}
diff --git a/src/com/android/contacts/ContactListItemView.java b/src/com/android/contacts/list/ContactListItemView.java
similarity index 64%
rename from src/com/android/contacts/ContactListItemView.java
rename to src/com/android/contacts/list/ContactListItemView.java
index 89e4265..755fed7 100644
--- a/src/com/android/contacts/ContactListItemView.java
+++ b/src/com/android/contacts/list/ContactListItemView.java
@@ -14,16 +14,24 @@
* limitations under the License.
*/
-package com.android.contacts;
+package com.android.contacts.list;
-import com.android.contacts.ui.widget.DontPressWithParentImageView;
+import com.android.contacts.ContactPresenceIconUtil;
+import com.android.contacts.R;
+import com.android.contacts.widget.TextWithHighlighting;
+import com.android.contacts.widget.TextWithHighlightingFactory;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
+import android.database.CharArrayBuffer;
+import android.database.Cursor;
import android.graphics.Canvas;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.Nickname;
+import android.provider.ContactsContract.CommonDataKinds.Organization;
import android.provider.ContactsContract.Contacts;
import android.text.TextUtils;
import android.text.TextUtils.TruncateAt;
@@ -32,9 +40,9 @@
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
+import android.widget.ImageView.ScaleType;
import android.widget.QuickContactBadge;
import android.widget.TextView;
-import android.widget.ImageView.ScaleType;
/**
* A custom view for an item in the contact list.
@@ -44,7 +52,9 @@
private static final int QUICK_CONTACT_BADGE_STYLE =
com.android.internal.R.attr.quickContactBadgeStyleWindowMedium;
- private final Context mContext;
+ protected final Context mContext;
+
+ private boolean mItemSelected;
private final int mPreferredHeight;
private final int mVerticalDividerMargin;
@@ -58,7 +68,9 @@
private final int mPresenceIconMargin;
private final int mHeaderTextWidth;
- private boolean mHorizontalDividerVisible;
+ private Drawable mSelectedBackgroundDrawable;
+
+ private boolean mHorizontalDividerVisible = true;
private Drawable mHorizontalDividerDrawable;
private int mHorizontalDividerHeight;
@@ -74,6 +86,7 @@
private QuickContactBadge mQuickContact;
private ImageView mPhotoView;
private TextView mNameTextView;
+ private TextView mPhoneticNameTextView;
private DontPressWithParentImageView mCallButton;
private TextView mLabelView;
private TextView mDataView;
@@ -85,8 +98,38 @@
private int mLine1Height;
private int mLine2Height;
private int mLine3Height;
+ private int mLine4Height;
private OnClickListener mCallButtonClickListener;
+ private TextWithHighlightingFactory mTextWithHighlightingFactory;
+ public CharArrayBuffer nameBuffer = new CharArrayBuffer(128);
+ public CharArrayBuffer dataBuffer = new CharArrayBuffer(128);
+ public CharArrayBuffer highlightedTextBuffer = new CharArrayBuffer(128);
+ public TextWithHighlighting textWithHighlighting;
+ public CharArrayBuffer phoneticNameBuffer = new CharArrayBuffer(128);
+
+ private CharSequence mUnknownNameText;
+
+ /**
+ * Special class to allow the parent to be pressed without being pressed itself.
+ * This way the line of a tab can be pressed, but the image itself is not.
+ */
+ // TODO: understand this
+ private static class DontPressWithParentImageView extends ImageView {
+
+ public DontPressWithParentImageView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ public void setPressed(boolean pressed) {
+ // If the parent is pressed, do not set to pressed.
+ if (pressed && ((View) getParent()).isPressed()) {
+ return;
+ }
+ super.setPressed(pressed);
+ }
+ }
public ContactListItemView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -128,6 +171,14 @@
mCallButtonClickListener = callButtonClickListener;
}
+ public void setTextWithHighlightingFactory(TextWithHighlightingFactory factory) {
+ mTextWithHighlightingFactory = factory;
+ }
+
+ public void setUnknownNameText(CharSequence unknownNameText) {
+ mUnknownNameText = unknownNameText;
+ }
+
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// We will match parent's width and wrap content vertically, but make sure
@@ -138,28 +189,36 @@
mLine1Height = 0;
mLine2Height = 0;
mLine3Height = 0;
+ mLine4Height = 0;
// Obtain the natural dimensions of the name text (we only care about height)
- mNameTextView.measure(0, 0);
+ if (isVisible(mNameTextView)) {
+ mNameTextView.measure(0, 0);
+ mLine1Height = mNameTextView.getMeasuredHeight();
+ }
- mLine1Height = mNameTextView.getMeasuredHeight();
+ if (isVisible(mPhoneticNameTextView)) {
+ mPhoneticNameTextView.measure(0, 0);
+ mLine2Height = mPhoneticNameTextView.getMeasuredHeight();
+ }
if (isVisible(mLabelView)) {
mLabelView.measure(0, 0);
- mLine2Height = mLabelView.getMeasuredHeight();
+ mLine3Height = mLabelView.getMeasuredHeight();
}
if (isVisible(mDataView)) {
mDataView.measure(0, 0);
- mLine2Height = Math.max(mLine2Height, mDataView.getMeasuredHeight());
+ mLine3Height = Math.max(mLine3Height, mDataView.getMeasuredHeight());
}
if (isVisible(mSnippetView)) {
mSnippetView.measure(0, 0);
- mLine3Height = mSnippetView.getMeasuredHeight();
+ mLine4Height = mSnippetView.getMeasuredHeight();
}
- height += mLine1Height + mLine2Height + mLine3Height;
+ height += mLine1Height + mLine2Height + mLine3Height + mLine4Height
+ + mPaddingTop + mPaddingBottom;
if (isVisible(mCallButton)) {
mCallButton.measure(0, 0);
@@ -178,7 +237,12 @@
mHeaderTextView.measure(
MeasureSpec.makeMeasureSpec(mHeaderTextWidth, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(mHeaderBackgroundHeight, MeasureSpec.EXACTLY));
- height += mHeaderBackgroundDrawable.getIntrinsicHeight();
+ height += mHeaderBackgroundHeight;
+ }
+
+ if (mHorizontalDividerVisible) {
+ ensureHorizontalDivider();
+ height += mHorizontalDividerHeight;
}
setMeasuredDimension(width, height);
@@ -202,13 +266,84 @@
topBound += mHeaderBackgroundHeight;
}
+ if (mItemSelected) {
+ ensureCheckedBackgroundDivider();
+ mSelectedBackgroundDrawable.setBounds(0, topBound, width, height);
+ }
+
// Positions of views on the left are fixed and so are those on the right side.
// The stretchable part of the layout is in the middle. So, we will start off
// by laying out the left and right sides. Then we will allocate the remainder
// to the text fields in the middle.
- // Left side
- int leftBound = mPaddingLeft;
+ int leftBound = layoutLeftSide(height, topBound, mPaddingLeft);
+ int rightBound = layoutRightSide(height, topBound, right);
+
+ if (mHorizontalDividerVisible) {
+ ensureHorizontalDivider();
+ mHorizontalDividerDrawable.setBounds(
+ 0,
+ height - mHorizontalDividerHeight,
+ width,
+ height);
+ }
+
+ topBound += mPaddingTop;
+
+ int bottomBound = height - mPaddingBottom;
+
+ // Text lines, centered vertically
+ rightBound -= mPaddingRight;
+
+ // Center text vertically
+ int totalTextHeight = mLine1Height + mLine2Height + mLine3Height + mLine4Height;
+ int textTopBound = (bottomBound + topBound - totalTextHeight) / 2;
+
+ if (isVisible(mNameTextView)) {
+ mNameTextView.layout(leftBound,
+ textTopBound,
+ rightBound,
+ textTopBound + mLine1Height);
+ }
+
+ int dataLeftBound = leftBound;
+ if (isVisible(mPhoneticNameTextView)) {
+ mPhoneticNameTextView.layout(leftBound,
+ textTopBound + mLine1Height,
+ rightBound,
+ textTopBound + mLine1Height + mLine2Height);
+ }
+
+ if (isVisible(mLabelView)) {
+ dataLeftBound = leftBound + mLabelView.getMeasuredWidth();
+ mLabelView.layout(leftBound,
+ textTopBound + mLine1Height + mLine2Height,
+ dataLeftBound,
+ textTopBound + mLine1Height + mLine2Height + mLine3Height);
+ dataLeftBound += mGapBetweenLabelAndData;
+ }
+
+ if (isVisible(mDataView)) {
+ mDataView.layout(dataLeftBound,
+ textTopBound + mLine1Height + mLine2Height,
+ rightBound,
+ textTopBound + mLine1Height + mLine2Height + mLine3Height);
+ }
+
+ if (isVisible(mSnippetView)) {
+ mSnippetView.layout(leftBound,
+ textTopBound + mLine1Height + mLine2Height + mLine3Height,
+ rightBound,
+ textTopBound + mLine1Height + mLine2Height + mLine3Height + mLine4Height);
+ }
+ }
+
+ /**
+ * Performs layout of the left side of the view
+ *
+ * @return new left boundary
+ */
+ protected int layoutLeftSide(int height, int topBound, int leftBound) {
View photoView = mQuickContact != null ? mQuickContact : mPhotoView;
if (photoView != null) {
// Center the photo vertically
@@ -220,9 +355,15 @@
photoTop + mPhotoViewHeight);
leftBound += mPhotoViewWidth + mGapBetweenImageAndText;
}
+ return leftBound;
+ }
- // Right side
- int rightBound = right;
+ /**
+ * Performs layout of the right side of the view
+ *
+ * @return new right boundary
+ */
+ protected int layoutRightSide(int height, int topBound, int rightBound) {
if (isVisible(mCallButton)) {
int buttonWidth = mCallButton.getMeasuredWidth();
rightBound -= buttonWidth;
@@ -230,7 +371,7 @@
rightBound,
topBound,
rightBound + buttonWidth,
- height);
+ height - mHorizontalDividerHeight);
mVerticalDividerVisible = true;
ensureVerticalDivider();
rightBound -= mVerticalDividerWidth;
@@ -252,61 +393,25 @@
rightBound + iconWidth,
height);
}
-
- if (mHorizontalDividerVisible) {
- ensureHorizontalDivider();
- mHorizontalDividerDrawable.setBounds(
- 0,
- height - mHorizontalDividerHeight,
- width,
- height);
- }
-
- topBound += mPaddingTop;
- int bottomBound = height - mPaddingBottom;
-
- // Text lines, centered vertically
- rightBound -= mPaddingRight;
-
- // Center text vertically
- int totalTextHeight = mLine1Height + mLine2Height + mLine3Height;
- int textTopBound = (bottomBound + topBound - totalTextHeight) / 2;
-
- mNameTextView.layout(leftBound,
- textTopBound,
- rightBound,
- textTopBound + mLine1Height);
-
- int dataLeftBound = leftBound;
- if (isVisible(mLabelView)) {
- dataLeftBound = leftBound + mLabelView.getMeasuredWidth();
- mLabelView.layout(leftBound,
- textTopBound + mLine1Height,
- dataLeftBound,
- textTopBound + mLine1Height + mLine2Height);
- dataLeftBound += mGapBetweenLabelAndData;
- }
-
- if (isVisible(mDataView)) {
- mDataView.layout(dataLeftBound,
- textTopBound + mLine1Height,
- rightBound,
- textTopBound + mLine1Height + mLine2Height);
- }
-
- if (isVisible(mSnippetView)) {
- mSnippetView.layout(leftBound,
- textTopBound + mLine1Height + mLine2Height,
- rightBound,
- textTopBound + mLine1Height + mLine2Height + mLine3Height);
- }
+ return rightBound;
}
- private boolean isVisible(View view) {
+ protected boolean isVisible(View view) {
return view != null && view.getVisibility() == View.VISIBLE;
}
/**
+ * Loads the drawable for the item background used when the item is checked.
+ */
+ private void ensureCheckedBackgroundDivider() {
+ if (mSelectedBackgroundDrawable == null) {
+ mSelectedBackgroundDrawable = mContext.getResources().getDrawable(
+ R.drawable.list_item_checked_bg);
+ mSelectedBackgroundDrawable.setBounds(0, 0, getWidth(), getHeight());
+ }
+ }
+
+ /**
* Loads the drawable for the vertical divider if it has not yet been loaded.
*/
private void ensureVerticalDivider() {
@@ -359,6 +464,9 @@
@Override
public void dispatchDraw(Canvas canvas) {
+ if (mItemSelected) {
+ mSelectedBackgroundDrawable.draw(canvas);
+ }
if (mHeaderVisible) {
mHeaderBackgroundDrawable.draw(canvas);
}
@@ -430,6 +538,24 @@
}
/**
+ * Removes the photo view. Should not be needed once we start handling different
+ * types of views as different types of views from the List's perspective.
+ *
+ * @deprecated
+ */
+ @Deprecated
+ public void removePhotoView() {
+ if (mPhotoView != null) {
+ removeView(mPhotoView);
+ mPhotoView = null;
+ }
+ if (mQuickContact != null) {
+ removeView(mQuickContact);
+ mQuickContact = null;
+ }
+ }
+
+ /**
* Returns the text view for the contact name, creating it if necessary.
*/
public TextView getNameTextView() {
@@ -470,6 +596,36 @@
}
/**
+ * Adds or updates a text view for the phonetic name.
+ */
+ public void setPhoneticName(char[] text, int size) {
+ if (text == null || size == 0) {
+ if (mPhoneticNameTextView != null) {
+ mPhoneticNameTextView.setVisibility(View.GONE);
+ }
+ } else {
+ getPhoneticNameTextView();
+ mPhoneticNameTextView.setText(text, 0, size);
+ mPhoneticNameTextView.setVisibility(VISIBLE);
+ }
+ }
+
+ /**
+ * Returns the text view for the phonetic name, creating it if necessary.
+ */
+ public TextView getPhoneticNameTextView() {
+ if (mPhoneticNameTextView == null) {
+ mPhoneticNameTextView = new TextView(mContext);
+ mPhoneticNameTextView.setSingleLine(true);
+ mPhoneticNameTextView.setEllipsize(TruncateAt.MARQUEE);
+ mPhoneticNameTextView.setTextAppearance(mContext, android.R.style.TextAppearance_Small);
+ mPhoneticNameTextView.setTypeface(mPhoneticNameTextView.getTypeface(), Typeface.BOLD);
+ addView(mPhoneticNameTextView);
+ }
+ return mPhoneticNameTextView;
+ }
+
+ /**
* Adds or updates a text view for the data label.
*/
public void setLabel(CharSequence text) {
@@ -592,4 +748,117 @@
}
}
}
+
+ public void showDisplayName(Cursor cursor, int nameColumnIndex, boolean highlightingEnabled,
+ int alternativeNameColumnIndex) {
+ cursor.copyStringToBuffer(nameColumnIndex, nameBuffer);
+ TextView nameView = getNameTextView();
+ int size = nameBuffer.sizeCopied;
+ if (size != 0) {
+ if (highlightingEnabled) {
+ if (textWithHighlighting == null) {
+ textWithHighlighting =
+ mTextWithHighlightingFactory.createTextWithHighlighting();
+ }
+ cursor.copyStringToBuffer(alternativeNameColumnIndex, highlightedTextBuffer);
+ textWithHighlighting.setText(nameBuffer, highlightedTextBuffer);
+ nameView.setText(textWithHighlighting);
+ } else {
+ nameView.setText(nameBuffer.data, 0, size);
+ }
+ } else {
+ nameView.setText(mUnknownNameText);
+ }
+ }
+
+ public void showPhoneticName(Cursor cursor, int phoneticNameColumnIndex) {
+ cursor.copyStringToBuffer(phoneticNameColumnIndex, phoneticNameBuffer);
+ int phoneticNameSize = phoneticNameBuffer.sizeCopied;
+ if (phoneticNameSize != 0) {
+ setPhoneticName(phoneticNameBuffer.data, phoneticNameSize);
+ } else {
+ setPhoneticName(null, 0);
+ }
+ }
+
+ /**
+ * Sets the proper icon (star or presence or nothing)
+ */
+ public void showPresence(Cursor cursor, int presenceColumnIndex) {
+ int serverStatus;
+ if (!cursor.isNull(presenceColumnIndex)) {
+ serverStatus = cursor.getInt(presenceColumnIndex);
+
+ // TODO consider caching these drawables
+ Drawable icon = ContactPresenceIconUtil.getPresenceIcon(getContext(), serverStatus);
+ if (icon != null) {
+ setPresence(icon);
+ } else {
+ setPresence(null);
+ }
+ } else {
+ setPresence(null);
+ }
+ }
+
+ /**
+ * Shows search snippet.
+ */
+ public void showSnippet(Cursor cursor, int summarySnippetMimetypeColumnIndex,
+ int summarySnippetData1ColumnIndex, int summarySnippetData4ColumnIndex) {
+ String snippet = null;
+ String snippetMimeType = cursor.getString(summarySnippetMimetypeColumnIndex);
+ if (Email.CONTENT_ITEM_TYPE.equals(snippetMimeType)) {
+ String email = cursor.getString(summarySnippetData1ColumnIndex);
+ if (!TextUtils.isEmpty(email)) {
+ snippet = email;
+ }
+ } else if (Organization.CONTENT_ITEM_TYPE.equals(snippetMimeType)) {
+ String company = cursor.getString(summarySnippetData1ColumnIndex);
+ String title = cursor.getString(summarySnippetData4ColumnIndex);
+ if (!TextUtils.isEmpty(company)) {
+ if (!TextUtils.isEmpty(title)) {
+ snippet = company + " / " + title;
+ } else {
+ snippet = company;
+ }
+ } else if (!TextUtils.isEmpty(title)) {
+ snippet = title;
+ }
+ } else if (Nickname.CONTENT_ITEM_TYPE.equals(snippetMimeType)) {
+ String nickname = cursor.getString(summarySnippetData1ColumnIndex);
+ if (!TextUtils.isEmpty(nickname)) {
+ snippet = nickname;
+ }
+ }
+
+ setSnippet(snippet);
+ }
+
+ /**
+ * Shows data element (e.g. phone number).
+ */
+ public void showData(Cursor cursor, int dataColumnIndex) {
+ cursor.copyStringToBuffer(dataColumnIndex, dataBuffer);
+ setData(dataBuffer.data, dataBuffer.sizeCopied);
+ }
+
+ public boolean isItemSelected() {
+ return mItemSelected;
+ }
+
+ public void setItemSelected(boolean selected) {
+ if (mItemSelected != selected) {
+ mItemSelected = selected;
+ requestLayout();
+ }
+ }
+
+ @Override
+ public void requestLayout() {
+ // We will assume that once measured this will not need to resize
+ // itself, so there is no need to pass the layout request to the parent
+ // view (ListView).
+ forceLayout();
+ }
}
diff --git a/src/com/android/contacts/list/ContactPickerFragment.java b/src/com/android/contacts/list/ContactPickerFragment.java
new file mode 100644
index 0000000..166294b
--- /dev/null
+++ b/src/com/android/contacts/list/ContactPickerFragment.java
@@ -0,0 +1,172 @@
+/*
+ * 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.list;
+
+import com.android.contacts.ContactsSearchManager;
+import com.android.contacts.R;
+import com.android.contacts.list.ShortcutIntentBuilder.OnShortcutIntentCreatedListener;
+
+import android.content.Intent;
+import android.net.Uri;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+
+/**
+ * Fragment for the contact list used for browsing contacts (as compared to
+ * picking a contact with one of the PICK or SHORTCUT intents).
+ */
+public class ContactPickerFragment extends ContactEntryListFragment<ContactEntryListAdapter>
+ implements OnShortcutIntentCreatedListener {
+
+ private OnContactPickerActionListener mListener;
+ private boolean mCreateContactEnabled;
+ private boolean mShortcutRequested;
+
+ public ContactPickerFragment() {
+ setPhotoLoaderEnabled(true);
+ setSectionHeaderDisplayEnabled(true);
+ setAizyEnabled(true);
+ }
+
+ public void setOnContactPickerActionListener(OnContactPickerActionListener listener) {
+ mListener = listener;
+ }
+
+ public boolean isCreateContactEnabled() {
+ return mCreateContactEnabled;
+ }
+
+ public void setCreateContactEnabled(boolean flag) {
+ this.mCreateContactEnabled = flag;
+ }
+
+ public boolean isShortcutRequested() {
+ return mShortcutRequested;
+ }
+
+ public void setShortcutRequested(boolean flag) {
+ mShortcutRequested = flag;
+ }
+
+ @Override
+ protected void onCreateView(LayoutInflater inflater, ViewGroup container) {
+ super.onCreateView(inflater, container);
+ if (mCreateContactEnabled) {
+ getListView().addHeaderView(inflater.inflate(R.layout.create_new_contact, null, false));
+ }
+ }
+
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ if (position == 0 && !isSearchMode() && mCreateContactEnabled) {
+ mListener.onCreateNewContactAction();
+ } else {
+ super.onItemClick(parent, view, position, id);
+ }
+ }
+
+ @Override
+ protected void onItemClick(int position, long id) {
+ Uri uri;
+ if (isLegacyCompatibilityMode()) {
+ uri = ((LegacyContactListAdapter)getAdapter()).getPersonUri(position);
+ } else {
+ uri = ((ContactListAdapter)getAdapter()).getContactUri(position);
+ }
+ if (mShortcutRequested) {
+ ShortcutIntentBuilder builder = new ShortcutIntentBuilder(getActivity(), this);
+ builder.createContactShortcutIntent(uri);
+ } else {
+ mListener.onPickContactAction(uri);
+ }
+ }
+
+ @Override
+ protected ContactEntryListAdapter createListAdapter() {
+ if (!isLegacyCompatibilityMode()) {
+ ContactListAdapter adapter = new DefaultContactListAdapter(getActivity());
+ adapter.setSectionHeaderDisplayEnabled(true);
+ adapter.setDisplayPhotos(true);
+ adapter.setQuickContactEnabled(false);
+ return adapter;
+ } else {
+ LegacyContactListAdapter adapter = new LegacyContactListAdapter(getActivity());
+ adapter.setSectionHeaderDisplayEnabled(false);
+ adapter.setDisplayPhotos(false);
+ return adapter;
+ }
+ }
+
+ @Override
+ protected void configureAdapter() {
+ super.configureAdapter();
+
+ ContactEntryListAdapter adapter = getAdapter();
+ if (adapter instanceof DefaultContactListAdapter) {
+ ((DefaultContactListAdapter)adapter).setVisibleContactsOnly(true);
+ }
+
+ // If "Create new contact" is shown, don't display the empty list UI
+ adapter.setEmptyListEnabled(!isCreateContactEnabled());
+ }
+
+ @Override
+ protected View inflateView(LayoutInflater inflater, ViewGroup container) {
+ return inflater.inflate(R.layout.contacts_list_content, null);
+ }
+
+ @Override
+ protected void prepareEmptyView() {
+ if (isSearchMode()) {
+ return;
+ } else if (isSyncActive()) {
+ if (mShortcutRequested) {
+ // Help text is the same no matter whether there is SIM or not.
+ setEmptyText(R.string.noContactsHelpTextWithSyncForCreateShortcut);
+ } else if (hasIccCard()) {
+ setEmptyText(R.string.noContactsHelpTextWithSync);
+ } else {
+ setEmptyText(R.string.noContactsNoSimHelpTextWithSync);
+ }
+ } else {
+ if (mShortcutRequested) {
+ // Help text is the same no matter whether there is SIM or not.
+ setEmptyText(R.string.noContactsHelpTextWithSyncForCreateShortcut);
+ } else if (hasIccCard()) {
+ setEmptyText(R.string.noContactsHelpText);
+ } else {
+ setEmptyText(R.string.noContactsNoSimHelpText);
+ }
+ }
+ }
+
+ public void onShortcutIntentCreated(Uri uri, Intent shortcutIntent) {
+ mListener.onShortcutIntentCreated(shortcutIntent);
+ }
+
+ @Override
+ public void startSearch(String initialQuery) {
+ ContactsSearchManager.startSearchForResult(getActivity(), initialQuery,
+ ACTIVITY_REQUEST_CODE_PICKER, getContactsRequest());
+ }
+
+ @Override
+ public void onPickerResult(Intent data) {
+ mListener.onPickContactAction(data.getData());
+ }
+}
diff --git a/src/com/android/contacts/list/ContactsIntentResolver.java b/src/com/android/contacts/list/ContactsIntentResolver.java
new file mode 100644
index 0000000..0570b2c
--- /dev/null
+++ b/src/com/android/contacts/list/ContactsIntentResolver.java
@@ -0,0 +1,206 @@
+/*
+ * 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.list;
+
+import com.android.contacts.CallContactActivity;
+import com.android.contacts.ContactsSearchManager;
+import com.android.contacts.R;
+
+import android.app.Activity;
+import android.app.SearchManager;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.Contacts.ContactMethods;
+import android.provider.Contacts.People;
+import android.provider.Contacts.Phones;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Intents;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+import android.provider.ContactsContract.Intents.UI;
+import android.text.TextUtils;
+import android.util.Log;
+
+/**
+ * Parses a Contacts intent, extracting all relevant parts and packaging them
+ * as a {@link ContactsRequest} object.
+ */
+@SuppressWarnings("deprecation")
+public class ContactsIntentResolver {
+
+ private static final String TAG = "ContactsIntentResolver";
+
+ private final Activity mContext;
+
+ public ContactsIntentResolver(Activity context) {
+ this.mContext = context;
+ }
+
+ public ContactsRequest resolveIntent(Intent intent) {
+ ContactsRequest request = new ContactsRequest();
+ request.setDisplayOnlyVisible(true);
+
+ String action = intent.getAction();
+
+ Log.i(TAG, "Called with action: " + action);
+
+ if (UI.LIST_DEFAULT.equals(action) ) {
+ request.setActionCode(ContactsRequest.ACTION_DEFAULT);
+ request.setDisplayWithPhonesOnlyOption(
+ ContactsRequest.DISPLAY_ONLY_WITH_PHONES_PREFERENCE);
+ } else if (UI.LIST_ALL_CONTACTS_ACTION.equals(action)) {
+ request.setActionCode(ContactsRequest.ACTION_DEFAULT);
+ request.setDisplayWithPhonesOnlyOption(
+ ContactsRequest.DISPLAY_ONLY_WITH_PHONES_DISABLED);
+ request.setDisplayOnlyVisible(false);
+ } else if (UI.LIST_CONTACTS_WITH_PHONES_ACTION.equals(action)) {
+ request.setActionCode(ContactsRequest.ACTION_DEFAULT);
+ request.setDisplayWithPhonesOnlyOption(
+ ContactsRequest.DISPLAY_ONLY_WITH_PHONES_ENABLED);
+ } else if (UI.LIST_STARRED_ACTION.equals(action)) {
+ request.setActionCode(ContactsRequest.ACTION_STARRED);
+ } else if (UI.LIST_FREQUENT_ACTION.equals(action)) {
+ request.setActionCode(ContactsRequest.ACTION_FREQUENT);
+ } else if (UI.LIST_STREQUENT_ACTION.equals(action)) {
+ request.setActionCode(ContactsRequest.ACTION_STREQUENT);
+ } else if (UI.LIST_GROUP_ACTION.equals(action)) {
+ request.setActionCode(ContactsRequest.ACTION_GROUP);
+ String groupName = intent.getStringExtra(UI.GROUP_NAME_EXTRA_KEY);
+ if (!TextUtils.isEmpty(groupName)) {
+ request.setGroupName(groupName);
+ } else {
+ Log.e(TAG, "Intent missing a required extra: " + UI.GROUP_NAME_EXTRA_KEY);
+ request.setValid(false);
+ }
+ } else if (Intent.ACTION_PICK.equals(action)) {
+ final String resolvedType = intent.resolveType(mContext);
+ if (Contacts.CONTENT_TYPE.equals(resolvedType)) {
+ request.setActionCode(ContactsRequest.ACTION_PICK_CONTACT);
+ } else if (People.CONTENT_TYPE.equals(resolvedType)) {
+ request.setActionCode(ContactsRequest.ACTION_PICK_CONTACT);
+ request.setLegacyCompatibilityMode(true);
+ } else if (Phone.CONTENT_TYPE.equals(resolvedType)) {
+ request.setActionCode(ContactsRequest.ACTION_PICK_PHONE);
+ } else if (Phones.CONTENT_TYPE.equals(resolvedType)) {
+ request.setActionCode(ContactsRequest.ACTION_PICK_PHONE);
+ request.setLegacyCompatibilityMode(true);
+ } else if (StructuredPostal.CONTENT_TYPE.equals(resolvedType)) {
+ request.setActionCode(ContactsRequest.ACTION_PICK_POSTAL);
+ } else if (ContactMethods.CONTENT_POSTAL_TYPE.equals(resolvedType)) {
+ request.setActionCode(ContactsRequest.ACTION_PICK_POSTAL);
+ request.setLegacyCompatibilityMode(true);
+ }
+ } else if (Intent.ACTION_CREATE_SHORTCUT.equals(action)) {
+ String component = intent.getComponent().getClassName();
+ if (component.equals("alias.DialShortcut")) {
+ request.setActionCode(ContactsRequest.ACTION_CREATE_SHORTCUT_CALL);
+ request.setActivityTitle(mContext.getString(R.string.callShortcutActivityTitle));
+ } else if (component.equals("alias.MessageShortcut")) {
+ request.setActionCode(ContactsRequest.ACTION_CREATE_SHORTCUT_SMS);
+ request.setActivityTitle(mContext.getString(R.string.messageShortcutActivityTitle));
+ } else {
+ request.setActionCode(ContactsRequest.ACTION_CREATE_SHORTCUT_CONTACT);
+ request.setActivityTitle(mContext.getString(R.string.shortcutActivityTitle));
+ }
+ } else if (Intent.ACTION_GET_CONTENT.equals(action)) {
+ String type = intent.getType();
+ if (Contacts.CONTENT_ITEM_TYPE.equals(type)) {
+ request.setActionCode(ContactsRequest.ACTION_PICK_OR_CREATE_CONTACT);
+ } else if (Phone.CONTENT_ITEM_TYPE.equals(type)) {
+ request.setActionCode(ContactsRequest.ACTION_PICK_PHONE);
+ } else if (Phones.CONTENT_ITEM_TYPE.equals(type)) {
+ request.setActionCode(ContactsRequest.ACTION_PICK_PHONE);
+ request.setLegacyCompatibilityMode(true);
+ } else if (StructuredPostal.CONTENT_ITEM_TYPE.equals(type)) {
+ request.setActionCode(ContactsRequest.ACTION_PICK_POSTAL);
+ } else if (ContactMethods.CONTENT_POSTAL_ITEM_TYPE.equals(type)) {
+ request.setActionCode(ContactsRequest.ACTION_PICK_POSTAL);
+ request.setLegacyCompatibilityMode(true);
+ } else if (People.CONTENT_ITEM_TYPE.equals(type)) {
+ request.setActionCode(ContactsRequest.ACTION_PICK_OR_CREATE_CONTACT);
+ request.setLegacyCompatibilityMode(true);
+ }
+ } else if (Intent.ACTION_INSERT_OR_EDIT.equals(action)) {
+ request.setActionCode(ContactsRequest.ACTION_INSERT_OR_EDIT_CONTACT);
+ } else if (Intent.ACTION_SEARCH.equals(action)) {
+ // See if the suggestion was clicked with a search action key (call button)
+ if ("call".equals(intent.getStringExtra(SearchManager.ACTION_MSG))) {
+ String query = intent.getStringExtra(SearchManager.QUERY);
+ if (!TextUtils.isEmpty(query)) {
+ Intent newIntent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
+ Uri.fromParts("tel", query, null));
+ request.setRedirectIntent(newIntent);
+ }
+ } else {
+ request.setQueryString(intent.getStringExtra(SearchManager.QUERY));
+ request.setSearchMode(true);
+ }
+ } else if (UI.FILTER_CONTACTS_ACTION.equals(action)) {
+ // When we get a FILTER_CONTACTS_ACTION, it represents search in the context
+ // of some other action. Let's retrieve the original action to provide proper
+ // context for the search queries.
+ request.setActionCode(ContactsRequest.ACTION_DEFAULT);
+ Bundle extras = intent.getExtras();
+ if (extras != null) {
+ request.setQueryString(extras.getString(UI.FILTER_TEXT_EXTRA_KEY));
+
+ ContactsRequest originalRequest =
+ (ContactsRequest)extras.get(ContactsSearchManager.ORIGINAL_REQUEST_KEY);
+ if (originalRequest != null) {
+ request.copyFrom(originalRequest);
+ }
+ }
+
+ if (request == null) {
+ request = new ContactsRequest();
+ }
+
+ request.setSearchMode(true);
+
+ // Since this is the filter activity it receives all intents
+ // dispatched from the SearchManager for security reasons
+ // so we need to re-dispatch from here to the intended target.
+ } else if (Intents.SEARCH_SUGGESTION_CLICKED.equals(action)) {
+ Uri data = intent.getData();
+ // See if the suggestion was clicked with a search action key (call button)
+ if ("call".equals(intent.getStringExtra(SearchManager.ACTION_MSG))) {
+ Intent newIntent = new Intent(mContext, CallContactActivity.class);
+ newIntent.setData(data);
+ request.setRedirectIntent(newIntent);
+ } else {
+ request.setRedirectIntent(new Intent(Intent.ACTION_VIEW, data));
+ }
+ } else if (Intents.SEARCH_SUGGESTION_DIAL_NUMBER_CLICKED.equals(action)) {
+ request.setRedirectIntent(new Intent(Intent.ACTION_CALL_PRIVILEGED, intent.getData()));
+ } else if (Intents.SEARCH_SUGGESTION_CREATE_CONTACT_CLICKED.equals(action)) {
+ // TODO actually support this in EditContactActivity.
+ String number = intent.getData().getSchemeSpecificPart();
+ Intent newIntent = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI);
+ newIntent.putExtra(Intents.Insert.PHONE, number);
+ request.setRedirectIntent(newIntent);
+
+ }
+ // Allow the title to be set to a custom String using an extra on the intent
+ String title = intent.getStringExtra(UI.TITLE_EXTRA_KEY);
+ if (title != null) {
+ request.setActivityTitle(title);
+ }
+
+ return request;
+ }
+}
diff --git a/src/com/android/contacts/list/ContactsRequest.java b/src/com/android/contacts/list/ContactsRequest.java
new file mode 100644
index 0000000..33f1685
--- /dev/null
+++ b/src/com/android/contacts/list/ContactsRequest.java
@@ -0,0 +1,233 @@
+/*
+ * 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.list;
+
+import android.content.Intent;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Parsed form of the intent sent to the Contacts application.
+ */
+public class ContactsRequest implements Parcelable {
+
+ /** Default mode: browse contacts */
+ public static final int ACTION_DEFAULT = 10;
+
+ /** Show contents of a specific group */
+ public static final int ACTION_GROUP = 20;
+
+ /** Show all starred contacts */
+ public static final int ACTION_STARRED = 30;
+
+ /** Show frequently contacted contacts */
+ public static final int ACTION_FREQUENT = 40;
+
+ /** Show starred and the frequent */
+ public static final int ACTION_STREQUENT = 50;
+
+ /** Show all contacts and pick them when clicking */
+ public static final int ACTION_PICK_CONTACT = 60;
+
+ /** Show all contacts as well as the option to create a new one */
+ public static final int ACTION_PICK_OR_CREATE_CONTACT = 70;
+
+ /** Show all contacts and pick them for edit when clicking, and allow creating a new contact */
+ public static final int ACTION_INSERT_OR_EDIT_CONTACT = 80;
+
+ /** Show all phone numbers and pick them when clicking */
+ public static final int ACTION_PICK_PHONE = 90;
+
+ /** Show all postal addresses and pick them when clicking */
+ public static final int ACTION_PICK_POSTAL = 100;
+
+ /** Show all contacts and create a shortcut for the picked contact */
+ public static final int ACTION_CREATE_SHORTCUT_CONTACT = 110;
+
+ /** Show all phone numbers and create a call shortcut for the picked number */
+ public static final int ACTION_CREATE_SHORTCUT_CALL = 120;
+
+ /** Show all phone numbers and create an SMS shortcut for the picked number */
+ public static final int ACTION_CREATE_SHORTCUT_SMS = 130;
+
+ private boolean mValid = true;
+ private int mActionCode = ACTION_DEFAULT;
+ private Intent mRedirectIntent;
+ private CharSequence mTitle;
+ private boolean mSearchMode;
+ private String mQueryString;
+
+ public static final int DISPLAY_ONLY_WITH_PHONES_PREFERENCE = 0;
+ public static final int DISPLAY_ONLY_WITH_PHONES_ENABLED = 1;
+ public static final int DISPLAY_ONLY_WITH_PHONES_DISABLED = 2;
+
+ private int mDisplayOnlyWithPhones;
+ private boolean mDisplayOnlyVisible;
+ private String mGroupName;
+ private boolean mLegacyCompatibilityMode;
+ private boolean mDirectorySearchEnabled = true;
+
+ /**
+ * Copies all fields.
+ */
+ public void copyFrom(ContactsRequest request) {
+ mValid = request.mValid;
+ mActionCode = request.mActionCode;
+ mRedirectIntent = request.mRedirectIntent;
+ mTitle = request.mTitle;
+ mSearchMode = request.mSearchMode;
+ mQueryString = request.mQueryString;
+ mDisplayOnlyWithPhones = request.mDisplayOnlyWithPhones;
+ mDisplayOnlyVisible = request.mDisplayOnlyVisible;
+ mGroupName = request.mGroupName;
+ mLegacyCompatibilityMode = request.mLegacyCompatibilityMode;
+ mDirectorySearchEnabled = request.mDirectorySearchEnabled;
+ }
+
+ public static Parcelable.Creator<ContactsRequest> CREATOR = new Creator<ContactsRequest>() {
+
+ public ContactsRequest[] newArray(int size) {
+ return new ContactsRequest[size];
+ }
+
+ public ContactsRequest createFromParcel(Parcel source) {
+ ContactsRequest request = new ContactsRequest();
+ request.mValid = source.readInt() != 0;
+ request.mActionCode = source.readInt();
+ request.mRedirectIntent = source.readParcelable(this.getClass().getClassLoader());
+ request.mTitle = source.readCharSequence();
+ request.mSearchMode = source.readInt() != 0;
+ request.mQueryString = source.readString();
+ request.mDisplayOnlyWithPhones = source.readInt();
+ request.mDisplayOnlyVisible = source.readInt() != 0;
+ request.mGroupName = source.readString();
+ request.mLegacyCompatibilityMode = source.readInt() != 0;
+ request.mDirectorySearchEnabled = source.readInt() != 0;
+ return request;
+ }
+ };
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mValid ? 1 : 0);
+ dest.writeInt(mActionCode);
+ dest.writeParcelable(mRedirectIntent, 0);
+ dest.writeCharSequence(mTitle);
+ dest.writeInt(mSearchMode ? 1 : 0);
+ dest.writeString(mQueryString);
+ dest.writeInt(mDisplayOnlyWithPhones);
+ dest.writeInt(mDisplayOnlyVisible ? 1 : 0);
+ dest.writeString(mGroupName);
+ dest.writeInt(mLegacyCompatibilityMode ? 1 : 0);
+ dest.writeInt(mDirectorySearchEnabled ? 1 : 0);
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public boolean isValid() {
+ return mValid;
+ }
+
+ public void setValid(boolean flag) {
+ mValid = flag;
+ }
+
+ public Intent getRedirectIntent() {
+ return mRedirectIntent;
+ }
+
+ public void setRedirectIntent(Intent intent) {
+ mRedirectIntent = intent;
+ }
+
+ public void setActivityTitle(CharSequence title) {
+ mTitle = title;
+ }
+
+ public CharSequence getActivityTitle() {
+ return mTitle;
+ }
+
+ public int getActionCode() {
+ return mActionCode;
+ }
+
+ public void setActionCode(int actionCode) {
+ mActionCode = actionCode;
+ }
+
+ public boolean getDisplayOnlyVisible() {
+ return mDisplayOnlyVisible;
+ }
+
+ public void setDisplayOnlyVisible(boolean flag) {
+ mDisplayOnlyVisible = flag;
+ }
+
+ public int getDisplayWithPhonesOnlyOption() {
+ return mDisplayOnlyWithPhones;
+ }
+
+ public void setDisplayWithPhonesOnlyOption(int option) {
+ mDisplayOnlyWithPhones = option;
+ }
+
+ public boolean isSearchMode() {
+ return mSearchMode;
+ }
+
+ public void setSearchMode(boolean flag) {
+ mSearchMode = flag;
+ }
+
+ public String getQueryString() {
+ return mQueryString;
+ }
+
+ public void setQueryString(String string) {
+ mQueryString = string;
+ }
+
+ public String getGroupName() {
+ return mGroupName;
+ }
+
+ public void setGroupName(String groupName) {
+ mGroupName = groupName;
+ }
+
+ public boolean isLegacyCompatibilityMode() {
+ return mLegacyCompatibilityMode;
+ }
+
+ public void setLegacyCompatibilityMode(boolean flag) {
+ mLegacyCompatibilityMode = flag;
+ }
+
+ /**
+ * Determines whether this search request should include directories or
+ * is limited to local contacts only.
+ */
+ public boolean isDirectorySearchEnabled() {
+ return mDirectorySearchEnabled;
+ }
+
+ public void setDirectorySearchEnabled(boolean flag) {
+ mDirectorySearchEnabled = flag;
+ }
+}
diff --git a/src/com/android/contacts/list/DefaultContactBrowseListFragment.java b/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
new file mode 100644
index 0000000..dd0ae3e
--- /dev/null
+++ b/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
@@ -0,0 +1,207 @@
+/*
+ * 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.list;
+
+import com.android.contacts.R;
+import com.android.contacts.ui.ContactsPreferencesActivity.Prefs;
+
+import android.content.SharedPreferences;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.preference.PreferenceManager;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.TextView;
+
+/**
+ * Fragment containing a contact list used for browsing (as compared to
+ * picking a contact with one of the PICK intents).
+ */
+public class DefaultContactBrowseListFragment extends ContactBrowseListFragment {
+
+ private static final String KEY_EDIT_MODE = "editMode";
+ private static final String KEY_CREATE_CONTACT_ENABLED = "createContactEnabled";
+ private static final String KEY_DISPLAY_WITH_PHONES_ONLY = "displayWithPhonesOnly";
+ private static final String KEY_VISIBLE_CONTACTS_RESTRICTION = "visibleContactsRestriction";
+
+ private boolean mEditMode;
+ private boolean mCreateContactEnabled;
+ private int mDisplayWithPhonesOnlyOption = ContactsRequest.DISPLAY_ONLY_WITH_PHONES_DISABLED;
+ private boolean mVisibleContactsRestrictionEnabled = true;
+ private View mHeaderView;
+
+ public DefaultContactBrowseListFragment() {
+ setPhotoLoaderEnabled(true);
+ setSectionHeaderDisplayEnabled(true);
+ setAizyEnabled(true);
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putBoolean(KEY_EDIT_MODE, mEditMode);
+ outState.putBoolean(KEY_CREATE_CONTACT_ENABLED, mCreateContactEnabled);
+ outState.putInt(KEY_DISPLAY_WITH_PHONES_ONLY, mDisplayWithPhonesOnlyOption);
+ outState.putBoolean(KEY_VISIBLE_CONTACTS_RESTRICTION, mVisibleContactsRestrictionEnabled);
+ }
+
+ @Override
+ public void restoreSavedState(Bundle savedState) {
+ super.restoreSavedState(savedState);
+
+ if (savedState == null) {
+ return;
+ }
+
+ mEditMode = savedState.getBoolean(KEY_EDIT_MODE);
+ mCreateContactEnabled = savedState.getBoolean(KEY_CREATE_CONTACT_ENABLED);
+ mDisplayWithPhonesOnlyOption = savedState.getInt(KEY_DISPLAY_WITH_PHONES_ONLY);
+ mVisibleContactsRestrictionEnabled =
+ savedState.getBoolean(KEY_VISIBLE_CONTACTS_RESTRICTION);
+ }
+
+ @Override
+ protected void prepareEmptyView() {
+ if (isShowingContactsWithPhonesOnly()) {
+ setEmptyText(R.string.noContactsWithPhoneNumbers);
+ } else {
+ super.prepareEmptyView();
+ }
+ }
+
+ private boolean isShowingContactsWithPhonesOnly() {
+ switch (mDisplayWithPhonesOnlyOption) {
+ case ContactsRequest.DISPLAY_ONLY_WITH_PHONES_DISABLED:
+ return false;
+ case ContactsRequest.DISPLAY_ONLY_WITH_PHONES_ENABLED:
+ return true;
+ case ContactsRequest.DISPLAY_ONLY_WITH_PHONES_PREFERENCE:
+ SharedPreferences prefs = PreferenceManager
+ .getDefaultSharedPreferences(getContext());
+ return prefs.getBoolean(Prefs.DISPLAY_ONLY_PHONES,
+ Prefs.DISPLAY_ONLY_PHONES_DEFAULT);
+ }
+ return false;
+ }
+
+ public void setDisplayWithPhonesOnlyOption(int displayWithPhonesOnly) {
+ mDisplayWithPhonesOnlyOption = displayWithPhonesOnly;
+ configureAdapter();
+ }
+
+ public void setVisibleContactsRestrictionEnabled(boolean flag) {
+ mVisibleContactsRestrictionEnabled = flag;
+ configureAdapter();
+ }
+
+ @Override
+ protected void onItemClick(int position, long id) {
+ ContactListAdapter adapter = getAdapter();
+ if (isEditMode()) {
+ if (position == 0 && !isSearchMode() && isCreateContactEnabled()) {
+ createNewContact();
+ } else {
+ editContact(adapter.getContactUri(position));
+ }
+ } else {
+ viewContact(adapter.getContactUri(position), false);
+ }
+ }
+
+ @Override
+ protected ContactListAdapter createListAdapter() {
+ DefaultContactListAdapter adapter = new DefaultContactListAdapter(getContext());
+ adapter.setSectionHeaderDisplayEnabled(isSectionHeaderDisplayEnabled());
+ adapter.setDisplayPhotos(true);
+ adapter.setQuickContactEnabled(true);
+ return adapter;
+ }
+
+ @Override
+ protected void configureAdapter() {
+ super.configureAdapter();
+
+ DefaultContactListAdapter adapter = (DefaultContactListAdapter)getAdapter();
+ if (adapter != null) {
+ adapter.setContactsWithPhoneNumbersOnly(isShowingContactsWithPhonesOnly());
+ adapter.setVisibleContactsOnly(mVisibleContactsRestrictionEnabled);
+ }
+ }
+
+ @Override
+ protected View inflateView(LayoutInflater inflater, ViewGroup container) {
+ return inflater.inflate(R.layout.contacts_list_content, null);
+ }
+
+ @Override
+ protected void onCreateView(LayoutInflater inflater, ViewGroup container) {
+ super.onCreateView(inflater, container);
+
+ // Putting the header view inside a container will allow us to make
+ // it invisible later. See checkHeaderViewVisibility()
+ FrameLayout headerContainer = new FrameLayout(inflater.getContext());
+ mHeaderView = inflater.inflate(R.layout.total_contacts, null, false);
+ headerContainer.addView(mHeaderView);
+ getListView().addHeaderView(headerContainer);
+ checkHeaderViewVisibility();
+ }
+
+ @Override
+ public void setSearchMode(boolean flag) {
+ super.setSearchMode(flag);
+ checkHeaderViewVisibility();
+ }
+
+ private void checkHeaderViewVisibility() {
+ if (mHeaderView != null) {
+ mHeaderView.setVisibility(isSearchMode() ? View.GONE : View.VISIBLE);
+ }
+ }
+
+ @Override
+ protected void showCount(int partitionIndex, Cursor data) {
+ if (!isSearchMode() && data != null) {
+ int count = data.getCount();
+ // TODO
+ // if (contactsListActivity.mDisplayOnlyPhones) {
+ // text = contactsListActivity.getQuantityText(count,
+ // R.string.listTotalPhoneContactsZero,
+ // R.plurals.listTotalPhoneContacts);
+ TextView textView = (TextView)getView().findViewById(R.id.totalContactsText);
+ String text = getQuantityText(count, R.string.listTotalAllContactsZero,
+ R.plurals.listTotalAllContacts);
+ textView.setText(text);
+ }
+ }
+
+ public boolean isEditMode() {
+ return mEditMode;
+ }
+
+ public void setEditMode(boolean flag) {
+ mEditMode = flag;
+ }
+
+ public boolean isCreateContactEnabled() {
+ return mCreateContactEnabled;
+ }
+
+ public void setCreateContactEnabled(boolean flag) {
+ this.mCreateContactEnabled = flag;
+ }
+}
diff --git a/src/com/android/contacts/list/DefaultContactListAdapter.java b/src/com/android/contacts/list/DefaultContactListAdapter.java
new file mode 100644
index 0000000..593b9ba
--- /dev/null
+++ b/src/com/android/contacts/list/DefaultContactListAdapter.java
@@ -0,0 +1,120 @@
+/*
+ * 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.list;
+
+import android.content.Context;
+import android.content.CursorLoader;
+import android.database.Cursor;
+import android.net.Uri;
+import android.net.Uri.Builder;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Directory;
+import android.text.TextUtils;
+import android.view.View;
+
+/**
+ * A cursor adapter for the {@link ContactsContract.Contacts#CONTENT_TYPE} content type.
+ */
+public class DefaultContactListAdapter extends ContactListAdapter {
+
+ private boolean mContactsWithPhoneNumbersOnly;
+ private boolean mVisibleContactsOnly;
+
+ public DefaultContactListAdapter(Context context) {
+ super(context);
+ }
+
+ public void setContactsWithPhoneNumbersOnly(boolean flag) {
+ mContactsWithPhoneNumbersOnly = flag;
+ }
+
+ public void setVisibleContactsOnly(boolean flag) {
+ mVisibleContactsOnly = flag;
+ }
+
+ @Override
+ public void configureLoader(CursorLoader loader, long directoryId) {
+ Uri uri;
+
+ if (isSearchMode()) {
+ String query = getQueryString();
+ Builder builder = Contacts.CONTENT_FILTER_URI.buildUpon();
+ if (TextUtils.isEmpty(query)) {
+ builder.appendPath("");
+ } else {
+ builder.appendPath(query); // Builder will encode the query
+ }
+
+ builder.appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY,
+ String.valueOf(directoryId));
+ uri = builder.build();
+ loader.setProjection(FILTER_PROJECTION);
+ } else {
+ uri = Contacts.CONTENT_URI;
+ loader.setProjection(PROJECTION);
+ }
+
+ if (directoryId == Directory.DEFAULT) {
+ if (mVisibleContactsOnly && mContactsWithPhoneNumbersOnly) {
+ loader.setSelection(Contacts.IN_VISIBLE_GROUP + "=1"
+ + " AND " + Contacts.HAS_PHONE_NUMBER + "=1");
+ } else if (mVisibleContactsOnly) {
+ loader.setSelection(Contacts.IN_VISIBLE_GROUP + "=1");
+ } else if (mContactsWithPhoneNumbersOnly) {
+ loader.setSelection(Contacts.HAS_PHONE_NUMBER + "=1");
+ }
+ if (isSectionHeaderDisplayEnabled()) {
+ uri = buildSectionIndexerUri(uri);
+ }
+ }
+
+ loader.setUri(uri);
+
+ String sortOrder;
+ if (getSortOrder() == ContactsContract.Preferences.SORT_ORDER_PRIMARY) {
+ sortOrder = Contacts.SORT_KEY_PRIMARY;
+ } else {
+ sortOrder = Contacts.SORT_KEY_ALTERNATIVE;
+ }
+
+ loader.setSortOrder(sortOrder);
+ }
+
+ @Override
+ protected void bindView(View itemView, int partition, Cursor cursor, int position) {
+ final ContactListItemView view = (ContactListItemView)itemView;
+
+ if (isSelectionVisible()) {
+ view.setItemSelected(isSelectedContact(partition, cursor));
+ }
+
+ bindSectionHeaderAndDivider(view, position);
+
+ if (isQuickContactEnabled()) {
+ bindQuickContact(view, partition, cursor);
+ } else {
+ bindPhoto(view, cursor);
+ }
+
+ bindName(view, cursor);
+ bindPresence(view, cursor);
+
+ if (isSearchMode()) {
+ bindSearchSnippet(view, cursor);
+ }
+ }
+}
diff --git a/src/com/android/contacts/list/DirectoryListLoader.java b/src/com/android/contacts/list/DirectoryListLoader.java
new file mode 100644
index 0000000..b8cc2c9
--- /dev/null
+++ b/src/com/android/contacts/list/DirectoryListLoader.java
@@ -0,0 +1,156 @@
+/*
+ * 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.list;
+
+import com.android.contacts.R;
+
+import android.content.AsyncTaskLoader;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.net.Uri;
+import android.os.Handler;
+import android.provider.ContactsContract.Directory;
+import android.text.TextUtils;
+import android.util.Log;
+
+/**
+ * A specialized loader for the list of directories, see {@link Directory}.
+ */
+public class DirectoryListLoader extends AsyncTaskLoader<Cursor> {
+
+ private static final String TAG = "ContactEntryListAdapter";
+
+ private static final class DirectoryQuery {
+ public static final Uri URI = Directory.CONTENT_URI;
+ public static final String ORDER_BY = Directory._ID;
+
+ public static final String[] PROJECTION = {
+ Directory._ID,
+ Directory.PACKAGE_NAME,
+ Directory.TYPE_RESOURCE_ID,
+ Directory.DISPLAY_NAME,
+ };
+
+ public static final int ID = 0;
+ public static final int PACKAGE_NAME = 1;
+ public static final int TYPE_RESOURCE_ID = 2;
+ public static final int DISPLAY_NAME = 3;
+ }
+
+ public static final String DIRECTORY_TYPE = "directoryType";
+
+ private static final String[] RESULT_PROJECTION = {
+ Directory._ID,
+ DIRECTORY_TYPE,
+ Directory.DISPLAY_NAME,
+ };
+
+ private final ContentObserver mObserver = new ContentObserver(new Handler()) {
+ @Override
+ public void onChange(boolean selfChange) {
+ forceLoad();
+ }
+ };
+
+ private boolean mDirectorySearchEnabled;
+
+ private MatrixCursor mDefaultDirectoryList;
+
+ public DirectoryListLoader(Context context) {
+ super(context);
+ }
+
+ public void setDirectorySearchEnabled(boolean flag) {
+ mDirectorySearchEnabled = flag;
+ }
+
+ @Override
+ public void startLoading() {
+ getContext().getContentResolver().
+ registerContentObserver(Directory.CONTENT_URI, false, mObserver);
+ forceLoad();
+ }
+
+ @Override
+ public void stopLoading() {
+ getContext().getContentResolver().unregisterContentObserver(mObserver);
+ }
+
+ @Override
+ public Cursor loadInBackground() {
+ if (mDirectorySearchEnabled) {
+ return loadDirectories();
+ } else {
+ return getDefaultDirectories();
+ }
+ }
+
+ private Cursor loadDirectories() {
+ MatrixCursor result = new MatrixCursor(RESULT_PROJECTION);
+ Context context = getContext();
+ PackageManager pm = context.getPackageManager();
+ Cursor cursor = context.getContentResolver().query(DirectoryQuery.URI,
+ DirectoryQuery.PROJECTION, null, null, DirectoryQuery.ORDER_BY);
+ try {
+ while(cursor.moveToNext()) {
+ Object[] row = new Object[RESULT_PROJECTION.length];
+ long directoryId = cursor.getLong(DirectoryQuery.ID);
+ String directoryType = null;
+
+ String packageName = cursor.getString(DirectoryQuery.PACKAGE_NAME);
+ int typeResourceId = cursor.getInt(DirectoryQuery.TYPE_RESOURCE_ID);
+ if (!TextUtils.isEmpty(packageName) && typeResourceId != 0) {
+ try {
+ directoryType = pm.getResourcesForApplication(packageName)
+ .getString(typeResourceId);
+ } catch (Exception e) {
+ Log.e(TAG, "Cannot obtain directory type from package: " + packageName);
+ }
+ }
+ String displayName = cursor.getString(DirectoryQuery.DISPLAY_NAME);
+ result.addRow(new Object[]{directoryId, directoryType, displayName});
+ }
+ } finally {
+ cursor.close();
+ }
+ return result;
+ }
+
+ private Cursor getDefaultDirectories() {
+ if (mDefaultDirectoryList == null) {
+ mDefaultDirectoryList = new MatrixCursor(RESULT_PROJECTION);
+ mDefaultDirectoryList.addRow(new Object[] {
+ Directory.DEFAULT,
+ getContext().getString(R.string.contactsList),
+ null
+ });
+ mDefaultDirectoryList.addRow(new Object[] {
+ Directory.LOCAL_INVISIBLE,
+ getContext().getString(R.string.local_invisible_directory),
+ null
+ });
+ }
+ return mDefaultDirectoryList;
+ }
+
+ @Override
+ public void destroy() {
+ stopLoading();
+ }
+}
diff --git a/src/com/android/contacts/list/DirectoryPartition.java b/src/com/android/contacts/list/DirectoryPartition.java
new file mode 100644
index 0000000..b55ed31
--- /dev/null
+++ b/src/com/android/contacts/list/DirectoryPartition.java
@@ -0,0 +1,88 @@
+/*
+ * 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.list;
+
+import com.android.common.widget.CompositeCursorAdapter;
+
+import android.provider.ContactsContract.Directory;
+
+/**
+ * Model object for a {@link Directory} row.
+ */
+public final class DirectoryPartition extends CompositeCursorAdapter.Partition {
+ private long mDirectoryId;
+ private String mDirectoryType;
+ private String mDisplayName;
+ private boolean mLoading;
+ private boolean mPriorityDirectory;
+
+ public DirectoryPartition(boolean showIfEmpty, boolean hasHeader) {
+ super(showIfEmpty, hasHeader);
+ }
+
+ /**
+ * Directory ID, see {@link Directory}.
+ */
+ public long getDirectoryId() {
+ return mDirectoryId;
+ }
+
+ public void setDirectoryId(long directoryId) {
+ this.mDirectoryId = directoryId;
+ }
+
+ /**
+ * Directory type resolved from {@link Directory#PACKAGE_NAME} and
+ * {@link Directory#TYPE_RESOURCE_ID};
+ */
+ public String getDirectoryType() {
+ return mDirectoryType;
+ }
+
+ public void setDirectoryType(String directoryType) {
+ this.mDirectoryType = directoryType;
+ }
+
+ /**
+ * See {@link Directory#DISPLAY_NAME}.
+ */
+ public String getDisplayName() {
+ return mDisplayName;
+ }
+
+ public void setDisplayName(String displayName) {
+ this.mDisplayName = displayName;
+ }
+
+ public boolean isLoading() {
+ return mLoading;
+ }
+
+ public void setLoading(boolean loading) {
+ mLoading = loading;
+ }
+
+ /**
+ * Returns true if this directory should be loaded before non-priority directories.
+ */
+ public boolean isPriorityDirectory() {
+ return mPriorityDirectory;
+ }
+
+ public void setPriorityDirectory(boolean priorityDirectory) {
+ mPriorityDirectory = priorityDirectory;
+ }
+}
diff --git a/src/com/android/contacts/list/JoinContactListAdapter.java b/src/com/android/contacts/list/JoinContactListAdapter.java
new file mode 100644
index 0000000..14cfa8f
--- /dev/null
+++ b/src/com/android/contacts/list/JoinContactListAdapter.java
@@ -0,0 +1,233 @@
+/*
+ * 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.list;
+
+import com.android.contacts.R;
+
+import android.content.Context;
+import android.content.CursorLoader;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.net.Uri;
+import android.net.Uri.Builder;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Contacts.AggregationSuggestions;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+public class JoinContactListAdapter extends ContactListAdapter {
+
+ /** Maximum number of suggestions shown for joining aggregates */
+ private static final int MAX_SUGGESTIONS = 4;
+
+ public static final int PARTITION_SUGGESTIONS = 0;
+ public static final int PARTITION_SHOW_ALL_CONTACTS = 1;
+ public static final int PARTITION_ALL_CONTACTS = 2;
+
+ private long mTargetContactId;
+
+ private int mShowAllContactsViewType;
+
+ /**
+ * Determines whether we display a list item with the label
+ * "Show all contacts" or actually show all contacts
+ */
+ private boolean mAllContactsListShown;
+
+
+ public JoinContactListAdapter(Context context) {
+ super(context);
+ setPinnedPartitionHeadersEnabled(true);
+ setSectionHeaderDisplayEnabled(true);
+ setIndexedPartition(PARTITION_ALL_CONTACTS);
+ setDirectorySearchEnabled(false);
+ mShowAllContactsViewType = getViewTypeCount() - 1;
+ }
+
+ @Override
+ protected void addPartitions() {
+
+ // Partition 0: suggestions
+ addPartition(false, true);
+
+ // Partition 1: "Show all contacts"
+ addPartition(false, false);
+
+ // Partition 2: All contacts
+ addPartition(createDefaultDirectoryPartition());
+ }
+
+ public void setTargetContactId(long targetContactId) {
+ this.mTargetContactId = targetContactId;
+ }
+
+ @Override
+ public void configureLoader(CursorLoader cursorLoader, long directoryId) {
+ JoinContactLoader loader = (JoinContactLoader)cursorLoader;
+ loader.setLoadSuggestionsAndAllContacts(mAllContactsListShown);
+
+ Builder builder = Contacts.CONTENT_URI.buildUpon();
+ builder.appendEncodedPath(String.valueOf(mTargetContactId));
+ builder.appendEncodedPath(AggregationSuggestions.CONTENT_DIRECTORY);
+
+ String filter = getQueryString();
+ if (!TextUtils.isEmpty(filter)) {
+ builder.appendEncodedPath(Uri.encode(filter));
+ }
+
+ builder.appendQueryParameter("limit", String.valueOf(MAX_SUGGESTIONS));
+
+ loader.setSuggestionUri(builder.build());
+
+ // TODO simplify projection
+ loader.setProjection(PROJECTION);
+ loader.setUri(buildSectionIndexerUri(Contacts.CONTENT_URI));
+ loader.setSelection(Contacts.IN_VISIBLE_GROUP + "=1 AND " + Contacts._ID + "!=?");
+ loader.setSelectionArgs(new String[]{String.valueOf(mTargetContactId)});
+ if (getSortOrder() == ContactsContract.Preferences.SORT_ORDER_PRIMARY) {
+ loader.setSortOrder(Contacts.SORT_KEY_PRIMARY);
+ } else {
+ loader.setSortOrder(Contacts.SORT_KEY_ALTERNATIVE);
+ }
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return false;
+ }
+
+ public boolean isAllContactsListShown() {
+ return mAllContactsListShown;
+ }
+
+ public void setAllContactsListShown(boolean flag) {
+ mAllContactsListShown = flag;
+ }
+
+ public void setSuggestionsCursor(Cursor cursor) {
+ changeCursor(PARTITION_SUGGESTIONS, cursor);
+ if (cursor != null && cursor.getCount() != 0 && !mAllContactsListShown) {
+ changeCursor(PARTITION_SHOW_ALL_CONTACTS, getShowAllContactsLabelCursor());
+ } else {
+ changeCursor(PARTITION_SHOW_ALL_CONTACTS, null);
+ }
+ }
+
+ @Override
+ public void changeCursor(Cursor cursor) {
+ changeCursor(PARTITION_ALL_CONTACTS, cursor);
+ }
+
+ @Override
+ public void configureDefaultPartition(boolean showIfEmpty, boolean hasHeader) {
+ // Don't change default partition parameters from these defaults
+ super.configureDefaultPartition(false, true);
+ }
+
+ @Override
+ public int getViewTypeCount() {
+ return super.getViewTypeCount() + 1;
+ }
+
+ @Override
+ public int getItemViewType(int partition, int position) {
+ if (partition == PARTITION_SHOW_ALL_CONTACTS) {
+ return mShowAllContactsViewType;
+ }
+ return super.getItemViewType(partition, position);
+ }
+
+ @Override
+ protected View newHeaderView(Context context, int partition, Cursor cursor,
+ ViewGroup parent) {
+ switch (partition) {
+ case PARTITION_SUGGESTIONS: {
+ TextView view = (TextView) inflate(R.layout.list_separator, parent);
+ view.setText(R.string.separatorJoinAggregateSuggestions);
+ return view;
+ }
+ case PARTITION_ALL_CONTACTS: {
+ TextView view = (TextView) inflate(R.layout.list_separator, parent);
+ view.setText(R.string.separatorJoinAggregateAll);
+ return view;
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ protected void bindHeaderView(View view, int partitionIndex, Cursor cursor) {
+ // Header views are static - nothing needs to be bound
+ }
+
+ @Override
+ protected View newView(Context context, int partition, Cursor cursor, int position,
+ ViewGroup parent) {
+ switch (partition) {
+ case PARTITION_SUGGESTIONS:
+ case PARTITION_ALL_CONTACTS:
+ return super.newView(context, partition, cursor, position, parent);
+ case PARTITION_SHOW_ALL_CONTACTS:
+ return inflate(R.layout.contacts_list_show_all_item, parent);
+ }
+ return null;
+ }
+
+ private View inflate(int layoutId, ViewGroup parent) {
+ return LayoutInflater.from(getContext()).inflate(layoutId, parent, false);
+ }
+
+ @Override
+ protected void bindView(View itemView, int partition, Cursor cursor, int position) {
+ switch (partition) {
+ case PARTITION_SUGGESTIONS: {
+ final ContactListItemView view = (ContactListItemView)itemView;
+ bindPhoto(view, cursor);
+ bindName(view, cursor);
+ break;
+ }
+ case PARTITION_SHOW_ALL_CONTACTS: {
+ break;
+ }
+ case PARTITION_ALL_CONTACTS: {
+ final ContactListItemView view = (ContactListItemView)itemView;
+ bindSectionHeaderAndDivider(view, position);
+ bindPhoto(view, cursor);
+ bindName(view, cursor);
+ break;
+ }
+ }
+ }
+
+ public Cursor getShowAllContactsLabelCursor() {
+ MatrixCursor matrixCursor = new MatrixCursor(PROJECTION);
+ Object[] row = new Object[PROJECTION.length];
+ matrixCursor.addRow(row);
+ return matrixCursor;
+ }
+
+ @Override
+ public Uri getContactUri(int partitionIndex, Cursor cursor) {
+ long contactId = cursor.getLong(CONTACT_ID_COLUMN_INDEX);
+ String lookupKey = cursor.getString(CONTACT_LOOKUP_KEY_COLUMN_INDEX);
+ return Contacts.getLookupUri(contactId, lookupKey);
+ }
+}
diff --git a/src/com/android/contacts/list/JoinContactListFragment.java b/src/com/android/contacts/list/JoinContactListFragment.java
new file mode 100644
index 0000000..e17fbbf
--- /dev/null
+++ b/src/com/android/contacts/list/JoinContactListFragment.java
@@ -0,0 +1,166 @@
+/*
+ * 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.list;
+
+import com.android.contacts.ContactsSearchManager;
+import com.android.contacts.R;
+
+import android.app.Activity;
+import android.app.LoaderManager.LoaderCallbacks;
+import android.content.ContentUris;
+import android.content.CursorLoader;
+import android.content.Intent;
+import android.content.Loader;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.provider.ContactsContract.Contacts;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+/**
+ * Fragment for the Join Contact list.
+ */
+public class JoinContactListFragment extends ContactEntryListFragment<JoinContactListAdapter> {
+
+ private static final int DISPLAY_NAME_LOADER = -2;
+
+ private OnContactPickerActionListener mListener;
+ private long mTargetContactId;
+ private boolean mAllContactsListShown = false;
+
+ private LoaderCallbacks<Cursor> mLoaderCallbacks = new LoaderCallbacks<Cursor>() {
+
+ @Override
+ public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+ switch (id) {
+ case DISPLAY_NAME_LOADER: {
+ // Loader for the display name of the target contact
+ return new CursorLoader(getActivity(),
+ ContentUris.withAppendedId(Contacts.CONTENT_URI, mTargetContactId),
+ new String[] { Contacts.DISPLAY_NAME }, null, null, null);
+ }
+ case JoinContactListAdapter.PARTITION_ALL_CONTACTS: {
+ JoinContactLoader loader = new JoinContactLoader(getActivity());
+ JoinContactListAdapter adapter = getAdapter();
+ if (adapter != null) {
+ adapter.configureLoader(loader, 0);
+ }
+ return loader;
+ }
+ }
+ throw new IllegalArgumentException("No loader for ID=" + id);
+ }
+
+ @Override
+ public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+ switch (loader.getId()) {
+ case DISPLAY_NAME_LOADER: {
+ if (data != null && data.moveToFirst()) {
+ showTargetContactName(data.getString(0));
+ }
+ break;
+ }
+ case JoinContactListAdapter.PARTITION_ALL_CONTACTS: {
+ setAizyEnabled(mAllContactsListShown);
+
+ JoinContactListAdapter adapter = getAdapter();
+ Cursor suggestionsCursor = ((JoinContactLoader)loader).getSuggestionsCursor();
+ adapter.setSuggestionsCursor(suggestionsCursor);
+ onPartitionLoaded(JoinContactListAdapter.PARTITION_ALL_CONTACTS, data);
+ break;
+ }
+ }
+ }
+ };
+
+ public JoinContactListFragment() {
+ setPhotoLoaderEnabled(true);
+ setSectionHeaderDisplayEnabled(true);
+ setAizyEnabled(false);
+ }
+
+ public void setOnContactPickerActionListener(OnContactPickerActionListener listener) {
+ mListener = listener;
+ }
+
+ @Override
+ protected void startLoading() {
+ configureAdapter();
+
+ getLoaderManager().initLoader(DISPLAY_NAME_LOADER, null, mLoaderCallbacks);
+ getLoaderManager().initLoader(JoinContactListAdapter.PARTITION_ALL_CONTACTS,
+ null, mLoaderCallbacks);
+ }
+
+ private void showTargetContactName(String displayName) {
+ Activity activity = getActivity();
+ TextView blurbView = (TextView)activity.findViewById(R.id.join_contact_blurb);
+ String blurb = activity.getString(R.string.blurbJoinContactDataWith, displayName);
+ blurbView.setText(blurb);
+ }
+
+ public void setTargetContactId(long targetContactId) {
+ mTargetContactId = targetContactId;
+ }
+
+ @Override
+ public JoinContactListAdapter createListAdapter() {
+ return new JoinContactListAdapter(getActivity());
+ }
+
+ @Override
+ protected void configureAdapter() {
+ super.configureAdapter();
+ JoinContactListAdapter adapter = getAdapter();
+ adapter.setAllContactsListShown(mAllContactsListShown);
+ adapter.setTargetContactId(mTargetContactId);
+ }
+
+ @Override
+ protected View inflateView(LayoutInflater inflater, ViewGroup container) {
+ return inflater.inflate(R.layout.contacts_list_content_join, null);
+ }
+
+ @Override
+ protected void onItemClick(int position, long id) {
+ JoinContactListAdapter adapter = getAdapter();
+ int partition = adapter.getPartitionForPosition(position);
+ if (partition == JoinContactListAdapter.PARTITION_SHOW_ALL_CONTACTS) {
+ mAllContactsListShown = true;
+ configureAdapter();
+ getLoaderManager().restartLoader(JoinContactListAdapter.PARTITION_ALL_CONTACTS,
+ null, mLoaderCallbacks);
+ } else {
+ mListener.onPickContactAction(adapter.getContactUri(position));
+ }
+ }
+
+ @Override
+ public void startSearch(String initialQuery) {
+ ContactsRequest request = new ContactsRequest();
+ request.setActionCode(ContactsRequest.ACTION_PICK_CONTACT);
+ request.setDirectorySearchEnabled(false);
+ ContactsSearchManager.startSearchForResult(getActivity(), initialQuery,
+ ACTIVITY_REQUEST_CODE_PICKER, request);
+ }
+
+ @Override
+ public void onPickerResult(Intent data) {
+ mListener.onPickContactAction(data.getData());
+ }
+}
diff --git a/src/com/android/contacts/list/JoinContactLoader.java b/src/com/android/contacts/list/JoinContactLoader.java
new file mode 100644
index 0000000..25c9ab4
--- /dev/null
+++ b/src/com/android/contacts/list/JoinContactLoader.java
@@ -0,0 +1,92 @@
+/*
+ * 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.list;
+
+import android.content.Context;
+import android.content.CursorLoader;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.net.Uri;
+
+/**
+ * A specialized loader for the Join Contacts UI. It executes two queries:
+ * join suggestions and (optionally) the full contact list.
+ */
+public class JoinContactLoader extends CursorLoader {
+
+ private boolean mLoadSuggestionsAndAllContact;
+ private String[] mProjection;
+ private Uri mSuggestionUri;
+ private MatrixCursor mSuggestionsCursor;
+
+ public JoinContactLoader(Context context) {
+ super(context, null, null, null, null, null);
+ }
+
+ public void setLoadSuggestionsAndAllContacts(boolean flag) {
+ mLoadSuggestionsAndAllContact = flag;
+ }
+
+ public void setSuggestionUri(Uri uri) {
+ this.mSuggestionUri = uri;
+ }
+
+ @Override
+ public void setProjection(String[] projection) {
+ super.setProjection(projection);
+ this.mProjection = projection;
+ }
+
+ public Cursor getSuggestionsCursor() {
+ return mSuggestionsCursor;
+ }
+
+ @Override
+ public Cursor loadInBackground() {
+ // First execute the suggestions query, then call super.loadInBackground
+ // to load the entire list
+ mSuggestionsCursor = loadSuggestions();
+ if (!mLoadSuggestionsAndAllContact && mSuggestionsCursor.getCount() != 0) {
+ // In case we only need suggestions, send "0" as the search query, which
+ // will always return an empty cursor (but we can still register to
+ // listen for changes on it).
+ setSelection("0");
+ setSelectionArgs(null);
+ }
+ return super.loadInBackground();
+ }
+
+ /**
+ * Loads join suggestions into a MatrixCursor.
+ */
+ private MatrixCursor loadSuggestions() {
+ Cursor cursor = getContext().getContentResolver().query(mSuggestionUri, mProjection,
+ null, null, null);
+ try {
+ MatrixCursor matrix = new MatrixCursor(mProjection);
+ Object[] row = new Object[mProjection.length];
+ while (cursor.moveToNext()) {
+ for (int i = 0; i < row.length; i++) {
+ row[i] = cursor.getString(i);
+ }
+ matrix.addRow(row);
+ }
+ return matrix;
+ } finally {
+ cursor.close();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/contacts/list/LegacyContactListAdapter.java b/src/com/android/contacts/list/LegacyContactListAdapter.java
new file mode 100644
index 0000000..39c0b53
--- /dev/null
+++ b/src/com/android/contacts/list/LegacyContactListAdapter.java
@@ -0,0 +1,95 @@
+/*
+ * 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.list;
+
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.CursorLoader;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.Contacts.People;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * A cursor adapter for the People.CONTENT_TYPE content type.
+ */
+@SuppressWarnings("deprecation")
+public class LegacyContactListAdapter extends ContactEntryListAdapter {
+
+ static final String[] PEOPLE_PROJECTION = new String[] {
+ People._ID, // 0
+ People.DISPLAY_NAME, // 1
+ People.PHONETIC_NAME, // 2
+ People.STARRED, // 3
+ People.PRESENCE_STATUS, // 4
+ };
+
+ protected static final int PERSON_ID_COLUMN_INDEX = 0;
+ protected static final int PERSON_DISPLAY_NAME_COLUMN_INDEX = 1;
+ protected static final int PERSON_PHONETIC_NAME_COLUMN_INDEX = 2;
+ protected static final int PERSON_STARRED_COLUMN_INDEX = 3;
+ protected static final int PERSON_PRESENCE_STATUS_COLUMN_INDEX = 4;
+
+ private CharSequence mUnknownNameText;
+
+ public LegacyContactListAdapter(Context context) {
+ super(context);
+ mUnknownNameText = context.getText(android.R.string.unknownName);
+ }
+
+ @Override
+ public void configureLoader(CursorLoader loader, long directoryId) {
+ loader.setUri(People.CONTENT_URI);
+ loader.setProjection(PEOPLE_PROJECTION);
+ loader.setSortOrder(People.DISPLAY_NAME);
+ }
+
+ @Override
+ public String getContactDisplayName(int position) {
+ return ((Cursor)getItem(position)).getString(PERSON_DISPLAY_NAME_COLUMN_INDEX);
+ }
+
+ public Uri getPersonUri(int position) {
+ Cursor cursor = ((Cursor)getItem(position));
+ long personId = cursor.getLong(PERSON_ID_COLUMN_INDEX);
+ return ContentUris.withAppendedId(People.CONTENT_URI, personId);
+ }
+
+ @Override
+ protected View newView(Context context, int partition, Cursor cursor, int position,
+ ViewGroup parent) {
+ final ContactListItemView view = new ContactListItemView(context, null);
+ view.setUnknownNameText(mUnknownNameText);
+ return view;
+ }
+
+ @Override
+ protected void bindView(View itemView, int partition, Cursor cursor, int position) {
+ ContactListItemView view = (ContactListItemView)itemView;
+ bindName(view, cursor);
+ bindPresence(view, cursor);
+ }
+
+ protected void bindName(final ContactListItemView view, Cursor cursor) {
+ view.showDisplayName(cursor, PERSON_DISPLAY_NAME_COLUMN_INDEX, false, 0);
+ view.showPhoneticName(cursor, PERSON_PHONETIC_NAME_COLUMN_INDEX);
+ }
+
+ protected void bindPresence(final ContactListItemView view, Cursor cursor) {
+ view.showPresence(cursor, PERSON_PRESENCE_STATUS_COLUMN_INDEX);
+ }
+}
diff --git a/src/com/android/contacts/list/LegacyPhoneNumberListAdapter.java b/src/com/android/contacts/list/LegacyPhoneNumberListAdapter.java
new file mode 100644
index 0000000..47747fb
--- /dev/null
+++ b/src/com/android/contacts/list/LegacyPhoneNumberListAdapter.java
@@ -0,0 +1,108 @@
+/*
+ * 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.list;
+
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.CursorLoader;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.Contacts.People;
+import android.provider.Contacts.Phones;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * A cursor adapter for the Phones.CONTENT_TYPE content type.
+ */
+@SuppressWarnings("deprecation")
+public class LegacyPhoneNumberListAdapter extends ContactEntryListAdapter {
+
+ private static final String[] PHONES_PROJECTION = new String[] {
+ Phones._ID, // 0
+ Phones.TYPE, // 1
+ Phones.LABEL, // 2
+ Phones.NUMBER, // 3
+ People.DISPLAY_NAME, // 4
+ People.PHONETIC_NAME, // 5
+ };
+
+ private static final int PHONE_ID_COLUMN_INDEX = 0;
+ private static final int PHONE_TYPE_COLUMN_INDEX = 1;
+ private static final int PHONE_LABEL_COLUMN_INDEX = 2;
+ private static final int PHONE_NUMBER_COLUMN_INDEX = 3;
+ private static final int PHONE_DISPLAY_NAME_COLUMN_INDEX = 4;
+ private static final int PHONE_PHONETIC_NAME_COLUMN_INDEX = 5;
+
+ private CharSequence mUnknownNameText;
+
+ public LegacyPhoneNumberListAdapter(Context context) {
+ super(context);
+ mUnknownNameText = context.getText(android.R.string.unknownName);
+ }
+
+ @Override
+ public void configureLoader(CursorLoader loader, long directoryId) {
+ loader.setUri(Phones.CONTENT_URI);
+ loader.setProjection(PHONES_PROJECTION);
+ loader.setSortOrder(Phones.DISPLAY_NAME);
+ }
+
+ @Override
+ public String getContactDisplayName(int position) {
+ return ((Cursor)getItem(position)).getString(PHONE_DISPLAY_NAME_COLUMN_INDEX);
+ }
+
+ public Uri getPhoneUri(int position) {
+ Cursor cursor = ((Cursor)getItem(position));
+ long id = cursor.getLong(PHONE_ID_COLUMN_INDEX);
+ return ContentUris.withAppendedId(Phones.CONTENT_URI, id);
+ }
+
+ @Override
+ protected View newView(Context context, int partition, Cursor cursor, int position,
+ ViewGroup parent) {
+ final ContactListItemView view = new ContactListItemView(context, null);
+ view.setUnknownNameText(mUnknownNameText);
+ return view;
+ }
+
+ @Override
+ protected void bindView(View itemView, int partition, Cursor cursor, int position) {
+ ContactListItemView view = (ContactListItemView)itemView;
+ bindName(view, cursor);
+ bindPhoneNumber(view, cursor);
+ }
+
+ protected void bindName(final ContactListItemView view, Cursor cursor) {
+ view.showDisplayName(cursor, PHONE_DISPLAY_NAME_COLUMN_INDEX, false, 0);
+ view.showPhoneticName(cursor, PHONE_PHONETIC_NAME_COLUMN_INDEX);
+ }
+
+ protected void bindPhoneNumber(ContactListItemView view, Cursor cursor) {
+ CharSequence label = null;
+ if (!cursor.isNull(PHONE_TYPE_COLUMN_INDEX)) {
+ final int type = cursor.getInt(PHONE_TYPE_COLUMN_INDEX);
+ final String customLabel = cursor.getString(PHONE_LABEL_COLUMN_INDEX);
+
+ // TODO cache
+ label = Phone.getTypeLabel(getContext().getResources(), type, customLabel);
+ }
+ view.setLabel(label);
+ view.showData(cursor, PHONE_NUMBER_COLUMN_INDEX);
+ }
+}
diff --git a/src/com/android/contacts/list/LegacyPostalAddressListAdapter.java b/src/com/android/contacts/list/LegacyPostalAddressListAdapter.java
new file mode 100644
index 0000000..3796c62
--- /dev/null
+++ b/src/com/android/contacts/list/LegacyPostalAddressListAdapter.java
@@ -0,0 +1,109 @@
+/*
+ * 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.list;
+
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.CursorLoader;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.Contacts.ContactMethods;
+import android.provider.Contacts.People;
+import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * A cursor adapter for the ContactMethods.CONTENT_TYPE content type.
+ */
+@SuppressWarnings("deprecation")
+public class LegacyPostalAddressListAdapter extends ContactEntryListAdapter {
+
+ static final String[] POSTALS_PROJECTION = new String[] {
+ ContactMethods._ID, // 0
+ ContactMethods.TYPE, // 1
+ ContactMethods.LABEL, // 2
+ ContactMethods.DATA, // 3
+ People.DISPLAY_NAME, // 4
+ People.PHONETIC_NAME, // 5
+ };
+
+ public static final int POSTAL_ID_COLUMN_INDEX = 0;
+ public static final int POSTAL_TYPE_COLUMN_INDEX = 1;
+ public static final int POSTAL_LABEL_COLUMN_INDEX = 2;
+ public static final int POSTAL_NUMBER_COLUMN_INDEX = 3;
+ public static final int POSTAL_DISPLAY_NAME_COLUMN_INDEX = 4;
+ public static final int POSTAL_PHONETIC_NAME_COLUMN_INDEX = 5;
+
+ private CharSequence mUnknownNameText;
+
+ public LegacyPostalAddressListAdapter(Context context) {
+ super(context);
+ mUnknownNameText = context.getText(android.R.string.unknownName);
+ }
+
+ @Override
+ public void configureLoader(CursorLoader loader, long directoryId) {
+ loader.setUri(ContactMethods.CONTENT_URI);
+ loader.setProjection(POSTALS_PROJECTION);
+ loader.setSortOrder(People.DISPLAY_NAME);
+ loader.setSelection(ContactMethods.KIND + "=" + android.provider.Contacts.KIND_POSTAL);
+ }
+
+ @Override
+ public String getContactDisplayName(int position) {
+ return ((Cursor)getItem(position)).getString(POSTAL_DISPLAY_NAME_COLUMN_INDEX);
+ }
+
+ public Uri getContactMethodUri(int position) {
+ Cursor cursor = ((Cursor)getItem(position));
+ long id = cursor.getLong(POSTAL_ID_COLUMN_INDEX);
+ return ContentUris.withAppendedId(ContactMethods.CONTENT_URI, id);
+ }
+
+ @Override
+ protected View newView(Context context, int partition, Cursor cursor, int position,
+ ViewGroup parent) {
+ final ContactListItemView view = new ContactListItemView(context, null);
+ view.setUnknownNameText(mUnknownNameText);
+ return view;
+ }
+
+ @Override
+ protected void bindView(View itemView, int partition, Cursor cursor, int position) {
+ ContactListItemView view = (ContactListItemView)itemView;
+ bindName(view, cursor);
+ bindPostalAddress(view, cursor);
+ }
+
+ protected void bindName(final ContactListItemView view, Cursor cursor) {
+ view.showDisplayName(cursor, POSTAL_DISPLAY_NAME_COLUMN_INDEX, false, 0);
+ view.showPhoneticName(cursor, POSTAL_PHONETIC_NAME_COLUMN_INDEX);
+ }
+
+ protected void bindPostalAddress(ContactListItemView view, Cursor cursor) {
+ CharSequence label = null;
+ if (!cursor.isNull(POSTAL_TYPE_COLUMN_INDEX)) {
+ final int type = cursor.getInt(POSTAL_TYPE_COLUMN_INDEX);
+ final String customLabel = cursor.getString(POSTAL_LABEL_COLUMN_INDEX);
+
+ // TODO cache
+ label = StructuredPostal.getTypeLabel(getContext().getResources(), type, customLabel);
+ }
+ view.setLabel(label);
+ view.showData(cursor, POSTAL_NUMBER_COLUMN_INDEX);
+ }
+}
diff --git a/src/com/android/contacts/list/MultiplePhonePickerAdapter.java b/src/com/android/contacts/list/MultiplePhonePickerAdapter.java
new file mode 100644
index 0000000..1eba085
--- /dev/null
+++ b/src/com/android/contacts/list/MultiplePhonePickerAdapter.java
@@ -0,0 +1,361 @@
+/*
+ * 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.list;
+
+import com.android.contacts.R;
+
+import android.content.ContentUris;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.text.TextUtils;
+import android.util.SparseIntArray;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.CheckBox;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+
+/**
+ * List adapter for the multiple phone picker.
+ */
+public class MultiplePhonePickerAdapter extends PhoneNumberListAdapter {
+
+ public interface OnSelectionChangeListener {
+ void onSelectionChange();
+ }
+
+ private static final int[] CHIP_COLOR_ARRAY = {
+ R.drawable.appointment_indicator_leftside_1,
+ R.drawable.appointment_indicator_leftside_2,
+ R.drawable.appointment_indicator_leftside_3,
+ R.drawable.appointment_indicator_leftside_4,
+ R.drawable.appointment_indicator_leftside_5,
+ R.drawable.appointment_indicator_leftside_6,
+ R.drawable.appointment_indicator_leftside_7,
+ R.drawable.appointment_indicator_leftside_8,
+ R.drawable.appointment_indicator_leftside_9,
+ R.drawable.appointment_indicator_leftside_10,
+ R.drawable.appointment_indicator_leftside_11,
+ R.drawable.appointment_indicator_leftside_12,
+ R.drawable.appointment_indicator_leftside_13,
+ R.drawable.appointment_indicator_leftside_14,
+ R.drawable.appointment_indicator_leftside_15,
+ R.drawable.appointment_indicator_leftside_16,
+ R.drawable.appointment_indicator_leftside_17,
+ R.drawable.appointment_indicator_leftside_18,
+ R.drawable.appointment_indicator_leftside_19,
+ R.drawable.appointment_indicator_leftside_20,
+ R.drawable.appointment_indicator_leftside_21,
+ };
+
+ public static final long INVALID_PHONE_ID = -1;
+
+ /** The phone numbers */
+ private ArrayList<String> mPhoneNumbers = new ArrayList<String>();
+
+ /** The selected phone numbers in the PhoneNumberAdapter */
+ private HashSet<String> mSelectedPhoneNumbers = new HashSet<String>();
+
+ /** The phone numbers after the filtering */
+ private ArrayList<String> mFilteredPhoneNumbers = new ArrayList<String>();
+
+ /** The PHONE_ID of selected number in user contacts*/
+ private HashSet<Long> mSelectedPhoneIds = new HashSet<Long>();
+
+ private boolean mSelectionChanged;
+
+ private OnSelectionChangeListener mSelectionChangeListener;
+
+ /**
+ * This is a map from contact ID to color index. A colored chip is used to
+ * indicate the number of phone numbers belong to one contact
+ */
+ private SparseIntArray mContactColor = new SparseIntArray();
+
+ public MultiplePhonePickerAdapter(Context context) {
+ super(context);
+ }
+
+ public void setOnSelectionChangeListener(OnSelectionChangeListener listener) {
+ this.mSelectionChangeListener = listener;
+ }
+
+ public void setPhoneNumbers(ArrayList<String> phoneNumbers) {
+ mPhoneNumbers.clear();
+ mPhoneNumbers.addAll(phoneNumbers);
+ }
+
+ public int getSelectedCount() {
+ return mSelectedPhoneNumbers.size() + mSelectedPhoneIds.size();
+ }
+
+ public Uri[] getSelectedUris() {
+ Uri[] uris = new Uri[mSelectedPhoneNumbers.size() + mSelectedPhoneIds.size()];
+ int count = mPhoneNumbers.size();
+ int index = 0;
+ for (int i = 0; i < count; i++) {
+ String phoneNumber = mPhoneNumbers.get(i);
+ if (isSelected(phoneNumber)) {
+ uris[index++] = Uri.parse("tel:" + phoneNumber);
+ }
+ }
+ for (Long contactId : mSelectedPhoneIds) {
+ uris[index++] = ContentUris.withAppendedId(Phone.CONTENT_URI, contactId);
+ }
+ return uris;
+ }
+
+ public void setSelectedUris(Uri[] uris) {
+ mSelectedPhoneNumbers.clear();
+ mSelectedPhoneIds.clear();
+ if (uris != null) {
+ for (Uri uri : uris) {
+ String scheme = uri.getScheme();
+ if ("tel".equals(scheme)) {
+ String phoneNumber = uri.getSchemeSpecificPart();
+ if (!mPhoneNumbers.contains(phoneNumber)) {
+ mPhoneNumbers.add(phoneNumber);
+ }
+ mSelectedPhoneNumbers.add(phoneNumber);
+ } else if ("content".equals(scheme)) {
+ mSelectedPhoneIds.add(ContentUris.parseId(uri));
+ }
+ }
+ }
+ mFilteredPhoneNumbers.clear();
+ mFilteredPhoneNumbers.addAll(mPhoneNumbers);
+ }
+
+ public void toggleSelection(int position) {
+ if (position < mFilteredPhoneNumbers.size()) {
+ String phoneNumber = mPhoneNumbers.get(position);
+ setPhoneSelected(phoneNumber, !isSelected(phoneNumber));
+ } else {
+ Cursor cursor = ((Cursor)getItem(position));
+ cursor.moveToPosition(position - mFilteredPhoneNumbers.size());
+ long phoneId = cursor.getLong(PHONE_ID_COLUMN_INDEX);
+ setPhoneSelected(phoneId, !isSelected(phoneId));
+ }
+ notifyDataSetChanged();
+ }
+
+ public boolean isSelectionChanged() {
+ return mSelectionChanged;
+ }
+
+ public void setSelectionChanged(boolean flag) {
+ mSelectionChanged = flag;
+ if (mSelectionChangeListener != null) {
+ mSelectionChangeListener.onSelectionChange();
+ }
+ }
+
+ public void setPhoneSelected(final String phoneNumber, boolean selected) {
+ if (!TextUtils.isEmpty(phoneNumber)) {
+ if (selected) {
+ mSelectedPhoneNumbers.add(phoneNumber);
+ } else {
+ mSelectedPhoneNumbers.remove(phoneNumber);
+ }
+ }
+ setSelectionChanged(true);
+ }
+
+ public void setPhoneSelected(long phoneId, boolean selected) {
+ if (selected) {
+ mSelectedPhoneIds.add(phoneId);
+ } else {
+ mSelectedPhoneIds.remove(phoneId);
+ }
+ setSelectionChanged(true);
+ }
+
+ public boolean isSelected(long phoneId) {
+ return mSelectedPhoneIds.contains(phoneId);
+ }
+
+ public boolean isSelected(final String phoneNumber) {
+ return mSelectedPhoneNumbers.contains(phoneNumber);
+ }
+
+ public void setAllPhonesSelected(boolean selected) {
+// if (selected) {
+// Cursor cursor = this.mMultiplePhoneSelectionActivity.mAdapter.getCursor();
+// if (cursor != null) {
+// int backupPos = cursor.getPosition();
+// cursor.moveToPosition(-1);
+// while (cursor.moveToNext()) {
+// setPhoneSelected(cursor
+// .getLong(MultiplePhonePickerActivity.PHONE_ID_COLUMN_INDEX), true);
+// }
+// cursor.moveToPosition(backupPos);
+// }
+// for (String number : this.mMultiplePhoneSelectionActivity.mPhoneNumberAdapter
+// .getFilteredPhoneNumbers()) {
+// setPhoneSelected(number, true);
+// }
+// } else {
+// mSelectedPhoneIds.clear();
+// mSelectedPhoneNumbers.clear();
+// }
+ }
+
+ public boolean isAllSelected() {
+ return false;
+// return selectedCount() == this.mMultiplePhoneSelectionActivity.mPhoneNumberAdapter
+// .getFilteredPhoneNumbers().size()
+// + this.mMultiplePhoneSelectionActivity.mAdapter.getCount();
+ }
+
+ public Iterator<Long> getSelectedPhoneIds() {
+ return mSelectedPhoneIds.iterator();
+ }
+
+ @Override
+ public int getItemViewTypeCount() {
+ return 2;
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ return position < mPhoneNumbers.size() ? 0 : 1;
+ }
+
+ // TODO redo as two separate partitions
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ View view = null;
+// if (convertView == null || convertView.getTag() == null) {
+// view = newView(getContext(), null, parent);
+// } else {
+// view = convertView;
+// }
+//
+// boolean showingSuggestion = false;
+//
+// if (position < mFilteredPhoneNumbers.size()) {
+// bindExtraPhoneView(view, position);
+// } else {
+// Cursor cursor = ((Cursor)getItem(position));
+// cursor.moveToPosition(position - mFilteredPhoneNumbers.size());
+// bindView(view, getContext(), cursor);
+// }
+ return view;
+ }
+
+ @Override
+ protected View newView(Context context, int partition, Cursor cursor, int position,
+ ViewGroup parent) {
+ final MultiplePhonePickerItemView view = new MultiplePhonePickerItemView(context, null);
+ view.setUnknownNameText(getUnknownNameText());
+ view.setTextWithHighlightingFactory(getTextWithHighlightingFactory());
+ return view;
+ }
+
+ private void bindExtraPhoneView(View itemView, int position) {
+ final MultiplePhonePickerItemView view = (MultiplePhonePickerItemView)itemView;
+ String phoneNumber = mFilteredPhoneNumbers.get(position);
+ view.getNameTextView().setText(phoneNumber);
+ CheckBox checkBox = view.getCheckBoxView();
+ checkBox.setChecked(isSelected(phoneNumber));
+ view.phoneId = INVALID_PHONE_ID;
+ view.phoneNumber = phoneNumber;
+ }
+
+ @Override
+ protected void bindView(View itemView, int partition, Cursor cursor, int position) {
+ super.bindView(itemView, partition, cursor, position);
+
+ final MultiplePhonePickerItemView view = (MultiplePhonePickerItemView)itemView;
+ view.phoneId = Long.valueOf(cursor.getLong(PHONE_ID_COLUMN_INDEX));
+ CheckBox checkBox = view.getCheckBoxView();
+ checkBox.setChecked(isSelected(view.phoneId));
+
+ long contactId = cursor.getLong(PHONE_CONTACT_ID_COLUMN_INDEX);
+ view.getChipView().setBackgroundResource(getChipColor(contactId));
+ }
+
+// @Override
+// protected void prepareEmptyView() {
+// mMultiplePhonePickerActivity.mEmptyView.show(mMultiplePhonePickerActivity.mSearchMode,
+// true, false, false, false, true, mMultiplePhonePickerActivity.mShowSelectedOnly);
+// }
+
+ /**
+ * Get assigned chip color resource id for a given contact, 0 is returned if there is no mapped
+ * resource.
+ */
+ public int getChipColor(long contactId) {
+ return mContactColor.get((int)contactId);
+ }
+
+ // TODO filtering
+// public void doFilter(final String constraint, boolean selectedOnly) {
+// if (mPhoneNumbers == null) {
+// return;
+// }
+// mFilteredPhoneNumbers.clear();
+// for (String number : mPhoneNumbers) {
+// if (selectedOnly && !mSelection.isSelected(number) ||
+// !TextUtils.isEmpty(constraint) && !number.startsWith(constraint)) {
+// continue;
+// }
+// mFilteredPhoneNumbers.add(number);
+// }
+// }
+
+ @Override
+ public int getCount() {
+ return super.getCount() + mFilteredPhoneNumbers.size();
+ }
+
+ @Override
+ public void changeCursor(Cursor cursor) {
+ super.changeCursor(cursor);
+ updateChipColor(cursor);
+ }
+
+ /**
+ * Go through the cursor and assign the chip color to contact who has more
+ * than one phone numbers. Assume the cursor is clustered by CONTACT_ID.
+ */
+ public void updateChipColor(Cursor cursor) {
+ if (cursor == null || cursor.getCount() == 0) {
+ return;
+ }
+ mContactColor.clear();
+ cursor.moveToFirst();
+ int colorIndex = 0;
+ long prevContactId = cursor.getLong(PHONE_CONTACT_ID_COLUMN_INDEX);
+ while (cursor.moveToNext()) {
+ long contactId = cursor.getLong(PHONE_CONTACT_ID_COLUMN_INDEX);
+ if (prevContactId == contactId) {
+ if (mContactColor.indexOfKey((int)contactId) < 0) {
+ mContactColor.put((int)contactId, CHIP_COLOR_ARRAY[colorIndex]);
+ colorIndex++;
+ if (colorIndex >= CHIP_COLOR_ARRAY.length) {
+ colorIndex = 0;
+ }
+ }
+ }
+ prevContactId = contactId;
+ }
+ }
+}
diff --git a/src/com/android/contacts/list/MultiplePhonePickerFragment.java b/src/com/android/contacts/list/MultiplePhonePickerFragment.java
new file mode 100644
index 0000000..bf75a9f
--- /dev/null
+++ b/src/com/android/contacts/list/MultiplePhonePickerFragment.java
@@ -0,0 +1,181 @@
+/*
+ * 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.list;
+
+import com.android.contacts.R;
+import com.android.contacts.list.MultiplePhonePickerAdapter.OnSelectionChangeListener;
+
+import android.app.Activity;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewStub;
+import android.view.View.OnClickListener;
+import android.view.animation.AnimationUtils;
+import android.widget.Button;
+
+/**
+ * Fragment for the multiple phone picker.
+ */
+public class MultiplePhonePickerFragment
+ extends ContactEntryListFragment<MultiplePhonePickerAdapter>
+ implements OnClickListener, OnSelectionChangeListener {
+
+ private static final String SELECTION_EXTRA_KEY = "selection";
+ private static final String SELECTION_CHANGED_EXTRA_KEY = "selectionChanged";
+
+ private OnMultiplePhoneNumberPickerActionListener mListener;
+
+ /**
+ * UI control of action panel in MODE_PICK_MULTIPLE_PHONES mode.
+ */
+ private View mFooterView;
+
+ private Uri[] mSelectedUris;
+ private boolean mSelectionChanged;
+
+ public MultiplePhonePickerFragment() {
+ setSectionHeaderDisplayEnabled(false);
+ setPhotoLoaderEnabled(true);
+ }
+
+ public void setOnMultiplePhoneNumberPickerActionListener(
+ OnMultiplePhoneNumberPickerActionListener listener) {
+ mListener = listener;
+ }
+
+ public Uri[] getSelectedUris() {
+ return getAdapter().getSelectedUris();
+ }
+
+ public void setSelectedUris(Parcelable[] extras) {
+ Uri[] uris = new Uri[extras == null ? 0 : extras.length];
+ if (extras != null) {
+ for (int i = 0; i < extras.length; i++) {
+ uris[i] = (Uri)extras[i];
+ }
+ }
+ setSelectedUris(uris);
+ }
+
+ public void setSelectedUris(Uri[] uris) {
+ mSelectedUris = uris;
+ MultiplePhonePickerAdapter adapter = getAdapter();
+ if (adapter != null) {
+ adapter.setSelectedUris(uris);
+ }
+ }
+
+ @Override
+ protected MultiplePhonePickerAdapter createListAdapter() {
+ return new MultiplePhonePickerAdapter(getActivity());
+ }
+
+ @Override
+ protected void configureAdapter() {
+ super.configureAdapter();
+ MultiplePhonePickerAdapter adapter = getAdapter();
+ adapter.setSelectedUris(mSelectedUris);
+ adapter.setSelectionChanged(mSelectionChanged);
+ adapter.setOnSelectionChangeListener(this);
+ }
+
+ @Override
+ protected View inflateView(LayoutInflater inflater, ViewGroup container) {
+ View view = inflater.inflate(R.layout.contacts_list_content, null);
+ ViewStub stub = (ViewStub)view.findViewById(R.id.footer_stub);
+ if (stub != null) {
+ View stubView = stub.inflate();
+ mFooterView = stubView.findViewById(R.id.footer);
+ mFooterView.setVisibility(View.GONE);
+ Button doneButton = (Button) stubView.findViewById(R.id.done);
+ doneButton.setOnClickListener(this);
+ Button revertButton = (Button) stubView.findViewById(R.id.revert);
+ revertButton.setOnClickListener(this);
+ }
+ return view;
+ }
+
+ @Override
+ protected void onItemClick(int position, long id) {
+ getAdapter().toggleSelection(position);
+ }
+
+ public void onClick(View v) {
+ int id = v.getId();
+ switch (id) {
+ case R.id.done:
+ mListener.onPhoneNumbersSelectedAction(getAdapter().getSelectedUris());
+ break;
+ case R.id.revert:
+ mListener.onFinishAction();
+ break;
+ }
+ }
+
+ @Override
+ public void onCreate(Bundle savedState) {
+ super.onCreate(savedState);
+ if (savedState != null) {
+ setSelectedUris(savedState.getParcelableArray(SELECTION_EXTRA_KEY));
+ mSelectionChanged = savedState.getBoolean(SELECTION_CHANGED_EXTRA_KEY, false);
+ if (getAdapter() != null) {
+ getAdapter().setSelectionChanged(mSelectionChanged);
+ }
+ }
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ updateWidgets();
+ }
+
+ public void onSelectionChange() {
+ updateWidgets();
+ }
+
+ private void updateWidgets() {
+ int selected = getAdapter().getSelectedCount();
+
+ Activity context = getActivity();
+ if (selected >= 1) {
+ final String format = context.getResources().getQuantityString(
+ R.plurals.multiple_picker_title, selected);
+
+ // TODO: turn this into a callback
+ context.setTitle(String.format(format, selected));
+ } else {
+ // TODO: turn this into a callback
+ context.setTitle(context.getString(R.string.contactsList));
+ }
+
+ if (getAdapter().isSelectionChanged() && mFooterView.getVisibility() == View.GONE) {
+ mFooterView.setVisibility(View.VISIBLE);
+ mFooterView.startAnimation(AnimationUtils.loadAnimation(context, R.anim.footer_appear));
+ }
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle icicle) {
+ super.onSaveInstanceState(icicle);
+ icicle.putParcelableArray(SELECTION_EXTRA_KEY, getAdapter().getSelectedUris());
+ icicle.putBoolean(SELECTION_CHANGED_EXTRA_KEY, getAdapter().isSelectionChanged());
+ }
+}
diff --git a/src/com/android/contacts/list/MultiplePhonePickerItemView.java b/src/com/android/contacts/list/MultiplePhonePickerItemView.java
new file mode 100644
index 0000000..4801d33
--- /dev/null
+++ b/src/com/android/contacts/list/MultiplePhonePickerItemView.java
@@ -0,0 +1,121 @@
+/*
+ * 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.list;
+
+import com.android.contacts.R;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.CheckBox;
+
+/**
+ * A custom view for an item in the phone multi-picker list.
+ */
+public class MultiplePhonePickerItemView extends ContactListItemView {
+
+ // Used to indicate the sequence of phones belong to the same contact in multi-picker
+ private View mChipView;
+ // Used to select the phone in multi-picker
+ private CheckBox mCheckBox;
+
+ private int mChipWidth;
+ private int mChipRightMargin;
+ private int mCheckBoxMargin;
+
+ public long phoneId;
+ // phoneNumber only validates when phoneId = INVALID_PHONE_ID
+ public String phoneNumber;
+
+ public MultiplePhonePickerItemView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ Resources resources = context.getResources();
+ mChipWidth =
+ resources.getDimensionPixelOffset(R.dimen.list_item_header_chip_width);
+ mChipRightMargin =
+ resources.getDimensionPixelOffset(R.dimen.list_item_header_chip_right_margin);
+ mCheckBoxMargin =
+ resources.getDimensionPixelOffset(R.dimen.list_item_header_checkbox_margin);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ if (isVisible(mChipView)) {
+ mChipView.measure(0, 0);
+ }
+
+ if (isVisible(mCheckBox)) {
+ mCheckBox.measure(0, 0);
+ }
+
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+
+ @Override
+ protected int layoutLeftSide(int height, int topBound, int leftBound) {
+ if (mChipView != null) {
+ mChipView.layout(leftBound, topBound, leftBound + mChipWidth, height);
+ leftBound += mChipWidth + mChipRightMargin;
+ }
+
+ return super.layoutLeftSide(height, topBound, leftBound);
+ }
+
+ @Override
+ protected int layoutRightSide(int height, int topBound, int rightBound) {
+ rightBound = super.layoutRightSide(height, topBound, rightBound);
+
+ if (isVisible(mCheckBox)) {
+ int checkBoxWidth = mCheckBox.getMeasuredWidth();
+ int checkBoxHight = mCheckBox.getMeasuredHeight();
+ rightBound -= mCheckBoxMargin + checkBoxWidth;
+ int checkBoxTop = topBound + (height - topBound - checkBoxHight) / 2;
+ mCheckBox.layout(
+ rightBound,
+ checkBoxTop,
+ rightBound + checkBoxWidth,
+ checkBoxTop + checkBoxHight);
+ }
+
+ return rightBound;
+ }
+
+ /**
+ * Returns the chip view for the multipicker, creating it if necessary.
+ */
+ public View getChipView() {
+ if (mChipView == null) {
+ mChipView = new View(mContext);
+ addView(mChipView);
+ }
+ return mChipView;
+ }
+
+ /**
+ * Returns the CheckBox view for the multipicker, creating it if necessary.
+ */
+ public CheckBox getCheckBoxView() {
+ if (mCheckBox == null) {
+ mCheckBox = new CheckBox(mContext);
+ mCheckBox.setClickable(false);
+ mCheckBox.setFocusable(false);
+ addView(mCheckBox);
+ }
+ return mCheckBox;
+ }
+}
diff --git a/src/com/android/contacts/list/OnContactBrowserActionListener.java b/src/com/android/contacts/list/OnContactBrowserActionListener.java
new file mode 100644
index 0000000..5291639
--- /dev/null
+++ b/src/com/android/contacts/list/OnContactBrowserActionListener.java
@@ -0,0 +1,72 @@
+/*
+ * 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.list;
+
+import android.net.Uri;
+
+/**
+ * Action callbacks that can be sent by a contact list.
+ */
+public interface OnContactBrowserActionListener {
+
+ /**
+ * Opens the specified contact for viewing.
+ *
+ * @param contactLookupUri The lookup-uri of the Contact that should be opened
+ * @param finishEditing The user has explicitly requested to leave the edit mode
+ */
+ void onViewContactAction(Uri contactLookupUri, boolean finishEditing);
+
+ /**
+ * Creates a new contact.
+ */
+ void onCreateNewContactAction();
+
+ /**
+ * Opens the specified contact for editing.
+ */
+ void onEditContactAction(Uri contactLookupUri);
+
+ /**
+ * Initiates the contact deletion process.
+ */
+ void onDeleteContactAction(Uri contactUri);
+
+ /**
+ * Adds the specified contact to favorites
+ */
+ void onAddToFavoritesAction(Uri contactUri);
+
+ /**
+ * Removes the specified contact from favorites.
+ */
+ void onRemoveFromFavoritesAction(Uri contactUri);
+
+ /**
+ * Places a call to the specified contact.
+ */
+ void onCallContactAction(Uri contactUri);
+
+ /**
+ * Initiates a text message to the specified contact.
+ */
+ void onSmsContactAction(Uri contactUri);
+
+ /**
+ * Closes the contact browser.
+ */
+ void onFinishAction();
+}
diff --git a/src/com/android/contacts/list/OnContactPickerActionListener.java b/src/com/android/contacts/list/OnContactPickerActionListener.java
new file mode 100644
index 0000000..1216888
--- /dev/null
+++ b/src/com/android/contacts/list/OnContactPickerActionListener.java
@@ -0,0 +1,40 @@
+/*
+ * 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.list;
+
+import android.content.Intent;
+import android.net.Uri;
+
+/**
+ * Action callbacks that can be sent by a contact picker.
+ */
+public interface OnContactPickerActionListener {
+
+ /**
+ * Creates a new contact and then returns it to the caller.
+ */
+ void onCreateNewContactAction();
+
+ /**
+ * Returns the selected contact to the requester.
+ */
+ void onPickContactAction(Uri contactUri);
+
+ /**
+ * Returns the selected contact as a shortcut intent.
+ */
+ void onShortcutIntentCreated(Intent intent);
+}
diff --git a/src/com/android/contacts/list/OnMultiplePhoneNumberPickerActionListener.java b/src/com/android/contacts/list/OnMultiplePhoneNumberPickerActionListener.java
new file mode 100644
index 0000000..ac010ba
--- /dev/null
+++ b/src/com/android/contacts/list/OnMultiplePhoneNumberPickerActionListener.java
@@ -0,0 +1,34 @@
+/*
+ * 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.list;
+
+import android.net.Uri;
+
+/**
+ * Action callbacks that can be sent by a multiple phone number picker.
+ */
+public interface OnMultiplePhoneNumberPickerActionListener {
+
+ /**
+ * Returns the selected phone numbers to the requester.
+ */
+ void onPhoneNumbersSelectedAction(Uri[] dataUris);
+
+ /**
+ * Closes the picker without changing the selection.
+ */
+ void onFinishAction();
+}
diff --git a/src/com/android/contacts/list/OnPhoneNumberPickerActionListener.java b/src/com/android/contacts/list/OnPhoneNumberPickerActionListener.java
new file mode 100644
index 0000000..1a90122
--- /dev/null
+++ b/src/com/android/contacts/list/OnPhoneNumberPickerActionListener.java
@@ -0,0 +1,35 @@
+/*
+ * 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.list;
+
+import android.content.Intent;
+import android.net.Uri;
+
+/**
+ * Action callbacks that can be sent by a phone number picker.
+ */
+public interface OnPhoneNumberPickerActionListener {
+
+ /**
+ * Returns the selected phone number to the requester.
+ */
+ void onPickPhoneNumberAction(Uri dataUri);
+
+ /**
+ * Returns the selected number as a shortcut intent.
+ */
+ void onShortcutIntentCreated(Intent intent);
+}
diff --git a/src/com/android/contacts/SearchResultsActivity.java b/src/com/android/contacts/list/OnPostalAddressPickerActionListener.java
similarity index 61%
copy from src/com/android/contacts/SearchResultsActivity.java
copy to src/com/android/contacts/list/OnPostalAddressPickerActionListener.java
index 09f0014..6ecde61 100644
--- a/src/com/android/contacts/SearchResultsActivity.java
+++ b/src/com/android/contacts/list/OnPostalAddressPickerActionListener.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2007 The Android Open Source Project
+ * 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.
@@ -13,11 +13,17 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.contacts;
+package com.android.contacts.list;
+
+import android.net.Uri;
/**
- * The activity that displays the list of contact search results. We need a separate
- * class because it uses a different theme from {@link ContactsListActivity}.
+ * Action callbacks that can be sent by a postal address picker.
*/
-public class SearchResultsActivity extends ContactsListActivity {
+public interface OnPostalAddressPickerActionListener {
+
+ /**
+ * Returns the selected phone number to the requester.
+ */
+ void onPickPostalAddressAction(Uri dataUri);
}
diff --git a/src/com/android/contacts/list/PhoneNumberListAdapter.java b/src/com/android/contacts/list/PhoneNumberListAdapter.java
new file mode 100644
index 0000000..0bfd6dc
--- /dev/null
+++ b/src/com/android/contacts/list/PhoneNumberListAdapter.java
@@ -0,0 +1,220 @@
+/*
+ * 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.list;
+
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.CursorLoader;
+import android.database.Cursor;
+import android.net.Uri;
+import android.net.Uri.Builder;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.ContactCounts;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.Directory;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.text.TextUtils;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * A cursor adapter for the {@link Phone#CONTENT_TYPE} content type.
+ */
+public class PhoneNumberListAdapter extends ContactEntryListAdapter {
+
+ protected static final String[] PHONES_PROJECTION = new String[] {
+ Phone._ID, // 0
+ Phone.TYPE, // 1
+ Phone.LABEL, // 2
+ Phone.NUMBER, // 3
+ Phone.DISPLAY_NAME_PRIMARY, // 4
+ Phone.DISPLAY_NAME_ALTERNATIVE, // 5
+ Phone.CONTACT_ID, // 6
+ Phone.PHOTO_ID, // 7
+ Phone.PHONETIC_NAME, // 8
+ };
+
+ protected static final int PHONE_ID_COLUMN_INDEX = 0;
+ protected static final int PHONE_TYPE_COLUMN_INDEX = 1;
+ protected static final int PHONE_LABEL_COLUMN_INDEX = 2;
+ protected static final int PHONE_NUMBER_COLUMN_INDEX = 3;
+ protected static final int PHONE_PRIMARY_DISPLAY_NAME_COLUMN_INDEX = 4;
+ protected static final int PHONE_ALTERNATIVE_DISPLAY_NAME_COLUMN_INDEX = 5;
+ protected static final int PHONE_CONTACT_ID_COLUMN_INDEX = 6;
+ protected static final int PHONE_PHOTO_ID_COLUMN_INDEX = 7;
+ protected static final int PHONE_PHONETIC_NAME_COLUMN_INDEX = 8;
+
+ private CharSequence mUnknownNameText;
+ private int mDisplayNameColumnIndex;
+ private int mAlternativeDisplayNameColumnIndex;
+ private boolean mVisibleContactsOnly = true;
+
+ public PhoneNumberListAdapter(Context context) {
+ super(context);
+
+ mUnknownNameText = context.getText(android.R.string.unknownName);
+ }
+
+ protected CharSequence getUnknownNameText() {
+ return mUnknownNameText;
+ }
+
+ public void setVisibleContactsOnly(boolean flag) {
+ mVisibleContactsOnly = flag;
+ }
+
+ @Override
+ public void configureLoader(CursorLoader loader, long directoryId) {
+ Uri uri;
+
+ if (isSearchMode()) {
+ String query = getQueryString();
+ Builder builder = Phone.CONTENT_FILTER_URI.buildUpon();
+ if (TextUtils.isEmpty(query)) {
+ builder.appendPath("");
+ } else {
+ builder.appendPath(query); // Builder will encode the query
+ }
+
+ builder.appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY,
+ String.valueOf(directoryId));
+ uri = builder.build();
+ // TODO a projection that includes the search snippet
+ loader.setProjection(PHONES_PROJECTION);
+ } else {
+ uri = Phone.CONTENT_URI;
+ loader.setProjection(PHONES_PROJECTION);
+ }
+
+ if (directoryId == Directory.DEFAULT) {
+ if (mVisibleContactsOnly) {
+ loader.setSelection(Contacts.IN_VISIBLE_GROUP + "=1");
+ }
+ if (isSectionHeaderDisplayEnabled()) {
+ uri = buildSectionIndexerUri(uri);
+ }
+ }
+
+ loader.setUri(uri);
+ if (getSortOrder() == ContactsContract.Preferences.SORT_ORDER_PRIMARY) {
+ loader.setSortOrder(Phone.SORT_KEY_PRIMARY);
+ } else {
+ loader.setSortOrder(Phone.SORT_KEY_ALTERNATIVE);
+ }
+ }
+
+ protected static Uri buildSectionIndexerUri(Uri uri) {
+ return uri.buildUpon()
+ .appendQueryParameter(ContactCounts.ADDRESS_BOOK_INDEX_EXTRAS, "true").build();
+ }
+
+ @Override
+ public String getContactDisplayName(int position) {
+ return ((Cursor)getItem(position)).getString(mDisplayNameColumnIndex);
+ }
+
+ @Override
+ public void setContactNameDisplayOrder(int displayOrder) {
+ super.setContactNameDisplayOrder(displayOrder);
+ if (getContactNameDisplayOrder() == ContactsContract.Preferences.DISPLAY_ORDER_PRIMARY) {
+ mDisplayNameColumnIndex = PHONE_PRIMARY_DISPLAY_NAME_COLUMN_INDEX;
+ mAlternativeDisplayNameColumnIndex = PHONE_ALTERNATIVE_DISPLAY_NAME_COLUMN_INDEX;
+ } else {
+ mDisplayNameColumnIndex = PHONE_ALTERNATIVE_DISPLAY_NAME_COLUMN_INDEX;
+ mAlternativeDisplayNameColumnIndex = PHONE_PRIMARY_DISPLAY_NAME_COLUMN_INDEX;
+ }
+ }
+
+ /**
+ * Builds a {@link Data#CONTENT_URI} for the given cursor position.
+ */
+ public Uri getDataUri(int position) {
+ Cursor cursor = ((Cursor)getItem(position));
+ long id = cursor.getLong(PHONE_ID_COLUMN_INDEX);
+ return ContentUris.withAppendedId(Data.CONTENT_URI, id);
+ }
+
+ @Override
+ protected View newView(Context context, int partition, Cursor cursor, int position,
+ ViewGroup parent) {
+ final ContactListItemView view = new ContactListItemView(context, null);
+ view.setUnknownNameText(mUnknownNameText);
+ view.setTextWithHighlightingFactory(getTextWithHighlightingFactory());
+ return view;
+ }
+
+ @Override
+ protected void bindView(View itemView, int partition, Cursor cursor, int position) {
+ ContactListItemView view = (ContactListItemView)itemView;
+ bindSectionHeaderAndDivider(view, position);
+ bindName(view, cursor);
+ bindPhoto(view, cursor);
+ bindPhoneNumber(view, cursor);
+ }
+
+ protected void bindPhoneNumber(ContactListItemView view, Cursor cursor) {
+ CharSequence label = null;
+ if (!cursor.isNull(PHONE_TYPE_COLUMN_INDEX)) {
+ final int type = cursor.getInt(PHONE_TYPE_COLUMN_INDEX);
+ final String customLabel = cursor.getString(PHONE_LABEL_COLUMN_INDEX);
+
+ // TODO cache
+ label = Phone.getTypeLabel(getContext().getResources(), type, customLabel);
+ }
+ view.setLabel(label);
+ view.showData(cursor, PHONE_NUMBER_COLUMN_INDEX);
+ }
+
+ protected void bindSectionHeaderAndDivider(final ContactListItemView view, int position) {
+ final int section = getSectionForPosition(position);
+ if (getPositionForSection(section) == position) {
+ String title = (String)getSections()[section];
+ view.setSectionHeader(title);
+ } else {
+ view.setDividerVisible(false);
+ view.setSectionHeader(null);
+ }
+
+ // move the divider for the last item in a section
+ if (getPositionForSection(section + 1) - 1 == position) {
+ view.setDividerVisible(false);
+ } else {
+ view.setDividerVisible(true);
+ }
+ }
+
+ protected void bindName(final ContactListItemView view, Cursor cursor) {
+ view.showDisplayName(cursor, mDisplayNameColumnIndex, isNameHighlightingEnabled(),
+ mAlternativeDisplayNameColumnIndex);
+ view.showPhoneticName(cursor, PHONE_PHONETIC_NAME_COLUMN_INDEX);
+ }
+
+ protected void bindPhoto(final ContactListItemView view, Cursor cursor) {
+ long photoId = 0;
+ if (!cursor.isNull(PHONE_PHOTO_ID_COLUMN_INDEX)) {
+ photoId = cursor.getLong(PHONE_PHOTO_ID_COLUMN_INDEX);
+ }
+
+ getPhotoLoader().loadPhoto(view.getPhotoView(), photoId);
+ }
+//
+// protected void bindSearchSnippet(final ContactListItemView view, Cursor cursor) {
+// view.showSnippet(cursor, SUMMARY_SNIPPET_MIMETYPE_COLUMN_INDEX,
+// SUMMARY_SNIPPET_DATA1_COLUMN_INDEX, SUMMARY_SNIPPET_DATA4_COLUMN_INDEX);
+// }
+
+}
diff --git a/src/com/android/contacts/list/PhoneNumberPickerFragment.java b/src/com/android/contacts/list/PhoneNumberPickerFragment.java
new file mode 100644
index 0000000..93b66ac
--- /dev/null
+++ b/src/com/android/contacts/list/PhoneNumberPickerFragment.java
@@ -0,0 +1,114 @@
+/*
+ * 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.list;
+
+import com.android.contacts.ContactsSearchManager;
+import com.android.contacts.R;
+import com.android.contacts.list.ShortcutIntentBuilder.OnShortcutIntentCreatedListener;
+
+import android.content.Intent;
+import android.net.Uri;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * Fragment containing a phone number list for picking.
+ */
+public class PhoneNumberPickerFragment extends ContactEntryListFragment<ContactEntryListAdapter>
+ implements OnShortcutIntentCreatedListener {
+ private OnPhoneNumberPickerActionListener mListener;
+ private String mShortcutAction;
+
+ public PhoneNumberPickerFragment() {
+ setPhotoLoaderEnabled(true);
+ }
+
+ public void setOnPhoneNumberPickerActionListener(OnPhoneNumberPickerActionListener listener) {
+ this.mListener = listener;
+ }
+
+ /**
+ * @param shortcutAction either {@link Intent#ACTION_CALL} or
+ * {@link Intent#ACTION_SENDTO} or null.
+ */
+ public void setShortcutAction(String shortcutAction) {
+ this.mShortcutAction = shortcutAction;
+ }
+
+ @Override
+ protected void onItemClick(int position, long id) {
+ if (!isLegacyCompatibilityMode()) {
+ PhoneNumberListAdapter adapter = (PhoneNumberListAdapter)getAdapter();
+ pickPhoneNumber(adapter.getDataUri(position));
+ } else {
+ LegacyPhoneNumberListAdapter adapter = (LegacyPhoneNumberListAdapter)getAdapter();
+ pickPhoneNumber(adapter.getPhoneUri(position));
+ }
+ }
+
+ @Override
+ protected ContactEntryListAdapter createListAdapter() {
+ if (!isLegacyCompatibilityMode()) {
+ PhoneNumberListAdapter adapter = new PhoneNumberListAdapter(getActivity());
+ adapter.setDisplayPhotos(true);
+ return adapter;
+ } else {
+ LegacyPhoneNumberListAdapter adapter = new LegacyPhoneNumberListAdapter(getActivity());
+ adapter.setDisplayPhotos(true);
+ return adapter;
+ }
+ }
+
+ @Override
+ protected void configureAdapter() {
+ setSectionHeaderDisplayEnabled(!isSearchMode());
+ setAizyEnabled(!isSearchMode());
+ super.configureAdapter();
+ }
+
+ @Override
+ protected View inflateView(LayoutInflater inflater, ViewGroup container) {
+ return inflater.inflate(R.layout.contacts_list_content, null);
+ }
+
+ public void pickPhoneNumber(Uri uri) {
+ if (mShortcutAction == null) {
+ mListener.onPickPhoneNumberAction(uri);
+ } else {
+ if (isLegacyCompatibilityMode()) {
+ throw new UnsupportedOperationException();
+ }
+ ShortcutIntentBuilder builder = new ShortcutIntentBuilder(getActivity(), this);
+ builder.createPhoneNumberShortcutIntent(uri, mShortcutAction);
+ }
+ }
+
+ public void onShortcutIntentCreated(Uri uri, Intent shortcutIntent) {
+ mListener.onShortcutIntentCreated(shortcutIntent);
+ }
+
+ @Override
+ public void startSearch(String initialQuery) {
+ ContactsSearchManager.startSearchForResult(getActivity(), initialQuery,
+ ACTIVITY_REQUEST_CODE_PICKER, getContactsRequest());
+ }
+
+ @Override
+ public void onPickerResult(Intent data) {
+ mListener.onPickPhoneNumberAction(data.getData());
+ }
+}
diff --git a/src/com/android/contacts/list/PostalAddressListAdapter.java b/src/com/android/contacts/list/PostalAddressListAdapter.java
new file mode 100644
index 0000000..797089c
--- /dev/null
+++ b/src/com/android/contacts/list/PostalAddressListAdapter.java
@@ -0,0 +1,175 @@
+/*
+ * 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.list;
+
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.CursorLoader;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.ContactCounts;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * A cursor adapter for the {@link StructuredPostal#CONTENT_TYPE} content type.
+ */
+public class PostalAddressListAdapter extends ContactEntryListAdapter {
+
+ static final String[] POSTALS_PROJECTION = new String[] {
+ StructuredPostal._ID, // 0
+ StructuredPostal.TYPE, // 1
+ StructuredPostal.LABEL, // 2
+ StructuredPostal.DATA, // 3
+ StructuredPostal.DISPLAY_NAME_PRIMARY, // 4
+ StructuredPostal.DISPLAY_NAME_ALTERNATIVE, // 5
+ StructuredPostal.PHOTO_ID, // 6
+ };
+
+ protected static final int POSTAL_ID_COLUMN_INDEX = 0;
+ protected static final int POSTAL_TYPE_COLUMN_INDEX = 1;
+ protected static final int POSTAL_LABEL_COLUMN_INDEX = 2;
+ protected static final int POSTAL_ADDRESS_COLUMN_INDEX = 3;
+ protected static final int POSTAL_PRIMARY_DISPLAY_NAME_COLUMN_INDEX = 4;
+ protected static final int POSTAL_ALTERNATIVE_DISPLAY_NAME_COLUMN_INDEX = 5;
+ protected static final int POSTAL_PHOTO_ID_COLUMN_INDEX = 6;
+
+ private CharSequence mUnknownNameText;
+ private int mDisplayNameColumnIndex;
+ private int mAlternativeDisplayNameColumnIndex;
+
+ public PostalAddressListAdapter(Context context) {
+ super(context);
+
+ mUnknownNameText = context.getText(android.R.string.unknownName);
+ }
+
+ @Override
+ public void configureLoader(CursorLoader loader, long directoryId) {
+ loader.setUri(buildSectionIndexerUri(StructuredPostal.CONTENT_URI));
+ loader.setProjection(POSTALS_PROJECTION);
+
+ if (getSortOrder() == ContactsContract.Preferences.SORT_ORDER_PRIMARY) {
+ loader.setSortOrder(StructuredPostal.SORT_KEY_PRIMARY);
+ } else {
+ loader.setSortOrder(StructuredPostal.SORT_KEY_ALTERNATIVE);
+ }
+ }
+
+ protected static Uri buildSectionIndexerUri(Uri uri) {
+ return uri.buildUpon()
+ .appendQueryParameter(ContactCounts.ADDRESS_BOOK_INDEX_EXTRAS, "true").build();
+ }
+
+ @Override
+ public String getContactDisplayName(int position) {
+ return ((Cursor)getItem(position)).getString(mDisplayNameColumnIndex);
+ }
+
+ @Override
+ public void setContactNameDisplayOrder(int displayOrder) {
+ super.setContactNameDisplayOrder(displayOrder);
+ if (getContactNameDisplayOrder() == ContactsContract.Preferences.DISPLAY_ORDER_PRIMARY) {
+ mDisplayNameColumnIndex = POSTAL_PRIMARY_DISPLAY_NAME_COLUMN_INDEX;
+ mAlternativeDisplayNameColumnIndex = POSTAL_ALTERNATIVE_DISPLAY_NAME_COLUMN_INDEX;
+ } else {
+ mDisplayNameColumnIndex = POSTAL_ALTERNATIVE_DISPLAY_NAME_COLUMN_INDEX;
+ mAlternativeDisplayNameColumnIndex = POSTAL_PRIMARY_DISPLAY_NAME_COLUMN_INDEX;
+ }
+ }
+
+ /**
+ * Builds a {@link Data#CONTENT_URI} for the current cursor
+ * position.
+ */
+ public Uri getDataUri(int position) {
+ long id = ((Cursor)getItem(position)).getLong(POSTAL_ID_COLUMN_INDEX);
+ return ContentUris.withAppendedId(Data.CONTENT_URI, id);
+ }
+
+ @Override
+ protected View newView(Context context, int partition, Cursor cursor, int position,
+ ViewGroup parent) {
+ final ContactListItemView view = new ContactListItemView(context, null);
+ view.setUnknownNameText(mUnknownNameText);
+ view.setTextWithHighlightingFactory(getTextWithHighlightingFactory());
+ return view;
+ }
+
+ @Override
+ protected void bindView(View itemView, int partition, Cursor cursor, int position) {
+ ContactListItemView view = (ContactListItemView)itemView;
+ bindSectionHeaderAndDivider(view, position);
+ bindName(view, cursor);
+ bindPhoto(view, cursor);
+ bindPostalAddress(view, cursor);
+ }
+
+ protected void bindPostalAddress(ContactListItemView view, Cursor cursor) {
+ CharSequence label = null;
+ if (!cursor.isNull(POSTAL_TYPE_COLUMN_INDEX)) {
+ final int type = cursor.getInt(POSTAL_TYPE_COLUMN_INDEX);
+ final String customLabel = cursor.getString(POSTAL_LABEL_COLUMN_INDEX);
+
+ // TODO cache
+ label = StructuredPostal.getTypeLabel(getContext().getResources(), type, label);
+ }
+ view.setLabel(label);
+ view.showData(cursor, POSTAL_ADDRESS_COLUMN_INDEX);
+ }
+
+ protected void bindSectionHeaderAndDivider(final ContactListItemView view, int position) {
+ final int section = getSectionForPosition(position);
+ if (getPositionForSection(section) == position) {
+ String title = (String)getSections()[section];
+ view.setSectionHeader(title);
+ } else {
+ view.setDividerVisible(false);
+ view.setSectionHeader(null);
+ }
+
+ // move the divider for the last item in a section
+ if (getPositionForSection(section + 1) - 1 == position) {
+ view.setDividerVisible(false);
+ } else {
+ view.setDividerVisible(true);
+ }
+ }
+
+ protected void bindName(final ContactListItemView view, Cursor cursor) {
+ view.showDisplayName(cursor, mDisplayNameColumnIndex, isNameHighlightingEnabled(),
+ mAlternativeDisplayNameColumnIndex);
+// view.showPhoneticName(cursor, PHONE_PHONETIC_NAME_COLUMN_INDEX);
+ }
+
+ protected void bindPhoto(final ContactListItemView view, Cursor cursor) {
+ long photoId = 0;
+ if (!cursor.isNull(POSTAL_PHOTO_ID_COLUMN_INDEX)) {
+ photoId = cursor.getLong(POSTAL_PHOTO_ID_COLUMN_INDEX);
+ }
+
+ getPhotoLoader().loadPhoto(view.getPhotoView(), photoId);
+ }
+//
+// protected void bindSearchSnippet(final ContactListItemView view, Cursor cursor) {
+// view.showSnippet(cursor, SUMMARY_SNIPPET_MIMETYPE_COLUMN_INDEX,
+// SUMMARY_SNIPPET_DATA1_COLUMN_INDEX, SUMMARY_SNIPPET_DATA4_COLUMN_INDEX);
+// }
+
+}
diff --git a/src/com/android/contacts/list/PostalAddressPickerFragment.java b/src/com/android/contacts/list/PostalAddressPickerFragment.java
new file mode 100644
index 0000000..1f07ce2
--- /dev/null
+++ b/src/com/android/contacts/list/PostalAddressPickerFragment.java
@@ -0,0 +1,85 @@
+/*
+ * 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.list;
+
+import com.android.contacts.R;
+
+import android.net.Uri;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * Fragment containing a postal address list for picking.
+ */
+public class PostalAddressPickerFragment
+ extends ContactEntryListFragment<ContactEntryListAdapter> {
+ private OnPostalAddressPickerActionListener mListener;
+
+ public PostalAddressPickerFragment() {
+ setPhotoLoaderEnabled(true);
+ setSectionHeaderDisplayEnabled(true);
+ }
+
+ public void setOnPostalAddressPickerActionListener(
+ OnPostalAddressPickerActionListener listener) {
+ this.mListener = listener;
+ }
+
+ @Override
+ protected void onItemClick(int position, long id) {
+ if (!isLegacyCompatibilityMode()) {
+ PostalAddressListAdapter adapter = (PostalAddressListAdapter)getAdapter();
+// if (adapter.isSearchAllContactsItemPosition(position)) {
+// searchAllContacts();
+// } else {
+ pickPostalAddress(adapter.getDataUri(position));
+// }
+ } else {
+ LegacyPostalAddressListAdapter adapter = (LegacyPostalAddressListAdapter)getAdapter();
+ pickPostalAddress(adapter.getContactMethodUri(position));
+ }
+ }
+
+ @Override
+ protected ContactEntryListAdapter createListAdapter() {
+ if (!isLegacyCompatibilityMode()) {
+ PostalAddressListAdapter adapter = new PostalAddressListAdapter(getActivity());
+ adapter.setSectionHeaderDisplayEnabled(true);
+ adapter.setDisplayPhotos(true);
+ return adapter;
+ } else {
+ LegacyPostalAddressListAdapter adapter =
+ new LegacyPostalAddressListAdapter(getActivity());
+ adapter.setSectionHeaderDisplayEnabled(false);
+ adapter.setDisplayPhotos(false);
+ return adapter;
+ }
+ }
+
+ @Override
+ protected View inflateView(LayoutInflater inflater, ViewGroup container) {
+ if (isSearchMode()) {
+ return inflater.inflate(R.layout.contacts_search_content, null);
+ } else {
+ return inflater.inflate(R.layout.contacts_list_content, null);
+ }
+ }
+
+ public void pickPostalAddress(Uri uri) {
+ mListener.onPickPostalAddressAction(uri);
+ }
+}
diff --git a/src/com/android/contacts/list/ProviderStatusLoader.java b/src/com/android/contacts/list/ProviderStatusLoader.java
new file mode 100644
index 0000000..2eb9672
--- /dev/null
+++ b/src/com/android/contacts/list/ProviderStatusLoader.java
@@ -0,0 +1,211 @@
+/*
+ * 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.list;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.provider.ContactsContract.ProviderStatus;
+
+/**
+ * Checks provider status and configures a list adapter accordingly.
+ */
+public class ProviderStatusLoader {
+
+ private final Context mContext;
+
+ public ProviderStatusLoader(Context context) {
+ this.mContext = context;
+ }
+
+ public int getProviderStatus() {
+ // This query can be performed on the UI thread because
+ // the API explicitly allows such use.
+ Cursor cursor = mContext.getContentResolver().query(
+ ProviderStatus.CONTENT_URI,
+ new String[] { ProviderStatus.STATUS, ProviderStatus.DATA1 }, null, null, null);
+ if (cursor != null) {
+ try {
+ if (cursor.moveToFirst()) {
+ return cursor.getInt(0);
+ }
+ } finally {
+ cursor.close();
+ }
+ }
+
+ return ProviderStatus.STATUS_NORMAL;
+ }
+
+
+// View importFailureView = findViewById(R.id.import_failure);
+// if (importFailureView == null) {
+// return true;
+// }
+//
+// TextView messageView = (TextView) findViewById(R.id.emptyText);
+//
+// // This query can be performed on the UI thread because
+// // the API explicitly allows such use.
+// Cursor cursor = getContentResolver().query(ProviderStatus.CONTENT_URI,
+// new String[] { ProviderStatus.STATUS, ProviderStatus.DATA1 }, null, null, null);
+// if (cursor != null) {
+// try {
+// if (cursor.moveToFirst()) {
+// int status = cursor.getInt(0);
+// if (status != mProviderStatus) {
+// mProviderStatus = status;
+// switch (status) {
+// case ProviderStatus.STATUS_NORMAL:
+// mAdapter.notifyDataSetInvalidated();
+// if (loadData) {
+// startQuery();
+// }
+// break;
+//
+// case ProviderStatus.STATUS_CHANGING_LOCALE:
+// messageView.setText(R.string.locale_change_in_progress);
+// mAdapter.changeCursor(null);
+// mAdapter.notifyDataSetInvalidated();
+// break;
+//
+// case ProviderStatus.STATUS_UPGRADING:
+// messageView.setText(R.string.upgrade_in_progress);
+// mAdapter.changeCursor(null);
+// mAdapter.notifyDataSetInvalidated();
+// break;
+//
+// case ProviderStatus.STATUS_UPGRADE_OUT_OF_MEMORY:
+// long size = cursor.getLong(1);
+// String message = getResources().getString(
+// R.string.upgrade_out_of_memory, new Object[] {size});
+// messageView.setText(message);
+// configureImportFailureView(importFailureView);
+// mAdapter.changeCursor(null);
+// mAdapter.notifyDataSetInvalidated();
+// break;
+// }
+// }
+// }
+// } finally {
+// cursor.close();
+// }
+// }
+//
+// importFailureView.setVisibility(
+// mProviderStatus == ProviderStatus.STATUS_UPGRADE_OUT_OF_MEMORY
+// ? View.VISIBLE
+// : View.GONE);
+// return mProviderStatus == ProviderStatus.STATUS_NORMAL;
+//}
+
+//
+// /**
+// * Obtains the contacts provider status and configures the UI accordingly.
+// *
+// * @param loadData true if the method needs to start a query when the
+// * provider is in the normal state
+// * @return true if the provider status is normal
+// */
+// private boolean checkProviderState(boolean loadData) {
+// View importFailureView = findViewById(R.id.import_failure);
+// if (importFailureView == null) {
+// return true;
+// }
+//
+// TextView messageView = (TextView) findViewById(R.id.emptyText);
+//
+// // This query can be performed on the UI thread because
+// // the API explicitly allows such use.
+// Cursor cursor = getContentResolver().query(ProviderStatus.CONTENT_URI,
+// new String[] { ProviderStatus.STATUS, ProviderStatus.DATA1 }, null, null, null);
+// if (cursor != null) {
+// try {
+// if (cursor.moveToFirst()) {
+// int status = cursor.getInt(0);
+// if (status != mProviderStatus) {
+// mProviderStatus = status;
+// switch (status) {
+// case ProviderStatus.STATUS_NORMAL:
+// mAdapter.notifyDataSetInvalidated();
+// if (loadData) {
+// startQuery();
+// }
+// break;
+//
+// case ProviderStatus.STATUS_CHANGING_LOCALE:
+// messageView.setText(R.string.locale_change_in_progress);
+// mAdapter.changeCursor(null);
+// mAdapter.notifyDataSetInvalidated();
+// break;
+//
+// case ProviderStatus.STATUS_UPGRADING:
+// messageView.setText(R.string.upgrade_in_progress);
+// mAdapter.changeCursor(null);
+// mAdapter.notifyDataSetInvalidated();
+// break;
+//
+// case ProviderStatus.STATUS_UPGRADE_OUT_OF_MEMORY:
+// long size = cursor.getLong(1);
+// String message = getResources().getString(
+// R.string.upgrade_out_of_memory, new Object[] {size});
+// messageView.setText(message);
+// configureImportFailureView(importFailureView);
+// mAdapter.changeCursor(null);
+// mAdapter.notifyDataSetInvalidated();
+// break;
+// }
+// }
+// }
+// } finally {
+// cursor.close();
+// }
+// }
+//
+// importFailureView.setVisibility(
+// mProviderStatus == ProviderStatus.STATUS_UPGRADE_OUT_OF_MEMORY
+// ? View.VISIBLE
+// : View.GONE);
+// return mProviderStatus == ProviderStatus.STATUS_NORMAL;
+// }
+//
+// private void configureImportFailureView(View importFailureView) {
+//
+// OnClickListener listener = new OnClickListener(){
+//
+// public void onClick(View v) {
+// switch(v.getId()) {
+// case R.id.import_failure_uninstall_apps: {
+// startActivity(new Intent(Settings.ACTION_MANAGE_APPLICATIONS_SETTINGS));
+// break;
+// }
+// case R.id.import_failure_retry_upgrade: {
+// // Send a provider status update, which will trigger a retry
+// ContentValues values = new ContentValues();
+// values.put(ProviderStatus.STATUS, ProviderStatus.STATUS_UPGRADING);
+// getContentResolver().update(ProviderStatus.CONTENT_URI, values, null, null);
+// break;
+// }
+// }
+// }};
+//
+// Button uninstallApps = (Button) findViewById(R.id.import_failure_uninstall_apps);
+// uninstallApps.setOnClickListener(listener);
+//
+// Button retryUpgrade = (Button) findViewById(R.id.import_failure_retry_upgrade);
+// retryUpgrade.setOnClickListener(listener);
+// }
+
+}
diff --git a/src/com/android/contacts/list/ShortcutIntentBuilder.java b/src/com/android/contacts/list/ShortcutIntentBuilder.java
new file mode 100644
index 0000000..7a4f9de
--- /dev/null
+++ b/src/com/android/contacts/list/ShortcutIntentBuilder.java
@@ -0,0 +1,397 @@
+/*
+ * 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.list;
+
+import com.android.contacts.R;
+import com.android.contacts.util.Constants;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.Typeface;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.Photo;
+
+import java.util.Random;
+
+/**
+ * Constructs shortcut intents.
+ */
+public class ShortcutIntentBuilder {
+
+ private static final String[] CONTACT_COLUMNS = {
+ Contacts.DISPLAY_NAME,
+ Contacts.PHOTO_ID,
+ };
+
+ private static final int CONTACT_DISPLAY_NAME_COLUMN_INDEX = 0;
+ private static final int CONTACT_PHOTO_ID_COLUMN_INDEX = 1;
+
+ private static final String[] PHONE_COLUMNS = {
+ Phone.DISPLAY_NAME,
+ Phone.PHOTO_ID,
+ Phone.NUMBER,
+ Phone.TYPE,
+ };
+
+ private static final int PHONE_DISPLAY_NAME_COLUMN_INDEX = 0;
+ private static final int PHONE_PHOTO_ID_COLUMN_INDEX = 1;
+ private static final int PHONE_NUMBER_COLUMN_INDEX = 2;
+ private static final int PHONE_TYPE_COLUMN_INDEX = 3;
+
+ private static final String[] PHOTO_COLUMNS = {
+ Photo.PHOTO,
+ };
+
+ private static final int PHOTO_PHOTO_COLUMN_INDEX = 0;
+
+ private static final String PHOTO_SELECTION = Photo._ID + "=?";
+
+ private final OnShortcutIntentCreatedListener mListener;
+ private final Context mContext;
+ private final int mIconSize;
+
+ /**
+ * Listener interface.
+ */
+ public interface OnShortcutIntentCreatedListener {
+
+ /**
+ * Callback for shortcut intent creation.
+ *
+ * @param uri the original URI for which the shortcut intent has been
+ * created.
+ * @param shortcutIntent resulting shortcut intent.
+ */
+ void onShortcutIntentCreated(Uri uri, Intent shortcutIntent);
+ }
+
+ public ShortcutIntentBuilder(Context context, OnShortcutIntentCreatedListener listener) {
+ mContext = context;
+ mListener = listener;
+
+ mIconSize = context.getResources().getDimensionPixelSize(android.R.dimen.app_icon_size);
+ }
+
+ public void createContactShortcutIntent(Uri contactUri) {
+ new ContactLoadingAsyncTask(contactUri).execute();
+ }
+
+ public void createPhoneNumberShortcutIntent(Uri dataUri, String shortcutAction) {
+ new PhoneNumberLoadingAsyncTask(dataUri, shortcutAction).execute();
+ }
+
+ /**
+ * An asynchronous task that loads name, photo and other data from the database.
+ */
+ private abstract class LoadingAsyncTask extends AsyncTask<Void, Void, Void> {
+ protected Uri mUri;
+ protected String mDisplayName;
+ protected byte[] mBitmapData;
+ protected long mPhotoId;
+
+ public LoadingAsyncTask(Uri uri) {
+ mUri = uri;
+ }
+
+ @Override
+ protected Void doInBackground(Void... params) {
+ loadData();
+ loadPhoto();
+ return null;
+ }
+
+ protected abstract void loadData();
+
+ private void loadPhoto() {
+ if (mPhotoId == 0) {
+ return;
+ }
+
+ ContentResolver resolver = mContext.getContentResolver();
+ Cursor cursor = resolver.query(Data.CONTENT_URI, PHOTO_COLUMNS, PHOTO_SELECTION,
+ new String[] { String.valueOf(mPhotoId) }, null);
+ if (cursor != null) {
+ try {
+ if (cursor.moveToFirst()) {
+ mBitmapData = cursor.getBlob(PHOTO_PHOTO_COLUMN_INDEX);
+ }
+ } finally {
+ cursor.close();
+ }
+ }
+ }
+ }
+
+ private final class ContactLoadingAsyncTask extends LoadingAsyncTask {
+ public ContactLoadingAsyncTask(Uri uri) {
+ super(uri);
+ }
+
+ @Override
+ protected void loadData() {
+ ContentResolver resolver = mContext.getContentResolver();
+ Cursor cursor = resolver.query(mUri, CONTACT_COLUMNS, null, null, null);
+ if (cursor != null) {
+ try {
+ if (cursor.moveToFirst()) {
+ mDisplayName = cursor.getString(CONTACT_DISPLAY_NAME_COLUMN_INDEX);
+ mPhotoId = cursor.getLong(CONTACT_PHOTO_ID_COLUMN_INDEX);
+ }
+ } finally {
+ cursor.close();
+ }
+ }
+ }
+ @Override
+ protected void onPostExecute(Void result) {
+ createContactShortcutIntent(mUri, mDisplayName, mBitmapData);
+ }
+ }
+
+ private final class PhoneNumberLoadingAsyncTask extends LoadingAsyncTask {
+ private final String mShortcutAction;
+ private String mPhoneNumber;
+ private int mPhoneType;
+
+ public PhoneNumberLoadingAsyncTask(Uri uri, String shortcutAction) {
+ super(uri);
+ mShortcutAction = shortcutAction;
+ }
+
+ @Override
+ protected void loadData() {
+ ContentResolver resolver = mContext.getContentResolver();
+ Cursor cursor = resolver.query(mUri, PHONE_COLUMNS, null, null, null);
+ if (cursor != null) {
+ try {
+ if (cursor.moveToFirst()) {
+ mDisplayName = cursor.getString(PHONE_DISPLAY_NAME_COLUMN_INDEX);
+ mPhotoId = cursor.getLong(PHONE_PHOTO_ID_COLUMN_INDEX);
+ mPhoneNumber = cursor.getString(PHONE_NUMBER_COLUMN_INDEX);
+ mPhoneType = cursor.getInt(PHONE_TYPE_COLUMN_INDEX);
+ }
+ } finally {
+ cursor.close();
+ }
+ }
+ }
+
+ @Override
+ protected void onPostExecute(Void result) {
+ createPhoneNumberShortcutIntent(mUri, mDisplayName, mBitmapData, mPhoneNumber,
+ mPhoneType, mShortcutAction);
+ }
+ }
+
+ private void createContactShortcutIntent(Uri contactUri, String displayName,
+ byte[] bitmapData) {
+ Bitmap bitmap;
+ if (bitmapData != null) {
+ bitmap = BitmapFactory.decodeByteArray(bitmapData, 0, bitmapData.length, null);
+ } else {
+ final int[] fallbacks = {
+ R.drawable.ic_contact_picture,
+ R.drawable.ic_contact_picture_2,
+ R.drawable.ic_contact_picture_3
+ };
+ bitmap = BitmapFactory.decodeResource(mContext.getResources(),
+ fallbacks[new Random().nextInt(fallbacks.length)]);
+ }
+
+ Intent shortcutIntent;
+ // This is a simple shortcut to view a contact.
+ shortcutIntent = new Intent(ContactsContract.QuickContact.ACTION_QUICK_CONTACT);
+ shortcutIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
+ Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+
+ shortcutIntent.setData(contactUri);
+ shortcutIntent.putExtra(ContactsContract.QuickContact.EXTRA_MODE,
+ ContactsContract.QuickContact.MODE_LARGE);
+ shortcutIntent.putExtra(ContactsContract.QuickContact.EXTRA_EXCLUDE_MIMES,
+ (String[]) null);
+ shortcutIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+
+ final Bitmap icon = scaleToAppIconSize(framePhoto(bitmap));
+
+ Intent intent = new Intent();
+ intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, icon);
+ intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
+ intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, displayName);
+
+ mListener.onShortcutIntentCreated(contactUri, intent);
+ }
+
+ private void createPhoneNumberShortcutIntent(Uri uri, String displayName, byte[] bitmapData,
+ String phoneNumber, int phoneType, String shortcutAction) {
+ Bitmap bitmap = null;
+ if (bitmapData != null) {
+ bitmap = BitmapFactory.decodeByteArray(bitmapData, 0, bitmapData.length, null);
+ }
+
+ Uri phoneUri;
+ if (Intent.ACTION_CALL.equals(shortcutAction)) {
+ // Make the URI a direct tel: URI so that it will always continue to work
+ phoneUri = Uri.fromParts(Constants.SCHEME_TEL, phoneNumber, null);
+ bitmap = generatePhoneNumberIcon(bitmap, phoneType, R.drawable.badge_action_call);
+ } else {
+ phoneUri = Uri.fromParts(Constants.SCHEME_SMSTO, phoneNumber, null);
+ bitmap = generatePhoneNumberIcon(bitmap, phoneType, R.drawable.badge_action_sms);
+ }
+
+ Intent shortcutIntent = new Intent(shortcutAction, phoneUri);
+ shortcutIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+
+ Intent intent = new Intent();
+ intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, bitmap);
+ intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
+ intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, displayName);
+
+ mListener.onShortcutIntentCreated(uri, intent);
+ }
+
+ private Bitmap framePhoto(Bitmap photo) {
+ final Resources r = mContext.getResources();
+ final Drawable frame = r.getDrawable(com.android.internal.R.drawable.quickcontact_badge);
+
+ final int width = r.getDimensionPixelSize(R.dimen.contact_shortcut_frame_width);
+ final int height = r.getDimensionPixelSize(R.dimen.contact_shortcut_frame_height);
+
+ frame.setBounds(0, 0, width, height);
+
+ final Rect padding = new Rect();
+ frame.getPadding(padding);
+
+ final Rect source = new Rect(0, 0, photo.getWidth(), photo.getHeight());
+ final Rect destination = new Rect(padding.left, padding.top,
+ width - padding.right, height - padding.bottom);
+
+ final int d = Math.max(width, height);
+ final Bitmap b = Bitmap.createBitmap(d, d, Bitmap.Config.ARGB_8888);
+ final Canvas c = new Canvas(b);
+
+ c.translate((d - width) / 2.0f, (d - height) / 2.0f);
+ frame.draw(c);
+ c.drawBitmap(photo, source, destination, new Paint(Paint.FILTER_BITMAP_FLAG));
+
+ return b;
+ }
+
+ private Bitmap scaleToAppIconSize(Bitmap photo) {
+
+ // Setup the drawing classes
+ Bitmap icon = Bitmap.createBitmap(mIconSize, mIconSize, Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(icon);
+
+ // Copy in the photo
+ Paint photoPaint = new Paint();
+ photoPaint.setDither(true);
+ photoPaint.setFilterBitmap(true);
+ Rect src = new Rect(0,0, photo.getWidth(),photo.getHeight());
+ Rect dst = new Rect(0,0, mIconSize, mIconSize);
+ canvas.drawBitmap(photo, src, dst, photoPaint);
+
+ return icon;
+ }
+
+ /**
+ * Generates a phone number shortcut icon. Adds an overlay describing the type of the phone
+ * number, and if there is a photo also adds the call action icon.
+ */
+ private Bitmap generatePhoneNumberIcon(Bitmap photo, int phoneType, int actionResId) {
+ final Resources r = mContext.getResources();
+ boolean drawPhoneOverlay = true;
+ final float scaleDensity = r.getDisplayMetrics().scaledDensity;
+
+ Bitmap phoneIcon = ((BitmapDrawable) r.getDrawable(actionResId)).getBitmap();
+
+ // If there isn't a photo use the generic phone action icon instead
+ if (photo == null) {
+ photo = phoneIcon;
+ drawPhoneOverlay = false;
+ }
+
+ // Setup the drawing classes
+ Bitmap icon = Bitmap.createBitmap(mIconSize, mIconSize, Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(icon);
+
+ // Copy in the photo
+ Paint photoPaint = new Paint();
+ photoPaint.setDither(true);
+ photoPaint.setFilterBitmap(true);
+ Rect src = new Rect(0, 0, photo.getWidth(), photo.getHeight());
+ Rect dst = new Rect(0, 0, mIconSize, mIconSize);
+ canvas.drawBitmap(photo, src, dst, photoPaint);
+
+ // Create an overlay for the phone number type
+ String overlay = null;
+ switch (phoneType) {
+ case Phone.TYPE_HOME:
+ overlay = mContext.getString(R.string.type_short_home);
+ break;
+
+ case Phone.TYPE_MOBILE:
+ overlay = mContext.getString(R.string.type_short_mobile);
+ break;
+
+ case Phone.TYPE_WORK:
+ overlay = mContext.getString(R.string.type_short_work);
+ break;
+
+ case Phone.TYPE_PAGER:
+ overlay = mContext.getString(R.string.type_short_pager);
+ break;
+
+ case Phone.TYPE_OTHER:
+ overlay = mContext.getString(R.string.type_short_other);
+ break;
+ }
+
+ if (overlay != null) {
+ Paint textPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG);
+ textPaint.setTextSize(20.0f * scaleDensity);
+ textPaint.setTypeface(Typeface.DEFAULT_BOLD);
+ textPaint.setColor(r.getColor(R.color.textColorIconOverlay));
+ textPaint.setShadowLayer(3f, 1, 1, r.getColor(R.color.textColorIconOverlayShadow));
+ canvas.drawText(overlay, 2 * scaleDensity, 16 * scaleDensity, textPaint);
+ }
+
+ // Draw the phone action icon as an overlay
+ if (drawPhoneOverlay) {
+ src.set(0, 0, phoneIcon.getWidth(), phoneIcon.getHeight());
+ int iconWidth = icon.getWidth();
+ dst.set(iconWidth - ((int) (20 * scaleDensity)), -1,
+ iconWidth, ((int) (19 * scaleDensity)));
+ canvas.drawBitmap(phoneIcon, src, dst, photoPaint);
+ }
+
+ return icon;
+ }
+}
diff --git a/src/com/android/contacts/list/StrequentContactListAdapter.java b/src/com/android/contacts/list/StrequentContactListAdapter.java
new file mode 100644
index 0000000..9f1910e
--- /dev/null
+++ b/src/com/android/contacts/list/StrequentContactListAdapter.java
@@ -0,0 +1,216 @@
+/*
+ * 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.list;
+
+import com.android.contacts.R;
+
+import android.content.Context;
+import android.content.CursorLoader;
+import android.database.Cursor;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.Contacts;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.View.OnClickListener;
+import android.widget.ListView;
+import android.widget.TextView;
+
+/**
+ * A cursor adapter for the {@link ContactsContract.Contacts#CONTENT_TYPE} content type loading
+ * a combination of starred and frequently contacted.
+ */
+public class StrequentContactListAdapter extends ContactListAdapter {
+
+ private int mFrequentSeparatorPos = ListView.INVALID_POSITION;
+ private TextView mSeparatorView;
+ private OnClickListener mCallButtonListener;
+ private int mCallButtonId;
+ private boolean mStarredContactsIncluded;
+ private boolean mFrequentlyContactedContactsIncluded;
+
+ public StrequentContactListAdapter(Context context, int callButtonId) {
+ super(context);
+ mCallButtonId = callButtonId;
+ }
+
+ public void setCallButtonListener(OnClickListener callButtonListener) {
+ mCallButtonListener = callButtonListener;
+ }
+
+ public void setStarredContactsIncluded(boolean flag) {
+ mStarredContactsIncluded = flag;
+ }
+
+ public void setFrequentlyContactedContactsIncluded(boolean flag) {
+ mFrequentlyContactedContactsIncluded = flag;
+ }
+
+ @Override
+ public void configureLoader(CursorLoader loader, long directoryId) {
+ String sortOrder = getSortOrder() == ContactsContract.Preferences.SORT_ORDER_PRIMARY
+ ? Contacts.SORT_KEY_PRIMARY
+ : Contacts.SORT_KEY_ALTERNATIVE;
+ if (mStarredContactsIncluded && mFrequentlyContactedContactsIncluded) {
+ loader.setUri(Contacts.CONTENT_STREQUENT_URI);
+ } else if (mStarredContactsIncluded) {
+ loader.setUri(Contacts.CONTENT_URI);
+ loader.setSelection(Contacts.STARRED + "!=0");
+ } else if (mFrequentlyContactedContactsIncluded) {
+ loader.setUri(Contacts.CONTENT_URI);
+ loader.setSelection(Contacts.TIMES_CONTACTED + " > 0");
+ sortOrder = Contacts.TIMES_CONTACTED + " DESC";
+ } else {
+ throw new UnsupportedOperationException("Neither StarredContactsIncluded nor "
+ + "FrequentlyContactedContactsIncluded is set");
+ }
+
+ loader.setProjection(PROJECTION);
+ loader.setSortOrder(sortOrder);
+ }
+
+ @Override
+ protected void invalidate() {
+ super.invalidate();
+
+ // Sometimes the adapter is invalidated without calling changeCursor,
+ // need to reset the separator position then.
+ mFrequentSeparatorPos = ListView.INVALID_POSITION;
+ }
+
+ @Override
+ public void changeCursor(int partition, Cursor cursor) {
+ super.changeCursor(partition, cursor);
+
+ // Get the split between starred and frequent items, if the mode is strequent
+ mFrequentSeparatorPos = ListView.INVALID_POSITION;
+
+ if (mStarredContactsIncluded && mFrequentlyContactedContactsIncluded) {
+ int count = 0;
+ if (cursor != null && (count = cursor.getCount()) > 0) {
+ cursor.moveToPosition(-1);
+ for (int i = 0; cursor.moveToNext(); i++) {
+ int starred = cursor.getInt(CONTACT_STARRED_COLUMN_INDEX);
+ if (starred == 0) {
+ if (i > 0) {
+ // Only add the separator when there are starred items present
+ mFrequentSeparatorPos = i;
+ }
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public int getCount() {
+ if (mFrequentSeparatorPos == ListView.INVALID_POSITION) {
+ return super.getCount();
+ } else {
+ // Add a row for the separator
+ return super.getCount() + 1;
+ }
+ }
+
+ @Override
+ public boolean areAllItemsEnabled() {
+ return mFrequentSeparatorPos == ListView.INVALID_POSITION;
+ }
+
+ @Override
+ public boolean isEnabled(int position) {
+ return position != mFrequentSeparatorPos;
+ }
+
+ @Override
+ public Object getItem(int position) {
+ if (mFrequentSeparatorPos == ListView.INVALID_POSITION
+ || position < mFrequentSeparatorPos) {
+ return super.getItem(position);
+ } else {
+ return super.getItem(position - 1);
+ }
+ }
+
+ @Override
+ public long getItemId(int position) {
+ if (mFrequentSeparatorPos == ListView.INVALID_POSITION
+ || position < mFrequentSeparatorPos) {
+ return super.getItemId(position);
+ } else {
+ return super.getItemId(position - 1);
+ }
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ if (mFrequentSeparatorPos == ListView.INVALID_POSITION
+ || position < mFrequentSeparatorPos) {
+ return super.getItemViewType(position);
+ } else if (position == mFrequentSeparatorPos) {
+ return IGNORE_ITEM_VIEW_TYPE;
+ } else {
+ return super.getItemViewType(position - 1);
+ }
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ if (mFrequentSeparatorPos == ListView.INVALID_POSITION
+ || position < mFrequentSeparatorPos) {
+ return super.getView(position, convertView, parent);
+ } else if (position == mFrequentSeparatorPos) {
+ if (mSeparatorView == null) {
+ mSeparatorView = (TextView)LayoutInflater.from(getContext()).
+ inflate(R.layout.list_separator, parent, false);
+ mSeparatorView.setText(R.string.favoritesFrquentSeparator);
+ }
+ return mSeparatorView;
+ } else {
+ return super.getView(position - 1, convertView, parent);
+ }
+ }
+
+ @Override
+ protected View newView(Context context, int partition, Cursor cursor, int position,
+ ViewGroup parent) {
+ ContactListItemView view = (ContactListItemView)super.newView(context, partition, cursor,
+ position, parent);
+ view.setOnCallButtonClickListener(mCallButtonListener);
+ return view;
+ }
+
+ @Override
+ protected void bindView(View itemView, int partition, Cursor cursor, int position) {
+ final ContactListItemView view = (ContactListItemView)itemView;
+
+ if (isSelectionVisible()) {
+ view.setItemSelected(isSelectedContact(partition, cursor));
+ }
+
+ bindName(view, cursor);
+ bindQuickContact(view, partition, cursor);
+ bindPresence(view, cursor);
+
+ // Make the call button visible if requested.
+ if (getHasPhoneNumber(position)) {
+ view.showCallButton(mCallButtonId, position);
+ } else {
+ view.hideCallButton();
+ }
+ }
+}
diff --git a/src/com/android/contacts/list/StrequentContactListFragment.java b/src/com/android/contacts/list/StrequentContactListFragment.java
new file mode 100644
index 0000000..722e3b7
--- /dev/null
+++ b/src/com/android/contacts/list/StrequentContactListFragment.java
@@ -0,0 +1,108 @@
+/*
+ * 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.list;
+
+import com.android.contacts.R;
+
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.View.OnClickListener;
+
+/**
+ * Fragment containing a list of starred contacts followed by a list of frequently contacted.
+ */
+public class StrequentContactListFragment extends ContactBrowseListFragment
+ implements OnClickListener {
+
+ private static final int CALL_BUTTON_ID = android.R.id.button1;
+
+ private boolean mStarredContactsIncluded = true;
+ private boolean mFrequentlyContactedContactsIncluded = true;
+
+ public StrequentContactListFragment() {
+ setSectionHeaderDisplayEnabled(false);
+ setPhotoLoaderEnabled(true);
+ }
+
+ @Override
+ protected boolean isNameHighlighingEnabled() {
+ // Since the list is not ordered alphabetically, we don't need to highlight the part
+ // that is used for sorting.
+ return false;
+ }
+
+ public void setStarredContactsIncluded(boolean flag) {
+ mStarredContactsIncluded = flag;
+ configureAdapter();
+ }
+
+ public void setFrequentlyContactedContactsIncluded(boolean flag) {
+ mFrequentlyContactedContactsIncluded = flag;
+ configureAdapter();
+ }
+
+ @Override
+ protected void onItemClick(int position, long id) {
+ ContactListAdapter adapter = getAdapter();
+ viewContact(adapter.getContactUri(position), false);
+ }
+
+ @Override
+ protected ContactListAdapter createListAdapter() {
+ StrequentContactListAdapter adapter =
+ new StrequentContactListAdapter(getActivity(), CALL_BUTTON_ID);
+ adapter.setSectionHeaderDisplayEnabled(false);
+ adapter.setDisplayPhotos(true);
+ adapter.setQuickContactEnabled(true);
+ adapter.setCallButtonListener(this);
+
+ return adapter;
+ }
+
+ @Override
+ protected void configureAdapter() {
+ super.configureAdapter();
+
+ StrequentContactListAdapter adapter = (StrequentContactListAdapter)getAdapter();
+ if (adapter != null) {
+ adapter.setStarredContactsIncluded(mStarredContactsIncluded);
+ adapter.setFrequentlyContactedContactsIncluded(mFrequentlyContactedContactsIncluded);
+ }
+ }
+
+ @Override
+ protected View inflateView(LayoutInflater inflater, ViewGroup container) {
+ return inflater.inflate(R.layout.contacts_list_content, null);
+ }
+
+ @Override
+ protected void prepareEmptyView() {
+ setEmptyText(R.string.noFavoritesHelpText);
+ }
+
+ public void onClick(View v) {
+ int id = v.getId();
+ switch (id) {
+ case CALL_BUTTON_ID: {
+ final int position = (Integer)v.getTag();
+ ContactListAdapter adapter = getAdapter();
+ callContact(adapter.getContactUri(position));
+ break;
+ }
+ }
+ }
+}
diff --git a/src/com/android/contacts/model/ContactsSource.java b/src/com/android/contacts/model/ContactsSource.java
index d008482..b417224 100644
--- a/src/com/android/contacts/model/ContactsSource.java
+++ b/src/com/android/contacts/model/ContactsSource.java
@@ -25,11 +25,12 @@
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.graphics.drawable.Drawable;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.RawContacts;
-import android.provider.ContactsContract.CommonDataKinds.Phone;
-import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+import android.view.View;
import android.widget.EditText;
import java.util.ArrayList;
@@ -221,10 +222,17 @@
public ContentValues defaultValues;
+ public Class<? extends View> editorClass;
+
public DataKind() {
}
public DataKind(String mimeType, int titleRes, int iconRes, int weight, boolean editable) {
+ this(mimeType, titleRes, iconRes, weight, editable, null);
+ }
+
+ public DataKind(String mimeType, int titleRes, int iconRes, int weight, boolean editable,
+ Class<? extends View> editorClass) {
this.mimeType = mimeType;
this.titleRes = titleRes;
this.iconRes = iconRes;
@@ -232,6 +240,7 @@
this.editable = editable;
this.isList = true;
this.typeOverallMax = -1;
+ this.editorClass = editorClass;
}
}
@@ -297,6 +306,8 @@
public int inputType;
public int minLines;
public boolean optional;
+ public boolean shortForm;
+ public boolean longForm;
public EditField(String column, int titleRes) {
this.column = column;
@@ -312,6 +323,21 @@
this.optional = optional;
return this;
}
+
+ public EditField setShortForm(boolean shortForm) {
+ this.shortForm = shortForm;
+ return this;
+ }
+
+ public EditField setLongForm(boolean longForm) {
+ this.longForm = longForm;
+ return this;
+ }
+
+ public EditField setMinLines(int minLines) {
+ this.minLines = minLines;
+ return this;
+ }
}
/**
diff --git a/src/com/android/contacts/model/Editor.java b/src/com/android/contacts/model/Editor.java
index 473f0d3..c73839d 100644
--- a/src/com/android/contacts/model/Editor.java
+++ b/src/com/android/contacts/model/Editor.java
@@ -44,6 +44,10 @@
public static final int REQUEST_PICK_PHOTO = 1;
public static final int FIELD_CHANGED = 2;
+
+ // The editor has switched between different representations of the same
+ // data, e.g. from full name to structured name
+ public static final int EDITOR_FORM_CHANGED = 3;
}
/**
@@ -54,6 +58,8 @@
public void setValues(DataKind kind, ValuesDelta values, EntityDelta state, boolean readOnly,
ViewIdGenerator vig);
+ public void setDeletable(boolean deletable);
+
/**
* Add a specific {@link EditorListener} to this {@link Editor}.
*/
diff --git a/src/com/android/contacts/model/EntityDelta.java b/src/com/android/contacts/model/EntityDelta.java
index cdf2e41..e353d70 100644
--- a/src/com/android/contacts/model/EntityDelta.java
+++ b/src/com/android/contacts/model/EntityDelta.java
@@ -578,6 +578,21 @@
}
}
+ public boolean isChanged(String key) {
+ if (mAfter == null || !mAfter.containsKey(key)) {
+ return false;
+ }
+
+ Object newValue = mAfter.get(key);
+ Object oldValue = mBefore.get(key);
+
+ if (oldValue == null) {
+ return newValue != null;
+ }
+
+ return !oldValue.equals(newValue);
+ }
+
public String getMimetype() {
return getAsString(Data.MIMETYPE);
}
@@ -612,33 +627,45 @@
return (mBefore != null && mBefore.containsKey(mIdColumn));
}
+ /**
+ * When "after" is present, then visible
+ */
public boolean isVisible() {
- // When "after" is present, then visible
return (mAfter != null);
}
+ /**
+ * When "after" is wiped, action is "delete"
+ */
public boolean isDelete() {
- // When "after" is wiped, action is "delete"
return beforeExists() && (mAfter == null);
}
+ /**
+ * When no "before" or "after", is transient
+ */
public boolean isTransient() {
- // When no "before" or "after", is transient
return (mBefore == null) && (mAfter == null);
}
+ /**
+ * When "after" has some changes, action is "update"
+ */
public boolean isUpdate() {
- // When "after" has some changes, action is "update"
return beforeExists() && (mAfter != null && mAfter.size() > 0);
}
+ /**
+ * When "after" has no changes, action is no-op
+ */
public boolean isNoop() {
- // When "after" has no changes, action is no-op
return beforeExists() && (mAfter != null && mAfter.size() == 0);
}
+ /**
+ * When no "before" id, and has "after", action is "insert"
+ */
public boolean isInsert() {
- // When no "before" id, and has "after", action is "insert"
return !beforeExists() && (mAfter != null);
}
@@ -670,6 +697,11 @@
mAfter.put(key, value);
}
+ public void putNull(String key) {
+ ensureUpdate();
+ mAfter.putNull(key);
+ }
+
/**
* Return set of all keys defined through this object.
*/
diff --git a/src/com/android/contacts/model/EntitySet.java b/src/com/android/contacts/model/EntityDeltaList.java
similarity index 75%
rename from src/com/android/contacts/model/EntitySet.java
rename to src/com/android/contacts/model/EntityDeltaList.java
index 83fe338..e68b6ef 100644
--- a/src/com/android/contacts/model/EntitySet.java
+++ b/src/com/android/contacts/model/EntityDeltaList.java
@@ -33,60 +33,71 @@
import com.android.contacts.model.EntityDelta.ValuesDelta;
import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
/**
* Container for multiple {@link EntityDelta} objects, usually when editing
* together as an entire aggregate. Provides convenience methods for parceling
- * and applying another {@link EntitySet} over it.
+ * and applying another {@link EntityDeltaList} over it.
*/
-public class EntitySet extends ArrayList<EntityDelta> implements Parcelable {
+public class EntityDeltaList extends ArrayList<EntityDelta> implements Parcelable {
private boolean mSplitRawContacts;
+ private List<Long> mJoinWithRawContactIds;
- private EntitySet() {
+ private EntityDeltaList() {
}
/**
- * Create an {@link EntitySet} that contains the given {@link EntityDelta},
+ * Create an {@link EntityDeltaList} that contains the given {@link EntityDelta},
* usually when inserting a new {@link Contacts} entry.
*/
- public static EntitySet fromSingle(EntityDelta delta) {
- final EntitySet state = new EntitySet();
+ public static EntityDeltaList fromSingle(EntityDelta delta) {
+ final EntityDeltaList state = new EntityDeltaList();
state.add(delta);
return state;
}
/**
- * Create an {@link EntitySet} based on {@link Contacts} specified by the
+ * Create an {@link EntityDeltaList} based on {@link Contacts} specified by the
* given query parameters. This closes the {@link EntityIterator} when
* finished, so it doesn't subscribe to updates.
*/
- public static EntitySet fromQuery(ContentResolver resolver, String selection,
+ public static EntityDeltaList fromQuery(ContentResolver resolver, String selection,
String[] selectionArgs, String sortOrder) {
- EntityIterator iterator = RawContacts.newEntityIterator(resolver.query(
+ final EntityIterator iterator = RawContacts.newEntityIterator(resolver.query(
RawContactsEntity.CONTENT_URI, null, selection, selectionArgs,
sortOrder));
try {
- final EntitySet state = new EntitySet();
- // Perform background query to pull contact details
- while (iterator.hasNext()) {
- // Read all contacts into local deltas to prepare for edits
- final Entity before = iterator.next();
- final EntityDelta entity = EntityDelta.fromBefore(before);
- state.add(entity);
- }
- return state;
+ return fromIterator(iterator);
} finally {
iterator.close();
}
}
/**
- * Merge the "after" values from the given {@link EntitySet}, discarding any
- * previous "after" states. This is typically used when re-parenting user
- * edits onto an updated {@link EntitySet}.
+ * Create an {@link EntityDeltaList} that contains the entities of the Iterator as before
+ * values.
*/
- public static EntitySet mergeAfter(EntitySet local, EntitySet remote) {
- if (local == null) local = new EntitySet();
+ public static EntityDeltaList fromIterator(Iterator<Entity> iterator) {
+ final EntityDeltaList state = new EntityDeltaList();
+ // Perform background query to pull contact details
+ while (iterator.hasNext()) {
+ // Read all contacts into local deltas to prepare for edits
+ final Entity before = iterator.next();
+ final EntityDelta entity = EntityDelta.fromBefore(before);
+ state.add(entity);
+ }
+ return state;
+ }
+
+ /**
+ * Merge the "after" values from the given {@link EntityDeltaList}, discarding any
+ * previous "after" states. This is typically used when re-parenting user
+ * edits onto an updated {@link EntityDeltaList}.
+ */
+ public static EntityDeltaList mergeAfter(EntityDeltaList local, EntityDeltaList remote) {
+ if (local == null) local = new EntityDeltaList();
// For each entity in the remote set, try matching over existing
for (EntityDelta remoteEntity : remote) {
@@ -133,6 +144,22 @@
backRefs[rawContactIndex++] = firstBatch;
delta.buildDiff(diff);
+ // If the user chose to join with some other existing raw contact(s) at save time,
+ // add aggregation exceptions for all those raw contacts.
+ if (mJoinWithRawContactIds != null) {
+ for (Long joinedRawContactId : mJoinWithRawContactIds) {
+ final Builder builder = beginKeepTogether();
+ builder.withValue(AggregationExceptions.RAW_CONTACT_ID1, joinedRawContactId);
+ if (rawContactId != -1) {
+ builder.withValue(AggregationExceptions.RAW_CONTACT_ID2, rawContactId);
+ } else {
+ builder.withValueBackReference(
+ AggregationExceptions.RAW_CONTACT_ID2, firstBatch);
+ }
+ diff.add(builder.build());
+ }
+ }
+
// Only create rules for inserts
if (!delta.isContactInsert()) continue;
@@ -153,7 +180,8 @@
} else {
// Additional insert case, so point at first insert
final Builder builder = beginKeepTogether();
- builder.withValueBackReference(AggregationExceptions.RAW_CONTACT_ID1, firstInsertRow);
+ builder.withValueBackReference(AggregationExceptions.RAW_CONTACT_ID1,
+ firstInsertRow);
builder.withValueBackReference(AggregationExceptions.RAW_CONTACT_ID2, firstBatch);
diff.add(builder.build());
}
@@ -296,10 +324,17 @@
return randomEntry;
}
- public void splitRawContacts() {
+ /**
+ * Sets a flag that will split ("explode") the raw_contacts into seperate contacts
+ */
+ public void markRawContactsForSplitting() {
mSplitRawContacts = true;
}
+ public void setJoinWithRawContacts(List<Long> rawContactIds) {
+ mJoinWithRawContactIds = rawContactIds;
+ }
+
/** {@inheritDoc} */
public int describeContents() {
// Nothing special about this parcel
@@ -313,25 +348,29 @@
for (EntityDelta delta : this) {
dest.writeParcelable(delta, flags);
}
+ dest.writeList(mJoinWithRawContactIds);
}
+ @SuppressWarnings("unchecked")
public void readFromParcel(Parcel source) {
final ClassLoader loader = getClass().getClassLoader();
final int size = source.readInt();
for (int i = 0; i < size; i++) {
this.add(source.<EntityDelta> readParcelable(loader));
}
+ mJoinWithRawContactIds = source.readArrayList(loader);
}
- public static final Parcelable.Creator<EntitySet> CREATOR = new Parcelable.Creator<EntitySet>() {
- public EntitySet createFromParcel(Parcel in) {
- final EntitySet state = new EntitySet();
+ public static final Parcelable.Creator<EntityDeltaList> CREATOR =
+ new Parcelable.Creator<EntityDeltaList>() {
+ public EntityDeltaList createFromParcel(Parcel in) {
+ final EntityDeltaList state = new EntityDeltaList();
state.readFromParcel(in);
return state;
}
- public EntitySet[] newArray(int size) {
- return new EntitySet[size];
+ public EntityDeltaList[] newArray(int size) {
+ return new EntityDeltaList[size];
}
};
}
diff --git a/src/com/android/contacts/model/EntityDiff.java b/src/com/android/contacts/model/EntityDiff.java
deleted file mode 100644
index ea46567..0000000
--- a/src/com/android/contacts/model/EntityDiff.java
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
- * Copyright (C) 2009 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.model;
-
-import android.content.ContentProviderOperation;
-import android.content.ContentValues;
-import android.content.Entity;
-import android.content.ContentProviderOperation.Builder;
-import android.content.Entity.NamedContentValues;
-import android.net.Uri;
-import android.provider.BaseColumns;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-
-
-/**
- * Describes a set of {@link ContentProviderOperation} that need to be
- * executed to transform a database from one {@link Entity} to another.
- */
-@Deprecated
-public class EntityDiff extends ArrayList<ContentProviderOperation> {
- private EntityDiff() {
- }
-
- /**
- * Build the set of {@link ContentProviderOperation} needed to translate
- * from "before" to "after". Tries its best to keep operations to
- * minimal number required. Assumes that all {@link ContentValues} are
- * keyed using {@link BaseColumns#_ID} values.
- */
- public static EntityDiff buildDiff(Entity before, Entity after, Uri targetUri,
- String childForeignKey) {
- final EntityDiff diff = new EntityDiff();
-
- Builder builder;
- ContentValues values;
-
- if (before == null) {
- // Before doesn't exist, so insert "after" values
- builder = ContentProviderOperation.newInsert(targetUri);
- builder.withValues(after.getEntityValues());
- diff.add(builder.build());
-
- for (NamedContentValues child : after.getSubValues()) {
- // Add builder with reference to original _id when needed
- builder = ContentProviderOperation.newInsert(child.uri);
- builder.withValues(child.values);
- if (childForeignKey != null) {
- builder.withValueBackReference(childForeignKey, 0);
- }
- diff.add(builder.build());
- }
-
- } else if (after == null) {
- // After doesn't exist, so delete "before" values
- for (NamedContentValues child : before.getSubValues()) {
- builder = ContentProviderOperation.newDelete(child.uri);
- builder.withSelection(getSelectIdClause(child.values), null);
- diff.add(builder.build());
- }
-
- builder = ContentProviderOperation.newDelete(targetUri);
- builder.withSelection(getSelectIdClause(before.getEntityValues()), null);
- diff.add(builder.build());
-
- } else {
- // Somewhere between, so update any changed values
- values = after.getEntityValues();
- if (!before.getEntityValues().equals(values)) {
- // Top-level values changed, so update
- builder = ContentProviderOperation.newUpdate(targetUri);
- builder.withSelection(getSelectIdClause(values), null);
- builder.withValues(values);
- diff.add(builder.build());
- }
-
- // Build lookup maps for children on both sides
- final HashMap<String, NamedContentValues> beforeChildren = buildChildrenMap(before);
- final HashMap<String, NamedContentValues> afterChildren = buildChildrenMap(after);
-
- // Walk through "before" children looking for deletes and updates
- for (NamedContentValues beforeChild : beforeChildren.values()) {
- final String key = buildChildKey(beforeChild);
- final NamedContentValues afterChild = afterChildren.get(key);
-
- if (afterChild == null) {
- // After child doesn't exist, so delete "before" child
- builder = ContentProviderOperation.newDelete(beforeChild.uri);
- builder.withSelection(getSelectIdClause(beforeChild.values), null);
- diff.add(builder.build());
- } else if (!beforeChild.values.equals(afterChild.values)) {
- // After child still exists, and is different, so update
- values = afterChild.values;
- builder = ContentProviderOperation.newUpdate(afterChild.uri);
- builder.withSelection(getSelectIdClause(values), null);
- builder.withValues(values);
- diff.add(builder.build());
- }
-
- // Remove the now-handled "after" child
- afterChildren.remove(key);
- }
-
- // Walk through remaining "after" children, which are inserts
- for (NamedContentValues afterChild : afterChildren.values()) {
- builder = ContentProviderOperation.newInsert(afterChild.uri);
- builder.withValues(afterChild.values);
- diff.add(builder.build());
- }
- }
-
- return diff;
- }
-
- private static String buildChildKey(NamedContentValues child) {
- return child.uri.toString() + child.values.getAsString(BaseColumns._ID);
- }
-
- private static String getSelectIdClause(ContentValues values) {
- return BaseColumns._ID + "=" + values.getAsLong(BaseColumns._ID);
- }
-
- private static HashMap<String, NamedContentValues> buildChildrenMap(Entity entity) {
- final ArrayList<NamedContentValues> children = entity.getSubValues();
- final HashMap<String, NamedContentValues> childrenMap = new HashMap<String, NamedContentValues>(
- children.size());
- for (NamedContentValues child : children) {
- final String key = buildChildKey(child);
- childrenMap.put(key, child);
- }
- return childrenMap;
- }
-}
diff --git a/src/com/android/contacts/model/EntityModifier.java b/src/com/android/contacts/model/EntityModifier.java
index 2e6899e..37267ec 100644
--- a/src/com/android/contacts/model/EntityModifier.java
+++ b/src/com/android/contacts/model/EntityModifier.java
@@ -348,11 +348,11 @@
/**
* Processing to trim any empty {@link ValuesDelta} and {@link EntityDelta}
- * from the given {@link EntitySet}, assuming the given {@link Sources}
+ * from the given {@link EntityDeltaList}, assuming the given {@link Sources}
* dictates the structure for various fields. This method ignores rows not
* described by the {@link ContactsSource}.
*/
- public static void trimEmpty(EntitySet set, Sources sources) {
+ public static void trimEmpty(EntityDeltaList set, Sources sources) {
for (EntityDelta state : set) {
final String accountType = state.getValues().getAsString(RawContacts.ACCOUNT_TYPE);
final ContactsSource source = sources.getInflatedSource(accountType,
diff --git a/src/com/android/contacts/model/FallbackSource.java b/src/com/android/contacts/model/FallbackSource.java
index 9cc855c..e052fed 100644
--- a/src/com/android/contacts/model/FallbackSource.java
+++ b/src/com/android/contacts/model/FallbackSource.java
@@ -107,45 +107,52 @@
if (kind == null) {
kind = addKind(new DataKind(StructuredName.CONTENT_ITEM_TYPE,
R.string.nameLabelsGroup, -1, -1, true));
+ kind.actionHeader = new SimpleInflater(R.string.nameLabelsGroup);
+ kind.actionBody = new SimpleInflater(Nickname.NAME);
}
if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
+ kind.fieldList = Lists.newArrayList();
+ kind.fieldList.add(new EditField(StructuredName.DISPLAY_NAME,
+ R.string.full_name, FLAGS_PERSON_NAME).setShortForm(true));
+
boolean displayOrderPrimary =
context.getResources().getBoolean(R.bool.config_editor_field_order_primary);
- kind.fieldList = Lists.newArrayList();
- kind.fieldList.add(new EditField(StructuredName.PREFIX, R.string.name_prefix,
- FLAGS_PERSON_NAME).setOptional(true));
if (!displayOrderPrimary) {
+ kind.fieldList.add(new EditField(StructuredName.PREFIX, R.string.name_prefix,
+ FLAGS_PERSON_NAME).setLongForm(true));
kind.fieldList.add(new EditField(StructuredName.FAMILY_NAME, R.string.name_family,
- FLAGS_PERSON_NAME));
+ FLAGS_PERSON_NAME).setLongForm(true));
kind.fieldList.add(new EditField(StructuredName.MIDDLE_NAME, R.string.name_middle,
- FLAGS_PERSON_NAME).setOptional(true));
+ FLAGS_PERSON_NAME).setLongForm(true));
kind.fieldList.add(new EditField(StructuredName.GIVEN_NAME, R.string.name_given,
- FLAGS_PERSON_NAME));
+ FLAGS_PERSON_NAME).setLongForm(true));
kind.fieldList.add(new EditField(StructuredName.SUFFIX, R.string.name_suffix,
- FLAGS_PERSON_NAME).setOptional(true));
+ FLAGS_PERSON_NAME).setLongForm(true));
kind.fieldList.add(new EditField(StructuredName.PHONETIC_FAMILY_NAME,
- R.string.name_phonetic_family, FLAGS_PHONETIC).setOptional(true));
+ R.string.name_phonetic_family, FLAGS_PHONETIC).setLongForm(true));
kind.fieldList.add(new EditField(StructuredName.PHONETIC_MIDDLE_NAME,
- R.string.name_phonetic_middle, FLAGS_PHONETIC).setOptional(true));
+ R.string.name_phonetic_middle, FLAGS_PHONETIC).setLongForm(true));
kind.fieldList.add(new EditField(StructuredName.PHONETIC_GIVEN_NAME,
- R.string.name_phonetic_given, FLAGS_PHONETIC).setOptional(true));
+ R.string.name_phonetic_given, FLAGS_PHONETIC).setLongForm(true));
} else {
+ kind.fieldList.add(new EditField(StructuredName.PREFIX, R.string.name_prefix,
+ FLAGS_PERSON_NAME).setLongForm(true));
kind.fieldList.add(new EditField(StructuredName.GIVEN_NAME, R.string.name_given,
- FLAGS_PERSON_NAME));
+ FLAGS_PERSON_NAME).setLongForm(true));
kind.fieldList.add(new EditField(StructuredName.MIDDLE_NAME, R.string.name_middle,
- FLAGS_PERSON_NAME).setOptional(true));
+ FLAGS_PERSON_NAME).setLongForm(true));
kind.fieldList.add(new EditField(StructuredName.FAMILY_NAME, R.string.name_family,
- FLAGS_PERSON_NAME));
+ FLAGS_PERSON_NAME).setLongForm(true));
kind.fieldList.add(new EditField(StructuredName.SUFFIX, R.string.name_suffix,
- FLAGS_PERSON_NAME).setOptional(true));
+ FLAGS_PERSON_NAME).setLongForm(true));
kind.fieldList.add(new EditField(StructuredName.PHONETIC_GIVEN_NAME,
- R.string.name_phonetic_given, FLAGS_PHONETIC).setOptional(true));
+ R.string.name_phonetic_given, FLAGS_PHONETIC).setLongForm(true));
kind.fieldList.add(new EditField(StructuredName.PHONETIC_MIDDLE_NAME,
- R.string.name_phonetic_middle, FLAGS_PHONETIC).setOptional(true));
+ R.string.name_phonetic_middle, FLAGS_PHONETIC).setLongForm(true));
kind.fieldList.add(new EditField(StructuredName.PHONETIC_FAMILY_NAME,
- R.string.name_phonetic_family, FLAGS_PHONETIC).setOptional(true));
+ R.string.name_phonetic_family, FLAGS_PHONETIC).setLongForm(true));
}
}
@@ -256,8 +263,6 @@
}
if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
- final boolean useJapaneseOrder =
- Locale.JAPANESE.getLanguage().equals(Locale.getDefault().getLanguage());
kind.typeColumn = StructuredPostal.TYPE;
kind.typeList = Lists.newArrayList();
kind.typeList.add(buildPostalType(StructuredPostal.TYPE_HOME));
@@ -267,38 +272,9 @@
.setCustomColumn(StructuredPostal.LABEL));
kind.fieldList = Lists.newArrayList();
-
- if (useJapaneseOrder) {
- kind.fieldList.add(new EditField(StructuredPostal.COUNTRY,
- R.string.postal_country, FLAGS_POSTAL).setOptional(true));
- kind.fieldList.add(new EditField(StructuredPostal.POSTCODE,
- R.string.postal_postcode, FLAGS_POSTAL));
- kind.fieldList.add(new EditField(StructuredPostal.REGION,
- R.string.postal_region, FLAGS_POSTAL));
- kind.fieldList.add(new EditField(StructuredPostal.CITY,
- R.string.postal_city, FLAGS_POSTAL));
- kind.fieldList.add(new EditField(StructuredPostal.NEIGHBORHOOD,
- R.string.postal_neighborhood, FLAGS_POSTAL).setOptional(true));
- kind.fieldList.add(new EditField(StructuredPostal.STREET,
- R.string.postal_street, FLAGS_POSTAL));
- kind.fieldList.add(new EditField(StructuredPostal.POBOX,
- R.string.postal_pobox, FLAGS_POSTAL).setOptional(true));
- } else {
- kind.fieldList.add(new EditField(StructuredPostal.STREET,
- R.string.postal_street, FLAGS_POSTAL));
- kind.fieldList.add(new EditField(StructuredPostal.POBOX,
- R.string.postal_pobox, FLAGS_POSTAL).setOptional(true));
- kind.fieldList.add(new EditField(StructuredPostal.NEIGHBORHOOD,
- R.string.postal_neighborhood, FLAGS_POSTAL).setOptional(true));
- kind.fieldList.add(new EditField(StructuredPostal.CITY,
- R.string.postal_city, FLAGS_POSTAL));
- kind.fieldList.add(new EditField(StructuredPostal.REGION,
- R.string.postal_region, FLAGS_POSTAL));
- kind.fieldList.add(new EditField(StructuredPostal.POSTCODE,
- R.string.postal_postcode, FLAGS_POSTAL));
- kind.fieldList.add(new EditField(StructuredPostal.COUNTRY,
- R.string.postal_country, FLAGS_POSTAL).setOptional(true));
- }
+ kind.fieldList.add(
+ new EditField(StructuredPostal.FORMATTED_ADDRESS, R.string.postal_address,
+ FLAGS_POSTAL).setMinLines(3));
}
return kind;
diff --git a/src/com/android/contacts/model/GoogleSource.java b/src/com/android/contacts/model/GoogleSource.java
index 90abc92..149249a 100644
--- a/src/com/android/contacts/model/GoogleSource.java
+++ b/src/com/android/contacts/model/GoogleSource.java
@@ -17,28 +17,12 @@
package com.android.contacts.model;
import com.android.contacts.R;
-import com.android.contacts.model.EntityDelta.ValuesDelta;
import com.google.android.collect.Lists;
-import android.accounts.Account;
-import android.content.ContentProviderOperation;
-import android.content.ContentProviderResult;
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
import android.content.Context;
-import android.content.OperationApplicationException;
-import android.database.Cursor;
-import android.os.RemoteException;
-import android.provider.ContactsContract;
-import android.provider.ContactsContract.Groups;
-import android.provider.ContactsContract.RawContacts;
import android.provider.ContactsContract.CommonDataKinds.Email;
-import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
import android.provider.ContactsContract.CommonDataKinds.Phone;
-import android.provider.ContactsContract.Contacts.Data;
-
-import java.util.ArrayList;
+import android.provider.ContactsContract.Groups;
public class GoogleSource extends FallbackSource {
public static final String ACCOUNT_TYPE = "com.google";
@@ -157,113 +141,6 @@
return super.inflateWebsite(context, inflateLevel);
}
- // TODO: this should come from resource in the future
- // Note that frameworks/base/core/java/android/pim/vcard/VCardEntry.java also wants
- // this String.
- private static final String GOOGLE_MY_CONTACTS_GROUP = "System Group: My Contacts";
-
- public static final void attemptMyContactsMembership(EntityDelta state, Context context) {
- final ValuesDelta stateValues = state.getValues();
- stateValues.setFromTemplate(true);
- final String accountName = stateValues.getAsString(RawContacts.ACCOUNT_NAME);
- final String accountType = stateValues.getAsString(RawContacts.ACCOUNT_TYPE);
- attemptMyContactsMembership(state, accountName, accountType, context, true);
- }
-
- public static final void createMyContactsIfNotExist(Account account, Context context) {
- attemptMyContactsMembership(null, account.name, account.type, context, true);
- }
-
- /**
- *
- * @param allowRecur If the group is created between querying/about to create, we recur. But
- * to prevent excess recursion, we provide a flag to make sure we only do the recursion loop
- * once
- */
- private static final void attemptMyContactsMembership(EntityDelta state,
- final String accountName, final String accountType, Context context,
- boolean allowRecur) {
- final ContentResolver resolver = context.getContentResolver();
-
- Cursor cursor = resolver.query(Groups.CONTENT_URI,
- new String[] {Groups.TITLE, Groups.SOURCE_ID, Groups.SHOULD_SYNC},
- Groups.ACCOUNT_NAME + " =? AND " + Groups.ACCOUNT_TYPE + " =?",
- new String[] {accountName, accountType}, null);
-
- boolean myContactsExists = false;
- long assignToGroupSourceId = -1;
- while (cursor.moveToNext()) {
- if (GOOGLE_MY_CONTACTS_GROUP.equals(cursor.getString(0))) {
- myContactsExists = true;
- }
- if (assignToGroupSourceId == -1 && cursor.getInt(2) != 0) {
- assignToGroupSourceId = cursor.getInt(1);
- }
-
- if (myContactsExists && assignToGroupSourceId != -1) {
- break;
- }
- }
-
- if (myContactsExists && state == null) {
- return;
- }
-
- try {
- final ContentValues values = new ContentValues();
- values.put(Data.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE);
-
- if (!myContactsExists) {
- // create the group if it doesn't exist
- final ContentValues newGroup = new ContentValues();
- newGroup.put(Groups.TITLE, GOOGLE_MY_CONTACTS_GROUP);
-
- newGroup.put(Groups.ACCOUNT_NAME, accountName);
- newGroup.put(Groups.ACCOUNT_TYPE, accountType);
- newGroup.put(Groups.GROUP_VISIBLE, "1");
-
- ArrayList<ContentProviderOperation> operations =
- new ArrayList<ContentProviderOperation>();
-
- operations.add(ContentProviderOperation
- .newAssertQuery(Groups.CONTENT_URI)
- .withSelection(SELECTION_GROUPS_BY_TITLE_AND_ACCOUNT,
- new String[] {GOOGLE_MY_CONTACTS_GROUP, accountName, accountType})
- .withExpectedCount(0).build());
- operations.add(ContentProviderOperation
- .newInsert(Groups.CONTENT_URI)
- .withValues(newGroup)
- .build());
- try {
- ContentProviderResult[] results = resolver.applyBatch(
- ContactsContract.AUTHORITY, operations);
- values.put(GroupMembership.GROUP_ROW_ID, ContentUris.parseId(results[1].uri));
- } catch (RemoteException e) {
- throw new IllegalStateException("Problem querying for groups", e);
- } catch (OperationApplicationException e) {
- // the group was created after the query but before we tried to create it
- if (allowRecur) {
- attemptMyContactsMembership(
- state, accountName, accountType, context, false);
- }
- return;
- }
- } else {
- if (assignToGroupSourceId != -1) {
- values.put(GroupMembership.GROUP_SOURCE_ID, assignToGroupSourceId);
- } else {
- // there are no Groups to add this contact to, so don't apply any membership
- // TODO: alert user that their contact will be dropped?
- }
- }
- if (state != null) {
- state.addEntry(ValuesDelta.fromAfter(values));
- }
- } finally {
- cursor.close();
- }
- }
-
@Override
public int getHeaderColor(Context context) {
return 0xff89c2c2;
diff --git a/src/com/android/contacts/model/Sources.java b/src/com/android/contacts/model/Sources.java
index be3f17d..44631e8 100644
--- a/src/com/android/contacts/model/Sources.java
+++ b/src/com/android/contacts/model/Sources.java
@@ -42,7 +42,6 @@
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.Locale;
/**
* Singleton holder for all parsed {@link ContactsSource} available on the
@@ -64,8 +63,7 @@
/**
* Requests the singleton instance of {@link Sources} with data bound from
- * the available authenticators. This method blocks until its interaction
- * with {@link AccountManager} is finished, so don't call from a UI thread.
+ * the available authenticators. This method can safely be called from the UI thread.
*/
public static synchronized Sources getInstance(Context context) {
Sources sources = sInstance == null ? null : sInstance.get();
@@ -179,8 +177,7 @@
}
/**
- * Blocking call to load all {@link AuthenticatorDescription} known by the
- * {@link AccountManager} on the system.
+ * Loads all {@link AuthenticatorDescription} known by the {@link AccountManager} on the system.
*/
protected synchronized void queryAccounts() {
mSources.clear();
diff --git a/src/com/android/contacts/ui/ContactsPreferencesActivity.java b/src/com/android/contacts/ui/ContactsPreferencesActivity.java
index 5a89745..8e0d94e 100644
--- a/src/com/android/contacts/ui/ContactsPreferencesActivity.java
+++ b/src/com/android/contacts/ui/ContactsPreferencesActivity.java
@@ -23,6 +23,7 @@
import com.android.contacts.model.Sources;
import com.android.contacts.model.EntityDelta.ValuesDelta;
import com.android.contacts.util.EmptyService;
+import com.android.contacts.util.LocalizedNameResolver;
import com.android.contacts.util.WeakAsyncTask;
import com.google.android.collect.Lists;
@@ -441,8 +442,17 @@
put(mUngrouped ? Settings.UNGROUPED_VISIBLE : Groups.GROUP_VISIBLE, visible ? 1 : 0);
}
+ private String getAccountType() {
+ return (mBefore == null ? mAfter : mBefore).getAsString(Settings.ACCOUNT_TYPE);
+ }
+
public CharSequence getTitle(Context context) {
if (mUngrouped) {
+ final String customAllContactsName =
+ LocalizedNameResolver.getAllContactsName(context, getAccountType());
+ if (customAllContactsName != null) {
+ return customAllContactsName;
+ }
if (mAccountHasGroups) {
return context.getText(R.string.display_ungrouped);
} else {
diff --git a/src/com/android/contacts/ui/EditContactActivity.java b/src/com/android/contacts/ui/EditContactActivity.java
deleted file mode 100644
index 3e248ea..0000000
--- a/src/com/android/contacts/ui/EditContactActivity.java
+++ /dev/null
@@ -1,1415 +0,0 @@
-/*
- * Copyright (C) 2009 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.ui;
-
-import com.android.contacts.ContactsListActivity;
-import com.android.contacts.ContactsSearchManager;
-import com.android.contacts.ContactsUtils;
-import com.android.contacts.R;
-import com.android.contacts.model.ContactsSource;
-import com.android.contacts.model.Editor;
-import com.android.contacts.model.EntityDelta;
-import com.android.contacts.model.EntityModifier;
-import com.android.contacts.model.EntitySet;
-import com.android.contacts.model.GoogleSource;
-import com.android.contacts.model.Sources;
-import com.android.contacts.model.ContactsSource.EditType;
-import com.android.contacts.model.Editor.EditorListener;
-import com.android.contacts.model.EntityDelta.ValuesDelta;
-import com.android.contacts.ui.widget.BaseContactEditorView;
-import com.android.contacts.ui.widget.PhotoEditorView;
-import com.android.contacts.util.EmptyService;
-import com.android.contacts.util.WeakAsyncTask;
-import com.google.android.collect.Lists;
-
-import android.accounts.Account;
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.app.ProgressDialog;
-import android.content.ActivityNotFoundException;
-import android.content.ContentProviderOperation;
-import android.content.ContentProviderResult;
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Entity;
-import android.content.Intent;
-import android.content.OperationApplicationException;
-import android.content.ContentProviderOperation.Builder;
-import android.database.Cursor;
-import android.graphics.Bitmap;
-import android.media.MediaScannerConnection;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Environment;
-import android.os.RemoteException;
-import android.provider.ContactsContract;
-import android.provider.MediaStore;
-import android.provider.ContactsContract.AggregationExceptions;
-import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.RawContacts;
-import android.provider.ContactsContract.CommonDataKinds.Email;
-import android.provider.ContactsContract.CommonDataKinds.Phone;
-import android.provider.ContactsContract.Contacts.Data;
-import android.util.Log;
-import android.view.ContextThemeWrapper;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ArrayAdapter;
-import android.widget.LinearLayout;
-import android.widget.ListAdapter;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import java.io.File;
-import java.lang.ref.WeakReference;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.Date;
-
-/**
- * Activity for editing or inserting a contact.
- */
-public final class EditContactActivity extends Activity
- implements View.OnClickListener, Comparator<EntityDelta> {
-
- private static final String TAG = "EditContactActivity";
-
- /** The launch code when picking a photo and the raw data is returned */
- private static final int PHOTO_PICKED_WITH_DATA = 3021;
-
- /** The launch code when a contact to join with is returned */
- private static final int REQUEST_JOIN_CONTACT = 3022;
-
- /** The launch code when taking a picture */
- private static final int CAMERA_WITH_DATA = 3023;
-
- private static final String KEY_EDIT_STATE = "state";
- private static final String KEY_RAW_CONTACT_ID_REQUESTING_PHOTO = "photorequester";
- private static final String KEY_VIEW_ID_GENERATOR = "viewidgenerator";
- private static final String KEY_CURRENT_PHOTO_FILE = "currentphotofile";
- private static final String KEY_QUERY_SELECTION = "queryselection";
- private static final String KEY_CONTACT_ID_FOR_JOIN = "contactidforjoin";
-
- /** The result code when view activity should close after edit returns */
- public static final int RESULT_CLOSE_VIEW_ACTIVITY = 777;
-
- public static final int SAVE_MODE_DEFAULT = 0;
- public static final int SAVE_MODE_SPLIT = 1;
- public static final int SAVE_MODE_JOIN = 2;
-
- private long mRawContactIdRequestingPhoto = -1;
-
- private static final int DIALOG_CONFIRM_DELETE = 1;
- private static final int DIALOG_CONFIRM_READONLY_DELETE = 2;
- private static final int DIALOG_CONFIRM_MULTIPLE_DELETE = 3;
- private static final int DIALOG_CONFIRM_READONLY_HIDE = 4;
-
- private static final int ICON_SIZE = 96;
-
- private static final File PHOTO_DIR = new File(
- Environment.getExternalStorageDirectory() + "/DCIM/Camera");
-
- private File mCurrentPhotoFile;
-
- String mQuerySelection;
-
- private long mContactIdForJoin;
-
- private static final int STATUS_LOADING = 0;
- private static final int STATUS_EDITING = 1;
- private static final int STATUS_SAVING = 2;
-
- private int mStatus;
-
- EntitySet mState;
-
- /** The linear layout holding the ContactEditorViews */
- LinearLayout mContent;
-
- private ArrayList<Dialog> mManagedDialogs = Lists.newArrayList();
-
- private ViewIdGenerator mViewIdGenerator;
-
- @Override
- protected void onCreate(Bundle icicle) {
- super.onCreate(icicle);
-
- final Intent intent = getIntent();
- final String action = intent.getAction();
-
- setContentView(R.layout.act_edit);
-
- // Build editor and listen for photo requests
- mContent = (LinearLayout) findViewById(R.id.editors);
-
- findViewById(R.id.btn_done).setOnClickListener(this);
- findViewById(R.id.btn_discard).setOnClickListener(this);
-
- // Handle initial actions only when existing state missing
- final boolean hasIncomingState = icicle != null && icicle.containsKey(KEY_EDIT_STATE);
-
- if (Intent.ACTION_EDIT.equals(action) && !hasIncomingState) {
- setTitle(R.string.editContact_title_edit);
- mStatus = STATUS_LOADING;
-
- // Read initial state from database
- new QueryEntitiesTask(this).execute(intent);
- } else if (Intent.ACTION_INSERT.equals(action) && !hasIncomingState) {
- setTitle(R.string.editContact_title_insert);
- mStatus = STATUS_EDITING;
- // Trigger dialog to pick account type
- doAddAction();
- }
-
- if (icicle == null) {
- // If icicle is non-null, onRestoreInstanceState() will restore the generator.
- mViewIdGenerator = new ViewIdGenerator();
- }
- }
-
- private static class QueryEntitiesTask extends
- WeakAsyncTask<Intent, Void, EntitySet, EditContactActivity> {
-
- private String mSelection;
-
- public QueryEntitiesTask(EditContactActivity target) {
- super(target);
- }
-
- @Override
- protected EntitySet doInBackground(EditContactActivity target, Intent... params) {
- final Intent intent = params[0];
-
- final ContentResolver resolver = target.getContentResolver();
-
- // Handle both legacy and new authorities
- final Uri data = intent.getData();
- final String authority = data.getAuthority();
- final String mimeType = intent.resolveType(resolver);
-
- mSelection = "0";
- if (ContactsContract.AUTHORITY.equals(authority)) {
- if (Contacts.CONTENT_ITEM_TYPE.equals(mimeType)) {
- // Handle selected aggregate
- final long contactId = ContentUris.parseId(data);
- mSelection = RawContacts.CONTACT_ID + "=" + contactId;
- } else if (RawContacts.CONTENT_ITEM_TYPE.equals(mimeType)) {
- final long rawContactId = ContentUris.parseId(data);
- final long contactId = ContactsUtils.queryForContactId(resolver, rawContactId);
- mSelection = RawContacts.CONTACT_ID + "=" + contactId;
- }
- } else if (android.provider.Contacts.AUTHORITY.equals(authority)) {
- final long rawContactId = ContentUris.parseId(data);
- mSelection = Data.RAW_CONTACT_ID + "=" + rawContactId;
- }
-
- return EntitySet.fromQuery(target.getContentResolver(), mSelection, null, null);
- }
-
- @Override
- protected void onPostExecute(EditContactActivity target, EntitySet entitySet) {
- target.mQuerySelection = mSelection;
-
- // Load edit details in background
- final Context context = target;
- final Sources sources = Sources.getInstance(context);
-
- // Handle any incoming values that should be inserted
- final Bundle extras = target.getIntent().getExtras();
- final boolean hasExtras = extras != null && extras.size() > 0;
- final boolean hasState = entitySet.size() > 0;
- if (hasExtras && hasState) {
- // Find source defining the first RawContact found
- final EntityDelta state = entitySet.get(0);
- final String accountType = state.getValues().getAsString(RawContacts.ACCOUNT_TYPE);
- final ContactsSource source = sources.getInflatedSource(accountType,
- ContactsSource.LEVEL_CONSTRAINTS);
- EntityModifier.parseExtras(context, source, state, extras);
- }
-
- target.mState = entitySet;
-
- // Bind UI to new background state
- target.bindEditors();
- }
- }
-
- @Override
- protected void onSaveInstanceState(Bundle outState) {
- if (hasValidState()) {
- // Store entities with modifications
- outState.putParcelable(KEY_EDIT_STATE, mState);
- }
-
- outState.putLong(KEY_RAW_CONTACT_ID_REQUESTING_PHOTO, mRawContactIdRequestingPhoto);
- outState.putParcelable(KEY_VIEW_ID_GENERATOR, mViewIdGenerator);
- if (mCurrentPhotoFile != null) {
- outState.putString(KEY_CURRENT_PHOTO_FILE, mCurrentPhotoFile.toString());
- }
- outState.putString(KEY_QUERY_SELECTION, mQuerySelection);
- outState.putLong(KEY_CONTACT_ID_FOR_JOIN, mContactIdForJoin);
- super.onSaveInstanceState(outState);
- }
-
- @Override
- protected void onRestoreInstanceState(Bundle savedInstanceState) {
- // Read modifications from instance
- mState = savedInstanceState.<EntitySet> getParcelable(KEY_EDIT_STATE);
- mRawContactIdRequestingPhoto = savedInstanceState.getLong(
- KEY_RAW_CONTACT_ID_REQUESTING_PHOTO);
- mViewIdGenerator = savedInstanceState.getParcelable(KEY_VIEW_ID_GENERATOR);
- String fileName = savedInstanceState.getString(KEY_CURRENT_PHOTO_FILE);
- if (fileName != null) {
- mCurrentPhotoFile = new File(fileName);
- }
- mQuerySelection = savedInstanceState.getString(KEY_QUERY_SELECTION);
- mContactIdForJoin = savedInstanceState.getLong(KEY_CONTACT_ID_FOR_JOIN);
-
- bindEditors();
-
- super.onRestoreInstanceState(savedInstanceState);
- }
-
- @Override
- protected void onDestroy() {
- super.onDestroy();
-
- for (Dialog dialog : mManagedDialogs) {
- dismissDialog(dialog);
- }
- }
-
- @Override
- protected Dialog onCreateDialog(int id, Bundle bundle) {
- switch (id) {
- case DIALOG_CONFIRM_DELETE:
- return new AlertDialog.Builder(this)
- .setTitle(R.string.deleteConfirmation_title)
- .setIcon(android.R.drawable.ic_dialog_alert)
- .setMessage(R.string.deleteConfirmation)
- .setNegativeButton(android.R.string.cancel, null)
- .setPositiveButton(android.R.string.ok, new DeleteClickListener())
- .setCancelable(false)
- .create();
- case DIALOG_CONFIRM_READONLY_DELETE:
- return new AlertDialog.Builder(this)
- .setTitle(R.string.deleteConfirmation_title)
- .setIcon(android.R.drawable.ic_dialog_alert)
- .setMessage(R.string.readOnlyContactDeleteConfirmation)
- .setNegativeButton(android.R.string.cancel, null)
- .setPositiveButton(android.R.string.ok, new DeleteClickListener())
- .setCancelable(false)
- .create();
- case DIALOG_CONFIRM_MULTIPLE_DELETE:
- return new AlertDialog.Builder(this)
- .setTitle(R.string.deleteConfirmation_title)
- .setIcon(android.R.drawable.ic_dialog_alert)
- .setMessage(R.string.multipleContactDeleteConfirmation)
- .setNegativeButton(android.R.string.cancel, null)
- .setPositiveButton(android.R.string.ok, new DeleteClickListener())
- .setCancelable(false)
- .create();
- case DIALOG_CONFIRM_READONLY_HIDE:
- return new AlertDialog.Builder(this)
- .setTitle(R.string.deleteConfirmation_title)
- .setIcon(android.R.drawable.ic_dialog_alert)
- .setMessage(R.string.readOnlyContactWarning)
- .setPositiveButton(android.R.string.ok, new DeleteClickListener())
- .setCancelable(false)
- .create();
- }
- return null;
- }
-
- /**
- * Start managing this {@link Dialog} along with the {@link Activity}.
- */
- private void startManagingDialog(Dialog dialog) {
- synchronized (mManagedDialogs) {
- mManagedDialogs.add(dialog);
- }
- }
-
- /**
- * Show this {@link Dialog} and manage with the {@link Activity}.
- */
- void showAndManageDialog(Dialog dialog) {
- startManagingDialog(dialog);
- dialog.show();
- }
-
- /**
- * Dismiss the given {@link Dialog}.
- */
- static void dismissDialog(Dialog dialog) {
- try {
- // Only dismiss when valid reference and still showing
- if (dialog != null && dialog.isShowing()) {
- dialog.dismiss();
- }
- } catch (Exception e) {
- Log.w(TAG, "Ignoring exception while dismissing dialog: " + e.toString());
- }
- }
-
- /**
- * Check if our internal {@link #mState} is valid, usually checked before
- * performing user actions.
- */
- protected boolean hasValidState() {
- return mStatus == STATUS_EDITING && mState != null && mState.size() > 0;
- }
-
- /**
- * Rebuild the editors to match our underlying {@link #mState} object, usually
- * called once we've parsed {@link Entity} data or have inserted a new
- * {@link RawContacts}.
- */
- protected void bindEditors() {
- if (mState == null) {
- return;
- }
-
- final LayoutInflater inflater = (LayoutInflater) getSystemService(
- Context.LAYOUT_INFLATER_SERVICE);
- final Sources sources = Sources.getInstance(this);
-
- // Sort the editors
- Collections.sort(mState, this);
-
- // Remove any existing editors and rebuild any visible
- mContent.removeAllViews();
- int size = mState.size();
- for (int i = 0; i < size; i++) {
- // TODO ensure proper ordering of entities in the list
- EntityDelta entity = mState.get(i);
- final ValuesDelta values = entity.getValues();
- if (!values.isVisible()) continue;
-
- final String accountType = values.getAsString(RawContacts.ACCOUNT_TYPE);
- final ContactsSource source = sources.getInflatedSource(accountType,
- ContactsSource.LEVEL_CONSTRAINTS);
- final long rawContactId = values.getAsLong(RawContacts._ID);
-
- BaseContactEditorView editor;
- if (!source.readOnly) {
- editor = (BaseContactEditorView) inflater.inflate(R.layout.item_contact_editor,
- mContent, false);
- } else {
- editor = (BaseContactEditorView) inflater.inflate(
- R.layout.item_read_only_contact_editor, mContent, false);
- }
- PhotoEditorView photoEditor = editor.getPhotoEditor();
- photoEditor.setEditorListener(new PhotoListener(rawContactId, source.readOnly,
- photoEditor));
-
- mContent.addView(editor);
- editor.setState(entity, source, mViewIdGenerator);
- }
-
- // Show editor now that we've loaded state
- mContent.setVisibility(View.VISIBLE);
- mStatus = STATUS_EDITING;
- }
-
- /**
- * Class that listens to requests coming from photo editors
- */
- private class PhotoListener implements EditorListener, DialogInterface.OnClickListener {
- private long mRawContactId;
- private boolean mReadOnly;
- private PhotoEditorView mEditor;
-
- public PhotoListener(long rawContactId, boolean readOnly, PhotoEditorView editor) {
- mRawContactId = rawContactId;
- mReadOnly = readOnly;
- mEditor = editor;
- }
-
- public void onDeleted(Editor editor) {
- // Do nothing
- }
-
- public void onRequest(int request) {
- if (!hasValidState()) return;
-
- if (request == EditorListener.REQUEST_PICK_PHOTO) {
- if (mEditor.hasSetPhoto()) {
- // There is an existing photo, offer to remove, replace, or promoto to primary
- createPhotoDialog().show();
- } else if (!mReadOnly) {
- // No photo set and not read-only, try to set the photo
- doPickPhotoAction(mRawContactId);
- }
- }
- }
-
- /**
- * Prepare dialog for picking a new {@link EditType} or entering a
- * custom label. This dialog is limited to the valid types as determined
- * by {@link EntityModifier}.
- */
- public Dialog createPhotoDialog() {
- Context context = EditContactActivity.this;
-
- // Wrap our context to inflate list items using correct theme
- final Context dialogContext = new ContextThemeWrapper(context,
- android.R.style.Theme_Light);
-
- String[] choices;
- if (mReadOnly) {
- choices = new String[1];
- choices[0] = getString(R.string.use_photo_as_primary);
- } else {
- choices = new String[3];
- choices[0] = getString(R.string.use_photo_as_primary);
- choices[1] = getString(R.string.removePicture);
- choices[2] = getString(R.string.changePicture);
- }
- final ListAdapter adapter = new ArrayAdapter<String>(dialogContext,
- android.R.layout.simple_list_item_1, choices);
-
- final AlertDialog.Builder builder = new AlertDialog.Builder(dialogContext);
- builder.setTitle(R.string.attachToContact);
- builder.setSingleChoiceItems(adapter, -1, this);
- return builder.create();
- }
-
- /**
- * Called when something in the dialog is clicked
- */
- public void onClick(DialogInterface dialog, int which) {
- dialog.dismiss();
-
- switch (which) {
- case 0:
- // Set the photo as super primary
- mEditor.setSuperPrimary(true);
-
- // And set all other photos as not super primary
- int count = mContent.getChildCount();
- for (int i = 0; i < count; i++) {
- View childView = mContent.getChildAt(i);
- if (childView instanceof BaseContactEditorView) {
- BaseContactEditorView editor = (BaseContactEditorView) childView;
- PhotoEditorView photoEditor = editor.getPhotoEditor();
- if (!photoEditor.equals(mEditor)) {
- photoEditor.setSuperPrimary(false);
- }
- }
- }
- break;
-
- case 1:
- // Remove the photo
- mEditor.setPhotoBitmap(null);
- break;
-
- case 2:
- // Pick a new photo for the contact
- doPickPhotoAction(mRawContactId);
- break;
- }
- }
- }
-
- /** {@inheritDoc} */
- public void onClick(View view) {
- switch (view.getId()) {
- case R.id.btn_done:
- doSaveAction(SAVE_MODE_DEFAULT);
- break;
- case R.id.btn_discard:
- doRevertAction();
- break;
- }
- }
-
- /** {@inheritDoc} */
- @Override
- public void onBackPressed() {
- doSaveAction(SAVE_MODE_DEFAULT);
- }
-
- /** {@inheritDoc} */
- @Override
- protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- // Ignore failed requests
- if (resultCode != RESULT_OK) return;
-
- switch (requestCode) {
- case PHOTO_PICKED_WITH_DATA: {
- BaseContactEditorView requestingEditor = null;
- for (int i = 0; i < mContent.getChildCount(); i++) {
- View childView = mContent.getChildAt(i);
- if (childView instanceof BaseContactEditorView) {
- BaseContactEditorView editor = (BaseContactEditorView) childView;
- if (editor.getRawContactId() == mRawContactIdRequestingPhoto) {
- requestingEditor = editor;
- break;
- }
- }
- }
-
- if (requestingEditor != null) {
- final Bitmap photo = data.getParcelableExtra("data");
- requestingEditor.setPhotoBitmap(photo);
- mRawContactIdRequestingPhoto = -1;
- } else {
- // The contact that requested the photo is no longer present.
- // TODO: Show error message
- }
-
- break;
- }
-
- case CAMERA_WITH_DATA: {
- doCropPhoto(mCurrentPhotoFile);
- break;
- }
-
- case REQUEST_JOIN_CONTACT: {
- if (resultCode == RESULT_OK && data != null) {
- final long contactId = ContentUris.parseId(data.getData());
- joinAggregate(contactId);
- }
- }
- }
- }
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- super.onCreateOptionsMenu(menu);
-
- MenuInflater inflater = getMenuInflater();
- inflater.inflate(R.menu.edit, menu);
-
-
- return true;
- }
-
- @Override
- public boolean onPrepareOptionsMenu(Menu menu) {
- menu.findItem(R.id.menu_split).setVisible(mState != null && mState.size() > 1);
- return true;
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case R.id.menu_done:
- return doSaveAction(SAVE_MODE_DEFAULT);
- case R.id.menu_discard:
- return doRevertAction();
- case R.id.menu_add:
- return doAddAction();
- case R.id.menu_delete:
- return doDeleteAction();
- case R.id.menu_split:
- return doSplitContactAction();
- case R.id.menu_join:
- return doJoinContactAction();
- }
- return false;
- }
-
- /**
- * Background task for persisting edited contact data, using the changes
- * defined by a set of {@link EntityDelta}. This task starts
- * {@link EmptyService} to make sure the background thread can finish
- * persisting in cases where the system wants to reclaim our process.
- */
- public static class PersistTask extends
- WeakAsyncTask<EntitySet, Void, Integer, EditContactActivity> {
- private static final int PERSIST_TRIES = 3;
-
- private static final int RESULT_UNCHANGED = 0;
- private static final int RESULT_SUCCESS = 1;
- private static final int RESULT_FAILURE = 2;
-
- private WeakReference<ProgressDialog> mProgress;
-
- private int mSaveMode;
- private Uri mContactLookupUri = null;
-
- public PersistTask(EditContactActivity target, int saveMode) {
- super(target);
- mSaveMode = saveMode;
- }
-
- /** {@inheritDoc} */
- @Override
- protected void onPreExecute(EditContactActivity target) {
- mProgress = new WeakReference<ProgressDialog>(ProgressDialog.show(target, null,
- target.getText(R.string.savingContact)));
-
- // Before starting this task, start an empty service to protect our
- // process from being reclaimed by the system.
- final Context context = target;
- context.startService(new Intent(context, EmptyService.class));
- }
-
- /** {@inheritDoc} */
- @Override
- protected Integer doInBackground(EditContactActivity target, EntitySet... params) {
- final Context context = target;
- final ContentResolver resolver = context.getContentResolver();
-
- EntitySet state = params[0];
-
- // Trim any empty fields, and RawContacts, before persisting
- final Sources sources = Sources.getInstance(context);
- EntityModifier.trimEmpty(state, sources);
-
- // Attempt to persist changes
- int tries = 0;
- Integer result = RESULT_FAILURE;
- while (tries++ < PERSIST_TRIES) {
- try {
- // Build operations and try applying
- final ArrayList<ContentProviderOperation> diff = state.buildDiff();
- ContentProviderResult[] results = null;
- if (!diff.isEmpty()) {
- results = resolver.applyBatch(ContactsContract.AUTHORITY, diff);
- }
-
- final long rawContactId = getRawContactId(state, diff, results);
- if (rawContactId != -1) {
- final Uri rawContactUri = ContentUris.withAppendedId(
- RawContacts.CONTENT_URI, rawContactId);
-
- // convert the raw contact URI to a contact URI
- mContactLookupUri = RawContacts.getContactLookupUri(resolver,
- rawContactUri);
- }
- result = (diff.size() > 0) ? RESULT_SUCCESS : RESULT_UNCHANGED;
- break;
-
- } catch (RemoteException e) {
- // Something went wrong, bail without success
- Log.e(TAG, "Problem persisting user edits", e);
- break;
-
- } catch (OperationApplicationException e) {
- // Version consistency failed, re-parent change and try again
- Log.w(TAG, "Version consistency failed, re-parenting: " + e.toString());
- final EntitySet newState = EntitySet.fromQuery(resolver,
- target.mQuerySelection, null, null);
- state = EntitySet.mergeAfter(newState, state);
- }
- }
-
- return result;
- }
-
- private long getRawContactId(EntitySet state,
- final ArrayList<ContentProviderOperation> diff,
- final ContentProviderResult[] results) {
- long rawContactId = state.findRawContactId();
- if (rawContactId != -1) {
- return rawContactId;
- }
-
- // we gotta do some searching for the id
- final int diffSize = diff.size();
- for (int i = 0; i < diffSize; i++) {
- ContentProviderOperation operation = diff.get(i);
- if (operation.getType() == ContentProviderOperation.TYPE_INSERT
- && operation.getUri().getEncodedPath().contains(
- RawContacts.CONTENT_URI.getEncodedPath())) {
- return ContentUris.parseId(results[i].uri);
- }
- }
- return -1;
- }
-
- /** {@inheritDoc} */
- @Override
- protected void onPostExecute(EditContactActivity target, Integer result) {
- final Context context = target;
- final ProgressDialog progress = mProgress.get();
-
- if (result == RESULT_SUCCESS && mSaveMode != SAVE_MODE_JOIN) {
- Toast.makeText(context, R.string.contactSavedToast, Toast.LENGTH_SHORT).show();
- } else if (result == RESULT_FAILURE) {
- Toast.makeText(context, R.string.contactSavedErrorToast, Toast.LENGTH_LONG).show();
- }
-
- dismissDialog(progress);
-
- // Stop the service that was protecting us
- context.stopService(new Intent(context, EmptyService.class));
-
- target.onSaveCompleted(result != RESULT_FAILURE, mSaveMode, mContactLookupUri);
- }
- }
-
- /**
- * Saves or creates the contact based on the mode, and if successful
- * finishes the activity.
- */
- boolean doSaveAction(int saveMode) {
- if (!hasValidState()) {
- return false;
- }
-
- mStatus = STATUS_SAVING;
- final PersistTask task = new PersistTask(this, saveMode);
- task.execute(mState);
-
- return true;
- }
-
- private class DeleteClickListener implements DialogInterface.OnClickListener {
-
- public void onClick(DialogInterface dialog, int which) {
- Sources sources = Sources.getInstance(EditContactActivity.this);
- // Mark all raw contacts for deletion
- for (EntityDelta delta : mState) {
- delta.markDeleted();
- }
- // Save the deletes
- doSaveAction(SAVE_MODE_DEFAULT);
- finish();
- }
- }
-
- private void onSaveCompleted(boolean success, int saveMode, Uri contactLookupUri) {
- switch (saveMode) {
- case SAVE_MODE_DEFAULT:
- if (success && contactLookupUri != null) {
- final Intent resultIntent = new Intent();
-
- final Uri requestData = getIntent().getData();
- final String requestAuthority = requestData == null ? null : requestData
- .getAuthority();
-
- if (android.provider.Contacts.AUTHORITY.equals(requestAuthority)) {
- // Build legacy Uri when requested by caller
- final long contactId = ContentUris.parseId(Contacts.lookupContact(
- getContentResolver(), contactLookupUri));
- final Uri legacyUri = ContentUris.withAppendedId(
- android.provider.Contacts.People.CONTENT_URI, contactId);
- resultIntent.setData(legacyUri);
- } else {
- // Otherwise pass back a lookup-style Uri
- resultIntent.setData(contactLookupUri);
- }
-
- setResult(RESULT_OK, resultIntent);
- } else {
- setResult(RESULT_CANCELED, null);
- }
- finish();
- break;
-
- case SAVE_MODE_SPLIT:
- if (success) {
- Intent intent = new Intent();
- intent.setData(contactLookupUri);
- setResult(RESULT_CLOSE_VIEW_ACTIVITY, intent);
- }
- finish();
- break;
-
- case SAVE_MODE_JOIN:
- mStatus = STATUS_EDITING;
- if (success) {
- showJoinAggregateActivity(contactLookupUri);
- }
- break;
- }
- }
-
- /**
- * Shows a list of aggregates that can be joined into the currently viewed aggregate.
- *
- * @param contactLookupUri the fresh URI for the currently edited contact (after saving it)
- */
- public void showJoinAggregateActivity(Uri contactLookupUri) {
- if (contactLookupUri == null) {
- return;
- }
-
- mContactIdForJoin = ContentUris.parseId(contactLookupUri);
- Intent intent = new Intent(ContactsListActivity.JOIN_AGGREGATE);
- intent.putExtra(ContactsListActivity.EXTRA_AGGREGATE_ID, mContactIdForJoin);
- startActivityForResult(intent, REQUEST_JOIN_CONTACT);
- }
-
- private interface JoinContactQuery {
- String[] PROJECTION = {
- RawContacts._ID,
- RawContacts.CONTACT_ID,
- RawContacts.NAME_VERIFIED,
- };
-
- String SELECTION = RawContacts.CONTACT_ID + "=? OR " + RawContacts.CONTACT_ID + "=?";
-
- int _ID = 0;
- int CONTACT_ID = 1;
- int NAME_VERIFIED = 2;
- }
-
- /**
- * Performs aggregation with the contact selected by the user from suggestions or A-Z list.
- */
- private void joinAggregate(final long contactId) {
- ContentResolver resolver = getContentResolver();
-
- // Load raw contact IDs for all raw contacts involved - currently edited and selected
- // in the join UIs
- Cursor c = resolver.query(RawContacts.CONTENT_URI,
- JoinContactQuery.PROJECTION,
- JoinContactQuery.SELECTION,
- new String[]{String.valueOf(contactId), String.valueOf(mContactIdForJoin)}, null);
-
- long rawContactIds[];
- long verifiedNameRawContactId = -1;
- try {
- rawContactIds = new long[c.getCount()];
- for (int i = 0; i < rawContactIds.length; i++) {
- c.moveToNext();
- long rawContactId = c.getLong(JoinContactQuery._ID);
- rawContactIds[i] = rawContactId;
- if (c.getLong(JoinContactQuery.CONTACT_ID) == mContactIdForJoin) {
- if (verifiedNameRawContactId == -1
- || c.getInt(JoinContactQuery.NAME_VERIFIED) != 0) {
- verifiedNameRawContactId = rawContactId;
- }
- }
- }
- } finally {
- c.close();
- }
-
- // For each pair of raw contacts, insert an aggregation exception
- ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>();
- for (int i = 0; i < rawContactIds.length; i++) {
- for (int j = 0; j < rawContactIds.length; j++) {
- if (i != j) {
- buildJoinContactDiff(operations, rawContactIds[i], rawContactIds[j]);
- }
- }
- }
-
- // Mark the original contact as "name verified" to make sure that the contact
- // display name does not change as a result of the join
- Builder builder = ContentProviderOperation.newUpdate(
- ContentUris.withAppendedId(RawContacts.CONTENT_URI, verifiedNameRawContactId));
- builder.withValue(RawContacts.NAME_VERIFIED, 1);
- operations.add(builder.build());
-
- // Apply all aggregation exceptions as one batch
- try {
- getContentResolver().applyBatch(ContactsContract.AUTHORITY, operations);
-
- // We can use any of the constituent raw contacts to refresh the UI - why not the first
- Intent intent = new Intent();
- intent.setData(ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactIds[0]));
-
- // Reload the new state from database
- new QueryEntitiesTask(this).execute(intent);
-
- Toast.makeText(this, R.string.contactsJoinedMessage, Toast.LENGTH_LONG).show();
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to apply aggregation exception batch", e);
- Toast.makeText(this, R.string.contactSavedErrorToast, Toast.LENGTH_LONG).show();
- } catch (OperationApplicationException e) {
- Log.e(TAG, "Failed to apply aggregation exception batch", e);
- Toast.makeText(this, R.string.contactSavedErrorToast, Toast.LENGTH_LONG).show();
- }
- }
-
- /**
- * Construct a {@link AggregationExceptions#TYPE_KEEP_TOGETHER} ContentProviderOperation.
- */
- private void buildJoinContactDiff(ArrayList<ContentProviderOperation> operations,
- long rawContactId1, long rawContactId2) {
- Builder builder =
- ContentProviderOperation.newUpdate(AggregationExceptions.CONTENT_URI);
- builder.withValue(AggregationExceptions.TYPE, AggregationExceptions.TYPE_KEEP_TOGETHER);
- builder.withValue(AggregationExceptions.RAW_CONTACT_ID1, rawContactId1);
- builder.withValue(AggregationExceptions.RAW_CONTACT_ID2, rawContactId2);
- operations.add(builder.build());
- }
-
- /**
- * Revert any changes the user has made, and finish the activity.
- */
- private boolean doRevertAction() {
- finish();
- return true;
- }
-
- /**
- * Create a new {@link RawContacts} which will exist as another
- * {@link EntityDelta} under the currently edited {@link Contacts}.
- */
- private boolean doAddAction() {
- if (mStatus != STATUS_EDITING) {
- return false;
- }
-
- // Adding is okay when missing state
- new AddContactTask(this).execute();
- return true;
- }
-
- /**
- * Delete the entire contact currently being edited, which usually asks for
- * user confirmation before continuing.
- */
- private boolean doDeleteAction() {
- if (!hasValidState())
- return false;
- int readOnlySourcesCnt = 0;
- int writableSourcesCnt = 0;
- Sources sources = Sources.getInstance(EditContactActivity.this);
- for (EntityDelta delta : mState) {
- final String accountType = delta.getValues().getAsString(RawContacts.ACCOUNT_TYPE);
- final ContactsSource contactsSource = sources.getInflatedSource(accountType,
- ContactsSource.LEVEL_CONSTRAINTS);
- if (contactsSource != null && contactsSource.readOnly) {
- readOnlySourcesCnt += 1;
- } else {
- writableSourcesCnt += 1;
- }
- }
-
- if (readOnlySourcesCnt > 0 && writableSourcesCnt > 0) {
- showDialog(DIALOG_CONFIRM_READONLY_DELETE);
- } else if (readOnlySourcesCnt > 0 && writableSourcesCnt == 0) {
- showDialog(DIALOG_CONFIRM_READONLY_HIDE);
- } else if (readOnlySourcesCnt == 0 && writableSourcesCnt > 1) {
- showDialog(DIALOG_CONFIRM_MULTIPLE_DELETE);
- } else {
- showDialog(DIALOG_CONFIRM_DELETE);
- }
- return true;
- }
-
- /**
- * Pick a specific photo to be added under the currently selected tab.
- */
- boolean doPickPhotoAction(long rawContactId) {
- if (!hasValidState()) return false;
-
- mRawContactIdRequestingPhoto = rawContactId;
-
- showAndManageDialog(createPickPhotoDialog());
-
- return true;
- }
-
- /**
- * Creates a dialog offering two options: take a photo or pick a photo from the gallery.
- */
- private Dialog createPickPhotoDialog() {
- Context context = EditContactActivity.this;
-
- // Wrap our context to inflate list items using correct theme
- final Context dialogContext = new ContextThemeWrapper(context,
- android.R.style.Theme_Light);
-
- String[] choices;
- choices = new String[2];
- choices[0] = getString(R.string.take_photo);
- choices[1] = getString(R.string.pick_photo);
- final ListAdapter adapter = new ArrayAdapter<String>(dialogContext,
- android.R.layout.simple_list_item_1, choices);
-
- final AlertDialog.Builder builder = new AlertDialog.Builder(dialogContext);
- builder.setTitle(R.string.attachToContact);
- builder.setSingleChoiceItems(adapter, -1, new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- dialog.dismiss();
- switch(which) {
- case 0:
- doTakePhoto();
- break;
- case 1:
- doPickPhotoFromGallery();
- break;
- }
- }
- });
- return builder.create();
- }
-
- /**
- * Create a file name for the icon photo using current time.
- */
- private String getPhotoFileName() {
- Date date = new Date(System.currentTimeMillis());
- SimpleDateFormat dateFormat = new SimpleDateFormat("'IMG'_yyyyMMdd_HHmmss");
- return dateFormat.format(date) + ".jpg";
- }
-
- /**
- * Launches Camera to take a picture and store it in a file.
- */
- protected void doTakePhoto() {
- try {
- // Launch camera to take photo for selected contact
- PHOTO_DIR.mkdirs();
- mCurrentPhotoFile = new File(PHOTO_DIR, getPhotoFileName());
- final Intent intent = getTakePickIntent(mCurrentPhotoFile);
- startActivityForResult(intent, CAMERA_WITH_DATA);
- } catch (ActivityNotFoundException e) {
- Toast.makeText(this, R.string.photoPickerNotFoundText, Toast.LENGTH_LONG).show();
- }
- }
-
- /**
- * Constructs an intent for capturing a photo and storing it in a temporary file.
- */
- public static Intent getTakePickIntent(File f) {
- Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE, null);
- intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(f));
- return intent;
- }
-
- /**
- * Sends a newly acquired photo to Gallery for cropping
- */
- protected void doCropPhoto(File f) {
- try {
-
- // Add the image to the media store
- MediaScannerConnection.scanFile(
- this,
- new String[] { f.getAbsolutePath() },
- new String[] { null },
- null);
-
- // Launch gallery to crop the photo
- final Intent intent = getCropImageIntent(Uri.fromFile(f));
- startActivityForResult(intent, PHOTO_PICKED_WITH_DATA);
- } catch (Exception e) {
- Log.e(TAG, "Cannot crop image", e);
- Toast.makeText(this, R.string.photoPickerNotFoundText, Toast.LENGTH_LONG).show();
- }
- }
-
- /**
- * Constructs an intent for image cropping.
- */
- public static Intent getCropImageIntent(Uri photoUri) {
- Intent intent = new Intent("com.android.camera.action.CROP");
- intent.setDataAndType(photoUri, "image/*");
- intent.putExtra("crop", "true");
- intent.putExtra("aspectX", 1);
- intent.putExtra("aspectY", 1);
- intent.putExtra("outputX", ICON_SIZE);
- intent.putExtra("outputY", ICON_SIZE);
- intent.putExtra("return-data", true);
- return intent;
- }
-
- /**
- * Launches Gallery to pick a photo.
- */
- protected void doPickPhotoFromGallery() {
- try {
- // Launch picker to choose photo for selected contact
- final Intent intent = getPhotoPickIntent();
- startActivityForResult(intent, PHOTO_PICKED_WITH_DATA);
- } catch (ActivityNotFoundException e) {
- Toast.makeText(this, R.string.photoPickerNotFoundText, Toast.LENGTH_LONG).show();
- }
- }
-
- /**
- * Constructs an intent for picking a photo from Gallery, cropping it and returning the bitmap.
- */
- public static Intent getPhotoPickIntent() {
- Intent intent = new Intent(Intent.ACTION_GET_CONTENT, null);
- intent.setType("image/*");
- intent.putExtra("crop", "true");
- intent.putExtra("aspectX", 1);
- intent.putExtra("aspectY", 1);
- intent.putExtra("outputX", ICON_SIZE);
- intent.putExtra("outputY", ICON_SIZE);
- intent.putExtra("return-data", true);
- return intent;
- }
-
- /** {@inheritDoc} */
- public void onDeleted(Editor editor) {
- // Ignore any editor deletes
- }
-
- private boolean doSplitContactAction() {
- if (!hasValidState()) return false;
-
- showAndManageDialog(createSplitDialog());
- return true;
- }
-
- private Dialog createSplitDialog() {
- final AlertDialog.Builder builder = new AlertDialog.Builder(this);
- builder.setTitle(R.string.splitConfirmation_title);
- builder.setIcon(android.R.drawable.ic_dialog_alert);
- builder.setMessage(R.string.splitConfirmation);
- builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- // Split the contacts
- mState.splitRawContacts();
- doSaveAction(SAVE_MODE_SPLIT);
- }
- });
- builder.setNegativeButton(android.R.string.cancel, null);
- builder.setCancelable(false);
- return builder.create();
- }
-
- private boolean doJoinContactAction() {
- return doSaveAction(SAVE_MODE_JOIN);
- }
-
- /**
- * Build dialog that handles adding a new {@link RawContacts} after the user
- * picks a specific {@link ContactsSource}.
- */
- private static class AddContactTask extends
- WeakAsyncTask<Void, Void, ArrayList<Account>, EditContactActivity> {
-
- public AddContactTask(EditContactActivity target) {
- super(target);
- }
-
- @Override
- protected ArrayList<Account> doInBackground(final EditContactActivity target,
- Void... params) {
- return Sources.getInstance(target).getAccounts(true);
- }
-
- @Override
- protected void onPostExecute(final EditContactActivity target, ArrayList<Account> accounts) {
- target.selectAccountAndCreateContact(accounts);
- }
- }
-
- public void selectAccountAndCreateContact(ArrayList<Account> accounts) {
- // No Accounts available. Create a phone-local contact.
- if (accounts.isEmpty()) {
- createContact(null);
- return; // Don't show a dialog.
- }
-
- // In the common case of a single account being writable, auto-select
- // it without showing a dialog.
- if (accounts.size() == 1) {
- createContact(accounts.get(0));
- return; // Don't show a dialog.
- }
-
- // Wrap our context to inflate list items using correct theme
- final Context dialogContext = new ContextThemeWrapper(this, android.R.style.Theme_Light);
- final LayoutInflater dialogInflater =
- (LayoutInflater)dialogContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-
- final Sources sources = Sources.getInstance(this);
-
- final ArrayAdapter<Account> accountAdapter = new ArrayAdapter<Account>(this,
- android.R.layout.simple_list_item_2, accounts) {
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- if (convertView == null) {
- convertView = dialogInflater.inflate(android.R.layout.simple_list_item_2,
- parent, false);
- }
-
- // TODO: show icon along with title
- final TextView text1 = (TextView)convertView.findViewById(android.R.id.text1);
- final TextView text2 = (TextView)convertView.findViewById(android.R.id.text2);
-
- final Account account = this.getItem(position);
- final ContactsSource source = sources.getInflatedSource(account.type,
- ContactsSource.LEVEL_SUMMARY);
-
- text1.setText(account.name);
- text2.setText(source.getDisplayLabel(EditContactActivity.this));
-
- return convertView;
- }
- };
-
- final DialogInterface.OnClickListener clickListener =
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- dialog.dismiss();
-
- // Create new contact based on selected source
- final Account account = accountAdapter.getItem(which);
- createContact(account);
- }
- };
-
- final DialogInterface.OnCancelListener cancelListener =
- new DialogInterface.OnCancelListener() {
- public void onCancel(DialogInterface dialog) {
- // If nothing remains, close activity
- if (!hasValidState()) {
- finish();
- }
- }
- };
-
- final AlertDialog.Builder builder = new AlertDialog.Builder(this);
- builder.setTitle(R.string.dialog_new_contact_account);
- builder.setSingleChoiceItems(accountAdapter, 0, clickListener);
- builder.setOnCancelListener(cancelListener);
- showAndManageDialog(builder.create());
- }
-
- /**
- * @param account may be null to signal a device-local contact should
- * be created.
- */
- private void createContact(Account account) {
- final Sources sources = Sources.getInstance(this);
- final ContentValues values = new ContentValues();
- if (account != null) {
- values.put(RawContacts.ACCOUNT_NAME, account.name);
- values.put(RawContacts.ACCOUNT_TYPE, account.type);
- } else {
- values.putNull(RawContacts.ACCOUNT_NAME);
- values.putNull(RawContacts.ACCOUNT_TYPE);
- }
-
- // Parse any values from incoming intent
- EntityDelta insert = new EntityDelta(ValuesDelta.fromAfter(values));
- final ContactsSource source = sources.getInflatedSource(
- account != null ? account.type : null,
- ContactsSource.LEVEL_CONSTRAINTS);
- final Bundle extras = getIntent().getExtras();
- EntityModifier.parseExtras(this, source, insert, extras);
-
- // Ensure we have some default fields
- EntityModifier.ensureKindExists(insert, source, Phone.CONTENT_ITEM_TYPE);
- EntityModifier.ensureKindExists(insert, source, Email.CONTENT_ITEM_TYPE);
-
- // Create "My Contacts" membership for Google contacts
- // TODO: move this off into "templates" for each given source
- if (GoogleSource.ACCOUNT_TYPE.equals(source.accountType)) {
- GoogleSource.attemptMyContactsMembership(insert, this);
- }
-
- if (mState == null) {
- // Create state if none exists yet
- mState = EntitySet.fromSingle(insert);
- } else {
- // Add contact onto end of existing state
- mState.add(insert);
- }
-
- bindEditors();
- }
-
- /**
- * Compare EntityDeltas for sorting the stack of editors.
- */
- public int compare(EntityDelta one, EntityDelta two) {
- // Check direct equality
- if (one.equals(two)) {
- return 0;
- }
-
- final Sources sources = Sources.getInstance(this);
- String accountType = one.getValues().getAsString(RawContacts.ACCOUNT_TYPE);
- final ContactsSource oneSource = sources.getInflatedSource(accountType,
- ContactsSource.LEVEL_SUMMARY);
- accountType = two.getValues().getAsString(RawContacts.ACCOUNT_TYPE);
- final ContactsSource twoSource = sources.getInflatedSource(accountType,
- ContactsSource.LEVEL_SUMMARY);
-
- // Check read-only
- if (oneSource.readOnly && !twoSource.readOnly) {
- return 1;
- } else if (twoSource.readOnly && !oneSource.readOnly) {
- return -1;
- }
-
- // Check account type
- boolean skipAccountTypeCheck = false;
- boolean oneIsGoogle = oneSource instanceof GoogleSource;
- boolean twoIsGoogle = twoSource instanceof GoogleSource;
- if (oneIsGoogle && !twoIsGoogle) {
- return -1;
- } else if (twoIsGoogle && !oneIsGoogle) {
- return 1;
- } else if (oneIsGoogle && twoIsGoogle){
- skipAccountTypeCheck = true;
- }
-
- int value;
- if (!skipAccountTypeCheck) {
- if (oneSource.accountType == null) {
- return 1;
- }
- value = oneSource.accountType.compareTo(twoSource.accountType);
- if (value != 0) {
- return value;
- }
- }
-
- // Check account name
- ValuesDelta oneValues = one.getValues();
- String oneAccount = oneValues.getAsString(RawContacts.ACCOUNT_NAME);
- if (oneAccount == null) oneAccount = "";
- ValuesDelta twoValues = two.getValues();
- String twoAccount = twoValues.getAsString(RawContacts.ACCOUNT_NAME);
- if (twoAccount == null) twoAccount = "";
- value = oneAccount.compareTo(twoAccount);
- if (value != 0) {
- return value;
- }
-
- // Both are in the same account, fall back to contact ID
- Long oneId = oneValues.getAsLong(RawContacts._ID);
- Long twoId = twoValues.getAsLong(RawContacts._ID);
- if (oneId == null) {
- return -1;
- } else if (twoId == null) {
- return 1;
- }
-
- return (int)(oneId - twoId);
- }
-
- @Override
- public void startSearch(String initialQuery, boolean selectInitialQuery, Bundle appSearchData,
- boolean globalSearch) {
- if (globalSearch) {
- super.startSearch(initialQuery, selectInitialQuery, appSearchData, globalSearch);
- } else {
- ContactsSearchManager.startSearch(this, initialQuery);
- }
- }
-}
diff --git a/src/com/android/contacts/ui/QuickContactWindow.java b/src/com/android/contacts/ui/QuickContactWindow.java
index 20d5bfd..c90961e 100644
--- a/src/com/android/contacts/ui/QuickContactWindow.java
+++ b/src/com/android/contacts/ui/QuickContactWindow.java
@@ -21,8 +21,8 @@
import com.android.contacts.ContactsUtils;
import com.android.contacts.R;
import com.android.contacts.model.ContactsSource;
-import com.android.contacts.model.Sources;
import com.android.contacts.model.ContactsSource.DataKind;
+import com.android.contacts.model.Sources;
import com.android.contacts.ui.widget.CheckableImageView;
import com.android.contacts.util.Constants;
import com.android.contacts.util.DataStatus;
@@ -46,20 +46,21 @@
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.net.Uri;
-import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.Data;
-import android.provider.ContactsContract.QuickContact;
-import android.provider.ContactsContract.RawContacts;
-import android.provider.ContactsContract.StatusUpdates;
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.CommonDataKinds.Im;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.CommonDataKinds.Photo;
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
import android.provider.ContactsContract.CommonDataKinds.Website;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.QuickContact;
+import android.provider.ContactsContract.RawContacts;
+import android.provider.ContactsContract.StatusUpdates;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
+import android.view.ActionMode;
import android.view.ContextThemeWrapper;
import android.view.Gravity;
import android.view.KeyEvent;
@@ -70,9 +71,9 @@
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewStub;
+import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.view.Window;
import android.view.WindowManager;
-import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.view.accessibility.AccessibilityEvent;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
@@ -1073,7 +1074,7 @@
if (isPrefer) return info;
- if (isSystem && firstSystem != null) firstSystem = info;
+ if (isSystem && firstSystem == null) firstSystem = info;
}
// Return first system found, otherwise first from list
@@ -1645,6 +1646,11 @@
// No actions
}
+ /** {@inheritDoc} */
+ public ActionMode onStartActionMode(ActionMode.Callback callback) {
+ return null;
+ }
+
private interface DataQuery {
final String[] PROJECTION = new String[] {
Data._ID,
diff --git a/src/com/android/contacts/ui/ShowOrCreateActivity.java b/src/com/android/contacts/ui/ShowOrCreateActivity.java
index e0781d2..2d8217f 100755
--- a/src/com/android/contacts/ui/ShowOrCreateActivity.java
+++ b/src/com/android/contacts/ui/ShowOrCreateActivity.java
@@ -17,8 +17,8 @@
package com.android.contacts.ui;
import com.android.contacts.ContactsSearchManager;
-import com.android.contacts.ContactsListActivity;
import com.android.contacts.R;
+import com.android.contacts.activities.ContactBrowserActivity;
import com.android.contacts.util.Constants;
import com.android.contacts.util.NotifyingAsyncQueryHandler;
@@ -176,7 +176,7 @@
} else if (count > 1) {
// If more than one, show pick list
Intent listIntent = new Intent(Intent.ACTION_SEARCH);
- listIntent.setComponent(new ComponentName(this, ContactsListActivity.class));
+ listIntent.setComponent(new ComponentName(this, ContactBrowserActivity.class));
listIntent.putExtras(mCreateExtras);
startActivity(listIntent);
finish();
diff --git a/src/com/android/contacts/ui/widget/AggregationSuggestionView.java b/src/com/android/contacts/ui/widget/AggregationSuggestionView.java
new file mode 100644
index 0000000..e03f78b
--- /dev/null
+++ b/src/com/android/contacts/ui/widget/AggregationSuggestionView.java
@@ -0,0 +1,109 @@
+/*
+ * 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.ui.widget;
+
+import com.android.contacts.R;
+import com.android.contacts.views.editor.AggregationSuggestionEngine.Suggestion;
+
+import android.content.Context;
+import android.graphics.BitmapFactory;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import java.util.List;
+
+/**
+ * A view that contains a name, picture and other data for a contact aggregation suggestion.
+ */
+public class AggregationSuggestionView extends RelativeLayout implements OnClickListener {
+
+ public interface Listener {
+
+ /**
+ * Callback that passes the contact ID to join with and, for convenience,
+ * also the list of constituent raw contact IDs to avoid a separate query
+ * for those.
+ */
+ public void onJoinAction(long contactId, List<Long> rawContacIds);
+ }
+
+ private Listener mListener;
+ private long mContactId;
+ private List<Long> mRawContactIds;
+
+ public AggregationSuggestionView(Context context) {
+ super(context);
+ }
+
+ public AggregationSuggestionView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public AggregationSuggestionView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ public void bindSuggestion(Suggestion suggestion, boolean showJoinButton) {
+ mContactId = suggestion.contactId;
+ mRawContactIds = suggestion.rawContactIds;
+ ImageView photo = (ImageView) findViewById(R.id.aggregation_suggestion_photo);
+ if (suggestion.photo != null) {
+ photo.setImageBitmap(BitmapFactory.decodeByteArray(
+ suggestion.photo, 0, suggestion.photo.length));
+ } else {
+ photo.setImageResource(R.drawable.ic_contact_picture_2);
+ }
+
+ TextView name = (TextView) findViewById(R.id.aggregation_suggestion_name);
+ name.setText(suggestion.name);
+
+ TextView data = (TextView) findViewById(R.id.aggregation_suggestion_data);
+ String dataText = null;
+ if (suggestion.nickname != null) {
+ dataText = suggestion.nickname;
+ } else if (suggestion.emailAddress != null) {
+ dataText = suggestion.emailAddress;
+ } else if (suggestion.phoneNumber != null) {
+ dataText = suggestion.phoneNumber;
+ }
+ data.setText(dataText);
+
+ Button join = (Button) findViewById(R.id.aggregation_suggestion_join_button);
+ if (showJoinButton) {
+ join.setOnClickListener(this);
+ join.setVisibility(View.VISIBLE);
+ } else {
+ join.setVisibility(View.INVISIBLE);
+ }
+ }
+
+ public void setListener(Listener listener) {
+ mListener = listener;
+ }
+
+ @Override
+ public void onClick(View v) {
+ if (mListener != null) {
+ mListener.onJoinAction(mContactId, mRawContactIds);
+ }
+ }
+}
diff --git a/src/com/android/contacts/ui/widget/BaseContactEditorView.java b/src/com/android/contacts/ui/widget/BaseContactEditorView.java
index c2f9136..7ee2dac 100644
--- a/src/com/android/contacts/ui/widget/BaseContactEditorView.java
+++ b/src/com/android/contacts/ui/widget/BaseContactEditorView.java
@@ -97,9 +97,4 @@
* apply to that state.
*/
public abstract void setState(EntityDelta state, ContactsSource source, ViewIdGenerator vig);
-
- /**
- * Sets the {@link EditorListener} on the name field
- */
- public abstract void setNameEditorListener(EditorListener listener);
}
diff --git a/src/com/android/contacts/ui/widget/ContactEditorView.java b/src/com/android/contacts/ui/widget/ContactEditorView.java
index 83bf2fb..15d1726 100644
--- a/src/com/android/contacts/ui/widget/ContactEditorView.java
+++ b/src/com/android/contacts/ui/widget/ContactEditorView.java
@@ -25,13 +25,15 @@
import com.android.contacts.model.Editor.EditorListener;
import com.android.contacts.model.EntityDelta.ValuesDelta;
import com.android.contacts.ui.ViewIdGenerator;
+import com.android.contacts.util.DialogManager;
+import com.android.contacts.util.DialogManager.DialogShowingView;
+import android.app.AlertDialog;
+import android.app.Dialog;
import android.content.Context;
+import android.content.DialogInterface;
import android.content.Entity;
-import android.content.res.Resources;
-import android.graphics.drawable.Drawable;
-import android.os.Parcel;
-import android.os.Parcelable;
+import android.os.Bundle;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.RawContacts;
@@ -42,46 +44,45 @@
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.view.View.OnClickListener;
+import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
+import java.util.ArrayList;
+
/**
* Custom view that provides all the editor interaction for a specific
* {@link Contacts} represented through an {@link EntityDelta}. Callers can
* reuse this view and quickly rebuild its contents through
- * {@link #setState(EntityDelta, ContactsSource)}.
+ * {@link #setState(EntityDelta, ContactsSource, ViewIdGenerator)}.
* <p>
* Internal updates are performed against {@link ValuesDelta} so that the
* source {@link Entity} can be swapped out. Any state-based changes, such as
* adding {@link Data} rows or changing {@link EditType}, are performed through
* {@link EntityModifier} to ensure that {@link ContactsSource} are enforced.
*/
-public class ContactEditorView extends BaseContactEditorView implements OnClickListener {
- private TextView mReadOnly;
- private TextView mReadOnlyName;
-
+public class ContactEditorView extends BaseContactEditorView implements DialogShowingView {
private View mPhotoStub;
private GenericEditorView mName;
- private boolean mIsSourceReadOnly;
- private ViewGroup mGeneral;
- private ViewGroup mSecondary;
- private boolean mSecondaryVisible;
+ private ViewGroup mFields;
- private TextView mSecondaryHeader;
-
- private Drawable mSecondaryOpen;
- private Drawable mSecondaryClosed;
-
- private View mHeaderColorBar;
- private View mSideBar;
+ private View mHeader;
+ private View mBody;
private ImageView mHeaderIcon;
private TextView mHeaderAccountType;
private TextView mHeaderAccountName;
+ private Button mAddFieldButton;
+ private boolean mExpanded = true;
+
private long mRawContactId = -1;
+ private DialogManager mDialogManager = null;
+
+ private static final String DIALOG_ID_KEY = "dialog_id";
+ private static final int DIALOG_ID_FIELD_SELECTOR = 1;
+
public ContactEditorView(Context context) {
super(context);
}
@@ -95,66 +96,37 @@
protected void onFinishInflate() {
super.onFinishInflate();
- mInflater = (LayoutInflater)getContext().getSystemService(
- Context.LAYOUT_INFLATER_SERVICE);
+ mInflater = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mPhoto = (PhotoEditorView)findViewById(R.id.edit_photo);
mPhotoStub = findViewById(R.id.stub_photo);
final int photoSize = getResources().getDimensionPixelSize(R.dimen.edit_photo_size);
- mReadOnly = (TextView)findViewById(R.id.edit_read_only);
-
mName = (GenericEditorView)findViewById(R.id.edit_name);
mName.setMinimumHeight(photoSize);
mName.setDeletable(false);
- mReadOnlyName = (TextView) findViewById(R.id.read_only_name);
+ mFields = (ViewGroup)findViewById(R.id.sect_fields);
- mGeneral = (ViewGroup)findViewById(R.id.sect_general);
- mSecondary = (ViewGroup)findViewById(R.id.sect_secondary);
-
- mHeaderColorBar = findViewById(R.id.header_color_bar);
- mSideBar = findViewById(R.id.color_bar);
+ mHeader = findViewById(R.id.header);
+ mBody = findViewById(R.id.body);
+ mHeader.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ setExpanded(!mExpanded, true);
+ }
+ });
mHeaderIcon = (ImageView) findViewById(R.id.header_icon);
mHeaderAccountType = (TextView) findViewById(R.id.header_account_type);
mHeaderAccountName = (TextView) findViewById(R.id.header_account_name);
- mSecondaryHeader = (TextView)findViewById(R.id.head_secondary);
- mSecondaryHeader.setOnClickListener(this);
-
- final Resources res = getResources();
- mSecondaryOpen = res.getDrawable(com.android.internal.R.drawable.expander_ic_maximized);
- mSecondaryClosed = res.getDrawable(com.android.internal.R.drawable.expander_ic_minimized);
-
- this.setSecondaryVisible(false);
- }
-
- /** {@inheritDoc} */
- public void onClick(View v) {
- // Toggle visibility of secondary kinds
- final boolean makeVisible = mSecondary.getVisibility() != View.VISIBLE;
- this.setSecondaryVisible(makeVisible);
- }
-
- /**
- * Set the visibility of secondary sections, along with header icon.
- *
- * <p>If the source is read-only and there's no secondary fields, the entire secondary section
- * will be hidden.
- */
- private void setSecondaryVisible(boolean makeVisible) {
- mSecondaryVisible = makeVisible;
-
- if (!mIsSourceReadOnly && mSecondary.getChildCount() > 0) {
- mSecondaryHeader.setVisibility(View.VISIBLE);
- mSecondaryHeader.setCompoundDrawablesWithIntrinsicBounds(
- makeVisible ? mSecondaryOpen : mSecondaryClosed, null, null, null);
- mSecondary.setVisibility(makeVisible ? View.VISIBLE : View.GONE);
- } else {
- mSecondaryHeader.setVisibility(View.GONE);
- mSecondary.setVisibility(View.GONE);
- }
+ mAddFieldButton = (Button) findViewById(R.id.button_add_field);
+ mAddFieldButton.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ showDialog(DIALOG_ID_FIELD_SELECTOR);
+ }
+ });
}
/**
@@ -165,16 +137,13 @@
@Override
public void setState(EntityDelta state, ContactsSource source, ViewIdGenerator vig) {
// Remove any existing sections
- mGeneral.removeAllViews();
- mSecondary.removeAllViews();
+ mFields.removeAllViews();
// Bail if invalid state or source
if (state == null || source == null) return;
setId(vig.getId(state, null, null, ViewIdGenerator.NO_VIEW_INDEX));
- mIsSourceReadOnly = source.readOnly;
-
// Make sure we have StructuredName
EntityModifier.ensureKindExists(state, source, StructuredName.CONTENT_ITEM_TYPE);
@@ -198,24 +167,13 @@
EntityModifier.ensureKindExists(state, source, Photo.CONTENT_ITEM_TYPE);
mHasPhotoEditor = (source.getKindForMimetype(Photo.CONTENT_ITEM_TYPE) != null);
mPhoto.setVisibility(mHasPhotoEditor ? View.VISIBLE : View.GONE);
- mPhoto.setEnabled(!mIsSourceReadOnly);
- mName.setEnabled(!mIsSourceReadOnly);
+ mPhoto.setEnabled(true);
+ mName.setEnabled(true);
// Show and hide the appropriate views
- if (mIsSourceReadOnly) {
- mGeneral.setVisibility(View.GONE);
- mName.setVisibility(View.GONE);
- mReadOnly.setVisibility(View.VISIBLE);
- mReadOnly.setText(mContext.getString(R.string.contact_read_only, accountType));
- mReadOnlyName.setVisibility(View.VISIBLE);
- } else {
- mGeneral.setVisibility(View.VISIBLE);
- mName.setVisibility(View.VISIBLE);
- mReadOnly.setVisibility(View.GONE);
- mReadOnlyName.setVisibility(View.GONE);
- }
+ mFields.setVisibility(View.VISIBLE);
+ mName.setVisibility(View.VISIBLE);
- boolean anySecondaryFieldFilled = false;
// Create editor sections for each possible data kind
for (DataKind kind : source.getSortedDataKinds()) {
// Skip kind of not editable
@@ -225,44 +183,25 @@
if (StructuredName.CONTENT_ITEM_TYPE.equals(mimeType)) {
// Handle special case editor for structured name
final ValuesDelta primary = state.getPrimaryEntry(mimeType);
- if (!mIsSourceReadOnly) {
- mName.setValues(kind, primary, state, mIsSourceReadOnly, vig);
- } else {
- String displayName = primary.getAsString(StructuredName.DISPLAY_NAME);
- mReadOnlyName.setText(displayName);
- }
+ mName.setValues(kind, primary, state, false, vig);
} else if (Photo.CONTENT_ITEM_TYPE.equals(mimeType)) {
// Handle special case editor for photos
final ValuesDelta primary = state.getPrimaryEntry(mimeType);
- mPhoto.setValues(kind, primary, state, mIsSourceReadOnly, vig);
- if (mIsSourceReadOnly && !mPhoto.hasSetPhoto()) {
- mPhotoStub.setVisibility(View.GONE);
- } else {
- mPhotoStub.setVisibility(View.VISIBLE);
- }
- } else if (!mIsSourceReadOnly) {
+ mPhoto.setValues(kind, primary, state, false, vig);
+ mPhotoStub.setVisibility(View.VISIBLE);
+ } else {
// Otherwise use generic section-based editors
if (kind.fieldList == null) continue;
- final ViewGroup parent = kind.secondary ? mSecondary : mGeneral;
final KindSectionView section = (KindSectionView)mInflater.inflate(
- R.layout.item_kind_section, parent, false);
- section.setState(kind, state, mIsSourceReadOnly, vig);
- if (kind.secondary && section.isAnyEditorFilledOut()) {
- anySecondaryFieldFilled = true;
- }
- parent.addView(section);
+ R.layout.item_kind_section, mFields, false);
+ section.setState(kind, state, false, vig);
+ mFields.addView(section);
}
}
-
- setSecondaryVisible(anySecondaryFieldFilled);
}
- /**
- * Sets the {@link EditorListener} on the name field
- */
- @Override
- public void setNameEditorListener(EditorListener listener) {
- mName.setEditorListener(listener);
+ public GenericEditorView getNameEditor() {
+ return mName;
}
@Override
@@ -270,56 +209,63 @@
return mRawContactId;
}
- private static class SavedState extends BaseSavedState {
- public boolean mSecondaryVisible;
+ /* package */ void setExpanded(boolean value, boolean animate) {
+ if (value == mExpanded) return;
- SavedState(Parcelable superState) {
- super(superState);
- }
-
- private SavedState(Parcel in) {
- super(in);
- mSecondaryVisible = (in.readInt() == 0 ? false : true);
- }
-
- @Override
- public void writeToParcel(Parcel out, int flags) {
- super.writeToParcel(out, flags);
- out.writeInt(mSecondaryVisible ? 1 : 0);
- }
-
- public static final Parcelable.Creator<SavedState> CREATOR
- = new Parcelable.Creator<SavedState>() {
- public SavedState createFromParcel(Parcel in) {
- return new SavedState(in);
- }
-
- public SavedState[] newArray(int size) {
- return new SavedState[size];
- }
- };
+ mExpanded = value;
+ mBody.setVisibility(value ? View.VISIBLE : View.GONE);
}
- /**
- * Saves the visibility of the secondary field.
- */
- @Override
- protected Parcelable onSaveInstanceState() {
- Parcelable superState = super.onSaveInstanceState();
- SavedState ss = new SavedState(superState);
-
- ss.mSecondaryVisible = mSecondaryVisible;
- return ss;
+ /* package */ void showDialog(int bundleDialogId) {
+ final Bundle bundle = new Bundle();
+ bundle.putInt(DIALOG_ID_KEY, bundleDialogId);
+ getDialogManager().showDialogInView(this, bundle);
}
- /**
- * Restores the visibility of the secondary field.
- */
- @Override
- protected void onRestoreInstanceState(Parcelable state) {
- SavedState ss = (SavedState) state;
- super.onRestoreInstanceState(ss.getSuperState());
+ private DialogManager getDialogManager() {
+ if (mDialogManager == null) {
+ Context context = getContext();
+ if (!(context instanceof DialogManager.DialogShowingViewActivity)) {
+ throw new IllegalStateException(
+ "View must be hosted in an Activity that implements " +
+ "DialogManager.DialogShowingViewActivity");
+ }
+ mDialogManager = ((DialogManager.DialogShowingViewActivity)context).getDialogManager();
+ }
+ return mDialogManager;
+ }
- setSecondaryVisible(ss.mSecondaryVisible);
+ public Dialog createDialog(Bundle bundle) {
+ if (bundle == null) throw new IllegalArgumentException("bundle must not be null");
+ final int dialogId = bundle.getInt(DIALOG_ID_KEY);
+ switch (dialogId) {
+ case DIALOG_ID_FIELD_SELECTOR:
+ final ArrayList<KindSectionView> usedFields =
+ new ArrayList<KindSectionView>(mFields.getChildCount());
+ final ArrayList<CharSequence> usedFieldTitles =
+ new ArrayList<CharSequence>(mFields.getChildCount());
+
+ for (int i = 0; i < mFields.getChildCount(); i++) {
+ final KindSectionView sectionView = (KindSectionView) mFields.getChildAt(i);
+ // not a list and already exists? ignore
+ if (!sectionView.getKind().isList && sectionView.getEditorCount() != 0) {
+ continue;
+ }
+ usedFieldTitles.add(sectionView.getTitle());
+ usedFields.add(sectionView);
+ }
+ final DialogInterface.OnClickListener itemClickListener =
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ final KindSectionView view = usedFields.get(which);
+ view.addItem();
+ }
+ };
+ return new AlertDialog.Builder(getContext())
+ .setItems(usedFieldTitles.toArray(new CharSequence[0]), itemClickListener)
+ .create();
+ default:
+ throw new IllegalArgumentException("Invalid dialogId: " + dialogId);
+ }
}
}
diff --git a/src/com/android/contacts/ui/widget/DontPressWithParentImageView.java b/src/com/android/contacts/ui/widget/DontPressWithParentImageView.java
deleted file mode 100644
index bdb0e0a..0000000
--- a/src/com/android/contacts/ui/widget/DontPressWithParentImageView.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2009 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.ui.widget;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.ImageView;
-
-/**
- * Special class to to allow the parent to be pressed without being pressed itself.
- * This way the line of a tab can be pressed, but the image itself is not.
- */
-public class DontPressWithParentImageView extends ImageView {
-
- public DontPressWithParentImageView(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- public void setPressed(boolean pressed) {
- // If the parent is pressed, do not set to pressed.
- if (pressed && ((View) getParent()).isPressed()) {
- return;
- }
- super.setPressed(pressed);
- }
-}
diff --git a/src/com/android/contacts/ui/widget/GenericEditorView.java b/src/com/android/contacts/ui/widget/GenericEditorView.java
index 24262bb..6be238f 100644
--- a/src/com/android/contacts/ui/widget/GenericEditorView.java
+++ b/src/com/android/contacts/ui/widget/GenericEditorView.java
@@ -18,37 +18,42 @@
import com.android.contacts.ContactsUtils;
import com.android.contacts.R;
-import com.android.contacts.model.Editor;
-import com.android.contacts.model.EntityDelta;
-import com.android.contacts.model.EntityModifier;
import com.android.contacts.model.ContactsSource.DataKind;
import com.android.contacts.model.ContactsSource.EditField;
import com.android.contacts.model.ContactsSource.EditType;
+import com.android.contacts.model.Editor;
+import com.android.contacts.model.EntityDelta;
import com.android.contacts.model.EntityDelta.ValuesDelta;
+import com.android.contacts.model.EntityModifier;
import com.android.contacts.ui.ViewIdGenerator;
+import com.android.contacts.util.DialogManager;
+import com.android.contacts.util.DialogManager.DialogShowingView;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Entity;
+import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.telephony.PhoneNumberFormattingTextWatcher;
import android.text.Editable;
import android.text.InputType;
-import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.view.ContextThemeWrapper;
+import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.widget.ArrayAdapter;
+import android.widget.Button;
import android.widget.EditText;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
import android.widget.ListAdapter;
-import android.widget.RelativeLayout;
import android.widget.TextView;
import java.util.List;
@@ -58,33 +63,36 @@
* the entry. Uses {@link ValuesDelta} to read any existing
* {@link Entity} values, and to correctly write any changes values.
*/
-public class GenericEditorView extends RelativeLayout implements Editor, View.OnClickListener {
- protected static final int RES_FIELD = R.layout.item_editor_field;
- protected static final int RES_LABEL_ITEM = android.R.layout.simple_list_item_1;
+public class GenericEditorView extends ViewGroup implements Editor, DialogShowingView {
+ private static final int RES_LABEL_ITEM = android.R.layout.simple_list_item_1;
- protected LayoutInflater mInflater;
+ private static final String DIALOG_ID_KEY = "dialog_id";
+ private static final int DIALOG_ID_LABEL = 1;
+ private static final int DIALOG_ID_CUSTOM = 2;
- protected static final int INPUT_TYPE_CUSTOM = EditorInfo.TYPE_CLASS_TEXT
+ private static final int INPUT_TYPE_CUSTOM = EditorInfo.TYPE_CLASS_TEXT
| EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
- protected TextView mLabel;
- protected ViewGroup mFields;
- protected View mDelete;
- protected View mMore;
- protected View mLess;
+ private Button mLabel;
+ private ImageButton mDelete;
+ private ImageButton mMoreOrLess;
- protected DataKind mKind;
- protected ValuesDelta mEntry;
- protected EntityDelta mState;
- protected boolean mReadOnly;
+ private DataKind mKind;
+ private ValuesDelta mEntry;
+ private EntityDelta mState;
+ private boolean mReadOnly;
+ private EditText[] mFieldEditTexts = null;
- protected boolean mHideOptional = true;
+ private boolean mHideOptional = true;
- protected EditType mType;
+ private EditType mType;
// Used only when a user tries to use custom label.
private EditType mPendingType;
private ViewIdGenerator mViewIdGenerator;
+ private DialogManager mDialogManager = null;
+ private EditorListener mListener;
+
public GenericEditorView(Context context) {
super(context);
@@ -94,47 +102,245 @@
super(context, attrs);
}
- /** {@inheritDoc} */
- @Override
- protected void onFinishInflate() {
- mInflater = (LayoutInflater)getContext().getSystemService(
- Context.LAYOUT_INFLATER_SERVICE);
-
- mLabel = (TextView)findViewById(R.id.edit_label);
- mLabel.setOnClickListener(this);
-
- mFields = (ViewGroup)findViewById(R.id.edit_fields);
-
- mDelete = findViewById(R.id.edit_delete);
- mDelete.setOnClickListener(this);
-
- mMore = findViewById(R.id.edit_more);
- mMore.setOnClickListener(this);
-
- mLess = findViewById(R.id.edit_less);
- mLess.setOnClickListener(this);
+ public GenericEditorView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
}
- protected EditorListener mListener;
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ // Subtract padding from the borders ==> x1 variables
+ int l1 = getPaddingLeft();
+ int t1 = getPaddingTop();
+ int r1 = getMeasuredWidth() - getPaddingRight();
+ int b1 = getMeasuredHeight() - getPaddingBottom();
+
+ // Label Button
+ final boolean hasLabel = mLabel != null;
+
+ if (hasLabel) {
+ mLabel.layout(
+ l1, t1,
+ l1 + mLabel.getMeasuredWidth(), t1 + mLabel.getMeasuredHeight());
+ }
+
+ // Delete Button
+ final boolean hasDelete = mDelete != null;
+ if (hasDelete) {
+ mDelete.layout(
+ r1 - mDelete.getMeasuredWidth(), t1,
+ r1, t1 + mDelete.getMeasuredHeight());
+ }
+
+ // MoreOrLess Button
+ final boolean hasMoreOrLess = mMoreOrLess != null;
+ if (hasMoreOrLess) {
+ mMoreOrLess.layout(
+ r1 - mMoreOrLess.getMeasuredWidth(), b1 - mMoreOrLess.getMeasuredHeight(),
+ r1, b1);
+ }
+
+ // Fields
+ // Subtract buttons left and right if necessary
+ final int l2 = hasLabel ? l1 + mLabel.getMeasuredWidth() : l1;
+ final int r2 = r1 - Math.max(
+ hasDelete ? mDelete.getMeasuredWidth() : 0,
+ hasMoreOrLess ? mMoreOrLess.getMeasuredWidth() : 0);
+ int y = 0;
+ if (mFieldEditTexts != null) {
+ for (EditText editText : mFieldEditTexts) {
+ if (editText.getVisibility() != View.GONE) {
+ int height = editText.getMeasuredHeight();
+ editText.layout(
+ l2, t1 + y,
+ r2, t1 + y + height);
+ y += height;
+ }
+ }
+ }
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ measureChildren(widthMeasureSpec, heightMeasureSpec);
+
+ // summarize the EditText heights
+ int totalHeight = 0;
+ int visibleFieldCount = 0;
+ EditText firstVisibleField = null;
+ if (mFieldEditTexts != null) {
+ for (EditText editText : mFieldEditTexts) {
+ if (editText.getVisibility() != View.GONE) {
+ visibleFieldCount ++;
+ if (firstVisibleField == null) {
+ firstVisibleField = editText;
+ }
+ totalHeight += editText.getMeasuredHeight();
+ }
+ }
+ }
+
+ int padding = getPaddingTop() + getPaddingBottom();
+ int minHeight = padding;
+
+ if (mMoreOrLess != null) {
+ minHeight += mMoreOrLess.getMeasuredHeight();
+ }
+
+ if (mDelete != null) {
+ minHeight += mDelete.getMeasuredHeight();
+ }
+
+ if (minHeight > totalHeight && visibleFieldCount == 1) {
+ firstVisibleField.measure(widthMeasureSpec,
+ MeasureSpec.makeMeasureSpec(minHeight - padding, MeasureSpec.EXACTLY));
+ }
+
+ totalHeight = Math.max(minHeight, totalHeight);
+
+ setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
+ resolveSize(totalHeight, heightMeasureSpec));
+ }
+
+ /**
+ * Creates or removes the type/label button. Doesn't do anything if already correctly configured
+ */
+ private void setupLabelButton(boolean shouldExist) {
+ // TODO: Unhardcode the constant 100
+ if (shouldExist && mLabel == null) {
+ mLabel = new Button(mContext);
+ mLabel.setLayoutParams(new LayoutParams(100, LayoutParams.WRAP_CONTENT));
+ mLabel.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ showDialog(DIALOG_ID_LABEL);
+ }
+ });
+ addView(mLabel);
+ } else if (!shouldExist && mLabel != null) {
+ removeView(mLabel);
+ mLabel = null;
+ }
+ }
+
+ /**
+ * Creates or removes the type/label button. Doesn't do anything if already correctly configured
+ */
+ private void setupDeleteButton(boolean shouldExist) {
+ if (shouldExist && mDelete == null) {
+ // Unfortunately, the style passed as constructor-parameter is mostly ignored,
+ // so we have to set the Background and Image seperately. However, if it is not given
+ // the size of the control is wrong
+ mDelete = new ImageButton(mContext, null, R.style.MinusButton);
+ mDelete.setBackgroundResource(R.drawable.btn_circle);
+ mDelete.setImageResource(R.drawable.ic_btn_round_minus);
+ mDelete.setContentDescription(
+ getResources().getText(R.string.description_minus_button));
+ mDelete.setLayoutParams(
+ new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
+ mDelete.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // Keep around in model, but mark as deleted
+ mEntry.markDeleted();
+
+// final ViewGroupAnimator animator = ViewGroupAnimator.captureView(getRootView());
+
+// animator.removeView(GenericEditorView.this);
+ ((ViewGroup) getParent()).removeView(GenericEditorView.this);
+
+ if (mListener != null) {
+ // Notify listener when present
+ mListener.onDeleted(GenericEditorView.this);
+ }
+
+// animator.animate();
+ }
+ });
+ addView(mDelete);
+ } else if (!shouldExist && mDelete != null) {
+ removeView(mDelete);
+ mDelete = null;
+ }
+ }
+
+ /**
+ * Creates or removes the type/label button. Doesn't do anything if already correctly configured
+ */
+ private void setupMoreOrLessButton(boolean shouldExist, boolean collapsed) {
+ if (shouldExist) {
+ if (mMoreOrLess == null) {
+ // Unfortunately, the style passed as constructor-parameter is mostly ignored,
+ // so we have to set the Background and Image seperately. However, if it is not
+ // given, the size of the control is wrong
+ mMoreOrLess = new ImageButton(mContext, null, R.style.EmptyButton);
+ mMoreOrLess.setLayoutParams(
+ new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
+ mMoreOrLess.setBackgroundResource(R.drawable.btn_circle);
+ mMoreOrLess.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // Save focus
+ final View focusedChild = getFocusedChild();
+ final int focusedViewId = focusedChild == null ? -1 : focusedChild.getId();
+
+ // Reconfigure GUI
+ mHideOptional = !mHideOptional;
+ onOptionalFieldVisibilityChange();
+ rebuildValues();
+
+ // Restore focus
+ View newFocusView = findViewById(focusedViewId);
+ if (newFocusView == null || newFocusView.getVisibility() == GONE) {
+ // find first visible child
+ newFocusView = GenericEditorView.this;
+ }
+ if (newFocusView != null) {
+ newFocusView.requestFocus();
+ }
+ }
+ });
+ addView(mMoreOrLess);
+ }
+ mMoreOrLess.setImageResource(
+ collapsed ? R.drawable.ic_btn_round_more : R.drawable.ic_btn_round_less);
+ } else if (mMoreOrLess != null) {
+ removeView(mMoreOrLess);
+ mMoreOrLess = null;
+ }
+ }
+
+ protected void onOptionalFieldVisibilityChange() {
+ if (mListener != null) {
+ mListener.onRequest(EditorListener.EDITOR_FORM_CHANGED);
+ }
+ }
public void setEditorListener(EditorListener listener) {
mListener = listener;
}
public void setDeletable(boolean deletable) {
- mDelete.setVisibility(deletable ? View.VISIBLE : View.INVISIBLE);
+ setupDeleteButton(deletable);
}
@Override
public void setEnabled(boolean enabled) {
- mLabel.setEnabled(enabled);
- final int count = mFields.getChildCount();
- for (int pos = 0; pos < count; pos++) {
- final View v = mFields.getChildAt(pos);
- v.setEnabled(enabled);
+ if (mLabel != null) mLabel.setEnabled(enabled);
+
+ if (mFieldEditTexts != null) {
+ for (int index = 0; index < mFieldEditTexts.length; index++) {
+ mFieldEditTexts[index].setEnabled(enabled);
+ }
}
- mMore.setEnabled(enabled);
- mLess.setEnabled(enabled);
+ if (mDelete != null) mDelete.setEnabled(enabled);
+ if (mMoreOrLess != null) mMoreOrLess.setEnabled(enabled);
+ }
+
+ /**
+ * Returns true if the editor is currently configured to show optional fields.
+ */
+ public boolean areOptionalFieldsVisible() {
+ return !mHideOptional;
}
/**
@@ -142,6 +348,7 @@
* possible custom label string.
*/
private void rebuildLabel() {
+ if (mLabel == null) return;
// Handle undetected types
if (mType == null) {
mLabel.setText(R.string.unknown);
@@ -170,17 +377,6 @@
}
}
- public boolean isAnyFieldFilledOut() {
- int childCount = mFields.getChildCount();
- for (int i = 0; i < childCount; i++) {
- EditText editorView = (EditText) mFields.getChildAt(i);
- if (!TextUtils.isEmpty(editorView.getText())) {
- return true;
- }
- }
- return false;
- }
-
private void rebuildValues() {
setValues(mKind, mEntry, mState, mReadOnly, mViewIdGenerator);
}
@@ -210,28 +406,39 @@
// Display label selector if multiple types available
final boolean hasTypes = EntityModifier.hasEditTypes(kind);
- mLabel.setVisibility(hasTypes ? View.VISIBLE : View.GONE);
- mLabel.setEnabled(enabled);
+ setupLabelButton(hasTypes);
+ if (mLabel != null) mLabel.setEnabled(enabled);
if (hasTypes) {
mType = EntityModifier.getCurrentType(entry, kind);
rebuildLabel();
}
- // Build out set of fields
- mFields.removeAllViews();
+ // Remove edit texts that we currently have
+ if (mFieldEditTexts != null) {
+ for (EditText fieldEditText : mFieldEditTexts) {
+ removeView(fieldEditText);
+ }
+ }
boolean hidePossible = false;
- int n = 0;
- for (EditField field : kind.fieldList) {
- // Inflate field from definition
- EditText fieldView = (EditText)mInflater.inflate(RES_FIELD, mFields, false);
- fieldView.setId(vig.getId(state, kind, entry, n++));
+
+ int fieldCount = kind.fieldList.size();
+ mFieldEditTexts = new EditText[fieldCount];
+ for (int index = 0; index < fieldCount; index++) {
+ final EditField field = kind.fieldList.get(index);
+ final EditText fieldView = new EditText(mContext);
+ fieldView.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT,
+ LayoutParams.WRAP_CONTENT));
+ fieldView.setGravity(Gravity.TOP);
+ mFieldEditTexts[index] = fieldView;
+ fieldView.setId(vig.getId(state, kind, entry, index));
if (field.titleRes > 0) {
fieldView.setHint(field.titleRes);
}
int inputType = field.inputType;
fieldView.setInputType(inputType);
if (inputType == InputType.TYPE_CLASS_PHONE) {
- fieldView.addTextChangedListener(new PhoneNumberFormattingTextWatcher());
+ fieldView.addTextChangedListener(new PhoneNumberFormattingTextWatcher(
+ ContactsUtils.getCurrentCountryIso(mContext)));
}
fieldView.setMinLines(field.minLines);
@@ -254,26 +461,32 @@
}
});
- // Hide field when empty and optional value
- final boolean couldHide = (!ContactsUtils.isGraphic(value) && field.optional);
- final boolean willHide = (mHideOptional && couldHide);
- fieldView.setVisibility(willHide ? View.GONE : View.VISIBLE);
fieldView.setEnabled(enabled);
- hidePossible = hidePossible || couldHide;
- mFields.addView(fieldView);
+ if (field.shortForm) {
+ hidePossible = true;
+ fieldView.setVisibility(mHideOptional ? View.VISIBLE : View.GONE);
+ } else if (field.longForm) {
+ hidePossible = true;
+ fieldView.setVisibility(mHideOptional ? View.GONE : View.VISIBLE);
+ } else {
+ // Hide field when empty and optional value
+ final boolean couldHide = (!ContactsUtils.isGraphic(value) && field.optional);
+ final boolean willHide = (mHideOptional && couldHide);
+ fieldView.setVisibility(willHide ? View.GONE : View.VISIBLE);
+ hidePossible = hidePossible || couldHide;
+ }
+
+ addView(fieldView);
}
// When hiding fields, place expandable
- if (hidePossible) {
- mMore.setVisibility(mHideOptional ? View.VISIBLE : View.GONE);
- mLess.setVisibility(mHideOptional ? View.GONE : View.VISIBLE);
- } else {
- mMore.setVisibility(View.GONE);
- mLess.setVisibility(View.GONE);
- }
- mMore.setEnabled(enabled);
- mLess.setEnabled(enabled);
+ setupMoreOrLessButton(hidePossible, mHideOptional);
+ if (mMoreOrLess != null) mMoreOrLess.setEnabled(enabled);
+ }
+
+ public ValuesDelta getValues() {
+ return mEntry;
}
/**
@@ -302,8 +515,7 @@
mEntry.put(mKind.typeColumn, mType.rawValue);
mEntry.put(mType.customColumn, customText);
rebuildLabel();
- if (!mFields.hasFocus())
- mFields.requestFocus();
+ requestFocusForFirstEditField();
}
}
});
@@ -325,7 +537,8 @@
// Wrap our context to inflate list items using correct theme
final Context dialogContext = new ContextThemeWrapper(mContext,
android.R.style.Theme_Light);
- final LayoutInflater dialogInflater = mInflater.cloneInContext(dialogContext);
+ final LayoutInflater dialogInflater = (LayoutInflater) dialogContext.getSystemService(
+ Context.LAYOUT_INFLATER_SERVICE);
final ListAdapter typeAdapter = new ArrayAdapter<EditType>(mContext, RES_LABEL_ITEM,
validTypes) {
@@ -354,14 +567,13 @@
// Only when the custum value input in the next step is correct one.
// this method also set the type value to what the user requested here.
mPendingType = selected;
- createCustomDialog().show();
+ showDialog(DIALOG_ID_CUSTOM);
} else {
// User picked type, and we're sure it's ok to actually write the entry.
mType = selected;
mEntry.put(mKind.typeColumn, mType.rawValue);
rebuildLabel();
- if (!mFields.hasFocus())
- mFields.requestFocus();
+ requestFocusForFirstEditField();
}
}
};
@@ -372,34 +584,24 @@
return builder.create();
}
- /** {@inheritDoc} */
- public void onClick(View v) {
- switch (v.getId()) {
- case R.id.edit_label: {
- createLabelDialog().show();
- break;
- }
- case R.id.edit_delete: {
- // Keep around in model, but mark as deleted
- mEntry.markDeleted();
+ /* package */
+ void showDialog(int bundleDialogId) {
+ Bundle bundle = new Bundle();
+ bundle.putInt(DIALOG_ID_KEY, bundleDialogId);
+ getDialogManager().showDialogInView(this, bundle);
+ }
- // Remove editor from parent view
- final ViewGroup parent = (ViewGroup)getParent();
- parent.removeView(this);
-
- if (mListener != null) {
- // Notify listener when present
- mListener.onDeleted(this);
- }
- break;
+ private DialogManager getDialogManager() {
+ if (mDialogManager == null) {
+ Context context = getContext();
+ if (!(context instanceof DialogManager.DialogShowingViewActivity)) {
+ throw new IllegalStateException(
+ "View must be hosted in an Activity that implements " +
+ "DialogManager.DialogShowingViewActivity");
}
- case R.id.edit_more:
- case R.id.edit_less: {
- mHideOptional = !mHideOptional;
- rebuildValues();
- break;
- }
+ mDialogManager = ((DialogManager.DialogShowingViewActivity)context).getDialogManager();
}
+ return mDialogManager;
}
private static class SavedState extends BaseSavedState {
@@ -445,10 +647,10 @@
ss.mHideOptional = mHideOptional;
- final int numChildren = mFields.getChildCount();
+ final int numChildren = mFieldEditTexts.length;
ss.mVisibilities = new int[numChildren];
for (int i = 0; i < numChildren; i++) {
- ss.mVisibilities[i] = mFields.getChildAt(i).getVisibility();
+ ss.mVisibilities[i] = mFieldEditTexts[i].getVisibility();
}
return ss;
@@ -464,9 +666,35 @@
mHideOptional = ss.mHideOptional;
- int numChildren = Math.min(mFields.getChildCount(), ss.mVisibilities.length);
+ int numChildren = Math.min(mFieldEditTexts.length, ss.mVisibilities.length);
for (int i = 0; i < numChildren; i++) {
- mFields.getChildAt(i).setVisibility(ss.mVisibilities[i]);
+ mFieldEditTexts[i].setVisibility(ss.mVisibilities[i]);
+ }
+ }
+
+ public Dialog createDialog(Bundle bundle) {
+ if (bundle == null) throw new IllegalArgumentException("bundle must not be null");
+ int dialogId = bundle.getInt(DIALOG_ID_KEY);
+ switch (dialogId) {
+ case DIALOG_ID_CUSTOM:
+ return createCustomDialog();
+ case DIALOG_ID_LABEL:
+ return createLabelDialog();
+ default:
+ throw new IllegalArgumentException("Invalid dialogId: " + dialogId);
+ }
+ }
+
+ private void requestFocusForFirstEditField() {
+ if (mFieldEditTexts != null && mFieldEditTexts.length != 0) {
+ boolean anyFieldHasFocus = false;
+ for (EditText editText : mFieldEditTexts) {
+ if (editText.hasFocus()) {
+ anyFieldHasFocus = true;
+ }
+ }
+ if (!anyFieldHasFocus)
+ mFieldEditTexts[0].requestFocus();
}
}
}
diff --git a/src/com/android/contacts/ui/widget/KindSectionView.java b/src/com/android/contacts/ui/widget/KindSectionView.java
index 221bc16..cd0b6fb 100644
--- a/src/com/android/contacts/ui/widget/KindSectionView.java
+++ b/src/com/android/contacts/ui/widget/KindSectionView.java
@@ -17,21 +17,23 @@
package com.android.contacts.ui.widget;
import com.android.contacts.R;
-import com.android.contacts.model.Editor;
-import com.android.contacts.model.EntityDelta;
-import com.android.contacts.model.EntityModifier;
import com.android.contacts.model.ContactsSource.DataKind;
+import com.android.contacts.model.Editor;
import com.android.contacts.model.Editor.EditorListener;
+import com.android.contacts.model.EntityDelta;
import com.android.contacts.model.EntityDelta.ValuesDelta;
+import com.android.contacts.model.EntityModifier;
import com.android.contacts.ui.ViewIdGenerator;
import android.content.Context;
import android.provider.ContactsContract.Data;
+import android.text.TextUtils;
import android.util.AttributeSet;
-import android.view.LayoutInflater;
+import android.util.DisplayMetrics;
+import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
-import android.view.View.OnClickListener;
+import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
@@ -41,10 +43,9 @@
* {@link DataKind} around a {@link Data#MIMETYPE}. This view shows a
* section header and a trigger for adding new {@link Data} rows.
*/
-public class KindSectionView extends LinearLayout implements OnClickListener, EditorListener {
+public class KindSectionView extends LinearLayout implements EditorListener {
private static final String TAG = "KindSectionView";
-
- private LayoutInflater mInflater;
+ private static int sCachedThemePaddingRight = -1;
private ViewGroup mEditors;
private View mAdd;
@@ -68,16 +69,17 @@
/** {@inheritDoc} */
@Override
protected void onFinishInflate() {
- mInflater = (LayoutInflater)getContext().getSystemService(
- Context.LAYOUT_INFLATER_SERVICE);
-
setDrawingCacheEnabled(true);
setAlwaysDrawnWithCacheEnabled(true);
mEditors = (ViewGroup)findViewById(R.id.kind_editors);
mAdd = findViewById(R.id.kind_header);
- mAdd.setOnClickListener(this);
+ mAdd.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ addItem();
+ }
+ });
mAddPlusButton = (ImageView) findViewById(R.id.kind_plus);
@@ -86,8 +88,8 @@
/** {@inheritDoc} */
public void onDeleted(Editor editor) {
- this.updateAddEnabled();
- this.updateEditorsVisible();
+ updateAddEnabled();
+ updateVisible();
}
/** {@inheritDoc} */
@@ -109,29 +111,13 @@
// Only show the add button if this is a list
mAddPlusButton.setVisibility(mKind.isList ? View.VISIBLE : View.GONE);
- this.rebuildFromState();
- this.updateAddEnabled();
- this.updateEditorsVisible();
+ rebuildFromState();
+ updateAddEnabled();
+ updateVisible();
}
- public boolean isAnyEditorFilledOut() {
- if (mState == null) {
- return false;
- }
-
- if (!mState.hasMimeEntries(mKind.mimeType)) {
- return false;
- }
-
- int editorCount = mEditors.getChildCount();
- for (int i = 0; i < editorCount; i++) {
- GenericEditorView editorView = (GenericEditorView) mEditors.getChildAt(i);
- if (editorView.isAnyFieldFilledOut()) {
- return true;
- }
- }
-
- return false;
+ public CharSequence getTitle() {
+ return mTitle.getText();
}
/**
@@ -144,49 +130,75 @@
// Check if we are displaying anything here
boolean hasEntries = mState.hasMimeEntries(mKind.mimeType);
- if (!mKind.isList) {
- if (hasEntries) {
- // we might have no visible entries. check that, too
- for (ValuesDelta entry : mState.getMimeEntries(mKind.mimeType)) {
- if (!entry.isVisible()) {
- hasEntries = false;
- break;
- }
- }
- }
-
- if (!hasEntries) {
- EntityModifier.insertChild(mState, mKind);
- hasEntries = true;
- }
- }
-
if (hasEntries) {
int entryIndex = 0;
for (ValuesDelta entry : mState.getMimeEntries(mKind.mimeType)) {
// Skip entries that aren't visible
if (!entry.isVisible()) continue;
+ if (isEmptyNoop(entry)) continue;
- final GenericEditorView editor = (GenericEditorView)mInflater.inflate(
- R.layout.item_generic_editor, mEditors, false);
- editor.setValues(mKind, entry, mState, mReadOnly, mViewIdGenerator);
- // older versions of android had lists where we now have a single value
- // in these cases we should show the remove button for all but the first value
- // to ensure that nothing is removed
- editor.mDelete.setVisibility((mKind.isList || (entryIndex != 0))
- ? View.VISIBLE : View.GONE);
- editor.setEditorListener(this);
- mEditors.addView(editor);
+ final View view;
+ if (mKind.editorClass == null) {
+ view = new GenericEditorView(mContext);
+ } else {
+ try {
+ view = mKind.editorClass.getConstructor(Context.class).newInstance(
+ mContext);
+ } catch (Exception e) {
+ throw new RuntimeException(
+ "Cannot allocate editor for " + mKind.editorClass);
+ }
+ }
+
+ view.setPadding(0, 0, getThemeScrollbarSize(mContext), 0);
+ if (view instanceof Editor) {
+ Editor editor = (Editor) view;
+ editor.setValues(mKind, entry, mState, mReadOnly, mViewIdGenerator);
+ editor.setEditorListener(this);
+ editor.setDeletable(true);
+ }
+ mEditors.addView(view);
entryIndex++;
}
}
}
- protected void updateEditorsVisible() {
- final boolean hasChildren = mEditors.getChildCount() > 0;
- mEditors.setVisibility(hasChildren ? View.VISIBLE : View.GONE);
+ /**
+ * Tests whether the given item has no changes (so it exists in the database) but is empty
+ */
+ private boolean isEmptyNoop(ValuesDelta item) {
+ if (!item.isNoop()) return false;
+ final int fieldCount = mKind.fieldList.size();
+ for (int i = 0; i < fieldCount; i++) {
+ final String column = mKind.fieldList.get(i).column;
+ final String value = item.getAsString(column);
+ if (!TextUtils.isEmpty(value)) return false;
+ }
+ return true;
}
+ /**
+ * Reads the scrollbarSize of the current theme
+ */
+ private static int getThemeScrollbarSize(Context context) {
+ if (sCachedThemePaddingRight == -1) {
+ final TypedValue outValue = new TypedValue();
+ context.getTheme().resolveAttribute(android.R.attr.scrollbarSize, outValue, true);
+ final WindowManager windowManager =
+ (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ final DisplayMetrics metrics = new DisplayMetrics();
+ windowManager.getDefaultDisplay().getMetrics(metrics);
+ sCachedThemePaddingRight = (int) TypedValue.complexToDimension(outValue.data, metrics);
+ }
+
+ return sCachedThemePaddingRight;
+ }
+
+ private void updateVisible() {
+ setVisibility(getEditorCount() != 0 ? VISIBLE : GONE);
+ }
+
+
protected void updateAddEnabled() {
// Set enabled state on the "add" view
final boolean canInsert = EntityModifier.canInsert(mState, mKind);
@@ -194,18 +206,15 @@
mAdd.setEnabled(isEnabled);
}
- /** {@inheritDoc} */
- public void onClick(View v) {
- // if this is not a list the plus button is not visible but the user might have clicked
- // the text.
- if (!mKind.isList)
+ public void addItem() {
+ // if this is a list, we can freely add. if not, only allow adding the first
+ if (!mKind.isList && getEditorCount() == 1)
return;
// Insert a new child and rebuild
final ValuesDelta newValues = EntityModifier.insertChild(mState, mKind);
- this.rebuildFromState();
- this.updateAddEnabled();
- this.updateEditorsVisible();
+ rebuildFromState();
+ updateAddEnabled();
// Find the newly added EditView and set focus.
final int newFieldId = mViewIdGenerator.getId(mState, mKind, newValues, 0);
@@ -213,5 +222,15 @@
if (newField != null) {
newField.requestFocus();
}
+
+ updateVisible();
+ }
+
+ public int getEditorCount() {
+ return mEditors.getChildCount();
+ }
+
+ public DataKind getKind() {
+ return mKind;
}
}
diff --git a/src/com/android/contacts/ui/widget/PhotoEditorView.java b/src/com/android/contacts/ui/widget/PhotoEditorView.java
index eff39d0..da1be85 100644
--- a/src/com/android/contacts/ui/widget/PhotoEditorView.java
+++ b/src/com/android/contacts/ui/widget/PhotoEditorView.java
@@ -168,4 +168,9 @@
public void setEditorListener(EditorListener listener) {
mListener = listener;
}
+
+ @Override
+ public void setDeletable(boolean deletable) {
+ // Photo is not deletable
+ }
}
diff --git a/src/com/android/contacts/ui/widget/ReadOnlyContactEditorView.java b/src/com/android/contacts/ui/widget/ReadOnlyContactEditorView.java
index 4635f6a..011bcb1 100644
--- a/src/com/android/contacts/ui/widget/ReadOnlyContactEditorView.java
+++ b/src/com/android/contacts/ui/widget/ReadOnlyContactEditorView.java
@@ -191,14 +191,6 @@
}
}
- /**
- * Sets the {@link EditorListener} on the name field
- */
- @Override
- public void setNameEditorListener(EditorListener listener) {
- // do nothing
- }
-
@Override
public long getRawContactId() {
return mRawContactId;
diff --git a/src/com/android/contacts/util/AccountSelectionUtil.java b/src/com/android/contacts/util/AccountSelectionUtil.java
index cc46d2b..19e21bc 100644
--- a/src/com/android/contacts/util/AccountSelectionUtil.java
+++ b/src/com/android/contacts/util/AccountSelectionUtil.java
@@ -16,18 +16,13 @@
package com.android.contacts.util;
-import com.android.contacts.ImportVCardActivity;
-import com.android.contacts.R;
-import com.android.contacts.model.ContactsSource;
-import com.android.contacts.model.GoogleSource;
-import com.android.contacts.model.Sources;
-
import android.accounts.Account;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
+import android.net.Uri;
import android.util.Log;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
@@ -35,7 +30,10 @@
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;
-import android.net.Uri;
+
+import com.android.contacts.R;
+import com.android.contacts.model.ContactsSource;
+import com.android.contacts.model.Sources;
import java.util.List;
@@ -163,10 +161,6 @@
}
public static void doImportFromSim(Context context, Account account) {
- if (account != null) {
- GoogleSource.createMyContactsIfNotExist(account, context);
- }
-
Intent importIntent = new Intent(Intent.ACTION_VIEW);
importIntent.setType("vnd.android.cursor.item/sim-contact");
if (account != null) {
@@ -178,11 +172,8 @@
}
public static void doImportFromSdCard(Context context, Account account) {
- if (account != null) {
- GoogleSource.createMyContactsIfNotExist(account, context);
- }
-
- Intent importIntent = new Intent(context, ImportVCardActivity.class);
+ Intent importIntent = new Intent(context,
+ com.android.contacts.vcard.ImportVCardActivity.class);
if (account != null) {
importIntent.putExtra("account_name", account.name);
importIntent.putExtra("account_type", account.type);
diff --git a/src/com/android/contacts/util/Constants.java b/src/com/android/contacts/util/Constants.java
index e0178ad..4e80345 100644
--- a/src/com/android/contacts/util/Constants.java
+++ b/src/com/android/contacts/util/Constants.java
@@ -16,14 +16,8 @@
package com.android.contacts.util;
-import android.app.Service;
import android.provider.ContactsContract.CommonDataKinds.Phone;
-/**
- * Background {@link Service} that is used to keep our process alive long enough
- * for background threads to finish. Started and stopped directly by specific
- * background tasks when needed.
- */
public class Constants {
/**
* Specific MIME-type for {@link Phone#CONTENT_ITEM_TYPE} entries that
diff --git a/src/com/android/contacts/util/DataStatus.java b/src/com/android/contacts/util/DataStatus.java
index 88c6594..ec3c1e5 100644
--- a/src/com/android/contacts/util/DataStatus.java
+++ b/src/com/android/contacts/util/DataStatus.java
@@ -24,6 +24,8 @@
import android.text.TextUtils;
import android.text.format.DateUtils;
+import com.android.contacts.R;
+
/**
* Storage for a social status update. Holds a single update, but can use
* {@link #possibleUpdate(Cursor)} to consider updating when a better status
@@ -112,11 +114,11 @@
if (validTimestamp && validLabel) {
return context.getString(
- com.android.internal.R.string.contact_status_update_attribution_with_date,
+ R.string.contact_status_update_attribution_with_date,
timeClause, labelClause);
} else if (validLabel) {
return context.getString(
- com.android.internal.R.string.contact_status_update_attribution,
+ R.string.contact_status_update_attribution,
labelClause);
} else if (validTimestamp) {
return timeClause;
diff --git a/src/com/android/contacts/util/DialogManager.java b/src/com/android/contacts/util/DialogManager.java
new file mode 100644
index 0000000..f1bae0f
--- /dev/null
+++ b/src/com/android/contacts/util/DialogManager.java
@@ -0,0 +1,129 @@
+/*
+ * 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.util;
+
+import com.android.contacts.R;
+
+import android.app.Activity;
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnDismissListener;
+import android.os.Bundle;
+import android.view.View;
+
+/**
+ * Manages creation and destruction of Dialogs that are to be shown by Views. Unlike how Dialogs
+ * are regularly used, the Dialogs are not recycled but immediately destroyed after dismissal.
+ * To be able to do that, two IDs are required which are used consecutively.
+ * How to use:<ul>
+ * <li>The owning Activity creates on instance of this class, passing itself and two Ids that are
+ * not used by other Dialogs of the Activity.</li>
+ * <li>Views owning Dialogs must implement {@link DialogManager.DialogShowingView}</li>
+ * <li>After creating the Views, configureManagingViews must be called to configure all views
+ * that implement {@link DialogManager.DialogShowingView}</li>
+ * <li>In the implementation of {@link Activity#onCreateDialog}, calls for the
+ * ViewId are forwarded to {@link DialogManager#onCreateDialog(int, Bundle)}</li>
+ * </ul>
+ * To actually show a Dialog, the View uses {@link DialogManager#showDialogInView(View, Bundle)},
+ * passing itself as a first parameter
+ */
+public class DialogManager {
+ private final Activity mActivity;
+ private boolean mUseDialogId2 = false;
+ public final static String VIEW_ID_KEY = "view_id";
+
+ public static final boolean isManagedId(int id) {
+ return (id == R.id.dialog_manager_id_1) || (id == R.id.dialog_manager_id_2);
+ }
+
+ /**
+ * Creates a new instance of this class for the given Activity.
+ * @param activity The activity this object is used for
+ */
+ public DialogManager(final Activity activity) {
+ if (activity == null) throw new IllegalArgumentException("activity must not be null");
+ mActivity = activity;
+ }
+
+ /**
+ * Called by a View to show a dialog. It has to pass itself and a Bundle with extra information.
+ * If the view can show several dialogs, it should distinguish them using an item in the Bundle.
+ * The View needs to have a valid and unique Id. This function modifies the bundle by adding a
+ * new item named {@link DialogManager#VIEW_ID_KEY}
+ */
+ public void showDialogInView(final View view, final Bundle bundle) {
+ final int viewId = view.getId();
+ if (bundle.containsKey(VIEW_ID_KEY)) {
+ throw new IllegalArgumentException("Bundle already contains a " + VIEW_ID_KEY);
+ }
+ if (viewId == View.NO_ID) {
+ throw new IllegalArgumentException("View does not have a proper ViewId");
+ }
+ bundle.putInt(VIEW_ID_KEY, viewId);
+ int dialogId = mUseDialogId2 ? R.id.dialog_manager_id_2 : R.id.dialog_manager_id_1;
+ mActivity.showDialog(dialogId, bundle);
+ }
+
+ /**
+ * Callback function called by the Activity to handle View-managed Dialogs.
+ * This function returns null if the id is not one of the two reserved Ids.
+ */
+ public Dialog onCreateDialog(final int id, final Bundle bundle) {
+ if (id == R.id.dialog_manager_id_1) {
+ mUseDialogId2 = true;
+ } else if (id == R.id.dialog_manager_id_2) {
+ mUseDialogId2 = false;
+ } else {
+ return null;
+ }
+ if (!bundle.containsKey(VIEW_ID_KEY)) {
+ throw new IllegalArgumentException("Bundle does not contain a ViewId");
+ }
+ final int viewId = bundle.getInt(VIEW_ID_KEY);
+ final View view = mActivity.findViewById(viewId);
+ if (view == null || !(view instanceof DialogShowingView)) {
+ return null;
+ }
+ final Dialog dialog = ((DialogShowingView)view).createDialog(bundle);
+
+ // As we will never re-use this dialog, we can completely kill it here
+ dialog.setOnDismissListener(new OnDismissListener() {
+ public void onDismiss(DialogInterface dialogInterface) {
+ mActivity.removeDialog(id);
+ }
+ });
+ return dialog;
+ }
+
+ /**
+ * Interface to implemented by Views that show Dialogs
+ */
+ public interface DialogShowingView {
+ /**
+ * Callback function to create a Dialog. Notice that the DialogManager overwrites the
+ * OnDismissListener on the returned Dialog, so the View should not use this Listener itself
+ */
+ Dialog createDialog(Bundle bundle);
+ }
+
+ /**
+ * Interface to implemented by Activities that host View-showing dialogs
+ */
+ public interface DialogShowingViewActivity {
+ DialogManager getDialogManager();
+ }
+}
diff --git a/src/com/android/contacts/util/LocalizedNameResolver.java b/src/com/android/contacts/util/LocalizedNameResolver.java
new file mode 100644
index 0000000..c2ee90a
--- /dev/null
+++ b/src/com/android/contacts/util/LocalizedNameResolver.java
@@ -0,0 +1,161 @@
+/*
+ * 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.util;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.accounts.AccountManager;
+import android.accounts.AuthenticatorDescription;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.content.res.Resources.NotFoundException;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Xml;
+
+import java.io.IOException;
+
+/**
+ * Retrieves localized names per account type. This allows customizing texts like
+ * "All Contacts" for certain account types, but e.g. "All Friends" or "All Connections" for others.
+ */
+public class LocalizedNameResolver {
+ private static final String TAG = "LocalizedNameResolver";
+
+ /**
+ * Meta-data key for the contacts configuration associated with a sync service.
+ */
+ private static final String METADATA_CONTACTS = "android.provider.CONTACTS_STRUCTURE";
+
+ private static final String CONTACTS_DATA_KIND = "ContactsDataKind";
+
+ /**
+ * Returns the name for All Contacts for the specified account type.
+ */
+ public static String getAllContactsName(Context context, String accountType) {
+ if (context == null) throw new IllegalArgumentException("Context must not be null");
+ if (accountType == null) return null;
+
+ return resolveAllContactsName(context, accountType);
+ }
+
+ /**
+ * Finds "All Contacts"-Name for the specified account type.
+ */
+ private static String resolveAllContactsName(Context context, String accountType) {
+ final AccountManager am = AccountManager.get(context);
+
+ for (AuthenticatorDescription auth : am.getAuthenticatorTypes()) {
+ if (accountType.equals(auth.type)) {
+ return resolveAllContactsNameFromMetaData(context, auth.packageName);
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Finds the meta-data XML containing the contacts configuration and
+ * reads the picture priority from that file.
+ */
+ private static String resolveAllContactsNameFromMetaData(Context context, String packageName) {
+ final PackageManager pm = context.getPackageManager();
+ try {
+ PackageInfo pi = pm.getPackageInfo(packageName, PackageManager.GET_SERVICES
+ | PackageManager.GET_META_DATA);
+ if (pi != null && pi.services != null) {
+ for (ServiceInfo si : pi.services) {
+ final XmlResourceParser parser = si.loadXmlMetaData(pm, METADATA_CONTACTS);
+ if (parser != null) {
+ return loadAllContactsNameFromXml(context, parser, packageName);
+ }
+ }
+ }
+ } catch (NameNotFoundException e) {
+ Log.w(TAG, "Problem loading \"All Contacts\"-name: " + e.toString());
+ }
+ return null;
+ }
+
+ private static String loadAllContactsNameFromXml(Context context, XmlPullParser parser,
+ String packageName) {
+ try {
+ final AttributeSet attrs = Xml.asAttributeSet(parser);
+ int type;
+ while ((type = parser.next()) != XmlPullParser.START_TAG
+ && type != XmlPullParser.END_DOCUMENT) {
+ // Drain comments and whitespace
+ }
+
+ if (type != XmlPullParser.START_TAG) {
+ throw new IllegalStateException("No start tag found");
+ }
+
+ final int depth = parser.getDepth();
+ while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
+ && type != XmlPullParser.END_DOCUMENT) {
+ String name = parser.getName();
+ if (type == XmlPullParser.START_TAG && CONTACTS_DATA_KIND.equals(name)) {
+ final TypedArray typedArray = context.obtainStyledAttributes(attrs,
+ android.R.styleable.ContactsDataKind);
+ try {
+ // See if a string has been hardcoded directly into the xml
+ final String nonResourceString = typedArray.getNonResourceString(
+ android.R.styleable.ContactsDataKind_allContactsName);
+ if (nonResourceString != null) {
+ return nonResourceString;
+ }
+
+ // See if a resource is referenced. We can't rely on getString
+ // to automatically resolve it as the resource lives in a different package
+ int id = typedArray.getResourceId(
+ android.R.styleable.ContactsDataKind_allContactsName, 0);
+ if (id == 0) return null;
+
+ // Resolve the resource Id
+ final PackageManager packageManager = context.getPackageManager();
+ final Resources resources;
+ try {
+ resources = packageManager.getResourcesForApplication(packageName);
+ } catch (NameNotFoundException e) {
+ return null;
+ }
+ try {
+ return resources.getString(id);
+ } catch (NotFoundException e) {
+ return null;
+ }
+ } finally {
+ typedArray.recycle();
+ }
+ }
+ }
+ return null;
+ } catch (XmlPullParserException e) {
+ throw new IllegalStateException("Problem reading XML", e);
+ } catch (IOException e) {
+ throw new IllegalStateException("Problem reading XML", e);
+ }
+ }
+}
diff --git a/src/com/android/contacts/util/PhoneCapabilityTester.java b/src/com/android/contacts/util/PhoneCapabilityTester.java
new file mode 100644
index 0000000..4b6c17e
--- /dev/null
+++ b/src/com/android/contacts/util/PhoneCapabilityTester.java
@@ -0,0 +1,56 @@
+/*
+ * 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.util;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.net.Uri;
+
+import java.util.List;
+
+public final class PhoneCapabilityTester {
+ /**
+ * Tests whether the Intent has a receiver registered. This can be used to show/hide
+ * functionality (like Phone, SMS)
+ */
+ public static boolean isIntentRegistered(Context context, Intent intent) {
+ final PackageManager packageManager = context.getPackageManager();
+ final List<ResolveInfo> receiverList = packageManager.queryIntentActivities(intent,
+ PackageManager.MATCH_DEFAULT_ONLY);
+ return receiverList.size() > 0;
+ }
+
+ /**
+ * Returns true if this device has a Phone application installed.
+ */
+ public static boolean isPhoneCallIntentRegistered(Context context) {
+ final Intent intent = new Intent(
+ Intent.ACTION_CALL_PRIVILEGED, Uri.fromParts(Constants.SCHEME_TEL, "", null));
+ return isIntentRegistered(context, intent);
+ }
+
+ /**
+ * Returns true if the device has an SMS application installed.
+ */
+ public static boolean isSmsIntentRegistered(Context context) {
+ final Intent intent = new Intent(Intent.ACTION_SENDTO,
+ Uri.fromParts(Constants.SCHEME_SMSTO, "", null));
+ return isIntentRegistered(context, intent);
+ }
+}
diff --git a/src/com/android/contacts/util/PhonebookCollatorFactory.java b/src/com/android/contacts/util/PhonebookCollatorFactory.java
new file mode 100644
index 0000000..08ce27a
--- /dev/null
+++ b/src/com/android/contacts/util/PhonebookCollatorFactory.java
@@ -0,0 +1,43 @@
+/*
+ * 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.util;
+
+import java.text.Collator;
+import java.util.Locale;
+
+/**
+ * Returns the collator that can be used to sort contact list entries. This
+ * collator is the same as the one that is used in sqlite.
+ */
+public final class PhonebookCollatorFactory {
+ public static final Collator getCollator() {
+ final Locale defaultLocale = Locale.getDefault();
+ final String defaultLocaleString = defaultLocale.toString();
+ // For Japanese we use a special collator that puts japanese characters before foreign
+ // ones (this is called a dictionary collator)
+ // Warning: This function has to match the behavior in sqlite3_android.cpp (located in
+ // the framework)
+ final Locale locale;
+ if ("ja".equals(defaultLocaleString) || "ja_JP".equals(defaultLocaleString)) {
+ locale = new Locale("ja@collation=phonebook");
+ } else {
+ locale = defaultLocale;
+ }
+
+ return Collator.getInstance(locale);
+ }
+}
diff --git a/src/com/android/contacts/vcard/AnimatedImageView.java b/src/com/android/contacts/vcard/AnimatedImageView.java
new file mode 100644
index 0000000..8911b52
--- /dev/null
+++ b/src/com/android/contacts/vcard/AnimatedImageView.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2008 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.vcard;
+
+import android.content.Context;
+import android.graphics.drawable.AnimationDrawable;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+import android.widget.RemoteViews.RemoteView;
+
+/**
+ * Just a bare copy from SystemUI. Do not edit.
+ */
+public class AnimatedImageView extends ImageView {
+ AnimationDrawable mAnim;
+ boolean mAttached;
+
+ public AnimatedImageView(Context context) {
+ super(context);
+ }
+
+ public AnimatedImageView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ private void updateAnim() {
+ Drawable drawable = getDrawable();
+ if (mAttached && mAnim != null) {
+ mAnim.stop();
+ }
+ if (drawable instanceof AnimationDrawable) {
+ mAnim = (AnimationDrawable)drawable;
+ if (mAttached) {
+ mAnim.start();
+ }
+ } else {
+ mAnim = null;
+ }
+ }
+
+ @Override
+ public void setImageDrawable(Drawable drawable) {
+ super.setImageDrawable(drawable);
+ updateAnim();
+ }
+
+ @Override
+ @android.view.RemotableViewMethod
+ public void setImageResource(int resid) {
+ super.setImageResource(resid);
+ updateAnim();
+ }
+
+ @Override
+ public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ if (mAnim != null) {
+ mAnim.start();
+ }
+ mAttached = true;
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ if (mAnim != null) {
+ mAnim.stop();
+ }
+ mAttached = false;
+ }
+}
+
diff --git a/src/com/android/contacts/vcard/CancelImportActivity.java b/src/com/android/contacts/vcard/CancelImportActivity.java
new file mode 100644
index 0000000..22de0da
--- /dev/null
+++ b/src/com/android/contacts/vcard/CancelImportActivity.java
@@ -0,0 +1,129 @@
+/*
+ * 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.vcard;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.contacts.R;
+
+/**
+ * The Activity for canceling ongoing vCard import.
+ *
+ * Currently we ignore tha case where there are more than one import requests
+ * with a same Uri in the queue.
+ */
+public class CancelImportActivity extends Activity {
+ private final String LOG_TAG = "CancelImportActivity";
+
+ /* package */ final String EXTRA_TARGET_URI = "extra_target_uri";
+
+ private class CustomConnection implements ServiceConnection {
+ private Messenger mMessenger;
+ public void doBindService() {
+ bindService(new Intent(CancelImportActivity.this,
+ VCardService.class), this, Context.BIND_AUTO_CREATE);
+ }
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ mMessenger = new Messenger(service);
+
+ try {
+ mMessenger.send(Message.obtain(null,
+ VCardService.MSG_CANCEL_IMPORT_REQUEST,
+ null));
+ finish();
+ } catch (RemoteException e) {
+ Log.e(LOG_TAG, "RemoteException is thrown when trying to send request");
+ CancelImportActivity.this.showDialog(R.string.fail_reason_unknown);
+ }
+ }
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ mMessenger = null;
+ }
+ }
+
+ private class RequestCancelImportListener implements DialogInterface.OnClickListener {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ mConnection.doBindService();
+ }
+ }
+
+ private class CancelListener
+ implements DialogInterface.OnClickListener, DialogInterface.OnCancelListener {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ finish();
+ }
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ finish();
+ }
+ }
+
+ private final CancelListener mCancelListener = new CancelListener();
+ private final CustomConnection mConnection = new CustomConnection();
+ // private String mTargetUri;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ showDialog(R.id.dialog_cancel_import_confirmation);
+ }
+
+ @Override
+ protected Dialog onCreateDialog(int resId, Bundle bundle) {
+ switch (resId) {
+
+ case R.id.dialog_cancel_import_confirmation: {
+ return getConfirmationDialog();
+ }
+ case R.string.fail_reason_unknown:
+ final AlertDialog.Builder builder = new AlertDialog.Builder(this)
+ .setTitle(getString(R.string.reading_vcard_failed_title))
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .setMessage(getString(resId))
+ .setOnCancelListener(mCancelListener)
+ .setPositiveButton(android.R.string.ok, mCancelListener);
+ return builder.create();
+ }
+ return super.onCreateDialog(resId, bundle);
+ }
+
+ private Dialog getConfirmationDialog() {
+ final AlertDialog.Builder builder = new AlertDialog.Builder(this)
+ .setTitle(R.string.cancel_import_confirmation_title)
+ .setMessage(R.string.cancel_import_confirmation_message)
+ .setPositiveButton(android.R.string.ok, new RequestCancelImportListener())
+ .setOnCancelListener(mCancelListener)
+ .setNegativeButton(android.R.string.cancel, mCancelListener);
+ return builder.create();
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/contacts/SearchResultsActivity.java b/src/com/android/contacts/vcard/CancelImportRequest.java
similarity index 63%
copy from src/com/android/contacts/SearchResultsActivity.java
copy to src/com/android/contacts/vcard/CancelImportRequest.java
index 09f0014..dd10187 100644
--- a/src/com/android/contacts/SearchResultsActivity.java
+++ b/src/com/android/contacts/vcard/CancelImportRequest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2007 The Android Open Source Project
+ * 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.
@@ -13,11 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.contacts;
+package com.android.contacts.vcard;
+
+import android.net.Uri;
/**
- * The activity that displays the list of contact search results. We need a separate
- * class because it uses a different theme from {@link ContactsListActivity}.
+ * Class representing one request for canceling vCard import (given as a Uri).
*/
-public class SearchResultsActivity extends ContactsListActivity {
-}
+public class CancelImportRequest {
+ public final Uri uri;
+ public CancelImportRequest(Uri uri) {
+ this.uri = uri;
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/contacts/vcard/ExportProcessor.java b/src/com/android/contacts/vcard/ExportProcessor.java
new file mode 100644
index 0000000..2532cb2
--- /dev/null
+++ b/src/com/android/contacts/vcard/ExportProcessor.java
@@ -0,0 +1,267 @@
+/*
+ * 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.vcard;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.net.Uri;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.contacts.R;
+import com.android.contacts.activities.ContactBrowserActivity;
+import com.android.vcard.VCardComposer;
+import com.android.vcard.VCardConfig;
+
+import java.io.FileNotFoundException;
+import java.io.OutputStream;
+import java.util.LinkedList;
+import java.util.Queue;
+
+public class ExportProcessor {
+ private static final String LOG_TAG = "ExportProcessor";
+
+ private final Context mContext;
+
+ private ContentResolver mResolver;
+ private NotificationManager mNotificationManager;
+
+ boolean mCanceled;
+
+ boolean mReadyForRequest;
+ private final Queue<ExportRequest> mPendingRequests =
+ new LinkedList<ExportRequest>();
+
+ public ExportProcessor(Context context) {
+ mContext = context;
+ }
+
+ /* package */ ThreadStarter mThreadStarter = new ThreadStarter() {
+ public void start() {
+ final Thread thread = new Thread(new Runnable() {
+ public void run() {
+ process();
+ }
+ });
+ thread.start();
+ }
+ };
+
+ public synchronized void pushRequest(ExportRequest parameter) {
+ if (mResolver == null) {
+ // Service object may not ready at the construction time
+ // (e.g. ContentResolver may be null).
+ mResolver = mContext.getContentResolver();
+ mNotificationManager =
+ (NotificationManager)mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+ }
+
+ final boolean needThreadStart;
+ if (!mReadyForRequest) {
+ needThreadStart = true;
+ } else {
+ needThreadStart = false;
+ }
+ mPendingRequests.add(parameter);
+ if (needThreadStart) {
+ mThreadStarter.start();
+ }
+
+ mReadyForRequest = true;
+ }
+
+ /* package */ void process() {
+ if (!mReadyForRequest) {
+ throw new RuntimeException(
+ "process() is called before request being pushed "
+ + "or after this object's finishing its processing.");
+ }
+
+ try {
+ while (!mCanceled) {
+ final ExportRequest parameter;
+ synchronized (this) {
+ if (mPendingRequests.size() == 0) {
+ mReadyForRequest = false;
+ break;
+ } else {
+ parameter = mPendingRequests.poll();
+ }
+ } // synchronized (this)
+ handleOneRequest(parameter);
+ }
+
+ doFinishNotification(mContext.getString(R.string.exporting_vcard_finished_title),
+ "");
+ } finally {
+ // Not thread safe. Just in case.
+ // TODO: verify this works fine.
+ mReadyForRequest = false;
+ }
+ }
+
+ /* package */ void handleOneRequest(ExportRequest request) {
+ boolean shouldCallFinish = true;
+ VCardComposer composer = null;
+ try {
+ final Uri uri = request.destUri;
+ final OutputStream outputStream;
+ try {
+ outputStream = mResolver.openOutputStream(uri);
+ } catch (FileNotFoundException e) {
+ // Need concise title.
+
+ final String errorReason =
+ mContext.getString(R.string.fail_reason_could_not_open_file,
+ uri, e.getMessage());
+ shouldCallFinish = false;
+ doFinishNotification(errorReason, "");
+ return;
+ }
+ final String exportType = request.exportType;
+ final int vcardType;
+ if (TextUtils.isEmpty(exportType)) {
+ vcardType = VCardConfig.getVCardTypeFromString(
+ mContext.getString(R.string.config_export_vcard_type));
+ } else {
+ vcardType = VCardConfig.getVCardTypeFromString(exportType);
+ }
+
+ composer = new VCardComposer(mContext, vcardType, true);
+
+ // for test
+ // int vcardType = (VCardConfig.VCARD_TYPE_V21_GENERIC |
+ // VCardConfig.FLAG_USE_QP_TO_PRIMARY_PROPERTIES);
+ // composer = new VCardComposer(ExportVCardActivity.this, vcardType, true);
+
+ composer.addHandler(composer.new HandlerForOutputStream(outputStream));
+
+ if (!composer.init()) {
+ final String errorReason = composer.getErrorReason();
+ Log.e(LOG_TAG, "initialization of vCard composer failed: " + errorReason);
+ final String translatedErrorReason =
+ translateComposerError(errorReason);
+ final String title =
+ mContext.getString(R.string.fail_reason_could_not_initialize_exporter,
+ translatedErrorReason);
+ doFinishNotification(title, "");
+ return;
+ }
+
+ final int total = composer.getCount();
+ if (total == 0) {
+ final String title =
+ mContext.getString(R.string.fail_reason_no_exportable_contact);
+ doFinishNotification(title, "");
+ return;
+ }
+
+ int current = 1; // 1-origin
+ while (!composer.isAfterLast()) {
+ if (mCanceled) {
+ return;
+ }
+ if (!composer.createOneEntry()) {
+ final String errorReason = composer.getErrorReason();
+ Log.e(LOG_TAG, "Failed to read a contact: " + errorReason);
+ final String translatedErrorReason =
+ translateComposerError(errorReason);
+ final String title =
+ mContext.getString(R.string.fail_reason_error_occurred_during_export,
+ translatedErrorReason);
+ doFinishNotification(title, "");
+ return;
+ }
+ doProgressNotification(uri, total, current);
+ current++;
+ }
+ } finally {
+ if (composer != null) {
+ composer.terminate();
+ }
+ }
+ }
+
+ private String translateComposerError(String errorMessage) {
+ final Resources resources = mContext.getResources();
+ if (VCardComposer.FAILURE_REASON_FAILED_TO_GET_DATABASE_INFO.equals(errorMessage)) {
+ return resources.getString(R.string.composer_failed_to_get_database_infomation);
+ } else if (VCardComposer.FAILURE_REASON_NO_ENTRY.equals(errorMessage)) {
+ return resources.getString(R.string.composer_has_no_exportable_contact);
+ } else if (VCardComposer.FAILURE_REASON_NOT_INITIALIZED.equals(errorMessage)) {
+ return resources.getString(R.string.composer_not_initialized);
+ } else {
+ return errorMessage;
+ }
+ }
+
+ private void doProgressNotification(Uri uri, int total, int current) {
+ final String title = mContext.getString(R.string.exporting_contact_list_title);
+ final String description =
+ mContext.getString(R.string.exporting_contact_list_message, uri);
+
+ /* TODO: fix this
+ final RemoteViews remoteViews = new RemoteViews(mService.getPackageName(),
+ R.layout.status_bar_ongoing_event_progress_bar);
+ remoteViews.setTextViewText(R.id.status_description, message);
+ remoteViews.setProgressBar(R.id.status_progress_bar, total, current, (total == -1));
+
+ final String percentage = mService.getString(R.string.percentage,
+ String.valueOf((current * 100)/total));
+ remoteViews.setTextViewText(R.id.status_progress_text, percentage);
+ remoteViews.setImageViewResource(R.id.status_icon, android.R.drawable.stat_sys_upload);
+
+ final Notification notification = new Notification();
+ notification.icon = android.R.drawable.stat_sys_upload;
+ notification.flags |= Notification.FLAG_ONGOING_EVENT;
+ notification.tickerText = title;
+ notification.contentView = remoteViews;
+ notification.contentIntent =
+ PendingIntent.getActivity(mService, 0,
+ new Intent(mService, ContactBrowserActivity.class), 0);*/
+
+ final long when = System.currentTimeMillis();
+ final Notification notification = new Notification(
+ android.R.drawable.stat_sys_upload,
+ description,
+ when);
+
+ final Context context = mContext.getApplicationContext();
+ final PendingIntent pendingIntent =
+ PendingIntent.getActivity(context, 0,
+ new Intent(context, ContactBrowserActivity.class),
+ PendingIntent.FLAG_UPDATE_CURRENT);
+
+ notification.setLatestEventInfo(context, title, description, pendingIntent);
+ mNotificationManager.notify(VCardService.EXPORT_NOTIFICATION_ID, notification);
+ }
+
+ private void doFinishNotification(final String title, final String message) {
+ final Notification notification = new Notification();
+ notification.icon = android.R.drawable.stat_sys_upload_done;
+ notification.flags |= Notification.FLAG_AUTO_CANCEL;
+ notification.setLatestEventInfo(mContext, title, message, null);
+ final Intent intent = new Intent(mContext, ContactBrowserActivity.class);
+ notification.contentIntent =
+ PendingIntent.getActivity(mContext, 0, intent, 0);
+ mNotificationManager.notify(VCardService.EXPORT_NOTIFICATION_ID, notification);
+ }
+}
diff --git a/src/com/android/contacts/vcard/ExportRequest.java b/src/com/android/contacts/vcard/ExportRequest.java
new file mode 100644
index 0000000..fae2d07
--- /dev/null
+++ b/src/com/android/contacts/vcard/ExportRequest.java
@@ -0,0 +1,35 @@
+/*
+ * 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.vcard;
+
+import android.net.Uri;
+
+public class ExportRequest {
+ public final Uri destUri;
+ /**
+ * Can be null.
+ */
+ public final String exportType;
+
+ public ExportRequest(Uri destUri) {
+ this(destUri, null);
+ }
+
+ public ExportRequest(Uri destUri, String exportType) {
+ this.destUri = destUri;
+ this.exportType = exportType;
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/contacts/ExportVCardActivity.java b/src/com/android/contacts/vcard/ExportVCardActivity.java
similarity index 64%
rename from src/com/android/contacts/ExportVCardActivity.java
rename to src/com/android/contacts/vcard/ExportVCardActivity.java
index 5bccc7a..fbe6d48 100644
--- a/src/com/android/contacts/ExportVCardActivity.java
+++ b/src/com/android/contacts/vcard/ExportVCardActivity.java
@@ -13,27 +13,33 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.contacts;
+package com.android.contacts.vcard;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
-import android.app.ProgressDialog;
+import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.ServiceConnection;
import android.content.res.Resources;
+import android.net.Uri;
import android.os.Bundle;
-import android.os.Handler;
-import android.os.PowerManager;
-import android.pim.vcard.VCardComposer;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
import android.text.TextUtils;
import android.util.Log;
+import com.android.contacts.R;
+import com.android.vcard.VCardComposer;
+
import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.OutputStream;
import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.Queue;
import java.util.Set;
/**
@@ -55,21 +61,76 @@
private int mFileIndexMinimum;
private int mFileIndexMaximum;
private String mFileNameExtension;
- private String mVCardTypeStr;
private Set<String> mExtensionsToConsider;
- private ProgressDialog mProgressDialog;
- private String mExportingFileName;
-
- private Handler mHandler = new Handler();
-
- // Used temporaly when asking users to confirm the file name
+ // Used temporarily when asking users to confirm the file name
private String mTargetFileName;
- // String for storing error reason temporaly.
+ // String for storing error reason temporarily.
private String mErrorReason;
- private ActualExportThread mActualExportThread;
+
+ private class CustomConnection implements ServiceConnection {
+ private Messenger mMessenger;
+ private Queue<ExportRequest> mPendingRequests = new LinkedList<ExportRequest>();
+
+ public void doBindService() {
+ bindService(new Intent(ExportVCardActivity.this,
+ VCardService.class), this, Context.BIND_AUTO_CREATE);
+ }
+
+ public synchronized void requestSend(final ExportRequest parameter) {
+ if (mMessenger != null) {
+ sendMessage(parameter);
+ } else {
+ mPendingRequests.add(parameter);
+ }
+ }
+
+ private void sendMessage(final ExportRequest request) {
+ try {
+ mMessenger.send(Message.obtain(null,
+ VCardService.MSG_EXPORT_REQUEST,
+ request));
+ } catch (RemoteException e) {
+ Log.e(LOG_TAG, "RemoteException is thrown when trying to send request");
+ runOnUiThread(new ErrorReasonDisplayer(
+ getString(R.string.fail_reason_unknown)));
+ }
+ }
+
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ synchronized (this) {
+ mMessenger = new Messenger(service);
+ // Send pending requests thrown from this Activity before an actual connection
+ // is established.
+ while (!mPendingRequests.isEmpty()) {
+ final ExportRequest parameter = mPendingRequests.poll();
+ if (parameter == null) {
+ throw new NullPointerException();
+ }
+ sendMessage(parameter);
+ }
+
+ unbindService(this);
+ finish();
+ }
+ }
+
+ public void onServiceDisconnected(ComponentName name) {
+ synchronized (this) {
+ if (!mPendingRequests.isEmpty()) {
+ Log.w(LOG_TAG, "Some request(s) are dropped.");
+ }
+ // Set to null so that we can detect inappropriate re-connection toward
+ // the Service via NullPointerException;
+ mPendingRequests = null;
+ mMessenger = null;
+ }
+ }
+ }
+
+ private final CustomConnection mConnection = new CustomConnection();
private class CancelListener
implements DialogInterface.OnClickListener, DialogInterface.OnCancelListener {
@@ -101,129 +162,28 @@
}
private class ExportConfirmationListener implements DialogInterface.OnClickListener {
- private final String mFileName;
+ private final Uri mDestUri;
public ExportConfirmationListener(String fileName) {
- mFileName = fileName;
+ this(Uri.parse("file://" + fileName));
+ }
+
+ public ExportConfirmationListener(Uri uri) {
+ mDestUri = uri;
}
public void onClick(DialogInterface dialog, int which) {
if (which == DialogInterface.BUTTON_POSITIVE) {
- mActualExportThread = new ActualExportThread(mFileName);
- showDialog(R.id.dialog_exporting_vcard);
+ mConnection.doBindService();
+
+ final ExportRequest request = new ExportRequest(mDestUri);
+
+ // The connection object will call finish().
+ mConnection.requestSend(request);
}
}
}
- private class ActualExportThread extends Thread
- implements DialogInterface.OnCancelListener {
- private PowerManager.WakeLock mWakeLock;
- private boolean mCanceled = false;
-
- public ActualExportThread(String fileName) {
- mExportingFileName = fileName;
- PowerManager powerManager = (PowerManager)getSystemService(Context.POWER_SERVICE);
- mWakeLock = powerManager.newWakeLock(
- PowerManager.SCREEN_DIM_WAKE_LOCK |
- PowerManager.ON_AFTER_RELEASE, LOG_TAG);
- }
-
- @Override
- public void run() {
- boolean shouldCallFinish = true;
- mWakeLock.acquire();
- VCardComposer composer = null;
- try {
- OutputStream outputStream = null;
- try {
- outputStream = new FileOutputStream(mExportingFileName);
- } catch (FileNotFoundException e) {
- final String errorReason =
- getString(R.string.fail_reason_could_not_open_file,
- mExportingFileName, e.getMessage());
- shouldCallFinish = false;
- mHandler.post(new ErrorReasonDisplayer(errorReason));
- return;
- }
-
- composer = new VCardComposer(ExportVCardActivity.this, mVCardTypeStr, true);
- /*int vcardType = (VCardConfig.VCARD_TYPE_V21_GENERIC |
- VCardConfig.FLAG_USE_QP_TO_PRIMARY_PROPERTIES);
- composer = new VCardComposer(ExportVCardActivity.this, vcardType, true);*/
-
- composer.addHandler(composer.new HandlerForOutputStream(outputStream));
-
- if (!composer.init()) {
- final String errorReason = composer.getErrorReason();
- Log.e(LOG_TAG, "initialization of vCard composer failed: " + errorReason);
- final String translatedErrorReason =
- translateComposerError(errorReason);
- mHandler.post(new ErrorReasonDisplayer(
- getString(R.string.fail_reason_could_not_initialize_exporter,
- translatedErrorReason)));
- shouldCallFinish = false;
- return;
- }
-
- int size = composer.getCount();
-
- if (size == 0) {
- mHandler.post(new ErrorReasonDisplayer(
- getString(R.string.fail_reason_no_exportable_contact)));
- shouldCallFinish = false;
- return;
- }
-
- mProgressDialog.setProgressNumberFormat(
- getString(R.string.exporting_contact_list_progress));
- mProgressDialog.setMax(size);
- mProgressDialog.setProgress(0);
-
- while (!composer.isAfterLast()) {
- if (mCanceled) {
- return;
- }
- if (!composer.createOneEntry()) {
- final String errorReason = composer.getErrorReason();
- Log.e(LOG_TAG, "Failed to read a contact: " + errorReason);
- final String translatedErrorReason =
- translateComposerError(errorReason);
- mHandler.post(new ErrorReasonDisplayer(
- getString(R.string.fail_reason_error_occurred_during_export,
- translatedErrorReason)));
- shouldCallFinish = false;
- return;
- }
- mProgressDialog.incrementProgressBy(1);
- }
- } finally {
- if (composer != null) {
- composer.terminate();
- }
- mWakeLock.release();
- mProgressDialog.dismiss();
- if (shouldCallFinish && !isFinishing()) {
- finish();
- }
- }
- }
-
- @Override
- public void finalize() {
- if (mWakeLock != null && mWakeLock.isHeld()) {
- mWakeLock.release();
- }
- }
-
- public void cancel() {
- mCanceled = true;
- }
-
- public void onCancel(DialogInterface dialog) {
- cancel();
- }
- }
-
private String translateComposerError(String errorMessage) {
Resources resources = getResources();
if (VCardComposer.FAILURE_REASON_FAILED_TO_GET_DATABASE_INFO.equals(errorMessage)) {
@@ -245,7 +205,6 @@
mFileNamePrefix = getString(R.string.config_export_file_prefix);
mFileNameSuffix = getString(R.string.config_export_file_suffix);
mFileNameExtension = getString(R.string.config_export_file_extension);
- mVCardTypeStr = getString(R.string.config_export_vcard_type);
mExtensionsToConsider = new HashSet<String>();
mExtensionsToConsider.add(mFileNameExtension);
@@ -269,7 +228,7 @@
}
@Override
- protected Dialog onCreateDialog(int id) {
+ protected Dialog onCreateDialog(int id, Bundle bundle) {
switch (id) {
case R.id.dialog_export_confirmation: {
return getExportConfirmationDialog();
@@ -293,44 +252,25 @@
.setPositiveButton(android.R.string.ok, mCancelListener);
return builder.create();
}
- case R.id.dialog_exporting_vcard: {
- if (mProgressDialog == null) {
- String title = getString(R.string.exporting_contact_list_title);
- String message = getString(R.string.exporting_contact_list_message,
- mExportingFileName);
- mProgressDialog = new ProgressDialog(ExportVCardActivity.this);
- mProgressDialog.setTitle(title);
- mProgressDialog.setMessage(message);
- mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
- mProgressDialog.setOnCancelListener(mActualExportThread);
- mActualExportThread.start();
- }
- return mProgressDialog;
- }
}
- return super.onCreateDialog(id);
+ return super.onCreateDialog(id, bundle);
}
@Override
- protected void onPrepareDialog(int id, Dialog dialog) {
+ protected void onPrepareDialog(int id, Dialog dialog, Bundle args) {
if (id == R.id.dialog_fail_to_export_with_reason) {
((AlertDialog)dialog).setMessage(getErrorReason());
} else if (id == R.id.dialog_export_confirmation) {
((AlertDialog)dialog).setMessage(
getString(R.string.confirm_export_message, mTargetFileName));
} else {
- super.onPrepareDialog(id, dialog);
+ super.onPrepareDialog(id, dialog, args);
}
}
@Override
protected void onStop() {
super.onStop();
- if (mActualExportThread != null) {
- // The Activity is no longer visible. Stop the thread.
- mActualExportThread.cancel();
- mActualExportThread = null;
- }
if (!isFinishing()) {
finish();
@@ -445,13 +385,6 @@
.create();
}
- public void cancelExport() {
- if (mActualExportThread != null) {
- mActualExportThread.cancel();
- mActualExportThread = null;
- }
- }
-
public String getErrorReason() {
return mErrorReason;
}
diff --git a/src/com/android/contacts/vcard/ImportProcessor.java b/src/com/android/contacts/vcard/ImportProcessor.java
new file mode 100644
index 0000000..e19aaf1
--- /dev/null
+++ b/src/com/android/contacts/vcard/ImportProcessor.java
@@ -0,0 +1,426 @@
+/*
+ * 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.vcard;
+
+import android.accounts.Account;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.net.Uri;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.provider.ContactsContract.RawContacts;
+import android.util.Log;
+
+import com.android.contacts.R;
+import com.android.vcard.VCardEntryCommitter;
+import com.android.vcard.VCardEntryConstructor;
+import com.android.vcard.VCardInterpreter;
+import com.android.vcard.VCardParser;
+import com.android.vcard.VCardParser_V21;
+import com.android.vcard.VCardParser_V30;
+import com.android.vcard.exception.VCardException;
+import com.android.vcard.exception.VCardNestedException;
+import com.android.vcard.exception.VCardNotSupportedException;
+import com.android.vcard.exception.VCardVersionException;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Queue;
+
+/**
+ * Class for processing incoming import request from {@link ImportVCardActivity}.
+ *
+ * This class is designed so that a user ({@link Service}) does not need to (and should not)
+ * recreate multiple instances, as this holds total count of vCard entries to be imported.
+ */
+public class ImportProcessor {
+ private static final String LOG_TAG = ImportProcessor.class.getSimpleName();
+
+ private class CustomConnection implements ServiceConnection {
+ private Messenger mMessenger;
+ public void doBindService() {
+ mContext.bindService(new Intent(mContext, VCardService.class),
+ this, Context.BIND_AUTO_CREATE);
+ }
+
+ public void sendFinisheNotification() {
+ try {
+ mMessenger.send(Message.obtain(null,
+ VCardService.MSG_NOTIFY_IMPORT_FINISHED,
+ null));
+ } catch (RemoteException e) {
+ Log.e(LOG_TAG, "RemoteException is thrown when trying to send request");
+ }
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ mMessenger = new Messenger(service);
+ }
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ mMessenger = null;
+ }
+ }
+
+ private final Context mContext;
+
+ private final CustomConnection mConnection = new CustomConnection();
+
+ private ContentResolver mResolver;
+ private NotificationManager mNotificationManager;
+
+ private final List<Uri> mFailedUris = new ArrayList<Uri>();
+ private final List<Uri> mCreatedUris = new ArrayList<Uri>();
+ private final ImportProgressNotifier mNotifier = new ImportProgressNotifier();
+
+ private VCardParser mVCardParser;
+
+ /**
+ * Meaning a controller of this object requests the operation should be canceled
+ * or not, which implies {@link #mReadyForRequest} should be set to false soon, but
+ * it does not meaning cancel request is able to immediately stop this object,
+ * so we have two variables.
+ */
+ private boolean mCanceled;
+
+ /**
+ * Meaning that this object is able to accept import requests.
+ */
+ private boolean mReadyForRequest;
+ private final Queue<ImportRequest> mPendingRequests =
+ new LinkedList<ImportRequest>();
+
+ // For testability.
+ /* package */ ThreadStarter mThreadStarter = new ThreadStarter() {
+ public void start() {
+ final Thread thread = new Thread(new Runnable() {
+ public void run() {
+ process();
+ }
+ });
+ thread.start();
+ }
+ };
+ /* package */ interface CommitterGenerator {
+ public VCardEntryCommitter generate(ContentResolver resolver);
+ }
+ /* package */ class DefaultCommitterGenerator implements CommitterGenerator {
+ public VCardEntryCommitter generate(ContentResolver resolver) {
+ return new VCardEntryCommitter(mResolver);
+ }
+ }
+
+ private CommitterGenerator mCommitterGenerator =
+ new DefaultCommitterGenerator();
+
+ public ImportProcessor(final Context context) {
+ mContext = context;
+
+ mConnection.doBindService();
+ }
+
+ /**
+ * Checks this object and initialize it if not.
+ *
+ * This method is needed since {@link VCardService} is not ready when this object is
+ * created and we need to delay this initialization, while we want to initialize
+ * this object soon in tests.
+ */
+ /* package */ void ensureInit() {
+ if (mResolver == null) {
+ // Service object may not ready at the construction time
+ // (e.g. ContentResolver may be null).
+ mResolver = mContext.getContentResolver();
+ mNotificationManager =
+ (NotificationManager)mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+ }
+ }
+
+ public synchronized void pushRequest(final ImportRequest request) {
+ ensureInit();
+
+ final boolean needThreadStart;
+ if (!mReadyForRequest) {
+ mFailedUris.clear();
+ mCreatedUris.clear();
+
+ mNotifier.init(mContext, mNotificationManager);
+ needThreadStart = true;
+ } else {
+ needThreadStart = false;
+ }
+ final int count = request.entryCount;
+ if (count > 0) {
+ mNotifier.addTotalCount(count);
+ }
+ mPendingRequests.add(request);
+ if (needThreadStart) {
+ mThreadStarter.start();
+ }
+
+ mReadyForRequest = true;
+ }
+
+ /**
+ * Starts processing import requests. Never stops until all given requests are
+ * processed or some error happens, assuming this method is called from a
+ * {@link Thread} object.
+ */
+ /* package */ void process() {
+ if (!mReadyForRequest) {
+ throw new RuntimeException(
+ "process() is called before request being pushed "
+ + "or after this object's finishing its processing.");
+ }
+ try {
+ while (!mCanceled) {
+ final ImportRequest parameter;
+ synchronized (this) {
+ if (mPendingRequests.size() == 0) {
+ mReadyForRequest = false;
+ break;
+ } else {
+ parameter = mPendingRequests.poll();
+ }
+ } // synchronized (this)
+ handleOneRequest(parameter);
+ }
+
+ // Currenty we don't have an appropriate way to let users see all entries
+ // imported in this procedure. Instead, we show them entries only when
+ // there's just one created uri.
+ doFinishNotification(mCreatedUris.size() > 0 ? mCreatedUris.get(0) : null);
+ mConnection.sendFinisheNotification();
+ mContext.unbindService(mConnection);
+ } finally {
+ // TODO: verify this works fine.
+ mReadyForRequest = false; // Just in case.
+ mNotifier.resetTotalCount();
+ }
+ }
+
+ /**
+ * Would be run inside synchronized block.
+ */
+ /* package */ boolean handleOneRequest(final ImportRequest request) {
+ if (mCanceled) {
+ Log.i(LOG_TAG, "Canceled before actually handling parameter ("
+ + request.uri + ")");
+ return false;
+ }
+ final int[] possibleVCardVersions;
+ if (request.vcardVersion == ImportVCardActivity.VCARD_VERSION_AUTO_DETECT) {
+ /**
+ * Note: this code assumes that a given Uri is able to be opened more than once,
+ * which may not be true in certain conditions.
+ */
+ possibleVCardVersions = new int[] {
+ ImportVCardActivity.VCARD_VERSION_V21,
+ ImportVCardActivity.VCARD_VERSION_V30
+ };
+ } else {
+ possibleVCardVersions = new int[] {
+ request.vcardVersion
+ };
+ }
+
+ final Uri uri = request.uri;
+ final Account account = request.account;
+ final int estimatedVCardType = request.estimatedVCardType;
+ final String estimatedCharset = request.estimatedCharset;
+
+ final VCardEntryConstructor constructor =
+ new VCardEntryConstructor(estimatedVCardType, account, estimatedCharset);
+ final VCardEntryCommitter committer = mCommitterGenerator.generate(mResolver);
+ constructor.addEntryHandler(committer);
+ constructor.addEntryHandler(mNotifier);
+
+ final boolean successful =
+ readOneVCard(uri, estimatedVCardType, estimatedCharset,
+ constructor, possibleVCardVersions);
+ if (successful) {
+ List<Uri> uris = committer.getCreatedUris();
+ if (uris != null) {
+ mCreatedUris.addAll(uris);
+ } else {
+ // Not critical, but suspicious.
+ Log.w(LOG_TAG,
+ "Created Uris is null while the creation itself is successful.");
+ }
+ } else {
+ mFailedUris.add(uri);
+ }
+
+ return successful;
+ }
+
+ /*
+ private void doErrorNotification(int id) {
+ final Notification notification = new Notification();
+ notification.icon = android.R.drawable.stat_sys_download_done;
+ notification.flags |= Notification.FLAG_AUTO_CANCEL;
+ final String title = mService.getString(R.string.reading_vcard_failed_title);
+ final PendingIntent intent =
+ PendingIntent.getActivity(mService, 0, new Intent(), 0);
+ notification.setLatestEventInfo(mService, title, "", intent);
+ mNotificationManager.notify(MESSAGE_ID, notification);
+ }
+ */
+
+ private void doFinishNotification(final Uri createdUri) {
+ final Notification notification = new Notification();
+ final String title;
+ notification.flags |= Notification.FLAG_AUTO_CANCEL;
+
+ if (isCanceled()) {
+ notification.icon = android.R.drawable.stat_notify_error;
+ title = mContext.getString(R.string.importing_vcard_canceled_title);
+ } else {
+ notification.icon = android.R.drawable.stat_sys_download_done;
+ title = mContext.getString(R.string.importing_vcard_finished_title);
+ }
+
+ final Intent intent;
+ if (createdUri != null) {
+ final long rawContactId = ContentUris.parseId(createdUri);
+ final Uri contactUri = RawContacts.getContactLookupUri(
+ mResolver, ContentUris.withAppendedId(
+ RawContacts.CONTENT_URI, rawContactId));
+ intent = new Intent(Intent.ACTION_VIEW, contactUri);
+ } else {
+ intent = null;
+ }
+
+ notification.setLatestEventInfo(mContext, title, "",
+ PendingIntent.getActivity(mContext, 0, intent, 0));
+ mNotificationManager.notify(VCardService.IMPORT_NOTIFICATION_ID, notification);
+ }
+
+ // Make package private for testing use.
+ /* package */ boolean readOneVCard(Uri uri, int vcardType, String charset,
+ final VCardInterpreter interpreter,
+ final int[] possibleVCardVersions) {
+ boolean successful = false;
+ final int length = possibleVCardVersions.length;
+ for (int i = 0; i < length; i++) {
+ InputStream is = null;
+ final int vcardVersion = possibleVCardVersions[i];
+ try {
+ if (i > 0 && (interpreter instanceof VCardEntryConstructor)) {
+ // Let the object clean up internal temporary objects,
+ ((VCardEntryConstructor) interpreter).clear();
+ }
+
+ is = mResolver.openInputStream(uri);
+
+ // We need synchronized block here,
+ // since we need to handle mCanceled and mVCardParser at once.
+ // In the worst case, a user may call cancel() just before creating
+ // mVCardParser.
+ synchronized (this) {
+ mVCardParser = (vcardVersion == ImportVCardActivity.VCARD_VERSION_V30 ?
+ new VCardParser_V30(vcardType) :
+ new VCardParser_V21(vcardType));
+ if (mCanceled) {
+ mVCardParser.cancel();
+ }
+ }
+ mVCardParser.parse(is, interpreter);
+
+ successful = true;
+ break;
+ } catch (IOException e) {
+ Log.e(LOG_TAG, "IOException was emitted: " + e.getMessage());
+ } catch (VCardNestedException e) {
+ // This exception should not be thrown here. We should instead handle it
+ // in the preprocessing session in ImportVCardActivity, as we don't try
+ // to detect the type of given vCard here.
+ //
+ // TODO: Handle this case appropriately, which should mean we have to have
+ // code trying to auto-detect the type of given vCard twice (both in
+ // ImportVCardActivity and ImportVCardService).
+ Log.e(LOG_TAG, "Nested Exception is found.");
+ } catch (VCardNotSupportedException e) {
+ Log.e(LOG_TAG, e.getMessage());
+ } catch (VCardVersionException e) {
+ if (i == length - 1) {
+ Log.e(LOG_TAG, "Appropriate version for this vCard is not found.");
+ } else {
+ // We'll try the other (v30) version.
+ }
+ } catch (VCardException e) {
+ Log.e(LOG_TAG, e.getMessage());
+ } finally {
+ if (is != null) {
+ try {
+ is.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+ }
+
+ return successful;
+ }
+
+ public synchronized boolean isReadyForRequest() {
+ return mReadyForRequest;
+ }
+
+ public boolean isCanceled() {
+ return mCanceled;
+ }
+
+ public void cancel() {
+ mCanceled = true;
+ synchronized (this) {
+ if (mVCardParser != null) {
+ mVCardParser.cancel();
+ }
+ }
+ }
+
+ public List<Uri> getCreatedUrisForTest() {
+ return mCreatedUris;
+ }
+
+ public List<Uri> getFailedUrisForTest() {
+ return mFailedUris;
+ }
+
+ public void injectCommitterGeneratorForTest(
+ final CommitterGenerator generator) {
+ mCommitterGenerator = generator;
+ }
+
+ public void initNotifierForTest() {
+ mNotifier.init(mContext, mNotificationManager);
+ }
+}
diff --git a/src/com/android/contacts/vcard/ImportProgressNotifier.java b/src/com/android/contacts/vcard/ImportProgressNotifier.java
new file mode 100644
index 0000000..99a6f55
--- /dev/null
+++ b/src/com/android/contacts/vcard/ImportProgressNotifier.java
@@ -0,0 +1,117 @@
+/*
+ * 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.vcard;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.widget.RemoteViews;
+
+import com.android.contacts.R;
+import com.android.vcard.VCardEntry;
+import com.android.vcard.VCardEntryHandler;
+
+/**
+ * {@link VCardEntryHandler} implementation which lets the system update
+ * the current status of vCard import.
+ */
+public class ImportProgressNotifier implements VCardEntryHandler {
+ private Context mContext;
+ private NotificationManager mNotificationManager;
+
+ private int mCurrentCount;
+ private int mTotalCount;
+
+ public void init(Context context, NotificationManager notificationManager) {
+ mContext = context;
+ mNotificationManager = notificationManager;
+ }
+
+ public void onStart() {
+ }
+
+ public void onEntryCreated(VCardEntry contactStruct) {
+ mCurrentCount++; // 1 origin.
+ if (contactStruct.isIgnorable()) {
+ return;
+ }
+
+ // We don't use startEntry() since:
+ // - We cannot know name there but here.
+ // - There's high probability where name comes soon after the beginning of entry, so
+ // we don't need to hurry to show something.
+
+
+ final String totalCountString;
+ synchronized (this) {
+ totalCountString = String.valueOf(mTotalCount);
+ }
+ final String tickerText =
+ mContext.getString(R.string.progress_notifier_message,
+ String.valueOf(mCurrentCount),
+ totalCountString,
+ contactStruct.getDisplayName());
+
+
+ final Context context = mContext.getApplicationContext();
+
+ final String description = mContext.getString(R.string.importing_vcard_description,
+ contactStruct.getDisplayName());
+ final RemoteViews remoteViews =
+ new RemoteViews(context.getPackageName(),
+ R.layout.status_bar_ongoing_event_progress_bar);
+ remoteViews.setTextViewText(R.id.status_description, description);
+ remoteViews.setProgressBar(R.id.status_progress_bar, mTotalCount, mCurrentCount,
+ mTotalCount == -1);
+ final String percentage;
+ if (mTotalCount > 0) {
+ percentage = context.getString(R.string.percentage,
+ String.valueOf(mCurrentCount * 100/mTotalCount));
+ } else {
+ percentage = "";
+ }
+ remoteViews.setTextViewText(R.id.status_progress_text, percentage);
+ remoteViews.setImageViewResource(R.id.status_icon, android.R.drawable.stat_sys_download);
+
+ final Notification notification = new Notification();
+ notification.icon = android.R.drawable.stat_sys_download;
+ notification.tickerText = tickerText;
+ notification.contentView = remoteViews;
+ notification.flags |= Notification.FLAG_ONGOING_EVENT;
+
+ final PendingIntent pendingIntent =
+ PendingIntent.getActivity(context, 0,
+ new Intent(context, CancelImportActivity.class),
+ PendingIntent.FLAG_UPDATE_CURRENT);
+
+ notification.contentIntent = pendingIntent;
+ // notification.setLatestEventInfo(context, title, description, pendingIntent);
+ mNotificationManager.notify(VCardService.IMPORT_NOTIFICATION_ID, notification);
+ }
+
+ public synchronized void addTotalCount(int additionalCount) {
+ mTotalCount += additionalCount;
+ }
+
+ public synchronized void resetTotalCount() {
+ mTotalCount = 0;
+ }
+
+ public void onEnd() {
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/contacts/vcard/ImportRequest.java b/src/com/android/contacts/vcard/ImportRequest.java
new file mode 100644
index 0000000..5d46166
--- /dev/null
+++ b/src/com/android/contacts/vcard/ImportRequest.java
@@ -0,0 +1,86 @@
+/*
+ * 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.vcard;
+
+import android.accounts.Account;
+import android.net.Uri;
+
+import com.android.vcard.VCardSourceDetector;
+
+/**
+ * Class representing one request for importing vCard (given as a Uri).
+ *
+ * Mainly used when {@link ImportVCardActivity} requests {@link VCardService}
+ * to import some specific Uri.
+ *
+ * Note: This object's accepting only One Uri does NOT mean that
+ * there's only one vCard entry inside the instance, as one Uri often has multiple
+ * vCard entries inside it.
+ */
+public class ImportRequest {
+ /**
+ * Can be null (typically when there's no Account available in the system).
+ */
+ public final Account account;
+ public final Uri uri;
+ /**
+ * Can be {@link VCardSourceDetector#PARSE_TYPE_UNKNOWN}.
+ */
+ public final int estimatedVCardType;
+ /**
+ * Can be null, meaning no preferable charset is available.
+ */
+ public final String estimatedCharset;
+ /**
+ * Assumes that one Uri contains only one version, while there's a (tiny) possibility
+ * we may have two types in one vCard.
+ *
+ * e.g.
+ * BEGIN:VCARD
+ * VERSION:2.1
+ * ...
+ * END:VCARD
+ * BEGIN:VCARD
+ * VERSION:3.0
+ * ...
+ * END:VCARD
+ *
+ * We've never seen this kind of a file, but we may have to cope with it in the future.
+ */
+ public final int vcardVersion;
+
+ /**
+ * The count of vCard entries in {@link #uri}. A receiver of this object can use it
+ * when showing the progress of import. Thus a receiver must be able to torelate this
+ * variable being invalid because of vCard's limitation.
+ *
+ * vCard does not let us know this count without looking over a whole file content,
+ * which means we have to open and scan over {@link #uri} to know this value, while
+ * it may not be opened more than once (Uri does not require it to be opened multiple times
+ * and may become invalid after its close() request).
+ */
+ public final int entryCount;
+ public ImportRequest(Account account,
+ Uri uri, int estimatedType, String estimatedCharset,
+ int vcardVersion, int entryCount) {
+ this.account = account;
+ this.uri = uri;
+ this.estimatedVCardType = estimatedType;
+ this.estimatedCharset = estimatedCharset;
+ this.vcardVersion = vcardVersion;
+ this.entryCount = entryCount;
+ }
+}
diff --git a/src/com/android/contacts/vcard/ImportVCardActivity.java b/src/com/android/contacts/vcard/ImportVCardActivity.java
new file mode 100644
index 0000000..1140ff8
--- /dev/null
+++ b/src/com/android/contacts/vcard/ImportVCardActivity.java
@@ -0,0 +1,1039 @@
+/*
+ * Copyright (C) 2009 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.vcard;
+
+import android.accounts.Account;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.ProgressDialog;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.DialogInterface.OnCancelListener;
+import android.content.DialogInterface.OnClickListener;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.TextUtils;
+import android.text.style.RelativeSizeSpan;
+import android.util.Log;
+
+import com.android.contacts.R;
+import com.android.contacts.model.Sources;
+import com.android.contacts.util.AccountSelectionUtil;
+import com.android.vcard.VCardEntryCounter;
+import com.android.vcard.VCardInterpreterCollection;
+import com.android.vcard.VCardParser;
+import com.android.vcard.VCardParser_V21;
+import com.android.vcard.VCardParser_V30;
+import com.android.vcard.VCardSourceDetector;
+import com.android.vcard.exception.VCardException;
+import com.android.vcard.exception.VCardNestedException;
+import com.android.vcard.exception.VCardVersionException;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.channels.Channels;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.WritableByteChannel;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Queue;
+import java.util.Set;
+import java.util.Vector;
+
+/**
+ * The class letting users to import vCard. This includes the UI part for letting them select
+ * an Account and posssibly a file if there's no Uri is given from its caller Activity.
+ *
+ * Note that this Activity assumes that the instance is a "one-shot Activity", which will be
+ * finished (with the method {@link Activity#finish()}) after the import and never reuse
+ * any Dialog in the instance. So this code is careless about the management around managed
+ * dialogs stuffs (like how onCreateDialog() is used).
+ */
+public class ImportVCardActivity extends Activity {
+ private static final String LOG_TAG = "ImportVCardActivity";
+
+ private static final int SELECT_ACCOUNT = 0;
+
+ /* package */ static final String VCARD_URI_ARRAY = "vcard_uri";
+ /* package */ static final String ESTIMATED_VCARD_TYPE_ARRAY = "estimated_vcard_type";
+ /* package */ static final String ESTIMATED_CHARSET_ARRAY = "estimated_charset";
+ /* package */ static final String VCARD_VERSION_ARRAY = "vcard_version";
+ /* package */ static final String ENTRY_COUNT_ARRAY = "entry_count";
+
+ /* package */ final static int VCARD_VERSION_AUTO_DETECT = 0;
+ /* package */ final static int VCARD_VERSION_V21 = 1;
+ /* package */ final static int VCARD_VERSION_V30 = 2;
+
+ private static final String SECURE_DIRECTORY_NAME = ".android_secure";
+
+ final static String CACHED_URIS = "cached_uris";
+
+ private AccountSelectionUtil.AccountSelectedListener mAccountSelectionListener;
+
+ private Account mAccount;
+
+ private String mAction;
+ private Uri mUri;
+
+ private ProgressDialog mProgressDialogForScanVCard;
+ private ProgressDialog mProgressDialogForCacheVCard;
+
+ private List<VCardFile> mAllVCardFileList;
+ private VCardScanThread mVCardScanThread;
+
+ private VCardCacheThread mVCardCacheThread;
+
+ private String mErrorMessage;
+
+ private class CustomConnection implements ServiceConnection {
+ private Messenger mMessenger;
+ /**
+ * Stores {@link ImportRequest} objects until actual connection is established.
+ */
+ private Queue<ImportRequest> mPendingRequests =
+ new LinkedList<ImportRequest>();
+
+ private boolean mConnected = false;
+ private boolean mNeedToCallFinish = false;
+ private boolean mDisconnectAndFinishDone = false;
+
+ public void doBindService() {
+ bindService(new Intent(ImportVCardActivity.this,
+ VCardService.class), this, Context.BIND_AUTO_CREATE);
+ }
+
+ /**
+ * Tries to unbind this connection and call {@link ImportVCardActivity#finish()}.
+ * When timing is not appropriate, this object remembers this call and
+ * call {@link ImportVCardActivity#unbindService(ServiceConnection)} and
+ * {@link ImportVCardActivity#finish()} afterwards.
+ */
+ public void tryDisconnectAndFinish() {
+ synchronized (this) {
+ if (!mDisconnectAndFinishDone) {
+ mNeedToCallFinish = true;
+ if (mConnected) {
+ // onServiceConnected() is already finished and we need to
+ // "manually" call unbindService() here.
+ unbindService(this);
+ mDisconnectAndFinishDone = true;
+ finish();
+ } else {
+ // If not connected, finish() must be called when connected, as
+ // We cann not call finish() now.
+ }
+ }
+ }
+ }
+
+ public void requestSend(final ImportRequest parameter) {
+ synchronized (mPendingRequests) {
+ if (mMessenger != null) {
+ sendMessage(parameter);
+ } else {
+ mPendingRequests.add(parameter);
+ }
+ }
+ }
+
+ private void sendMessage(final ImportRequest request) {
+ try {
+ mMessenger.send(Message.obtain(null,
+ VCardService.MSG_IMPORT_REQUEST,
+ request));
+ } catch (RemoteException e) {
+ Log.e(LOG_TAG, "RemoteException is thrown when trying to send request");
+ runOnUiThread(new DialogDisplayer(
+ getString(R.string.fail_reason_unknown)));
+ }
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ synchronized (mPendingRequests) {
+ mMessenger = new Messenger(service);
+
+ // Send pending requests thrown from this Activity before an actual connection
+ // is established.
+ while (!mPendingRequests.isEmpty()) {
+ final ImportRequest parameter = mPendingRequests.poll();
+ if (parameter == null) {
+ throw new NullPointerException();
+ }
+ sendMessage(parameter);
+ }
+ }
+
+ synchronized (this) {
+ if (!mDisconnectAndFinishDone) {
+ mConnected = true;
+ if (mNeedToCallFinish) {
+ mDisconnectAndFinishDone = true;
+ finish();
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ synchronized (mPendingRequests) {
+ if (!mPendingRequests.isEmpty()) {
+ Log.w(LOG_TAG, "Some request(s) are dropped.");
+ }
+ }
+
+ // Set to null so that we can detect inappropriate re-connection toward
+ // the Service via NullPointerException;
+ mPendingRequests = null;
+ mMessenger = null;
+ }
+ }
+
+ private final CustomConnection mConnection = new CustomConnection();
+
+ private static class VCardFile {
+ private final String mName;
+ private final String mCanonicalPath;
+ private final long mLastModified;
+
+ public VCardFile(String name, String canonicalPath, long lastModified) {
+ mName = name;
+ mCanonicalPath = canonicalPath;
+ mLastModified = lastModified;
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ public String getCanonicalPath() {
+ return mCanonicalPath;
+ }
+
+ public long getLastModified() {
+ return mLastModified;
+ }
+ }
+
+ // Runs on the UI thread.
+ private class DialogDisplayer implements Runnable {
+ private final int mResId;
+ public DialogDisplayer(int resId) {
+ mResId = resId;
+ }
+ public DialogDisplayer(String errorMessage) {
+ mResId = R.id.dialog_error_with_message;
+ mErrorMessage = errorMessage;
+ }
+ public void run() {
+ showDialog(mResId);
+ }
+ }
+
+ private class CancelListener
+ implements DialogInterface.OnClickListener, DialogInterface.OnCancelListener {
+ public void onClick(DialogInterface dialog, int which) {
+ finish();
+ }
+
+ public void onCancel(DialogInterface dialog) {
+ finish();
+ }
+ }
+
+ private CancelListener mCancelListener = new CancelListener();
+
+ /**
+ * Caches all vCard data into local data directory so that we allow
+ * {@link VCardService} to access all the contents in given Uris, some of
+ * which may not be accessible from other components due to permission problem.
+ * (Activity which gives the Uri may allow only this Activity to access that content,
+ * not the other components like {@link VCardService}.
+ *
+ * We also allow the Service to happen to exit during the vCard import procedure.
+ */
+ private class VCardCacheThread extends Thread
+ implements DialogInterface.OnCancelListener {
+ private static final String CACHE_FILE_PREFIX = "import_tmp_";
+ private boolean mCanceled;
+ private PowerManager.WakeLock mWakeLock;
+ private VCardParser mVCardParser;
+ private final Uri[] mSourceUris;
+
+ public VCardCacheThread(final Uri[] sourceUris) {
+ mSourceUris = sourceUris;
+ final int length = sourceUris.length;
+ final Context context = ImportVCardActivity.this;
+ final PowerManager powerManager =
+ (PowerManager)context.getSystemService(Context.POWER_SERVICE);
+ mWakeLock = powerManager.newWakeLock(
+ PowerManager.SCREEN_DIM_WAKE_LOCK |
+ PowerManager.ON_AFTER_RELEASE, LOG_TAG);
+ }
+
+ @Override
+ public void finalize() {
+ if (mWakeLock != null && mWakeLock.isHeld()) {
+ mWakeLock.release();
+ }
+ }
+
+ @Override
+ public void run() {
+ final Context context = ImportVCardActivity.this;
+ final ContentResolver resolver = context.getContentResolver();
+ String errorMessage = null;
+ mWakeLock.acquire();
+ try {
+ clearOldCache();
+ mConnection.doBindService();
+
+ final int length = mSourceUris.length;
+ // Uris given from caller applications may not be opened twice: consider when
+ // it is not from local storage (e.g. "file:///...") but from some special
+ // provider (e.g. "content://...").
+ // Thus we have to once copy the content of Uri into local storage, and read
+ // it after it. This copy is also useful fro the view of stability of the import,
+ // as we are able to restore the procedure even when it is aborted during it.
+ // Imagine the case the importer encountered memory-low situation when
+ // reading 10th entry of a vCard file.
+ //
+ // We may be able to read content of each vCard file during copying them
+ // to local storage, but currently vCard code does not allow us to do so.
+ for (int i = 0; i < length; i++) {
+ final Uri sourceUri = mSourceUris[i];
+ final Uri localDataUri = copyToLocal(sourceUri, i);
+ if (mCanceled) {
+ break;
+ }
+ if (localDataUri == null) {
+ Log.w(LOG_TAG, "destUri is null");
+ break;
+ }
+ final ImportRequest parameter = constructRequestParameter(localDataUri);
+ if (mCanceled) {
+ return;
+ }
+ mConnection.requestSend(parameter);
+ }
+ } catch (OutOfMemoryError e) {
+ Log.e(LOG_TAG, "OutOfMemoryError");
+ // We should take care of this case since Android devices may have
+ // smaller memory than we usually expect.
+ System.gc();
+ runOnUiThread(new DialogDisplayer(
+ getString(R.string.fail_reason_low_memory_during_import)));
+ } catch (IOException e) {
+ Log.e(LOG_TAG, e.getMessage());
+ runOnUiThread(new DialogDisplayer(
+ getString(R.string.fail_reason_io_error)));
+ } finally {
+ mWakeLock.release();
+ mProgressDialogForCacheVCard.dismiss();
+ mConnection.tryDisconnectAndFinish();
+ }
+ }
+
+ /**
+ * Copy the content of sourceUri to local storage.
+ */
+ private Uri copyToLocal(final Uri sourceUri, int i) throws IOException {
+ final Context context = ImportVCardActivity.this;
+ final ContentResolver resolver = context.getContentResolver();
+ ReadableByteChannel inputChannel = null;
+ WritableByteChannel outputChannel = null;
+ Uri destUri;
+ try {
+ // XXX: better way to copy stream?
+ {
+ inputChannel = Channels.newChannel(resolver.openInputStream(mSourceUris[i]));
+ final String filename = CACHE_FILE_PREFIX + i + ".vcf";
+ destUri = Uri.parse(context.getFileStreamPath(filename).toURI().toString());
+ outputChannel =
+ context.openFileOutput(filename, Context.MODE_PRIVATE).getChannel();
+ final ByteBuffer buffer = ByteBuffer.allocateDirect(8192);
+ while (inputChannel.read(buffer) != -1) {
+ if (mCanceled) {
+ Log.d(LOG_TAG, "Canceled during caching " + mSourceUris[i]);
+ return null;
+ }
+ buffer.flip();
+ outputChannel.write(buffer);
+ buffer.compact();
+ }
+ buffer.flip();
+ while (buffer.hasRemaining()) {
+ outputChannel.write(buffer);
+ }
+ }
+ } finally {
+ if (inputChannel != null) {
+ try {
+ inputChannel.close();
+ } catch (IOException e) {
+ Log.w(LOG_TAG, "Failed to close inputChannel.");
+ }
+ }
+ if (outputChannel != null) {
+ try {
+ outputChannel.close();
+ } catch(IOException e) {
+ Log.w(LOG_TAG, "Failed to close outputChannel");
+ }
+ }
+ }
+ return destUri;
+ }
+
+ /**
+ * Reads the Uri once (or twice) and constructs {@link ImportRequest} from
+ * its content.
+ */
+ private ImportRequest constructRequestParameter(final Uri uri) {
+ final ContentResolver resolver =
+ ImportVCardActivity.this.getContentResolver();
+ VCardEntryCounter counter = null;
+ VCardSourceDetector detector = null;
+ VCardInterpreterCollection interpreter = null;
+ int vcardVersion = VCARD_VERSION_V21;
+ try {
+ boolean shouldUseV30 = false;
+ InputStream is;
+
+ is = resolver.openInputStream(uri);
+ mVCardParser = new VCardParser_V21();
+ try {
+ counter = new VCardEntryCounter();
+ detector = new VCardSourceDetector();
+ interpreter =
+ new VCardInterpreterCollection(
+ Arrays.asList(counter, detector));
+ mVCardParser.parse(is, interpreter);
+ } catch (VCardVersionException e1) {
+ try {
+ is.close();
+ } catch (IOException e) {
+ }
+
+ shouldUseV30 = true;
+ is = resolver.openInputStream(uri);
+ mVCardParser = new VCardParser_V30();
+ try {
+ counter = new VCardEntryCounter();
+ detector = new VCardSourceDetector();
+ interpreter =
+ new VCardInterpreterCollection(
+ Arrays.asList(counter, detector));
+ mVCardParser.parse(is, interpreter);
+ } catch (VCardVersionException e2) {
+ throw new VCardException("vCard with unspported version.");
+ }
+ } finally {
+ if (is != null) {
+ try {
+ is.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+
+ vcardVersion = shouldUseV30 ? VCARD_VERSION_V30 : VCARD_VERSION_V21;
+ } catch (VCardNestedException e) {
+ Log.w(LOG_TAG, "Nested Exception is found (it may be false-positive).");
+ // Go through without returning null.
+ } catch (VCardException e) {
+ Log.e(LOG_TAG, e.getMessage());
+ return null;
+ } catch (IOException e) {
+ Log.e(LOG_TAG, "IOException was emitted: " + e.getMessage());
+ return null;
+ }
+ return new ImportRequest(mAccount, uri,
+ detector.getEstimatedType(),
+ detector.getEstimatedCharset(),
+ vcardVersion, counter.getCount());
+ }
+
+ /**
+ * We (currently) don't have any way to clean up cache files used in the previous
+ * import process,
+ * TODO(dmiyakawa): Can we do it after Service being done?
+ */
+ private void clearOldCache() {
+ final Context context = ImportVCardActivity.this;
+ final String[] fileLists = context.fileList();
+ for (String fileName : fileLists) {
+ if (fileName.startsWith(CACHE_FILE_PREFIX)) {
+ Log.d(LOG_TAG, "Remove temporary file: " + fileName);
+ context.deleteFile(fileName);
+ }
+ }
+ }
+
+ public Uri[] getSourceUris() {
+ return mSourceUris;
+ }
+
+ public void cancel() {
+ mCanceled = true;
+ if (mVCardParser != null) {
+ mVCardParser.cancel();
+ }
+ }
+
+ public void onCancel(DialogInterface dialog) {
+ cancel();
+ }
+ }
+
+ private class ImportTypeSelectedListener implements
+ DialogInterface.OnClickListener {
+ public static final int IMPORT_ONE = 0;
+ public static final int IMPORT_MULTIPLE = 1;
+ public static final int IMPORT_ALL = 2;
+ public static final int IMPORT_TYPE_SIZE = 3;
+
+ private int mCurrentIndex;
+
+ public void onClick(DialogInterface dialog, int which) {
+ if (which == DialogInterface.BUTTON_POSITIVE) {
+ switch (mCurrentIndex) {
+ case IMPORT_ALL:
+ importVCardFromSDCard(mAllVCardFileList);
+ break;
+ case IMPORT_MULTIPLE:
+ showDialog(R.id.dialog_select_multiple_vcard);
+ break;
+ default:
+ showDialog(R.id.dialog_select_one_vcard);
+ break;
+ }
+ } else if (which == DialogInterface.BUTTON_NEGATIVE) {
+ finish();
+ } else {
+ mCurrentIndex = which;
+ }
+ }
+ }
+
+ private class VCardSelectedListener implements
+ DialogInterface.OnClickListener, DialogInterface.OnMultiChoiceClickListener {
+ private int mCurrentIndex;
+ private Set<Integer> mSelectedIndexSet;
+
+ public VCardSelectedListener(boolean multipleSelect) {
+ mCurrentIndex = 0;
+ if (multipleSelect) {
+ mSelectedIndexSet = new HashSet<Integer>();
+ }
+ }
+
+ public void onClick(DialogInterface dialog, int which) {
+ if (which == DialogInterface.BUTTON_POSITIVE) {
+ if (mSelectedIndexSet != null) {
+ List<VCardFile> selectedVCardFileList = new ArrayList<VCardFile>();
+ final int size = mAllVCardFileList.size();
+ // We'd like to sort the files by its index, so we do not use Set iterator.
+ for (int i = 0; i < size; i++) {
+ if (mSelectedIndexSet.contains(i)) {
+ selectedVCardFileList.add(mAllVCardFileList.get(i));
+ }
+ }
+ importVCardFromSDCard(selectedVCardFileList);
+ } else {
+ importVCardFromSDCard(mAllVCardFileList.get(mCurrentIndex));
+ }
+ } else if (which == DialogInterface.BUTTON_NEGATIVE) {
+ finish();
+ } else {
+ // Some file is selected.
+ mCurrentIndex = which;
+ if (mSelectedIndexSet != null) {
+ if (mSelectedIndexSet.contains(which)) {
+ mSelectedIndexSet.remove(which);
+ } else {
+ mSelectedIndexSet.add(which);
+ }
+ }
+ }
+ }
+
+ public void onClick(DialogInterface dialog, int which, boolean isChecked) {
+ if (mSelectedIndexSet == null || (mSelectedIndexSet.contains(which) == isChecked)) {
+ Log.e(LOG_TAG, String.format("Inconsist state in index %d (%s)", which,
+ mAllVCardFileList.get(which).getCanonicalPath()));
+ } else {
+ onClick(dialog, which);
+ }
+ }
+ }
+
+ /**
+ * Thread scanning VCard from SDCard. After scanning, the dialog which lets a user select
+ * a vCard file is shown. After the choice, VCardReadThread starts running.
+ */
+ private class VCardScanThread extends Thread implements OnCancelListener, OnClickListener {
+ private boolean mCanceled;
+ private boolean mGotIOException;
+ private File mRootDirectory;
+
+ // To avoid recursive link.
+ private Set<String> mCheckedPaths;
+ private PowerManager.WakeLock mWakeLock;
+
+ private class CanceledException extends Exception {
+ }
+
+ public VCardScanThread(File sdcardDirectory) {
+ mCanceled = false;
+ mGotIOException = false;
+ mRootDirectory = sdcardDirectory;
+ mCheckedPaths = new HashSet<String>();
+ PowerManager powerManager = (PowerManager)ImportVCardActivity.this.getSystemService(
+ Context.POWER_SERVICE);
+ mWakeLock = powerManager.newWakeLock(
+ PowerManager.SCREEN_DIM_WAKE_LOCK |
+ PowerManager.ON_AFTER_RELEASE, LOG_TAG);
+ }
+
+ @Override
+ public void run() {
+ mAllVCardFileList = new Vector<VCardFile>();
+ try {
+ mWakeLock.acquire();
+ getVCardFileRecursively(mRootDirectory);
+ } catch (CanceledException e) {
+ mCanceled = true;
+ } catch (IOException e) {
+ mGotIOException = true;
+ } finally {
+ mWakeLock.release();
+ }
+
+ if (mCanceled) {
+ mAllVCardFileList = null;
+ }
+
+ mProgressDialogForScanVCard.dismiss();
+ mProgressDialogForScanVCard = null;
+
+ if (mGotIOException) {
+ runOnUiThread(new DialogDisplayer(R.id.dialog_io_exception));
+ } else if (mCanceled) {
+ finish();
+ } else {
+ int size = mAllVCardFileList.size();
+ final Context context = ImportVCardActivity.this;
+ if (size == 0) {
+ runOnUiThread(new DialogDisplayer(R.id.dialog_vcard_not_found));
+ } else {
+ startVCardSelectAndImport();
+ }
+ }
+ }
+
+ private void getVCardFileRecursively(File directory)
+ throws CanceledException, IOException {
+ if (mCanceled) {
+ throw new CanceledException();
+ }
+
+ // e.g. secured directory may return null toward listFiles().
+ final File[] files = directory.listFiles();
+ if (files == null) {
+ final String currentDirectoryPath = directory.getCanonicalPath();
+ final String secureDirectoryPath =
+ mRootDirectory.getCanonicalPath().concat(SECURE_DIRECTORY_NAME);
+ if (!TextUtils.equals(currentDirectoryPath, secureDirectoryPath)) {
+ Log.w(LOG_TAG, "listFiles() returned null (directory: " + directory + ")");
+ }
+ return;
+ }
+ for (File file : directory.listFiles()) {
+ if (mCanceled) {
+ throw new CanceledException();
+ }
+ String canonicalPath = file.getCanonicalPath();
+ if (mCheckedPaths.contains(canonicalPath)) {
+ continue;
+ }
+
+ mCheckedPaths.add(canonicalPath);
+
+ if (file.isDirectory()) {
+ getVCardFileRecursively(file);
+ } else if (canonicalPath.toLowerCase().endsWith(".vcf") &&
+ file.canRead()){
+ String fileName = file.getName();
+ VCardFile vcardFile = new VCardFile(
+ fileName, canonicalPath, file.lastModified());
+ mAllVCardFileList.add(vcardFile);
+ }
+ }
+ }
+
+ public void onCancel(DialogInterface dialog) {
+ mCanceled = true;
+ }
+
+ public void onClick(DialogInterface dialog, int which) {
+ if (which == DialogInterface.BUTTON_NEGATIVE) {
+ mCanceled = true;
+ }
+ }
+ }
+
+ private void startVCardSelectAndImport() {
+ int size = mAllVCardFileList.size();
+ if (getResources().getBoolean(R.bool.config_import_all_vcard_from_sdcard_automatically) ||
+ size == 1) {
+ importVCardFromSDCard(mAllVCardFileList);
+ } else if (getResources().getBoolean(R.bool.config_allow_users_select_all_vcard_import)) {
+ runOnUiThread(new DialogDisplayer(R.id.dialog_select_import_type));
+ } else {
+ runOnUiThread(new DialogDisplayer(R.id.dialog_select_one_vcard));
+ }
+ }
+
+ private void importVCardFromSDCard(final List<VCardFile> selectedVCardFileList) {
+ final int size = selectedVCardFileList.size();
+ String[] uriStrings = new String[size];
+ int i = 0;
+ for (VCardFile vcardFile : selectedVCardFileList) {
+ uriStrings[i] = "file://" + vcardFile.getCanonicalPath();
+ i++;
+ }
+ importVCard(uriStrings);
+ }
+
+ private void importVCardFromSDCard(final VCardFile vcardFile) {
+ importVCard(new Uri[] {Uri.parse("file://" + vcardFile.getCanonicalPath())});
+ }
+
+ private void importVCard(final Uri uri) {
+ importVCard(new Uri[] {uri});
+ }
+
+ private void importVCard(final String[] uriStrings) {
+ final int length = uriStrings.length;
+ final Uri[] uris = new Uri[length];
+ for (int i = 0; i < length; i++) {
+ uris[i] = Uri.parse(uriStrings[i]);
+ }
+ importVCard(uris);
+ }
+
+ private void importVCard(final Uri[] uris) {
+ runOnUiThread(new Runnable() {
+ public void run() {
+ mVCardCacheThread = new VCardCacheThread(uris);
+ showDialog(R.id.dialog_cache_vcard);
+ }
+ });
+ }
+
+ private Dialog getSelectImportTypeDialog() {
+ final DialogInterface.OnClickListener listener = new ImportTypeSelectedListener();
+ final AlertDialog.Builder builder = new AlertDialog.Builder(this)
+ .setTitle(R.string.select_vcard_title)
+ .setPositiveButton(android.R.string.ok, listener)
+ .setOnCancelListener(mCancelListener)
+ .setNegativeButton(android.R.string.cancel, mCancelListener);
+
+ final String[] items = new String[ImportTypeSelectedListener.IMPORT_TYPE_SIZE];
+ items[ImportTypeSelectedListener.IMPORT_ONE] =
+ getString(R.string.import_one_vcard_string);
+ items[ImportTypeSelectedListener.IMPORT_MULTIPLE] =
+ getString(R.string.import_multiple_vcard_string);
+ items[ImportTypeSelectedListener.IMPORT_ALL] =
+ getString(R.string.import_all_vcard_string);
+ builder.setSingleChoiceItems(items, ImportTypeSelectedListener.IMPORT_ONE, listener);
+ return builder.create();
+ }
+
+ private Dialog getVCardFileSelectDialog(boolean multipleSelect) {
+ final int size = mAllVCardFileList.size();
+ final VCardSelectedListener listener = new VCardSelectedListener(multipleSelect);
+ final AlertDialog.Builder builder =
+ new AlertDialog.Builder(this)
+ .setTitle(R.string.select_vcard_title)
+ .setPositiveButton(android.R.string.ok, listener)
+ .setOnCancelListener(mCancelListener)
+ .setNegativeButton(android.R.string.cancel, mCancelListener);
+
+ CharSequence[] items = new CharSequence[size];
+ DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+ for (int i = 0; i < size; i++) {
+ VCardFile vcardFile = mAllVCardFileList.get(i);
+ SpannableStringBuilder stringBuilder = new SpannableStringBuilder();
+ stringBuilder.append(vcardFile.getName());
+ stringBuilder.append('\n');
+ int indexToBeSpanned = stringBuilder.length();
+ // Smaller date text looks better, since each file name becomes easier to read.
+ // The value set to RelativeSizeSpan is arbitrary. You can change it to any other
+ // value (but the value bigger than 1.0f would not make nice appearance :)
+ stringBuilder.append(
+ "(" + dateFormat.format(new Date(vcardFile.getLastModified())) + ")");
+ stringBuilder.setSpan(
+ new RelativeSizeSpan(0.7f), indexToBeSpanned, stringBuilder.length(),
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ items[i] = stringBuilder;
+ }
+ if (multipleSelect) {
+ builder.setMultiChoiceItems(items, (boolean[])null, listener);
+ } else {
+ builder.setSingleChoiceItems(items, 0, listener);
+ }
+ return builder.create();
+ }
+
+ @Override
+ protected void onCreate(Bundle bundle) {
+ super.onCreate(bundle);
+
+ String accountName = null;
+ String accountType = null;
+ final Intent intent = getIntent();
+ if (intent != null) {
+ accountName = intent.getStringExtra(SelectAccountActivity.ACCOUNT_NAME);
+ accountType = intent.getStringExtra(SelectAccountActivity.ACCOUNT_TYPE);
+ mAction = intent.getAction();
+ mUri = intent.getData();
+ } else {
+ Log.e(LOG_TAG, "intent does not exist");
+ }
+
+ if (!TextUtils.isEmpty(accountName) && !TextUtils.isEmpty(accountType)) {
+ mAccount = new Account(accountName, accountType);
+ } else {
+ final Sources sources = Sources.getInstance(this);
+ final List<Account> accountList = sources.getAccounts(true);
+ if (accountList.size() == 0) {
+ mAccount = null;
+ } else if (accountList.size() == 1) {
+ mAccount = accountList.get(0);
+ } else {
+ startActivityForResult(new Intent(this, SelectAccountActivity.class),
+ SELECT_ACCOUNT);
+ return;
+ }
+ }
+
+ startImport(mAction, mUri);
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent intent) {
+ if (requestCode == SELECT_ACCOUNT) {
+ if (resultCode == RESULT_OK) {
+ mAccount = new Account(
+ intent.getStringExtra(SelectAccountActivity.ACCOUNT_NAME),
+ intent.getStringExtra(SelectAccountActivity.ACCOUNT_TYPE));
+ startImport(mAction, mUri);
+ } else {
+ if (resultCode != RESULT_CANCELED) {
+ Log.w(LOG_TAG, "Result code was not OK nor CANCELED: " + resultCode);
+ }
+ finish();
+ }
+ }
+ }
+
+ private void startImport(String action, Uri uri) {
+ Log.d(LOG_TAG, "action = " + action + " ; path = " + uri);
+
+ if (uri != null) {
+ importVCard(uri);
+ } else {
+ doScanExternalStorageAndImportVCard();
+ }
+ }
+
+ @Override
+ protected Dialog onCreateDialog(int resId, Bundle bundle) {
+ switch (resId) {
+ case R.string.import_from_sdcard: {
+ if (mAccountSelectionListener == null) {
+ throw new NullPointerException(
+ "mAccountSelectionListener must not be null.");
+ }
+ return AccountSelectionUtil.getSelectAccountDialog(this, resId,
+ mAccountSelectionListener, mCancelListener);
+ }
+ case R.id.dialog_searching_vcard: {
+ if (mProgressDialogForScanVCard == null) {
+ String title = getString(R.string.searching_vcard_title);
+ String message = getString(R.string.searching_vcard_message);
+ mProgressDialogForScanVCard =
+ ProgressDialog.show(this, title, message, true, false);
+ mProgressDialogForScanVCard.setOnCancelListener(mVCardScanThread);
+ mVCardScanThread.start();
+ }
+ return mProgressDialogForScanVCard;
+ }
+ case R.id.dialog_sdcard_not_found: {
+ AlertDialog.Builder builder = new AlertDialog.Builder(this)
+ .setTitle(R.string.no_sdcard_title)
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .setMessage(R.string.no_sdcard_message)
+ .setOnCancelListener(mCancelListener)
+ .setPositiveButton(android.R.string.ok, mCancelListener);
+ return builder.create();
+ }
+ case R.id.dialog_vcard_not_found: {
+ String message = (getString(R.string.scanning_sdcard_failed_message,
+ getString(R.string.fail_reason_no_vcard_file)));
+ AlertDialog.Builder builder = new AlertDialog.Builder(this)
+ .setTitle(R.string.scanning_sdcard_failed_title)
+ .setMessage(message)
+ .setOnCancelListener(mCancelListener)
+ .setPositiveButton(android.R.string.ok, mCancelListener);
+ return builder.create();
+ }
+ case R.id.dialog_select_import_type: {
+ return getSelectImportTypeDialog();
+ }
+ case R.id.dialog_select_multiple_vcard: {
+ return getVCardFileSelectDialog(true);
+ }
+ case R.id.dialog_select_one_vcard: {
+ return getVCardFileSelectDialog(false);
+ }
+ case R.id.dialog_cache_vcard: {
+ if (mProgressDialogForCacheVCard == null) {
+ final String title = getString(R.string.caching_vcard_title);
+ final String message = getString(R.string.caching_vcard_message);
+ mProgressDialogForCacheVCard = new ProgressDialog(this);
+ mProgressDialogForCacheVCard.setTitle(title);
+ mProgressDialogForCacheVCard.setMessage(message);
+ mProgressDialogForCacheVCard.setProgressStyle(ProgressDialog.STYLE_SPINNER);
+ mProgressDialogForCacheVCard.setOnCancelListener(mVCardCacheThread);
+ mVCardCacheThread.start();
+ }
+ return mProgressDialogForCacheVCard;
+ }
+ case R.id.dialog_io_exception: {
+ String message = (getString(R.string.scanning_sdcard_failed_message,
+ getString(R.string.fail_reason_io_error)));
+ AlertDialog.Builder builder = new AlertDialog.Builder(this)
+ .setTitle(R.string.scanning_sdcard_failed_title)
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .setMessage(message)
+ .setOnCancelListener(mCancelListener)
+ .setPositiveButton(android.R.string.ok, mCancelListener);
+ return builder.create();
+ }
+ case R.id.dialog_error_with_message: {
+ String message = mErrorMessage;
+ if (TextUtils.isEmpty(message)) {
+ Log.e(LOG_TAG, "Error message is null while it must not.");
+ message = getString(R.string.fail_reason_unknown);
+ }
+ final AlertDialog.Builder builder = new AlertDialog.Builder(this)
+ .setTitle(getString(R.string.reading_vcard_failed_title))
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .setMessage(message)
+ .setOnCancelListener(mCancelListener)
+ .setPositiveButton(android.R.string.ok, mCancelListener);
+ return builder.create();
+ }
+ }
+
+ return super.onCreateDialog(resId, bundle);
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ if (mVCardCacheThread != null) {
+ final Uri[] uris = mVCardCacheThread.getSourceUris();
+ final int length = uris.length;
+ final String[] uriStrings = new String[length];
+ for (int i = 0; i < length; i++) {
+ uriStrings[i] = uris[i].toString();
+ }
+ outState.putStringArray(CACHED_URIS, uriStrings);
+
+ mVCardCacheThread.cancel();
+ }
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Bundle inState) {
+ final String[] uriStrings = inState.getStringArray(CACHED_URIS);
+ if (uriStrings != null && uriStrings.length > 0) {
+ final int length = uriStrings.length;
+ final Uri[] uris = new Uri[length];
+ for (int i = 0; i < length; i++) {
+ uris[i] = Uri.parse(uriStrings[i]);
+ }
+
+ mVCardCacheThread = new VCardCacheThread(uris);
+ }
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+
+ mConnection.tryDisconnectAndFinish();
+
+ // ImportVCardActivity should not be persistent. In other words, if there's some
+ // event calling onPause(), this Activity should finish its work and give the main
+ // screen back to the caller Activity.
+ if (!isFinishing()) {
+ finish();
+ }
+ }
+
+ /**
+ * Scans vCard in external storage (typically SDCard) and tries to import it.
+ * - When there's no SDCard available, an error dialog is shown.
+ * - When multiple vCard files are available, asks a user to select one.
+ */
+ private void doScanExternalStorageAndImportVCard() {
+ // TODO: should use getExternalStorageState().
+ final File file = Environment.getExternalStorageDirectory();
+ if (!file.exists() || !file.isDirectory() || !file.canRead()) {
+ showDialog(R.id.dialog_sdcard_not_found);
+ } else {
+ mVCardScanThread = new VCardScanThread(file);
+ showDialog(R.id.dialog_searching_vcard);
+ }
+ }
+}
diff --git a/src/com/android/contacts/vcard/SelectAccountActivity.java b/src/com/android/contacts/vcard/SelectAccountActivity.java
new file mode 100644
index 0000000..dfd5196
--- /dev/null
+++ b/src/com/android/contacts/vcard/SelectAccountActivity.java
@@ -0,0 +1,107 @@
+/*
+ * 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.vcard;
+
+import android.accounts.Account;
+import android.app.Activity;
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+
+import com.android.contacts.R;
+import com.android.contacts.model.Sources;
+import com.android.contacts.util.AccountSelectionUtil;
+
+import java.util.List;
+
+public class SelectAccountActivity extends Activity {
+ private static final String LOG_TAG = "SelectAccountActivity";
+
+ public static final String ACCOUNT_NAME = "account_name";
+ public static final String ACCOUNT_TYPE = "account_type";
+
+ private class CancelListener
+ implements DialogInterface.OnClickListener, DialogInterface.OnCancelListener {
+ public void onClick(DialogInterface dialog, int which) {
+ finish();
+ }
+ public void onCancel(DialogInterface dialog) {
+ finish();
+ }
+ }
+
+ private AccountSelectionUtil.AccountSelectedListener mAccountSelectionListener;
+
+ @Override
+ protected void onCreate(Bundle bundle) {
+ super.onCreate(bundle);
+
+ // There's three possibilities:
+ // - more than one accounts -> ask the user
+ // - just one account -> use the account without asking the user
+ // - no account -> use phone-local storage without asking the user
+ final int resId = R.string.import_from_sdcard;
+ final Sources sources = Sources.getInstance(this);
+ final List<Account> accountList = sources.getAccounts(true);
+ if (accountList.size() == 0) {
+ Log.w(LOG_TAG, "Account does not exist");
+ finish();
+ } else if (accountList.size() == 1) {
+ final Account account = accountList.get(0);
+ final Intent intent = new Intent();
+ intent.putExtra(ACCOUNT_NAME, account.name);
+ intent.putExtra(ACCOUNT_TYPE, account.type);
+ setResult(RESULT_OK, intent);
+ finish();
+ }
+
+ // Multiple accounts. Let users to select one.
+ mAccountSelectionListener =
+ new AccountSelectionUtil.AccountSelectedListener(
+ this, accountList, resId) {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.dismiss();
+ final Account account = mAccountList.get(which);
+ final Intent intent = new Intent();
+ intent.putExtra(ACCOUNT_NAME, account.name);
+ intent.putExtra(ACCOUNT_TYPE, account.type);
+ setResult(RESULT_OK, intent);
+ finish();
+ }
+ };
+ showDialog(resId);
+ return;
+ }
+
+ @Override
+ protected Dialog onCreateDialog(int resId, Bundle bundle) {
+ switch (resId) {
+ case R.string.import_from_sdcard: {
+ if (mAccountSelectionListener == null) {
+ throw new NullPointerException(
+ "mAccountSelectionListener must not be null.");
+ }
+ return AccountSelectionUtil.getSelectAccountDialog(this, resId,
+ mAccountSelectionListener,
+ new CancelListener());
+ }
+ }
+ return super.onCreateDialog(resId, bundle);
+ }
+}
diff --git a/src/com/android/contacts/SearchResultsActivity.java b/src/com/android/contacts/vcard/ThreadStarter.java
similarity index 63%
copy from src/com/android/contacts/SearchResultsActivity.java
copy to src/com/android/contacts/vcard/ThreadStarter.java
index 09f0014..d7adad6 100644
--- a/src/com/android/contacts/SearchResultsActivity.java
+++ b/src/com/android/contacts/vcard/ThreadStarter.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2007 The Android Open Source Project
+ * 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.
@@ -13,11 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.contacts;
+package com.android.contacts.vcard;
-/**
- * The activity that displays the list of contact search results. We need a separate
- * class because it uses a different theme from {@link ContactsListActivity}.
- */
-public class SearchResultsActivity extends ContactsListActivity {
-}
+public interface ThreadStarter {
+ public void start();
+}
\ No newline at end of file
diff --git a/src/com/android/contacts/vcard/VCardService.java b/src/com/android/contacts/vcard/VCardService.java
new file mode 100644
index 0000000..02daf78
--- /dev/null
+++ b/src/com/android/contacts/vcard/VCardService.java
@@ -0,0 +1,114 @@
+/*
+ * 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.vcard;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.util.Log;
+import android.widget.Toast;
+
+import com.android.contacts.R;
+
+/**
+ * The class responsible for importing vCard from one ore multiple Uris.
+ */
+public class VCardService extends Service {
+ private final static String LOG_TAG = VCardService.class.getSimpleName();
+
+ /* package */ static final int MSG_IMPORT_REQUEST = 1;
+ /* package */ static final int MSG_EXPORT_REQUEST = 2;
+ /* package */ static final int MSG_CANCEL_IMPORT_REQUEST = 3;
+ /* package */ static final int MSG_NOTIFY_IMPORT_FINISHED = 5;
+
+ /* package */ static final int IMPORT_NOTIFICATION_ID = 1000;
+ /* package */ static final int EXPORT_NOTIFICATION_ID = 1001;
+
+ /**
+ * Small vCard file is imported soon, so any meassage saying "vCard import started" is
+ * not needed. We show the message when the size of vCard is larger than this constant.
+ */
+ private static final int IMPORT_NOTIFICATION_THRESHOLD = 10;
+
+ public class ImportRequestHandler extends Handler {
+ private ImportProcessor mImportProcessor;
+ private ExportProcessor mExportProcessor = new ExportProcessor(VCardService.this);
+
+ public ImportRequestHandler() {
+ super();
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_IMPORT_REQUEST: {
+ final ImportRequest parameter = (ImportRequest)msg.obj;
+
+ if (mImportProcessor == null) {
+ mImportProcessor = new ImportProcessor(VCardService.this);
+ } else if (mImportProcessor.isCanceled()) {
+ Log.i(LOG_TAG, "Existing ImporterProcessor is canceled. create another.");
+ mImportProcessor = new ImportProcessor(VCardService.this);
+ }
+
+ mImportProcessor.pushRequest(parameter);
+ if (parameter.entryCount > IMPORT_NOTIFICATION_THRESHOLD) {
+ Toast.makeText(VCardService.this,
+ getString(R.string.vcard_importer_start_message),
+ Toast.LENGTH_LONG).show();
+ }
+ break;
+ }
+ case MSG_EXPORT_REQUEST: {
+ final ExportRequest parameter = (ExportRequest)msg.obj;
+ mExportProcessor.pushRequest(parameter);
+ Toast.makeText(VCardService.this,
+ getString(R.string.vcard_exporter_start_message),
+ Toast.LENGTH_LONG).show();
+ break;
+ }
+ case MSG_CANCEL_IMPORT_REQUEST: {
+ mImportProcessor.cancel();
+ break;
+ }
+ case MSG_NOTIFY_IMPORT_FINISHED: {
+ Log.d(LOG_TAG, "MSG_NOTIFY_IMPORT_FINISHED");
+ break;
+ }
+ default: {
+ Log.e(LOG_TAG, "Unknown request type: " + msg.what);
+ super.hasMessages(msg.what);
+ }
+ }
+ }
+ }
+
+ private ImportRequestHandler mHandler = new ImportRequestHandler();
+ private Messenger mMessenger = new Messenger(mHandler);
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int id) {
+ return START_STICKY;
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return mMessenger.getBinder();
+ }
+}
diff --git a/src/com/android/contacts/views/ContactLoader.java b/src/com/android/contacts/views/ContactLoader.java
new file mode 100644
index 0000000..1cb003e
--- /dev/null
+++ b/src/com/android/contacts/views/ContactLoader.java
@@ -0,0 +1,603 @@
+/*
+ * 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.views;
+
+import com.android.contacts.util.DataStatus;
+
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Entity;
+import android.content.Loader;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.DisplayNameSources;
+import android.provider.ContactsContract.RawContacts;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * Loads a single Contact and all it constituent RawContacts.
+ */
+public class ContactLoader extends Loader<ContactLoader.Result> {
+ private static final String TAG = "ContactLoader";
+
+ private Uri mLookupUri;
+ private Result mContact;
+ private ForceLoadContentObserver mObserver;
+ private boolean mDestroyed;
+
+ public interface Listener {
+ public void onContactLoaded(Result contact);
+ }
+
+ /**
+ * The result of a load operation. Contains all data necessary to display the contact.
+ */
+ public static final class Result {
+ /**
+ * Singleton instance that represents "No Contact Found"
+ */
+ public static final Result NOT_FOUND = new Result();
+
+ /**
+ * Singleton instance that represents an error, e.g. because of an invalid Uri
+ * TODO: We should come up with something nicer here. Maybe use an Either type so
+ * that we can capture the Exception?
+ */
+ public static final Result ERROR = new Result();
+
+ private final Uri mLookupUri;
+ private final String mLookupKey;
+ private final Uri mUri;
+ private final long mId;
+ private final long mNameRawContactId;
+ private final int mDisplayNameSource;
+ private final long mPhotoId;
+ private final String mDisplayName;
+ private final String mPhoneticName;
+ private final boolean mStarred;
+ private final Integer mPresence;
+ private final ArrayList<Entity> mEntities;
+ private final HashMap<Long, DataStatus> mStatuses;
+ private final String mStatus;
+ private final Long mStatusTimestamp;
+ private final Integer mStatusLabel;
+ private final String mStatusResPackage;
+
+ /**
+ * Constructor for case "no contact found". This must only be used for the
+ * final {@link Result#NOT_FOUND} singleton
+ */
+ private Result() {
+ mLookupUri = null;
+ mLookupKey = null;
+ mUri = null;
+ mId = -1;
+ mEntities = null;
+ mStatuses = null;
+ mNameRawContactId = -1;
+ mDisplayNameSource = DisplayNameSources.UNDEFINED;
+ mPhotoId = -1;
+ mDisplayName = null;
+ mPhoneticName = null;
+ mStarred = false;
+ mPresence = null;
+ mStatus = null;
+ mStatusTimestamp = null;
+ mStatusLabel = null;
+ mStatusResPackage = null;
+ }
+
+ /**
+ * Constructor to call when contact was found
+ */
+ private Result(Uri lookupUri, String lookupKey, Uri uri, long id, long nameRawContactId,
+ int displayNameSource, long photoId, String displayName, String phoneticName,
+ boolean starred, Integer presence, String status, Long statusTimestamp,
+ Integer statusLabel, String statusResPackage) {
+ mLookupUri = lookupUri;
+ mLookupKey = lookupKey;
+ mUri = uri;
+ mId = id;
+ mEntities = new ArrayList<Entity>();
+ mStatuses = new HashMap<Long, DataStatus>();
+ mNameRawContactId = nameRawContactId;
+ mDisplayNameSource = displayNameSource;
+ mPhotoId = photoId;
+ mDisplayName = displayName;
+ mPhoneticName = phoneticName;
+ mStarred = starred;
+ mPresence = presence;
+ mStatus = status;
+ mStatusTimestamp = statusTimestamp;
+ mStatusLabel = statusLabel;
+ mStatusResPackage = statusResPackage;
+ }
+
+ public Uri getLookupUri() {
+ return mLookupUri;
+ }
+ public String getLookupKey() {
+ return mLookupKey;
+ }
+ public Uri getUri() {
+ return mUri;
+ }
+ public long getId() {
+ return mId;
+ }
+ public long getNameRawContactId() {
+ return mNameRawContactId;
+ }
+ public int getDisplayNameSource() {
+ return mDisplayNameSource;
+ }
+ public long getPhotoId() {
+ return mPhotoId;
+ }
+ public String getDisplayName() {
+ return mDisplayName;
+ }
+ public String getPhoneticName() {
+ return mPhoneticName;
+ }
+ public boolean getStarred() {
+ return mStarred;
+ }
+ public Integer getPresence() {
+ return mPresence;
+ }
+ public String getStatus() {
+ return mStatus;
+ }
+ public Long getStatusTimestamp() {
+ return mStatusTimestamp;
+ }
+ public Integer getStatusLabel() {
+ return mStatusLabel;
+ }
+ public String getStatusResPackage() {
+ return mStatusResPackage;
+ }
+ public ArrayList<Entity> getEntities() {
+ return mEntities;
+ }
+ public HashMap<Long, DataStatus> getStatuses() {
+ return mStatuses;
+ }
+ }
+
+ private static class ContactQuery {
+ // Projection used for the query that loads all data for the entire contact.
+ final static String[] COLUMNS = new String[] {
+ Contacts.NAME_RAW_CONTACT_ID,
+ Contacts.DISPLAY_NAME_SOURCE,
+ Contacts.LOOKUP_KEY,
+ Contacts.DISPLAY_NAME,
+ Contacts.PHONETIC_NAME,
+ Contacts.PHOTO_ID,
+ Contacts.STARRED,
+ Contacts.CONTACT_PRESENCE,
+ Contacts.CONTACT_STATUS,
+ Contacts.CONTACT_STATUS_TIMESTAMP,
+ Contacts.CONTACT_STATUS_RES_PACKAGE,
+ Contacts.CONTACT_STATUS_LABEL,
+ Contacts.Entity.CONTACT_ID,
+ Contacts.Entity.RAW_CONTACT_ID,
+
+ RawContacts.ACCOUNT_NAME,
+ RawContacts.ACCOUNT_TYPE,
+ RawContacts.DIRTY,
+ RawContacts.VERSION,
+ RawContacts.SOURCE_ID,
+ RawContacts.SYNC1,
+ RawContacts.SYNC2,
+ RawContacts.SYNC3,
+ RawContacts.SYNC4,
+ RawContacts.DELETED,
+ RawContacts.IS_RESTRICTED,
+ RawContacts.NAME_VERIFIED,
+
+ Contacts.Entity.DATA_ID,
+ Data.DATA1,
+ Data.DATA2,
+ Data.DATA3,
+ Data.DATA4,
+ Data.DATA5,
+ Data.DATA6,
+ Data.DATA7,
+ Data.DATA8,
+ Data.DATA9,
+ Data.DATA10,
+ Data.DATA11,
+ Data.DATA12,
+ Data.DATA13,
+ Data.DATA14,
+ Data.DATA15,
+ Data.SYNC1,
+ Data.SYNC2,
+ Data.SYNC3,
+ Data.SYNC4,
+ Data.DATA_VERSION,
+ Data.IS_PRIMARY,
+ Data.IS_SUPER_PRIMARY,
+ Data.MIMETYPE,
+ Data.RES_PACKAGE,
+
+ GroupMembership.GROUP_SOURCE_ID,
+
+ Data.PRESENCE,
+ Data.CHAT_CAPABILITY,
+ Data.STATUS,
+ Data.STATUS_RES_PACKAGE,
+ Data.STATUS_ICON,
+ Data.STATUS_LABEL,
+ Data.STATUS_TIMESTAMP
+ };
+
+ public final static int NAME_RAW_CONTACT_ID = 0;
+ public final static int DISPLAY_NAME_SOURCE = 1;
+ public final static int LOOKUP_KEY = 2;
+ public final static int DISPLAY_NAME = 3;
+ public final static int PHONETIC_NAME = 4;
+ public final static int PHOTO_ID = 5;
+ public final static int STARRED = 6;
+ public final static int CONTACT_PRESENCE = 7;
+ public final static int CONTACT_STATUS = 8;
+ public final static int CONTACT_STATUS_TIMESTAMP = 9;
+ public final static int CONTACT_STATUS_RES_PACKAGE = 10;
+ public final static int CONTACT_STATUS_LABEL = 11;
+ public final static int CONTACT_ID = 12;
+ public final static int RAW_CONTACT_ID = 13;
+
+ public final static int ACCOUNT_NAME = 14;
+ public final static int ACCOUNT_TYPE = 15;
+ public final static int DIRTY = 16;
+ public final static int VERSION = 17;
+ public final static int SOURCE_ID = 18;
+ public final static int SYNC1 = 19;
+ public final static int SYNC2 = 20;
+ public final static int SYNC3 = 21;
+ public final static int SYNC4 = 22;
+ public final static int DELETED = 23;
+ public final static int IS_RESTRICTED = 24;
+ public final static int NAME_VERIFIED = 25;
+
+ public final static int DATA_ID = 26;
+ public final static int DATA1 = 27;
+ public final static int DATA2 = 28;
+ public final static int DATA3 = 29;
+ public final static int DATA4 = 30;
+ public final static int DATA5 = 31;
+ public final static int DATA6 = 32;
+ public final static int DATA7 = 33;
+ public final static int DATA8 = 34;
+ public final static int DATA9 = 35;
+ public final static int DATA10 = 36;
+ public final static int DATA11 = 37;
+ public final static int DATA12 = 38;
+ public final static int DATA13 = 39;
+ public final static int DATA14 = 40;
+ public final static int DATA15 = 41;
+ public final static int DATA_SYNC1 = 42;
+ public final static int DATA_SYNC2 = 43;
+ public final static int DATA_SYNC3 = 44;
+ public final static int DATA_SYNC4 = 45;
+ public final static int DATA_VERSION = 46;
+ public final static int IS_PRIMARY = 47;
+ public final static int IS_SUPERPRIMARY = 48;
+ public final static int MIMETYPE = 49;
+ public final static int RES_PACKAGE = 50;
+
+ public final static int GROUP_SOURCE_ID = 51;
+
+ public final static int PRESENCE = 52;
+ public final static int CHAT_CAPABILITY = 53;
+ public final static int STATUS = 54;
+ }
+
+ private final class LoadContactTask extends AsyncTask<Void, Void, Result> {
+
+ @Override
+ protected Result doInBackground(Void... args) {
+ try {
+ final ContentResolver resolver = getContext().getContentResolver();
+ final Uri uriCurrentFormat = ensureIsContactUri(resolver, mLookupUri);
+ return loadContactEntity(resolver, uriCurrentFormat);
+ } catch (Exception e) {
+ Log.e(TAG, "Error loading the contact: " + mLookupUri, e);
+ return Result.ERROR;
+ }
+ }
+
+ /**
+ * Transforms the given Uri and returns a Lookup-Uri that represents the contact.
+ * For legacy contacts, a raw-contact lookup is performed.
+ * @param resolver
+ */
+ private Uri ensureIsContactUri(final ContentResolver resolver, final Uri uri) {
+ if (uri == null) throw new IllegalArgumentException("uri must not be null");
+
+ final String authority = uri.getAuthority();
+
+ // Current Style Uri?
+ if (ContactsContract.AUTHORITY.equals(authority)) {
+ final String type = resolver.getType(uri);
+ // Contact-Uri? Good, return it
+ if (Contacts.CONTENT_ITEM_TYPE.equals(type)) {
+ return uri;
+ }
+
+ // RawContact-Uri? Transform it to ContactUri
+ if (RawContacts.CONTENT_ITEM_TYPE.equals(type)) {
+ final long rawContactId = ContentUris.parseId(uri);
+ return RawContacts.getContactLookupUri(getContext().getContentResolver(),
+ ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId));
+ }
+
+ // Anything else? We don't know what this is
+ throw new IllegalArgumentException("uri format is unknown");
+ }
+
+ // Legacy Style? Convert to RawContact
+ final String OBSOLETE_AUTHORITY = "contacts";
+ if (OBSOLETE_AUTHORITY.equals(authority)) {
+ // Legacy Format. Convert to RawContact-Uri and then lookup the contact
+ final long rawContactId = ContentUris.parseId(uri);
+ return RawContacts.getContactLookupUri(resolver,
+ ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId));
+ }
+
+ throw new IllegalArgumentException("uri authority is unknown");
+ }
+
+ private Result loadContactEntity(ContentResolver resolver, Uri contactUri) {
+ Uri entityUri = Uri.withAppendedPath(contactUri, Contacts.Entity.CONTENT_DIRECTORY);
+ Cursor cursor = resolver.query(entityUri, ContactQuery.COLUMNS, null, null,
+ Contacts.Entity.RAW_CONTACT_ID);
+ if (cursor == null) {
+ Log.e(TAG, "No cursor returned in loadContactEntity");
+ return Result.NOT_FOUND;
+ }
+
+ try {
+ if (!cursor.moveToFirst()) {
+ cursor.close();
+ return Result.NOT_FOUND;
+ }
+
+ long currentRawContactId = -1;
+ Entity entity = null;
+ Result result = loadContactHeaderData(cursor, contactUri);
+ ArrayList<Entity> entities = result.getEntities();
+ HashMap<Long, DataStatus> statuses = result.getStatuses();
+ for (; !cursor.isAfterLast(); cursor.moveToNext()) {
+ long rawContactId = cursor.getLong(ContactQuery.RAW_CONTACT_ID);
+ if (rawContactId != currentRawContactId) {
+ currentRawContactId = rawContactId;
+ entity = new android.content.Entity(loadRawContact(cursor));
+ entities.add(entity);
+ }
+ if (!cursor.isNull(ContactQuery.DATA_ID)) {
+ ContentValues data = loadData(cursor);
+ entity.addSubValue(ContactsContract.Data.CONTENT_URI, data);
+
+ if (!cursor.isNull(ContactQuery.PRESENCE)
+ || !cursor.isNull(ContactQuery.STATUS)) {
+ final DataStatus status = new DataStatus(cursor);
+ final long dataId = cursor.getLong(ContactQuery.DATA_ID);
+ statuses.put(dataId, status);
+ }
+ }
+ }
+
+ return result;
+ } finally {
+ cursor.close();
+ }
+ }
+
+ /**
+ * Extracts Contact level columns from the cursor.
+ */
+ private Result loadContactHeaderData(final Cursor cursor, Uri contactUri) {
+ final long contactId = cursor.getLong(ContactQuery.CONTACT_ID);
+ final String lookupKey = cursor.getString(ContactQuery.LOOKUP_KEY);
+ final long nameRawContactId = cursor.getLong(ContactQuery.NAME_RAW_CONTACT_ID);
+ final int displayNameSource = cursor.getInt(ContactQuery.DISPLAY_NAME_SOURCE);
+ final String displayName = cursor.getString(ContactQuery.DISPLAY_NAME);
+ final String phoneticName = cursor.getString(ContactQuery.PHONETIC_NAME);
+ final long photoId = cursor.getLong(ContactQuery.PHOTO_ID);
+ final boolean starred = cursor.getInt(ContactQuery.STARRED) != 0;
+ final Integer presence = cursor.isNull(ContactQuery.CONTACT_PRESENCE)
+ ? null
+ : cursor.getInt(ContactQuery.CONTACT_PRESENCE);
+ final String status = cursor.getString(ContactQuery.CONTACT_STATUS);
+ final Long statusTimestamp = cursor.isNull(ContactQuery.CONTACT_STATUS_TIMESTAMP)
+ ? null
+ : cursor.getLong(ContactQuery.CONTACT_STATUS_TIMESTAMP);
+ final Integer statusLabel = cursor.isNull(ContactQuery.CONTACT_STATUS_LABEL)
+ ? null
+ : cursor.getInt(ContactQuery.CONTACT_STATUS_LABEL);
+ final String statusResPackage = cursor.getString(
+ ContactQuery.CONTACT_STATUS_RES_PACKAGE);
+
+ Uri lookupUri = ContentUris.withAppendedId(
+ Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey), contactId);
+ return new Result(lookupUri, lookupKey, contactUri, contactId, nameRawContactId,
+ displayNameSource, photoId, displayName, phoneticName, starred, presence,
+ status, statusTimestamp, statusLabel, statusResPackage);
+ }
+
+ /**
+ * Extracts RawContact level columns from the cursor.
+ */
+ private ContentValues loadRawContact(Cursor cursor) {
+ ContentValues cv = new ContentValues();
+
+ cv.put(RawContacts._ID, cursor.getLong(ContactQuery.RAW_CONTACT_ID));
+
+ cursorColumnToContentValues(cursor, cv, ContactQuery.ACCOUNT_NAME);
+ cursorColumnToContentValues(cursor, cv, ContactQuery.ACCOUNT_TYPE);
+ cursorColumnToContentValues(cursor, cv, ContactQuery.DIRTY);
+ cursorColumnToContentValues(cursor, cv, ContactQuery.VERSION);
+ cursorColumnToContentValues(cursor, cv, ContactQuery.SOURCE_ID);
+ cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC1);
+ cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC2);
+ cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC3);
+ cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC4);
+ cursorColumnToContentValues(cursor, cv, ContactQuery.DELETED);
+ cursorColumnToContentValues(cursor, cv, ContactQuery.CONTACT_ID);
+ cursorColumnToContentValues(cursor, cv, ContactQuery.STARRED);
+ cursorColumnToContentValues(cursor, cv, ContactQuery.IS_RESTRICTED);
+ cursorColumnToContentValues(cursor, cv, ContactQuery.NAME_VERIFIED);
+
+ return cv;
+ }
+
+ /**
+ * Extracts Data level columns from the cursor.
+ */
+ private ContentValues loadData(Cursor cursor) {
+ ContentValues cv = new ContentValues();
+
+ cv.put(Data._ID, cursor.getLong(ContactQuery.DATA_ID));
+
+ cursorColumnToContentValues(cursor, cv, ContactQuery.DATA1);
+ cursorColumnToContentValues(cursor, cv, ContactQuery.DATA2);
+ cursorColumnToContentValues(cursor, cv, ContactQuery.DATA3);
+ cursorColumnToContentValues(cursor, cv, ContactQuery.DATA4);
+ cursorColumnToContentValues(cursor, cv, ContactQuery.DATA5);
+ cursorColumnToContentValues(cursor, cv, ContactQuery.DATA6);
+ cursorColumnToContentValues(cursor, cv, ContactQuery.DATA7);
+ cursorColumnToContentValues(cursor, cv, ContactQuery.DATA8);
+ cursorColumnToContentValues(cursor, cv, ContactQuery.DATA9);
+ cursorColumnToContentValues(cursor, cv, ContactQuery.DATA10);
+ cursorColumnToContentValues(cursor, cv, ContactQuery.DATA11);
+ cursorColumnToContentValues(cursor, cv, ContactQuery.DATA12);
+ cursorColumnToContentValues(cursor, cv, ContactQuery.DATA13);
+ cursorColumnToContentValues(cursor, cv, ContactQuery.DATA14);
+ cursorColumnToContentValues(cursor, cv, ContactQuery.DATA15);
+ cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC1);
+ cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC2);
+ cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC3);
+ cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC4);
+ cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_VERSION);
+ cursorColumnToContentValues(cursor, cv, ContactQuery.IS_PRIMARY);
+ cursorColumnToContentValues(cursor, cv, ContactQuery.IS_SUPERPRIMARY);
+ cursorColumnToContentValues(cursor, cv, ContactQuery.MIMETYPE);
+ cursorColumnToContentValues(cursor, cv, ContactQuery.RES_PACKAGE);
+ cursorColumnToContentValues(cursor, cv, ContactQuery.GROUP_SOURCE_ID);
+ cursorColumnToContentValues(cursor, cv, ContactQuery.CHAT_CAPABILITY);
+
+ return cv;
+ }
+
+ private void cursorColumnToContentValues(
+ Cursor cursor, ContentValues values, int index) {
+ switch (cursor.getType(index)) {
+ case Cursor.FIELD_TYPE_NULL:
+ // don't put anything in the content values
+ break;
+ case Cursor.FIELD_TYPE_INTEGER:
+ values.put(ContactQuery.COLUMNS[index], cursor.getLong(index));
+ break;
+ case Cursor.FIELD_TYPE_STRING:
+ values.put(ContactQuery.COLUMNS[index], cursor.getString(index));
+ break;
+ case Cursor.FIELD_TYPE_BLOB:
+ values.put(ContactQuery.COLUMNS[index], cursor.getBlob(index));
+ break;
+ default:
+ throw new IllegalStateException("Invalid or unhandled data type");
+ }
+ }
+
+ @Override
+ protected void onPostExecute(Result result) {
+ // The creator isn't interested in any further updates
+ if (mDestroyed) {
+ return;
+ }
+
+ mContact = result;
+ mLookupUri = result.getLookupUri();
+ if (result != null) {
+ unregisterObserver();
+ if (mObserver == null) {
+ mObserver = new ForceLoadContentObserver();
+ }
+ Log.i(TAG, "Registering content observer for " + mLookupUri);
+
+ if (result != Result.ERROR && result != Result.NOT_FOUND) {
+ getContext().getContentResolver().registerContentObserver(mLookupUri, true,
+ mObserver);
+ }
+ deliverResult(result);
+ }
+ }
+ }
+
+ private void unregisterObserver() {
+ if (mObserver != null) {
+ getContext().getContentResolver().unregisterContentObserver(mObserver);
+ mObserver = null;
+ }
+ }
+
+ public ContactLoader(Context context, Uri lookupUri) {
+ super(context);
+ mLookupUri = lookupUri;
+ }
+
+ @Override
+ public void startLoading() {
+ if (mContact != null) {
+ deliverResult(mContact);
+ } else {
+ forceLoad();
+ }
+ }
+
+ @Override
+ public void forceLoad() {
+ final LoadContactTask task = new LoadContactTask();
+ task.execute((Void[])null);
+ }
+
+ @Override
+ public void stopLoading() {
+ unregisterObserver();
+ mContact = null;
+ }
+
+ @Override
+ public void destroy() {
+ unregisterObserver();
+ mContact = null;
+ mDestroyed = true;
+ }
+}
diff --git a/src/com/android/contacts/views/ContactSaveService.java b/src/com/android/contacts/views/ContactSaveService.java
new file mode 100644
index 0000000..936b8a4
--- /dev/null
+++ b/src/com/android/contacts/views/ContactSaveService.java
@@ -0,0 +1,59 @@
+/*
+ * 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.views;
+
+import android.app.IntentService;
+import android.content.ContentProviderOperation;
+import android.content.Intent;
+import android.content.OperationApplicationException;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.provider.ContactsContract;
+import android.util.Log;
+
+import java.util.ArrayList;
+
+public class ContactSaveService extends IntentService {
+ private static final String TAG = "ContactSaveService";
+
+ public static final String EXTRA_OPERATIONS = "Operations";
+
+ public ContactSaveService() {
+ super(TAG);
+ setIntentRedelivery(true);
+ }
+
+ @Override
+ protected void onHandleIntent(Intent intent) {
+ final Parcelable[] operationsArray = intent.getParcelableArrayExtra(EXTRA_OPERATIONS);
+
+ // We have to cast each item individually here
+ final ArrayList<ContentProviderOperation> operations =
+ new ArrayList<ContentProviderOperation>(operationsArray.length);
+ for (Parcelable p : operationsArray) {
+ operations.add((ContentProviderOperation) p);
+ }
+
+ try {
+ getContentResolver().applyBatch(ContactsContract.AUTHORITY, operations);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error saving", e);
+ } catch (OperationApplicationException e) {
+ Log.e(TAG, "Error saving", e);
+ }
+ }
+}
diff --git a/src/com/android/contacts/views/detail/ContactDetailFragment.java b/src/com/android/contacts/views/detail/ContactDetailFragment.java
new file mode 100644
index 0000000..d61b346
--- /dev/null
+++ b/src/com/android/contacts/views/detail/ContactDetailFragment.java
@@ -0,0 +1,1036 @@
+/*
+ * 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.views.detail;
+
+import com.android.contacts.Collapser;
+import com.android.contacts.ContactOptionsActivity;
+import com.android.contacts.ContactPresenceIconUtil;
+import com.android.contacts.ContactsUtils;
+import com.android.contacts.ContactsUtils.ImActions;
+import com.android.contacts.R;
+import com.android.contacts.TypePrecedence;
+import com.android.contacts.Collapser.Collapsible;
+import com.android.contacts.model.ContactsSource;
+import com.android.contacts.model.Sources;
+import com.android.contacts.model.ContactsSource.DataKind;
+import com.android.contacts.util.Constants;
+import com.android.contacts.util.DataStatus;
+import com.android.contacts.util.PhoneCapabilityTester;
+import com.android.contacts.views.ContactLoader;
+import com.android.internal.telephony.ITelephony;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.app.LoaderManager;
+import android.app.LoaderManager.LoaderCallbacks;
+import android.content.ActivityNotFoundException;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Entity;
+import android.content.Intent;
+import android.content.Loader;
+import android.content.Entity.NamedContentValues;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.net.ParseException;
+import android.net.Uri;
+import android.net.WebAddress;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.provider.ContactsContract.CommonDataKinds;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.DisplayNameSources;
+import android.provider.ContactsContract.RawContacts;
+import android.provider.ContactsContract.StatusUpdates;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.Im;
+import android.provider.ContactsContract.CommonDataKinds.Nickname;
+import android.provider.ContactsContract.CommonDataKinds.Note;
+import android.provider.ContactsContract.CommonDataKinds.Organization;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+import android.provider.ContactsContract.CommonDataKinds.Website;
+import android.telephony.PhoneNumberUtils;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.ContextMenu;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.View.OnClickListener;
+import android.view.View.OnCreateContextMenuListener;
+import android.widget.AdapterView;
+import android.widget.BaseAdapter;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.TextView;
+import android.widget.Toast;
+import android.widget.AdapterView.OnItemClickListener;
+
+import java.util.ArrayList;
+
+public class ContactDetailFragment extends Fragment
+ implements OnCreateContextMenuListener, OnItemClickListener {
+ private static final String TAG = "ContactDetailFragment";
+
+ private static final int MENU_ITEM_MAKE_DEFAULT = 3;
+
+ private static final int LOADER_DETAILS = 1;
+
+ private Context mContext;
+ private Uri mLookupUri;
+ private Listener mListener;
+
+ private ContactLoader.Result mContactData;
+ private ContactDetailHeaderView mHeaderView;
+ private ListView mListView;
+ private ViewAdapter mAdapter;
+ private Uri mPrimaryPhoneUri = null;
+
+ private boolean mAllRestricted;
+ private final ArrayList<Long> mWritableRawContactIds = new ArrayList<Long>();
+ private int mNumPhoneNumbers = 0;
+
+ /**
+ * Device capability: Set during buildEntries and used in the long-press context menu
+ */
+ private boolean mHasPhone;
+
+ /**
+ * Device capability: Set during buildEntries and used in the long-press context menu
+ */
+ private boolean mHasSms;
+
+ /**
+ * The view shown if the detail list is empty.
+ * We set this to the list view when first bind the adapter, so that it won't be shown while
+ * we're loading data.
+ */
+ private View mEmptyView;
+
+ /**
+ * A list of distinct contact IDs included in the current contact.
+ */
+ private ArrayList<Long> mRawContactIds = new ArrayList<Long>();
+ private ArrayList<ViewEntry> mPhoneEntries = new ArrayList<ViewEntry>();
+ private ArrayList<ViewEntry> mSmsEntries = new ArrayList<ViewEntry>();
+ private ArrayList<ViewEntry> mEmailEntries = new ArrayList<ViewEntry>();
+ private ArrayList<ViewEntry> mPostalEntries = new ArrayList<ViewEntry>();
+ private ArrayList<ViewEntry> mImEntries = new ArrayList<ViewEntry>();
+ private ArrayList<ViewEntry> mNicknameEntries = new ArrayList<ViewEntry>();
+ private ArrayList<ViewEntry> mOrganizationEntries = new ArrayList<ViewEntry>();
+ private ArrayList<ViewEntry> mGroupEntries = new ArrayList<ViewEntry>();
+ private ArrayList<ViewEntry> mOtherEntries = new ArrayList<ViewEntry>();
+ private ArrayList<ArrayList<ViewEntry>> mSections = new ArrayList<ArrayList<ViewEntry>>();
+ private LayoutInflater mInflater;
+
+ public ContactDetailFragment() {
+ // Explicit constructor for inflation
+
+ // Build the list of sections. The order they're added to mSections dictates the
+ // order they are displayed in the list.
+ mSections.add(mPhoneEntries);
+ mSections.add(mSmsEntries);
+ mSections.add(mEmailEntries);
+ mSections.add(mImEntries);
+ mSections.add(mPostalEntries);
+ mSections.add(mNicknameEntries);
+ mSections.add(mOrganizationEntries);
+ mSections.add(mGroupEntries);
+ mSections.add(mOtherEntries);
+ }
+
+ @Override
+ public void onAttach(Activity activity) {
+ super.onAttach(activity);
+ mContext = activity;
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
+ final View view = inflater.inflate(R.layout.contact_detail_fragment, container, false);
+
+ setHasOptionsMenu(true);
+
+ mInflater = inflater;
+
+ mHeaderView = (ContactDetailHeaderView) view.findViewById(R.id.contact_header_widget);
+ mHeaderView.setExcludeMimes(new String[] {
+ Contacts.CONTENT_ITEM_TYPE
+ });
+ mHeaderView.setListener(mHeaderViewListener);
+
+ mListView = (ListView) view.findViewById(android.R.id.list);
+ mListView.setOnCreateContextMenuListener(this);
+ mListView.setScrollBarStyle(ListView.SCROLLBARS_OUTSIDE_OVERLAY);
+ mListView.setOnItemClickListener(this);
+ // Don't set it to mListView yet. We do so later when we bind the adapter.
+ mEmptyView = view.findViewById(android.R.id.empty);
+
+ return view;
+ }
+
+ public void setListener(Listener value) {
+ mListener = value;
+ }
+
+ public Uri getUri() {
+ return mLookupUri;
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ getLoaderManager().initLoader(LOADER_DETAILS, null, mDetailLoaderListener);
+ }
+
+ public void loadUri(Uri lookupUri) {
+ mLookupUri = lookupUri;
+ if (getActivity() != null) {
+ getLoaderManager().restartLoader(LOADER_DETAILS, null, mDetailLoaderListener);
+ }
+ }
+
+ private void bindData() {
+ // Set the header
+ mHeaderView.loadData(mContactData);
+
+ // Build up the contact entries
+ buildEntries();
+
+ // Collapse similar data items in select sections.
+ Collapser.collapseList(mPhoneEntries);
+ Collapser.collapseList(mSmsEntries);
+ Collapser.collapseList(mEmailEntries);
+ Collapser.collapseList(mPostalEntries);
+ Collapser.collapseList(mImEntries);
+
+ if (mAdapter == null) {
+ mAdapter = new ViewAdapter();
+ mListView.setAdapter(mAdapter);
+ } else {
+ mAdapter.notifyDataSetChanged();
+ }
+ mListView.setEmptyView(mEmptyView);
+ }
+
+ /**
+ * Build up the entries to display on the screen.
+ */
+ private final void buildEntries() {
+ mHasPhone = PhoneCapabilityTester.isPhoneCallIntentRegistered(mContext);
+ mHasSms = PhoneCapabilityTester.isSmsIntentRegistered(mContext);
+
+ // Clear out the old entries
+ final int numSections = mSections.size();
+ for (int i = 0; i < numSections; i++) {
+ mSections.get(i).clear();
+ }
+
+ mRawContactIds.clear();
+
+ mAllRestricted = true;
+ mPrimaryPhoneUri = null;
+ mNumPhoneNumbers = 0;
+
+ mWritableRawContactIds.clear();
+
+ final Sources sources = Sources.getInstance(mContext);
+
+ // Build up method entries
+ if (mContactData == null) {
+ return;
+ }
+
+ for (Entity entity: mContactData.getEntities()) {
+ final ContentValues entValues = entity.getEntityValues();
+ final String accountType = entValues.getAsString(RawContacts.ACCOUNT_TYPE);
+ final long rawContactId = entValues.getAsLong(RawContacts._ID);
+
+ // Mark when this contact has any unrestricted components
+ Integer restricted = entValues.getAsInteger(RawContacts.IS_RESTRICTED);
+ final boolean isRestricted = restricted != null && restricted != 0;
+ if (!isRestricted) mAllRestricted = false;
+
+ if (!mRawContactIds.contains(rawContactId)) {
+ mRawContactIds.add(rawContactId);
+ }
+ ContactsSource contactsSource = sources.getInflatedSource(accountType,
+ ContactsSource.LEVEL_SUMMARY);
+ if (contactsSource == null || !contactsSource.readOnly) {
+ mWritableRawContactIds.add(rawContactId);
+ }
+
+
+ for (NamedContentValues subValue : entity.getSubValues()) {
+ final ContentValues entryValues = subValue.values;
+ entryValues.put(Data.RAW_CONTACT_ID, rawContactId);
+
+ final long dataId = entryValues.getAsLong(Data._ID);
+ final String mimeType = entryValues.getAsString(Data.MIMETYPE);
+ if (mimeType == null) continue;
+
+ final DataKind kind = sources.getKindOrFallback(accountType, mimeType, mContext,
+ ContactsSource.LEVEL_MIMETYPES);
+ if (kind == null) continue;
+
+ final ViewEntry entry = ViewEntry.fromValues(mContext, mimeType, kind,
+ rawContactId, dataId, entryValues);
+
+ final boolean hasData = !TextUtils.isEmpty(entry.data);
+ Integer superPrimary = entryValues.getAsInteger(Data.IS_SUPER_PRIMARY);
+ final boolean isSuperPrimary = superPrimary != null && superPrimary != 0;
+
+ if (StructuredName.CONTENT_ITEM_TYPE.equals(mimeType)) {
+ // Always ignore the name. It is shown in the header if set
+ } else if (Phone.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
+ // Build phone entries
+ mNumPhoneNumbers++;
+
+ final Intent phoneIntent = mHasPhone ? new Intent(Intent.ACTION_CALL_PRIVILEGED,
+ Uri.fromParts(Constants.SCHEME_TEL, entry.data, null)) : null;
+ final Intent smsIntent = mHasSms ? new Intent(Intent.ACTION_SENDTO,
+ Uri.fromParts(Constants.SCHEME_SMSTO, entry.data, null)) : null;
+
+ // Configure Icons and Intents. Notice actionIcon is already set to the phone
+ if (mHasPhone && mHasSms) {
+ entry.intent = phoneIntent;
+ entry.secondaryIntent = smsIntent;
+ entry.secondaryActionIcon = kind.iconAltRes;
+ } else if (mHasPhone) {
+ entry.intent = phoneIntent;
+ } else if (mHasSms) {
+ entry.intent = smsIntent;
+ entry.actionIcon = kind.iconAltRes;
+ } else {
+ entry.intent = null;
+ entry.actionIcon = -1;
+ }
+
+
+ // Remember super-primary phone
+ if (isSuperPrimary) mPrimaryPhoneUri = entry.uri;
+
+ entry.isPrimary = isSuperPrimary;
+ mPhoneEntries.add(entry);
+ } else if (Email.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
+ // Build email entries
+ entry.intent = new Intent(Intent.ACTION_SENDTO,
+ Uri.fromParts(Constants.SCHEME_MAILTO, entry.data, null));
+ entry.isPrimary = isSuperPrimary;
+ mEmailEntries.add(entry);
+
+ // When Email rows have status, create additional Im row
+ final DataStatus status = mContactData.getStatuses().get(entry.id);
+ if (status != null) {
+ final String imMime = Im.CONTENT_ITEM_TYPE;
+ final DataKind imKind = sources.getKindOrFallback(accountType,
+ imMime, mContext, ContactsSource.LEVEL_MIMETYPES);
+ final ViewEntry imEntry = ViewEntry.fromValues(mContext,
+ imMime, imKind, rawContactId, dataId, entryValues);
+ final ImActions imActions = ContactsUtils.buildImActions(entryValues);
+ if (imActions != null) {
+ imEntry.actionIcon = imActions.getPrimaryActionIcon();
+ imEntry.secondaryActionIcon = imActions.getSecondaryActionIcon();
+ imEntry.intent = imActions.getPrimaryIntent();
+ imEntry.secondaryIntent = imActions.getSecondaryIntent();
+ }
+ imEntry.applyStatus(status, false);
+ mImEntries.add(imEntry);
+ }
+ } else if (StructuredPostal.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
+ // Build postal entries
+ entry.maxLines = 4;
+ entry.intent = new Intent(Intent.ACTION_VIEW, entry.uri);
+ mPostalEntries.add(entry);
+ } else if (Im.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
+ // Build IM entries
+ final ImActions imActions = ContactsUtils.buildImActions(entryValues);
+ if (imActions != null) {
+ entry.actionIcon = imActions.getPrimaryActionIcon();
+ entry.secondaryActionIcon = imActions.getSecondaryActionIcon();
+ entry.intent = imActions.getPrimaryIntent();
+ entry.secondaryIntent = imActions.getSecondaryIntent();
+ }
+ if (TextUtils.isEmpty(entry.label)) {
+ entry.label = mContext.getString(R.string.chat).toLowerCase();
+ }
+
+ // Apply presence and status details when available
+ final DataStatus status = mContactData.getStatuses().get(entry.id);
+ if (status != null) {
+ entry.applyStatus(status, false);
+ }
+ mImEntries.add(entry);
+ } else if (Organization.CONTENT_ITEM_TYPE.equals(mimeType) &&
+ (hasData || !TextUtils.isEmpty(entry.label))) {
+ // Build organization entries
+ final boolean isNameRawContact =
+ (mContactData.getNameRawContactId() == rawContactId);
+
+ final boolean duplicatesTitle =
+ isNameRawContact
+ && mContactData.getDisplayNameSource()
+ == DisplayNameSources.ORGANIZATION
+ && (!hasData || TextUtils.isEmpty(entry.label));
+
+ if (!duplicatesTitle) {
+ entry.uri = null;
+
+ if (TextUtils.isEmpty(entry.label)) {
+ entry.label = entry.data;
+ entry.data = "";
+ }
+
+ mOrganizationEntries.add(entry);
+ }
+ } else if (Nickname.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
+ // Build nickname entries
+ final boolean isNameRawContact =
+ (mContactData.getNameRawContactId() == rawContactId);
+
+ final boolean duplicatesTitle =
+ isNameRawContact
+ && mContactData.getDisplayNameSource() == DisplayNameSources.NICKNAME;
+
+ if (!duplicatesTitle) {
+ entry.uri = null;
+ mNicknameEntries.add(entry);
+ }
+ } else if (Note.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
+ // Build note entries
+ entry.uri = null;
+ entry.maxLines = 100;
+ mOtherEntries.add(entry);
+ } else if (Website.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
+ // Build note entries
+ entry.uri = null;
+ entry.maxLines = 10;
+ try {
+ WebAddress webAddress = new WebAddress(entry.data);
+ entry.intent = new Intent(Intent.ACTION_VIEW,
+ Uri.parse(webAddress.toString()));
+ } catch (ParseException e) {
+ Log.e(TAG, "Couldn't parse website: " + entry.data);
+ }
+ mOtherEntries.add(entry);
+ } else {
+ // Handle showing custom rows
+ entry.intent = new Intent(Intent.ACTION_VIEW, entry.uri);
+
+ // Use social summary when requested by external source
+ final DataStatus status = mContactData.getStatuses().get(entry.id);
+ final boolean hasSocial = kind.actionBodySocial && status != null;
+ if (hasSocial) {
+ entry.applyStatus(status, true);
+ }
+
+ if (hasSocial || hasData) {
+ mOtherEntries.add(entry);
+ }
+ }
+ }
+ }
+ }
+
+ private static String buildActionString(DataKind kind, ContentValues values,
+ boolean lowerCase, Context context) {
+ if (kind.actionHeader == null) {
+ return null;
+ }
+ CharSequence actionHeader = kind.actionHeader.inflateUsing(context, values);
+ if (actionHeader == null) {
+ return null;
+ }
+ return lowerCase ? actionHeader.toString().toLowerCase() : actionHeader.toString();
+ }
+
+ private static String buildDataString(DataKind kind, ContentValues values,
+ Context context) {
+ if (kind.actionBody == null) {
+ return null;
+ }
+ CharSequence actionBody = kind.actionBody.inflateUsing(context, values);
+ return actionBody == null ? null : actionBody.toString();
+ }
+
+ /**
+ * A basic structure with the data for a contact entry in the list.
+ */
+ private static class ViewEntry implements Collapsible<ViewEntry> {
+ // Copied from baseclass
+ public int type = -1;
+ public String label;
+ public String data;
+ public Uri uri;
+ public long id = 0;
+ public int maxLines = 1;
+ public String mimetype;
+
+ public Context context = null;
+ public String resPackageName = null;
+ public int actionIcon = -1;
+ public boolean isPrimary = false;
+ public int secondaryActionIcon = -1;
+ public Intent intent;
+ public Intent secondaryIntent = null;
+ public int maxLabelLines = 1;
+ public ArrayList<Long> ids = new ArrayList<Long>();
+ public int collapseCount = 0;
+
+ public int presence = -1;
+
+ public CharSequence footerLine = null;
+
+ private ViewEntry() {
+ }
+
+ /**
+ * Build new {@link ViewEntry} and populate from the given values.
+ */
+ public static ViewEntry fromValues(Context context, String mimeType, DataKind kind,
+ long rawContactId, long dataId, ContentValues values) {
+ final ViewEntry entry = new ViewEntry();
+ entry.context = context;
+ entry.id = dataId;
+ entry.uri = ContentUris.withAppendedId(Data.CONTENT_URI, entry.id);
+ entry.mimetype = mimeType;
+ entry.label = buildActionString(kind, values, false, context);
+ entry.data = buildDataString(kind, values, context);
+
+ if (kind.typeColumn != null && values.containsKey(kind.typeColumn)) {
+ entry.type = values.getAsInteger(kind.typeColumn);
+ }
+ if (kind.iconRes > 0) {
+ entry.resPackageName = kind.resPackageName;
+ entry.actionIcon = kind.iconRes;
+ }
+
+ return entry;
+ }
+
+ /**
+ * Apply given {@link DataStatus} values over this {@link ViewEntry}
+ *
+ * @param fillData When true, the given status replaces {@link #data}
+ * and {@link #footerLine}. Otherwise only {@link #presence}
+ * is updated.
+ */
+ public ViewEntry applyStatus(DataStatus status, boolean fillData) {
+ presence = status.getPresence();
+ if (fillData && status.isValid()) {
+ this.data = status.getStatus().toString();
+ this.footerLine = status.getTimestampLabel(context);
+ }
+
+ return this;
+ }
+
+ public boolean collapseWith(ViewEntry entry) {
+ // assert equal collapse keys
+ if (!shouldCollapseWith(entry)) {
+ return false;
+ }
+
+ // Choose the label associated with the highest type precedence.
+ if (TypePrecedence.getTypePrecedence(mimetype, type)
+ > TypePrecedence.getTypePrecedence(entry.mimetype, entry.type)) {
+ type = entry.type;
+ label = entry.label;
+ }
+
+ // Choose the max of the maxLines and maxLabelLines values.
+ maxLines = Math.max(maxLines, entry.maxLines);
+ maxLabelLines = Math.max(maxLabelLines, entry.maxLabelLines);
+
+ // Choose the presence with the highest precedence.
+ if (StatusUpdates.getPresencePrecedence(presence)
+ < StatusUpdates.getPresencePrecedence(entry.presence)) {
+ presence = entry.presence;
+ }
+
+ // If any of the collapsed entries are primary make the whole thing primary.
+ isPrimary = entry.isPrimary ? true : isPrimary;
+
+ // uri, and contactdId, shouldn't make a difference. Just keep the original.
+
+ // Keep track of all the ids that have been collapsed with this one.
+ ids.add(entry.id);
+ collapseCount++;
+ return true;
+ }
+
+ public boolean shouldCollapseWith(ViewEntry entry) {
+ if (entry == null) {
+ return false;
+ }
+
+ if (!ContactsUtils.shouldCollapse(context, mimetype, data, entry.mimetype,
+ entry.data)) {
+ return false;
+ }
+
+ if (!TextUtils.equals(mimetype, entry.mimetype)
+ || !ContactsUtils.areIntentActionEqual(intent, entry.intent)
+ || !ContactsUtils.areIntentActionEqual(secondaryIntent, entry.secondaryIntent)
+ || actionIcon != entry.actionIcon) {
+ return false;
+ }
+
+ return true;
+ }
+ }
+
+ /** Cache of the children views of a row */
+ private static class ViewCache {
+ public TextView label;
+ public TextView data;
+ public TextView footer;
+ public ImageView actionIcon;
+ public ImageView presenceIcon;
+ public ImageView primaryIcon;
+ public ImageView secondaryActionButton;
+ public View secondaryActionDivider;
+ }
+
+ private final class ViewAdapter extends BaseAdapter {
+ public View getView(int position, View convertView, ViewGroup parent) {
+ final ViewEntry entry = getEntry(position);
+ final View v;
+ final ViewCache viewCache;
+
+ // Check to see if we can reuse convertView
+ if (convertView != null) {
+ v = convertView;
+ viewCache = (ViewCache) v.getTag();
+ } else {
+ // Create a new view if needed
+ v = mInflater.inflate(R.layout.list_item_text_icons, parent, false);
+
+ // Cache the children
+ viewCache = new ViewCache();
+ viewCache.label = (TextView) v.findViewById(android.R.id.text1);
+ viewCache.data = (TextView) v.findViewById(android.R.id.text2);
+ viewCache.footer = (TextView) v.findViewById(R.id.footer);
+ viewCache.actionIcon = (ImageView) v.findViewById(R.id.action_icon);
+ viewCache.primaryIcon = (ImageView) v.findViewById(R.id.primary_icon);
+ viewCache.presenceIcon = (ImageView) v.findViewById(R.id.presence_icon);
+ viewCache.secondaryActionButton = (ImageView) v.findViewById(
+ R.id.secondary_action_button);
+ viewCache.secondaryActionButton.setOnClickListener(mSecondaryActionClickListener);
+ viewCache.secondaryActionDivider = v.findViewById(R.id.divider);
+ v.setTag(viewCache);
+ }
+
+ // Bind the data to the view
+ bindView(v, entry);
+ return v;
+ }
+
+ protected void bindView(View view, ViewEntry entry) {
+ final Resources resources = mContext.getResources();
+ ViewCache views = (ViewCache) view.getTag();
+
+ // Set the label
+ TextView label = views.label;
+ setMaxLines(label, entry.maxLabelLines);
+ label.setText(entry.label);
+
+ // Set the data
+ TextView data = views.data;
+ if (data != null) {
+ if (entry.mimetype.equals(Phone.CONTENT_ITEM_TYPE)
+ || entry.mimetype.equals(Constants.MIME_SMS_ADDRESS)) {
+ data.setText(PhoneNumberUtils.formatNumber(entry.data));
+ } else {
+ data.setText(entry.data);
+ }
+ setMaxLines(data, entry.maxLines);
+ }
+
+ // Set the footer
+ if (!TextUtils.isEmpty(entry.footerLine)) {
+ views.footer.setText(entry.footerLine);
+ views.footer.setVisibility(View.VISIBLE);
+ } else {
+ views.footer.setVisibility(View.GONE);
+ }
+
+ // Set the primary icon
+ views.primaryIcon.setVisibility(entry.isPrimary ? View.VISIBLE : View.GONE);
+
+ // Set the action icon
+ final ImageView action = views.actionIcon;
+ if (entry.actionIcon != -1) {
+ Drawable actionIcon;
+ if (entry.resPackageName != null) {
+ // Load external resources through PackageManager
+ actionIcon = mContext.getPackageManager().getDrawable(entry.resPackageName,
+ entry.actionIcon, null);
+ } else {
+ actionIcon = resources.getDrawable(entry.actionIcon);
+ }
+ action.setImageDrawable(actionIcon);
+ action.setVisibility(View.VISIBLE);
+ } else {
+ // Things should still line up as if there was an icon, so make it invisible
+ action.setVisibility(View.INVISIBLE);
+ }
+
+ // Set the presence icon
+ final Drawable presenceIcon = ContactPresenceIconUtil.getPresenceIcon(
+ mContext, entry.presence);
+ final ImageView presenceIconView = views.presenceIcon;
+ if (presenceIcon != null) {
+ presenceIconView.setImageDrawable(presenceIcon);
+ presenceIconView.setVisibility(View.VISIBLE);
+ } else {
+ presenceIconView.setVisibility(View.GONE);
+ }
+
+ // Set the secondary action button
+ final ImageView secondaryActionView = views.secondaryActionButton;
+ Drawable secondaryActionIcon = null;
+ if (entry.secondaryActionIcon != -1) {
+ secondaryActionIcon = resources.getDrawable(entry.secondaryActionIcon);
+ }
+ if (entry.secondaryIntent != null && secondaryActionIcon != null) {
+ secondaryActionView.setImageDrawable(secondaryActionIcon);
+ secondaryActionView.setTag(entry);
+ secondaryActionView.setVisibility(View.VISIBLE);
+ views.secondaryActionDivider.setVisibility(View.VISIBLE);
+ } else {
+ secondaryActionView.setVisibility(View.GONE);
+ views.secondaryActionDivider.setVisibility(View.GONE);
+ }
+ }
+
+ private void setMaxLines(TextView textView, int maxLines) {
+ if (maxLines == 1) {
+ textView.setSingleLine(true);
+ textView.setEllipsize(TextUtils.TruncateAt.END);
+ } else {
+ textView.setSingleLine(false);
+ textView.setMaxLines(maxLines);
+ textView.setEllipsize(null);
+ }
+ }
+
+ private OnClickListener mSecondaryActionClickListener = new OnClickListener() {
+ public void onClick(View v) {
+ if (mListener == null) return;
+ if (v == null) return;
+ final ViewEntry entry = (ViewEntry) v.getTag();
+ if (entry == null) return;
+ final Intent intent = entry.secondaryIntent;
+ if (intent == null) return;
+ mListener.onItemClicked(intent);
+ }
+ };
+
+ public int getCount() {
+ int count = 0;
+ final int numSections = mSections.size();
+ for (int i = 0; i < numSections; i++) {
+ final ArrayList<ViewEntry> section = mSections.get(i);
+ count += section.size();
+ }
+ return count;
+ }
+
+ public Object getItem(int position) {
+ return getEntry(position);
+ }
+
+ public long getItemId(int position) {
+ final ViewEntry entry = getEntry(position);
+ if (entry != null) {
+ return entry.id;
+ } else {
+ return -1;
+ }
+ }
+
+ private ViewEntry getEntry(int position) {
+ final int numSections = mSections.size();
+ for (int i = 0; i < numSections; i++) {
+ final ArrayList<ViewEntry> section = mSections.get(i);
+ final int sectionSize = section.size();
+ if (position < sectionSize) {
+ return section.get(position);
+ }
+ position -= sectionSize;
+ }
+ return null;
+ }
+ }
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, final MenuInflater inflater) {
+ inflater.inflate(R.menu.view, menu);
+ }
+
+ @Override
+ public void onPrepareOptionsMenu(Menu menu) {
+ // Options only shows telephony-related settings (ringtone, send to voicemail).
+ // ==> Hide if we don't have a telephone
+ final MenuItem optionsMenu = menu.findItem(R.id.menu_options);
+ final boolean deviceHasPhone = PhoneCapabilityTester.isPhoneCallIntentRegistered(mContext);
+ optionsMenu.setVisible(deviceHasPhone);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.menu_edit: {
+ mListener.onEditRequested(mLookupUri);
+ break;
+ }
+ case R.id.menu_delete: {
+ if (mListener != null) mListener.onDeleteRequested(mLookupUri);
+ return true;
+ }
+ case R.id.menu_options: {
+ final Intent intent = new Intent(mContext, ContactOptionsActivity.class);
+ intent.setData(mContactData.getLookupUri());
+ mContext.startActivity(intent);
+ return true;
+ }
+ case R.id.menu_share: {
+ if (mAllRestricted) return false;
+
+ final String lookupKey = mContactData.getLookupKey();
+ final Uri shareUri = Uri.withAppendedPath(Contacts.CONTENT_VCARD_URI, lookupKey);
+
+ final Intent intent = new Intent(Intent.ACTION_SEND);
+ intent.setType(Contacts.CONTENT_VCARD_TYPE);
+ intent.putExtra(Intent.EXTRA_STREAM, shareUri);
+
+ // Launch chooser to share contact via
+ final CharSequence chooseTitle = mContext.getText(R.string.share_via);
+ final Intent chooseIntent = Intent.createChooser(intent, chooseTitle);
+
+ try {
+ mContext.startActivity(chooseIntent);
+ } catch (ActivityNotFoundException ex) {
+ Toast.makeText(mContext, R.string.share_error, Toast.LENGTH_SHORT).show();
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
+ AdapterView.AdapterContextMenuInfo info;
+ try {
+ info = (AdapterView.AdapterContextMenuInfo) menuInfo;
+ } catch (ClassCastException e) {
+ Log.e(TAG, "bad menuInfo", e);
+ return;
+ }
+
+ // This can be null sometimes, don't crash...
+ if (info == null) {
+ Log.e(TAG, "bad menuInfo");
+ return;
+ }
+
+ final ViewEntry entry = mAdapter.getEntry(info.position);
+ menu.setHeaderTitle(R.string.contactOptionsTitle);
+ if (entry.mimetype.equals(CommonDataKinds.Phone.CONTENT_ITEM_TYPE)) {
+ if (mHasPhone) {
+ menu.add(0, 0, 0, R.string.menu_call).setIntent(entry.intent);
+ }
+ if (mHasSms) {
+ // If there is no Phone, SMS is the primary intent
+ final Intent intent = mHasPhone ? entry.secondaryIntent : entry.intent;
+ menu.add(0, 0, 0, R.string.menu_sendSMS).setIntent(intent);
+ }
+ if (!entry.isPrimary && mHasPhone) {
+ menu.add(0, MENU_ITEM_MAKE_DEFAULT, 0, R.string.menu_makeDefaultNumber);
+ }
+ } else if (entry.mimetype.equals(CommonDataKinds.Email.CONTENT_ITEM_TYPE)) {
+ menu.add(0, 0, 0, R.string.menu_sendEmail).setIntent(entry.intent);
+ if (!entry.isPrimary) {
+ menu.add(0, MENU_ITEM_MAKE_DEFAULT, 0, R.string.menu_makeDefaultEmail);
+ }
+ } else if (entry.mimetype.equals(CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE)) {
+ menu.add(0, 0, 0, R.string.menu_viewAddress).setIntent(entry.intent);
+ }
+ }
+
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ if (mListener == null) return;
+ final ViewEntry entry = mAdapter.getEntry(position);
+ if (entry == null) return;
+ final Intent intent = entry.intent;
+ if (intent == null) return;
+ mListener.onItemClicked(intent);
+ }
+
+ @Override
+ public boolean onContextItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case MENU_ITEM_MAKE_DEFAULT: {
+ if (makeItemDefault(item)) {
+ return true;
+ }
+ break;
+ }
+ }
+
+ return false;
+ }
+
+ private boolean makeItemDefault(MenuItem item) {
+ ViewEntry entry = getViewEntryForMenuItem(item);
+ if (entry == null) {
+ return false;
+ }
+
+ // Update the primary values in the data record.
+ ContentValues values = new ContentValues(1);
+ values.put(Data.IS_SUPER_PRIMARY, 1);
+
+ mContext.getContentResolver().update(ContentUris.withAppendedId(Data.CONTENT_URI, entry.id),
+ values, null, null);
+ return true;
+ }
+
+ private ViewEntry getViewEntryForMenuItem(MenuItem item) {
+ AdapterView.AdapterContextMenuInfo info;
+ try {
+ info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
+ } catch (ClassCastException e) {
+ Log.e(TAG, "bad menuInfo", e);
+ return null;
+ }
+
+ return mAdapter.getEntry(info.position);
+ }
+
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_CALL: {
+ try {
+ ITelephony phone = ITelephony.Stub.asInterface(
+ ServiceManager.checkService("phone"));
+ if (phone != null && !phone.isIdle()) {
+ // Skip out and let the key be handled at a higher level
+ break;
+ }
+ } catch (RemoteException re) {
+ // Fall through and try to call the contact
+ }
+
+ int index = mListView.getSelectedItemPosition();
+ if (index != -1) {
+ final ViewEntry entry = mAdapter.getEntry(index);
+ if (entry != null && entry.intent != null &&
+ entry.intent.getAction() == Intent.ACTION_CALL_PRIVILEGED) {
+ mContext.startActivity(entry.intent);
+ return true;
+ }
+ } else if (mPrimaryPhoneUri != null) {
+ // There isn't anything selected, call the default number
+ final Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
+ mPrimaryPhoneUri);
+ mContext.startActivity(intent);
+ return true;
+ }
+ return false;
+ }
+
+ case KeyEvent.KEYCODE_DEL: {
+ if (mListener != null) mListener.onDeleteRequested(mLookupUri);
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * The listener for the detail loader
+ */
+ private final LoaderManager.LoaderCallbacks<ContactLoader.Result> mDetailLoaderListener =
+ new LoaderCallbacks<ContactLoader.Result>() {
+ @Override
+ public Loader<ContactLoader.Result> onCreateLoader(int id, Bundle args) {
+ return new ContactLoader(mContext, mLookupUri);
+ }
+
+ @Override
+ public void onLoadFinished(Loader<ContactLoader.Result> loader, ContactLoader.Result data) {
+ if (data == ContactLoader.Result.NOT_FOUND) {
+ // Item has been deleted
+ Log.i(TAG, "No contact found. Closing activity");
+ mListener.onContactNotFound();
+ return;
+ }
+ mContactData = data;
+ bindData();
+ }
+ };
+
+ private ContactDetailHeaderView.Listener mHeaderViewListener =
+ new ContactDetailHeaderView.Listener() {
+ @Override
+ public void onDisplayNameClick(View view) {
+ }
+
+ @Override
+ public void onPhotoClick(View view) {
+ }
+ };
+
+
+ public static interface Listener {
+ /**
+ * Contact was not found, so somehow close this fragment. This is raised after a contact
+ * is removed via Menu/Delete
+ */
+ public void onContactNotFound();
+
+ /**
+ * User decided to go to Edit-Mode
+ */
+ public void onEditRequested(Uri lookupUri);
+
+ /**
+ * User clicked a single item (e.g. mail)
+ */
+ public void onItemClicked(Intent intent);
+
+ /**
+ * User decided to delete the contact
+ */
+ public void onDeleteRequested(Uri lookupUri);
+ }
+}
diff --git a/src/com/android/contacts/views/detail/ContactDetailHeaderView.java b/src/com/android/contacts/views/detail/ContactDetailHeaderView.java
new file mode 100644
index 0000000..00203da
--- /dev/null
+++ b/src/com/android/contacts/views/detail/ContactDetailHeaderView.java
@@ -0,0 +1,368 @@
+/*
+ * 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.views.detail;
+
+import com.android.contacts.R;
+import com.android.contacts.views.ContactLoader;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Entity;
+import android.content.Entity.NamedContentValues;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.os.SystemClock;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.StatusUpdates;
+import android.provider.ContactsContract.CommonDataKinds.Photo;
+import android.text.TextUtils;
+import android.text.format.DateUtils;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.CheckBox;
+import android.widget.ImageButton;
+import android.widget.QuickContactBadge;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+/**
+ * Header for displaying a title bar with contact info. You
+ * can bind specific values by calling
+ * {@link ContactDetailHeaderView#loadData(com.android.contacts.views.ContactLoader.Result)}
+ */
+public class ContactDetailHeaderView extends FrameLayout implements View.OnClickListener {
+
+ private static final String TAG = "ContactDetailHeaderView";
+
+ private TextView mDisplayNameView;
+ private TextView mPhoneticNameView;
+ private CheckBox mStarredView;
+ private QuickContactBadge mPhotoView;
+ private ImageView mPresenceView;
+ private TextView mStatusView;
+ private TextView mStatusAttributionView;
+
+ private Uri mContactUri;
+ private Listener mListener;
+
+ /**
+ * Interface for callbacks invoked when the user interacts with a header.
+ */
+ public interface Listener {
+ public void onPhotoClick(View view);
+ public void onDisplayNameClick(View view);
+ }
+
+ public ContactDetailHeaderView(Context context) {
+ this(context, null);
+ }
+
+ public ContactDetailHeaderView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public ContactDetailHeaderView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ final LayoutInflater inflater =
+ (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ inflater.inflate(R.layout.contact_detail_header_view, this);
+
+ mDisplayNameView = (TextView) findViewById(R.id.name);
+
+ mPhoneticNameView = (TextView) findViewById(R.id.phonetic_name);
+
+ mStarredView = (CheckBox)findViewById(R.id.star);
+ mStarredView.setOnClickListener(this);
+
+ mPhotoView = (QuickContactBadge) findViewById(R.id.photo);
+
+ mPresenceView = (ImageView) findViewById(R.id.presence);
+
+ mStatusView = (TextView)findViewById(R.id.status);
+ mStatusAttributionView = (TextView)findViewById(R.id.status_date);
+ }
+
+ /**
+ * Loads the data from the Loader-Result. This is the only function that has to be called
+ * from the outside to fully setup the View
+ */
+ public void loadData(ContactLoader.Result contactData) {
+ mContactUri = contactData.getLookupUri();
+ mPhotoView.assignContactUri(contactData.getLookupUri());
+
+ setDisplayName(contactData.getDisplayName(), contactData.getPhoneticName());
+ setPhoto(findPhoto(contactData));
+ setStared(contactData.getStarred());
+ setPresence(contactData.getPresence());
+ setStatus(
+ contactData.getStatus(), contactData.getStatusTimestamp(),
+ contactData.getStatusLabel(), contactData.getStatusResPackage());
+ }
+
+ /**
+ * Looks for the photo data item in entities. If found, creates a new Bitmap instance. If
+ * not found, returns null
+ */
+ private Bitmap findPhoto(ContactLoader.Result contactData) {
+ final long photoId = contactData.getPhotoId();
+
+ for (Entity entity : contactData.getEntities()) {
+ for (NamedContentValues subValue : entity.getSubValues()) {
+ final ContentValues entryValues = subValue.values;
+ final long dataId = entryValues.getAsLong(Data._ID);
+ final String mimeType = entryValues.getAsString(Data.MIMETYPE);
+
+ if (dataId == photoId) {
+ // Correct Data Id but incorrect MimeType? Don't load
+ if (!Photo.CONTENT_ITEM_TYPE.equals(mimeType)) return null;
+ final byte[] binaryData = entryValues.getAsByteArray(Photo.PHOTO);
+ if (binaryData == null) return null;
+ return BitmapFactory.decodeByteArray(binaryData, 0, binaryData.length);
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Set the given {@link Listener} to handle header events.
+ */
+ public void setListener(Listener listener) {
+ mListener = listener;
+ }
+
+ private void performPhotoClick() {
+ if (mListener != null) {
+ mListener.onPhotoClick(mPhotoView);
+ }
+ }
+
+ private void performDisplayNameClick() {
+ if (mListener != null) {
+ mListener.onDisplayNameClick(mDisplayNameView);
+ }
+ }
+
+ /**
+ * Set the starred state of this header widget.
+ */
+ private void setStared(boolean starred) {
+ mStarredView.setChecked(starred);
+ }
+
+ /**
+ * Set the presence. If presence is null, it is hidden.
+ */
+ private void setPresence(Integer presence) {
+ if (presence == null) {
+ mPresenceView.setVisibility(View.GONE);
+ } else {
+ mPresenceView.setVisibility(View.VISIBLE);
+ mPresenceView.setImageResource(StatusUpdates.getPresenceIconResourceId(
+ presence.intValue()));
+ }
+ }
+
+ /**
+ * Set the photo to display in the header. If bitmap is null, the default placeholder
+ * image is shown
+ */
+ private void setPhoto(Bitmap bitmap) {
+ mPhotoView.setImageBitmap(bitmap == null ? loadPlaceholderPhoto() : bitmap);
+ }
+
+ /**
+ * Set the display name and phonetic name to show in the header.
+ */
+ private void setDisplayName(CharSequence displayName, CharSequence phoneticName) {
+ mDisplayNameView.setText(displayName);
+ if (TextUtils.isEmpty(phoneticName)) {
+ mPhoneticNameView.setVisibility(View.GONE);
+ } else {
+ mPhoneticNameView.setText(phoneticName);
+ mPhoneticNameView.setVisibility(View.VISIBLE);
+ }
+ }
+
+ /**
+ * Set the social snippet text to display in the header.
+ */
+ private void setSocialSnippet(CharSequence snippet) {
+ if (snippet == null) {
+ mStatusView.setVisibility(View.GONE);
+ mStatusAttributionView.setVisibility(View.GONE);
+ } else {
+ mStatusView.setText(snippet);
+ mStatusView.setVisibility(View.VISIBLE);
+ }
+ }
+
+ /**
+ * Set the status attribution text to display in the header.
+ */
+ private void setStatusAttribution(CharSequence attribution) {
+ if (attribution == null) {
+ mStatusAttributionView.setVisibility(View.GONE);
+ } else {
+ mStatusAttributionView.setText(attribution);
+ mStatusAttributionView.setVisibility(View.VISIBLE);
+ }
+ }
+
+ /**
+ * Set a list of specific MIME-types to exclude and not display. For
+ * example, this can be used to hide the {@link Contacts#CONTENT_ITEM_TYPE}
+ * profile icon.
+ */
+ public void setExcludeMimes(String[] excludeMimes) {
+ mPhotoView.setExcludeMimes(excludeMimes);
+ }
+
+ /**
+ * Set all the status values to display in the header.
+ * @param status The status of the contact. If this is either null or empty,
+ * the status is cleared and the other parameters are ignored.
+ * @param statusTimestamp The timestamp (retrieved via a call to
+ * {@link System#currentTimeMillis()}) of the last status update.
+ * This value can be null if it is not known.
+ * @param statusLabel The id of a resource string that specifies the current
+ * status. This value can be null if no Label should be used.
+ * @param statusResPackage The name of the resource package containing the resource string
+ * referenced in the parameter statusLabel.
+ */
+ private void setStatus(final String status, final Long statusTimestamp,
+ final Integer statusLabel, final String statusResPackage) {
+ if (TextUtils.isEmpty(status)) {
+ setSocialSnippet(null);
+ return;
+ }
+
+ setSocialSnippet(status);
+
+ final CharSequence timestampDisplayValue;
+
+ if (statusTimestamp != null) {
+ // Set the date/time field by mixing relative and absolute
+ // times.
+ int flags = DateUtils.FORMAT_ABBREV_RELATIVE;
+
+ timestampDisplayValue = DateUtils.getRelativeTimeSpanString(
+ statusTimestamp.longValue(), System.currentTimeMillis(),
+ DateUtils.MINUTE_IN_MILLIS, flags);
+ } else {
+ timestampDisplayValue = null;
+ }
+
+
+ String labelDisplayValue = null;
+
+ if (statusLabel != null) {
+ Resources resources;
+ if (TextUtils.isEmpty(statusResPackage)) {
+ resources = getResources();
+ } else {
+ PackageManager pm = getContext().getPackageManager();
+ try {
+ resources = pm.getResourcesForApplication(statusResPackage);
+ } catch (NameNotFoundException e) {
+ Log.w(TAG, "Contact status update resource package not found: "
+ + statusResPackage);
+ resources = null;
+ }
+ }
+
+ if (resources != null) {
+ try {
+ labelDisplayValue = resources.getString(statusLabel.intValue());
+ } catch (NotFoundException e) {
+ Log.w(TAG, "Contact status update resource not found: " + statusResPackage + "@"
+ + statusLabel.intValue());
+ }
+ }
+ }
+
+ final CharSequence attribution;
+ if (timestampDisplayValue != null && labelDisplayValue != null) {
+ attribution = getContext().getString(
+ R.string.contact_status_update_attribution_with_date,
+ timestampDisplayValue, labelDisplayValue);
+ } else if (timestampDisplayValue == null && labelDisplayValue != null) {
+ attribution = getContext().getString(
+ R.string.contact_status_update_attribution,
+ labelDisplayValue);
+ } else if (timestampDisplayValue != null) {
+ attribution = timestampDisplayValue;
+ } else {
+ attribution = null;
+ }
+ setStatusAttribution(attribution);
+ }
+
+ public void onClick(View view) {
+ switch (view.getId()) {
+ case R.id.star: {
+ // Toggle "starred" state
+ // Make sure there is a contact
+ if (mContactUri != null) {
+ // TODO: This should be done in the background
+ final ContentValues values = new ContentValues(1);
+ values.put(Contacts.STARRED, mStarredView.isChecked());
+ mContext.getContentResolver().update(mContactUri, values, null, null);
+ }
+ break;
+ }
+ case R.id.photo: {
+ performPhotoClick();
+ break;
+ }
+ case R.id.name: {
+ performDisplayNameClick();
+ break;
+ }
+ }
+ }
+
+ private Bitmap loadPlaceholderPhoto() {
+ // Set the photo with a random "no contact" image
+ final long now = SystemClock.elapsedRealtime();
+ final int num = (int) now & 0xf;
+ final int resourceId;
+ if (num < 9) {
+ // Leaning in from right, common
+ resourceId = R.drawable.ic_contact_picture;
+ } else if (num < 14) {
+ // Leaning in from left uncommon
+ resourceId = R.drawable.ic_contact_picture_2;
+ } else {
+ // Coming in from the top, rare
+ resourceId = R.drawable.ic_contact_picture_3;
+ }
+
+ return BitmapFactory.decodeResource(mContext.getResources(), resourceId);
+ }
+}
diff --git a/src/com/android/contacts/views/detail/ContactNoneFragment.java b/src/com/android/contacts/views/detail/ContactNoneFragment.java
new file mode 100644
index 0000000..8d5794e
--- /dev/null
+++ b/src/com/android/contacts/views/detail/ContactNoneFragment.java
@@ -0,0 +1,33 @@
+/*
+ * 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.views.detail;
+
+import com.android.contacts.R;
+
+import android.app.Fragment;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+public class ContactNoneFragment extends Fragment {
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
+ return inflater.inflate(R.layout.contact_none_fragment, container, false);
+ }
+}
diff --git a/src/com/android/contacts/views/editor/AggregationSuggestionEngine.java b/src/com/android/contacts/views/editor/AggregationSuggestionEngine.java
new file mode 100644
index 0000000..11f10f9
--- /dev/null
+++ b/src/com/android/contacts/views/editor/AggregationSuggestionEngine.java
@@ -0,0 +1,356 @@
+/*
+ * 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.views.editor;
+
+import com.android.contacts.model.EntityDelta.ValuesDelta;
+import com.google.android.collect.Lists;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Message;
+import android.os.Process;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.Nickname;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.Photo;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Contacts.AggregationSuggestions;
+import android.provider.ContactsContract.Contacts.AggregationSuggestions.Builder;
+import android.provider.ContactsContract.Data;
+import android.text.TextUtils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Runs asynchronous queries to obtain aggregation suggestions in the as-you-type mode.
+ */
+public class AggregationSuggestionEngine extends HandlerThread {
+ public static final String TAG = "AggregationSuggestionEngine";
+
+ private static final int MESSAGE_NAME_CHANGE = 1;
+ private static final int MESSAGE_DATA_CURSOR = 2;
+
+ private static final long SUGGESTION_LOOKUP_DELAY_MILLIS = 300;
+
+ private static final int MAX_SUGGESTION_COUNT = 3;
+
+ private final Context mContext;
+
+ private long[] mSuggestedContactIds = new long[0];
+
+ private Handler mMainHandler;
+ private Handler mHandler;
+ private long mContactId;
+ private Listener mListener;
+ private Cursor mDataCursor;
+
+ public interface Listener {
+ void onAggregationSuggestionChange();
+ }
+
+ public static final class Suggestion {
+ public long contactId;
+ public List<Long> rawContactIds;
+ public String lookupKey;
+ public String name;
+ public String phoneNumber;
+ public String emailAddress;
+ public String nickname;
+ public byte[] photo;
+
+ @Override
+ public String toString() {
+ return "ID: " + contactId + " rawContactIds: " + rawContactIds + " name: " + name
+ + " phone: " + phoneNumber + " email: " + emailAddress + " nickname: "
+ + nickname + (photo != null ? " [has photo]" : "");
+ }
+ }
+
+ public AggregationSuggestionEngine(Context context) {
+ super("AggregationSuggestions", Process.THREAD_PRIORITY_BACKGROUND);
+ mContext = context;
+ mMainHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ AggregationSuggestionEngine.this.deliverNotification((Cursor) msg.obj);
+ }
+ };
+ }
+
+ protected Handler getHandler() {
+ if (mHandler == null) {
+ mHandler = new Handler(getLooper()) {
+ @Override
+ public void handleMessage(Message msg) {
+ AggregationSuggestionEngine.this.handleMessage(msg);
+ }
+ };
+ }
+ return mHandler;
+ }
+
+ public void setContactId(long contactId) {
+ mContactId = contactId;
+ }
+
+ public void setListener(Listener listener) {
+ mListener = listener;
+ }
+
+ @Override
+ public boolean quit() {
+ if (mDataCursor != null) {
+ mDataCursor.close();
+ }
+ mDataCursor = null;
+ return super.quit();
+ }
+
+ public void onNameChange(ValuesDelta values) {
+ Handler handler = getHandler();
+ handler.removeMessages(MESSAGE_NAME_CHANGE);
+
+ Uri uri = buildAggregationSuggestionUri(values);
+ if (uri == null) {
+ return;
+ }
+
+ Message msg = handler.obtainMessage(MESSAGE_NAME_CHANGE, uri);
+ handler.sendMessageDelayed(msg, SUGGESTION_LOOKUP_DELAY_MILLIS);
+ }
+
+ private Uri buildAggregationSuggestionUri(ValuesDelta values) {
+ StringBuilder nameSb = new StringBuilder();
+ appendValue(nameSb, values, StructuredName.PREFIX);
+ appendValue(nameSb, values, StructuredName.GIVEN_NAME);
+ appendValue(nameSb, values, StructuredName.MIDDLE_NAME);
+ appendValue(nameSb, values, StructuredName.FAMILY_NAME);
+ appendValue(nameSb, values, StructuredName.SUFFIX);
+
+ StringBuilder phoneticNameSb = new StringBuilder();
+ appendValue(phoneticNameSb, values, StructuredName.PHONETIC_FAMILY_NAME);
+ appendValue(phoneticNameSb, values, StructuredName.PHONETIC_MIDDLE_NAME);
+ appendValue(phoneticNameSb, values, StructuredName.PHONETIC_GIVEN_NAME);
+
+ if (nameSb.length() == 0 && phoneticNameSb.length() == 0) {
+ return null;
+ }
+
+ Builder builder = AggregationSuggestions.builder()
+ .setLimit(MAX_SUGGESTION_COUNT)
+ .setContactId(mContactId);
+
+ if (nameSb.length() != 0) {
+ builder.addParameter(AggregationSuggestions.PARAMETER_MATCH_NAME, nameSb.toString());
+ }
+
+ if (phoneticNameSb.length() != 0) {
+ builder.addParameter(
+ AggregationSuggestions.PARAMETER_MATCH_NAME, phoneticNameSb.toString());
+ }
+
+ return builder.build();
+ }
+
+ private void appendValue(StringBuilder sb, ValuesDelta values, String column) {
+ String value = values.getAsString(column);
+ if (!TextUtils.isEmpty(value)) {
+ if (sb.length() > 0) {
+ sb.append(' ');
+ }
+ sb.append(value);
+ }
+ }
+
+ protected void handleMessage(Message msg) {
+ switch(msg.what) {
+ case MESSAGE_NAME_CHANGE:
+ loadAggregationSuggestions((Uri) msg.obj);
+ break;
+ }
+ }
+
+ private static final class DataQuery {
+
+ public static final String SELECTION_PREFIX =
+ Data.MIMETYPE + " IN ('"
+ + Phone.CONTENT_ITEM_TYPE + "','"
+ + Email.CONTENT_ITEM_TYPE + "','"
+ + StructuredName.CONTENT_ITEM_TYPE + "','"
+ + Nickname.CONTENT_ITEM_TYPE + "','"
+ + Photo.CONTENT_ITEM_TYPE + "')"
+ + " AND " + Data.CONTACT_ID + " IN (";
+
+ public static final String[] COLUMNS = {
+ Data._ID,
+ Data.CONTACT_ID,
+ Data.LOOKUP_KEY,
+ Data.PHOTO_ID,
+ Data.DISPLAY_NAME,
+ Data.RAW_CONTACT_ID,
+ Data.MIMETYPE,
+ Data.DATA1,
+ Data.IS_SUPER_PRIMARY,
+ Photo.PHOTO,
+ };
+
+ public static final int ID = 0;
+ public static final int CONTACT_ID = 1;
+ public static final int LOOKUP_KEY = 2;
+ public static final int PHOTO_ID = 3;
+ public static final int DISPLAY_NAME = 4;
+ public static final int RAW_CONTACT_ID = 5;
+ public static final int MIMETYPE = 6;
+ public static final int DATA1 = 7;
+ public static final int IS_SUPERPRIMARY = 8;
+ public static final int PHOTO = 9;
+ }
+
+ private void loadAggregationSuggestions(Uri uri) {
+ ContentResolver contentResolver = mContext.getContentResolver();
+ Cursor cursor = contentResolver.query(uri, new String[]{Contacts._ID}, null, null, null);
+ try {
+ // If a new request is pending, chuck the result of the previous request
+ if (getHandler().hasMessages(MESSAGE_NAME_CHANGE)) {
+ return;
+ }
+
+ boolean changed = updateSuggestedContactIds(cursor);
+ if (!changed) {
+ return;
+ }
+
+ StringBuilder sb = new StringBuilder(DataQuery.SELECTION_PREFIX);
+ int count = mSuggestedContactIds.length;
+ for (int i = 0; i < count; i++) {
+ if (i > 0) {
+ sb.append(',');
+ }
+ sb.append(mSuggestedContactIds[i]);
+ }
+ sb.append(')');
+ sb.toString();
+
+ Cursor dataCursor = contentResolver.query(Data.CONTENT_URI,
+ DataQuery.COLUMNS, sb.toString(), null, Data.CONTACT_ID);
+ mMainHandler.sendMessage(mMainHandler.obtainMessage(MESSAGE_DATA_CURSOR, dataCursor));
+ } finally {
+ cursor.close();
+ }
+ }
+
+ private boolean updateSuggestedContactIds(Cursor cursor) {
+ int count = cursor.getCount();
+ boolean changed = count != mSuggestedContactIds.length;
+ if (!changed) {
+ while (cursor.moveToNext()) {
+ long contactId = cursor.getLong(0);
+ if (Arrays.binarySearch(mSuggestedContactIds, contactId) < 0) {
+ changed = true;
+ break;
+ }
+ }
+ }
+
+ if (changed) {
+ mSuggestedContactIds = new long[count];
+ cursor.moveToPosition(-1);
+ for (int i = 0; i < count; i++) {
+ cursor.moveToNext();
+ mSuggestedContactIds[i] = cursor.getLong(0);
+ }
+ Arrays.sort(mSuggestedContactIds);
+ }
+
+ return changed;
+ }
+
+ protected void deliverNotification(Cursor dataCursor) {
+ if (mDataCursor != null) {
+ mDataCursor.close();
+ }
+ mDataCursor = dataCursor;
+ if (mListener != null) {
+ mListener.onAggregationSuggestionChange();
+ }
+ }
+
+ public int getSuggestedContactCount() {
+ return mSuggestedContactIds.length;
+ }
+
+ public List<Suggestion> getSuggestions() {
+ ArrayList<Suggestion> list = Lists.newArrayList();
+ if (mDataCursor != null) {
+ Suggestion suggestion = null;
+ long currentContactId = -1;
+ mDataCursor.moveToPosition(-1);
+ while (mDataCursor.moveToNext()) {
+ long contactId = mDataCursor.getLong(DataQuery.CONTACT_ID);
+ if (contactId != currentContactId) {
+ suggestion = new Suggestion();
+ suggestion.contactId = contactId;
+ suggestion.name = mDataCursor.getString(DataQuery.DISPLAY_NAME);
+ suggestion.rawContactIds = Lists.newArrayList();
+ list.add(suggestion);
+ currentContactId = contactId;
+ }
+
+ Long rawContactId = Long.valueOf(mDataCursor.getLong(DataQuery.RAW_CONTACT_ID));
+ if (!suggestion.rawContactIds.contains(rawContactId)) {
+ suggestion.rawContactIds.add(rawContactId);
+ }
+
+ String mimetype = mDataCursor.getString(DataQuery.MIMETYPE);
+ if (Phone.CONTENT_ITEM_TYPE.equals(mimetype)) {
+ String data = mDataCursor.getString(DataQuery.DATA1);
+ int superprimary = mDataCursor.getInt(DataQuery.IS_SUPERPRIMARY);
+ if (!TextUtils.isEmpty(data)
+ && (superprimary != 0 || suggestion.phoneNumber == null)) {
+ suggestion.phoneNumber = data;
+ }
+ } else if (Email.CONTENT_ITEM_TYPE.equals(mimetype)) {
+ String data = mDataCursor.getString(DataQuery.DATA1);
+ int superprimary = mDataCursor.getInt(DataQuery.IS_SUPERPRIMARY);
+ if (!TextUtils.isEmpty(data)
+ && (superprimary != 0 || suggestion.emailAddress == null)) {
+ suggestion.emailAddress = data;
+ }
+ } else if (Nickname.CONTENT_ITEM_TYPE.equals(mimetype)) {
+ String data = mDataCursor.getString(DataQuery.DATA1);
+ if (!TextUtils.isEmpty(data)) {
+ suggestion.nickname = data;
+ }
+ } else if (Photo.CONTENT_ITEM_TYPE.equals(mimetype)) {
+ long dataId = mDataCursor.getLong(DataQuery.ID);
+ long photoId = mDataCursor.getLong(DataQuery.PHOTO_ID);
+ if (dataId == photoId && !mDataCursor.isNull(DataQuery.PHOTO)) {
+ suggestion.photo = mDataCursor.getBlob(DataQuery.PHOTO);
+ }
+ }
+ }
+ }
+ return list;
+ }
+}
diff --git a/src/com/android/contacts/views/editor/ContactEditorFragment.java b/src/com/android/contacts/views/editor/ContactEditorFragment.java
new file mode 100644
index 0000000..55ec604
--- /dev/null
+++ b/src/com/android/contacts/views/editor/ContactEditorFragment.java
@@ -0,0 +1,1550 @@
+/*
+ * 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.views.editor;
+
+import com.android.contacts.JoinContactActivity;
+import com.android.contacts.R;
+import com.android.contacts.model.ContactsSource;
+import com.android.contacts.model.ContactsSource.EditType;
+import com.android.contacts.model.Editor;
+import com.android.contacts.model.Editor.EditorListener;
+import com.android.contacts.model.EntityDelta;
+import com.android.contacts.model.EntityDelta.ValuesDelta;
+import com.android.contacts.model.EntityDeltaList;
+import com.android.contacts.model.EntityModifier;
+import com.android.contacts.model.GoogleSource;
+import com.android.contacts.model.Sources;
+import com.android.contacts.ui.ViewIdGenerator;
+import com.android.contacts.ui.widget.AggregationSuggestionView;
+import com.android.contacts.ui.widget.BaseContactEditorView;
+import com.android.contacts.ui.widget.ContactEditorView;
+import com.android.contacts.ui.widget.GenericEditorView;
+import com.android.contacts.ui.widget.PhotoEditorView;
+import com.android.contacts.util.EmptyService;
+import com.android.contacts.util.WeakAsyncTask;
+import com.android.contacts.views.ContactLoader;
+import com.android.contacts.views.editor.AggregationSuggestionEngine.Suggestion;
+import com.google.android.collect.Lists;
+
+import android.accounts.Account;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.Fragment;
+import android.app.LoaderManager;
+import android.app.LoaderManager.LoaderCallbacks;
+import android.content.ActivityNotFoundException;
+import android.content.ContentProviderOperation;
+import android.content.ContentProviderOperation.Builder;
+import android.content.ContentProviderResult;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Entity;
+import android.content.Intent;
+import android.content.Loader;
+import android.content.OperationApplicationException;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.media.MediaScannerConnection;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.AggregationExceptions;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.RawContacts;
+import android.provider.MediaStore;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.ContextThemeWrapper;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+import android.view.ViewStub;
+import android.widget.ArrayAdapter;
+import android.widget.LinearLayout;
+import android.widget.ListAdapter;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import java.io.File;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.List;
+
+public class ContactEditorFragment extends Fragment implements
+ SplitContactConfirmationDialogFragment.Listener, PickPhotoDialogFragment.Listener,
+ SelectAccountDialogFragment.Listener, AggregationSuggestionEngine.Listener {
+
+ private static final String TAG = "ContactEditorFragment";
+
+ private static final int LOADER_DATA = 1;
+
+ private static final String KEY_URI = "uri";
+ private static final String KEY_ACTION = "action";
+ private static final String KEY_EDIT_STATE = "state";
+ private static final String KEY_RAW_CONTACT_ID_REQUESTING_PHOTO = "photorequester";
+ private static final String KEY_VIEW_ID_GENERATOR = "viewidgenerator";
+ private static final String KEY_CURRENT_PHOTO_FILE = "currentphotofile";
+ private static final String KEY_QUERY_SELECTION = "queryselection";
+ private static final String KEY_CONTACT_ID_FOR_JOIN = "contactidforjoin";
+
+ /**
+ * Modes that specify what the AsyncTask has to perform after saving
+ */
+ private interface SaveMode {
+ /**
+ * Close the editor after saving
+ */
+ public static final int CLOSE = 0;
+
+ /**
+ * Reload the data so that the user can continue editing
+ */
+ public static final int RELOAD = 1;
+
+ /**
+ * Split the contact after saving
+ */
+ public static final int SPLIT = 2;
+
+ /**
+ * Join another contact after saving
+ */
+ public static final int JOIN = 3;
+ }
+
+ private static final int REQUEST_CODE_JOIN = 0;
+ private static final int REQUEST_CODE_CAMERA_WITH_DATA = 1;
+ private static final int REQUEST_CODE_PHOTO_PICKED_WITH_DATA = 2;
+
+ private long mRawContactIdRequestingPhoto = -1;
+
+ private final EntityDeltaComparator mComparator = new EntityDeltaComparator();
+
+ private static final int ICON_SIZE = 96;
+
+ private static final File PHOTO_DIR = new File(
+ Environment.getExternalStorageDirectory() + "/DCIM/Camera");
+
+ /**
+ * A delay in milliseconds used for bringing aggregation suggestions to
+ * the visible part of the screen. The reason this has to be done after
+ * a delay is a race condition with the soft keyboard. The keyboard
+ * may expand to display its own autocomplete suggestions, which will
+ * reduce the visible area of the screen. We will yield to the keyboard
+ * hoping that the delay is sufficient. If not - part of the
+ * suggestion will be hidden, which is not fatal.
+ */
+ private static final int AGGREGATION_SUGGESTION_SCROLL_DELAY = 200;
+
+ private File mCurrentPhotoFile;
+
+ private Context mContext;
+ private String mAction;
+ private Uri mLookupUri;
+ private String mMimeType;
+ private Bundle mIntentExtras;
+ private Listener mListener;
+
+ private String mQuerySelection;
+
+ private long mContactIdForJoin;
+
+ private LinearLayout mContent;
+ private EntityDeltaList mState;
+
+ private ViewIdGenerator mViewIdGenerator;
+
+ private long mLoaderStartTime;
+
+ private AggregationSuggestionEngine mAggregationSuggestionEngine;
+
+ public ContactEditorFragment() {
+ }
+
+ @Override
+ public void onAttach(Activity activity) {
+ super.onAttach(activity);
+ mContext = activity;
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ if (mAggregationSuggestionEngine != null) {
+ mAggregationSuggestionEngine.quit();
+ }
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
+ final View view = inflater.inflate(R.layout.contact_editor_fragment, container, false);
+
+ mContent = (LinearLayout) view.findViewById(R.id.editors);
+
+ setHasOptionsMenu(true);
+
+ return view;
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ if (Intent.ACTION_EDIT.equals(mAction)) {
+ if (mListener != null) mListener.setTitleTo(R.string.editContact_title_edit);
+ getLoaderManager().initLoader(LOADER_DATA, null, mDataLoaderListener);
+ } else if (Intent.ACTION_INSERT.equals(mAction)) {
+ if (mListener != null) mListener.setTitleTo(R.string.editContact_title_insert);
+
+ doAddAction();
+ } else throw new IllegalArgumentException("Unknown Action String " + mAction +
+ ". Only support " + Intent.ACTION_EDIT + " or " + Intent.ACTION_INSERT);
+ }
+
+ public void load(String action, Uri lookupUri, String mimeType, Bundle intentExtras) {
+ mAction = action;
+ mLookupUri = lookupUri;
+ mMimeType = mimeType;
+ mIntentExtras = intentExtras;
+ }
+
+ public void setListener(Listener value) {
+ mListener = value;
+ }
+
+ @Override
+ public void onCreate(Bundle savedState) {
+ if (savedState != null) {
+ // Restore mUri before calling super.onCreate so that onInitializeLoaders
+ // would already have a uri and an action to work with
+ mLookupUri = savedState.getParcelable(KEY_URI);
+ mAction = savedState.getString(KEY_ACTION);
+ }
+
+ super.onCreate(savedState);
+
+ if (savedState == null) {
+ // If savedState is non-null, onRestoreInstanceState() will restore the generator.
+ mViewIdGenerator = new ViewIdGenerator();
+ } else {
+ // Read modifications from instance
+ mState = savedState.<EntityDeltaList> getParcelable(KEY_EDIT_STATE);
+ mRawContactIdRequestingPhoto = savedState.getLong(
+ KEY_RAW_CONTACT_ID_REQUESTING_PHOTO);
+ mViewIdGenerator = savedState.getParcelable(KEY_VIEW_ID_GENERATOR);
+ String fileName = savedState.getString(KEY_CURRENT_PHOTO_FILE);
+ if (fileName != null) {
+ mCurrentPhotoFile = new File(fileName);
+ }
+ mQuerySelection = savedState.getString(KEY_QUERY_SELECTION);
+ mContactIdForJoin = savedState.getLong(KEY_CONTACT_ID_FOR_JOIN);
+ }
+ }
+
+ public void setData(ContactLoader.Result data) {
+ // If we have already loaded data, we do not want to change it here to not confuse the user
+ if (mState != null) {
+ Log.v(TAG, "Ignoring background change. This will have to be rebased later");
+ return;
+ }
+
+ ArrayList<Entity> entities = data.getEntities();
+ StringBuilder sb = new StringBuilder(RawContacts._ID + " IN(");
+ int count = entities.size();
+ for (int i = 0; i < count; i++) {
+ if (i > 0) {
+ sb.append(',');
+ }
+ sb.append(entities.get(i).getEntityValues().get(RawContacts._ID));
+ }
+ sb.append(")");
+ mQuerySelection = sb.toString();
+ mState = EntityDeltaList.fromIterator(entities.iterator());
+
+
+ // TODO: Merge in Intent parameters can only be done on the first load.
+ // The behaviour for subsequent loads is probably broken, so fix this
+ final boolean hasExtras = mIntentExtras != null && mIntentExtras.size() > 0;
+ final boolean hasState = mState.size() > 0;
+ if (hasExtras && hasState) {
+ // Find source defining the first RawContact found
+ // TODO: Test this. Can we actually always use the first RawContact. This seems wrong
+ final EntityDelta state = mState.get(0);
+ final String accountType = state.getValues().getAsString(RawContacts.ACCOUNT_TYPE);
+ final Sources sources = Sources.getInstance(mContext);
+ final ContactsSource source = sources.getInflatedSource(accountType,
+ ContactsSource.LEVEL_CONSTRAINTS);
+ EntityModifier.parseExtras(mContext, source, state, mIntentExtras);
+ }
+ bindEditors();
+ }
+
+ public void selectAccountAndCreateContact(boolean isNewContact) {
+ final ArrayList<Account> accounts = Sources.getInstance(mContext).getAccounts(true);
+ // No Accounts available. Create a phone-local contact.
+ if (accounts.isEmpty()) {
+ createContact(null, isNewContact);
+ return; // Don't show a dialog.
+ }
+
+ // In the common case of a single account being writable, auto-select
+ // it without showing a dialog.
+ if (accounts.size() == 1) {
+ createContact(accounts.get(0), isNewContact);
+ return; // Don't show a dialog.
+ }
+
+ final SelectAccountDialogFragment dialog = new SelectAccountDialogFragment(getId());
+ dialog.show(getActivity(), SelectAccountDialogFragment.TAG);
+ }
+
+ /**
+ * @param account may be null to signal a device-local contact should
+ * be created.
+ * @param prefillFromIntent If this is set, the intent extras will be used to prefill the fields
+ */
+ private void createContact(Account account, boolean prefillFromIntent) {
+ final Sources sources = Sources.getInstance(mContext);
+ final ContentValues values = new ContentValues();
+ if (account != null) {
+ values.put(RawContacts.ACCOUNT_NAME, account.name);
+ values.put(RawContacts.ACCOUNT_TYPE, account.type);
+ } else {
+ values.putNull(RawContacts.ACCOUNT_NAME);
+ values.putNull(RawContacts.ACCOUNT_TYPE);
+ }
+
+ // Parse any values from incoming intent
+ EntityDelta insert = new EntityDelta(ValuesDelta.fromAfter(values));
+ final ContactsSource source = sources.getInflatedSource(
+ account != null ? account.type : null,
+ ContactsSource.LEVEL_CONSTRAINTS);
+ EntityModifier.parseExtras(mContext, source, insert,
+ prefillFromIntent ? mIntentExtras : null);
+
+ // Ensure we have some default fields
+ EntityModifier.ensureKindExists(insert, source, Phone.CONTENT_ITEM_TYPE);
+ EntityModifier.ensureKindExists(insert, source, Email.CONTENT_ITEM_TYPE);
+
+ if (mState == null) {
+ // Create state if none exists yet
+ mState = EntityDeltaList.fromSingle(insert);
+ } else {
+ // Add contact onto end of existing state
+ mState.add(insert);
+ }
+
+ bindEditors();
+ }
+
+ private void bindEditors() {
+ // Sort the editors
+ Collections.sort(mState, mComparator);
+
+ // Remove any existing editors and rebuild any visible
+ mContent.removeAllViews();
+
+ final LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
+ Context.LAYOUT_INFLATER_SERVICE);
+ final Sources sources = Sources.getInstance(mContext);
+ int size = mState.size();
+ for (int i = 0; i < size; i++) {
+ // TODO ensure proper ordering of entities in the list
+ final EntityDelta entity = mState.get(i);
+ final ValuesDelta values = entity.getValues();
+ if (!values.isVisible()) continue;
+
+ final String accountType = values.getAsString(RawContacts.ACCOUNT_TYPE);
+ final ContactsSource source = sources.getInflatedSource(accountType,
+ ContactsSource.LEVEL_CONSTRAINTS);
+ final long rawContactId = values.getAsLong(RawContacts._ID);
+
+ final BaseContactEditorView editor;
+ if (!source.readOnly) {
+ editor = (BaseContactEditorView) inflater.inflate(R.layout.item_contact_editor,
+ mContent, false);
+ } else {
+ editor = (BaseContactEditorView) inflater.inflate(
+ R.layout.item_read_only_contact_editor, mContent, false);
+ }
+ final PhotoEditorView photoEditor = editor.getPhotoEditor();
+ photoEditor.setEditorListener(new PhotoListener(rawContactId, source.readOnly,
+ photoEditor));
+
+ mContent.addView(editor);
+ editor.setState(entity, source, mViewIdGenerator);
+
+ if (editor instanceof ContactEditorView) {
+ final ContactEditorView rawContactEditor = (ContactEditorView) editor;
+ final GenericEditorView nameEditor = rawContactEditor.getNameEditor();
+ nameEditor.setEditorListener(new EditorListener() {
+
+ @Override
+ public void onRequest(int request) {
+ onContactNameChange(request, rawContactEditor, nameEditor);
+ }
+
+ @Override
+ public void onDeleted(Editor editor) {
+ }
+ });
+
+ // If the user has already decided to join with a specific contact,
+ // trigger a refresh of the aggregation suggestion view to redisplay
+ // the selection.
+ if (mContactIdForJoin != 0) {
+ acquireAggregationSuggestions(rawContactEditor);
+ }
+ }
+ }
+
+ // Show editor now that we've loaded state
+ mContent.setVisibility(View.VISIBLE);
+
+ // Refresh Action Bar as the visibility of the join command
+ getActivity().invalidateOptionsMenu();
+ }
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, final MenuInflater inflater) {
+ inflater.inflate(R.menu.edit, menu);
+ }
+
+ @Override
+ public void onPrepareOptionsMenu(Menu menu) {
+ menu.findItem(R.id.menu_split).setVisible(mState != null && mState.size() > 1);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.menu_done:
+ return doSaveAction(SaveMode.CLOSE);
+ case R.id.menu_discard:
+ return doRevertAction();
+ case R.id.menu_add_raw_contact:
+ return doAddAction();
+ case R.id.menu_delete:
+ return doDeleteAction();
+ case R.id.menu_split:
+ return doSplitContactAction();
+ case R.id.menu_join:
+ return doJoinContactAction();
+ }
+ return false;
+ }
+
+ private boolean doAddAction() {
+ // Load Accounts async so that we can present them
+ selectAccountAndCreateContact(true);
+
+ return true;
+ }
+
+ /**
+ * Delete the entire contact currently being edited, which usually asks for
+ * user confirmation before continuing.
+ */
+ private boolean doDeleteAction() {
+ if (!hasValidState())
+ return false;
+
+ // TODO: Make sure Insert turns into Edit if/once it is autosaved
+ if (Intent.ACTION_INSERT.equals(mAction)) {
+ if (mListener != null) mListener.onReverted();
+ } else {
+ if (mListener != null) mListener.onDeleteRequested(mLookupUri);
+ }
+ return true;
+ }
+
+ /**
+ * Pick a specific photo to be added under the currently selected tab.
+ */
+ /* package */ boolean doPickPhotoAction(long rawContactId) {
+ if (!hasValidState()) return false;
+
+ mRawContactIdRequestingPhoto = rawContactId;
+ final PickPhotoDialogFragment dialogFragment = new PickPhotoDialogFragment(getId());
+ dialogFragment.show(getActivity(), PickPhotoDialogFragment.TAG);
+
+ return true;
+ }
+
+ private boolean doSplitContactAction() {
+ if (!hasValidState()) return false;
+
+ final SplitContactConfirmationDialogFragment dialog =
+ new SplitContactConfirmationDialogFragment(getId());
+ dialog.show(getActivity(), SplitContactConfirmationDialogFragment.TAG);
+ return true;
+ }
+
+ private boolean doJoinContactAction() {
+ return doSaveAction(SaveMode.JOIN);
+ }
+
+ /**
+ * Constructs an intent for picking a photo from Gallery, cropping it and returning the bitmap.
+ */
+ public static Intent getPhotoPickIntent() {
+ Intent intent = new Intent(Intent.ACTION_GET_CONTENT, null);
+ intent.setType("image/*");
+ intent.putExtra("crop", "true");
+ intent.putExtra("aspectX", 1);
+ intent.putExtra("aspectY", 1);
+ intent.putExtra("outputX", ICON_SIZE);
+ intent.putExtra("outputY", ICON_SIZE);
+ intent.putExtra("return-data", true);
+ return intent;
+ }
+
+ /**
+ * Check if our internal {@link #mState} is valid, usually checked before
+ * performing user actions.
+ */
+ private boolean hasValidState() {
+ return mState != null && mState.size() > 0;
+ }
+
+ /**
+ * Create a file name for the icon photo using current time.
+ */
+ private String getPhotoFileName() {
+ Date date = new Date(System.currentTimeMillis());
+ SimpleDateFormat dateFormat = new SimpleDateFormat("'IMG'_yyyyMMdd_HHmmss");
+ return dateFormat.format(date) + ".jpg";
+ }
+
+ /**
+ * Constructs an intent for capturing a photo and storing it in a temporary file.
+ */
+ public static Intent getTakePickIntent(File f) {
+ Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE, null);
+ intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(f));
+ return intent;
+ }
+
+ /**
+ * Sends a newly acquired photo to Gallery for cropping
+ */
+ protected void doCropPhoto(File f) {
+ try {
+ // Add the image to the media store
+ MediaScannerConnection.scanFile(
+ mContext,
+ new String[] { f.getAbsolutePath() },
+ new String[] { null },
+ null);
+
+ // Launch gallery to crop the photo
+ final Intent intent = getCropImageIntent(Uri.fromFile(f));
+ startActivityForResult(intent, REQUEST_CODE_PHOTO_PICKED_WITH_DATA);
+ } catch (Exception e) {
+ Log.e(TAG, "Cannot crop image", e);
+ Toast.makeText(mContext, R.string.photoPickerNotFoundText, Toast.LENGTH_LONG).show();
+ }
+ }
+
+ /**
+ * Constructs an intent for image cropping.
+ */
+ public static Intent getCropImageIntent(Uri photoUri) {
+ Intent intent = new Intent("com.android.camera.action.CROP");
+ intent.setDataAndType(photoUri, "image/*");
+ intent.putExtra("crop", "true");
+ intent.putExtra("aspectX", 1);
+ intent.putExtra("aspectY", 1);
+ intent.putExtra("outputX", ICON_SIZE);
+ intent.putExtra("outputY", ICON_SIZE);
+ intent.putExtra("return-data", true);
+ return intent;
+ }
+
+ /**
+ * Saves or creates the contact based on the mode, and if successful
+ * finishes the activity.
+ */
+ private boolean doSaveAction(int saveMode) {
+ if (!hasValidState()) {
+ return false;
+ }
+
+ // TODO: Status still needed?
+ //mStatus = STATUS_SAVING;
+ final PersistTask task = new PersistTask(this, saveMode);
+ task.execute(mState);
+
+ return true;
+ }
+
+ /**
+ * Asynchronously saves the changes made by the user. This can be called even if nothing
+ * has changed
+ */
+ public void save(boolean closeAfterSave) {
+ doSaveAction(closeAfterSave ? SaveMode.CLOSE : SaveMode.RELOAD);
+ }
+
+ private boolean doRevertAction() {
+ if (mListener != null) mListener.onReverted();
+
+ return true;
+ }
+
+ private void onSaveCompleted(boolean success, int saveMode, Uri contactLookupUri) {
+ Log.d(TAG, "onSaveCompleted(" + success + ", " + saveMode + ", " + contactLookupUri);
+ switch (saveMode) {
+ case SaveMode.CLOSE:
+ final Intent resultIntent;
+ final int resultCode;
+ if (success && contactLookupUri != null) {
+ final String requestAuthority =
+ mLookupUri == null ? null : mLookupUri.getAuthority();
+
+ final String legacyAuthority = "contacts";
+
+ resultIntent = new Intent();
+ if (legacyAuthority.equals(requestAuthority)) {
+ // Build legacy Uri when requested by caller
+ final long contactId = ContentUris.parseId(Contacts.lookupContact(
+ mContext.getContentResolver(), contactLookupUri));
+ final Uri legacyContentUri = Uri.parse("content://contacts/people");
+ final Uri legacyUri = ContentUris.withAppendedId(
+ legacyContentUri, contactId);
+ resultIntent.setData(legacyUri);
+ } else {
+ // Otherwise pass back a lookup-style Uri
+ resultIntent.setData(contactLookupUri);
+ }
+
+ resultCode = Activity.RESULT_OK;
+ } else {
+ resultCode = Activity.RESULT_CANCELED;
+ resultIntent = null;
+ }
+ if (mListener != null) mListener.onSaveFinished(resultCode, resultIntent);
+ break;
+ case SaveMode.RELOAD:
+ if (success && contactLookupUri != null) {
+ // If this was in INSERT, we are changing into an EDIT now.
+ // If it already was an EDIT, we are changing to the new Uri now
+ mState = null;
+ load(Intent.ACTION_EDIT, contactLookupUri, mMimeType, null);
+ getLoaderManager().restartLoader(LOADER_DATA, null, mDataLoaderListener);
+ }
+ break;
+ case SaveMode.SPLIT:
+ if (mListener != null) {
+ mListener.onAggregationChangeFinished(contactLookupUri);
+ } else {
+ Log.d(TAG, "No listener registered, can not call onSplitFinished");
+ }
+ break;
+
+ case SaveMode.JOIN:
+ //mStatus = STATUS_EDITING;
+ if (success) {
+ showJoinAggregateActivity(contactLookupUri);
+ }
+ break;
+ }
+ }
+
+ /**
+ * Shows a list of aggregates that can be joined into the currently viewed aggregate.
+ *
+ * @param contactLookupUri the fresh URI for the currently edited contact (after saving it)
+ */
+ private void showJoinAggregateActivity(Uri contactLookupUri) {
+ if (contactLookupUri == null) {
+ return;
+ }
+
+ mContactIdForJoin = ContentUris.parseId(contactLookupUri);
+ final Intent intent = new Intent(JoinContactActivity.JOIN_CONTACT);
+ intent.putExtra(JoinContactActivity.EXTRA_TARGET_CONTACT_ID, mContactIdForJoin);
+ startActivityForResult(intent, REQUEST_CODE_JOIN);
+ }
+
+ private interface JoinContactQuery {
+ String[] PROJECTION = {
+ RawContacts._ID,
+ RawContacts.CONTACT_ID,
+ RawContacts.NAME_VERIFIED,
+ };
+
+ String SELECTION = RawContacts.CONTACT_ID + "=? OR " + RawContacts.CONTACT_ID + "=?";
+
+ int _ID = 0;
+ int CONTACT_ID = 1;
+ int NAME_VERIFIED = 2;
+ }
+
+ /**
+ * Performs aggregation with the contact selected by the user from suggestions or A-Z list.
+ */
+ private void joinAggregate(final long contactId) {
+ final ContentResolver resolver = mContext.getContentResolver();
+
+ // Load raw contact IDs for all raw contacts involved - currently edited and selected
+ // in the join UIs
+ Cursor c = resolver.query(RawContacts.CONTENT_URI,
+ JoinContactQuery.PROJECTION,
+ JoinContactQuery.SELECTION,
+ new String[]{String.valueOf(contactId), String.valueOf(mContactIdForJoin)}, null);
+
+ long rawContactIds[];
+ long verifiedNameRawContactId = -1;
+ try {
+ rawContactIds = new long[c.getCount()];
+ for (int i = 0; i < rawContactIds.length; i++) {
+ c.moveToNext();
+ long rawContactId = c.getLong(JoinContactQuery._ID);
+ rawContactIds[i] = rawContactId;
+ if (c.getLong(JoinContactQuery.CONTACT_ID) == mContactIdForJoin) {
+ if (verifiedNameRawContactId == -1
+ || c.getInt(JoinContactQuery.NAME_VERIFIED) != 0) {
+ verifiedNameRawContactId = rawContactId;
+ }
+ }
+ }
+ } finally {
+ c.close();
+ }
+
+ // For each pair of raw contacts, insert an aggregation exception
+ ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>();
+ for (int i = 0; i < rawContactIds.length; i++) {
+ for (int j = 0; j < rawContactIds.length; j++) {
+ if (i != j) {
+ buildJoinContactDiff(operations, rawContactIds[i], rawContactIds[j]);
+ }
+ }
+ }
+
+ // Mark the original contact as "name verified" to make sure that the contact
+ // display name does not change as a result of the join
+ Builder builder = ContentProviderOperation.newUpdate(
+ ContentUris.withAppendedId(RawContacts.CONTENT_URI, verifiedNameRawContactId));
+ builder.withValue(RawContacts.NAME_VERIFIED, 1);
+ operations.add(builder.build());
+
+ // Apply all aggregation exceptions as one batch
+ try {
+ resolver.applyBatch(ContactsContract.AUTHORITY, operations);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to apply aggregation exception batch", e);
+ Toast.makeText(mContext, R.string.contactSavedErrorToast, Toast.LENGTH_LONG).show();
+ } catch (OperationApplicationException e) {
+ Log.e(TAG, "Failed to apply aggregation exception batch", e);
+ Toast.makeText(mContext, R.string.contactSavedErrorToast, Toast.LENGTH_LONG).show();
+ }
+
+ Toast.makeText(mContext, R.string.contactsJoinedMessage, Toast.LENGTH_LONG).show();
+
+ // We pass back the Uri of the previous Contact (pre-join). While this is not correct,
+ // the provider will be able to later figure out the correct new aggregate
+ if (mListener != null) {
+ mListener.onAggregationChangeFinished(mLookupUri);
+ } else {
+ Log.d(TAG, "Listener is null. Can not call onAggregationChangeFinished");
+ }
+ }
+
+ /**
+ * Construct a {@link AggregationExceptions#TYPE_KEEP_TOGETHER} ContentProviderOperation.
+ */
+ private void buildJoinContactDiff(ArrayList<ContentProviderOperation> operations,
+ long rawContactId1, long rawContactId2) {
+ Builder builder =
+ ContentProviderOperation.newUpdate(AggregationExceptions.CONTENT_URI);
+ builder.withValue(AggregationExceptions.TYPE, AggregationExceptions.TYPE_KEEP_TOGETHER);
+ builder.withValue(AggregationExceptions.RAW_CONTACT_ID1, rawContactId1);
+ builder.withValue(AggregationExceptions.RAW_CONTACT_ID2, rawContactId2);
+ operations.add(builder.build());
+ }
+
+ public static interface Listener {
+ /**
+ * Contact was not found, so somehow close this fragment. This is raised after a contact
+ * is removed via Menu/Delete (unless it was a new contact)
+ */
+ void onContactNotFound();
+
+ /**
+ * Contact was split or joined, so we can close now.
+ * @param newLookupUri The lookup uri of the new contact that should be shown to the user.
+ * The editor tries best to chose the most natural contact here.
+ */
+ void onAggregationChangeFinished(Uri newLookupUri);
+
+ /**
+ * User was presented with an account selection and couldn't decide.
+ */
+ void onAccountSelectorAborted();
+
+ /**
+ * User has tapped Revert, close the fragment now.
+ */
+ void onReverted();
+
+ /**
+ * Set the Title (e.g. of the Activity)
+ */
+ void setTitleTo(int resourceId);
+
+ /**
+ * Contact was saved and the Fragment can now be closed safely.
+ */
+ void onSaveFinished(int resultCode, Intent resultIntent);
+
+ /**
+ * User decided to delete the contact.
+ */
+ void onDeleteRequested(Uri lookupUri);
+ }
+
+ private class EntityDeltaComparator implements Comparator<EntityDelta> {
+ /**
+ * Compare EntityDeltas for sorting the stack of editors.
+ */
+ public int compare(EntityDelta one, EntityDelta two) {
+ // Check direct equality
+ if (one.equals(two)) {
+ return 0;
+ }
+
+ final Sources sources = Sources.getInstance(mContext);
+ String accountType = one.getValues().getAsString(RawContacts.ACCOUNT_TYPE);
+ final ContactsSource oneSource = sources.getInflatedSource(accountType,
+ ContactsSource.LEVEL_SUMMARY);
+ accountType = two.getValues().getAsString(RawContacts.ACCOUNT_TYPE);
+ final ContactsSource twoSource = sources.getInflatedSource(accountType,
+ ContactsSource.LEVEL_SUMMARY);
+
+ // Check read-only
+ if (oneSource.readOnly && !twoSource.readOnly) {
+ return 1;
+ } else if (twoSource.readOnly && !oneSource.readOnly) {
+ return -1;
+ }
+
+ // Check account type
+ boolean skipAccountTypeCheck = false;
+ boolean oneIsGoogle = oneSource instanceof GoogleSource;
+ boolean twoIsGoogle = twoSource instanceof GoogleSource;
+ if (oneIsGoogle && !twoIsGoogle) {
+ return -1;
+ } else if (twoIsGoogle && !oneIsGoogle) {
+ return 1;
+ } else if (oneIsGoogle && twoIsGoogle){
+ skipAccountTypeCheck = true;
+ }
+
+ int value;
+ if (!skipAccountTypeCheck) {
+ if (oneSource.accountType == null) {
+ return 1;
+ }
+ value = oneSource.accountType.compareTo(twoSource.accountType);
+ if (value != 0) {
+ return value;
+ }
+ }
+
+ // Check account name
+ ValuesDelta oneValues = one.getValues();
+ String oneAccount = oneValues.getAsString(RawContacts.ACCOUNT_NAME);
+ if (oneAccount == null) oneAccount = "";
+ ValuesDelta twoValues = two.getValues();
+ String twoAccount = twoValues.getAsString(RawContacts.ACCOUNT_NAME);
+ if (twoAccount == null) twoAccount = "";
+ value = oneAccount.compareTo(twoAccount);
+ if (value != 0) {
+ return value;
+ }
+
+ // Both are in the same account, fall back to contact ID
+ Long oneId = oneValues.getAsLong(RawContacts._ID);
+ Long twoId = twoValues.getAsLong(RawContacts._ID);
+ if (oneId == null) {
+ return -1;
+ } else if (twoId == null) {
+ return 1;
+ }
+
+ return (int)(oneId - twoId);
+ }
+ }
+
+ /**
+ * Class that listens to requests coming from photo editors
+ */
+ private class PhotoListener implements EditorListener, DialogInterface.OnClickListener {
+ private long mRawContactId;
+ private boolean mReadOnly;
+ private PhotoEditorView mEditor;
+
+ public PhotoListener(long rawContactId, boolean readOnly, PhotoEditorView editor) {
+ mRawContactId = rawContactId;
+ mReadOnly = readOnly;
+ mEditor = editor;
+ }
+
+ public void onDeleted(Editor editor) {
+ // Do nothing
+ }
+
+ public void onRequest(int request) {
+ if (!hasValidState()) return;
+
+ if (request == EditorListener.REQUEST_PICK_PHOTO) {
+ if (mEditor.hasSetPhoto()) {
+ // There is an existing photo, offer to remove, replace, or promoto to primary
+ createPhotoDialog().show();
+ } else if (!mReadOnly) {
+ // No photo set and not read-only, try to set the photo
+ doPickPhotoAction(mRawContactId);
+ }
+ }
+ }
+
+ /**
+ * Prepare dialog for picking a new {@link EditType} or entering a
+ * custom label. This dialog is limited to the valid types as determined
+ * by {@link EntityModifier}.
+ */
+ public Dialog createPhotoDialog() {
+ // Wrap our context to inflate list items using correct theme
+ final Context dialogContext = new ContextThemeWrapper(mContext,
+ android.R.style.Theme_Light);
+
+ String[] choices;
+ if (mReadOnly) {
+ choices = new String[1];
+ choices[0] = mContext.getString(R.string.use_photo_as_primary);
+ } else {
+ choices = new String[3];
+ choices[0] = mContext.getString(R.string.use_photo_as_primary);
+ choices[1] = mContext.getString(R.string.removePicture);
+ choices[2] = mContext.getString(R.string.changePicture);
+ }
+ final ListAdapter adapter = new ArrayAdapter<String>(dialogContext,
+ android.R.layout.simple_list_item_1, choices);
+
+ final AlertDialog.Builder builder = new AlertDialog.Builder(dialogContext);
+ builder.setTitle(R.string.attachToContact);
+ builder.setSingleChoiceItems(adapter, -1, this);
+ return builder.create();
+ }
+
+ /**
+ * Called when something in the dialog is clicked
+ */
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.dismiss();
+
+ switch (which) {
+ case 0:
+ // Set the photo as super primary
+ mEditor.setSuperPrimary(true);
+
+ // And set all other photos as not super primary
+ int count = mContent.getChildCount();
+ for (int i = 0; i < count; i++) {
+ View childView = mContent.getChildAt(i);
+ if (childView instanceof BaseContactEditorView) {
+ BaseContactEditorView editor = (BaseContactEditorView) childView;
+ PhotoEditorView photoEditor = editor.getPhotoEditor();
+ if (!photoEditor.equals(mEditor)) {
+ photoEditor.setSuperPrimary(false);
+ }
+ }
+ }
+ break;
+
+ case 1:
+ // Remove the photo
+ mEditor.setPhotoBitmap(null);
+ break;
+
+ case 2:
+ // Pick a new photo for the contact
+ doPickPhotoAction(mRawContactId);
+ break;
+ }
+ }
+ }
+
+ /**
+ * Returns the contact ID for the currently edited contact or 0 if the contact is new.
+ */
+ protected long getContactId() {
+ for (EntityDelta rawContact : mState) {
+ Long contactId = rawContact.getValues().getAsLong(RawContacts.CONTACT_ID);
+ if (contactId != null) {
+ return contactId;
+ }
+ }
+ return 0;
+ }
+
+
+ private void onContactNameChange(int request, final ContactEditorView rawContactEditor,
+ GenericEditorView nameEditor) {
+
+ switch (request) {
+ case EditorListener.EDITOR_FORM_CHANGED:
+ if (nameEditor.areOptionalFieldsVisible()) {
+ switchFromFullNameToStructuredName(nameEditor);
+ } else {
+ switchFromStructuredNameToFullName(nameEditor);
+ }
+ break;
+
+ case EditorListener.FIELD_CHANGED:
+ if (nameEditor.areOptionalFieldsVisible()) {
+ eraseFullName(nameEditor.getValues());
+ } else {
+ eraseStructuredName(nameEditor.getValues());
+ }
+ acquireAggregationSuggestions(rawContactEditor);
+ break;
+ }
+ }
+
+ private void switchFromFullNameToStructuredName(GenericEditorView nameEditor) {
+ ValuesDelta values = nameEditor.getValues();
+
+ String displayName = values.getAsString(StructuredName.DISPLAY_NAME);
+ if (displayName == null) {
+ displayName = "";
+ }
+
+ Uri uri = ContactsContract.AUTHORITY_URI.buildUpon().appendPath("complete_name")
+ .appendQueryParameter(StructuredName.DISPLAY_NAME, displayName).build();
+ Cursor cursor = getActivity().getContentResolver().query(uri, new String[]{
+ StructuredName.PREFIX,
+ StructuredName.GIVEN_NAME,
+ StructuredName.MIDDLE_NAME,
+ StructuredName.FAMILY_NAME,
+ StructuredName.SUFFIX,
+ }, null, null, null);
+
+ try {
+ if (cursor.moveToFirst()) {
+ eraseFullName(values);
+ values.put(StructuredName.PREFIX, cursor.getString(0));
+ values.put(StructuredName.GIVEN_NAME, cursor.getString(1));
+ values.put(StructuredName.MIDDLE_NAME, cursor.getString(2));
+ values.put(StructuredName.FAMILY_NAME, cursor.getString(3));
+ values.put(StructuredName.SUFFIX, cursor.getString(4));
+ }
+ } finally {
+ cursor.close();
+ }
+ }
+
+ private void switchFromStructuredNameToFullName(GenericEditorView nameEditor) {
+ ValuesDelta values = nameEditor.getValues();
+
+ Uri.Builder builder = ContactsContract.AUTHORITY_URI.buildUpon().appendPath(
+ "complete_name");
+ appendQueryParameter(builder, values, StructuredName.PREFIX);
+ appendQueryParameter(builder, values, StructuredName.GIVEN_NAME);
+ appendQueryParameter(builder, values, StructuredName.MIDDLE_NAME);
+ appendQueryParameter(builder, values, StructuredName.FAMILY_NAME);
+ appendQueryParameter(builder, values, StructuredName.SUFFIX);
+ Uri uri = builder.build();
+ Cursor cursor = getActivity().getContentResolver().query(uri, new String[]{
+ StructuredName.DISPLAY_NAME,
+ }, null, null, null);
+
+ try {
+ if (cursor.moveToFirst()) {
+ eraseStructuredName(values);
+ values.put(StructuredName.DISPLAY_NAME, cursor.getString(0));
+ }
+ } finally {
+ cursor.close();
+ }
+ }
+
+ private void eraseFullName(ValuesDelta values) {
+ values.putNull(StructuredName.DISPLAY_NAME);
+ }
+
+ private void eraseStructuredName(ValuesDelta values) {
+ values.putNull(StructuredName.PREFIX);
+ values.putNull(StructuredName.GIVEN_NAME);
+ values.putNull(StructuredName.MIDDLE_NAME);
+ values.putNull(StructuredName.FAMILY_NAME);
+ values.putNull(StructuredName.SUFFIX);
+ }
+
+ private void appendQueryParameter(Uri.Builder builder, ValuesDelta values, String field) {
+ String value = values.getAsString(field);
+ if (!TextUtils.isEmpty(value)) {
+ builder.appendQueryParameter(field, value);
+ }
+ }
+
+ /**
+ * Triggers an asynchronous search for aggregation suggestions.
+ */
+ public void acquireAggregationSuggestions(ContactEditorView rawContactEditor) {
+ if (mAggregationSuggestionEngine == null) {
+ mAggregationSuggestionEngine = new AggregationSuggestionEngine(getActivity());
+ mAggregationSuggestionEngine.setContactId(getContactId());
+ mAggregationSuggestionEngine.setListener(this);
+ mAggregationSuggestionEngine.start();
+ }
+
+ GenericEditorView nameEditor = rawContactEditor.getNameEditor();
+ mAggregationSuggestionEngine.onNameChange(nameEditor.getValues());
+ }
+
+ @Override
+ public void onAggregationSuggestionChange() {
+ // We may have chosen some contact to join with but then changed the contact name.
+ // Let's drop the obsolete decision to join.
+ setSelectedAggregationSuggestion(0, null);
+ updateAggregationSuggestionView();
+ }
+
+ protected void updateAggregationSuggestionView() {
+ ViewStub stub = (ViewStub)mContent.findViewById(R.id.aggregation_suggestion_stub);
+ if (stub != null) {
+ stub.inflate();
+ final View view = mContent.findViewById(R.id.aggregation_suggestion);
+ mContent.postDelayed(new Runnable() {
+
+ @Override
+ public void run() {
+ requestAggregationSuggestionOnScreen(view);
+ }
+ }, AGGREGATION_SUGGESTION_SCROLL_DELAY);
+ }
+
+ View view = mContent.findViewById(R.id.aggregation_suggestion);
+
+ List<Suggestion> suggestions = null;
+ int count = mAggregationSuggestionEngine.getSuggestedContactCount();
+ if (count > 0) {
+ suggestions = mAggregationSuggestionEngine.getSuggestions();
+
+ if (mContactIdForJoin != 0) {
+ Suggestion chosenSuggestion = null;
+ for (Suggestion suggestion : suggestions) {
+ if (suggestion.contactId == mContactIdForJoin) {
+ chosenSuggestion = suggestion;
+ break;
+ }
+ }
+
+ if (chosenSuggestion != null) {
+ suggestions = Lists.newArrayList(chosenSuggestion);
+ } else {
+ // If the contact we wanted to join with is no longer suggested,
+ // forget our decision to join with it.
+ setSelectedAggregationSuggestion(0, null);
+ }
+ }
+
+ count = suggestions.size();
+ }
+
+ if (count == 0) {
+ view.setVisibility(View.GONE);
+ return;
+ }
+
+ TextView title = (TextView) view.findViewById(R.id.aggregation_suggestion_title);
+ if (mContactIdForJoin != 0) {
+ title.setText(R.string.aggregation_suggestion_joined_title);
+ } else {
+ title.setText(getActivity().getResources().getQuantityString(
+ R.plurals.aggregation_suggestion_title, count));
+ }
+
+ LinearLayout itemList = (LinearLayout) view.findViewById(R.id.aggregation_suggestions);
+ itemList.removeAllViews();
+
+ LayoutInflater inflater = getActivity().getLayoutInflater();
+
+ for (Suggestion suggestion : suggestions) {
+ AggregationSuggestionView suggestionView =
+ (AggregationSuggestionView) inflater.inflate(
+ R.layout.aggregation_suggestions_item, null);
+ suggestionView.setLayoutParams(
+ new LinearLayout.LayoutParams(
+ LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
+ suggestionView.setListener(new AggregationSuggestionView.Listener() {
+
+ @Override
+ public void onJoinAction(long contactId, List<Long> rawContactIds) {
+ mState.setJoinWithRawContacts(rawContactIds);
+ // If we are in the edit mode (indicated by a non-zero contact ID),
+ // join the suggested contact, save all changes, and stay in the editor.
+ if (getContactId() != 0) {
+ doSaveAction(SaveMode.RELOAD);
+ } else {
+ mContactIdForJoin = contactId;
+ updateAggregationSuggestionView();
+ }
+ }
+ });
+ suggestionView.bindSuggestion(suggestion, mContactIdForJoin == 0 /* showJoinButton */);
+ itemList.addView(suggestionView);
+ }
+ view.setVisibility(View.VISIBLE);
+ }
+
+ /**
+ * Scrolls the editor if necessary to reveal the aggregation suggestion that is
+ * shown below the name editor. Makes sure that the currently focused field
+ * remains visible.
+ */
+ private void requestAggregationSuggestionOnScreen(final View view) {
+ Rect rect = getRelativeBounds(mContent, view);
+ View focused = mContent.findFocus();
+ if (focused != null) {
+ rect.union(getRelativeBounds(mContent, focused));
+ }
+ mContent.requestRectangleOnScreen(rect);
+ }
+
+ /**
+ * Computes bounds of the supplied view relative to its ascendant.
+ */
+ private Rect getRelativeBounds(View ascendant, View view) {
+ Rect rect = new Rect();
+ rect.set(view.getLeft(), view.getTop(), view.getRight(), view.getBottom());
+
+ View parent = (View) view.getParent();
+ while (parent != ascendant) {
+ rect.offset(parent.getLeft(), parent.getTop());
+ parent = (View) parent.getParent();
+ }
+ return rect;
+ }
+
+ protected void setSelectedAggregationSuggestion(long contactId, List<Long> rawContactIds) {
+ mContactIdForJoin = contactId;
+ mState.setJoinWithRawContacts(rawContactIds);
+ }
+
+ // TODO: There has to be a nicer way than this WeakAsyncTask...? Maybe call a service?
+ /**
+ * Background task for persisting edited contact data, using the changes
+ * defined by a set of {@link EntityDelta}. This task starts
+ * {@link EmptyService} to make sure the background thread can finish
+ * persisting in cases where the system wants to reclaim our process.
+ */
+ public static class PersistTask extends
+ WeakAsyncTask<EntityDeltaList, Void, Integer, ContactEditorFragment> {
+ private static final int PERSIST_TRIES = 3;
+
+ private static final int RESULT_UNCHANGED = 0;
+ private static final int RESULT_SUCCESS = 1;
+ private static final int RESULT_FAILURE = 2;
+
+ private final Context mContext;
+
+ private int mSaveMode;
+ private Uri mContactLookupUri = null;
+
+ public PersistTask(ContactEditorFragment target, int saveMode) {
+ super(target);
+ mSaveMode = saveMode;
+ mContext = target.mContext;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void onPreExecute(ContactEditorFragment target) {
+ // Before starting this task, start an empty service to protect our
+ // process from being reclaimed by the system.
+ mContext.startService(new Intent(mContext, EmptyService.class));
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected Integer doInBackground(ContactEditorFragment target, EntityDeltaList... params) {
+ final ContentResolver resolver = mContext.getContentResolver();
+
+ EntityDeltaList state = params[0];
+
+ // Trim any empty fields, and RawContacts, before persisting
+ final Sources sources = Sources.getInstance(mContext);
+ EntityModifier.trimEmpty(state, sources);
+
+ // Attempt to persist changes
+ int tries = 0;
+ Integer result = RESULT_FAILURE;
+ while (tries++ < PERSIST_TRIES) {
+ try {
+ // Build operations and try applying
+ final ArrayList<ContentProviderOperation> diff = state.buildDiff();
+ ContentProviderResult[] results = null;
+ if (!diff.isEmpty()) {
+ results = resolver.applyBatch(ContactsContract.AUTHORITY, diff);
+ }
+
+ final long rawContactId = getRawContactId(state, diff, results);
+ if (rawContactId != -1) {
+ final Uri rawContactUri = ContentUris.withAppendedId(
+ RawContacts.CONTENT_URI, rawContactId);
+
+ // convert the raw contact URI to a contact URI
+ mContactLookupUri = RawContacts.getContactLookupUri(resolver,
+ rawContactUri);
+ Log.d(TAG, "Looked up RawContact Uri " + rawContactUri +
+ " into ContactLookupUri " + mContactLookupUri);
+ } else {
+ Log.w(TAG, "Could not determine RawContact ID after save");
+ }
+ result = (diff.size() > 0) ? RESULT_SUCCESS : RESULT_UNCHANGED;
+ break;
+
+ } catch (RemoteException e) {
+ // Something went wrong, bail without success
+ Log.e(TAG, "Problem persisting user edits", e);
+ break;
+
+ } catch (OperationApplicationException e) {
+ // Version consistency failed, re-parent change and try again
+ Log.w(TAG, "Version consistency failed, re-parenting: " + e.toString());
+ final EntityDeltaList newState = EntityDeltaList.fromQuery(resolver,
+ target.mQuerySelection, null, null);
+ state = EntityDeltaList.mergeAfter(newState, state);
+ }
+ }
+
+ return result;
+ }
+
+ private long getRawContactId(EntityDeltaList state,
+ final ArrayList<ContentProviderOperation> diff,
+ final ContentProviderResult[] results) {
+ long rawContactId = state.findRawContactId();
+ if (rawContactId != -1) {
+ return rawContactId;
+ }
+
+
+ // we gotta do some searching for the id
+ final int diffSize = diff.size();
+ for (int i = 0; i < diffSize; i++) {
+ ContentProviderOperation operation = diff.get(i);
+ if (operation.getType() == ContentProviderOperation.TYPE_INSERT
+ && operation.getUri().getEncodedPath().contains(
+ RawContacts.CONTENT_URI.getEncodedPath())) {
+ return ContentUris.parseId(results[i].uri);
+ }
+ }
+ return -1;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void onPostExecute(ContactEditorFragment target, Integer result) {
+ Log.d(TAG, "onPostExecute(something," + result + "). mSaveMode=" + mSaveMode);
+ if (result == RESULT_SUCCESS && mSaveMode != SaveMode.JOIN) {
+ Toast.makeText(mContext, R.string.contactSavedToast, Toast.LENGTH_SHORT).show();
+ } else if (result == RESULT_FAILURE) {
+ Toast.makeText(mContext, R.string.contactSavedErrorToast, Toast.LENGTH_LONG).show();
+ }
+
+ // Stop the service that was protecting us
+ mContext.stopService(new Intent(mContext, EmptyService.class));
+
+ target.onSaveCompleted(result != RESULT_FAILURE, mSaveMode, mContactLookupUri);
+ }
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ outState.putParcelable(KEY_URI, mLookupUri);
+ outState.putString(KEY_ACTION, mAction);
+
+ if (hasValidState()) {
+ // Store entities with modifications
+ outState.putParcelable(KEY_EDIT_STATE, mState);
+ }
+
+ outState.putLong(KEY_RAW_CONTACT_ID_REQUESTING_PHOTO, mRawContactIdRequestingPhoto);
+ outState.putParcelable(KEY_VIEW_ID_GENERATOR, mViewIdGenerator);
+ if (mCurrentPhotoFile != null) {
+ outState.putString(KEY_CURRENT_PHOTO_FILE, mCurrentPhotoFile.toString());
+ }
+ outState.putString(KEY_QUERY_SELECTION, mQuerySelection);
+ outState.putLong(KEY_CONTACT_ID_FOR_JOIN, mContactIdForJoin);
+ super.onSaveInstanceState(outState);
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ // Ignore failed requests
+ if (resultCode != Activity.RESULT_OK) return;
+ switch (requestCode) {
+ case REQUEST_CODE_PHOTO_PICKED_WITH_DATA: {
+ BaseContactEditorView requestingEditor = null;
+ for (int i = 0; i < mContent.getChildCount(); i++) {
+ View childView = mContent.getChildAt(i);
+ if (childView instanceof BaseContactEditorView) {
+ BaseContactEditorView editor = (BaseContactEditorView) childView;
+ if (editor.getRawContactId() == mRawContactIdRequestingPhoto) {
+ requestingEditor = editor;
+ break;
+ }
+ }
+ }
+
+ if (requestingEditor != null) {
+ final Bitmap photo = data.getParcelableExtra("data");
+ requestingEditor.setPhotoBitmap(photo);
+ mRawContactIdRequestingPhoto = -1;
+ } else {
+ // The contact that requested the photo is no longer present.
+ // TODO: Show error message
+ }
+
+ break;
+ }
+
+ case REQUEST_CODE_CAMERA_WITH_DATA: {
+ doCropPhoto(mCurrentPhotoFile);
+ break;
+ }
+ case REQUEST_CODE_JOIN: {
+ if (data != null) {
+ final long contactId = ContentUris.parseId(data.getData());
+ joinAggregate(contactId);
+ }
+ }
+ }
+ }
+
+ public Uri getLookupUri() {
+ return mLookupUri;
+ }
+
+ /**
+ * The listener for the data loader
+ */
+ private final LoaderManager.LoaderCallbacks<ContactLoader.Result> mDataLoaderListener =
+ new LoaderCallbacks<ContactLoader.Result>() {
+ @Override
+ public Loader<ContactLoader.Result> onCreateLoader(int id, Bundle args) {
+ mLoaderStartTime = SystemClock.elapsedRealtime();
+ return new ContactLoader(mContext, mLookupUri);
+ }
+
+ @Override
+ public void onLoadFinished(Loader<ContactLoader.Result> loader, ContactLoader.Result data) {
+ final long loaderCurrentTime = SystemClock.elapsedRealtime();
+ Log.v(TAG, "Time needed for loading: " + (loaderCurrentTime-mLoaderStartTime));
+ if (data == ContactLoader.Result.NOT_FOUND) {
+ // Item has been deleted
+ Log.i(TAG, "No contact found. Closing activity");
+ if (mListener != null) mListener.onContactNotFound();
+ return;
+ }
+
+ final long setDataStartTime = SystemClock.elapsedRealtime();
+ setData(data);
+ final long setDataEndTime = SystemClock.elapsedRealtime();
+ Log.v(TAG, "Time needed for setting UI: " + (setDataEndTime-setDataStartTime));
+ }
+ };
+
+ @Override
+ public void onSplitContactConfirmed() {
+ mState.markRawContactsForSplitting();
+ doSaveAction(SaveMode.SPLIT);
+ }
+
+ /**
+ * Launches Camera to take a picture and store it in a file.
+ */
+ @Override
+ public void onTakePhotoChosen() {
+ try {
+ // Launch camera to take photo for selected contact
+ PHOTO_DIR.mkdirs();
+ mCurrentPhotoFile = new File(PHOTO_DIR, getPhotoFileName());
+ final Intent intent = getTakePickIntent(mCurrentPhotoFile);
+
+ startActivityForResult(intent, REQUEST_CODE_CAMERA_WITH_DATA);
+ } catch (ActivityNotFoundException e) {
+ Toast.makeText(mContext, R.string.photoPickerNotFoundText, Toast.LENGTH_LONG).show();
+ }
+ }
+
+ /**
+ * Launches Gallery to pick a photo.
+ */
+ @Override
+ public void onPickFromGalleryChosen() {
+ try {
+ // Launch picker to choose photo for selected contact
+ final Intent intent = getPhotoPickIntent();
+ startActivityForResult(intent, REQUEST_CODE_PHOTO_PICKED_WITH_DATA);
+ } catch (ActivityNotFoundException e) {
+ Toast.makeText(mContext, R.string.photoPickerNotFoundText, Toast.LENGTH_LONG).show();
+ }
+ }
+
+ /**
+ * Account was chosen in the selector. Create a RawContact for this account now
+ */
+ @Override
+ public void onAccountChosen(Account account) {
+ createContact(account, false);
+ }
+
+ /**
+ * The account selector has been aborted. If we are in "New" mode, we have to close now
+ */
+ @Override
+ public void onAccountSelectorCancelled() {
+ // If nothing remains, close activity
+ if (!hasValidState()) {
+ if (mListener != null) mListener.onAccountSelectorAborted();
+ }
+ }
+}
diff --git a/src/com/android/contacts/views/editor/PickPhotoDialogFragment.java b/src/com/android/contacts/views/editor/PickPhotoDialogFragment.java
new file mode 100644
index 0000000..d93497d
--- /dev/null
+++ b/src/com/android/contacts/views/editor/PickPhotoDialogFragment.java
@@ -0,0 +1,81 @@
+/*
+ * 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.views.editor;
+
+import com.android.contacts.R;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.view.ContextThemeWrapper;
+import android.widget.ArrayAdapter;
+import android.widget.ListAdapter;
+
+/**
+ * Shows a dialog asking the user whether to take a photo or pick a photo from the gallery.
+ * The result is passed back to the Fragment with the Id that is passed in the constructor
+ * (or the Activity if -1 is passed).
+ * The target must implement {@link PickPhotoDialogFragment.Listener}.
+ * Does not perform any action by itself.
+ */
+public class PickPhotoDialogFragment extends TargetedDialogFragment {
+ public static final String TAG = "PickPhotoDialogFragment";
+
+ public PickPhotoDialogFragment() {
+ }
+
+ public PickPhotoDialogFragment(int targetFragmentId) {
+ super(targetFragmentId);
+ }
+
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ // Wrap our context to inflate list items using the light theme
+ final Context dialogContext = new ContextThemeWrapper(getActivity(),
+ android.R.style.Theme_Light);
+
+ String[] choices = new String[2];
+ choices[0] = getActivity().getString(R.string.take_photo);
+ choices[1] = getActivity().getString(R.string.pick_photo);
+ final ListAdapter adapter = new ArrayAdapter<String>(dialogContext,
+ android.R.layout.simple_list_item_1, choices);
+
+ final AlertDialog.Builder builder = new AlertDialog.Builder(dialogContext);
+ builder.setTitle(R.string.attachToContact);
+ builder.setSingleChoiceItems(adapter, -1, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.dismiss();
+ final Listener targetListener = (Listener) getTarget();
+ switch(which) {
+ case 0:
+ targetListener.onTakePhotoChosen();
+ break;
+ case 1:
+ targetListener.onPickFromGalleryChosen();
+ break;
+ }
+ }
+ });
+ return builder.create();
+ }
+
+ public interface Listener {
+ void onTakePhotoChosen();
+ void onPickFromGalleryChosen();
+ }
+}
diff --git a/src/com/android/contacts/views/editor/SelectAccountDialogFragment.java b/src/com/android/contacts/views/editor/SelectAccountDialogFragment.java
new file mode 100644
index 0000000..66370cf
--- /dev/null
+++ b/src/com/android/contacts/views/editor/SelectAccountDialogFragment.java
@@ -0,0 +1,117 @@
+/*
+ * 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.views.editor;
+
+import com.android.contacts.R;
+import com.android.contacts.model.ContactsSource;
+import com.android.contacts.model.Sources;
+
+import android.accounts.Account;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.view.ContextThemeWrapper;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+
+/**
+ * Shows a dialog asking the user which account to chose.
+ * The result is passed back to the Fragment with the Id that is passed in the constructor
+ * (or the Activity if -1 is passed).
+ * The target must implement {@link SelectAccountDialogFragment.Listener}.
+ * Does not perform any action by itself.
+ */
+public class SelectAccountDialogFragment extends TargetedDialogFragment {
+ public static final String TAG = "PickPhotoDialogFragment";
+
+ public SelectAccountDialogFragment() {
+ }
+
+ public SelectAccountDialogFragment(int targetFragmentId) {
+ super(targetFragmentId);
+ }
+
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ // Wrap our context to inflate list items using correct theme
+ final Context dialogContext = new ContextThemeWrapper(getActivity(),
+ android.R.style.Theme_Light);
+ final LayoutInflater dialogInflater =
+ (LayoutInflater)dialogContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+
+ final Sources sources = Sources.getInstance(getActivity());
+ final ArrayList<Account> accounts = Sources.getInstance(getActivity()).getAccounts(true);
+
+ final ArrayAdapter<Account> accountAdapter = new ArrayAdapter<Account>(getActivity(),
+ android.R.layout.simple_list_item_2, accounts) {
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ if (convertView == null) {
+ convertView = dialogInflater.inflate(android.R.layout.simple_list_item_2,
+ parent, false);
+ }
+
+ // TODO: show icon along with title
+ final TextView text1 = (TextView)convertView.findViewById(android.R.id.text1);
+ final TextView text2 = (TextView)convertView.findViewById(android.R.id.text2);
+
+ final Account account = this.getItem(position);
+ final ContactsSource source = sources.getInflatedSource(account.type,
+ ContactsSource.LEVEL_SUMMARY);
+
+ text1.setText(account.name);
+ text2.setText(source.getDisplayLabel(getContext()));
+
+ return convertView;
+ }
+ };
+
+ final Listener targetListener = (Listener) getTarget();
+ final DialogInterface.OnClickListener clickListener =
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.dismiss();
+
+ targetListener.onAccountChosen(accountAdapter.getItem(which));
+ }
+ };
+
+ final DialogInterface.OnCancelListener cancelListener =
+ new DialogInterface.OnCancelListener() {
+ public void onCancel(DialogInterface dialog) {
+ targetListener.onAccountSelectorCancelled();
+ }
+ };
+
+ final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+ builder.setTitle(R.string.dialog_new_contact_account);
+ builder.setSingleChoiceItems(accountAdapter, 0, clickListener);
+ builder.setOnCancelListener(cancelListener);
+ return builder.create();
+ }
+
+ public interface Listener {
+ void onAccountChosen(Account account);
+ void onAccountSelectorCancelled();
+ }
+}
diff --git a/src/com/android/contacts/views/editor/SplitContactConfirmationDialogFragment.java b/src/com/android/contacts/views/editor/SplitContactConfirmationDialogFragment.java
new file mode 100644
index 0000000..4089abf
--- /dev/null
+++ b/src/com/android/contacts/views/editor/SplitContactConfirmationDialogFragment.java
@@ -0,0 +1,62 @@
+/*
+ * 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.views.editor;
+
+import com.android.contacts.R;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.os.Bundle;
+
+/**
+ * Shows a dialog asking the user whether to split the contact. The result is passed back
+ * to the Fragment with the Id that is passed in the constructor (or the Activity if -1 is passed).
+ * The target must implement {@link SplitContactConfirmationDialogFragment.Listener}
+ * Does not split the contact itself.
+ */
+public class SplitContactConfirmationDialogFragment extends TargetedDialogFragment {
+ public static final String TAG = "SplitContactConfirmationDialog";
+
+ public SplitContactConfirmationDialogFragment() {
+ }
+
+ public SplitContactConfirmationDialogFragment(int targetFragmentId) {
+ super(targetFragmentId);
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+ builder.setTitle(R.string.splitConfirmation_title);
+ builder.setIcon(android.R.drawable.ic_dialog_alert);
+ builder.setMessage(R.string.splitConfirmation);
+ builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ final Listener targetListener = (Listener) getTarget();
+ targetListener.onSplitContactConfirmed();
+ }
+ });
+ builder.setNegativeButton(android.R.string.cancel, null);
+ builder.setCancelable(false);
+ return builder.create();
+ }
+
+ public interface Listener {
+ void onSplitContactConfirmed();
+ }
+}
diff --git a/src/com/android/contacts/views/editor/TargetedDialogFragment.java b/src/com/android/contacts/views/editor/TargetedDialogFragment.java
new file mode 100644
index 0000000..ee0047d
--- /dev/null
+++ b/src/com/android/contacts/views/editor/TargetedDialogFragment.java
@@ -0,0 +1,57 @@
+/*
+ * 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.views.editor;
+
+import android.app.DialogFragment;
+import android.os.Bundle;
+
+/**
+ * A DialogFragment that can send its result to a target (which is either an Activity or a Fragment)
+ * TODO: This should be removed once there is Framework support for handling of Targets.
+ */
+public class TargetedDialogFragment extends DialogFragment {
+ private static final String TARGET_FRAGMENT_ID = "TARGET_FRAGMENT_ID";
+
+ private int mTargetFragmentId;
+
+ public TargetedDialogFragment() {
+ mTargetFragmentId = -1;
+ }
+
+ public TargetedDialogFragment(int targetFragmentId) {
+ mTargetFragmentId = targetFragmentId;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (savedInstanceState != null) {
+ mTargetFragmentId = savedInstanceState.getInt(TARGET_FRAGMENT_ID);
+ }
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putInt(TARGET_FRAGMENT_ID, mTargetFragmentId);
+ }
+
+ protected Object getTarget() {
+ return mTargetFragmentId == -1 ? getActivity()
+ : getActivity().getFragmentManager().findFragmentById(mTargetFragmentId);
+ }
+}
diff --git a/src/com/android/contacts/widget/CompositeListAdapter.java b/src/com/android/contacts/widget/CompositeListAdapter.java
new file mode 100644
index 0000000..bd0c8d6
--- /dev/null
+++ b/src/com/android/contacts/widget/CompositeListAdapter.java
@@ -0,0 +1,222 @@
+/*
+ * 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.widget;
+
+import android.database.DataSetObserver;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ListAdapter;
+
+/**
+ * A general purpose adapter that is composed of multiple sub-adapters. It just
+ * appends them in the order they are added. It listens to changes from all
+ * sub-adapters and propagates them to its own listeners.
+ */
+public class CompositeListAdapter extends BaseAdapter {
+
+ private static final int INITIAL_CAPACITY = 2;
+
+ private ListAdapter[] mAdapters;
+ private int[] mCounts;
+ private int[] mViewTypeCounts;
+ private int mSize = 0;
+ private int mCount = 0;
+ private int mViewTypeCount = 0;
+ private boolean mAllItemsEnabled = true;
+ private boolean mCacheValid = true;
+
+ private DataSetObserver mDataSetObserver = new DataSetObserver() {
+
+ @Override
+ public void onChanged() {
+ invalidate();
+ notifyDataChanged();
+ }
+
+ @Override
+ public void onInvalidated() {
+ invalidate();
+ notifyDataChanged();
+ }
+ };
+
+ public CompositeListAdapter() {
+ this(INITIAL_CAPACITY);
+ }
+
+ public CompositeListAdapter(int initialCapacity) {
+ mAdapters = new ListAdapter[INITIAL_CAPACITY];
+ mCounts = new int[INITIAL_CAPACITY];
+ mViewTypeCounts = new int[INITIAL_CAPACITY];
+ }
+
+ public void addAdapter(ListAdapter adapter) {
+ if (mSize >= mAdapters.length) {
+ int newCapacity = mSize + 2;
+ ListAdapter[] newAdapters = new ListAdapter[newCapacity];
+ System.arraycopy(mAdapters, 0, newAdapters, 0, mSize);
+ mAdapters = newAdapters;
+
+ int[] newCounts = new int[newCapacity];
+ System.arraycopy(mCounts, 0, newCounts, 0, mSize);
+ mCounts = newCounts;
+
+ int[] newViewTypeCounts = new int[newCapacity];
+ System.arraycopy(mViewTypeCounts, 0, newViewTypeCounts, 0, mSize);
+ mViewTypeCounts = newViewTypeCounts;
+ }
+
+ adapter.registerDataSetObserver(mDataSetObserver);
+
+ int count = adapter.getCount();
+ int viewTypeCount = adapter.getViewTypeCount();
+
+ mAdapters[mSize] = adapter;
+ mCounts[mSize] = count;
+ mCount += count;
+ mAllItemsEnabled &= adapter.areAllItemsEnabled();
+ mViewTypeCounts[mSize] = viewTypeCount;
+ mViewTypeCount += viewTypeCount;
+ mSize++;
+
+ notifyDataChanged();
+ }
+
+ protected void notifyDataChanged() {
+ if (getCount() > 0) {
+ notifyDataSetChanged();
+ } else {
+ notifyDataSetInvalidated();
+ }
+ }
+
+ protected void invalidate() {
+ mCacheValid = false;
+ }
+
+ protected void ensureCacheValid() {
+ if (mCacheValid) {
+ return;
+ }
+
+ mCount = 0;
+ mAllItemsEnabled = true;
+ mViewTypeCount = 0;
+ for (int i = 0; i < mSize; i++) {
+ int count = mAdapters[i].getCount();
+ int viewTypeCount = mAdapters[i].getViewTypeCount();
+ mCounts[i] = count;
+ mCount += count;
+ mAllItemsEnabled &= mAdapters[i].areAllItemsEnabled();
+ mViewTypeCount += viewTypeCount;
+ }
+
+ mCacheValid = true;
+ }
+
+ public int getCount() {
+ ensureCacheValid();
+ return mCount;
+ }
+
+ public Object getItem(int position) {
+ ensureCacheValid();
+ int start = 0;
+ for (int i = 0; i < mCounts.length; i++) {
+ int end = start + mCounts[i];
+ if (position >= start && position < end) {
+ return mAdapters[i].getItem(position - start);
+ }
+ start = end;
+ }
+
+ throw new ArrayIndexOutOfBoundsException(position);
+ }
+
+ public long getItemId(int position) {
+ ensureCacheValid();
+ int start = 0;
+ for (int i = 0; i < mCounts.length; i++) {
+ int end = start + mCounts[i];
+ if (position >= start && position < end) {
+ return mAdapters[i].getItemId(position - start);
+ }
+ start = end;
+ }
+
+ throw new ArrayIndexOutOfBoundsException(position);
+ }
+
+ @Override
+ public int getViewTypeCount() {
+ ensureCacheValid();
+ return mViewTypeCount;
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ ensureCacheValid();
+ int start = 0;
+ int viewTypeOffset = 0;
+ for (int i = 0; i < mCounts.length; i++) {
+ int end = start + mCounts[i];
+ if (position >= start && position < end) {
+ return viewTypeOffset + mAdapters[i].getItemViewType(position - start);
+ }
+ viewTypeOffset += mViewTypeCounts[i];
+ start = end;
+ }
+
+ throw new ArrayIndexOutOfBoundsException(position);
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ ensureCacheValid();
+ int start = 0;
+ for (int i = 0; i < mCounts.length; i++) {
+ int end = start + mCounts[i];
+ if (position >= start && position < end) {
+ return mAdapters[i].getView(position - start, convertView, parent);
+ }
+ start = end;
+ }
+
+ throw new ArrayIndexOutOfBoundsException(position);
+ }
+
+ @Override
+ public boolean areAllItemsEnabled() {
+ ensureCacheValid();
+ return mAllItemsEnabled;
+ }
+
+ @Override
+ public boolean isEnabled(int position) {
+ ensureCacheValid();
+ int start = 0;
+ for (int i = 0; i < mCounts.length; i++) {
+ int end = start + mCounts[i];
+ if (position >= start && position < end) {
+ return mAdapters[i].areAllItemsEnabled()
+ || mAdapters[i].isEnabled(position - start);
+ }
+ start = end;
+ }
+
+ throw new ArrayIndexOutOfBoundsException(position);
+ }
+}
diff --git a/src/com/android/contacts/widget/ContextMenuAdapter.java b/src/com/android/contacts/widget/ContextMenuAdapter.java
new file mode 100644
index 0000000..660274a
--- /dev/null
+++ b/src/com/android/contacts/widget/ContextMenuAdapter.java
@@ -0,0 +1,30 @@
+/*
+ * 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.widget;
+
+import android.view.MenuItem;
+import android.view.View;
+
+/**
+ * An adapter for the contextual menu.
+ */
+public interface ContextMenuAdapter extends View.OnCreateContextMenuListener {
+
+ /**
+ * See {@link android.app.Activity#onContextItemSelected}.
+ */
+ boolean onContextItemSelected(MenuItem item);
+}
diff --git a/src/com/android/contacts/widget/IndexerListAdapter.java b/src/com/android/contacts/widget/IndexerListAdapter.java
new file mode 100644
index 0000000..b26c2dc
--- /dev/null
+++ b/src/com/android/contacts/widget/IndexerListAdapter.java
@@ -0,0 +1,226 @@
+/*
+ * 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.widget;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ListView;
+import android.widget.SectionIndexer;
+import android.widget.TextView;
+
+/**
+ * A list adapter that supports section indexer and a pinned header.
+ */
+public abstract class IndexerListAdapter extends PinnedHeaderListAdapter implements SectionIndexer {
+
+ private final int mSectionHeaderTextViewId;
+ private final int mSectionHeaderLayoutResId;
+
+ protected Context mContext;
+ private SectionIndexer mIndexer;
+ private int mIndexedPartition = 0;
+ private boolean mSectionHeaderDisplayEnabled;
+ private View mHeader;
+ private TextView mTitleView;
+
+ /**
+ * An item view is displayed differently depending on whether it is placed
+ * at the beginning, middle or end of a section. It also needs to know the
+ * section header when it is at the beginning of a section. This object
+ * captures all this configuration.
+ */
+ public static final class Placement {
+ private int position = ListView.INVALID_POSITION;
+ public boolean firstInSection;
+ public boolean lastInSection;
+ public String sectionHeader;
+
+ public void invalidate() {
+ position = ListView.INVALID_POSITION;
+ }
+ }
+
+ private Placement mPlacementCache = new Placement();
+
+ /**
+ * Constructor.
+ *
+ * @param context
+ * @param sectionHeaderLayoutResourceId section header layout resource ID
+ * @param sectionHeaderTextViewId section header text view ID
+ */
+ public IndexerListAdapter(Context context, int sectionHeaderLayoutResourceId,
+ int sectionHeaderTextViewId) {
+ super(context);
+ mContext = context;
+ mSectionHeaderLayoutResId = sectionHeaderLayoutResourceId;
+ mSectionHeaderTextViewId = sectionHeaderTextViewId;
+ }
+
+ public boolean isSectionHeaderDisplayEnabled() {
+ return mSectionHeaderDisplayEnabled;
+ }
+
+ public void setSectionHeaderDisplayEnabled(boolean flag) {
+ this.mSectionHeaderDisplayEnabled = flag;
+ }
+
+ public int getIndexedPartition() {
+ return mIndexedPartition;
+ }
+
+ public void setIndexedPartition(int partition) {
+ this.mIndexedPartition = partition;
+ }
+
+ public SectionIndexer getIndexer() {
+ return mIndexer;
+ }
+
+ public void setIndexer(SectionIndexer indexer) {
+ mIndexer = indexer;
+ mPlacementCache.invalidate();
+ }
+
+ public Object[] getSections() {
+ if (mIndexer == null) {
+ return new String[] { " " };
+ } else {
+ return mIndexer.getSections();
+ }
+ }
+
+ /**
+ * @return relative position of the section in the indexed partition
+ */
+ public int getPositionForSection(int sectionIndex) {
+ if (mIndexer == null) {
+ return -1;
+ }
+
+ return mIndexer.getPositionForSection(sectionIndex);
+ }
+
+ /**
+ * @param position relative position in the indexed partition
+ */
+ public int getSectionForPosition(int position) {
+ if (mIndexer == null) {
+ return -1;
+ }
+
+ return mIndexer.getSectionForPosition(position);
+ }
+
+ @Override
+ public int getPinnedHeaderCount() {
+ if (isSectionHeaderDisplayEnabled()) {
+ return super.getPinnedHeaderCount() + 1;
+ } else {
+ return super.getPinnedHeaderCount();
+ }
+ }
+
+ @Override
+ public View getPinnedHeaderView(int viewIndex, View convertView, ViewGroup parent) {
+ if (isSectionHeaderDisplayEnabled() && viewIndex == getPinnedHeaderCount() - 1) {
+ if (mHeader == null) {
+ mHeader = LayoutInflater.from(mContext).
+ inflate(mSectionHeaderLayoutResId, parent, false);
+ mTitleView = (TextView)mHeader.findViewById(mSectionHeaderTextViewId);
+ }
+ return mHeader;
+ } else {
+ return super.getPinnedHeaderView(viewIndex, convertView, parent);
+ }
+ }
+
+ @Override
+ public void configurePinnedHeaders(PinnedHeaderListView listView) {
+ super.configurePinnedHeaders(listView);
+
+ if (!isSectionHeaderDisplayEnabled()) {
+ return;
+ }
+
+ int index = getPinnedHeaderCount() - 1;
+ if (mIndexer == null || getCount() == 0) {
+ listView.setHeaderInvisible(index, false);
+ } else {
+ int listPosition = listView.getPositionAt(listView.getTotalTopPinnedHeaderHeight());
+ int position = listPosition - listView.getHeaderViewsCount();
+
+ int section = -1;
+ int partition = getPartitionForPosition(position);
+ if (partition == mIndexedPartition) {
+ int offset = getOffsetInPartition(position);
+ if (offset != -1) {
+ section = getSectionForPosition(offset);
+ }
+ }
+
+ if (section == -1) {
+ listView.setHeaderInvisible(index, false);
+ } else {
+ String title = (String)mIndexer.getSections()[section];
+ mTitleView.setText(title);
+
+ // Compute the item position where the current partition begins
+ int partitionStart = getPositionForPartition(mIndexedPartition);
+ if (hasHeader(mIndexedPartition)) {
+ partitionStart++;
+ }
+
+ // Compute the item position where the next section begins
+ int nextSectionPosition = partitionStart + getPositionForSection(section + 1);
+ boolean isLastInSection = position == nextSectionPosition - 1;
+ listView.setFadingHeader(index, listPosition, isLastInSection);
+ }
+ }
+ }
+
+ /**
+ * Computes the item's placement within its section and populates the {@code placement}
+ * object accordingly. Please note that the returned object is volatile and should be
+ * copied if the result needs to be used later.
+ */
+ public Placement getItemPlacementInSection(int position) {
+ if (mPlacementCache.position == position) {
+ return mPlacementCache;
+ }
+
+ mPlacementCache.position = position;
+ if (isSectionHeaderDisplayEnabled()) {
+ int section = getSectionForPosition(position);
+ if (section != -1 && getPositionForSection(section) == position) {
+ mPlacementCache.firstInSection = true;
+ mPlacementCache.sectionHeader = (String)getSections()[section];
+ } else {
+ mPlacementCache.firstInSection = false;
+ mPlacementCache.sectionHeader = null;
+ }
+
+ mPlacementCache.lastInSection = (getPositionForSection(section + 1) - 1 == position);
+ } else {
+ mPlacementCache.firstInSection = false;
+ mPlacementCache.lastInSection = false;
+ mPlacementCache.sectionHeader = null;
+ }
+ return mPlacementCache;
+ }
+}
diff --git a/src/com/android/contacts/widget/PinnedHeaderListAdapter.java b/src/com/android/contacts/widget/PinnedHeaderListAdapter.java
new file mode 100644
index 0000000..a4d375e
--- /dev/null
+++ b/src/com/android/contacts/widget/PinnedHeaderListAdapter.java
@@ -0,0 +1,167 @@
+/*
+ * 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.widget;
+
+import com.android.common.widget.CompositeCursorAdapter;
+
+import android.content.Context;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * A subclass of {@link CompositeCursorAdapter} that manages pinned partition headers.
+ */
+public abstract class PinnedHeaderListAdapter extends CompositeCursorAdapter
+ implements PinnedHeaderListView.PinnedHeaderAdapter {
+
+ public static final int PARTITION_HEADER_TYPE = 0;
+
+ private boolean mPinnedPartitionHeadersEnabled;
+ private boolean mHeaderVisibility[];
+
+ public PinnedHeaderListAdapter(Context context) {
+ super(context);
+ }
+
+ public PinnedHeaderListAdapter(Context context, int initialCapacity) {
+ super(context, initialCapacity);
+ }
+
+ public boolean getPinnedPartitionHeadersEnabled() {
+ return mPinnedPartitionHeadersEnabled;
+ }
+
+ public void setPinnedPartitionHeadersEnabled(boolean flag) {
+ this.mPinnedPartitionHeadersEnabled = flag;
+ }
+
+ public int getPinnedHeaderCount() {
+ if (mPinnedPartitionHeadersEnabled) {
+ return getPartitionCount();
+ } else {
+ return 0;
+ }
+ }
+
+ protected boolean isPinnedPartitionHeaderVisible(int partition) {
+ return mPinnedPartitionHeadersEnabled && hasHeader(partition)
+ && !isPartitionEmpty(partition);
+ }
+
+ /**
+ * The default implementation creates the same type of view as a normal
+ * partition header.
+ */
+ public View getPinnedHeaderView(int partition, View convertView, ViewGroup parent) {
+ if (hasHeader(partition)) {
+ View view = null;
+ if (convertView != null) {
+ Integer headerType = (Integer)convertView.getTag();
+ if (headerType != null && headerType == PARTITION_HEADER_TYPE) {
+ view = convertView;
+ }
+ }
+ if (view == null) {
+ view = newHeaderView(getContext(), partition, null, parent);
+ view.setTag(PARTITION_HEADER_TYPE);
+ view.setFocusable(false);
+ view.setEnabled(false);
+ }
+ bindHeaderView(view, partition, getCursor(partition));
+ return view;
+ } else {
+ return null;
+ }
+ }
+
+ public void configurePinnedHeaders(PinnedHeaderListView listView) {
+ if (!mPinnedPartitionHeadersEnabled) {
+ return;
+ }
+
+ int size = getPartitionCount();
+
+ // Cache visibility bits, because we will need them several times later on
+ if (mHeaderVisibility == null || mHeaderVisibility.length != size) {
+ mHeaderVisibility = new boolean[size];
+ }
+ for (int i = 0; i < size; i++) {
+ boolean visible = isPinnedPartitionHeaderVisible(i);
+ mHeaderVisibility[i] = visible;
+ if (!visible) {
+ listView.setHeaderInvisible(i, true);
+ }
+ }
+
+ int headerViewsCount = listView.getHeaderViewsCount();
+
+ // Starting at the top, find and pin headers for partitions preceding the visible one(s)
+ int maxTopHeader = -1;
+ int topHeaderHeight = 0;
+ for (int i = 0; i < size; i++) {
+ if (mHeaderVisibility[i]) {
+ int position = listView.getPositionAt(topHeaderHeight) - headerViewsCount;
+ int partition = getPartitionForPosition(position);
+ if (i > partition) {
+ break;
+ }
+
+ listView.setHeaderPinnedAtTop(i, topHeaderHeight, false);
+ topHeaderHeight += listView.getPinnedHeaderHeight(i);
+ maxTopHeader = i;
+ }
+ }
+
+ // Starting at the bottom, find and pin headers for partitions following the visible one(s)
+ int maxBottomHeader = size;
+ int bottomHeaderHeight = 0;
+ int listHeight = listView.getHeight();
+ for (int i = size; --i > maxTopHeader;) {
+ if (mHeaderVisibility[i]) {
+ int position = listView.getPositionAt(listHeight - bottomHeaderHeight)
+ - headerViewsCount;
+ if (position < 0) {
+ break;
+ }
+
+ int partition = getPartitionForPosition(position - 1);
+ if (partition == -1 || i <= partition) {
+ break;
+ }
+
+ int height = listView.getPinnedHeaderHeight(i);
+ bottomHeaderHeight += height;
+ // Animate the header only if the partition is completely invisible below
+ // the bottom of the view
+ int firstPositionForPartition = getPositionForPartition(i);
+ boolean animate = position < firstPositionForPartition;
+ listView.setHeaderPinnedAtBottom(i, listHeight - bottomHeaderHeight, animate);
+ maxBottomHeader = i;
+ }
+ }
+
+ // Headers in between the top-pinned and bottom-pinned should be hidden
+ for (int i = maxTopHeader + 1; i < maxBottomHeader; i++) {
+ if (mHeaderVisibility[i]) {
+ listView.setHeaderInvisible(i, isPartitionEmpty(i));
+ }
+ }
+ }
+
+ public int getScrollPositionForHeader(int viewIndex) {
+ return getPositionForPartition(viewIndex);
+ }
+}
diff --git a/src/com/android/contacts/widget/PinnedHeaderListDemoActivity.java b/src/com/android/contacts/widget/PinnedHeaderListDemoActivity.java
new file mode 100644
index 0000000..b553e3f
--- /dev/null
+++ b/src/com/android/contacts/widget/PinnedHeaderListDemoActivity.java
@@ -0,0 +1,142 @@
+/*
+ * 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.widget;
+
+import com.android.contacts.R;
+
+import android.app.ListActivity;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.os.Bundle;
+import android.os.Handler;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+/**
+ * An activity that demonstrates various use cases for the {@link PinnedHeaderListView}.
+ * If we decide to move PinnedHeaderListView to the framework, this class could go
+ * to API demos.
+ */
+public class PinnedHeaderListDemoActivity extends ListActivity {
+
+ public final static class TestPinnedHeaderListAdapter extends PinnedHeaderListAdapter {
+
+ public TestPinnedHeaderListAdapter(Context context) {
+ super(context);
+ setPinnedPartitionHeadersEnabled(true);
+ }
+
+ private String[] mHeaders;
+ private int mPinnedHeaderCount;
+
+ public void setHeaders(String[] headers) {
+ this.mHeaders = headers;
+ }
+
+ @Override
+ protected View newHeaderView(Context context, int partition, Cursor cursor,
+ ViewGroup parent) {
+ LayoutInflater inflater = LayoutInflater.from(context);
+ return inflater.inflate(R.layout.list_section, null);
+ }
+
+ @Override
+ protected void bindHeaderView(View view, int parition, Cursor cursor) {
+ TextView headerText = (TextView)view.findViewById(R.id.header_text);
+ headerText.setText(mHeaders[parition]);
+ }
+
+ @Override
+ protected View newView(Context context, int partition, Cursor cursor, int position,
+ ViewGroup parent) {
+ LayoutInflater inflater = LayoutInflater.from(context);
+ return inflater.inflate(android.R.layout.simple_list_item_1, null);
+ }
+
+ @Override
+ protected void bindView(View v, int partition, Cursor cursor, int position) {
+ TextView text = (TextView)v.findViewById(android.R.id.text1);
+ text.setText(cursor.getString(1));
+ }
+
+ @Override
+ public View getPinnedHeaderView(int viewIndex, View convertView, ViewGroup parent) {
+ LayoutInflater inflater = LayoutInflater.from(getContext());
+ View view = inflater.inflate(R.layout.list_section, parent, false);
+ view.setFocusable(false);
+ view.setEnabled(false);
+ bindHeaderView(view, viewIndex, null);
+ return view;
+ }
+
+ @Override
+ public int getPinnedHeaderCount() {
+ return mPinnedHeaderCount;
+ }
+ }
+
+ private Handler mHandler = new Handler();
+
+ @Override
+ protected void onCreate(Bundle bundle) {
+ super.onCreate(bundle);
+
+ setContentView(R.layout.pinned_header_list_demo);
+
+ final TestPinnedHeaderListAdapter adapter = new TestPinnedHeaderListAdapter(this);
+
+ Bundle extras = getIntent().getExtras();
+ int[] counts = extras.getIntArray("counts");
+ String[] names = extras.getStringArray("names");
+ boolean[] showIfEmpty = extras.getBooleanArray("showIfEmpty");
+ boolean[] hasHeader = extras.getBooleanArray("headers");
+ int[] delays = extras.getIntArray("delays");
+
+ if (counts == null || names == null || showIfEmpty == null || delays == null) {
+ throw new IllegalArgumentException("Missing required extras");
+ }
+
+ adapter.setHeaders(names);
+ for (int i = 0; i < counts.length; i++) {
+ adapter.addPartition(showIfEmpty[i], names[i] != null);
+ adapter.mPinnedHeaderCount = names.length;
+ }
+ setListAdapter(adapter);
+ for (int i = 0; i < counts.length; i++) {
+ final int sectionId = i;
+ final Cursor cursor = makeCursor(names[i], counts[i]);
+ mHandler.postDelayed(new Runnable() {
+
+ public void run() {
+ adapter.changeCursor(sectionId, cursor);
+
+ }
+ }, delays[i]);
+ }
+ }
+
+ private Cursor makeCursor(String name, int count) {
+ MatrixCursor cursor = new MatrixCursor(new String[]{"_id", name});
+ for (int i = 0; i < count; i++) {
+ cursor.addRow(new Object[]{i, name + "[" + i + "]"});
+ }
+ return cursor;
+ }
+}
diff --git a/src/com/android/contacts/widget/PinnedHeaderListView.java b/src/com/android/contacts/widget/PinnedHeaderListView.java
new file mode 100644
index 0000000..442ddd3
--- /dev/null
+++ b/src/com/android/contacts/widget/PinnedHeaderListView.java
@@ -0,0 +1,501 @@
+/*
+ * 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.widget;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AbsListView;
+import android.widget.AdapterView;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+import android.widget.AbsListView.OnScrollListener;
+import android.widget.AdapterView.OnItemSelectedListener;
+
+/**
+ * A ListView that maintains a header pinned at the top of the list. The
+ * pinned header can be pushed up and dissolved as needed.
+ */
+public class PinnedHeaderListView extends ListView
+ implements OnScrollListener, OnItemSelectedListener {
+
+ /**
+ * Adapter interface. The list adapter must implement this interface.
+ */
+ public interface PinnedHeaderAdapter {
+
+ /**
+ * Returns the overall number of pinned headers, visible or not.
+ */
+ int getPinnedHeaderCount();
+
+ /**
+ * Creates or updates the pinned header view.
+ */
+ View getPinnedHeaderView(int viewIndex, View convertView, ViewGroup parent);
+
+ /**
+ * Configures the pinned headers to match the visible list items. The
+ * adapter should call {@link PinnedHeaderListView#setHeaderPinnedAtTop},
+ * {@link PinnedHeaderListView#setHeaderPinnedAtBottom},
+ * {@link PinnedHeaderListView#setFadingHeader} or
+ * {@link PinnedHeaderListView#setHeaderInvisible}, for each header that
+ * needs to change its position or visibility.
+ */
+ void configurePinnedHeaders(PinnedHeaderListView listView);
+
+ /**
+ * Returns the list position to scroll to if the pinned header is touched.
+ * Return -1 if the list does not need to be scrolled.
+ */
+ int getScrollPositionForHeader(int viewIndex);
+ }
+
+ private static final int MAX_ALPHA = 255;
+ private static final int TOP = 0;
+ private static final int BOTTOM = 1;
+ private static final int FADING = 2;
+
+ private static final int DEFAULT_ANIMATION_DURATION = 100;
+
+ private static final class PinnedHeader {
+ View view;
+ boolean visible;
+ int y;
+ int height;
+ int alpha;
+ int state;
+
+ boolean animating;
+ boolean targetVisible;
+ int sourceY;
+ int targetY;
+ long targetTime;
+ }
+
+ private PinnedHeaderAdapter mAdapter;
+ private int mSize;
+ private PinnedHeader[] mHeaders;
+ private int mPinnedHeaderBackgroundColor;
+ private RectF mBounds = new RectF();
+ private Paint mPaint = new Paint();
+ private OnScrollListener mOnScrollListener;
+ private OnItemSelectedListener mOnItemSelectedListener;
+ private int mScrollState;
+
+ private int mAnimationDuration = DEFAULT_ANIMATION_DURATION;
+ private boolean mAnimating;
+ private long mAnimationTargetTime;
+
+ public PinnedHeaderListView(Context context) {
+ this(context, null, com.android.internal.R.attr.listViewStyle);
+ }
+
+ public PinnedHeaderListView(Context context, AttributeSet attrs) {
+ this(context, attrs, com.android.internal.R.attr.listViewStyle);
+ }
+
+ public PinnedHeaderListView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ super.setOnScrollListener(this);
+ super.setOnItemSelectedListener(this);
+ }
+
+ /**
+ * An approximation of the background color of the pinned header. This color
+ * is used when the pinned header is being pushed up. At that point the
+ * header "fades away". Rather than computing a faded bitmap based on the
+ * 9-patch normally used for the background, we will use a solid color,
+ * which will provide better performance and reduced complexity.
+ */
+ public void setPinnedHeaderBackgroundColor(int color) {
+ mPinnedHeaderBackgroundColor = color;
+ mPaint.setColor(mPinnedHeaderBackgroundColor);
+ }
+
+ public void setPinnedHeaderAnimationDuration(int duration) {
+ mAnimationDuration = duration;
+ }
+
+ @Override
+ public void setAdapter(ListAdapter adapter) {
+ mAdapter = (PinnedHeaderAdapter)adapter;
+ super.setAdapter(adapter);
+ }
+
+ @Override
+ public void setOnScrollListener(OnScrollListener onScrollListener) {
+ mOnScrollListener = onScrollListener;
+ super.setOnScrollListener(this);
+ }
+
+ @Override
+ public void setOnItemSelectedListener(OnItemSelectedListener listener) {
+ mOnItemSelectedListener = listener;
+ super.setOnItemSelectedListener(this);
+ }
+
+ public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
+ int totalItemCount) {
+ if (mAdapter != null) {
+ int count = mAdapter.getPinnedHeaderCount();
+ if (count != mSize) {
+ mSize = count;
+ if (mHeaders == null) {
+ mHeaders = new PinnedHeader[mSize];
+ } else if (mHeaders.length < mSize) {
+ PinnedHeader[] headers = mHeaders;
+ mHeaders = new PinnedHeader[mSize];
+ System.arraycopy(headers, 0, mHeaders, 0, headers.length);
+ }
+ }
+
+ for (int i = 0; i < mSize; i++) {
+ if (mHeaders[i] == null) {
+ mHeaders[i] = new PinnedHeader();
+ }
+ mHeaders[i].view = mAdapter.getPinnedHeaderView(i, mHeaders[i].view, this);
+ }
+
+ // Disable vertical fading when the pinned header is present
+ // TODO change ListView to allow separate measures for top and bottom fading edge;
+ // in this particular case we would like to disable the top, but not the bottom edge.
+ if (mSize > 0) {
+ setFadingEdgeLength(0);
+ }
+
+ mAnimationTargetTime = System.currentTimeMillis() + mAnimationDuration;
+ mAdapter.configurePinnedHeaders(this);
+ invalidateIfAnimating();
+
+ }
+ if (mOnScrollListener != null) {
+ mOnScrollListener.onScroll(this, firstVisibleItem, visibleItemCount, totalItemCount);
+ }
+ }
+
+ public void onScrollStateChanged(AbsListView view, int scrollState) {
+ mScrollState = scrollState;
+ if (mOnScrollListener != null) {
+ mOnScrollListener.onScrollStateChanged(this, scrollState);
+ }
+ }
+
+ /**
+ * Ensures that the selected item is positioned below the top-pinned headers
+ * and above the bottom-pinned ones.
+ */
+ public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+ int height = getHeight();
+
+ int windowTop = 0;
+ int windowBottom = height;
+
+ int prevHeaderBottom = 0;
+ for (int i = 0; i < mSize; i++) {
+ PinnedHeader header = mHeaders[i];
+ if (header.visible) {
+ if (header.state == TOP) {
+ windowTop = header.y + header.height;
+ } else if (header.state == BOTTOM) {
+ windowBottom = header.y;
+ break;
+ }
+ }
+ }
+
+ View selectedView = getSelectedView();
+ if (selectedView != null) {
+ if (selectedView.getTop() < windowTop) {
+ setSelectionFromTop(position, windowTop);
+ } else if (selectedView.getBottom() > windowBottom) {
+ setSelectionFromTop(position, windowBottom - selectedView.getHeight());
+ }
+ }
+
+ if (mOnItemSelectedListener != null) {
+ mOnItemSelectedListener.onItemSelected(parent, view, position, id);
+ }
+ }
+
+ public void onNothingSelected(AdapterView<?> parent) {
+ if (mOnItemSelectedListener != null) {
+ mOnItemSelectedListener.onNothingSelected(parent);
+ }
+ }
+
+ public int getPinnedHeaderHeight(int viewIndex) {
+ ensurePinnedHeaderLayout(viewIndex);
+ return mHeaders[viewIndex].view.getHeight();
+ }
+
+ /**
+ * Set header to be pinned at the top.
+ *
+ * @param viewIndex index of the header view
+ * @param y is position of the header in pixels.
+ * @param animate true if the transition to the new coordinate should be animated
+ */
+ public void setHeaderPinnedAtTop(int viewIndex, int y, boolean animate) {
+ ensurePinnedHeaderLayout(viewIndex);
+ PinnedHeader header = mHeaders[viewIndex];
+ header.visible = true;
+ header.y = y;
+ header.state = TOP;
+
+ // TODO perhaps we should animate at the top as well
+ header.animating = false;
+ }
+
+ /**
+ * Set header to be pinned at the bottom.
+ *
+ * @param viewIndex index of the header view
+ * @param y is position of the header in pixels.
+ * @param animate true if the transition to the new coordinate should be animated
+ */
+ public void setHeaderPinnedAtBottom(int viewIndex, int y, boolean animate) {
+ ensurePinnedHeaderLayout(viewIndex);
+ PinnedHeader header = mHeaders[viewIndex];
+ header.state = BOTTOM;
+ if (header.animating) {
+ header.targetTime = mAnimationTargetTime;
+ header.targetY = y;
+ } else if (animate && (header.y != y || !header.visible)) {
+ if (header.visible) {
+ header.sourceY = y;
+ } else {
+ header.visible = true;
+ header.sourceY = y + header.height;
+ }
+ header.animating = true;
+ header.targetVisible = true;
+ header.targetTime = mAnimationTargetTime;
+ header.targetY = y;
+ } else {
+ header.visible = true;
+ header.y = y;
+ }
+ }
+
+ /**
+ * Set header to be pinned at the top of the first visible item.
+ *
+ * @param viewIndex index of the header view
+ * @param position is position of the header in pixels.
+ */
+ public void setFadingHeader(int viewIndex, int position, boolean fade) {
+ ensurePinnedHeaderLayout(viewIndex);
+
+ View child = getChildAt(position - getFirstVisiblePosition());
+
+ PinnedHeader header = mHeaders[viewIndex];
+ header.visible = true;
+ header.state = FADING;
+ header.alpha = MAX_ALPHA;
+ header.animating = false;
+
+ int top = getTotalTopPinnedHeaderHeight();
+ header.y = top;
+ if (fade) {
+ int bottom = child.getBottom() - top;
+ int headerHeight = header.height;
+ if (bottom < headerHeight) {
+ int portion = bottom - headerHeight;
+ header.alpha = MAX_ALPHA * (headerHeight + portion) / headerHeight;
+ header.y = top + portion;
+ }
+ }
+ }
+
+ /**
+ * Makes header invisible.
+ *
+ * @param viewIndex index of the header view
+ * @param animate true if the transition to the new coordinate should be animated
+ */
+ public void setHeaderInvisible(int viewIndex, boolean animate) {
+ PinnedHeader header = mHeaders[viewIndex];
+ if (header.visible && (animate || header.animating) && header.state == BOTTOM) {
+ if (!header.animating) {
+ header.visible = true;
+ header.sourceY = header.y;
+ header.targetY = header.y + header.height;
+ }
+ header.animating = true;
+ header.targetTime = mAnimationTargetTime;
+ header.targetVisible = false;
+ } else {
+ header.visible = false;
+ }
+ }
+
+ private void ensurePinnedHeaderLayout(int viewIndex) {
+ View view = mHeaders[viewIndex].view;
+ if (view.isLayoutRequested()) {
+ int widthSpec = MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.EXACTLY);
+ int heightSpec;
+ int lpHeight = view.getLayoutParams().height;
+ if (lpHeight > 0) {
+ heightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
+ } else {
+ heightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+ }
+ view.measure(widthSpec, heightSpec);
+ int height = view.getMeasuredHeight();
+ mHeaders[viewIndex].height = height;
+ view.layout(0, 0, view.getMeasuredWidth(), height);
+ }
+ }
+
+ /**
+ * Returns the sum of heights of headers pinned to the top.
+ */
+ public int getTotalTopPinnedHeaderHeight() {
+ for (int i = mSize; --i >= 0;) {
+ PinnedHeader header = mHeaders[i];
+ if (header.visible && header.state == TOP) {
+ return header.y + header.height;
+ }
+ }
+ return 0;
+ }
+
+ /**
+ * Returns the list item position at the specified y coordinate.
+ */
+ public int getPositionAt(int y) {
+ do {
+ int position = pointToPosition(0, y);
+ if (position != -1) {
+ return position;
+ }
+ // If position == -1, we must have hit a separator. Let's examine
+ // a nearby pixel
+ y--;
+ } while (y > 0);
+ return 0;
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ if (mScrollState == SCROLL_STATE_IDLE) {
+ final int y = (int)ev.getY();
+ for (int i = mSize; --i >= 0;) {
+ PinnedHeader header = mHeaders[i];
+ if (header.visible && header.y <= y && header.y + header.height > y) {
+ if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ return smoothScrollToPartition(i);
+ } else {
+ return true;
+ }
+ }
+ }
+ }
+
+ return super.onInterceptTouchEvent(ev);
+ }
+
+ private boolean smoothScrollToPartition(int partition) {
+ final int position = mAdapter.getScrollPositionForHeader(partition);
+ if (position == -1) {
+ return false;
+ }
+
+ int offset = 0;
+ for (int i = 0; i < partition; i++) {
+ PinnedHeader header = mHeaders[i];
+ if (header.visible) {
+ offset += header.height;
+ }
+ }
+
+ smoothScrollToPositionFromTop(position + getHeaderViewsCount(), offset);
+ return true;
+ }
+
+ private void invalidateIfAnimating() {
+ mAnimating = false;
+ for (int i = 0; i < mSize; i++) {
+ if (mHeaders[i].animating) {
+ mAnimating = true;
+ invalidate();
+ return;
+ }
+ }
+ }
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ super.dispatchDraw(canvas);
+
+ long currentTime = mAnimating ? System.currentTimeMillis() : 0;
+
+ // First draw top headers, then the bottom ones to handle the Z axis correctly
+ for (int i = mSize; --i >= 0;) {
+ PinnedHeader header = mHeaders[i];
+ if (header.visible && (header.state == TOP || header.state == FADING)) {
+ drawHeader(canvas, header, currentTime);
+ }
+ }
+
+ for (int i = 0; i < mSize; i++) {
+ PinnedHeader header = mHeaders[i];
+ if (header.visible && header.state == BOTTOM) {
+ drawHeader(canvas, header, currentTime);
+ }
+ }
+
+ invalidateIfAnimating();
+ }
+
+ private void drawHeader(Canvas canvas, PinnedHeader header, long currentTime) {
+ if (header.animating) {
+ int timeLeft = (int)(header.targetTime - currentTime);
+ if (timeLeft <= 0) {
+ header.y = header.targetY;
+ header.visible = header.targetVisible;
+ header.animating = false;
+ } else {
+ header.y = header.targetY + (header.sourceY - header.targetY) * timeLeft
+ / mAnimationDuration;
+ }
+ }
+ if (header.visible) {
+ View view = header.view;
+ if (header.state == FADING) {
+ int saveCount = canvas.save();
+ canvas.translate(0, header.y);
+ mBounds.set(0, 0, view.getWidth(), view.getHeight());
+ canvas.drawRect(mBounds, mPaint);
+ canvas.saveLayerAlpha(mBounds, header.alpha, Canvas.ALL_SAVE_FLAG);
+ view.draw(canvas);
+ canvas.restoreToCount(saveCount);
+ } else {
+ canvas.save();
+ canvas.translate(0, header.y);
+ view.draw(canvas);
+ canvas.restore();
+ }
+ }
+ }
+}
diff --git a/src/com/android/contacts/widget/SearchEditText.java b/src/com/android/contacts/widget/SearchEditText.java
new file mode 100644
index 0000000..d8ea2be
--- /dev/null
+++ b/src/com/android/contacts/widget/SearchEditText.java
@@ -0,0 +1,141 @@
+/*
+ * 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.widget;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.text.Editable;
+import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.util.AttributeSet;
+import android.view.KeyEvent;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
+import android.widget.TextView;
+import android.widget.TextView.OnEditorActionListener;
+
+/**
+ * A custom text editor that helps automatically dismiss the activity along with the soft
+ * keyboard.
+ */
+public class SearchEditText extends EditText implements OnEditorActionListener, TextWatcher {
+
+ private boolean mMaginfyingGlassEnabled = true;
+ private Drawable mMagnifyingGlass;
+ private OnFilterTextListener mListener;
+
+ private boolean mMagnifyingGlassShown;
+
+ public interface OnFilterTextListener {
+ void onFilterChange(String queryString);
+ void onCancelSearch();
+ }
+
+ public SearchEditText(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ addTextChangedListener(this);
+ setOnEditorActionListener(this);
+ mMagnifyingGlass = getCompoundDrawables()[2];
+ setCompoundDrawables(null, null, null, null);
+ }
+
+ public boolean isMaginfyingGlassEnabled() {
+ return mMaginfyingGlassEnabled;
+ }
+
+ public void setMaginfyingGlassEnabled(boolean flag) {
+ this.mMaginfyingGlassEnabled = flag;
+ }
+
+ public void setOnFilterTextListener(OnFilterTextListener listener) {
+ this.mListener = listener;
+ }
+
+ /**
+ * Conditionally shows a magnifying glass icon on the right side of the text field
+ * when the text it empty.
+ */
+ @Override
+ public boolean onPreDraw() {
+ boolean emptyText = TextUtils.isEmpty(getText());
+ if (mMagnifyingGlassShown != emptyText) {
+ mMagnifyingGlassShown = emptyText;
+ if (mMagnifyingGlassShown && mMaginfyingGlassEnabled) {
+ setCompoundDrawables(null, null, mMagnifyingGlass, null);
+ } else {
+ setCompoundDrawables(null, null, null, null);
+ }
+ return false;
+ }
+ return super.onPreDraw();
+ }
+
+ /**
+ * Dismisses the search UI along with the keyboard if the filter text is empty.
+ */
+ @Override
+ public boolean onKeyPreIme(int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_BACK && TextUtils.isEmpty(getText()) && mListener != null) {
+ mListener.onCancelSearch();
+ return true;
+ }
+ return false;
+ }
+
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ }
+
+ /**
+ * Event handler for search UI.
+ */
+ public void afterTextChanged(Editable s) {
+ if (mListener != null) {
+ mListener.onFilterChange(trim(s));
+ }
+ }
+
+ private String trim(Editable s) {
+ return s.toString().trim();
+ }
+
+ /**
+ * Event handler for search UI.
+ */
+ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+ if (actionId == EditorInfo.IME_ACTION_DONE) {
+ hideSoftKeyboard();
+ if (TextUtils.isEmpty(trim(getText())) && mListener != null) {
+ mListener.onCancelSearch();
+ }
+ return true;
+ }
+ return false;
+ }
+
+ private void hideSoftKeyboard() {
+ // Hide soft keyboard, if visible
+ InputMethodManager inputMethodManager = (InputMethodManager)
+ getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+ inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
+ }
+
+}
diff --git a/src/com/android/contacts/widget/SingleItemAdapter.java b/src/com/android/contacts/widget/SingleItemAdapter.java
new file mode 100644
index 0000000..3532bfc
--- /dev/null
+++ b/src/com/android/contacts/widget/SingleItemAdapter.java
@@ -0,0 +1,47 @@
+/*
+ * 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.widget;
+
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+
+/**
+ * A general purpose adapter that contains exactly one item.
+ */
+public abstract class SingleItemAdapter extends BaseAdapter {
+
+ public int getCount() {
+ return 1;
+ }
+
+ public Object getItem(int position) {
+ return null;
+ }
+
+ public long getItemId(int position) {
+ return 0;
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ return getView(convertView, parent);
+ }
+
+ /**
+ * Creates the view.
+ */
+ protected abstract View getView(View convertView, ViewGroup parent);
+}
diff --git a/src/com/android/contacts/TextHighlightingAnimation.java b/src/com/android/contacts/widget/TextHighlightingAnimation.java
similarity index 96%
rename from src/com/android/contacts/TextHighlightingAnimation.java
rename to src/com/android/contacts/widget/TextHighlightingAnimation.java
index e35ae1e..21bbc63 100644
--- a/src/com/android/contacts/TextHighlightingAnimation.java
+++ b/src/com/android/contacts/widget/TextHighlightingAnimation.java
@@ -13,14 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.contacts;
+package com.android.contacts.widget;
import com.android.internal.R;
import android.database.CharArrayBuffer;
import android.graphics.Color;
import android.os.Handler;
-import android.text.Spanned;
import android.text.TextPaint;
import android.text.style.CharacterStyle;
import android.view.animation.AccelerateInterpolator;
@@ -29,7 +28,7 @@
/**
* An animation that alternately dims and brightens the non-highlighted portion of text.
*/
-public abstract class TextHighlightingAnimation implements Runnable {
+public abstract class TextHighlightingAnimation implements Runnable, TextWithHighlightingFactory {
private static final int MAX_ALPHA = 255;
private static final int MIN_ALPHA = 50;
@@ -54,7 +53,7 @@
/**
* A Spanned that highlights a part of text by dimming another part of that text.
*/
- public class TextWithHighlighting implements Spanned {
+ public class TextWithHighlightingImpl implements TextWithHighlighting {
private final DimmingSpan[] mSpans;
private boolean mDimmingEnabled;
@@ -63,7 +62,7 @@
private int mDimmingSpanEnd;
private String mString;
- public TextWithHighlighting() {
+ public TextWithHighlightingImpl() {
mSpans = new DimmingSpan[] { mDimmingSpan };
}
@@ -216,12 +215,12 @@
/**
* Returns a Spanned that can be used by a text view to show text with highlighting.
*/
- public TextWithHighlighting createTextWithHighlighting() {
- return new TextWithHighlighting();
+ public TextWithHighlightingImpl createTextWithHighlighting() {
+ return new TextWithHighlightingImpl();
}
/**
- * Override and invalidate (redraw) TextViews showing {@link TextWithHighlighting}.
+ * Override and invalidate (redraw) TextViews showing {@link TextWithHighlightingImpl}.
*/
protected abstract void invalidate();
diff --git a/src/com/android/contacts/SearchResultsActivity.java b/src/com/android/contacts/widget/TextWithHighlighting.java
similarity index 60%
copy from src/com/android/contacts/SearchResultsActivity.java
copy to src/com/android/contacts/widget/TextWithHighlighting.java
index 09f0014..3a32b02 100644
--- a/src/com/android/contacts/SearchResultsActivity.java
+++ b/src/com/android/contacts/widget/TextWithHighlighting.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2007 The Android Open Source Project
+ * 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.
@@ -13,11 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.contacts;
+package com.android.contacts.widget;
+
+import android.database.CharArrayBuffer;
+import android.text.Spanned;
/**
- * The activity that displays the list of contact search results. We need a separate
- * class because it uses a different theme from {@link ContactsListActivity}.
+ * A Spanned that highlights a part of text by dimming another part of that text.
*/
-public class SearchResultsActivity extends ContactsListActivity {
+public interface TextWithHighlighting extends Spanned {
+ void setText(CharArrayBuffer baseText, CharArrayBuffer highlightedText);
}
diff --git a/src/com/android/contacts/SearchResultsActivity.java b/src/com/android/contacts/widget/TextWithHighlightingFactory.java
similarity index 64%
copy from src/com/android/contacts/SearchResultsActivity.java
copy to src/com/android/contacts/widget/TextWithHighlightingFactory.java
index 09f0014..ee5744d 100644
--- a/src/com/android/contacts/SearchResultsActivity.java
+++ b/src/com/android/contacts/widget/TextWithHighlightingFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2007 The Android Open Source Project
+ * 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.
@@ -13,11 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.contacts;
+package com.android.contacts.widget;
/**
- * The activity that displays the list of contact search results. We need a separate
- * class because it uses a different theme from {@link ContactsListActivity}.
+ * A factory for text fields with animated highlighting.
*/
-public class SearchResultsActivity extends ContactsListActivity {
+public interface TextWithHighlightingFactory {
+ TextWithHighlighting createTextWithHighlighting();
}
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index 7af1a54..0c5ee70 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -4,9 +4,9 @@
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.
@@ -17,25 +17,57 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.contacts.tests">
+ <uses-permission android:name="android.permission.READ_CONTACTS" />
+
<application>
<uses-library android:name="android.test.runner" />
<meta-data android:name="com.android.contacts.iconset" android:resource="@xml/iconset" />
+ <uses-permission android:name="android.permission.READ_CONTACTS" />
+
+ <activity android:name=".allintents.AllIntentsActivity"
+ android:label="@string/contactsIntents"
+ >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name=".allintents.ResultActivity"
+ android:label="@string/result"
+ >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name=".widget.PinnedHeaderUseCaseActivity"
+ android:label="@string/pinnedHeaderList"
+ >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
</application>
<instrumentation android:name="android.test.InstrumentationTestRunner"
android:targetPackage="com.android.contacts"
android:label="Contacts app tests">
</instrumentation>
-
+
<instrumentation android:name="com.android.contacts.ContactsLaunchPerformance"
android:targetPackage="com.android.contacts"
android:label="Contacts launch performance">
</instrumentation>
-
<instrumentation android:name="com.android.contacts.DialerLaunchPerformance"
android:targetPackage="com.android.contacts"
android:label="Dialer launch performance">
</instrumentation>
-</manifest>
+</manifest>
diff --git a/tests/assets/v21_simple.vcf b/tests/assets/v21_simple.vcf
new file mode 100644
index 0000000..86f4d33
--- /dev/null
+++ b/tests/assets/v21_simple.vcf
@@ -0,0 +1,3 @@
+BEGIN:VCARD
+N:test
+END:VCARD
\ No newline at end of file
diff --git a/tests/assets/v30_simple.vcf b/tests/assets/v30_simple.vcf
new file mode 100644
index 0000000..418661f
--- /dev/null
+++ b/tests/assets/v30_simple.vcf
@@ -0,0 +1,13 @@
+BEGIN:VCARD
+VERSION:3.0
+FN:And Roid
+N:And;Roid;;;
+ORG:Open;Handset; Alliance
+SORT-STRING:android
+TEL;TYPE=PREF;TYPE=VOICE:0300000000
+CLASS:PUBLIC
+X-GNO:0
+X-GN:group0
+X-REDUCTION:0
+REV:20081031T065854Z
+END:VCARD
diff --git a/res/layout/item_editor_field.xml b/tests/res/layout/intent_list_item.xml
similarity index 61%
copy from res/layout/item_editor_field.xml
copy to tests/res/layout/intent_list_item.xml
index 9ad8689..4749224 100644
--- a/res/layout/item_editor_field.xml
+++ b/tests/res/layout/intent_list_item.xml
@@ -1,12 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2008 The Android Open Source Project
+<!-- Copyright (C) 2006 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.
@@ -14,7 +14,12 @@
limitations under the License.
-->
-<EditText
- xmlns:android="http://schemas.android.com/apk/res/android"
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@android:id/text1"
android:layout_width="match_parent"
- android:layout_height="wrap_content" />
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:gravity="center_vertical"
+ android:paddingLeft="6dip"
+ android:minHeight="?android:attr/listPreferredItemHeight"
+/>
diff --git a/res/layout/item_editor_field.xml b/tests/res/layout/result.xml
similarity index 61%
copy from res/layout/item_editor_field.xml
copy to tests/res/layout/result.xml
index 9ad8689..0ab32c6 100644
--- a/res/layout/item_editor_field.xml
+++ b/tests/res/layout/result.xml
@@ -1,12 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2008 The Android Open Source Project
+<!-- 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.
@@ -14,7 +14,19 @@
limitations under the License.
-->
-<EditText
+<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
- android:layout_height="wrap_content" />
+ android:layout_height="match_parent"
+ android:fillViewport="true"
+>
+
+ <TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/table"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:shrinkColumns="1"
+ android:stretchColumns="*">
+ </TableLayout>
+</ScrollView>
+
diff --git a/tests/res/values/donottranslate_strings.xml b/tests/res/values/donottranslate_strings.xml
new file mode 100644
index 0000000..21a8e7a
--- /dev/null
+++ b/tests/res/values/donottranslate_strings.xml
@@ -0,0 +1,91 @@
+<?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.
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+ <string name="contactsIntents">Contacts Intents</string>
+ <string name="result">Result returned by activity</string>
+
+ <string-array name="allIntents">
+ <!-- List modes -->
+ <item>LIST_DEFAULT</item>
+ <item>LIST_ALL_CONTACTS_ACTION</item>
+ <item>LIST_CONTACTS_WITH_PHONES_ACTION</item>
+ <item>LIST_STARRED_ACTION</item>
+ <item>LIST_STARRED_ACTION (filter)</item>
+ <item>LIST_FREQUENT_ACTION</item>
+ <item>LIST_FREQUENT_ACTION (filter)</item>
+ <item>LIST_STREQUENT_ACTION</item>
+ <item>LIST_STREQUENT_ACTION (filter)</item>
+ <item>ACTION_PICK: contact</item>
+ <item>ACTION_PICK: contact (legacy)</item>
+ <item>ACTION_PICK: phone</item>
+ <item>ACTION_PICK: phone (legacy)</item>
+ <item>ACTION_PICK: postal</item>
+ <item>ACTION_PICK: postal (legacy)</item>
+ <item>ACTION_CREATE_SHORTCUT: contact</item>
+ <item>ACTION_CREATE_SHORTCUT: contact (filter)</item>
+ <item>ACTION_CREATE_SHORTCUT: dial</item>
+ <item>ACTION_CREATE_SHORTCUT: dial (filter)</item>
+ <item>ACTION_CREATE_SHORTCUT: message</item>
+ <item>ACTION_CREATE_SHORTCUT: message (filter)</item>
+ <item>ACTION_GET_CONTENT: contact</item>
+ <item>ACTION_GET_CONTENT: contact (filter)</item>
+ <item>ACTION_GET_CONTENT: contact (legacy)</item>
+ <item>ACTION_GET_CONTENT: contact (filter, legacy)</item>
+ <item>ACTION_GET_CONTENT: phone</item>
+ <item>ACTION_GET_CONTENT: phone (filter)</item>
+ <item>ACTION_GET_CONTENT: phone (legacy)</item>
+ <item>ACTION_GET_CONTENT: postal</item>
+ <item>ACTION_GET_CONTENT: postal (filter)</item>
+ <item>ACTION_GET_CONTENT: postal (legacy)</item>
+ <item>ACTION_INSERT_OR_EDIT</item>
+ <item>ACTION_SEARCH (call button)</item>
+ <item>ACTION_SEARCH: contact</item>
+ <item>ACTION_SEARCH: email</item>
+ <item>ACTION_SEARCH: phone</item>
+ <item>SEARCH_SUGGESTION_CLICKED (call button)</item>
+ <item>SEARCH_SUGGESTION_CLICKED: contact</item>
+ <item>SEARCH_SUGGESTION_DIAL_NUMBER_CLICKED</item>
+ <item>SEARCH_SUGGESTION_CREATE_CONTACT_CLICKED</item>
+ <item>TODO: JOIN_CONTACT</item>
+ <item>ACTION_GET_MULTIPLE_PHONES</item>
+
+ <!-- Edit Contact -->
+ <item>EDIT (content uri with only id)</item>
+ <item>EDIT (lookup uri without id)</item>
+ <item>EDIT (lookup uri)</item>
+ <item>EDIT (called for raw contact)</item>
+ <item>EDIT (legacy style uri)</item>
+ <item>EDIT (create new contact)</item>
+ <item>EDIT (create new raw contact)</item>
+ <item>EDIT (create new legacy)</item>
+
+ <!-- View Contact -->
+ <item>VIEW (content uri with only id)</item>
+ <item>VIEW (lookup uri without id)</item>
+ <item>VIEW (lookup uri)</item>
+ <item>VIEW (called for raw contact)</item>
+ <item>VIEW (legacy style uri)</item>
+ </string-array>
+
+ <string name="pinnedHeaderList">Pinned Headers</string>
+
+ <string-array name="pinnedHeaderUseCases">
+ <item>One short section - no headers</item>
+ <item>Two short sections with headers</item>
+ <item>Five short sections with headers</item>
+ </string-array>
+</resources>
diff --git a/tests/src/com/android/contacts/ContactDetailTest.java b/tests/src/com/android/contacts/ContactDetailTest.java
new file mode 100644
index 0000000..0b850b7
--- /dev/null
+++ b/tests/src/com/android/contacts/ContactDetailTest.java
@@ -0,0 +1,41 @@
+package com.android.contacts;
+
+import com.android.contacts.activities.ContactDetailActivity;
+import com.android.contacts.tests.mocks.ContactsMockContext;
+import com.android.contacts.tests.mocks.MockContentProvider;
+
+import android.test.ActivityUnitTestCase;
+
+public class ContactDetailTest extends ActivityUnitTestCase<ContactDetailActivity> {
+ private ContactsMockContext mContext;
+ private MockContentProvider mContactsProvider;
+
+ public ContactDetailTest() {
+ super(ContactDetailActivity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mContext = new ContactsMockContext(getInstrumentation().getTargetContext());
+ mContactsProvider = mContext.getContactsProvider();
+ setActivityContext(mContext);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+// public void testFoo() {
+// // Use lookup-style Uris that also contain the Contact-ID
+// //long rawContactId1 = mCreator.createRawContact("JohnDoe", "John", "Doe");
+// //long contactId1 = mCreator.getContactIdByRawContactId(rawContactId1);
+// //Uri contactUri1 = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId1);
+// Intent intent = new Intent(Intent.ACTION_VIEW,
+// ContentUris.withAppendedId(Contacts.CONTENT_URI, 123));
+// startActivity(intent, null, null);
+// ContactDetailActivity activity = getActivity();
+// mContactsProvider.verify();
+// }
+}
diff --git a/tests/src/com/android/contacts/ContactLoaderTest.java b/tests/src/com/android/contacts/ContactLoaderTest.java
new file mode 100644
index 0000000..3ef109d
--- /dev/null
+++ b/tests/src/com/android/contacts/ContactLoaderTest.java
@@ -0,0 +1,350 @@
+/*
+ * 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;
+
+import com.android.contacts.tests.mocks.ContactsMockContext;
+import com.android.contacts.tests.mocks.MockContentProvider;
+import com.android.contacts.views.ContactLoader;
+
+import android.content.ContentUris;
+import android.net.Uri;
+import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.DisplayNameSources;
+import android.provider.ContactsContract.RawContacts;
+import android.provider.ContactsContract.StatusUpdates;
+import android.test.LoaderTestCase;
+
+/**
+ * Runs ContactLoader tests for the the contact-detail and editor view.
+ */
+public class ContactLoaderTest extends LoaderTestCase {
+ ContactsMockContext mMockContext;
+ MockContentProvider mContactsProvider;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mMockContext = new ContactsMockContext(getContext());
+ mContactsProvider = mMockContext.getContactsProvider();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ private ContactLoader.Result assertLoadContact(Uri uri) {
+ final ContactLoader loader = new ContactLoader(mMockContext, uri);
+ return getLoaderResultSynchronously(loader);
+ }
+
+ public void testNullUri() {
+ ContactLoader.Result result = assertLoadContact(null);
+ assertEquals(ContactLoader.Result.ERROR, result);
+ }
+
+ public void testEmptyUri() {
+ ContactLoader.Result result = assertLoadContact(Uri.EMPTY);
+ assertEquals(ContactLoader.Result.ERROR, result);
+ }
+
+ public void testInvalidUri() {
+ ContactLoader.Result result = assertLoadContact(Uri.parse("content://wtf"));
+ assertEquals(ContactLoader.Result.ERROR, result);
+ }
+
+ public void testLoadContactWithContactIdUri() {
+ // Use content Uris that only contain the ID
+ final long contactId = 1;
+ final long rawContactId = 11;
+ final long dataId = 21;
+
+ final String lookupKey = "aa%12%@!";
+ final Uri baseUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
+ final Uri entityUri = Uri.withAppendedPath(baseUri, Contacts.Entity.CONTENT_DIRECTORY);
+ final Uri lookupUri = ContentUris.withAppendedId(
+ Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey),
+ contactId);
+
+ ContactQueries queries = new ContactQueries();
+ mContactsProvider.expectTypeQuery(baseUri, Contacts.CONTENT_ITEM_TYPE);
+ queries.fetchAllData(entityUri, contactId, rawContactId, dataId, lookupKey);
+
+ ContactLoader.Result contact = assertLoadContact(baseUri);
+
+ assertEquals(contactId, contact.getId());
+ assertEquals(rawContactId, contact.getNameRawContactId());
+ assertEquals(DisplayNameSources.STRUCTURED_NAME, contact.getDisplayNameSource());
+ assertEquals(lookupKey, contact.getLookupKey());
+ assertEquals(lookupUri, contact.getLookupUri());
+ assertEquals(1, contact.getEntities().size());
+ assertEquals(1, contact.getStatuses().size());
+ mContactsProvider.verify();
+ }
+
+ public void testLoadContactWithOldStyleUri() {
+ // Use content Uris that only contain the ID but use the format used in Donut
+ final long contactId = 1;
+ final long rawContactId = 11;
+ final long dataId = 21;
+
+ final String lookupKey = "aa%12%@!";
+ final Uri legacyUri = ContentUris.withAppendedId(
+ Uri.parse("content://contacts"), rawContactId);
+ final Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
+ final Uri baseUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
+ final Uri lookupUri = ContentUris.withAppendedId(
+ Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey),
+ contactId);
+ final Uri entityUri = Uri.withAppendedPath(lookupUri, Contacts.Entity.CONTENT_DIRECTORY);
+
+ ContactQueries queries = new ContactQueries();
+ queries.fetchContactIdAndLookupFromRawContactUri(rawContactUri, contactId, lookupKey);
+ queries.fetchAllData(entityUri, contactId, rawContactId, dataId, lookupKey);
+
+ ContactLoader.Result contact = assertLoadContact(legacyUri);
+
+ assertEquals(contactId, contact.getId());
+ assertEquals(rawContactId, contact.getNameRawContactId());
+ assertEquals(DisplayNameSources.STRUCTURED_NAME, contact.getDisplayNameSource());
+ assertEquals(lookupKey, contact.getLookupKey());
+ assertEquals(lookupUri, contact.getLookupUri());
+ assertEquals(1, contact.getEntities().size());
+ assertEquals(1, contact.getStatuses().size());
+ mContactsProvider.verify();
+ }
+
+ public void testLoadContactWithRawContactIdUri() {
+ // Use content Uris that only contain the ID but use the format used in Donut
+ final long contactId = 1;
+ final long rawContactId = 11;
+ final long dataId = 21;
+
+ final String lookupKey = "aa%12%@!";
+ final Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
+ final Uri baseUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
+ final Uri lookupUri = ContentUris.withAppendedId(
+ Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey),
+ contactId);
+ final Uri entityUri = Uri.withAppendedPath(lookupUri, Contacts.Entity.CONTENT_DIRECTORY);
+
+ ContactQueries queries = new ContactQueries();
+ mContactsProvider.expectTypeQuery(rawContactUri, RawContacts.CONTENT_ITEM_TYPE);
+ queries.fetchContactIdAndLookupFromRawContactUri(rawContactUri, contactId, lookupKey);
+ queries.fetchAllData(entityUri, contactId, rawContactId, dataId, lookupKey);
+
+ ContactLoader.Result contact = assertLoadContact(rawContactUri);
+
+ assertEquals(contactId, contact.getId());
+ assertEquals(rawContactId, contact.getNameRawContactId());
+ assertEquals(DisplayNameSources.STRUCTURED_NAME, contact.getDisplayNameSource());
+ assertEquals(lookupKey, contact.getLookupKey());
+ assertEquals(lookupUri, contact.getLookupUri());
+ assertEquals(1, contact.getEntities().size());
+ assertEquals(1, contact.getStatuses().size());
+ mContactsProvider.verify();
+ }
+
+ public void testLoadContactWithContactLookupUri() {
+ // Use lookup-style Uris that do not contain the Contact-ID
+
+ final long contactId = 1;
+ final long rawContactId = 11;
+ final long dataId = 21;
+
+ final String lookupKey = "aa%12%@!";
+ final Uri baseUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
+ final Uri lookupNoIdUri = Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey);
+ final Uri lookupUri = ContentUris.withAppendedId(lookupNoIdUri, contactId);
+ final Uri entityUri = Uri.withAppendedPath(lookupNoIdUri, Contacts.Entity.CONTENT_DIRECTORY);
+
+ ContactQueries queries = new ContactQueries();
+ mContactsProvider.expectTypeQuery(lookupNoIdUri, Contacts.CONTENT_ITEM_TYPE);
+ queries.fetchAllData(entityUri, contactId, rawContactId, dataId, lookupKey);
+
+ ContactLoader.Result contact = assertLoadContact(lookupNoIdUri);
+
+ assertEquals(contactId, contact.getId());
+ assertEquals(rawContactId, contact.getNameRawContactId());
+ assertEquals(DisplayNameSources.STRUCTURED_NAME, contact.getDisplayNameSource());
+ assertEquals(lookupKey, contact.getLookupKey());
+ assertEquals(lookupUri, contact.getLookupUri());
+ assertEquals(1, contact.getEntities().size());
+ assertEquals(1, contact.getStatuses().size());
+ mContactsProvider.verify();
+ }
+
+ public void testLoadContactWithContactLookupAndIdUri() {
+ // Use lookup-style Uris that also contain the Contact-ID
+ final long contactId = 1;
+ final long rawContactId = 11;
+ final long dataId = 21;
+
+ final String lookupKey = "aa%12%@!";
+ final Uri baseUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
+ final Uri lookupUri = ContentUris.withAppendedId(
+ Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey),
+ contactId);
+ final Uri entityUri = Uri.withAppendedPath(lookupUri, Contacts.Entity.CONTENT_DIRECTORY);
+
+ ContactQueries queries = new ContactQueries();
+ mContactsProvider.expectTypeQuery(lookupUri, Contacts.CONTENT_ITEM_TYPE);
+ queries.fetchAllData(entityUri, contactId, rawContactId, dataId, lookupKey);
+
+ ContactLoader.Result contact = assertLoadContact(lookupUri);
+
+ assertEquals(contactId, contact.getId());
+ assertEquals(rawContactId, contact.getNameRawContactId());
+ assertEquals(DisplayNameSources.STRUCTURED_NAME, contact.getDisplayNameSource());
+ assertEquals(lookupKey, contact.getLookupKey());
+ assertEquals(lookupUri, contact.getLookupUri());
+ assertEquals(1, contact.getEntities().size());
+ assertEquals(1, contact.getStatuses().size());
+ mContactsProvider.verify();
+ }
+
+ public void testLoadContactWithContactLookupWithIncorrectIdUri() {
+ // Use lookup-style Uris that contain incorrect Contact-ID
+ // (we want to ensure that still the correct contact is chosen)
+
+ final long contactId = 1;
+ final long wrongContactId = 2;
+ final long rawContactId = 11;
+ final long wrongRawContactId = 12;
+ final long dataId = 21;
+
+ final String lookupKey = "aa%12%@!";
+ final String wrongLookupKey = "ab%12%@!";
+ final Uri baseUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
+ final Uri wrongBaseUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, wrongContactId);
+ final Uri lookupUri = ContentUris.withAppendedId(
+ Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey),
+ contactId);
+ final Uri lookupWithWrongIdUri = ContentUris.withAppendedId(
+ Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey),
+ wrongContactId);
+ final Uri entityUri = Uri.withAppendedPath(lookupWithWrongIdUri,
+ Contacts.Entity.CONTENT_DIRECTORY);
+
+ ContactQueries queries = new ContactQueries();
+ mContactsProvider.expectTypeQuery(lookupWithWrongIdUri, Contacts.CONTENT_ITEM_TYPE);
+ queries.fetchAllData(entityUri, contactId, rawContactId, dataId, lookupKey);
+
+ ContactLoader.Result contact = assertLoadContact(lookupWithWrongIdUri);
+
+ assertEquals(contactId, contact.getId());
+ assertEquals(rawContactId, contact.getNameRawContactId());
+ assertEquals(DisplayNameSources.STRUCTURED_NAME, contact.getDisplayNameSource());
+ assertEquals(lookupKey, contact.getLookupKey());
+ assertEquals(lookupUri, contact.getLookupUri());
+ assertEquals(1, contact.getEntities().size());
+ assertEquals(1, contact.getStatuses().size());
+
+ mContactsProvider.verify();
+ }
+
+ class ContactQueries {
+ public void fetchAllData(
+ Uri baseUri, long contactId, long rawContactId, long dataId, String encodedLookup) {
+ mContactsProvider.expectQuery(baseUri)
+ .withProjection(new String[] {
+ Contacts.NAME_RAW_CONTACT_ID, Contacts.DISPLAY_NAME_SOURCE,
+ Contacts.LOOKUP_KEY, Contacts.DISPLAY_NAME, Contacts.PHONETIC_NAME,
+ Contacts.PHOTO_ID, Contacts.STARRED, Contacts.CONTACT_PRESENCE,
+ Contacts.CONTACT_STATUS, Contacts.CONTACT_STATUS_TIMESTAMP,
+ Contacts.CONTACT_STATUS_RES_PACKAGE, Contacts.CONTACT_STATUS_LABEL,
+
+ Contacts.Entity.CONTACT_ID,
+ Contacts.Entity.RAW_CONTACT_ID,
+
+ RawContacts.ACCOUNT_NAME, RawContacts.ACCOUNT_TYPE, RawContacts.DIRTY,
+ RawContacts.VERSION, RawContacts.SOURCE_ID, RawContacts.SYNC1,
+ RawContacts.SYNC2, RawContacts.SYNC3, RawContacts.SYNC4,
+ RawContacts.DELETED, RawContacts.IS_RESTRICTED,
+ RawContacts.NAME_VERIFIED,
+
+ Contacts.Entity.DATA_ID,
+
+ Data.DATA1, Data.DATA2, Data.DATA3, Data.DATA4, Data.DATA5,
+ Data.DATA6, Data.DATA7, Data.DATA8, Data.DATA9, Data.DATA10,
+ Data.DATA11, Data.DATA12, Data.DATA13, Data.DATA14, Data.DATA15,
+ Data.SYNC1, Data.SYNC2, Data.SYNC3, Data.SYNC4,
+ Data.DATA_VERSION, Data.IS_PRIMARY,
+ Data.IS_SUPER_PRIMARY, Data.MIMETYPE, Data.RES_PACKAGE,
+
+ GroupMembership.GROUP_SOURCE_ID,
+
+ Data.PRESENCE, Data.CHAT_CAPABILITY,
+ Data.STATUS, Data.STATUS_RES_PACKAGE, Data.STATUS_ICON,
+ Data.STATUS_LABEL, Data.STATUS_TIMESTAMP,
+ })
+ .withSortOrder(Contacts.Entity.RAW_CONTACT_ID)
+ .returnRow(
+ rawContactId, 40,
+ "aa%12%@!", "John Doe", "jdo",
+ 0, 0, StatusUpdates.AVAILABLE,
+ "Having lunch", 0,
+ "mockPkg1", 10,
+
+ contactId,
+ rawContactId,
+
+ "mockAccountName", "mockAccountType", 0,
+ 1, 0, "sync1",
+ "sync2", "sync3", "sync4",
+ 0, 0, 0,
+
+ dataId,
+
+ "dat1", "dat2", "dat3", "dat4", "dat5",
+ "dat6", "dat7", "dat8", "dat9", "dat10",
+ "dat11", "dat12", "dat13", "dat14", "dat15",
+ "syn1", "syn2", "syn3", "syn4",
+
+ 0, 0,
+ 0, StructuredName.CONTENT_ITEM_TYPE, "mockPkg2",
+
+ "groupId",
+
+ StatusUpdates.INVISIBLE, null,
+ "Having dinner", "mockPkg3", 0,
+ 20, 0
+ );
+ }
+
+ void fetchLookupAndId(final Uri sourceUri, final long expectedContactId,
+ final String expectedEncodedLookup) {
+ mContactsProvider.expectQuery(sourceUri)
+ .withProjection(Contacts.LOOKUP_KEY, Contacts._ID)
+ .returnRow(expectedEncodedLookup, expectedContactId);
+ }
+
+ void fetchContactIdAndLookupFromRawContactUri(final Uri rawContactUri,
+ final long expectedContactId, final String expectedEncodedLookup) {
+ // TODO: use a lighter query by joining rawcontacts with contacts in provider
+ // (See ContactContracts.java)
+ final Uri dataUri = Uri.withAppendedPath(rawContactUri,
+ RawContacts.Data.CONTENT_DIRECTORY);
+ mContactsProvider.expectQuery(dataUri)
+ .withProjection(RawContacts.CONTACT_ID, Contacts.LOOKUP_KEY)
+ .returnRow(expectedContactId, expectedEncodedLookup);
+ }
+ }
+}
diff --git a/tests/src/com/android/contacts/ContactsUtilsTests.java b/tests/src/com/android/contacts/ContactsUtilsTests.java
index 8e18044..7e85c32 100644
--- a/tests/src/com/android/contacts/ContactsUtilsTests.java
+++ b/tests/src/com/android/contacts/ContactsUtilsTests.java
@@ -16,6 +16,8 @@
package com.android.contacts;
+import com.android.contacts.ContactsUtils.ImActions;
+
import android.content.ContentValues;
import android.content.Intent;
import android.net.Uri;
@@ -34,24 +36,63 @@
private static final String TEST_PROTOCOL = "prot%col";
public void testImIntent() throws Exception {
- // Normal IM is appended as path
+ // Test GTalk XMPP URI. No chat capabilities provided
final ContentValues values = new ContentValues();
values.put(Im.MIMETYPE, Im.CONTENT_ITEM_TYPE);
values.put(Im.TYPE, Im.TYPE_HOME);
values.put(Im.PROTOCOL, Im.PROTOCOL_GOOGLE_TALK);
values.put(Im.DATA, TEST_ADDRESS);
- final Intent intent = ContactsUtils.buildImIntent(values);
+ final ImActions imActions = ContactsUtils.buildImActions(values);
+ final Intent intent = imActions.getPrimaryIntent();
assertEquals(Intent.ACTION_SENDTO, intent.getAction());
+ assertEquals("xmpp:" + TEST_ADDRESS + "?message", intent.getData().toString());
- final Uri data = intent.getData();
- assertEquals("imto", data.getScheme());
- assertEquals("gtalk", data.getAuthority());
- assertEquals(TEST_ADDRESS, data.getPathSegments().get(0));
+ assertNull(imActions.getSecondaryIntent());
+ }
+
+ public void testImIntentWithAudio() throws Exception {
+ // Test GTalk XMPP URI. Audio chat capabilities provided
+ final ContentValues values = new ContentValues();
+ values.put(Im.MIMETYPE, Im.CONTENT_ITEM_TYPE);
+ values.put(Im.TYPE, Im.TYPE_HOME);
+ values.put(Im.PROTOCOL, Im.PROTOCOL_GOOGLE_TALK);
+ values.put(Im.DATA, TEST_ADDRESS);
+ values.put(Im.CHAT_CAPABILITY, Im.CAPABILITY_HAS_VOICE | Im.CAPABILITY_HAS_VIDEO);
+
+ final ImActions imActions = ContactsUtils.buildImActions(values);
+ final Intent primaryIntent = imActions.getPrimaryIntent();
+ assertEquals(Intent.ACTION_SENDTO, primaryIntent.getAction());
+ assertEquals("xmpp:" + TEST_ADDRESS + "?message", primaryIntent.getData().toString());
+
+ final Intent secondaryIntent = imActions.getSecondaryIntent();
+ assertEquals(Intent.ACTION_SENDTO, secondaryIntent.getAction());
+ assertEquals("xmpp:" + TEST_ADDRESS + "?call", secondaryIntent.getData().toString());
+ }
+
+ public void testImIntentWithVideo() throws Exception {
+ // Test GTalk XMPP URI. Video chat capabilities provided
+ final ContentValues values = new ContentValues();
+ values.put(Im.MIMETYPE, Im.CONTENT_ITEM_TYPE);
+ values.put(Im.TYPE, Im.TYPE_HOME);
+ values.put(Im.PROTOCOL, Im.PROTOCOL_GOOGLE_TALK);
+ values.put(Im.DATA, TEST_ADDRESS);
+ values.put(Im.CHAT_CAPABILITY, Im.CAPABILITY_HAS_VOICE | Im.CAPABILITY_HAS_VIDEO |
+ Im.CAPABILITY_HAS_VOICE);
+
+ final ImActions imActions = ContactsUtils.buildImActions(values);
+ final Intent primaryIntent = imActions.getPrimaryIntent();
+ assertEquals(Intent.ACTION_SENDTO, primaryIntent.getAction());
+ assertEquals("xmpp:" + TEST_ADDRESS + "?message", primaryIntent.getData().toString());
+
+ final Intent secondaryIntent = imActions.getSecondaryIntent();
+ assertEquals(Intent.ACTION_SENDTO, secondaryIntent.getAction());
+ assertEquals("xmpp:" + TEST_ADDRESS + "?call", secondaryIntent.getData().toString());
}
public void testImIntentCustom() throws Exception {
- // Custom IM types have encoded authority
+ // Custom IM types have encoded authority. We send the imto Intent here, because
+ // legacy third party apps might not accept xmpp yet
final ContentValues values = new ContentValues();
values.put(Im.MIMETYPE, Im.CONTENT_ITEM_TYPE);
values.put(Im.TYPE, Im.TYPE_HOME);
@@ -59,29 +100,37 @@
values.put(Im.CUSTOM_PROTOCOL, TEST_PROTOCOL);
values.put(Im.DATA, TEST_ADDRESS);
- final Intent intent = ContactsUtils.buildImIntent(values);
+ final ImActions actions = ContactsUtils.buildImActions(values);
+ final Intent intent = actions.getPrimaryIntent();
assertEquals(Intent.ACTION_SENDTO, intent.getAction());
final Uri data = intent.getData();
assertEquals("imto", data.getScheme());
assertEquals(TEST_PROTOCOL, data.getAuthority());
assertEquals(TEST_ADDRESS, data.getPathSegments().get(0));
+
+ assertNull(actions.getSecondaryIntent());
}
public void testImEmailIntent() throws Exception {
// Email addresses are treated as Google Talk entries
+ // This test only tests the VIDEO+CAMERA case. The other cases have been addressed by the
+ // Im tests
final ContentValues values = new ContentValues();
values.put(Email.MIMETYPE, Email.CONTENT_ITEM_TYPE);
values.put(Email.TYPE, Email.TYPE_HOME);
values.put(Email.DATA, TEST_ADDRESS);
+ values.put(Email.CHAT_CAPABILITY, Im.CAPABILITY_HAS_VOICE | Im.CAPABILITY_HAS_VIDEO |
+ Im.CAPABILITY_HAS_VOICE);
- final Intent intent = ContactsUtils.buildImIntent(values);
- assertEquals(Intent.ACTION_SENDTO, intent.getAction());
+ final ImActions imActions = ContactsUtils.buildImActions(values);
+ final Intent primaryIntent = imActions.getPrimaryIntent();
+ assertEquals(Intent.ACTION_SENDTO, primaryIntent.getAction());
+ assertEquals("xmpp:" + TEST_ADDRESS + "?message", primaryIntent.getData().toString());
- final Uri data = intent.getData();
- assertEquals("imto", data.getScheme());
- assertEquals("gtalk", data.getAuthority());
- assertEquals(TEST_ADDRESS, data.getPathSegments().get(0));
+ final Intent secondaryIntent = imActions.getSecondaryIntent();
+ assertEquals(Intent.ACTION_SENDTO, secondaryIntent.getAction());
+ assertEquals("xmpp:" + TEST_ADDRESS + "?call", secondaryIntent.getData().toString());
}
public void testIsGraphicNull() throws Exception {
diff --git a/tests/src/com/android/contacts/DefaultContactBrowseListFragmentTest.java b/tests/src/com/android/contacts/DefaultContactBrowseListFragmentTest.java
new file mode 100644
index 0000000..8ad3cb9
--- /dev/null
+++ b/tests/src/com/android/contacts/DefaultContactBrowseListFragmentTest.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2009 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;
+
+import com.android.contacts.list.DefaultContactBrowseListFragment;
+import com.android.contacts.tests.mocks.ContactsMockContext;
+import com.android.contacts.tests.mocks.MockContentProvider;
+import com.android.contacts.widget.TestLoaderManager;
+
+import android.database.Cursor;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.ContactCounts;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.ProviderStatus;
+import android.provider.ContactsContract.StatusUpdates;
+import android.provider.Settings;
+import android.test.InstrumentationTestCase;
+import android.test.suitebuilder.annotation.Smoke;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+
+
+/**
+ * Tests for {@link DefaultContactBrowseListFragment}.
+ *
+ * Running all tests:
+ *
+ * runtest contacts
+ * or
+ * adb shell am instrument \
+ * -w com.android.contacts.tests/android.test.InstrumentationTestRunner
+ */
+@Smoke
+public class DefaultContactBrowseListFragmentTest
+ extends InstrumentationTestCase {
+
+ private ContactsMockContext mContext;
+ private MockContentProvider mContactsProvider;
+ private MockContentProvider mSettingsProvider;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mContext = new ContactsMockContext(getInstrumentation().getTargetContext());
+ mContactsProvider = mContext.getContactsProvider();
+ mSettingsProvider = mContext.getSettingsProvider();
+ }
+
+ public void testDefaultMode() throws Exception {
+
+ mSettingsProvider.expectQuery(Settings.System.CONTENT_URI)
+ .withProjection(Settings.System.VALUE)
+ .withSelection(Settings.System.NAME + "=?",
+ ContactsContract.Preferences.DISPLAY_ORDER);
+
+ mSettingsProvider.expectQuery(Settings.System.CONTENT_URI)
+ .withProjection(Settings.System.VALUE)
+ .withSelection(Settings.System.NAME + "=?",
+ ContactsContract.Preferences.SORT_ORDER);
+
+ mContactsProvider.expectQuery(
+ Contacts.CONTENT_URI.buildUpon()
+ .appendQueryParameter(ContactCounts.ADDRESS_BOOK_INDEX_EXTRAS, "true")
+ .build())
+ .withProjection(
+ Contacts._ID,
+ Contacts.DISPLAY_NAME,
+ Contacts.DISPLAY_NAME_ALTERNATIVE,
+ Contacts.SORT_KEY_PRIMARY,
+ Contacts.STARRED,
+ Contacts.CONTACT_PRESENCE,
+ Contacts.PHOTO_ID,
+ Contacts.LOOKUP_KEY,
+ Contacts.PHONETIC_NAME,
+ Contacts.HAS_PHONE_NUMBER)
+ .withSelection(Contacts.IN_VISIBLE_GROUP + "=1")
+ .withSortOrder(Contacts.SORT_KEY_PRIMARY)
+ .returnRow(1, "John", "John", "john", 1,
+ StatusUpdates.AVAILABLE, 23, "lk1", "john", 1)
+ .returnRow(2, "Jim", "Jim", "jim", 1,
+ StatusUpdates.AWAY, 24, "lk2", "jim", 0);
+
+ mContactsProvider.expectQuery(ProviderStatus.CONTENT_URI)
+ .withProjection(ProviderStatus.STATUS, ProviderStatus.DATA1);
+
+ DefaultContactBrowseListFragment fragment = new DefaultContactBrowseListFragment();
+
+ TestLoaderManager loaderManager = new TestLoaderManager();
+
+ // Divert loader registration the TestLoaderManager to ensure that loading is
+ // done synchronously
+ fragment.setLoaderManager(loaderManager);
+
+ // Fragment life cycle
+ fragment.onCreate(null);
+
+ // Instead of attaching the fragment to an activity, "attach" it to the target context
+ // of the instrumentation
+ fragment.setContext(mContext);
+
+ // Fragment life cycle
+ View view = fragment.onCreateView(LayoutInflater.from(mContext), null, null);
+
+ // Fragment life cycle
+ fragment.onStart();
+
+ // All loaders have been registered. Now perform the loading synchronously.
+ loaderManager.executeLoaders();
+
+ // Now we can assert that the data got loaded into the list.
+ ListView listView = (ListView)view.findViewById(android.R.id.list);
+ ListAdapter adapter = listView.getAdapter();
+ assertEquals(3, adapter.getCount()); // It has two items + header view
+
+ // Assert that all queries have been called
+ mSettingsProvider.verify();
+ mContactsProvider.verify();
+ }
+}
diff --git a/tests/src/com/android/contacts/EntitySetTests.java b/tests/src/com/android/contacts/EntityDeltaListTests.java
similarity index 86%
rename from tests/src/com/android/contacts/EntitySetTests.java
rename to tests/src/com/android/contacts/EntityDeltaListTests.java
index edfca6d..efd843f 100644
--- a/tests/src/com/android/contacts/EntitySetTests.java
+++ b/tests/src/com/android/contacts/EntityDeltaListTests.java
@@ -25,7 +25,7 @@
import com.android.contacts.model.ContactsSource;
import com.android.contacts.model.EntityDelta;
import com.android.contacts.model.EntityModifier;
-import com.android.contacts.model.EntitySet;
+import com.android.contacts.model.EntityDeltaList;
import com.android.contacts.model.EntityDelta.ValuesDelta;
import com.google.android.collect.Lists;
@@ -46,12 +46,12 @@
import java.util.ArrayList;
/**
- * Tests for {@link EntitySet} which focus on "diff" operations that should
+ * Tests for {@link EntityDeltaList} which focus on "diff" operations that should
* create {@link AggregationExceptions} in certain cases.
*/
@LargeTest
-public class EntitySetTests extends AndroidTestCase {
- public static final String TAG = "EntitySetTests";
+public class EntityDeltaListTests extends AndroidTestCase {
+ public static final String TAG = "EntityDeltaListTests";
private static final long CONTACT_FIRST = 1;
private static final long CONTACT_SECOND = 2;
@@ -71,7 +71,7 @@
public static final String TEST_PHONE = "555-1212";
public static final String TEST_ACCOUNT = "org.example.test";
- public EntitySetTests() {
+ public EntityDeltaListTests() {
super();
}
@@ -112,8 +112,8 @@
return new EntityDelta(values);
}
- static EntitySet buildSet(EntityDelta... deltas) {
- final EntitySet set = EntitySet.fromSingle(deltas[0]);
+ static EntityDeltaList buildSet(EntityDelta... deltas) {
+ final EntityDeltaList set = EntityDeltaList.fromSingle(deltas[0]);
for (int i = 1; i < deltas.length; i++) {
set.add(deltas[i]);
}
@@ -166,12 +166,12 @@
return values;
}
- static void insertPhone(EntitySet set, long rawContactId, ContentValues values) {
+ static void insertPhone(EntityDeltaList set, long rawContactId, ContentValues values) {
final EntityDelta match = set.getByRawContactId(rawContactId);
match.addEntry(ValuesDelta.fromAfter(values));
}
- static ValuesDelta getPhone(EntitySet set, long rawContactId, long dataId) {
+ static ValuesDelta getPhone(EntityDeltaList set, long rawContactId, long dataId) {
final EntityDelta match = set.getByRawContactId(rawContactId);
return match.getEntry(dataId);
}
@@ -183,7 +183,7 @@
assertDiffPattern(diff, pattern);
}
- static void assertDiffPattern(EntitySet set, ContentProviderOperation... pattern) {
+ static void assertDiffPattern(EntityDeltaList set, ContentProviderOperation... pattern) {
assertDiffPattern(set.buildDiff(), pattern);
}
@@ -283,7 +283,7 @@
return null;
}
- static Long getVersion(EntitySet set, Long rawContactId) {
+ static Long getVersion(EntityDeltaList set, Long rawContactId) {
return set.getByRawContactId(rawContactId).getValues().getAsLong(RawContacts.VERSION);
}
@@ -304,7 +304,7 @@
public void testInsert() {
final EntityDelta insert = getInsert();
- final EntitySet set = buildSet(insert);
+ final EntityDeltaList set = buildSet(insert);
// Inserting single shouldn't create rules
final ArrayList<ContentProviderOperation> diff = set.buildDiff();
@@ -315,7 +315,7 @@
public void testUpdateUpdate() {
final EntityDelta updateFirst = getUpdate(CONTACT_FIRST);
final EntityDelta updateSecond = getUpdate(CONTACT_SECOND);
- final EntitySet set = buildSet(updateFirst, updateSecond);
+ final EntityDeltaList set = buildSet(updateFirst, updateSecond);
// Updating two existing shouldn't create rules
final ArrayList<ContentProviderOperation> diff = set.buildDiff();
@@ -326,7 +326,7 @@
public void testUpdateInsert() {
final EntityDelta update = getUpdate(CONTACT_FIRST);
final EntityDelta insert = getInsert();
- final EntitySet set = buildSet(update, insert);
+ final EntityDeltaList set = buildSet(update, insert);
// New insert should only create one rule
final ArrayList<ContentProviderOperation> diff = set.buildDiff();
@@ -338,7 +338,7 @@
final EntityDelta insertFirst = getInsert();
final EntityDelta update = getUpdate(CONTACT_FIRST);
final EntityDelta insertSecond = getInsert();
- final EntitySet set = buildSet(insertFirst, update, insertSecond);
+ final EntityDeltaList set = buildSet(insertFirst, update, insertSecond);
// Two inserts should create two rules to bind against single existing
final ArrayList<ContentProviderOperation> diff = set.buildDiff();
@@ -350,7 +350,7 @@
final EntityDelta insertFirst = getInsert();
final EntityDelta insertSecond = getInsert();
final EntityDelta insertThird = getInsert();
- final EntitySet set = buildSet(insertFirst, insertSecond, insertThird);
+ final EntityDeltaList set = buildSet(insertFirst, insertSecond, insertThird);
// Three new inserts should create only two binding rules
final ArrayList<ContentProviderOperation> diff = set.buildDiff();
@@ -359,20 +359,20 @@
}
public void testMergeDataRemoteInsert() {
- final EntitySet first = buildSet(buildBeforeEntity(CONTACT_BOB, VER_FIRST,
+ final EntityDeltaList first = buildSet(buildBeforeEntity(CONTACT_BOB, VER_FIRST,
buildPhone(PHONE_RED)));
- final EntitySet second = buildSet(buildBeforeEntity(CONTACT_BOB, VER_SECOND,
+ final EntityDeltaList second = buildSet(buildBeforeEntity(CONTACT_BOB, VER_SECOND,
buildPhone(PHONE_RED), buildPhone(PHONE_GREEN)));
// Merge in second version, verify they match
- final EntitySet merged = EntitySet.mergeAfter(second, first);
+ final EntityDeltaList merged = EntityDeltaList.mergeAfter(second, first);
assertEquals("Unexpected change when merging", second, merged);
}
public void testMergeDataLocalUpdateRemoteInsert() {
- final EntitySet first = buildSet(buildBeforeEntity(CONTACT_BOB, VER_FIRST,
+ final EntityDeltaList first = buildSet(buildBeforeEntity(CONTACT_BOB, VER_FIRST,
buildPhone(PHONE_RED)));
- final EntitySet second = buildSet(buildBeforeEntity(CONTACT_BOB, VER_SECOND,
+ final EntityDeltaList second = buildSet(buildBeforeEntity(CONTACT_BOB, VER_SECOND,
buildPhone(PHONE_RED), buildPhone(PHONE_GREEN)));
// Change the local number to trigger update
@@ -386,7 +386,7 @@
buildUpdateAggregationDefault());
// Merge in the second version, verify diff matches
- final EntitySet merged = EntitySet.mergeAfter(second, first);
+ final EntityDeltaList merged = EntityDeltaList.mergeAfter(second, first);
assertDiffPattern(merged,
buildAssertVersion(VER_SECOND),
buildUpdateAggregationSuspended(),
@@ -395,9 +395,9 @@
}
public void testMergeDataLocalUpdateRemoteDelete() {
- final EntitySet first = buildSet(buildBeforeEntity(CONTACT_BOB, VER_FIRST,
+ final EntityDeltaList first = buildSet(buildBeforeEntity(CONTACT_BOB, VER_FIRST,
buildPhone(PHONE_RED)));
- final EntitySet second = buildSet(buildBeforeEntity(CONTACT_BOB, VER_SECOND,
+ final EntityDeltaList second = buildSet(buildBeforeEntity(CONTACT_BOB, VER_SECOND,
buildPhone(PHONE_GREEN)));
// Change the local number to trigger update
@@ -412,7 +412,7 @@
// Merge in the second version, verify that our update changed to
// insert, since RED was deleted on remote side
- final EntitySet merged = EntitySet.mergeAfter(second, first);
+ final EntityDeltaList merged = EntityDeltaList.mergeAfter(second, first);
assertDiffPattern(merged,
buildAssertVersion(VER_SECOND),
buildUpdateAggregationSuspended(),
@@ -421,9 +421,9 @@
}
public void testMergeDataLocalDeleteRemoteUpdate() {
- final EntitySet first = buildSet(buildBeforeEntity(CONTACT_BOB, VER_FIRST,
+ final EntityDeltaList first = buildSet(buildBeforeEntity(CONTACT_BOB, VER_FIRST,
buildPhone(PHONE_RED)));
- final EntitySet second = buildSet(buildBeforeEntity(CONTACT_BOB, VER_SECOND,
+ final EntityDeltaList second = buildSet(buildBeforeEntity(CONTACT_BOB, VER_SECOND,
buildPhone(PHONE_RED, TEST_PHONE)));
// Delete phone locally
@@ -437,7 +437,7 @@
buildUpdateAggregationDefault());
// Merge in the second version, verify that our delete remains
- final EntitySet merged = EntitySet.mergeAfter(second, first);
+ final EntityDeltaList merged = EntityDeltaList.mergeAfter(second, first);
assertDiffPattern(merged,
buildAssertVersion(VER_SECOND),
buildUpdateAggregationSuspended(),
@@ -446,9 +446,9 @@
}
public void testMergeDataLocalInsertRemoteInsert() {
- final EntitySet first = buildSet(buildBeforeEntity(CONTACT_BOB, VER_FIRST,
+ final EntityDeltaList first = buildSet(buildBeforeEntity(CONTACT_BOB, VER_FIRST,
buildPhone(PHONE_RED)));
- final EntitySet second = buildSet(buildBeforeEntity(CONTACT_BOB, VER_SECOND,
+ final EntityDeltaList second = buildSet(buildBeforeEntity(CONTACT_BOB, VER_SECOND,
buildPhone(PHONE_RED), buildPhone(PHONE_GREEN)));
// Insert new phone locally
@@ -461,7 +461,7 @@
buildUpdateAggregationDefault());
// Merge in the second version, verify that our insert remains
- final EntitySet merged = EntitySet.mergeAfter(second, first);
+ final EntityDeltaList merged = EntityDeltaList.mergeAfter(second, first);
assertDiffPattern(merged,
buildAssertVersion(VER_SECOND),
buildUpdateAggregationSuspended(),
@@ -470,9 +470,9 @@
}
public void testMergeRawContactLocalInsertRemoteInsert() {
- final EntitySet first = buildSet(buildBeforeEntity(CONTACT_BOB, VER_FIRST,
+ final EntityDeltaList first = buildSet(buildBeforeEntity(CONTACT_BOB, VER_FIRST,
buildPhone(PHONE_RED)));
- final EntitySet second = buildSet(buildBeforeEntity(CONTACT_BOB, VER_SECOND,
+ final EntityDeltaList second = buildSet(buildBeforeEntity(CONTACT_BOB, VER_SECOND,
buildPhone(PHONE_RED)), buildBeforeEntity(CONTACT_MARY, VER_SECOND,
buildPhone(PHONE_RED)));
@@ -490,7 +490,7 @@
buildUpdateAggregationKeepTogether(CONTACT_BOB));
// Merge in the second version, verify that our insert remains
- final EntitySet merged = EntitySet.mergeAfter(second, first);
+ final EntityDeltaList merged = EntityDeltaList.mergeAfter(second, first);
assertDiffPattern(merged,
buildAssertVersion(VER_SECOND),
buildAssertVersion(VER_SECOND),
@@ -501,10 +501,10 @@
}
public void testMergeRawContactLocalDeleteRemoteDelete() {
- final EntitySet first = buildSet(
+ final EntityDeltaList first = buildSet(
buildBeforeEntity(CONTACT_BOB, VER_FIRST, buildPhone(PHONE_RED)),
buildBeforeEntity(CONTACT_MARY, VER_FIRST, buildPhone(PHONE_RED)));
- final EntitySet second = buildSet(
+ final EntityDeltaList second = buildSet(
buildBeforeEntity(CONTACT_BOB, VER_SECOND, buildPhone(PHONE_RED)));
// Remove contact locally
@@ -515,15 +515,15 @@
buildDelete(RawContacts.CONTENT_URI));
// Merge in the second version, verify that our delete isn't needed
- final EntitySet merged = EntitySet.mergeAfter(second, first);
+ final EntityDeltaList merged = EntityDeltaList.mergeAfter(second, first);
assertDiffPattern(merged);
}
public void testMergeRawContactLocalUpdateRemoteDelete() {
- final EntitySet first = buildSet(
+ final EntityDeltaList first = buildSet(
buildBeforeEntity(CONTACT_BOB, VER_FIRST, buildPhone(PHONE_RED)),
buildBeforeEntity(CONTACT_MARY, VER_FIRST, buildPhone(PHONE_RED)));
- final EntitySet second = buildSet(
+ final EntityDeltaList second = buildSet(
buildBeforeEntity(CONTACT_BOB, VER_SECOND, buildPhone(PHONE_RED)));
// Perform local update
@@ -542,7 +542,7 @@
contactInsert.put(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_SUSPENDED);
// Merge and verify that update turned into insert
- final EntitySet merged = EntitySet.mergeAfter(second, first);
+ final EntityDeltaList merged = EntityDeltaList.mergeAfter(second, first);
assertDiffPattern(merged,
buildAssertVersion(VER_SECOND),
buildOper(RawContacts.CONTENT_URI, TYPE_INSERT, contactInsert),
@@ -552,22 +552,22 @@
}
public void testMergeUsesNewVersion() {
- final EntitySet first = buildSet(buildBeforeEntity(CONTACT_BOB, VER_FIRST,
+ final EntityDeltaList first = buildSet(buildBeforeEntity(CONTACT_BOB, VER_FIRST,
buildPhone(PHONE_RED)));
- final EntitySet second = buildSet(buildBeforeEntity(CONTACT_BOB, VER_SECOND,
+ final EntityDeltaList second = buildSet(buildBeforeEntity(CONTACT_BOB, VER_SECOND,
buildPhone(PHONE_RED)));
assertEquals((Long)VER_FIRST, getVersion(first, CONTACT_BOB));
assertEquals((Long)VER_SECOND, getVersion(second, CONTACT_BOB));
- final EntitySet merged = EntitySet.mergeAfter(second, first);
+ final EntityDeltaList merged = EntityDeltaList.mergeAfter(second, first);
assertEquals((Long)VER_SECOND, getVersion(merged, CONTACT_BOB));
}
public void testMergeAfterEnsureAndTrim() {
- final EntitySet first = buildSet(buildBeforeEntity(CONTACT_BOB, VER_FIRST,
+ final EntityDeltaList first = buildSet(buildBeforeEntity(CONTACT_BOB, VER_FIRST,
buildEmail(EMAIL_YELLOW)));
- final EntitySet second = buildSet(buildBeforeEntity(CONTACT_BOB, VER_SECOND,
+ final EntityDeltaList second = buildSet(buildBeforeEntity(CONTACT_BOB, VER_SECOND,
buildEmail(EMAIL_YELLOW)));
// Ensure we have at least one phone
@@ -588,7 +588,7 @@
assertDiffPattern(first);
// Now re-parent the change, which should remain no-op
- final EntitySet merged = EntitySet.mergeAfter(second, first);
+ final EntityDeltaList merged = EntityDeltaList.mergeAfter(second, first);
assertDiffPattern(merged);
}
}
diff --git a/tests/src/com/android/contacts/EntityModifierTests.java b/tests/src/com/android/contacts/EntityModifierTests.java
index 18877a3..af36815 100644
--- a/tests/src/com/android/contacts/EntityModifierTests.java
+++ b/tests/src/com/android/contacts/EntityModifierTests.java
@@ -23,7 +23,7 @@
import com.android.contacts.model.ContactsSource;
import com.android.contacts.model.EntityDelta;
import com.android.contacts.model.EntityModifier;
-import com.android.contacts.model.EntitySet;
+import com.android.contacts.model.EntityDeltaList;
import com.android.contacts.model.Sources;
import com.android.contacts.model.ContactsSource.DataKind;
import com.android.contacts.model.ContactsSource.EditType;
@@ -376,23 +376,23 @@
final EditType typeHome = EntityModifier.getType(kindPhone, Phone.TYPE_HOME);
// Test row that has type values, but values are spaces
- final EntityDelta state = EntitySetTests.buildBeforeEntity(TEST_ID, VER_FIRST);
+ final EntityDelta state = EntityDeltaListTests.buildBeforeEntity(TEST_ID, VER_FIRST);
final ValuesDelta values = EntityModifier.insertChild(state, kindPhone, typeHome);
values.put(Phone.NUMBER, " ");
// Build diff, expecting insert for data row and update enforcement
- EntitySetTests.assertDiffPattern(state,
- EntitySetTests.buildAssertVersion(VER_FIRST),
- EntitySetTests.buildUpdateAggregationSuspended(),
- EntitySetTests.buildOper(Data.CONTENT_URI, TYPE_INSERT,
- EntitySetTests.buildDataInsert(values, TEST_ID)),
- EntitySetTests.buildUpdateAggregationDefault());
+ EntityDeltaListTests.assertDiffPattern(state,
+ EntityDeltaListTests.buildAssertVersion(VER_FIRST),
+ EntityDeltaListTests.buildUpdateAggregationSuspended(),
+ EntityDeltaListTests.buildOper(Data.CONTENT_URI, TYPE_INSERT,
+ EntityDeltaListTests.buildDataInsert(values, TEST_ID)),
+ EntityDeltaListTests.buildUpdateAggregationDefault());
// Trim empty rows and try again, expecting delete of overall contact
EntityModifier.trimEmpty(state, source);
- EntitySetTests.assertDiffPattern(state,
- EntitySetTests.buildAssertVersion(VER_FIRST),
- EntitySetTests.buildDelete(RawContacts.CONTENT_URI));
+ EntityDeltaListTests.assertDiffPattern(state,
+ EntityDeltaListTests.buildAssertVersion(VER_FIRST),
+ EntityDeltaListTests.buildDelete(RawContacts.CONTENT_URI));
}
public void testTrimLeaveValid() {
@@ -401,26 +401,26 @@
final EditType typeHome = EntityModifier.getType(kindPhone, Phone.TYPE_HOME);
// Test row that has type values with valid number
- final EntityDelta state = EntitySetTests.buildBeforeEntity(TEST_ID, VER_FIRST);
+ final EntityDelta state = EntityDeltaListTests.buildBeforeEntity(TEST_ID, VER_FIRST);
final ValuesDelta values = EntityModifier.insertChild(state, kindPhone, typeHome);
values.put(Phone.NUMBER, TEST_PHONE);
// Build diff, expecting insert for data row and update enforcement
- EntitySetTests.assertDiffPattern(state,
- EntitySetTests.buildAssertVersion(VER_FIRST),
- EntitySetTests.buildUpdateAggregationSuspended(),
- EntitySetTests.buildOper(Data.CONTENT_URI, TYPE_INSERT,
- EntitySetTests.buildDataInsert(values, TEST_ID)),
- EntitySetTests.buildUpdateAggregationDefault());
+ EntityDeltaListTests.assertDiffPattern(state,
+ EntityDeltaListTests.buildAssertVersion(VER_FIRST),
+ EntityDeltaListTests.buildUpdateAggregationSuspended(),
+ EntityDeltaListTests.buildOper(Data.CONTENT_URI, TYPE_INSERT,
+ EntityDeltaListTests.buildDataInsert(values, TEST_ID)),
+ EntityDeltaListTests.buildUpdateAggregationDefault());
// Trim empty rows and try again, expecting no differences
EntityModifier.trimEmpty(state, source);
- EntitySetTests.assertDiffPattern(state,
- EntitySetTests.buildAssertVersion(VER_FIRST),
- EntitySetTests.buildUpdateAggregationSuspended(),
- EntitySetTests.buildOper(Data.CONTENT_URI, TYPE_INSERT,
- EntitySetTests.buildDataInsert(values, TEST_ID)),
- EntitySetTests.buildUpdateAggregationDefault());
+ EntityDeltaListTests.assertDiffPattern(state,
+ EntityDeltaListTests.buildAssertVersion(VER_FIRST),
+ EntityDeltaListTests.buildUpdateAggregationSuspended(),
+ EntityDeltaListTests.buildOper(Data.CONTENT_URI, TYPE_INSERT,
+ EntityDeltaListTests.buildDataInsert(values, TEST_ID)),
+ EntityDeltaListTests.buildUpdateAggregationDefault());
}
public void testTrimEmptyUntouched() {
@@ -507,7 +507,7 @@
// Try creating a contact without any child entries
final EntityDelta state = getEntity(null);
- final EntitySet set = EntitySet.fromSingle(state);
+ final EntityDeltaList set = EntityDeltaList.fromSingle(state);
// Build diff, expecting single insert
final ArrayList<ContentProviderOperation> diff = Lists.newArrayList();
@@ -535,7 +535,7 @@
// Try creating a contact with single empty entry
final EntityDelta state = getEntity(null);
final ValuesDelta values = EntityModifier.insertChild(state, kindPhone, typeHome);
- final EntitySet set = EntitySet.fromSingle(state);
+ final EntityDeltaList set = EntityDeltaList.fromSingle(state);
// Build diff, expecting two insert operations
final ArrayList<ContentProviderOperation> diff = Lists.newArrayList();
@@ -579,7 +579,7 @@
second.put(Phone.NUMBER, TEST_PHONE);
final EntityDelta state = getEntity(TEST_ID, first, second);
- final EntitySet set = EntitySet.fromSingle(state);
+ final EntityDeltaList set = EntityDeltaList.fromSingle(state);
// Build diff, expecting no changes
final ArrayList<ContentProviderOperation> diff = Lists.newArrayList();
@@ -644,7 +644,7 @@
first.put(Phone.NUMBER, TEST_PHONE);
final EntityDelta state = getEntity(TEST_ID, first);
- final EntitySet set = EntitySet.fromSingle(state);
+ final EntityDeltaList set = EntityDeltaList.fromSingle(state);
// Build diff, expecting no changes
final ArrayList<ContentProviderOperation> diff = Lists.newArrayList();
diff --git a/tests/src/com/android/contacts/interactions/ContactDeletionInteractionTest.java b/tests/src/com/android/contacts/interactions/ContactDeletionInteractionTest.java
new file mode 100644
index 0000000..b2ca81d
--- /dev/null
+++ b/tests/src/com/android/contacts/interactions/ContactDeletionInteractionTest.java
@@ -0,0 +1,123 @@
+/*
+ * 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 com.android.contacts.R;
+import com.android.contacts.model.Sources;
+import com.android.contacts.tests.mocks.ContactsMockContext;
+import com.android.contacts.tests.mocks.MockContentProvider;
+import com.android.contacts.tests.mocks.MockContentProvider.Query;
+import com.android.contacts.tests.mocks.MockSources;
+
+import android.content.AsyncTaskLoader;
+import android.content.ContentUris;
+import android.content.Loader;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.RawContacts;
+import android.test.InstrumentationTestCase;
+import android.test.suitebuilder.annotation.Smoke;
+
+/**
+ * Tests for {@link ContactDeletionInteraction}.
+ *
+ * Running all tests:
+ *
+ * runtest contacts
+ * or
+ * adb shell am instrument \
+ * -w com.android.contacts.tests/android.test.InstrumentationTestRunner
+ */
+@Smoke
+public class ContactDeletionInteractionTest extends InstrumentationTestCase {
+
+ private final static class TestContactDeletionInteraction extends ContactDeletionInteraction {
+ public Uri contactUri;
+ public int messageId;
+
+ @Override
+ void startLoading(Loader<Cursor> loader) {
+ // Execute the loader synchronously
+ AsyncTaskLoader<Cursor> atLoader = (AsyncTaskLoader<Cursor>)loader;
+ Cursor data = atLoader.loadInBackground();
+ atLoader.deliverResult(data);
+ }
+
+ @Override
+ void showDialog(Bundle bundle) {
+ contactUri = bundle.getParcelable(EXTRA_KEY_CONTACT_URI);
+ messageId = bundle.getInt(EXTRA_KEY_MESSAGE_ID);
+ }
+
+ @Override
+ Sources getSources() {
+ return new MockSources();
+ }
+ }
+
+ private ContactsMockContext mContext;
+ private MockContentProvider mContactsProvider;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mContext = new ContactsMockContext(getInstrumentation().getTargetContext());
+ mContactsProvider = mContext.getContactsProvider();
+ }
+
+ public void testSingleWritableRawContact() {
+ expectQuery().returnRow(1, MockSources.WRITABLE_ACCOUNT_TYPE);
+ assertWithMessageId(R.string.deleteConfirmation);
+ }
+
+ public void testReadOnlyRawContacts() {
+ expectQuery().returnRow(1, MockSources.READONLY_ACCOUNT_TYPE);
+ assertWithMessageId(R.string.readOnlyContactWarning);
+ }
+
+ public void testMixOfWritableAndReadOnlyRawContacts() {
+ expectQuery()
+ .returnRow(1, MockSources.WRITABLE_ACCOUNT_TYPE)
+ .returnRow(2, MockSources.READONLY_ACCOUNT_TYPE);
+ assertWithMessageId(R.string.readOnlyContactDeleteConfirmation);
+ }
+
+ public void testMultipleWritableRawContacts() {
+ expectQuery()
+ .returnRow(1, MockSources.WRITABLE_ACCOUNT_TYPE)
+ .returnRow(2, MockSources.WRITABLE_ACCOUNT_TYPE);
+ assertWithMessageId(R.string.multipleContactDeleteConfirmation);
+ }
+
+ private Query expectQuery() {
+ return mContactsProvider.expectQuery(RawContacts.CONTENT_URI)
+ .withProjection(RawContacts._ID, RawContacts.ACCOUNT_TYPE)
+ .withSelection("contact_id=?", "13");
+ }
+
+ private void assertWithMessageId(int messageId) {
+ TestContactDeletionInteraction interaction = new TestContactDeletionInteraction();
+ interaction.setContext(mContext);
+ interaction.deleteContact(ContentUris.withAppendedId(Contacts.CONTENT_URI, 13));
+ assertEquals("content://com.android.contacts/contacts/13",
+ interaction.contactUri.toString());
+ assertEquals(messageId, interaction.messageId);
+ mContactsProvider.verify();
+ }
+}
diff --git a/tests/src/com/android/contacts/interactions/PhoneNumberInteractionTest.java b/tests/src/com/android/contacts/interactions/PhoneNumberInteractionTest.java
new file mode 100644
index 0000000..8c6a1ac
--- /dev/null
+++ b/tests/src/com/android/contacts/interactions/PhoneNumberInteractionTest.java
@@ -0,0 +1,184 @@
+/*
+ * 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 com.android.contacts.R;
+import com.android.contacts.interactions.PhoneNumberInteraction.PhoneItem;
+import com.android.contacts.tests.mocks.ContactsMockContext;
+import com.android.contacts.tests.mocks.MockContentProvider;
+import com.android.contacts.tests.mocks.MockContentProvider.Query;
+
+import android.content.AsyncTaskLoader;
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.DialogInterface.OnDismissListener;
+import android.content.Intent;
+import android.content.Loader;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.RawContacts;
+import android.test.InstrumentationTestCase;
+import android.test.suitebuilder.annotation.Smoke;
+
+import java.util.ArrayList;
+
+/**
+ * Tests for {@link PhoneNumberInteraction}.
+ *
+ * Running all tests:
+ *
+ * runtest contacts
+ * or
+ * adb shell am instrument \
+ * -w com.android.contacts.tests/android.test.InstrumentationTestRunner
+ */
+@Smoke
+public class PhoneNumberInteractionTest extends InstrumentationTestCase {
+
+ private final static class TestPhoneNumberInteraction extends PhoneNumberInteraction {
+ Intent startedIntent;
+ int dialogId;
+ Bundle dialogArgs;
+
+ public TestPhoneNumberInteraction(
+ Context context, boolean sendTextMessage, OnDismissListener dismissListener) {
+ super(context, sendTextMessage, dismissListener);
+ }
+
+ @Override
+ void startLoading(Loader<Cursor> loader) {
+ // Execute the loader synchronously
+ AsyncTaskLoader<Cursor> atLoader = (AsyncTaskLoader<Cursor>)loader;
+ Cursor data = atLoader.loadInBackground();
+ atLoader.deliverResult(data);
+ }
+
+ @Override
+ void startActivity(Intent intent) {
+ this.startedIntent = intent;
+ }
+
+ @Override
+ void showDialog(int dialogId, Bundle bundle) {
+ this.dialogId = dialogId;
+ this.dialogArgs = bundle;
+ }
+ }
+
+ private ContactsMockContext mContext;
+ private MockContentProvider mContactsProvider;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mContext = new ContactsMockContext(getInstrumentation().getTargetContext());
+ mContactsProvider = mContext.getContactsProvider();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ mContactsProvider.verify();
+ super.tearDown();
+ }
+
+ public void testSendSmsWhenOnlyOneNumberAvailable() {
+ Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, 13);
+ expectQuery(contactUri)
+ .returnRow(1, "123", 0, null, Phone.TYPE_HOME, null);
+
+ TestPhoneNumberInteraction interaction = new TestPhoneNumberInteraction(
+ mContext, true, null);
+
+ interaction.startInteraction(contactUri);
+
+ assertEquals(Intent.ACTION_SENDTO, interaction.startedIntent.getAction());
+ assertEquals("sms:123", interaction.startedIntent.getDataString());
+ }
+
+ public void testSendSmsWhenThereIsPrimaryNumber() {
+ Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, 13);
+ expectQuery(contactUri)
+ .returnRow(1, "123", 0, null, Phone.TYPE_HOME, null)
+ .returnRow(2, "456", 1, null, Phone.TYPE_HOME, null);
+
+ TestPhoneNumberInteraction interaction = new TestPhoneNumberInteraction(
+ mContext, true, null);
+
+ interaction.startInteraction(contactUri);
+
+ assertEquals(Intent.ACTION_SENDTO, interaction.startedIntent.getAction());
+ assertEquals("sms:456", interaction.startedIntent.getDataString());
+ }
+
+ public void testCallNumberWhenThereAreDuplicates() {
+ Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, 13);
+ expectQuery(contactUri)
+ .returnRow(1, "123", 0, null, Phone.TYPE_HOME, null)
+ .returnRow(2, "123", 0, null, Phone.TYPE_WORK, null);
+
+ TestPhoneNumberInteraction interaction = new TestPhoneNumberInteraction(
+ mContext, false, null);
+
+ interaction.startInteraction(contactUri);
+
+ assertEquals(Intent.ACTION_CALL_PRIVILEGED, interaction.startedIntent.getAction());
+ assertEquals("tel:123", interaction.startedIntent.getDataString());
+ }
+
+ public void testShowDisambigDialogForCalling() {
+ Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, 13);
+ expectQuery(contactUri)
+ .returnRow(1, "123", 0, "account", Phone.TYPE_HOME, "label")
+ .returnRow(2, "456", 0, null, Phone.TYPE_WORK, null);
+
+ TestPhoneNumberInteraction interaction = new TestPhoneNumberInteraction(
+ mContext, false, null);
+
+ interaction.startInteraction(contactUri);
+
+ assertEquals(R.id.dialog_phone_number_call_disambiguation, interaction.dialogId);
+
+ ArrayList<PhoneItem> items = interaction.dialogArgs.getParcelableArrayList(
+ PhoneNumberInteraction.EXTRA_KEY_ITEMS);
+ assertEquals(2, items.size());
+
+ PhoneItem item = items.get(0);
+ assertEquals(1, item.id);
+ assertEquals("123", item.phoneNumber);
+ assertEquals("account", item.accountType);
+ assertEquals(Phone.TYPE_HOME, item.type);
+ assertEquals("label", item.label);
+ }
+
+ private Query expectQuery(Uri contactUri) {
+ Uri dataUri = Uri.withAppendedPath(contactUri, Contacts.Data.CONTENT_DIRECTORY);
+ return mContactsProvider
+ .expectQuery(dataUri)
+ .withProjection(
+ Phone._ID,
+ Phone.NUMBER,
+ Phone.IS_SUPER_PRIMARY,
+ RawContacts.ACCOUNT_TYPE,
+ Phone.TYPE,
+ Phone.LABEL)
+ .withSelection("mimetype='vnd.android.cursor.item/phone_v2' AND data1 NOT NULL");
+ }
+}
diff --git a/tests/src/com/android/contacts/tests/allintents/AllIntentsActivity.java b/tests/src/com/android/contacts/tests/allintents/AllIntentsActivity.java
new file mode 100644
index 0000000..94f2aad
--- /dev/null
+++ b/tests/src/com/android/contacts/tests/allintents/AllIntentsActivity.java
@@ -0,0 +1,555 @@
+/*
+ * 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.tests.allintents;
+
+import com.android.contacts.ContactsSearchManager;
+import com.android.contacts.list.ContactsRequest;
+import com.android.contacts.tests.R;
+
+import android.app.ListActivity;
+import android.app.SearchManager;
+import android.content.ComponentName;
+import android.content.ContentUris;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.Contacts.ContactMethods;
+import android.provider.Contacts.People;
+import android.provider.Contacts.Phones;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Intents;
+import android.provider.ContactsContract.Intents.Insert;
+import android.provider.ContactsContract.Intents.UI;
+import android.provider.ContactsContract.RawContacts;
+import android.view.View;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+import android.widget.Toast;
+
+/**
+ * An activity that provides access to various modes of the contacts application.
+ * Useful for manual and scripted tests.
+ */
+@SuppressWarnings("deprecation")
+public class AllIntentsActivity extends ListActivity {
+
+ private static final String ANDROID_CONTACTS_PACKAGE = "com.android.contacts";
+
+ private static final String CONTACT_LIST_ACTIVITY_CLASS_NAME =
+ "com.android.contacts.activities.ContactListActivity";
+ private static final String SEARCH_RESULTS_ACTIVITY_CLASS_NAME =
+ "com.android.contacts.SearchResultsActivity";
+ private static final String MULTIPLE_PHONE_PICKER_ACTIVITY_CLASS_NAME =
+ "com.android.contacts.MultiplePhonePickerActivity";
+
+ private static final int LIST_DEFAULT = 0;
+ private static final int LIST_ALL_CONTACTS_ACTION = 1;
+ private static final int LIST_CONTACTS_WITH_PHONES_ACTION = 2;
+ private static final int LIST_STARRED_ACTION = 3;
+ private static final int LIST_STARRED_ACTION_WITH_FILTER = 4;
+ private static final int LIST_FREQUENT_ACTION = 5;
+ private static final int LIST_FREQUENT_ACTION_WITH_FILTER = 6;
+ private static final int LIST_STREQUENT_ACTION = 7;
+ private static final int LIST_STREQUENT_ACTION_WITH_FILTER = 8;
+ private static final int ACTION_PICK_CONTACT = 9;
+ private static final int ACTION_PICK_CONTACT_LEGACY = 10;
+ private static final int ACTION_PICK_PHONE = 11;
+ private static final int ACTION_PICK_PHONE_LEGACY = 12;
+ private static final int ACTION_PICK_POSTAL = 13;
+ private static final int ACTION_PICK_POSTAL_LEGACY = 14;
+ private static final int ACTION_CREATE_SHORTCUT_CONTACT = 15;
+ private static final int ACTION_CREATE_SHORTCUT_CONTACT_FILTER = 16;
+ private static final int ACTION_CREATE_SHORTCUT_DIAL = 17;
+ private static final int ACTION_CREATE_SHORTCUT_DIAL_FILTER = 18;
+ private static final int ACTION_CREATE_SHORTCUT_MESSAGE = 19;
+ private static final int ACTION_CREATE_SHORTCUT_MESSAGE_FILTER = 20;
+ private static final int ACTION_GET_CONTENT_CONTACT = 21;
+ private static final int ACTION_GET_CONTENT_CONTACT_FILTER = 22;
+ private static final int ACTION_GET_CONTENT_CONTACT_LEGACY = 23;
+ private static final int ACTION_GET_CONTENT_CONTACT_FILTER_LEGACY = 24;
+ private static final int ACTION_GET_CONTENT_PHONE = 25;
+ private static final int ACTION_GET_CONTENT_PHONE_FILTER = 26;
+ private static final int ACTION_GET_CONTENT_PHONE_LEGACY = 27;
+ private static final int ACTION_GET_CONTENT_POSTAL = 28;
+ private static final int ACTION_GET_CONTENT_POSTAL_FILTER = 29;
+ private static final int ACTION_GET_CONTENT_POSTAL_LEGACY = 30;
+ private static final int ACTION_INSERT_OR_EDIT = 31;
+ private static final int ACTION_SEARCH_CALL = 32;
+ private static final int ACTION_SEARCH_CONTACT = 33;
+ private static final int ACTION_SEARCH_EMAIL = 34;
+ private static final int ACTION_SEARCH_PHONE = 35;
+ private static final int SEARCH_SUGGESTION_CLICKED_CALL_BUTTON = 36;
+ private static final int SEARCH_SUGGESTION_CLICKED_CONTACT = 37;
+ private static final int SEARCH_SUGGESTION_DIAL_NUMBER_CLICKED = 38;
+ private static final int SEARCH_SUGGESTION_CREATE_CONTACT_CLICKED = 39;
+ private static final int JOIN_CONTACT = 40;
+ private static final int ACTION_GET_MULTIPLE_PHONES = 41;
+
+ private static final int EDIT_CONTACT = 42;
+ private static final int EDIT_CONTACT_LOOKUP = 43;
+ private static final int EDIT_CONTACT_LOOKUP_ID = 44;
+ private static final int EDIT_RAW_CONTACT = 45;
+ private static final int EDIT_LEGACY = 46;
+ private static final int EDIT_NEW_CONTACT = 47;
+ private static final int EDIT_NEW_RAW_CONTACT = 48;
+ private static final int EDIT_NEW_LEGACY = 49;
+
+ private static final int VIEW_CONTACT = 50;
+ private static final int VIEW_CONTACT_LOOKUP = 51;
+ private static final int VIEW_CONTACT_LOOKUP_ID = 52;
+ private static final int VIEW_RAW_CONTACT = 53;
+ private static final int VIEW_LEGACY = 54;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setListAdapter(new ArrayAdapter<String>(this, R.layout.intent_list_item,
+ getResources().getStringArray(R.array.allIntents)));
+ }
+
+ @Override
+ protected void onListItemClick(ListView l, View v, int position, long id) {
+ super.onListItemClick(l, v, position, id);
+
+ switch (position) {
+ case LIST_DEFAULT: {
+ startContactListActivity(
+ new Intent(Intent.ACTION_VIEW, Contacts.CONTENT_URI));
+ break;
+ }
+ case LIST_ALL_CONTACTS_ACTION: {
+ startContactListActivity(
+ new Intent(UI.LIST_ALL_CONTACTS_ACTION, Contacts.CONTENT_URI));
+ break;
+ }
+ case LIST_CONTACTS_WITH_PHONES_ACTION: {
+ startContactListActivity(
+ new Intent(UI.LIST_CONTACTS_WITH_PHONES_ACTION, Contacts.CONTENT_URI));
+ break;
+ }
+ case LIST_STARRED_ACTION: {
+ startContactListActivity(
+ new Intent(UI.LIST_STARRED_ACTION, Contacts.CONTENT_URI));
+ break;
+ }
+ case LIST_STARRED_ACTION_WITH_FILTER: {
+ startContactListActivity(buildFilterIntent(ContactsRequest.ACTION_STARRED, false));
+ break;
+ }
+ case LIST_FREQUENT_ACTION: {
+ startContactListActivity(
+ new Intent(UI.LIST_FREQUENT_ACTION, Contacts.CONTENT_URI));
+ break;
+ }
+ case LIST_FREQUENT_ACTION_WITH_FILTER: {
+ startContactListActivity(
+ buildFilterIntent(ContactsRequest.ACTION_FREQUENT, false));
+ break;
+ }
+ case LIST_STREQUENT_ACTION: {
+ startContactListActivity(
+ new Intent(UI.LIST_STREQUENT_ACTION, Contacts.CONTENT_URI));
+ break;
+ }
+ case LIST_STREQUENT_ACTION_WITH_FILTER: {
+ startContactListActivity(
+ buildFilterIntent(ContactsRequest.ACTION_STREQUENT, false));
+ break;
+ }
+ case ACTION_PICK_CONTACT: {
+ startContactSelectionActivityForResult(
+ new Intent(Intent.ACTION_PICK, Contacts.CONTENT_URI));
+ break;
+ }
+ case ACTION_PICK_CONTACT_LEGACY: {
+ startContactSelectionActivityForResult(
+ new Intent(Intent.ACTION_PICK, People.CONTENT_URI));
+ break;
+ }
+ case ACTION_PICK_PHONE: {
+ startContactSelectionActivityForResult(
+ new Intent(Intent.ACTION_PICK, Phone.CONTENT_URI));
+ break;
+ }
+ case ACTION_PICK_PHONE_LEGACY: {
+ startContactSelectionActivityForResult(
+ new Intent(Intent.ACTION_PICK, Phones.CONTENT_URI));
+ break;
+ }
+ case ACTION_PICK_POSTAL: {
+ startContactSelectionActivityForResult(
+ new Intent(Intent.ACTION_PICK, StructuredPostal.CONTENT_URI));
+ break;
+ }
+ case ACTION_PICK_POSTAL_LEGACY: {
+ Intent intent = new Intent(Intent.ACTION_PICK);
+ intent.setType(ContactMethods.CONTENT_POSTAL_TYPE);
+ startContactSelectionActivityForResult(intent);
+ break;
+ }
+ case ACTION_CREATE_SHORTCUT_CONTACT: {
+ Intent intent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
+ startContactSelectionActivityForResult(intent);
+ break;
+ }
+ case ACTION_CREATE_SHORTCUT_CONTACT_FILTER: {
+ startContactSelectionActivityForResult(
+ buildFilterIntent(ContactsRequest.ACTION_CREATE_SHORTCUT_CONTACT,
+ false));
+ break;
+ }
+ case ACTION_CREATE_SHORTCUT_DIAL: {
+ Intent intent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
+ intent.setComponent(
+ new ComponentName(ANDROID_CONTACTS_PACKAGE, "alias.DialShortcut"));
+ startActivityForResult(intent, 0);
+ break;
+ }
+ case ACTION_CREATE_SHORTCUT_DIAL_FILTER: {
+ startContactSelectionActivityForResult(
+ buildFilterIntent(ContactsRequest.ACTION_CREATE_SHORTCUT_CALL,
+ false));
+ break;
+ }
+ case ACTION_CREATE_SHORTCUT_MESSAGE: {
+ Intent intent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
+ intent.setComponent(
+ new ComponentName(ANDROID_CONTACTS_PACKAGE, "alias.MessageShortcut"));
+ startActivityForResult(intent, 0);
+ break;
+ }
+ case ACTION_CREATE_SHORTCUT_MESSAGE_FILTER: {
+ startContactSelectionActivityForResult(
+ buildFilterIntent(ContactsRequest.ACTION_CREATE_SHORTCUT_CALL, false));
+ break;
+ }
+ case ACTION_GET_CONTENT_CONTACT: {
+ Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+ intent.setType(Contacts.CONTENT_ITEM_TYPE);
+ startContactSelectionActivityForResult(intent);
+ break;
+ }
+ case ACTION_GET_CONTENT_CONTACT_LEGACY: {
+ Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+ intent.setType(People.CONTENT_ITEM_TYPE);
+ startContactSelectionActivityForResult(intent);
+ break;
+ }
+ case ACTION_GET_CONTENT_CONTACT_FILTER: {
+ startContactSelectionActivityForResult(
+ buildFilterIntent(ContactsRequest.ACTION_PICK_OR_CREATE_CONTACT, false));
+ break;
+ }
+ case ACTION_GET_CONTENT_CONTACT_FILTER_LEGACY: {
+ startContactSelectionActivityForResult(
+ buildFilterIntent(ContactsRequest.ACTION_PICK_OR_CREATE_CONTACT,
+ true));
+ break;
+ }
+ case ACTION_GET_CONTENT_PHONE: {
+ Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+ intent.setType(Phone.CONTENT_ITEM_TYPE);
+ startContactSelectionActivityForResult(intent);
+ break;
+ }
+ case ACTION_GET_CONTENT_PHONE_FILTER: {
+ startContactSelectionActivityForResult(
+ buildFilterIntent(ContactsRequest.ACTION_PICK_PHONE, true));
+ break;
+ }
+ case ACTION_GET_CONTENT_PHONE_LEGACY: {
+ Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+ intent.setType(Phones.CONTENT_ITEM_TYPE);
+ startContactSelectionActivityForResult(intent);
+ break;
+ }
+ case ACTION_GET_CONTENT_POSTAL: {
+ Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+ intent.setType(StructuredPostal.CONTENT_ITEM_TYPE);
+ startContactSelectionActivityForResult(intent);
+ break;
+ }
+ case ACTION_GET_CONTENT_POSTAL_FILTER: {
+ startContactSelectionActivityForResult(
+ buildFilterIntent(ContactsRequest.ACTION_PICK_POSTAL, false));
+ break;
+ }
+ case ACTION_GET_CONTENT_POSTAL_LEGACY: {
+ Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+ intent.setType(ContactMethods.CONTENT_POSTAL_ITEM_TYPE);
+ startContactSelectionActivityForResult(intent);
+ break;
+ }
+ case ACTION_INSERT_OR_EDIT: {
+ Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
+ startContactListActivity(intent);
+ break;
+ }
+ case ACTION_SEARCH_CALL: {
+ Intent intent = new Intent(Intent.ACTION_SEARCH);
+ intent.putExtra(SearchManager.ACTION_MSG, "call");
+ intent.putExtra(SearchManager.QUERY, "800-4664-411");
+ startSearchResultActivity(intent);
+ break;
+ }
+ case ACTION_SEARCH_CONTACT: {
+ Intent intent = new Intent(Intent.ACTION_SEARCH);
+ intent.putExtra(SearchManager.QUERY, "a");
+ startSearchResultActivity(intent);
+ break;
+ }
+ case ACTION_SEARCH_EMAIL: {
+ Intent intent = new Intent(Intent.ACTION_SEARCH);
+ intent.putExtra(Insert.EMAIL, "a");
+ startSearchResultActivity(intent);
+ break;
+ }
+ case ACTION_SEARCH_PHONE: {
+ Intent intent = new Intent(Intent.ACTION_SEARCH);
+ intent.putExtra(Insert.PHONE, "800");
+ startSearchResultActivity(intent);
+ break;
+ }
+ case SEARCH_SUGGESTION_CLICKED_CALL_BUTTON: {
+ long contactId = findArbitraryContactWithPhoneNumber();
+ if (contactId != -1) {
+ Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
+ Intent intent = new Intent(Intents.SEARCH_SUGGESTION_CLICKED);
+ intent.setData(contactUri);
+ intent.putExtra(SearchManager.ACTION_MSG, "call");
+ startContactListActivity(intent);
+ }
+ break;
+ }
+ case SEARCH_SUGGESTION_CLICKED_CONTACT: {
+ long contactId = findArbitraryContactWithPhoneNumber();
+ if (contactId != -1) {
+ Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
+ Intent intent = new Intent(Intents.SEARCH_SUGGESTION_CLICKED);
+ intent.setData(contactUri);
+ startContactListActivity(intent);
+ }
+ break;
+ }
+ case SEARCH_SUGGESTION_DIAL_NUMBER_CLICKED: {
+ Intent intent = new Intent(Intents.SEARCH_SUGGESTION_DIAL_NUMBER_CLICKED);
+ intent.setData(Uri.parse("tel:800-4664411"));
+ startContactListActivity(intent);
+ break;
+ }
+ case SEARCH_SUGGESTION_CREATE_CONTACT_CLICKED: {
+ Intent intent = new Intent(Intents.SEARCH_SUGGESTION_CREATE_CONTACT_CLICKED);
+ intent.setData(Uri.parse("tel:800-4664411"));
+ startContactListActivity(intent);
+ break;
+ }
+ case JOIN_CONTACT: {
+ // TODO
+ break;
+ }
+ case ACTION_GET_MULTIPLE_PHONES: {
+ Intent intent = new Intent(Intents.ACTION_GET_MULTIPLE_PHONES);
+ intent.setType(Phone.CONTENT_TYPE);
+ intent.putExtra(Intents.EXTRA_PHONE_URIS, new Uri[] {
+ Uri.parse("tel:555-1212"), Uri.parse("tel:555-2121")
+ });
+ startMultiplePhoneSelectionActivityForResult(intent);
+ break;
+ }
+ case EDIT_CONTACT: {
+ final long contactId = findArbitraryContactWithPhoneNumber();
+ final Uri uri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
+ final Intent intent = new Intent(Intent.ACTION_EDIT, uri);
+ startActivity(intent);
+ break;
+ }
+ case EDIT_CONTACT_LOOKUP: {
+ final long contactId = findArbitraryContactWithPhoneNumber();
+ final Uri uri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
+ final Uri lookupUri = Contacts.getLookupUri(getContentResolver(), uri);
+ final String lookupKey = lookupUri.getPathSegments().get(2);
+ final Uri lookupWithoutIdUri = Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI,
+ lookupKey);
+ final Intent intent = new Intent(Intent.ACTION_EDIT, lookupWithoutIdUri);
+ startActivity(intent);
+ break;
+ }
+ case EDIT_CONTACT_LOOKUP_ID: {
+ final long contactId = findArbitraryContactWithPhoneNumber();
+ final Uri uri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
+ final Uri lookupUri = Contacts.getLookupUri(getContentResolver(), uri);
+ final Intent intent = new Intent(Intent.ACTION_EDIT, lookupUri);
+ startActivity(intent);
+ break;
+ }
+ case EDIT_RAW_CONTACT: {
+ final long contactId = findArbitraryContactWithPhoneNumber();
+ final long rawContactId = findArbitraryRawContactOfContact(contactId);
+ final Uri uri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
+ final Intent intent = new Intent(Intent.ACTION_EDIT, uri);
+ startActivity(intent);
+ break;
+ }
+ case EDIT_LEGACY: {
+ final Uri legacyContentUri = Uri.parse("content://contacts/people");
+ final long contactId = findArbitraryContactWithPhoneNumber();
+ final long rawContactId = findArbitraryRawContactOfContact(contactId);
+ final Uri uri = ContentUris.withAppendedId(legacyContentUri, rawContactId);
+ final Intent intent = new Intent(Intent.ACTION_EDIT, uri);
+ startActivity(intent);
+ break;
+ }
+ case EDIT_NEW_CONTACT: {
+ startActivity(new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI));
+ break;
+ }
+ case EDIT_NEW_RAW_CONTACT: {
+ startActivity(new Intent(Intent.ACTION_INSERT, RawContacts.CONTENT_URI));
+ break;
+ }
+ case EDIT_NEW_LEGACY: {
+ final Uri legacyContentUri = Uri.parse("content://contacts/people");
+ startActivity(new Intent(Intent.ACTION_INSERT, legacyContentUri));
+ break;
+ }
+ case VIEW_CONTACT: {
+ final long contactId = findArbitraryContactWithPhoneNumber();
+ final Uri uri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
+ final Intent intent = new Intent(Intent.ACTION_VIEW, uri);
+ startActivity(intent);
+ break;
+ }
+ case VIEW_CONTACT_LOOKUP: {
+ final long contactId = findArbitraryContactWithPhoneNumber();
+ final Uri uri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
+ final Uri lookupUri = Contacts.getLookupUri(getContentResolver(), uri);
+ final String lookupKey = lookupUri.getPathSegments().get(2);
+ final Uri lookupWithoutIdUri = Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI,
+ lookupKey);
+ final Intent intent = new Intent(Intent.ACTION_VIEW, lookupWithoutIdUri);
+ startActivity(intent);
+ break;
+ }
+ case VIEW_CONTACT_LOOKUP_ID: {
+ final long contactId = findArbitraryContactWithPhoneNumber();
+ final Uri uri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
+ final Uri lookupUri = Contacts.getLookupUri(getContentResolver(), uri);
+ final Intent intent = new Intent(Intent.ACTION_VIEW, lookupUri);
+ startActivity(intent);
+ break;
+ }
+ case VIEW_RAW_CONTACT: {
+ final long contactId = findArbitraryContactWithPhoneNumber();
+ final long rawContactId = findArbitraryRawContactOfContact(contactId);
+ final Uri uri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
+ final Intent intent = new Intent(Intent.ACTION_VIEW, uri);
+ startActivity(intent);
+ break;
+ }
+ case VIEW_LEGACY: {
+ final Uri legacyContentUri = Uri.parse("content://contacts/people");
+ final long contactId = findArbitraryContactWithPhoneNumber();
+ final long rawContactId = findArbitraryRawContactOfContact(contactId);
+ final Uri uri = ContentUris.withAppendedId(legacyContentUri, rawContactId);
+ final Intent intent = new Intent(Intent.ACTION_VIEW, uri);
+ startActivity(intent);
+ break;
+ }
+ default: {
+ Toast.makeText(this, "Sorry, we forgot to write this...", Toast.LENGTH_LONG).show();
+ }
+ }
+ }
+
+ private Intent buildFilterIntent(int actionCode, boolean legacy) {
+ Intent intent = new Intent(UI.FILTER_CONTACTS_ACTION);
+ intent.putExtra(UI.FILTER_TEXT_EXTRA_KEY, "A");
+ ContactsRequest request = new ContactsRequest();
+ request.setActionCode(actionCode);
+ intent.putExtra(ContactsSearchManager.ORIGINAL_REQUEST_KEY, request);
+ return intent;
+ }
+
+ private void startContactListActivity(Intent intent) {
+ intent.setComponent(
+ new ComponentName(ANDROID_CONTACTS_PACKAGE, CONTACT_LIST_ACTIVITY_CLASS_NAME));
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ startActivity(intent);
+ }
+
+ private void startContactSelectionActivityForResult(Intent intent) {
+ startActivityForResult(intent, 12);
+ }
+
+ private void startSearchResultActivity(Intent intent) {
+ intent.setComponent(
+ new ComponentName(ANDROID_CONTACTS_PACKAGE, SEARCH_RESULTS_ACTIVITY_CLASS_NAME));
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ startActivity(intent);
+ }
+
+ private void startMultiplePhoneSelectionActivityForResult(Intent intent) {
+ intent.setComponent(
+ new ComponentName(ANDROID_CONTACTS_PACKAGE,
+ MULTIPLE_PHONE_PICKER_ACTIVITY_CLASS_NAME));
+ startActivityForResult(intent, 13);
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ Intent intent = new Intent(this, ResultActivity.class);
+ intent.putExtra("resultCode", resultCode);
+ intent.putExtra("data", data);
+ startActivity(intent);
+ }
+
+ private long findArbitraryContactWithPhoneNumber() {
+ final Cursor cursor = getContentResolver().query(Contacts.CONTENT_URI,
+ new String[] { Contacts._ID },
+ Contacts.HAS_PHONE_NUMBER + "!=0 AND " + Contacts.STARRED + "!=0" ,
+ null, "RANDOM() LIMIT 1");
+ try {
+ if (cursor.moveToFirst()) {
+ return cursor.getLong(0);
+ }
+ } finally {
+ cursor.close();
+ }
+
+ return -1;
+ }
+
+ private long findArbitraryRawContactOfContact(long contactId) {
+ final Cursor cursor = getContentResolver().query(RawContacts.CONTENT_URI,
+ new String[] { RawContacts._ID },
+ RawContacts.CONTACT_ID + "=?",
+ new String[] { String.valueOf(contactId) },
+ RawContacts._ID + " LIMIT 1");
+ try {
+ if (cursor.moveToFirst()) {
+ return cursor.getLong(0);
+ }
+ } finally {
+ cursor.close();
+ }
+
+ return -1;
+ }
+}
diff --git a/tests/src/com/android/contacts/tests/allintents/ResultActivity.java b/tests/src/com/android/contacts/tests/allintents/ResultActivity.java
new file mode 100644
index 0000000..562f2ba
--- /dev/null
+++ b/tests/src/com/android/contacts/tests/allintents/ResultActivity.java
@@ -0,0 +1,196 @@
+/*
+ * 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.tests.allintents;
+
+import com.android.contacts.tests.R;
+
+import android.app.Activity;
+import android.content.ContentUris;
+import android.content.Intent;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Color;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.CommonDataKinds.Photo;
+import android.text.TextUtils;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.TableLayout;
+import android.widget.TableRow;
+import android.widget.TextView;
+import android.widget.ImageView.ScaleType;
+
+import java.util.Arrays;
+
+/**
+ * An activity that shows the result of a contacts activity invocation.
+ */
+public class ResultActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.result);
+
+ Intent intent = getIntent();
+ addRowsForIntent((Intent)intent.getExtras().get("data"));
+ }
+
+ private void addRowsForIntent(Intent intent) {
+ if (intent == null) {
+ addRow("", "No data intent returned");
+ } else {
+ addRow("INTENT", intent.toString());
+ addSeparator(3);
+
+ Bundle extras = intent.getExtras();
+ if (extras != null && !extras.isEmpty()) {
+ for (String key : extras.keySet()) {
+ Object value = extras.get(key);
+ addRow("EXTRA", key);
+ addRowForValue("", value);
+ }
+
+ addSeparator(3);
+ }
+
+ String dataUri = intent.getDataString();
+ if (dataUri != null) {
+ addRowsForQuery(Uri.parse(dataUri));
+ }
+ }
+ }
+
+ private void addRowForValue(String label, Object value) {
+ if (value == null) {
+ addRow(label, "null");
+ } else if (value instanceof Bitmap) {
+ addRowWithBitmap(label, (Bitmap)value);
+ } else if (value instanceof Intent) {
+ addRow(label, "INTENT");
+ addRowsForIntent((Intent)value);
+ } else if (value instanceof Uri) {
+ addRow(label, "DATA");
+ addRowsForQuery((Uri)value);
+ } else if (value.getClass().isArray()) {
+ addRow(label, "ARRAY");
+ Parcelable[] array = (Parcelable[])value;
+ for (int i = 0; i < array.length; i++) {
+ addRowForValue("[" + i + "]", String.valueOf(array[i]));
+ }
+ } else {
+ addRow(label, String.valueOf(value));
+ }
+ }
+
+ private void addRowsForQuery(Uri dataUri) {
+ Cursor cursor = getContentResolver().query(dataUri, null, null, null, null);
+ if (cursor == null) {
+ addRow("", "No data for this URI");
+ } else {
+ try {
+ while (cursor.moveToNext()) {
+ addRow("", "DATA");
+ String[] columnNames = cursor.getColumnNames();
+ String[] names = new String[columnNames.length];
+ System.arraycopy(columnNames, 0, names, 0, columnNames.length);
+ Arrays.sort(names);
+ for (int i = 0; i < names.length; i++) {
+ int index = cursor.getColumnIndex(names[i]);
+ String value = cursor.getString(index);
+ addRow(names[i], value);
+
+ if (names[i].equals(Contacts.PHOTO_ID) && !TextUtils.isEmpty(value)) {
+ addRowWithPhoto(Long.parseLong(value));
+ }
+ }
+ }
+ } finally {
+ cursor.close();
+ }
+ }
+ }
+
+ private void addRow(String column0, String column1) {
+ TextView label = new TextView(this);
+ label.setPadding(4, 4, 4, 4);
+ label.setText(column0);
+ TextView value = new TextView(this);
+ value.setPadding(4, 4, 4, 4);
+ value.setText(column1);
+ addRow(label, value);
+ }
+
+ private void addRowWithPhoto(long photoId) {
+ byte[] data = null;
+ Cursor cursor = getContentResolver().query(
+ ContentUris.withAppendedId(Data.CONTENT_URI, photoId),
+ new String[]{Photo.PHOTO}, null, null, null);
+ try {
+ if (cursor.moveToNext()) {
+ data = cursor.getBlob(0);
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+
+ if (data == null) {
+ return;
+ }
+
+ addRowWithBitmap("Photo", BitmapFactory.decodeByteArray(data, 0, data.length));
+ }
+
+ private void addRowWithBitmap(String label, Bitmap bitmap) {
+ TextView labelView = new TextView(this);
+ labelView.setPadding(4, 4, 4, 4);
+ labelView.setText(label);
+
+ ImageView imageView = new ImageView(this);
+ imageView.setImageBitmap(bitmap);
+ imageView.setPadding(4, 4, 4, 4);
+ imageView.setScaleType(ScaleType.FIT_START);
+ addRow(labelView, imageView);
+ }
+
+ private void addRow(View column0, View column1) {
+ TableLayout table = (TableLayout)findViewById(R.id.table);
+ TableRow row = new TableRow(this);
+ row.addView(column0);
+ row.addView(column1);
+ table.addView(row);
+
+ addSeparator(1);
+ }
+
+ private void addSeparator(int height) {
+ TableLayout table = (TableLayout)findViewById(R.id.table);
+ View separator = new View(this);
+ TableLayout.LayoutParams params = new TableLayout.LayoutParams();
+ params.height = height;
+ separator.setLayoutParams(params);
+ separator.setBackgroundColor(Color.rgb(33, 66, 33));
+ table.addView(separator);
+ }
+}
diff --git a/tests/src/com/android/contacts/tests/mocks/ContactsMockContext.java b/tests/src/com/android/contacts/tests/mocks/ContactsMockContext.java
new file mode 100644
index 0000000..4697b83
--- /dev/null
+++ b/tests/src/com/android/contacts/tests/mocks/ContactsMockContext.java
@@ -0,0 +1,63 @@
+/*
+ * 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.tests.mocks;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.provider.ContactsContract;
+import android.provider.Settings;
+import android.test.mock.MockContentResolver;
+
+/**
+ * A mock context for contact activity unit tests. Forwards everything to
+ * a supplied context, except content resolver operations, which are sent
+ * to mock content providers.
+ */
+public class ContactsMockContext extends ContextWrapper {
+
+ private MockContentResolver mContentResolver;
+ private MockContentProvider mContactsProvider;
+ private MockContentProvider mSettingsProvider;
+
+ public ContactsMockContext(Context base) {
+ super(base);
+ mContentResolver = new MockContentResolver();
+ mContactsProvider = new MockContentProvider();
+ mContentResolver.addProvider(ContactsContract.AUTHORITY, mContactsProvider);
+ mSettingsProvider = new MockContentProvider();
+ mContentResolver.addProvider(Settings.AUTHORITY, mSettingsProvider);
+ }
+
+ @Override
+ public ContentResolver getContentResolver() {
+ return mContentResolver;
+ }
+
+ public MockContentProvider getContactsProvider() {
+ return mContactsProvider;
+ }
+
+ public MockContentProvider getSettingsProvider() {
+ return mSettingsProvider;
+ }
+
+ @Override
+ public Context getApplicationContext() {
+ return this;
+ }
+}
diff --git a/tests/src/com/android/contacts/tests/mocks/MockContentProvider.java b/tests/src/com/android/contacts/tests/mocks/MockContentProvider.java
new file mode 100644
index 0000000..14d563c
--- /dev/null
+++ b/tests/src/com/android/contacts/tests/mocks/MockContentProvider.java
@@ -0,0 +1,259 @@
+/*
+ * 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.tests.mocks;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.net.Uri;
+import android.text.TextUtils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedList;
+
+import junit.framework.Assert;
+
+/**
+ * A programmable mock content provider.
+ */
+public class MockContentProvider extends ContentProvider {
+ private static final String TAG = "MockContentProvider";
+
+ public static class Query {
+
+ private final Uri mUri;
+ private String[] mProjection;
+ private String[] mDefaultProjection;
+ private String mSelection;
+ private String[] mSelectionArgs;
+ private String mSortOrder;
+ private ArrayList<Object[]> mRows = new ArrayList<Object[]>();
+
+ public Query(Uri uri) {
+ mUri = uri;
+ }
+
+ @Override
+ public String toString() {
+ return queryToString(mUri, mProjection, mSelection, mSelectionArgs, mSortOrder);
+ }
+
+ public Query withProjection(String... projection) {
+ mProjection = projection;
+ return this;
+ }
+
+ public Query withDefaultProjection(String... projection) {
+ mDefaultProjection = projection;
+ return this;
+ }
+
+ public Query withSelection(String selection, String... selectionArgs) {
+ mSelection = selection;
+ mSelectionArgs = selectionArgs;
+ return this;
+ }
+
+ public Query withSortOrder(String sortOrder) {
+ mSortOrder = sortOrder;
+ return this;
+ }
+
+ public Query returnRow(Object... row) {
+ mRows.add(row);
+ return this;
+ }
+
+ public boolean equals(Uri uri, String[] projection, String selection,
+ String[] selectionArgs, String sortOrder) {
+ if (!uri.equals(mUri)) {
+ return false;
+ }
+
+ if (!equals(projection, mProjection)) {
+ return false;
+ }
+
+ if (!TextUtils.equals(selection, mSelection)) {
+ return false;
+ }
+
+ if (!equals(selectionArgs, mSelectionArgs)) {
+ return false;
+ }
+
+ if (!TextUtils.equals(sortOrder, mSortOrder)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ private static boolean equals(String[] array1, String[] array2) {
+ boolean empty1 = array1 == null || array1.length == 0;
+ boolean empty2 = array2 == null || array2.length == 0;
+ if (empty1 && empty2) {
+ return true;
+ }
+ if (empty1 != empty2 && (empty1 || empty2)) {
+ return false;
+ }
+
+ if (array1.length != array2.length) return false;
+
+ for (int i = 0; i < array1.length; i++) {
+ if (!array1[i].equals(array2[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public Cursor getResult() {
+ String[] columnNames = mProjection != null ? mProjection : mDefaultProjection;
+ MatrixCursor cursor = new MatrixCursor(columnNames);
+ for (Object[] row : mRows) {
+ cursor.addRow(row);
+ }
+ return cursor;
+ }
+ }
+
+ public static class TypeQuery {
+ private final Uri mUri;
+ private final String mType;
+
+ public TypeQuery(Uri uri, String type) {
+ mUri = uri;
+ mType = type;
+ }
+
+ public Uri getUri() {
+ return mUri;
+ }
+
+ public String getType() {
+ return mType;
+ }
+
+ @Override
+ public String toString() {
+ return mUri + " --> " + mType;
+ }
+
+ public boolean equals(Uri uri) {
+ return getUri().equals(uri);
+ }
+ }
+
+ private LinkedList<Query> mExpectedQueries = new LinkedList<Query>();
+ private LinkedList<TypeQuery> mExpectedTypeQueries = new LinkedList<TypeQuery>();
+
+ @Override
+ public boolean onCreate() {
+ return true;
+ }
+
+ public Query expectQuery(Uri contentUri) {
+ Query query = new Query(contentUri);
+ mExpectedQueries.offer(query);
+ return query;
+ }
+
+ public void expectTypeQuery(Uri uri, String type) {
+ TypeQuery result = new TypeQuery(uri, type);
+ mExpectedTypeQueries.offer(result);
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) {
+ if (mExpectedQueries.isEmpty()) {
+ Assert.fail("Unexpected query: "
+ + queryToString(uri, projection, selection, selectionArgs, sortOrder));
+ }
+
+ Query query = mExpectedQueries.remove();
+ if (!query.equals(uri, projection, selection, selectionArgs, sortOrder)) {
+ Assert.fail("Incorrect query.\n Expected: " + query + "\n Actual: " +
+ queryToString(uri, projection, selection, selectionArgs, sortOrder));
+ }
+
+ return query.getResult();
+ }
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ if (mExpectedTypeQueries.isEmpty()) {
+ Assert.fail("Unexpected getType query: " + uri);
+ }
+
+ TypeQuery query = mExpectedTypeQueries.remove();
+ if (!query.equals(uri)) {
+ Assert.fail("Incorrect query.\n Expected: " + query + "\n Actual: " + uri);
+ }
+
+ return query.getType();
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ throw new UnsupportedOperationException();
+ }
+
+ private static String queryToString(Uri uri, String[] projection, String selection,
+ String[] selectionArgs, String sortOrder) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(uri).append(" ");
+ if (projection != null) {
+ sb.append(Arrays.toString(projection));
+ } else {
+ sb.append("[]");
+ }
+ if (selection != null) {
+ sb.append(" selection: '").append(selection).append("'");
+ if (selectionArgs != null) {
+ sb.append(Arrays.toString(selectionArgs));
+ } else {
+ sb.append("[]");
+ }
+ }
+ if (sortOrder != null) {
+ sb.append(" sort: '").append(sortOrder).append("'");
+ }
+ return sb.toString();
+ }
+
+ public void verify() {
+ Assert.assertTrue("Not all expected queries have been called: " +
+ mExpectedQueries, mExpectedQueries.isEmpty());
+ Assert.assertTrue("Not all expected getType-queries have been called: " +
+ mExpectedQueries, mExpectedTypeQueries.isEmpty());
+ }
+}
diff --git a/tests/src/com/android/contacts/tests/mocks/MockSources.java b/tests/src/com/android/contacts/tests/mocks/MockSources.java
new file mode 100644
index 0000000..201928f
--- /dev/null
+++ b/tests/src/com/android/contacts/tests/mocks/MockSources.java
@@ -0,0 +1,46 @@
+/*
+ * 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.tests.mocks;
+
+import com.android.contacts.model.ContactsSource;
+import com.android.contacts.model.FallbackSource;
+import com.android.contacts.model.Sources;
+
+/**
+ * A mock {@link Sources} class.
+ */
+public class MockSources extends Sources {
+
+ public static final String WRITABLE_ACCOUNT_TYPE = "writable";
+ public static final String READONLY_ACCOUNT_TYPE = "readonly";
+
+ @Override
+ public ContactsSource getInflatedSource(String accountType, int inflateLevel) {
+ if (accountType.equals(WRITABLE_ACCOUNT_TYPE)) {
+ ContactsSource source = new FallbackSource();
+ source.readOnly = false;
+ return source;
+ }
+
+ if (accountType.equals(READONLY_ACCOUNT_TYPE)) {
+ ContactsSource source = new FallbackSource();
+ source.readOnly = true;
+ return source;
+ }
+
+ return null;
+ }
+}
diff --git a/tests/src/com/android/contacts/tests/widget/PinnedHeaderUseCaseActivity.java b/tests/src/com/android/contacts/tests/widget/PinnedHeaderUseCaseActivity.java
new file mode 100644
index 0000000..b01963f
--- /dev/null
+++ b/tests/src/com/android/contacts/tests/widget/PinnedHeaderUseCaseActivity.java
@@ -0,0 +1,89 @@
+/*
+ * 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.tests.widget;
+
+import com.android.contacts.tests.R;
+import com.android.contacts.widget.PinnedHeaderListView;
+
+import android.app.ListActivity;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+
+/**
+ * An activity that demonstrates various use cases for the {@link PinnedHeaderListView}.
+ */
+public class PinnedHeaderUseCaseActivity extends ListActivity {
+
+ private static final int SINGLE_SHORT_SECTION_NO_HEADERS = 0;
+ private static final int TWO_SHORT_SECTIONS_WITH_HEADERS = 1;
+ private static final int FIVE_SHORT_SECTIONS_WITH_HEADERS = 2;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setListAdapter(new ArrayAdapter<String>(this, R.layout.intent_list_item,
+ getResources().getStringArray(R.array.pinnedHeaderUseCases)));
+ }
+
+ @Override
+ protected void onListItemClick(ListView l, View v, int position, long id) {
+ switch (position) {
+ case SINGLE_SHORT_SECTION_NO_HEADERS:
+ startActivity(
+ new int[]{5},
+ new String[]{"Line"},
+ new boolean[]{false},
+ new boolean[]{false},
+ new int[]{0});
+ break;
+ case TWO_SHORT_SECTIONS_WITH_HEADERS:
+ startActivity(
+ new int[]{2, 30},
+ new String[]{"First", "Second"},
+ new boolean[]{true, true},
+ new boolean[]{false, false},
+ new int[]{0, 2000});
+ break;
+ case FIVE_SHORT_SECTIONS_WITH_HEADERS:
+ startActivity(
+ new int[]{1, 5, 5, 5, 5},
+ new String[]{"First", "Second", "Third", "Fourth", "Fifth"},
+ new boolean[]{true, true, true, true, true},
+ new boolean[]{false, false, false, false, false},
+ new int[]{0, 2000, 3000, 4000, 5000});
+ break;
+ }
+ }
+
+ private void startActivity(int[] counts, String[] names, boolean[] headers,
+ boolean[] showIfEmpty, int[] delays) {
+ Intent intent = new Intent();
+ intent.setComponent(new ComponentName("com.android.contacts",
+ "com.android.contacts.widget.PinnedHeaderListDemoActivity"));
+ intent.putExtra("counts", counts);
+ intent.putExtra("names", names);
+ intent.putExtra("headers", headers);
+ intent.putExtra("showIfEmpty", showIfEmpty);
+ intent.putExtra("delays", delays);
+
+ startActivity(intent);
+ }
+}
diff --git a/tests/src/com/android/contacts/vcard/ImportProcessorTest.java b/tests/src/com/android/contacts/vcard/ImportProcessorTest.java
new file mode 100644
index 0000000..72245ba
--- /dev/null
+++ b/tests/src/com/android/contacts/vcard/ImportProcessorTest.java
@@ -0,0 +1,180 @@
+/*
+ * 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.vcard;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.net.Uri;
+import android.test.AndroidTestCase;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.contacts.vcard.ImportProcessor.CommitterGenerator;
+import com.android.vcard.VCardEntryCommitter;
+import com.android.vcard.VCardInterpreter;
+import com.android.vcard.VCardSourceDetector;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.Channels;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.WritableByteChannel;
+import java.util.List;
+
+public class ImportProcessorTest extends AndroidTestCase {
+ private static final String LOG_TAG = "ImportProcessorTest";
+ private ImportProcessor mImportProcessor;
+
+ private String mCopiedFileName;
+
+ // XXX: better way to copy stream?
+ private Uri copyToLocal(final String fileName) throws IOException {
+ final Context context = getContext();
+ // We need to use Context of this unit test runner (not of test to be tested),
+ // as only the former knows assets to be copied.
+ final Context testContext = getTestContext();
+ final ContentResolver resolver = testContext.getContentResolver();
+ mCopiedFileName = fileName;
+ ReadableByteChannel inputChannel = null;
+ WritableByteChannel outputChannel = null;
+ Uri destUri;
+ try {
+ inputChannel = Channels.newChannel(testContext.getAssets().open(fileName));
+ destUri = Uri.parse(context.getFileStreamPath(fileName).toURI().toString());
+ outputChannel =
+ getContext().openFileOutput(fileName,
+ Context.MODE_WORLD_WRITEABLE).getChannel();
+ final ByteBuffer buffer = ByteBuffer.allocateDirect(8192);
+ while (inputChannel.read(buffer) != -1) {
+ buffer.flip();
+ outputChannel.write(buffer);
+ buffer.compact();
+ }
+ buffer.flip();
+ while (buffer.hasRemaining()) {
+ outputChannel.write(buffer);
+ }
+ } finally {
+ if (inputChannel != null) {
+ try {
+ inputChannel.close();
+ } catch (IOException e) {
+ Log.w(LOG_TAG, "Failed to close inputChannel.");
+ }
+ }
+ if (outputChannel != null) {
+ try {
+ outputChannel.close();
+ } catch(IOException e) {
+ Log.w(LOG_TAG, "Failed to close outputChannel");
+ }
+ }
+ }
+ return destUri;
+ }
+
+ @Override
+ public void setUp() {
+ mImportProcessor = new ImportProcessor(getContext());
+ mImportProcessor.ensureInit();
+ mCopiedFileName = null;
+ }
+
+ @Override
+ public void tearDown() {
+ if (!TextUtils.isEmpty(mCopiedFileName)) {
+ getContext().deleteFile(mCopiedFileName);
+ mCopiedFileName = null;
+ }
+ }
+
+ /**
+ * Confirms {@link ImportProcessor#readOneVCard(android.net.Uri, int, String,
+ * com.android.vcard.VCardInterpreter, int[])} successfully handles correct input.
+ */
+ public void testProcessSimple() throws IOException {
+ final Uri uri = copyToLocal("v21_simple.vcf");
+ final int vcardType = VCardSourceDetector.PARSE_TYPE_UNKNOWN;
+ final String charset = null;
+ final VCardInterpreter interpreter = new EmptyVCardInterpreter();
+ final int[] versions = new int[] {
+ ImportVCardActivity.VCARD_VERSION_V21
+ };
+
+ assertTrue(mImportProcessor.readOneVCard(
+ uri, vcardType, charset, interpreter, versions));
+ }
+
+ /**
+ * Confirms {@link ImportProcessor#handleOneRequest(ImportRequest)} accepts
+ * one request and import it.
+ */
+ public void testHandleOneRequestSimple() throws IOException {
+ CommitterGenerator generator = new CommitterGenerator() {
+ public VCardEntryCommitter generate(ContentResolver resolver) {
+ return new MockVCardEntryCommitter();
+ }
+ };
+ mImportProcessor.injectCommitterGeneratorForTest(generator);
+ mImportProcessor.initNotifierForTest();
+
+ final ImportRequest request = new ImportRequest(
+ null, // account
+ copyToLocal("v30_simple.vcf"),
+ VCardSourceDetector.PARSE_TYPE_UNKNOWN,
+ null, // estimatedCharset
+ ImportVCardActivity.VCARD_VERSION_AUTO_DETECT,
+ 1);
+ assertTrue(mImportProcessor.handleOneRequest(request));
+ assertEquals(1, mImportProcessor.getCreatedUrisForTest().size());
+ }
+}
+
+/* package */ class EmptyVCardInterpreter implements VCardInterpreter {
+ @Override
+ public void end() {
+ }
+ @Override
+ public void endEntry() {
+ }
+ @Override
+ public void endProperty() {
+ }
+ @Override
+ public void propertyGroup(String group) {
+ }
+ @Override
+ public void propertyName(String name) {
+ }
+ @Override
+ public void propertyParamType(String type) {
+ }
+ @Override
+ public void propertyParamValue(String value) {
+ }
+ @Override
+ public void propertyValues(List<String> values) {
+ }
+ @Override
+ public void start() {
+ }
+ @Override
+ public void startEntry() {
+ }
+ @Override
+ public void startProperty() {
+ }
+}
diff --git a/tests/src/com/android/contacts/vcard/MockVCardEntryCommitter.java b/tests/src/com/android/contacts/vcard/MockVCardEntryCommitter.java
new file mode 100644
index 0000000..4765b38
--- /dev/null
+++ b/tests/src/com/android/contacts/vcard/MockVCardEntryCommitter.java
@@ -0,0 +1,56 @@
+/*
+ * 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.vcard;
+
+import android.net.Uri;
+
+import com.android.vcard.VCardEntry;
+import com.android.vcard.VCardEntryCommitter;
+
+import java.util.ArrayList;
+
+public class MockVCardEntryCommitter extends VCardEntryCommitter {
+
+ private final ArrayList<Uri> mUris = new ArrayList<Uri>();
+
+ public MockVCardEntryCommitter() {
+ super(null);
+ }
+
+ /**
+ * Exists for forcing super class to do nothing.
+ */
+ @Override
+ public void onStart() {
+ }
+
+ /**
+ * Exists for forcing super class to do nothing.
+ */
+ @Override
+ public void onEnd() {
+ }
+
+ @Override
+ public void onEntryCreated(final VCardEntry vcardEntry) {
+ mUris.add(null);
+ }
+
+ @Override
+ public ArrayList<Uri> getCreatedUris() {
+ return mUris;
+ }
+}
\ No newline at end of file
diff --git a/tests/src/com/android/contacts/widget/CompositeListAdapterTest.java b/tests/src/com/android/contacts/widget/CompositeListAdapterTest.java
new file mode 100644
index 0000000..87d268b
--- /dev/null
+++ b/tests/src/com/android/contacts/widget/CompositeListAdapterTest.java
@@ -0,0 +1,324 @@
+/*
+ * 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.widget;
+
+import com.google.android.collect.Lists;
+
+import android.content.Context;
+import android.database.DataSetObserver;
+import android.test.AndroidTestCase;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+
+/**
+ * Tests for {@link CompositeListAdapter}.
+ */
+public class CompositeListAdapterTest extends AndroidTestCase {
+
+ private final class MockAdapter extends ArrayAdapter<String> {
+ boolean allItemsEnabled = true;
+ HashSet<Integer> enabledItems = new HashSet<Integer>();
+ int viewTypeCount = 1;
+ HashMap<Integer, Integer> viewTypes = new HashMap<Integer, Integer>();
+
+ private MockAdapter(Context context, List<String> objects) {
+ super(context, android.R.layout.simple_list_item_1, objects);
+ for (int i = 0; i < objects.size(); i++) {
+ viewTypes.put(i, 0);
+ }
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ return new MockView(getContext(), position);
+ }
+
+ @Override
+ public boolean areAllItemsEnabled() {
+ return allItemsEnabled;
+ }
+
+ @Override
+ public boolean isEnabled(int position) {
+ return enabledItems.contains(position);
+ }
+
+ @Override
+ public int getViewTypeCount() {
+ return viewTypeCount;
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ return viewTypes.get(position);
+ }
+ }
+
+ private final class MockView extends View {
+ public MockView(Context context, int position) {
+ super(context);
+ setTag(position);
+ }
+ }
+
+ private final class TestDataSetObserver extends DataSetObserver {
+
+ public int changeCount;
+ public int invalidationCount;
+
+ @Override
+ public void onChanged() {
+ changeCount++;
+ }
+
+ @Override
+ public void onInvalidated() {
+ invalidationCount++;
+ }
+ }
+
+ private MockAdapter mAdapter1;
+ private MockAdapter mAdapter2;
+ private MockAdapter mAdapter3;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mAdapter1 = new MockAdapter(getContext(), Lists.newArrayList("A", "B"));
+ mAdapter2 = new MockAdapter(getContext(), new ArrayList<String>());
+ mAdapter3 = new MockAdapter(getContext(), Lists.newArrayList("C", "D", "E"));
+ }
+
+ public void testGetCount() {
+ CompositeListAdapter adapter = new CompositeListAdapter();
+ adapter.addAdapter(mAdapter1);
+ adapter.addAdapter(mAdapter2);
+ adapter.addAdapter(mAdapter3);
+
+ assertEquals(5, adapter.getCount());
+ }
+
+ public void testGetCountWithInvalidation() {
+ CompositeListAdapter adapter = new CompositeListAdapter();
+ assertEquals(0, adapter.getCount());
+
+ adapter.addAdapter(mAdapter1);
+ assertEquals(2, adapter.getCount());
+
+ adapter.addAdapter(mAdapter2);
+ assertEquals(2, adapter.getCount());
+
+ adapter.addAdapter(mAdapter3);
+ assertEquals(5, adapter.getCount());
+ }
+
+ public void testGetItem() {
+ CompositeListAdapter adapter = new CompositeListAdapter();
+ adapter.addAdapter(mAdapter1);
+ adapter.addAdapter(mAdapter2);
+ adapter.addAdapter(mAdapter3);
+
+ assertEquals("A", adapter.getItem(0));
+ assertEquals("B", adapter.getItem(1));
+ assertEquals("C", adapter.getItem(2));
+ assertEquals("D", adapter.getItem(3));
+ assertEquals("E", adapter.getItem(4));
+ }
+
+ public void testGetItemId() {
+ CompositeListAdapter adapter = new CompositeListAdapter();
+ adapter.addAdapter(mAdapter1);
+ adapter.addAdapter(mAdapter2);
+ adapter.addAdapter(mAdapter3);
+
+ assertEquals(0, adapter.getItemId(0));
+ assertEquals(1, adapter.getItemId(1));
+ assertEquals(0, adapter.getItemId(2));
+ assertEquals(1, adapter.getItemId(3));
+ assertEquals(2, adapter.getItemId(4));
+ }
+
+ public void testGetView() {
+ CompositeListAdapter adapter = new CompositeListAdapter();
+ adapter.addAdapter(mAdapter1);
+ adapter.addAdapter(mAdapter2);
+ adapter.addAdapter(mAdapter3);
+
+ assertEquals(0, adapter.getView(0, null, null).getTag());
+ assertEquals(1, adapter.getView(1, null, null).getTag());
+ assertEquals(0, adapter.getView(2, null, null).getTag());
+ assertEquals(1, adapter.getView(3, null, null).getTag());
+ assertEquals(2, adapter.getView(4, null, null).getTag());
+ }
+
+ public void testGetViewTypeCount() {
+ mAdapter1.viewTypeCount = 2;
+ mAdapter2.viewTypeCount = 3;
+ CompositeListAdapter adapter = new CompositeListAdapter();
+ adapter.addAdapter(mAdapter1);
+ adapter.addAdapter(mAdapter2);
+ adapter.addAdapter(mAdapter3);
+
+ // Note that mAdapter2 adds an implicit +1
+ assertEquals(6, adapter.getViewTypeCount());
+ }
+
+ public void testGetItemViewType() {
+ mAdapter1.viewTypeCount = 2;
+ mAdapter1.viewTypes.put(0, 1);
+ mAdapter1.viewTypes.put(1, 0);
+
+ mAdapter3.viewTypeCount = 3;
+ mAdapter3.viewTypes.put(0, 1);
+ mAdapter3.viewTypes.put(1, 2);
+ mAdapter3.viewTypes.put(2, 0);
+
+ CompositeListAdapter adapter = new CompositeListAdapter();
+ adapter.addAdapter(mAdapter1);
+ adapter.addAdapter(mAdapter2);
+ adapter.addAdapter(mAdapter3);
+
+ assertEquals(1, adapter.getItemViewType(0));
+ assertEquals(0, adapter.getItemViewType(1));
+
+ // Note: mAdapter2 throws in a +1
+
+ assertEquals(4, adapter.getItemViewType(2));
+ assertEquals(5, adapter.getItemViewType(3));
+ assertEquals(3, adapter.getItemViewType(4));
+ }
+
+ public void testNotifyDataSetChangedPropagated() {
+ CompositeListAdapter adapter = new CompositeListAdapter();
+ adapter.addAdapter(mAdapter1);
+ adapter.addAdapter(mAdapter2);
+
+ TestDataSetObserver observer = new TestDataSetObserver();
+ adapter.registerDataSetObserver(observer);
+ mAdapter1.add("X");
+
+ assertEquals(1, observer.changeCount);
+ assertEquals(0, observer.invalidationCount);
+ assertEquals(3, adapter.getCount());
+ assertEquals("A", adapter.getItem(0));
+ assertEquals("B", adapter.getItem(1));
+ assertEquals("X", adapter.getItem(2));
+
+ mAdapter2.add("Y");
+ assertEquals(2, observer.changeCount);
+ assertEquals(0, observer.invalidationCount);
+ assertEquals(4, adapter.getCount());
+ assertEquals("A", adapter.getItem(0));
+ assertEquals("B", adapter.getItem(1));
+ assertEquals("X", adapter.getItem(2));
+ assertEquals("Y", adapter.getItem(3));
+
+ }
+
+ public void testNotifyDataSetChangedOnAddingAdapter() {
+ CompositeListAdapter adapter = new CompositeListAdapter();
+ adapter.addAdapter(mAdapter1);
+
+ TestDataSetObserver observer = new TestDataSetObserver();
+ adapter.registerDataSetObserver(observer);
+ adapter.addAdapter(mAdapter3);
+
+ assertEquals(1, observer.changeCount);
+ assertEquals(0, observer.invalidationCount);
+ assertEquals(5, adapter.getCount());
+ assertEquals("A", adapter.getItem(0));
+ assertEquals("B", adapter.getItem(1));
+ assertEquals("C", adapter.getItem(2));
+ assertEquals("D", adapter.getItem(3));
+ assertEquals("E", adapter.getItem(4));
+ }
+
+ public void testNotifyDataSetInvalidated() {
+ CompositeListAdapter adapter = new CompositeListAdapter();
+ adapter.addAdapter(mAdapter1);
+
+ TestDataSetObserver observer = new TestDataSetObserver();
+ adapter.registerDataSetObserver(observer);
+
+ mAdapter1.remove("A");
+ assertEquals(1, observer.changeCount);
+ assertEquals(0, observer.invalidationCount);
+ assertEquals(1, adapter.getCount());
+
+ mAdapter1.remove("B");
+ assertEquals(1, observer.changeCount);
+ assertEquals(1, observer.invalidationCount);
+ assertEquals(0, adapter.getCount());
+ }
+
+ public void testAreAllItemsEnabled() {
+ CompositeListAdapter adapter = new CompositeListAdapter();
+ adapter.addAdapter(mAdapter1);
+ adapter.addAdapter(mAdapter3);
+
+ assertTrue(adapter.areAllItemsEnabled());
+ }
+
+ public void testAreAllItemsEnabledWithInvalidation() {
+ CompositeListAdapter adapter = new CompositeListAdapter();
+ adapter.addAdapter(mAdapter1);
+ assertTrue(adapter.areAllItemsEnabled());
+
+ mAdapter3.allItemsEnabled = false;
+ adapter.addAdapter(mAdapter3);
+
+ assertFalse(adapter.areAllItemsEnabled());
+ }
+
+ public void testIsEnabled() {
+ mAdapter1.allItemsEnabled = false;
+ mAdapter1.enabledItems.add(1);
+
+ CompositeListAdapter adapter = new CompositeListAdapter();
+ adapter.addAdapter(mAdapter1);
+ adapter.addAdapter(mAdapter2);
+ adapter.addAdapter(mAdapter3);
+
+ assertFalse(adapter.isEnabled(0));
+ assertTrue(adapter.isEnabled(1));
+ assertTrue(adapter.isEnabled(2));
+ assertTrue(adapter.isEnabled(3));
+ assertTrue(adapter.isEnabled(4));
+ }
+
+ public void testIsEnabledWhenAllEnabledAtLeastOneAdapter() {
+ mAdapter1.allItemsEnabled = false;
+ mAdapter1.enabledItems.add(1);
+ mAdapter3.allItemsEnabled = false;
+ mAdapter3.enabledItems.add(1);
+
+ CompositeListAdapter adapter = new CompositeListAdapter();
+ adapter.addAdapter(mAdapter1);
+ adapter.addAdapter(mAdapter3);
+
+ assertFalse(adapter.isEnabled(0));
+ assertTrue(adapter.isEnabled(1));
+ assertFalse(adapter.isEnabled(2));
+ assertTrue(adapter.isEnabled(3));
+ assertFalse(adapter.isEnabled(4));
+ }
+}
diff --git a/tests/src/com/android/contacts/widget/TestLoaderManager.java b/tests/src/com/android/contacts/widget/TestLoaderManager.java
new file mode 100644
index 0000000..e964024
--- /dev/null
+++ b/tests/src/com/android/contacts/widget/TestLoaderManager.java
@@ -0,0 +1,99 @@
+/*
+ * 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.widget;
+
+import android.app.LoaderManager;
+import android.content.AsyncTaskLoader;
+import android.content.Loader;
+import android.content.Loader.OnLoadCompleteListener;
+import android.os.Bundle;
+
+import java.util.LinkedHashMap;
+
+import junit.framework.Assert;
+
+/**
+ * A {@link LoaderManager} that performs synchronous loading on demand for unit
+ * testing.
+ */
+public class TestLoaderManager implements LoaderManager {
+
+ // Using a linked hash map to get all loading done in a predictable order.
+ private LinkedHashMap<Integer, Loader<?>> mStartedLoaders = new LinkedHashMap<
+ Integer, Loader<?>>();
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public <D> Loader<D> getLoader(int id) {
+ return (Loader<D>)mStartedLoaders.get(id);
+ }
+
+ @Override
+ public <D> Loader<D> initLoader(int id, Bundle args, final LoaderCallbacks<D> callbacks) {
+ Loader<D> loader = callbacks.onCreateLoader(id, args);
+ loader.registerListener(id, new OnLoadCompleteListener<D>() {
+ @Override
+ public void onLoadComplete(Loader<D> loader, D data) {
+ callbacks.onLoadFinished(loader, data);
+ }
+ });
+
+ mStartedLoaders.put(id, loader);
+ return loader;
+ }
+
+ @Override
+ public <D> Loader<D> restartLoader(int id, Bundle args, LoaderCallbacks<D> callbacks) {
+ return initLoader(id, args, callbacks);
+ }
+
+ @Override
+ public void stopLoader(int id) {
+ mStartedLoaders.get(id).stopLoading();
+ }
+
+ /**
+ * Synchronously runs all started loaders.
+ */
+ public void executeLoaders() {
+ for (Loader<?> loader : mStartedLoaders.values()) {
+ executeLoader(loader);
+ }
+ }
+
+ /**
+ * Synchronously runs the specified loader.
+ */
+ public void executeLoader(int id) {
+ Loader<?> loader = mStartedLoaders.get(id);
+ if (loader == null) {
+ Assert.fail("Loader not started: " + id);
+ }
+ executeLoader(loader);
+ }
+
+ @SuppressWarnings("unchecked")
+ private <D> void executeLoader(final Loader<D> loader) {
+ if (loader instanceof AsyncTaskLoader) {
+ AsyncTaskLoader<D> atLoader = (AsyncTaskLoader<D>)loader;
+ D data = atLoader.loadInBackground();
+ loader.deliverResult(data);
+ } else {
+ loader.forceLoad();
+ }
+ }
+}