am 906af3fb: (-s ours) Import revised translations. DO NOT MERGE
Merge commit '906af3fbedb9034c84fd18139fa16d3f542d1f67' into eclair
* commit '906af3fbedb9034c84fd18139fa16d3f542d1f67':
Import revised translations. DO NOT MERGE
diff --git a/Android.mk b/Android.mk
index a570324..2632f47 100644
--- a/Android.mk
+++ b/Android.mk
@@ -10,8 +10,6 @@
LOCAL_PACKAGE_NAME := Contacts
LOCAL_CERTIFICATE := shared
-LOCAL_STATIC_JAVA_LIBRARIES := googlelogin-client
-
include $(BUILD_PACKAGE)
# Use the folloing include to make our test apk.
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 9ff4b4b..5735306 100644
--- a/AndroidManifest.xml
+++ b/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.
@@ -15,9 +15,10 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.contacts"
- android:sharedUserId="android.uid.shared"
+ package="com.android.contacts"
+ android:sharedUserId="android.uid.shared"
>
+
<uses-permission android:name="android.permission.CALL_PRIVILEGED" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
@@ -27,6 +28,7 @@
<uses-permission android:name="com.google.android.googleapps.permission.GOOGLE_AUTH.mail" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.USE_CREDENTIALS" />
<application
android:label="@string/contactsList"
@@ -60,7 +62,7 @@
<!-- Tab container for TwelveKeyDialer and RecentCallsList -->
<activity android:name="DialtactsActivity"
android:label="@string/launcherDialer"
- android:theme="@android:style/Theme.NoTitleBar"
+ android:theme="@style/DialtactsTheme"
android:launchMode="singleTask"
android:clearTaskOnLaunch="true"
android:icon="@drawable/ic_launcher_phone"
@@ -108,8 +110,8 @@
<category android:name="android.intent.category.BROWSABLE" />
</intent-filter>
</activity>
-
- <!-- An empty activity that presents the DialtactActivity's Contacts tab -->
+
+ <!-- Tab container for Activity Stream and Contacts -->
<activity-alias android:name="DialtactsContactsEntryActivity"
android:targetActivity="DialtactsActivity"
android:label="@string/contactsList"
@@ -119,14 +121,16 @@
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="android.intent.category.BROWSABLE" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
- <data android:mimeType="vnd.android.cursor.dir/person" />
+ <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>
-
+
<intent-filter>
<action android:name="com.android.contacts.action.FILTER_CONTACTS" />
<category android:name="android.intent.category.DEFAULT" />
@@ -136,15 +140,13 @@
<!-- An empty activity that presents the DialtactActivity's Favorites tab -->
<activity-alias android:name="DialtactsFavoritesEntryActivity"
android:targetActivity="DialtactsActivity"
- android:label="@string/strequentList"
- android:icon="@drawable/ic_launcher_contacts"
>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity-alias>
-
+
<!-- The actual list of contacts, usually embedded in ContactsActivity -->
<activity android:name="ContactsListActivity"
android:label="@string/contactsList"
@@ -179,7 +181,7 @@
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.TAB" />
</intent-filter>
-
+
<intent-filter android:label="@string/frequentList">
<action android:name="com.android.contacts.action.LIST_FREQUENT" />
<category android:name="android.intent.category.DEFAULT" />
@@ -196,29 +198,30 @@
<action android:name="android.intent.action.INSERT_OR_EDIT" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="vnd.android.cursor.item/person" />
+ <data android:mimeType="vnd.android.cursor.item/contact" />
+ <data android:mimeType="vnd.android.cursor.item/raw_contact" />
</intent-filter>
-<!--
- <intent-filter android:label="Add To Contacts">
- <action android:name="com.android.contacts.action.ADD_CONTACT" />
- <category android:name="android.intent.category.SELECTED_ALTERNATIVE" />
- <data android:scheme="mailto" />
- <data android:scheme="tel" />
- </intent-filter>
--->
+
<intent-filter>
<action android:name="android.intent.action.PICK" />
<category android:name="android.intent.category.DEFAULT" />
- <data android:mimeType="vnd.android.cursor.dir/person" />
- <data android:mimeType="vnd.android.cursor.dir/phone" />
- <data android:mimeType="vnd.android.cursor.dir/postal-address" />
+ <data android:mimeType="vnd.android.cursor.dir/contact" android:host="com.android.contacts" />
+ <data android:mimeType="vnd.android.cursor.dir/person" android:host="contacts" />
+ <data android:mimeType="vnd.android.cursor.dir/phone_v2" android:host="com.android.contacts" />
+ <data android:mimeType="vnd.android.cursor.dir/phone" android:host="contacts" />
+ <data android:mimeType="vnd.android.cursor.dir/postal-address_v2" android:host="com.android.contacts" />
+ <data android:mimeType="vnd.android.cursor.dir/postal-address" android:host="contacts" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.GET_CONTENT" />
<category android:name="android.intent.category.DEFAULT" />
- <data android:mimeType="vnd.android.cursor.item/person" />
- <data android:mimeType="vnd.android.cursor.item/phone" />
- <data android:mimeType="vnd.android.cursor.item/postal-address" />
+ <data android:mimeType="vnd.android.cursor.item/contact" android:host="com.android.contacts" />
+ <data android:mimeType="vnd.android.cursor.item/person" android:host="contacts" />
+ <data android:mimeType="vnd.android.cursor.item/phone_v2" android:host="com.android.contacts" />
+ <data android:mimeType="vnd.android.cursor.item/phone" android:host="contacts" />
+ <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>
<intent-filter>
@@ -231,13 +234,46 @@
/>
</activity>
- <activity android:name="ShowOrCreateActivity"
- android:theme="@android:style/Theme.Translucent.NoTitleBar">
+ <!-- An activity for joining contacts -->
+ <activity android:name="ContactsListActivity$JoinContactActivity"
+ android:theme="@style/TallTitleBarTheme"
+ android:clearTaskOnLaunch="true"
+ >
+ <intent-filter>
+ <action android:name="com.android.contacts.action.JOIN_AGGREGATE" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+
+
+ <!-- Used to select display and sync groups -->
+ <activity android:name=".ui.DisplayGroupsActivity" android:label="@string/displayGroups" />
+
+ <activity
+ android:name=".ui.ShowOrCreateActivity"
+ android:theme="@style/FullyTranslucent">
+
<intent-filter>
<action android:name="com.android.contacts.action.SHOW_OR_CREATE_CONTACT" />
+ <category android:name="android.intent.category.DEFAULT" />
<data android:scheme="mailto" />
<data android:scheme="tel" />
+ </intent-filter>
+ </activity>
+
+ <!-- Used to show QuickContact window over a translucent activity, which is a
+ temporary hack until we add better framework support. -->
+ <activity
+ android:name=".ui.QuickContactActivity"
+ android:theme="@style/FullyTranslucent.QuickContact"
+ android:launchMode="singleTop"
+ android:excludeFromRecents="true"
+ android:taskAffinity="android.task.quickcontact">
+
+ <intent-filter>
+ <action android:name="com.android.contacts.action.QUICK_CONTACT" />
<category android:name="android.intent.category.DEFAULT" />
+ <data android:mimeType="vnd.android.cursor.item/contact" android:host="com.android.contacts" />
</intent-filter>
</activity>
@@ -256,7 +292,7 @@
<activity-alias android:name="alias.DialShortcut"
android:targetActivity="ContactsListActivity"
android:label="@string/shortcutDialContact"
- android:icon="@drawable/ic_launcher_shortcut_contact">
+ android:icon="@drawable/ic_launcher_shortcut_directdial">
<intent-filter>
<action android:name="android.intent.action.CREATE_SHORTCUT" />
@@ -268,7 +304,7 @@
<activity-alias android:name="alias.MessageShortcut"
android:targetActivity="ContactsListActivity"
android:label="@string/shortcutMessageContact"
- android:icon="@drawable/ic_launcher_shortcut_contact">
+ android:icon="@drawable/ic_launcher_shortcut_directmessage">
<intent-filter>
<action android:name="android.intent.action.CREATE_SHORTCUT" />
@@ -291,27 +327,54 @@
<!-- Views the details of a single contact -->
<activity android:name="ViewContactActivity"
android:label="@string/viewContactTitle"
- android:theme="@style/TallTitleBarTheme"
- >
+ android:theme="@style/TallTitleBarTheme">
+
<intent-filter android:label="@string/viewContactDesription">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
- <data android:mimeType="vnd.android.cursor.item/person" />
+ <data android:mimeType="vnd.android.cursor.item/person" android:host="contacts" />
+ <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>
</activity>
- <!-- Edits the details of a single contact -->
- <activity android:name="EditContactActivity"
- android:windowSoftInputMode="stateVisible|adjustResize">
+ <!-- Edit or insert details for a contact -->
+ <activity
+ android:name=".ui.EditContactActivity"
+ android:label="@string/editContactDescription"
+ android:windowSoftInputMode="stateVisible|adjustResize">
+
<intent-filter android:label="@string/editContactDescription">
<action android:name="android.intent.action.EDIT" />
<category android:name="android.intent.category.DEFAULT" />
- <data android:mimeType="vnd.android.cursor.item/person" />
+ <data android:mimeType="vnd.android.cursor.item/person" android:host="contacts" />
+ <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" />
<data android:mimeType="vnd.android.cursor.dir/person" />
+ <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
+ background threads to finish their operations. -->
+ <service
+ android:name=".util.EmptyService"
+ android:exported="false" />
+
+ <!-- Views the details of a single contact -->
+ <activity android:name="ContactOptionsActivity"
+ android:label="@string/contactOptionsTitle"
+ >
+ <intent-filter>
+ <action android:name="android.intent.action.EDIT" />
+ <category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
@@ -325,15 +388,10 @@
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
/>
- </activity>
-
- <!-- Activity used to select the groups that should be synced -->
- <activity android:name="ContactsGroupSyncSelector"
- android:label="@string/seclectSyncGroups_title"
- />
+ </activity>
<!-- Makes .ContactsListActivity the search target for any activity in Contacts -->
- <meta-data android:name="android.app.default_searchable"
+ <meta-data android:name="android.app.default_searchable"
android:value=".ContactsListActivity" />
@@ -371,8 +429,7 @@
<activity android:name=".ImportVCardActivity"
android:theme="@style/BackgroundOnly" />
+ <activity android:name=".ExportVCardActivity"
+ android:theme="@style/BackgroundOnly" />
</application>
</manifest>
-
-
-
diff --git a/res/anim/dummy_animation.xml b/res/anim/dummy_animation.xml
new file mode 100644
index 0000000..5b42f24
--- /dev/null
+++ b/res/anim/dummy_animation.xml
@@ -0,0 +1,24 @@
+<?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:fromXDelta="0"
+ android:toXDelta="0"
+ android:duration="@android:integer/config_shortAnimTime" />
diff --git a/res/anim/quickcontact.xml b/res/anim/quickcontact.xml
new file mode 100644
index 0000000..6fd1a27
--- /dev/null
+++ b/res/anim/quickcontact.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<translate
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:fromXDelta="100%p"
+ android:toXDelta="0"
+ android:duration="400" />
diff --git a/res/anim/quickcontact_above_enter.xml b/res/anim/quickcontact_above_enter.xml
new file mode 100644
index 0000000..dc2d053
--- /dev/null
+++ b/res/anim/quickcontact_above_enter.xml
@@ -0,0 +1,29 @@
+<?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.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+ <scale android:interpolator="@android:anim/decelerate_interpolator"
+ android:fromXScale="0.75" android:toXScale="1.0"
+ android:fromYScale="0.75" android:toYScale="1.0"
+ android:pivotX="50%" android:pivotY="100%"
+ android:duration="@android:integer/config_shortAnimTime" />
+ <alpha android:interpolator="@android:anim/decelerate_interpolator"
+ android:fromAlpha="0.0" android:toAlpha="1.0"
+ android:duration="@android:integer/config_shortAnimTime" />
+</set>
diff --git a/res/anim/quickcontact_above_exit.xml b/res/anim/quickcontact_above_exit.xml
new file mode 100644
index 0000000..dd34f87
--- /dev/null
+++ b/res/anim/quickcontact_above_exit.xml
@@ -0,0 +1,28 @@
+<?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.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+ android:interpolator="@android:anim/accelerate_interpolator">
+ <scale android:fromXScale="1.0" android:toXScale=".5"
+ android:fromYScale="1.0" android:toYScale=".5"
+ android:pivotX="50%" android:pivotY="100%"
+ android:duration="@android:integer/config_shortAnimTime" />
+ <alpha android:fromAlpha="1.0" android:toAlpha="0"
+ android:duration="@android:integer/config_shortAnimTime" />
+</set>
diff --git a/res/anim/quickcontact_below_enter.xml b/res/anim/quickcontact_below_enter.xml
new file mode 100644
index 0000000..9a1a577
--- /dev/null
+++ b/res/anim/quickcontact_below_enter.xml
@@ -0,0 +1,29 @@
+<?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.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+ <scale android:interpolator="@android:anim/decelerate_interpolator"
+ android:fromXScale="0.75" android:toXScale="1.0"
+ android:fromYScale="0.75" android:toYScale="1.0"
+ android:pivotX="50%" android:pivotY="0%"
+ android:duration="@android:integer/config_shortAnimTime" />
+ <alpha android:interpolator="@android:anim/decelerate_interpolator"
+ android:fromAlpha="0.0" android:toAlpha="1.0"
+ android:duration="@android:integer/config_shortAnimTime" />
+</set>
diff --git a/res/anim/quickcontact_below_exit.xml b/res/anim/quickcontact_below_exit.xml
new file mode 100644
index 0000000..7587c7a
--- /dev/null
+++ b/res/anim/quickcontact_below_exit.xml
@@ -0,0 +1,28 @@
+<?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.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+ android:interpolator="@android:anim/accelerate_interpolator">
+ <scale android:fromXScale="1.0" android:toXScale=".5"
+ android:fromYScale="1.0" android:toYScale=".5"
+ android:pivotX="50%" android:pivotY="0%"
+ android:duration="@android:integer/config_shortAnimTime" />
+ <alpha android:fromAlpha="1.0" android:toAlpha="0"
+ android:duration="@android:integer/config_shortAnimTime" />
+</set>
diff --git a/res/drawable-finger/dial_num_1.xml b/res/color-finger/kind_title.xml
similarity index 68%
copy from res/drawable-finger/dial_num_1.xml
copy to res/color-finger/kind_title.xml
index 48737b2..7489f75 100644
--- a/res/drawable-finger/dial_num_1.xml
+++ b/res/color-finger/kind_title.xml
@@ -1,5 +1,5 @@
<?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.
@@ -15,11 +15,5 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_pressed="true"
- android:drawable="@drawable/dial_num_1_blk" />
- <item android:state_focused="true"
- android:drawable="@drawable/dial_num_1_blk" />
- <item
- android:drawable="@drawable/dial_num_1_wht" />
+ <item android:color="@*android:color/bright_foreground_dark" />
</selector>
-
diff --git a/res/drawable-finger/dial_num_1.xml b/res/color-finger/tab_indicator_text.xml
similarity index 73%
copy from res/drawable-finger/dial_num_1.xml
copy to res/color-finger/tab_indicator_text.xml
index 48737b2..50ca824 100644
--- a/res/drawable-finger/dial_num_1.xml
+++ b/res/color-finger/tab_indicator_text.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.
@@ -15,11 +15,6 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_pressed="true"
- android:drawable="@drawable/dial_num_1_blk" />
- <item android:state_focused="true"
- android:drawable="@drawable/dial_num_1_blk" />
- <item
- android:drawable="@drawable/dial_num_1_wht" />
+ <item android:state_selected="true" android:color="@*android:color/dim_foreground_dark_inverse"/>
+ <item android:color="@*android:color/dim_foreground_dark"/> <!-- not selected -->
</selector>
-
diff --git a/res/drawable-finger/btn_dial.xml b/res/drawable-finger/btn_dial.xml
index 4dfcd64..22a785a 100644
--- a/res/drawable-finger/btn_dial.xml
+++ b/res/drawable-finger/btn_dial.xml
@@ -14,6 +14,9 @@
limitations under the License.
-->
+<!-- Background resource for digit buttons in the various dialpads
+ used by the Contacts app (see dialpad.xml).
+ -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true"
android:drawable="@drawable/btn_dial_pressed" />
diff --git a/res/drawable-finger/btn_dial_action.xml b/res/drawable-finger/btn_dial_action.xml
new file mode 100644
index 0000000..9ffb31b
--- /dev/null
+++ b/res/drawable-finger/btn_dial_action.xml
@@ -0,0 +1,32 @@
+<?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.
+-->
+
+<!-- Background resource for dial button for the various 12 key dialers. -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <!-- Disabled views -->
+ <item android:state_focused="true" android:state_enabled="false"
+ android:drawable="@drawable/btn_dial_action_middle_disable_focused" />
+ <item android:state_enabled="false"
+ android:drawable="@drawable/btn_dial_action_middle_disable" />
+
+ <!-- Enabled views -->
+ <item android:state_pressed="true" android:state_enabled="true"
+ android:drawable="@drawable/btn_dial_action_middle_pressed" />
+ <item android:state_focused="true" android:state_enabled="true"
+ android:drawable="@drawable/btn_dial_action_middle_selected" />
+ <item android:state_enabled="true"
+ android:drawable="@drawable/btn_dial_action_middle_normal" />
+</selector>
diff --git a/res/drawable-finger/btn_dial_delete.xml b/res/drawable-finger/btn_dial_delete.xml
index 235554d..b8c672d 100644
--- a/res/drawable-finger/btn_dial_delete.xml
+++ b/res/drawable-finger/btn_dial_delete.xml
@@ -1,5 +1,5 @@
<?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.
@@ -14,12 +14,20 @@
limitations under the License.
-->
+<!-- Background resource for backspace button for the various 12 key dialers. -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_pressed="true"
- android:drawable="@drawable/btn_dial_delete_pressed" />
- <item android:state_focused="true"
- android:drawable="@drawable/btn_dial_delete_selected" />
- <item
- android:drawable="@drawable/btn_dial_delete_normal" />
+ <!-- Disabled views -->
+ <item android:state_focused="true" android:state_enabled="false"
+ android:drawable="@drawable/btn_dial_action_right_disable_focused" />
+ <item android:state_enabled="false"
+ android:drawable="@drawable/btn_dial_action_right_disable" />
+
+ <!-- Enabled views -->
+ <item android:state_pressed="true" android:state_enabled="true"
+ android:drawable="@drawable/btn_dial_action_right_pressed" />
+ <item android:state_focused="true" android:state_enabled="true"
+ android:drawable="@drawable/btn_dial_action_right_selected" />
+ <item android:state_enabled="true"
+ android:drawable="@drawable/btn_dial_action_right_normal" />
</selector>
diff --git a/res/drawable-finger/btn_dial_delete_activated.9.png b/res/drawable-finger/btn_dial_delete_activated.9.png
deleted file mode 100644
index 453a368..0000000
--- a/res/drawable-finger/btn_dial_delete_activated.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-finger/btn_dial_delete_active.xml b/res/drawable-finger/btn_dial_delete_active.xml
deleted file mode 100644
index 3403f34..0000000
--- a/res/drawable-finger/btn_dial_delete_active.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_delete_pressed" />
- <item android:state_focused="true"
- android:drawable="@drawable/btn_dial_delete_selected" />
- <item
- android:drawable="@drawable/btn_dial_delete_activated" />
-</selector>
-
diff --git a/res/drawable-finger/btn_dial_delete_normal.9.png b/res/drawable-finger/btn_dial_delete_normal.9.png
deleted file mode 100644
index 9da1cff..0000000
--- a/res/drawable-finger/btn_dial_delete_normal.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-finger/btn_dial_delete_pressed.9.png b/res/drawable-finger/btn_dial_delete_pressed.9.png
deleted file mode 100644
index 5bbc20d..0000000
--- a/res/drawable-finger/btn_dial_delete_pressed.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-finger/btn_dial_delete_selected.9.png b/res/drawable-finger/btn_dial_delete_selected.9.png
deleted file mode 100644
index d959768..0000000
--- a/res/drawable-finger/btn_dial_delete_selected.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-finger/btn_dial_normal.png b/res/drawable-finger/btn_dial_normal.png
deleted file mode 100644
index ff8796b..0000000
--- a/res/drawable-finger/btn_dial_normal.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-finger/btn_dial_pressed.png b/res/drawable-finger/btn_dial_pressed.png
deleted file mode 100644
index 3c198c4..0000000
--- a/res/drawable-finger/btn_dial_pressed.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-finger/btn_dial_selected.png b/res/drawable-finger/btn_dial_selected.png
deleted file mode 100644
index 8ca2b0d..0000000
--- a/res/drawable-finger/btn_dial_selected.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-finger/btn_dial_textfield.xml b/res/drawable-finger/btn_dial_textfield.xml
index 4eabf18..109f6ae 100644
--- a/res/drawable-finger/btn_dial_textfield.xml
+++ b/res/drawable-finger/btn_dial_textfield.xml
@@ -15,10 +15,10 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_pressed="true"
+ <item android:state_pressed="true"
android:drawable="@drawable/btn_dial_textfield_pressed" />
<item android:state_focused="true"
- android:drawable="@drawable/btn_dial_textfield_selected" />
+ android:drawable="@drawable/btn_dial_textfield_normal" />
<item
android:drawable="@drawable/btn_dial_textfield_normal" />
</selector>
diff --git a/res/drawable-finger/btn_dial_textfield_activated.9.png b/res/drawable-finger/btn_dial_textfield_activated.9.png
deleted file mode 100644
index 4c34576..0000000
--- a/res/drawable-finger/btn_dial_textfield_activated.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-finger/btn_dial_textfield_active.xml b/res/drawable-finger/btn_dial_textfield_active.xml
index 18b84c6..246d543 100644
--- a/res/drawable-finger/btn_dial_textfield_active.xml
+++ b/res/drawable-finger/btn_dial_textfield_active.xml
@@ -15,10 +15,10 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_pressed="true"
+ <item android:state_pressed="true"
android:drawable="@drawable/btn_dial_textfield_pressed" />
<item android:state_focused="true"
- android:drawable="@drawable/btn_dial_textfield_selected" />
+ android:drawable="@drawable/btn_dial_textfield_activated" />
<item
android:drawable="@drawable/btn_dial_textfield_activated" />
</selector>
diff --git a/res/drawable-finger/btn_dial_textfield_normal.9.png b/res/drawable-finger/btn_dial_textfield_normal.9.png
deleted file mode 100644
index 40ab866..0000000
--- a/res/drawable-finger/btn_dial_textfield_normal.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-finger/btn_dial_textfield_pressed.9.png b/res/drawable-finger/btn_dial_textfield_pressed.9.png
deleted file mode 100644
index 0087327..0000000
--- a/res/drawable-finger/btn_dial_textfield_pressed.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-finger/btn_dial_textfield_selected.9.png b/res/drawable-finger/btn_dial_textfield_selected.9.png
deleted file mode 100644
index 63fceaa..0000000
--- a/res/drawable-finger/btn_dial_textfield_selected.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-finger/btn_dial_voicemail.xml b/res/drawable-finger/btn_dial_voicemail.xml
new file mode 100644
index 0000000..8669f0f
--- /dev/null
+++ b/res/drawable-finger/btn_dial_voicemail.xml
@@ -0,0 +1,32 @@
+<?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.
+-->
+
+<!-- Background resource for dial voicemail button for the various 12 key dialers. -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <!-- Disabled views -->
+ <item android:state_focused="true" android:state_enabled="false"
+ android:drawable="@drawable/btn_dial_action_left_disable_focused" />
+ <item android:state_enabled="false"
+ android:drawable="@drawable/btn_dial_action_left_disable" />
+
+ <!-- Enabled views -->
+ <item android:state_pressed="true" android:state_enabled="true"
+ android:drawable="@drawable/btn_dial_action_left_pressed" />
+ <item android:state_focused="true" android:state_enabled="true"
+ android:drawable="@drawable/btn_dial_action_left_selected" />
+ <item android:state_enabled="true"
+ android:drawable="@drawable/btn_dial_action_left_normal" />
+</selector>
diff --git a/res/drawable-finger/dial_num_0_blk.png b/res/drawable-finger/dial_num_0_blk.png
deleted file mode 100644
index a10c601..0000000
--- a/res/drawable-finger/dial_num_0_blk.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-finger/dial_num_0_wht.png b/res/drawable-finger/dial_num_0_wht.png
deleted file mode 100644
index 2de90a1..0000000
--- a/res/drawable-finger/dial_num_0_wht.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-finger/dial_num_1_blk.png b/res/drawable-finger/dial_num_1_blk.png
deleted file mode 100644
index 822f9ca..0000000
--- a/res/drawable-finger/dial_num_1_blk.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-finger/dial_num_1.xml b/res/drawable-finger/dial_num_1_no_vm.xml
similarity index 78%
rename from res/drawable-finger/dial_num_1.xml
rename to res/drawable-finger/dial_num_1_no_vm.xml
index 48737b2..daa4613 100644
--- a/res/drawable-finger/dial_num_1.xml
+++ b/res/drawable-finger/dial_num_1_no_vm.xml
@@ -15,11 +15,11 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_pressed="true"
- android:drawable="@drawable/dial_num_1_blk" />
+ <item android:state_pressed="true"
+ android:drawable="@drawable/dial_num_1_no_vm_blk" />
<item android:state_focused="true"
- android:drawable="@drawable/dial_num_1_blk" />
+ android:drawable="@drawable/dial_num_1_no_vm_blk" />
<item
- android:drawable="@drawable/dial_num_1_wht" />
+ android:drawable="@drawable/dial_num_1_no_vm_wht" />
</selector>
diff --git a/res/drawable-finger/dial_num_1_wht.png b/res/drawable-finger/dial_num_1_wht.png
deleted file mode 100644
index e488beb..0000000
--- a/res/drawable-finger/dial_num_1_wht.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-finger/dial_num_2_blk.png b/res/drawable-finger/dial_num_2_blk.png
deleted file mode 100644
index 539c301..0000000
--- a/res/drawable-finger/dial_num_2_blk.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-finger/dial_num_2_wht.png b/res/drawable-finger/dial_num_2_wht.png
deleted file mode 100644
index cd1ae8d..0000000
--- a/res/drawable-finger/dial_num_2_wht.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-finger/dial_num_3_blk.png b/res/drawable-finger/dial_num_3_blk.png
deleted file mode 100644
index dc70f33..0000000
--- a/res/drawable-finger/dial_num_3_blk.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-finger/dial_num_3_wht.png b/res/drawable-finger/dial_num_3_wht.png
deleted file mode 100644
index dc7c8e2..0000000
--- a/res/drawable-finger/dial_num_3_wht.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-finger/dial_num_4_blk.png b/res/drawable-finger/dial_num_4_blk.png
deleted file mode 100644
index 084db44..0000000
--- a/res/drawable-finger/dial_num_4_blk.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-finger/dial_num_4_wht.png b/res/drawable-finger/dial_num_4_wht.png
deleted file mode 100644
index 44128a1..0000000
--- a/res/drawable-finger/dial_num_4_wht.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-finger/dial_num_5_blk.png b/res/drawable-finger/dial_num_5_blk.png
deleted file mode 100644
index 0a06f20..0000000
--- a/res/drawable-finger/dial_num_5_blk.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-finger/dial_num_5_wht.png b/res/drawable-finger/dial_num_5_wht.png
deleted file mode 100644
index fc605b8..0000000
--- a/res/drawable-finger/dial_num_5_wht.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-finger/dial_num_6_blk.png b/res/drawable-finger/dial_num_6_blk.png
deleted file mode 100644
index 2498d77..0000000
--- a/res/drawable-finger/dial_num_6_blk.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-finger/dial_num_6_wht.png b/res/drawable-finger/dial_num_6_wht.png
deleted file mode 100644
index 086e8ce..0000000
--- a/res/drawable-finger/dial_num_6_wht.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-finger/dial_num_7_blk.png b/res/drawable-finger/dial_num_7_blk.png
deleted file mode 100644
index cfb20a7..0000000
--- a/res/drawable-finger/dial_num_7_blk.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-finger/dial_num_7_wht.png b/res/drawable-finger/dial_num_7_wht.png
deleted file mode 100644
index cb1b097..0000000
--- a/res/drawable-finger/dial_num_7_wht.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-finger/dial_num_8_blk.png b/res/drawable-finger/dial_num_8_blk.png
deleted file mode 100644
index 845ee68..0000000
--- a/res/drawable-finger/dial_num_8_blk.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-finger/dial_num_8_wht.png b/res/drawable-finger/dial_num_8_wht.png
deleted file mode 100644
index 1954f10..0000000
--- a/res/drawable-finger/dial_num_8_wht.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-finger/dial_num_9_blk.png b/res/drawable-finger/dial_num_9_blk.png
deleted file mode 100644
index 752df25..0000000
--- a/res/drawable-finger/dial_num_9_blk.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-finger/dial_num_9_wht.png b/res/drawable-finger/dial_num_9_wht.png
deleted file mode 100644
index d8b5aa1..0000000
--- a/res/drawable-finger/dial_num_9_wht.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-finger/dial_num_pound_blk.png b/res/drawable-finger/dial_num_pound_blk.png
deleted file mode 100644
index 4dabda4..0000000
--- a/res/drawable-finger/dial_num_pound_blk.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-finger/dial_num_pound_wht.png b/res/drawable-finger/dial_num_pound_wht.png
deleted file mode 100644
index f27ae87..0000000
--- a/res/drawable-finger/dial_num_pound_wht.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-finger/dial_num_star_blk.png b/res/drawable-finger/dial_num_star_blk.png
deleted file mode 100644
index af917ef..0000000
--- a/res/drawable-finger/dial_num_star_blk.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-finger/dial_num_star_wht.png b/res/drawable-finger/dial_num_star_wht.png
deleted file mode 100644
index ffbd43f..0000000
--- a/res/drawable-finger/dial_num_star_wht.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-finger/divider_vertical_dark.png b/res/drawable-finger/divider_vertical_dark.png
deleted file mode 100644
index dcf850e..0000000
--- a/res/drawable-finger/divider_vertical_dark.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-finger/ic_default_number.png b/res/drawable-finger/ic_default_number.png
deleted file mode 100644
index a27685e..0000000
--- a/res/drawable-finger/ic_default_number.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-finger/ic_delete_phone_number_blk.png b/res/drawable-finger/ic_delete_phone_number_blk.png
deleted file mode 100644
index 2e83d36..0000000
--- a/res/drawable-finger/ic_delete_phone_number_blk.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-finger/ic_delete_phone_number_wht.png b/res/drawable-finger/ic_delete_phone_number_wht.png
deleted file mode 100644
index 988bcfd..0000000
--- a/res/drawable-finger/ic_delete_phone_number_wht.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-finger/dial_num_1.xml b/res/drawable-finger/ic_tab_friends.xml
similarity index 74%
copy from res/drawable-finger/dial_num_1.xml
copy to res/drawable-finger/ic_tab_friends.xml
index 48737b2..58234ae 100644
--- a/res/drawable-finger/dial_num_1.xml
+++ b/res/drawable-finger/ic_tab_friends.xml
@@ -15,11 +15,7 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_pressed="true"
- android:drawable="@drawable/dial_num_1_blk" />
- <item android:state_focused="true"
- android:drawable="@drawable/dial_num_1_blk" />
- <item
- android:drawable="@drawable/dial_num_1_wht" />
+ <item android:state_selected="true" android:state_pressed="false" android:drawable="@drawable/ic_tab_selected_friends_list" />
+ <item android:drawable="@drawable/ic_tab_unselected_friends_list" />
</selector>
diff --git a/res/drawable-finger/ic_tab_selected_contacts.png b/res/drawable-finger/ic_tab_selected_contacts.png
deleted file mode 100644
index 16c4a91..0000000
--- a/res/drawable-finger/ic_tab_selected_contacts.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-finger/ic_tab_selected_dialer.png b/res/drawable-finger/ic_tab_selected_dialer.png
deleted file mode 100644
index cb715ed..0000000
--- a/res/drawable-finger/ic_tab_selected_dialer.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-finger/ic_tab_selected_recent.png b/res/drawable-finger/ic_tab_selected_recent.png
deleted file mode 100644
index bdbfc41..0000000
--- a/res/drawable-finger/ic_tab_selected_recent.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-finger/ic_tab_selected_starred.png b/res/drawable-finger/ic_tab_selected_starred.png
deleted file mode 100644
index 1b884cb..0000000
--- a/res/drawable-finger/ic_tab_selected_starred.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-finger/ic_tab_unselected_contacts.png b/res/drawable-finger/ic_tab_unselected_contacts.png
deleted file mode 100644
index 7b5b84f..0000000
--- a/res/drawable-finger/ic_tab_unselected_contacts.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-finger/ic_tab_unselected_dialer.png b/res/drawable-finger/ic_tab_unselected_dialer.png
deleted file mode 100644
index dc3c5d8..0000000
--- a/res/drawable-finger/ic_tab_unselected_dialer.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-finger/ic_tab_unselected_recent.png b/res/drawable-finger/ic_tab_unselected_recent.png
deleted file mode 100644
index 712e405..0000000
--- a/res/drawable-finger/ic_tab_unselected_recent.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-finger/ic_tab_unselected_starred.png b/res/drawable-finger/ic_tab_unselected_starred.png
deleted file mode 100644
index 8eaccb8..0000000
--- a/res/drawable-finger/ic_tab_unselected_starred.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-finger/dial_num_1.xml b/res/drawable-finger/quickcontact_disambig_checkbox.xml
similarity index 69%
copy from res/drawable-finger/dial_num_1.xml
copy to res/drawable-finger/quickcontact_disambig_checkbox.xml
index 48737b2..4add69c 100644
--- a/res/drawable-finger/dial_num_1.xml
+++ b/res/drawable-finger/quickcontact_disambig_checkbox.xml
@@ -1,5 +1,5 @@
<?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.
@@ -14,12 +14,12 @@
limitations under the License.
-->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_pressed="true"
- android:drawable="@drawable/dial_num_1_blk" />
- <item android:state_focused="true"
- android:drawable="@drawable/dial_num_1_blk" />
- <item
- android:drawable="@drawable/dial_num_1_wht" />
-</selector>
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:dither="true">
+ <item android:state_checked="true"
+ android:drawable="@drawable/quickcontact_disambig_checkbox_on" />
+ <item
+ android:drawable="@drawable/quickcontact_disambig_checkbox_off" />
+
+</selector>
diff --git a/res/drawable-finger/quickcontact_slider_btn.xml b/res/drawable-finger/quickcontact_slider_btn.xml
new file mode 100644
index 0000000..a1be8f4
--- /dev/null
+++ b/res/drawable-finger/quickcontact_slider_btn.xml
@@ -0,0 +1,31 @@
+<?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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:dither="true">
+
+ <item android:state_checked="true"
+ android:drawable="@drawable/quickcontact_slider_btn_on" />
+ <item android:state_window_focused="false"
+ android:drawable="@drawable/quickcontact_slider_btn_normal" />
+ <item android:state_pressed="true"
+ android:drawable="@drawable/quickcontact_slider_btn_pressed" />
+ <item android:state_focused="true"
+ android:drawable="@drawable/quickcontact_slider_btn_selected" />
+ <item
+ android:drawable="@drawable/quickcontact_slider_btn_normal" />
+
+</selector>
diff --git a/res/drawable-finger/sym_action_sms.png b/res/drawable-finger/sym_action_sms.png
deleted file mode 100644
index e0ce4bb..0000000
--- a/res/drawable-finger/sym_action_sms.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-finger/tab_bottom.xml b/res/drawable-finger/tab_bottom.xml
new file mode 100644
index 0000000..96f1a24
--- /dev/null
+++ b/res/drawable-finger/tab_bottom.xml
@@ -0,0 +1,41 @@
+<?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/tab_pressed_bottom"/>
+ <item android:state_focused="false" android:drawable="@drawable/tab_selected_bottom"/>
+ <item android:state_focused="true" android:drawable="@drawable/tab_focused_bottom"/>
+ -->
+
+ <item
+ android:state_focused="false"
+ android:state_selected="true"
+ android:state_pressed="false"
+ android:drawable="@drawable/tab_selected_bottom" />
+
+ <!-- Focused states -->
+ <item
+ android:state_focused="true"
+ android:state_selected="true"
+ android:state_pressed="false"
+ android:drawable="@drawable/tab_focused_bottom" />
+
+ <!-- Pressed -->
+ <item
+ android:state_pressed="true"
+ android:drawable="@drawable/tab_pressed_bottom" />
+</selector>
\ No newline at end of file
diff --git a/res/drawable-finger/tab_indicator_bg.xml b/res/drawable-finger/tab_indicator_bg.xml
new file mode 100644
index 0000000..fb54954
--- /dev/null
+++ b/res/drawable-finger/tab_indicator_bg.xml
@@ -0,0 +1,42 @@
+<?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">
+ <!-- Non focused states -->
+ <item
+ android:state_focused="false"
+ android:state_selected="false"
+ android:state_pressed="false"
+ android:drawable="@drawable/tab_unselected" />
+ <item
+ android:state_focused="false"
+ android:state_selected="true"
+ android:state_pressed="false"
+ android:drawable="@drawable/tab_selected" />
+
+ <!-- Focused states -->
+ <item
+ android:state_focused="true"
+ android:state_selected="true"
+ android:state_pressed="false"
+ android:drawable="@drawable/tab_focused" />
+
+ <!-- Pressed -->
+ <item
+ android:state_pressed="true"
+ android:drawable="@drawable/tab_pressed" />
+
+</selector>
\ No newline at end of file
diff --git a/res/drawable-hdpi-finger/badge_action_call.png b/res/drawable-hdpi-finger/badge_action_call.png
new file mode 100755
index 0000000..105f7d0
--- /dev/null
+++ b/res/drawable-hdpi-finger/badge_action_call.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/badge_action_sms.png b/res/drawable-hdpi-finger/badge_action_sms.png
new file mode 100755
index 0000000..a7862f6
--- /dev/null
+++ b/res/drawable-hdpi-finger/badge_action_sms.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/btn_circle_disable.png b/res/drawable-hdpi-finger/btn_circle_disable.png
new file mode 100755
index 0000000..ae063b5
--- /dev/null
+++ b/res/drawable-hdpi-finger/btn_circle_disable.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/btn_circle_disable_focused.png b/res/drawable-hdpi-finger/btn_circle_disable_focused.png
new file mode 100755
index 0000000..7a5d4fe
--- /dev/null
+++ b/res/drawable-hdpi-finger/btn_circle_disable_focused.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/btn_circle_normal.png b/res/drawable-hdpi-finger/btn_circle_normal.png
new file mode 100755
index 0000000..5eda668
--- /dev/null
+++ b/res/drawable-hdpi-finger/btn_circle_normal.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/btn_circle_pressed.png b/res/drawable-hdpi-finger/btn_circle_pressed.png
new file mode 100755
index 0000000..88848ba
--- /dev/null
+++ b/res/drawable-hdpi-finger/btn_circle_pressed.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/btn_circle_selected.png b/res/drawable-hdpi-finger/btn_circle_selected.png
new file mode 100755
index 0000000..7469070
--- /dev/null
+++ b/res/drawable-hdpi-finger/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-finger/btn_dial_action_left_disable.9.png
new file mode 100644
index 0000000..7ba8672
--- /dev/null
+++ b/res/drawable-hdpi-finger/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-finger/btn_dial_action_left_disable_focused.9.png
new file mode 100644
index 0000000..b4300b6
--- /dev/null
+++ b/res/drawable-hdpi-finger/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-finger/btn_dial_action_left_normal.9.png
new file mode 100644
index 0000000..7ba8672
--- /dev/null
+++ b/res/drawable-hdpi-finger/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-finger/btn_dial_action_left_pressed.9.png
new file mode 100644
index 0000000..1dc40b7
--- /dev/null
+++ b/res/drawable-hdpi-finger/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-finger/btn_dial_action_left_selected.9.png
new file mode 100644
index 0000000..4f6c7cf
--- /dev/null
+++ b/res/drawable-hdpi-finger/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-finger/btn_dial_action_middle_disable.9.png
new file mode 100644
index 0000000..0a6cd66
--- /dev/null
+++ b/res/drawable-hdpi-finger/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-finger/btn_dial_action_middle_disable_focused.9.png
new file mode 100644
index 0000000..b28176f
--- /dev/null
+++ b/res/drawable-hdpi-finger/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-finger/btn_dial_action_middle_normal.9.png
new file mode 100644
index 0000000..0a6cd66
--- /dev/null
+++ b/res/drawable-hdpi-finger/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-finger/btn_dial_action_middle_pressed.9.png
new file mode 100644
index 0000000..58f187d
--- /dev/null
+++ b/res/drawable-hdpi-finger/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-finger/btn_dial_action_middle_selected.9.png
new file mode 100644
index 0000000..f201dee
--- /dev/null
+++ b/res/drawable-hdpi-finger/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-finger/btn_dial_action_right_disable.9.png
new file mode 100644
index 0000000..6e6fa30
--- /dev/null
+++ b/res/drawable-hdpi-finger/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-finger/btn_dial_action_right_disable_focused.9.png
new file mode 100644
index 0000000..46a042f
--- /dev/null
+++ b/res/drawable-hdpi-finger/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-finger/btn_dial_action_right_normal.9.png
new file mode 100644
index 0000000..6e6fa30
--- /dev/null
+++ b/res/drawable-hdpi-finger/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-finger/btn_dial_action_right_pressed.9.png
new file mode 100644
index 0000000..d66a509
--- /dev/null
+++ b/res/drawable-hdpi-finger/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-finger/btn_dial_action_right_selected.9.png
new file mode 100644
index 0000000..d2ee98b
--- /dev/null
+++ b/res/drawable-hdpi-finger/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-finger/btn_dial_normal.9.png
new file mode 100644
index 0000000..5702e47
--- /dev/null
+++ b/res/drawable-hdpi-finger/btn_dial_normal.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/btn_dial_normal_blue.9.png b/res/drawable-hdpi-finger/btn_dial_normal_blue.9.png
new file mode 100644
index 0000000..ddad155
--- /dev/null
+++ b/res/drawable-hdpi-finger/btn_dial_normal_blue.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/btn_dial_normal_green.9.png b/res/drawable-hdpi-finger/btn_dial_normal_green.9.png
new file mode 100644
index 0000000..dbb0f97
--- /dev/null
+++ b/res/drawable-hdpi-finger/btn_dial_normal_green.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/btn_dial_pressed.9.png b/res/drawable-hdpi-finger/btn_dial_pressed.9.png
new file mode 100644
index 0000000..a5b51d5
--- /dev/null
+++ b/res/drawable-hdpi-finger/btn_dial_pressed.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/btn_dial_selected.9.png b/res/drawable-hdpi-finger/btn_dial_selected.9.png
new file mode 100644
index 0000000..b578146
--- /dev/null
+++ b/res/drawable-hdpi-finger/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-finger/btn_dial_textfield_activated.9.png
new file mode 100644
index 0000000..c937c5e
--- /dev/null
+++ b/res/drawable-hdpi-finger/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-finger/btn_dial_textfield_normal.9.png
new file mode 100644
index 0000000..0c38b39
--- /dev/null
+++ b/res/drawable-hdpi-finger/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-finger/btn_dial_textfield_pressed.9.png
new file mode 100644
index 0000000..22d8235
--- /dev/null
+++ b/res/drawable-hdpi-finger/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-finger/btn_dial_textfield_selected.9.png
new file mode 100644
index 0000000..1fe4dfc
--- /dev/null
+++ b/res/drawable-hdpi-finger/btn_dial_textfield_selected.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/contact_picture_border_highlight.9.png b/res/drawable-hdpi-finger/contact_picture_border_highlight.9.png
new file mode 100755
index 0000000..776d614
--- /dev/null
+++ b/res/drawable-hdpi-finger/contact_picture_border_highlight.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/contact_picture_border_in_list.9.png b/res/drawable-hdpi-finger/contact_picture_border_in_list.9.png
new file mode 100755
index 0000000..8166d4f
--- /dev/null
+++ b/res/drawable-hdpi-finger/contact_picture_border_in_list.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/contact_picture_border_normal.9.png b/res/drawable-hdpi-finger/contact_picture_border_normal.9.png
new file mode 100755
index 0000000..be67b1a
--- /dev/null
+++ b/res/drawable-hdpi-finger/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-finger/contact_picture_border_pressed.9.png
new file mode 100755
index 0000000..32fbaa5
--- /dev/null
+++ b/res/drawable-hdpi-finger/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-finger/dial_num_0_blk.png
new file mode 100644
index 0000000..960d968
--- /dev/null
+++ b/res/drawable-hdpi-finger/dial_num_0_blk.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/dial_num_0_wht.png b/res/drawable-hdpi-finger/dial_num_0_wht.png
new file mode 100644
index 0000000..c257817
--- /dev/null
+++ b/res/drawable-hdpi-finger/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-finger/dial_num_1_no_vm_blk.png
new file mode 100644
index 0000000..d150354
--- /dev/null
+++ b/res/drawable-hdpi-finger/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-finger/dial_num_1_no_vm_wht.png
new file mode 100644
index 0000000..9a1152b
--- /dev/null
+++ b/res/drawable-hdpi-finger/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-finger/dial_num_2_blk.png
new file mode 100644
index 0000000..7b0cee7
--- /dev/null
+++ b/res/drawable-hdpi-finger/dial_num_2_blk.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/dial_num_2_wht.png b/res/drawable-hdpi-finger/dial_num_2_wht.png
new file mode 100644
index 0000000..cad5485
--- /dev/null
+++ b/res/drawable-hdpi-finger/dial_num_2_wht.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/dial_num_3_blk.png b/res/drawable-hdpi-finger/dial_num_3_blk.png
new file mode 100644
index 0000000..d2efe88
--- /dev/null
+++ b/res/drawable-hdpi-finger/dial_num_3_blk.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/dial_num_3_wht.png b/res/drawable-hdpi-finger/dial_num_3_wht.png
new file mode 100644
index 0000000..ddb890c
--- /dev/null
+++ b/res/drawable-hdpi-finger/dial_num_3_wht.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/dial_num_4_blk.png b/res/drawable-hdpi-finger/dial_num_4_blk.png
new file mode 100644
index 0000000..fc3ed43
--- /dev/null
+++ b/res/drawable-hdpi-finger/dial_num_4_blk.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/dial_num_4_wht.png b/res/drawable-hdpi-finger/dial_num_4_wht.png
new file mode 100644
index 0000000..bb8064c
--- /dev/null
+++ b/res/drawable-hdpi-finger/dial_num_4_wht.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/dial_num_5_blk.png b/res/drawable-hdpi-finger/dial_num_5_blk.png
new file mode 100644
index 0000000..5c78c75
--- /dev/null
+++ b/res/drawable-hdpi-finger/dial_num_5_blk.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/dial_num_5_wht.png b/res/drawable-hdpi-finger/dial_num_5_wht.png
new file mode 100644
index 0000000..1368d36
--- /dev/null
+++ b/res/drawable-hdpi-finger/dial_num_5_wht.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/dial_num_6_blk.png b/res/drawable-hdpi-finger/dial_num_6_blk.png
new file mode 100644
index 0000000..583fb2a
--- /dev/null
+++ b/res/drawable-hdpi-finger/dial_num_6_blk.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/dial_num_6_wht.png b/res/drawable-hdpi-finger/dial_num_6_wht.png
new file mode 100644
index 0000000..7f1bf4a
--- /dev/null
+++ b/res/drawable-hdpi-finger/dial_num_6_wht.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/dial_num_7_blk.png b/res/drawable-hdpi-finger/dial_num_7_blk.png
new file mode 100644
index 0000000..793660b
--- /dev/null
+++ b/res/drawable-hdpi-finger/dial_num_7_blk.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/dial_num_7_wht.png b/res/drawable-hdpi-finger/dial_num_7_wht.png
new file mode 100644
index 0000000..d8038c7
--- /dev/null
+++ b/res/drawable-hdpi-finger/dial_num_7_wht.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/dial_num_8_blk.png b/res/drawable-hdpi-finger/dial_num_8_blk.png
new file mode 100644
index 0000000..0ee87fe
--- /dev/null
+++ b/res/drawable-hdpi-finger/dial_num_8_blk.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/dial_num_8_wht.png b/res/drawable-hdpi-finger/dial_num_8_wht.png
new file mode 100644
index 0000000..9d0d0eb
--- /dev/null
+++ b/res/drawable-hdpi-finger/dial_num_8_wht.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/dial_num_9_blk.png b/res/drawable-hdpi-finger/dial_num_9_blk.png
new file mode 100644
index 0000000..920235a
--- /dev/null
+++ b/res/drawable-hdpi-finger/dial_num_9_blk.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/dial_num_9_wht.png b/res/drawable-hdpi-finger/dial_num_9_wht.png
new file mode 100644
index 0000000..ac3727d
--- /dev/null
+++ b/res/drawable-hdpi-finger/dial_num_9_wht.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/dial_num_pound_blk.png b/res/drawable-hdpi-finger/dial_num_pound_blk.png
new file mode 100644
index 0000000..fcfc58c
--- /dev/null
+++ b/res/drawable-hdpi-finger/dial_num_pound_blk.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/dial_num_pound_wht.png b/res/drawable-hdpi-finger/dial_num_pound_wht.png
new file mode 100644
index 0000000..df67810
--- /dev/null
+++ b/res/drawable-hdpi-finger/dial_num_pound_wht.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/dial_num_star_blk.png b/res/drawable-hdpi-finger/dial_num_star_blk.png
new file mode 100644
index 0000000..ddda118
--- /dev/null
+++ b/res/drawable-hdpi-finger/dial_num_star_blk.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/dial_num_star_wht.png b/res/drawable-hdpi-finger/dial_num_star_wht.png
new file mode 100644
index 0000000..ded1900
--- /dev/null
+++ b/res/drawable-hdpi-finger/dial_num_star_wht.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/divider_vertical_dark.9.png b/res/drawable-hdpi-finger/divider_vertical_dark.9.png
new file mode 100644
index 0000000..702b878
--- /dev/null
+++ b/res/drawable-hdpi-finger/divider_vertical_dark.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/ic_btn_round_minus.png b/res/drawable-hdpi-finger/ic_btn_round_minus.png
new file mode 100755
index 0000000..27af3fa
--- /dev/null
+++ b/res/drawable-hdpi-finger/ic_btn_round_minus.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/ic_btn_round_more.png b/res/drawable-hdpi-finger/ic_btn_round_more.png
new file mode 100755
index 0000000..9883d55
--- /dev/null
+++ b/res/drawable-hdpi-finger/ic_btn_round_more.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/ic_btn_round_plus.png b/res/drawable-hdpi-finger/ic_btn_round_plus.png
new file mode 100755
index 0000000..b24168c
--- /dev/null
+++ b/res/drawable-hdpi-finger/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-finger/ic_call_log_header_incoming_call.png
new file mode 100755
index 0000000..95c0255
--- /dev/null
+++ b/res/drawable-hdpi-finger/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-finger/ic_call_log_header_missed_call.png
new file mode 100755
index 0000000..0a43e69
--- /dev/null
+++ b/res/drawable-hdpi-finger/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-finger/ic_call_log_header_outgoing_call.png
new file mode 100755
index 0000000..d061ba3
--- /dev/null
+++ b/res/drawable-hdpi-finger/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-finger/ic_call_log_list_incoming_call.png
new file mode 100755
index 0000000..9c016fa
--- /dev/null
+++ b/res/drawable-hdpi-finger/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-finger/ic_call_log_list_missed_call.png
new file mode 100755
index 0000000..8dcb279
--- /dev/null
+++ b/res/drawable-hdpi-finger/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-finger/ic_call_log_list_outgoing_call.png
new file mode 100755
index 0000000..256de19
--- /dev/null
+++ b/res/drawable-hdpi-finger/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-finger/ic_contact_list_picture.png
new file mode 100755
index 0000000..296ab9f
--- /dev/null
+++ b/res/drawable-hdpi-finger/ic_contact_list_picture.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/ic_contact_picture.png b/res/drawable-hdpi-finger/ic_contact_picture.png
new file mode 100755
index 0000000..7c34f5c
--- /dev/null
+++ b/res/drawable-hdpi-finger/ic_contact_picture.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/ic_contact_picture_2.png b/res/drawable-hdpi-finger/ic_contact_picture_2.png
new file mode 100755
index 0000000..5e65276
--- /dev/null
+++ b/res/drawable-hdpi-finger/ic_contact_picture_2.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/ic_contact_picture_3.png b/res/drawable-hdpi-finger/ic_contact_picture_3.png
new file mode 100755
index 0000000..a8ec1e1
--- /dev/null
+++ b/res/drawable-hdpi-finger/ic_contact_picture_3.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/ic_default_number.png b/res/drawable-hdpi-finger/ic_default_number.png
new file mode 100755
index 0000000..cdc05a8
--- /dev/null
+++ b/res/drawable-hdpi-finger/ic_default_number.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/ic_dial_action_call.png b/res/drawable-hdpi-finger/ic_dial_action_call.png
new file mode 100644
index 0000000..1ba3a98
--- /dev/null
+++ b/res/drawable-hdpi-finger/ic_dial_action_call.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/ic_dial_action_delete.png b/res/drawable-hdpi-finger/ic_dial_action_delete.png
new file mode 100644
index 0000000..2e206c8
--- /dev/null
+++ b/res/drawable-hdpi-finger/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-finger/ic_dial_action_voice_mail.png
new file mode 100644
index 0000000..8a3f366
--- /dev/null
+++ b/res/drawable-hdpi-finger/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-finger/ic_dial_number_blk.png
new file mode 100755
index 0000000..cad1d80
--- /dev/null
+++ b/res/drawable-hdpi-finger/ic_dial_number_blk.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/ic_dial_number_wht.png b/res/drawable-hdpi-finger/ic_dial_number_wht.png
new file mode 100755
index 0000000..54f5ac0
--- /dev/null
+++ b/res/drawable-hdpi-finger/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-finger/ic_dialer_fork_add_call.png
new file mode 100755
index 0000000..e046996
--- /dev/null
+++ b/res/drawable-hdpi-finger/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-finger/ic_dialer_fork_current_call.png
new file mode 100755
index 0000000..6e1a395
--- /dev/null
+++ b/res/drawable-hdpi-finger/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-finger/ic_dialer_fork_tt_keypad.png
new file mode 100755
index 0000000..6b50da1
--- /dev/null
+++ b/res/drawable-hdpi-finger/ic_dialer_fork_tt_keypad.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/ic_join.png b/res/drawable-hdpi-finger/ic_join.png
new file mode 100644
index 0000000..8f140d4
--- /dev/null
+++ b/res/drawable-hdpi-finger/ic_join.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/ic_list_split.png b/res/drawable-hdpi-finger/ic_list_split.png
new file mode 100644
index 0000000..92f486b
--- /dev/null
+++ b/res/drawable-hdpi-finger/ic_list_split.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/ic_menu_account_list.png b/res/drawable-hdpi-finger/ic_menu_account_list.png
new file mode 100755
index 0000000..a69c642
--- /dev/null
+++ b/res/drawable-hdpi-finger/ic_menu_account_list.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/ic_menu_add_picture.png b/res/drawable-hdpi-finger/ic_menu_add_picture.png
new file mode 100755
index 0000000..85faf1c
--- /dev/null
+++ b/res/drawable-hdpi-finger/ic_menu_add_picture.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/ic_menu_export_contact.png b/res/drawable-hdpi-finger/ic_menu_export_contact.png
new file mode 100755
index 0000000..c9781f7
--- /dev/null
+++ b/res/drawable-hdpi-finger/ic_menu_export_contact.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/ic_menu_import_contact.png b/res/drawable-hdpi-finger/ic_menu_import_contact.png
new file mode 100755
index 0000000..18de61f
--- /dev/null
+++ b/res/drawable-hdpi-finger/ic_menu_import_contact.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/ic_menu_import_export.png b/res/drawable-hdpi-finger/ic_menu_import_export.png
new file mode 100644
index 0000000..4f1b608
--- /dev/null
+++ b/res/drawable-hdpi-finger/ic_menu_import_export.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/ic_menu_merge.png b/res/drawable-hdpi-finger/ic_menu_merge.png
new file mode 100644
index 0000000..d3ed8cc
--- /dev/null
+++ b/res/drawable-hdpi-finger/ic_menu_merge.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/ic_menu_show_barcode.png b/res/drawable-hdpi-finger/ic_menu_show_barcode.png
new file mode 100755
index 0000000..e36efdd
--- /dev/null
+++ b/res/drawable-hdpi-finger/ic_menu_show_barcode.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/ic_menu_split.png b/res/drawable-hdpi-finger/ic_menu_split.png
new file mode 100644
index 0000000..33ef601
--- /dev/null
+++ b/res/drawable-hdpi-finger/ic_menu_split.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/ic_tab_selected_contacts.png b/res/drawable-hdpi-finger/ic_tab_selected_contacts.png
new file mode 100644
index 0000000..7c92375
--- /dev/null
+++ b/res/drawable-hdpi-finger/ic_tab_selected_contacts.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/ic_tab_selected_dialer.png b/res/drawable-hdpi-finger/ic_tab_selected_dialer.png
new file mode 100644
index 0000000..44b4db0
--- /dev/null
+++ b/res/drawable-hdpi-finger/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-finger/ic_tab_selected_friends_list.png
new file mode 100755
index 0000000..201b80f
--- /dev/null
+++ b/res/drawable-hdpi-finger/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-finger/ic_tab_selected_recent.png
new file mode 100644
index 0000000..dfa268b
--- /dev/null
+++ b/res/drawable-hdpi-finger/ic_tab_selected_recent.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/ic_tab_selected_starred.png b/res/drawable-hdpi-finger/ic_tab_selected_starred.png
new file mode 100644
index 0000000..d9182b5
--- /dev/null
+++ b/res/drawable-hdpi-finger/ic_tab_selected_starred.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/ic_tab_unselected_contacts.png b/res/drawable-hdpi-finger/ic_tab_unselected_contacts.png
new file mode 100644
index 0000000..ee09bf1
--- /dev/null
+++ b/res/drawable-hdpi-finger/ic_tab_unselected_contacts.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/ic_tab_unselected_dialer.png b/res/drawable-hdpi-finger/ic_tab_unselected_dialer.png
new file mode 100644
index 0000000..4151cd4
--- /dev/null
+++ b/res/drawable-hdpi-finger/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-finger/ic_tab_unselected_friends_list.png
new file mode 100755
index 0000000..6a31485
--- /dev/null
+++ b/res/drawable-hdpi-finger/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-finger/ic_tab_unselected_recent.png
new file mode 100644
index 0000000..4ecfa21
--- /dev/null
+++ b/res/drawable-hdpi-finger/ic_tab_unselected_recent.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/ic_tab_unselected_starred.png b/res/drawable-hdpi-finger/ic_tab_unselected_starred.png
new file mode 100644
index 0000000..259d2d3
--- /dev/null
+++ b/res/drawable-hdpi-finger/ic_tab_unselected_starred.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/infobar_dark.9.png b/res/drawable-hdpi-finger/infobar_dark.9.png
new file mode 100644
index 0000000..105f6d0
--- /dev/null
+++ b/res/drawable-hdpi-finger/infobar_dark.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/quickcontact_arrow_down.png b/res/drawable-hdpi-finger/quickcontact_arrow_down.png
new file mode 100644
index 0000000..7eba756
--- /dev/null
+++ b/res/drawable-hdpi-finger/quickcontact_arrow_down.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/quickcontact_arrow_up.png b/res/drawable-hdpi-finger/quickcontact_arrow_up.png
new file mode 100644
index 0000000..6daf90a
--- /dev/null
+++ b/res/drawable-hdpi-finger/quickcontact_arrow_up.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/quickcontact_bottom_frame.9.png b/res/drawable-hdpi-finger/quickcontact_bottom_frame.9.png
new file mode 100644
index 0000000..9fac225
--- /dev/null
+++ b/res/drawable-hdpi-finger/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-finger/quickcontact_disambig_bottom_bg.9.png
new file mode 100644
index 0000000..4702f16
--- /dev/null
+++ b/res/drawable-hdpi-finger/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-finger/quickcontact_disambig_checkbox_off.png
new file mode 100644
index 0000000..f87572c
--- /dev/null
+++ b/res/drawable-hdpi-finger/quickcontact_disambig_checkbox_off.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/quickcontact_disambig_checkbox_on.png b/res/drawable-hdpi-finger/quickcontact_disambig_checkbox_on.png
new file mode 100644
index 0000000..3ea5360
--- /dev/null
+++ b/res/drawable-hdpi-finger/quickcontact_disambig_checkbox_on.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/quickcontact_disambig_divider.9.png b/res/drawable-hdpi-finger/quickcontact_disambig_divider.9.png
new file mode 100644
index 0000000..8c35e8c
--- /dev/null
+++ b/res/drawable-hdpi-finger/quickcontact_disambig_divider.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/quickcontact_drop_shadow.9.png b/res/drawable-hdpi-finger/quickcontact_drop_shadow.9.png
new file mode 100644
index 0000000..0dcf076
--- /dev/null
+++ b/res/drawable-hdpi-finger/quickcontact_drop_shadow.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/quickcontact_frame_divider_med.png b/res/drawable-hdpi-finger/quickcontact_frame_divider_med.png
new file mode 100644
index 0000000..e0e9ca4
--- /dev/null
+++ b/res/drawable-hdpi-finger/quickcontact_frame_divider_med.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/quickcontact_photo_frame.9.png b/res/drawable-hdpi-finger/quickcontact_photo_frame.9.png
new file mode 100644
index 0000000..990c75d
--- /dev/null
+++ b/res/drawable-hdpi-finger/quickcontact_photo_frame.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/quickcontact_slider_background.png b/res/drawable-hdpi-finger/quickcontact_slider_background.png
new file mode 100644
index 0000000..c9c09ee
--- /dev/null
+++ b/res/drawable-hdpi-finger/quickcontact_slider_background.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/quickcontact_slider_btn_normal.9.png b/res/drawable-hdpi-finger/quickcontact_slider_btn_normal.9.png
new file mode 100644
index 0000000..9d3d7ad
--- /dev/null
+++ b/res/drawable-hdpi-finger/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-finger/quickcontact_slider_btn_on.9.png
new file mode 100644
index 0000000..ac2b496
--- /dev/null
+++ b/res/drawable-hdpi-finger/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-finger/quickcontact_slider_btn_pressed.9.png
new file mode 100644
index 0000000..d9da598
--- /dev/null
+++ b/res/drawable-hdpi-finger/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-finger/quickcontact_slider_btn_selected.9.png
new file mode 100644
index 0000000..72d053b
--- /dev/null
+++ b/res/drawable-hdpi-finger/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-finger/quickcontact_slider_grip_left.png
new file mode 100644
index 0000000..97f12aa
--- /dev/null
+++ b/res/drawable-hdpi-finger/quickcontact_slider_grip_left.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/quickcontact_slider_grip_right.png b/res/drawable-hdpi-finger/quickcontact_slider_grip_right.png
new file mode 100644
index 0000000..e410059
--- /dev/null
+++ b/res/drawable-hdpi-finger/quickcontact_slider_grip_right.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/quickcontact_slider_presence_active.png b/res/drawable-hdpi-finger/quickcontact_slider_presence_active.png
new file mode 100644
index 0000000..f62e681
--- /dev/null
+++ b/res/drawable-hdpi-finger/quickcontact_slider_presence_active.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/quickcontact_slider_presence_away.png b/res/drawable-hdpi-finger/quickcontact_slider_presence_away.png
new file mode 100644
index 0000000..0516b97
--- /dev/null
+++ b/res/drawable-hdpi-finger/quickcontact_slider_presence_away.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/quickcontact_slider_presence_busy.png b/res/drawable-hdpi-finger/quickcontact_slider_presence_busy.png
new file mode 100644
index 0000000..26063f4
--- /dev/null
+++ b/res/drawable-hdpi-finger/quickcontact_slider_presence_busy.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/quickcontact_slider_presence_inactive.png b/res/drawable-hdpi-finger/quickcontact_slider_presence_inactive.png
new file mode 100644
index 0000000..fdcf75e
--- /dev/null
+++ b/res/drawable-hdpi-finger/quickcontact_slider_presence_inactive.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/quickcontact_top_frame.9.png b/res/drawable-hdpi-finger/quickcontact_top_frame.9.png
new file mode 100644
index 0000000..4556bb2
--- /dev/null
+++ b/res/drawable-hdpi-finger/quickcontact_top_frame.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/sym_action_add.png b/res/drawable-hdpi-finger/sym_action_add.png
new file mode 100755
index 0000000..45a9ec5
--- /dev/null
+++ b/res/drawable-hdpi-finger/sym_action_add.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/sym_action_map.png b/res/drawable-hdpi-finger/sym_action_map.png
new file mode 100755
index 0000000..cf00c7b
--- /dev/null
+++ b/res/drawable-hdpi-finger/sym_action_map.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/sym_action_organization.png b/res/drawable-hdpi-finger/sym_action_organization.png
new file mode 100755
index 0000000..9db8b44
--- /dev/null
+++ b/res/drawable-hdpi-finger/sym_action_organization.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/sym_action_view_contact.png b/res/drawable-hdpi-finger/sym_action_view_contact.png
new file mode 100755
index 0000000..3a016ff
--- /dev/null
+++ b/res/drawable-hdpi-finger/sym_action_view_contact.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/sym_note.png b/res/drawable-hdpi-finger/sym_note.png
new file mode 100755
index 0000000..5257329
--- /dev/null
+++ b/res/drawable-hdpi-finger/sym_note.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/sym_ringtone.png b/res/drawable-hdpi-finger/sym_ringtone.png
new file mode 100755
index 0000000..ad103e8
--- /dev/null
+++ b/res/drawable-hdpi-finger/sym_ringtone.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/sym_send_to_voicemail.png b/res/drawable-hdpi-finger/sym_send_to_voicemail.png
new file mode 100755
index 0000000..ac43473
--- /dev/null
+++ b/res/drawable-hdpi-finger/sym_send_to_voicemail.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/tab_focused.9.png b/res/drawable-hdpi-finger/tab_focused.9.png
new file mode 100644
index 0000000..a65b8f5
--- /dev/null
+++ b/res/drawable-hdpi-finger/tab_focused.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/tab_focused_bottom.9.png b/res/drawable-hdpi-finger/tab_focused_bottom.9.png
new file mode 100644
index 0000000..2fe4a9b
--- /dev/null
+++ b/res/drawable-hdpi-finger/tab_focused_bottom.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/tab_left_arrow.png b/res/drawable-hdpi-finger/tab_left_arrow.png
new file mode 100644
index 0000000..c2274f1
--- /dev/null
+++ b/res/drawable-hdpi-finger/tab_left_arrow.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/tab_pressed.9.png b/res/drawable-hdpi-finger/tab_pressed.9.png
new file mode 100644
index 0000000..0f90065
--- /dev/null
+++ b/res/drawable-hdpi-finger/tab_pressed.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/tab_pressed_bottom.9.png b/res/drawable-hdpi-finger/tab_pressed_bottom.9.png
new file mode 100644
index 0000000..00361aa
--- /dev/null
+++ b/res/drawable-hdpi-finger/tab_pressed_bottom.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/tab_right_arrow.png b/res/drawable-hdpi-finger/tab_right_arrow.png
new file mode 100644
index 0000000..8a847e0
--- /dev/null
+++ b/res/drawable-hdpi-finger/tab_right_arrow.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/tab_selected.9.png b/res/drawable-hdpi-finger/tab_selected.9.png
new file mode 100644
index 0000000..08a39ff
--- /dev/null
+++ b/res/drawable-hdpi-finger/tab_selected.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/tab_selected_bottom.9.png b/res/drawable-hdpi-finger/tab_selected_bottom.9.png
new file mode 100644
index 0000000..3cc26bc
--- /dev/null
+++ b/res/drawable-hdpi-finger/tab_selected_bottom.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/tab_unselected.9.png b/res/drawable-hdpi-finger/tab_unselected.9.png
new file mode 100644
index 0000000..6150b5b
--- /dev/null
+++ b/res/drawable-hdpi-finger/tab_unselected.9.png
Binary files differ
diff --git a/res/drawable-hdpi-finger/title_bar_shadow.9.png b/res/drawable-hdpi-finger/title_bar_shadow.9.png
new file mode 100755
index 0000000..a5e458a
--- /dev/null
+++ b/res/drawable-hdpi-finger/title_bar_shadow.9.png
Binary files differ
diff --git a/res/drawable-hdpi/bg_infobar_new.9.png b/res/drawable-hdpi/bg_infobar_new.9.png
new file mode 100644
index 0000000..104ced9
--- /dev/null
+++ b/res/drawable-hdpi/bg_infobar_new.9.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_contacts_details.png b/res/drawable-hdpi/ic_contacts_details.png
new file mode 100755
index 0000000..35f8106
--- /dev/null
+++ b/res/drawable-hdpi/ic_contacts_details.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_launcher_contacts.png b/res/drawable-hdpi/ic_launcher_contacts.png
new file mode 100644
index 0000000..69a72c4
--- /dev/null
+++ b/res/drawable-hdpi/ic_launcher_contacts.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_launcher_folder_live_contacts.png b/res/drawable-hdpi/ic_launcher_folder_live_contacts.png
new file mode 100644
index 0000000..84babe2
--- /dev/null
+++ b/res/drawable-hdpi/ic_launcher_folder_live_contacts.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_launcher_folder_live_contacts_phone.png b/res/drawable-hdpi/ic_launcher_folder_live_contacts_phone.png
new file mode 100644
index 0000000..004e849
--- /dev/null
+++ b/res/drawable-hdpi/ic_launcher_folder_live_contacts_phone.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_launcher_folder_live_contacts_starred.png b/res/drawable-hdpi/ic_launcher_folder_live_contacts_starred.png
new file mode 100644
index 0000000..73b4fa5
--- /dev/null
+++ b/res/drawable-hdpi/ic_launcher_folder_live_contacts_starred.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_launcher_phone.png b/res/drawable-hdpi/ic_launcher_phone.png
new file mode 100644
index 0000000..0943ce5
--- /dev/null
+++ b/res/drawable-hdpi/ic_launcher_phone.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_launcher_shortcut_contact.png b/res/drawable-hdpi/ic_launcher_shortcut_contact.png
new file mode 100644
index 0000000..d86b264
--- /dev/null
+++ b/res/drawable-hdpi/ic_launcher_shortcut_contact.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_launcher_shortcut_directdial.png b/res/drawable-hdpi/ic_launcher_shortcut_directdial.png
new file mode 100644
index 0000000..e7ff8f8
--- /dev/null
+++ b/res/drawable-hdpi/ic_launcher_shortcut_directdial.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_launcher_shortcut_directmessage.png b/res/drawable-hdpi/ic_launcher_shortcut_directmessage.png
new file mode 100644
index 0000000..5170a75
--- /dev/null
+++ b/res/drawable-hdpi/ic_launcher_shortcut_directmessage.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_menu_mark.png b/res/drawable-hdpi/ic_menu_mark.png
new file mode 100755
index 0000000..724d787
--- /dev/null
+++ b/res/drawable-hdpi/ic_menu_mark.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_menu_sources.png b/res/drawable-hdpi/ic_menu_sources.png
new file mode 100644
index 0000000..45355e2
--- /dev/null
+++ b/res/drawable-hdpi/ic_menu_sources.png
Binary files differ
diff --git a/res/drawable-hdpi/sym_action_sms.png b/res/drawable-hdpi/sym_action_sms.png
new file mode 100644
index 0000000..9f18105
--- /dev/null
+++ b/res/drawable-hdpi/sym_action_sms.png
Binary files differ
diff --git a/res/drawable-finger/badge_action_call.png b/res/drawable-mdpi-finger/badge_action_call.png
similarity index 100%
rename from res/drawable-finger/badge_action_call.png
rename to res/drawable-mdpi-finger/badge_action_call.png
Binary files differ
diff --git a/res/drawable-finger/badge_action_sms.png b/res/drawable-mdpi-finger/badge_action_sms.png
similarity index 100%
rename from res/drawable-finger/badge_action_sms.png
rename to res/drawable-mdpi-finger/badge_action_sms.png
Binary files differ
diff --git a/res/drawable-finger/btn_circle_disable.png b/res/drawable-mdpi-finger/btn_circle_disable.png
similarity index 100%
rename from res/drawable-finger/btn_circle_disable.png
rename to res/drawable-mdpi-finger/btn_circle_disable.png
Binary files differ
diff --git a/res/drawable-finger/btn_circle_disable_focused.png b/res/drawable-mdpi-finger/btn_circle_disable_focused.png
similarity index 100%
rename from res/drawable-finger/btn_circle_disable_focused.png
rename to res/drawable-mdpi-finger/btn_circle_disable_focused.png
Binary files differ
diff --git a/res/drawable-finger/btn_circle_normal.png b/res/drawable-mdpi-finger/btn_circle_normal.png
similarity index 100%
rename from res/drawable-finger/btn_circle_normal.png
rename to res/drawable-mdpi-finger/btn_circle_normal.png
Binary files differ
diff --git a/res/drawable-finger/btn_circle_pressed.png b/res/drawable-mdpi-finger/btn_circle_pressed.png
similarity index 100%
rename from res/drawable-finger/btn_circle_pressed.png
rename to res/drawable-mdpi-finger/btn_circle_pressed.png
Binary files differ
diff --git a/res/drawable-finger/btn_circle_selected.png b/res/drawable-mdpi-finger/btn_circle_selected.png
similarity index 100%
rename from res/drawable-finger/btn_circle_selected.png
rename to res/drawable-mdpi-finger/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-finger/btn_dial_action_left_disable.9.png
new file mode 100644
index 0000000..6ab27d8
--- /dev/null
+++ b/res/drawable-mdpi-finger/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-finger/btn_dial_action_left_disable_focused.9.png
new file mode 100644
index 0000000..5bba3c4
--- /dev/null
+++ b/res/drawable-mdpi-finger/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-finger/btn_dial_action_left_normal.9.png
new file mode 100644
index 0000000..6ab27d8
--- /dev/null
+++ b/res/drawable-mdpi-finger/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-finger/btn_dial_action_left_pressed.9.png
new file mode 100644
index 0000000..542abe7
--- /dev/null
+++ b/res/drawable-mdpi-finger/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-finger/btn_dial_action_left_selected.9.png
new file mode 100644
index 0000000..34caba1
--- /dev/null
+++ b/res/drawable-mdpi-finger/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-finger/btn_dial_action_middle_disable.9.png
new file mode 100644
index 0000000..0740c95
--- /dev/null
+++ b/res/drawable-mdpi-finger/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-finger/btn_dial_action_middle_disable_focused.9.png
new file mode 100644
index 0000000..c57627f
--- /dev/null
+++ b/res/drawable-mdpi-finger/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-finger/btn_dial_action_middle_normal.9.png
new file mode 100644
index 0000000..0740c95
--- /dev/null
+++ b/res/drawable-mdpi-finger/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-finger/btn_dial_action_middle_pressed.9.png
new file mode 100644
index 0000000..a5f9f98
--- /dev/null
+++ b/res/drawable-mdpi-finger/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-finger/btn_dial_action_middle_selected.9.png
new file mode 100644
index 0000000..dcd4b82
--- /dev/null
+++ b/res/drawable-mdpi-finger/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-finger/btn_dial_action_right_disable.9.png
new file mode 100644
index 0000000..85dfff0
--- /dev/null
+++ b/res/drawable-mdpi-finger/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-finger/btn_dial_action_right_disable_focused.9.png
new file mode 100644
index 0000000..1f76f0c
--- /dev/null
+++ b/res/drawable-mdpi-finger/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-finger/btn_dial_action_right_normal.9.png
new file mode 100644
index 0000000..85dfff0
--- /dev/null
+++ b/res/drawable-mdpi-finger/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-finger/btn_dial_action_right_pressed.9.png
new file mode 100644
index 0000000..226633f
--- /dev/null
+++ b/res/drawable-mdpi-finger/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-finger/btn_dial_action_right_selected.9.png
new file mode 100644
index 0000000..9cca52c
--- /dev/null
+++ b/res/drawable-mdpi-finger/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-finger/btn_dial_normal.9.png
new file mode 100644
index 0000000..748dd8a
--- /dev/null
+++ b/res/drawable-mdpi-finger/btn_dial_normal.9.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/btn_dial_normal_blue.9.png b/res/drawable-mdpi-finger/btn_dial_normal_blue.9.png
new file mode 100644
index 0000000..7c8c2ca
--- /dev/null
+++ b/res/drawable-mdpi-finger/btn_dial_normal_blue.9.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/btn_dial_normal_green.9.png b/res/drawable-mdpi-finger/btn_dial_normal_green.9.png
new file mode 100644
index 0000000..b220650
--- /dev/null
+++ b/res/drawable-mdpi-finger/btn_dial_normal_green.9.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/btn_dial_pressed.9.png b/res/drawable-mdpi-finger/btn_dial_pressed.9.png
new file mode 100644
index 0000000..83f9c62
--- /dev/null
+++ b/res/drawable-mdpi-finger/btn_dial_pressed.9.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/btn_dial_selected.9.png b/res/drawable-mdpi-finger/btn_dial_selected.9.png
new file mode 100644
index 0000000..edc7bcb
--- /dev/null
+++ b/res/drawable-mdpi-finger/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-finger/btn_dial_textfield_activated.9.png
new file mode 100644
index 0000000..de65d44
--- /dev/null
+++ b/res/drawable-mdpi-finger/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-finger/btn_dial_textfield_normal.9.png
new file mode 100644
index 0000000..d3613e3
--- /dev/null
+++ b/res/drawable-mdpi-finger/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-finger/btn_dial_textfield_pressed.9.png
new file mode 100644
index 0000000..fa7147e
--- /dev/null
+++ b/res/drawable-mdpi-finger/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-finger/btn_dial_textfield_selected.9.png
new file mode 100644
index 0000000..09db422
--- /dev/null
+++ b/res/drawable-mdpi-finger/btn_dial_textfield_selected.9.png
Binary files differ
diff --git a/res/drawable-finger/contact_picture_border_highlight.9.png b/res/drawable-mdpi-finger/contact_picture_border_highlight.9.png
similarity index 100%
rename from res/drawable-finger/contact_picture_border_highlight.9.png
rename to res/drawable-mdpi-finger/contact_picture_border_highlight.9.png
Binary files differ
diff --git a/res/drawable-finger/contact_picture_border_in_list.9.png b/res/drawable-mdpi-finger/contact_picture_border_in_list.9.png
similarity index 100%
rename from res/drawable-finger/contact_picture_border_in_list.9.png
rename to res/drawable-mdpi-finger/contact_picture_border_in_list.9.png
Binary files differ
diff --git a/res/drawable-finger/contact_picture_border_normal.9.png b/res/drawable-mdpi-finger/contact_picture_border_normal.9.png
similarity index 100%
rename from res/drawable-finger/contact_picture_border_normal.9.png
rename to res/drawable-mdpi-finger/contact_picture_border_normal.9.png
Binary files differ
diff --git a/res/drawable-finger/contact_picture_border_pressed.9.png b/res/drawable-mdpi-finger/contact_picture_border_pressed.9.png
similarity index 100%
rename from res/drawable-finger/contact_picture_border_pressed.9.png
rename to res/drawable-mdpi-finger/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-finger/dial_num_0_blk.png
new file mode 100644
index 0000000..d04add7
--- /dev/null
+++ b/res/drawable-mdpi-finger/dial_num_0_blk.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/dial_num_0_wht.png b/res/drawable-mdpi-finger/dial_num_0_wht.png
new file mode 100644
index 0000000..c3b3f2c
--- /dev/null
+++ b/res/drawable-mdpi-finger/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-finger/dial_num_1_no_vm_blk.png
new file mode 100644
index 0000000..75a8ed8
--- /dev/null
+++ b/res/drawable-mdpi-finger/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-finger/dial_num_1_no_vm_wht.png
new file mode 100644
index 0000000..a5bdb41
--- /dev/null
+++ b/res/drawable-mdpi-finger/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-finger/dial_num_2_blk.png
new file mode 100644
index 0000000..1d8a35c
--- /dev/null
+++ b/res/drawable-mdpi-finger/dial_num_2_blk.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/dial_num_2_wht.png b/res/drawable-mdpi-finger/dial_num_2_wht.png
new file mode 100644
index 0000000..ac99cec
--- /dev/null
+++ b/res/drawable-mdpi-finger/dial_num_2_wht.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/dial_num_3_blk.png b/res/drawable-mdpi-finger/dial_num_3_blk.png
new file mode 100644
index 0000000..cf78e04
--- /dev/null
+++ b/res/drawable-mdpi-finger/dial_num_3_blk.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/dial_num_3_wht.png b/res/drawable-mdpi-finger/dial_num_3_wht.png
new file mode 100644
index 0000000..69170b9
--- /dev/null
+++ b/res/drawable-mdpi-finger/dial_num_3_wht.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/dial_num_4_blk.png b/res/drawable-mdpi-finger/dial_num_4_blk.png
new file mode 100644
index 0000000..c9c12c2
--- /dev/null
+++ b/res/drawable-mdpi-finger/dial_num_4_blk.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/dial_num_4_wht.png b/res/drawable-mdpi-finger/dial_num_4_wht.png
new file mode 100644
index 0000000..48a02a5
--- /dev/null
+++ b/res/drawable-mdpi-finger/dial_num_4_wht.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/dial_num_5_blk.png b/res/drawable-mdpi-finger/dial_num_5_blk.png
new file mode 100644
index 0000000..a89dd8f
--- /dev/null
+++ b/res/drawable-mdpi-finger/dial_num_5_blk.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/dial_num_5_wht.png b/res/drawable-mdpi-finger/dial_num_5_wht.png
new file mode 100644
index 0000000..e3c9940
--- /dev/null
+++ b/res/drawable-mdpi-finger/dial_num_5_wht.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/dial_num_6_blk.png b/res/drawable-mdpi-finger/dial_num_6_blk.png
new file mode 100644
index 0000000..c282afb
--- /dev/null
+++ b/res/drawable-mdpi-finger/dial_num_6_blk.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/dial_num_6_wht.png b/res/drawable-mdpi-finger/dial_num_6_wht.png
new file mode 100644
index 0000000..ab12781
--- /dev/null
+++ b/res/drawable-mdpi-finger/dial_num_6_wht.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/dial_num_7_blk.png b/res/drawable-mdpi-finger/dial_num_7_blk.png
new file mode 100644
index 0000000..df273a2
--- /dev/null
+++ b/res/drawable-mdpi-finger/dial_num_7_blk.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/dial_num_7_wht.png b/res/drawable-mdpi-finger/dial_num_7_wht.png
new file mode 100644
index 0000000..9e66205
--- /dev/null
+++ b/res/drawable-mdpi-finger/dial_num_7_wht.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/dial_num_8_blk.png b/res/drawable-mdpi-finger/dial_num_8_blk.png
new file mode 100644
index 0000000..9e5654f
--- /dev/null
+++ b/res/drawable-mdpi-finger/dial_num_8_blk.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/dial_num_8_wht.png b/res/drawable-mdpi-finger/dial_num_8_wht.png
new file mode 100644
index 0000000..2af30fa
--- /dev/null
+++ b/res/drawable-mdpi-finger/dial_num_8_wht.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/dial_num_9_blk.png b/res/drawable-mdpi-finger/dial_num_9_blk.png
new file mode 100644
index 0000000..6ae1943
--- /dev/null
+++ b/res/drawable-mdpi-finger/dial_num_9_blk.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/dial_num_9_wht.png b/res/drawable-mdpi-finger/dial_num_9_wht.png
new file mode 100644
index 0000000..1c99b61
--- /dev/null
+++ b/res/drawable-mdpi-finger/dial_num_9_wht.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/dial_num_pound_blk.png b/res/drawable-mdpi-finger/dial_num_pound_blk.png
new file mode 100644
index 0000000..add133e
--- /dev/null
+++ b/res/drawable-mdpi-finger/dial_num_pound_blk.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/dial_num_pound_wht.png b/res/drawable-mdpi-finger/dial_num_pound_wht.png
new file mode 100644
index 0000000..e17f2bf
--- /dev/null
+++ b/res/drawable-mdpi-finger/dial_num_pound_wht.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/dial_num_star_blk.png b/res/drawable-mdpi-finger/dial_num_star_blk.png
new file mode 100644
index 0000000..f649f14
--- /dev/null
+++ b/res/drawable-mdpi-finger/dial_num_star_blk.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/dial_num_star_wht.png b/res/drawable-mdpi-finger/dial_num_star_wht.png
new file mode 100644
index 0000000..86113ed
--- /dev/null
+++ b/res/drawable-mdpi-finger/dial_num_star_wht.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/divider_vertical_dark.9.png b/res/drawable-mdpi-finger/divider_vertical_dark.9.png
new file mode 100644
index 0000000..702b878
--- /dev/null
+++ b/res/drawable-mdpi-finger/divider_vertical_dark.9.png
Binary files differ
diff --git a/res/drawable-finger/ic_btn_round_minus.png b/res/drawable-mdpi-finger/ic_btn_round_minus.png
similarity index 100%
rename from res/drawable-finger/ic_btn_round_minus.png
rename to res/drawable-mdpi-finger/ic_btn_round_minus.png
Binary files differ
diff --git a/res/drawable-finger/ic_btn_round_more.png b/res/drawable-mdpi-finger/ic_btn_round_more.png
similarity index 100%
rename from res/drawable-finger/ic_btn_round_more.png
rename to res/drawable-mdpi-finger/ic_btn_round_more.png
Binary files differ
diff --git a/res/drawable-finger/ic_btn_round_plus.png b/res/drawable-mdpi-finger/ic_btn_round_plus.png
similarity index 100%
rename from res/drawable-finger/ic_btn_round_plus.png
rename to res/drawable-mdpi-finger/ic_btn_round_plus.png
Binary files differ
diff --git a/res/drawable-finger/ic_call_log_header_incoming_call.png b/res/drawable-mdpi-finger/ic_call_log_header_incoming_call.png
similarity index 100%
rename from res/drawable-finger/ic_call_log_header_incoming_call.png
rename to res/drawable-mdpi-finger/ic_call_log_header_incoming_call.png
Binary files differ
diff --git a/res/drawable-finger/ic_call_log_header_missed_call.png b/res/drawable-mdpi-finger/ic_call_log_header_missed_call.png
similarity index 100%
rename from res/drawable-finger/ic_call_log_header_missed_call.png
rename to res/drawable-mdpi-finger/ic_call_log_header_missed_call.png
Binary files differ
diff --git a/res/drawable-finger/ic_call_log_header_outgoing_call.png b/res/drawable-mdpi-finger/ic_call_log_header_outgoing_call.png
similarity index 100%
rename from res/drawable-finger/ic_call_log_header_outgoing_call.png
rename to res/drawable-mdpi-finger/ic_call_log_header_outgoing_call.png
Binary files differ
diff --git a/res/drawable-finger/ic_call_log_list_incoming_call.png b/res/drawable-mdpi-finger/ic_call_log_list_incoming_call.png
similarity index 100%
rename from res/drawable-finger/ic_call_log_list_incoming_call.png
rename to res/drawable-mdpi-finger/ic_call_log_list_incoming_call.png
Binary files differ
diff --git a/res/drawable-finger/ic_call_log_list_missed_call.png b/res/drawable-mdpi-finger/ic_call_log_list_missed_call.png
similarity index 100%
rename from res/drawable-finger/ic_call_log_list_missed_call.png
rename to res/drawable-mdpi-finger/ic_call_log_list_missed_call.png
Binary files differ
diff --git a/res/drawable-finger/ic_call_log_list_outgoing_call.png b/res/drawable-mdpi-finger/ic_call_log_list_outgoing_call.png
similarity index 100%
rename from res/drawable-finger/ic_call_log_list_outgoing_call.png
rename to res/drawable-mdpi-finger/ic_call_log_list_outgoing_call.png
Binary files differ
diff --git a/res/drawable-finger/ic_contact_list_picture.png b/res/drawable-mdpi-finger/ic_contact_list_picture.png
similarity index 100%
rename from res/drawable-finger/ic_contact_list_picture.png
rename to res/drawable-mdpi-finger/ic_contact_list_picture.png
Binary files differ
diff --git a/res/drawable-finger/ic_contact_picture.png b/res/drawable-mdpi-finger/ic_contact_picture.png
similarity index 100%
rename from res/drawable-finger/ic_contact_picture.png
rename to res/drawable-mdpi-finger/ic_contact_picture.png
Binary files differ
diff --git a/res/drawable-finger/ic_contact_picture_2.png b/res/drawable-mdpi-finger/ic_contact_picture_2.png
similarity index 100%
rename from res/drawable-finger/ic_contact_picture_2.png
rename to res/drawable-mdpi-finger/ic_contact_picture_2.png
Binary files differ
diff --git a/res/drawable-finger/ic_contact_picture_3.png b/res/drawable-mdpi-finger/ic_contact_picture_3.png
similarity index 100%
rename from res/drawable-finger/ic_contact_picture_3.png
rename to res/drawable-mdpi-finger/ic_contact_picture_3.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/ic_default_number.png b/res/drawable-mdpi-finger/ic_default_number.png
new file mode 100644
index 0000000..30ead23
--- /dev/null
+++ b/res/drawable-mdpi-finger/ic_default_number.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/ic_dial_action_call.png b/res/drawable-mdpi-finger/ic_dial_action_call.png
new file mode 100644
index 0000000..1942899
--- /dev/null
+++ b/res/drawable-mdpi-finger/ic_dial_action_call.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/ic_dial_action_delete.png b/res/drawable-mdpi-finger/ic_dial_action_delete.png
new file mode 100644
index 0000000..440ae37
--- /dev/null
+++ b/res/drawable-mdpi-finger/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-finger/ic_dial_action_voice_mail.png
new file mode 100644
index 0000000..cb07d1a
--- /dev/null
+++ b/res/drawable-mdpi-finger/ic_dial_action_voice_mail.png
Binary files differ
diff --git a/res/drawable-finger/ic_dial_number_blk.png b/res/drawable-mdpi-finger/ic_dial_number_blk.png
similarity index 100%
rename from res/drawable-finger/ic_dial_number_blk.png
rename to res/drawable-mdpi-finger/ic_dial_number_blk.png
Binary files differ
diff --git a/res/drawable-finger/ic_dial_number_wht.png b/res/drawable-mdpi-finger/ic_dial_number_wht.png
similarity index 100%
rename from res/drawable-finger/ic_dial_number_wht.png
rename to res/drawable-mdpi-finger/ic_dial_number_wht.png
Binary files differ
diff --git a/res/drawable-finger/ic_dialer_fork_add_call.png b/res/drawable-mdpi-finger/ic_dialer_fork_add_call.png
similarity index 100%
rename from res/drawable-finger/ic_dialer_fork_add_call.png
rename to res/drawable-mdpi-finger/ic_dialer_fork_add_call.png
Binary files differ
diff --git a/res/drawable-finger/ic_dialer_fork_current_call.png b/res/drawable-mdpi-finger/ic_dialer_fork_current_call.png
similarity index 100%
rename from res/drawable-finger/ic_dialer_fork_current_call.png
rename to res/drawable-mdpi-finger/ic_dialer_fork_current_call.png
Binary files differ
diff --git a/res/drawable-finger/ic_dialer_fork_tt_keypad.png b/res/drawable-mdpi-finger/ic_dialer_fork_tt_keypad.png
similarity index 100%
rename from res/drawable-finger/ic_dialer_fork_tt_keypad.png
rename to res/drawable-mdpi-finger/ic_dialer_fork_tt_keypad.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/ic_join.png b/res/drawable-mdpi-finger/ic_join.png
new file mode 100644
index 0000000..177a582
--- /dev/null
+++ b/res/drawable-mdpi-finger/ic_join.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/ic_list_split.png b/res/drawable-mdpi-finger/ic_list_split.png
new file mode 100644
index 0000000..cbaab12
--- /dev/null
+++ b/res/drawable-mdpi-finger/ic_list_split.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/ic_menu_account_list.png b/res/drawable-mdpi-finger/ic_menu_account_list.png
new file mode 100644
index 0000000..f0945b2
--- /dev/null
+++ b/res/drawable-mdpi-finger/ic_menu_account_list.png
Binary files differ
diff --git a/res/drawable-finger/ic_menu_add_picture.png b/res/drawable-mdpi-finger/ic_menu_add_picture.png
similarity index 100%
rename from res/drawable-finger/ic_menu_add_picture.png
rename to res/drawable-mdpi-finger/ic_menu_add_picture.png
Binary files differ
diff --git a/res/drawable-finger/ic_menu_export_contact.png b/res/drawable-mdpi-finger/ic_menu_export_contact.png
similarity index 100%
rename from res/drawable-finger/ic_menu_export_contact.png
rename to res/drawable-mdpi-finger/ic_menu_export_contact.png
Binary files differ
diff --git a/res/drawable-finger/ic_menu_import_contact.png b/res/drawable-mdpi-finger/ic_menu_import_contact.png
similarity index 100%
rename from res/drawable-finger/ic_menu_import_contact.png
rename to res/drawable-mdpi-finger/ic_menu_import_contact.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/ic_menu_import_export.png b/res/drawable-mdpi-finger/ic_menu_import_export.png
new file mode 100644
index 0000000..1cefb7c
--- /dev/null
+++ b/res/drawable-mdpi-finger/ic_menu_import_export.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/ic_menu_merge.png b/res/drawable-mdpi-finger/ic_menu_merge.png
new file mode 100644
index 0000000..b448c27
--- /dev/null
+++ b/res/drawable-mdpi-finger/ic_menu_merge.png
Binary files differ
diff --git a/res/drawable-finger/ic_menu_show_barcode.png b/res/drawable-mdpi-finger/ic_menu_show_barcode.png
similarity index 100%
rename from res/drawable-finger/ic_menu_show_barcode.png
rename to res/drawable-mdpi-finger/ic_menu_show_barcode.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/ic_menu_split.png b/res/drawable-mdpi-finger/ic_menu_split.png
new file mode 100644
index 0000000..9d69e4c
--- /dev/null
+++ b/res/drawable-mdpi-finger/ic_menu_split.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/ic_tab_selected_contacts.png b/res/drawable-mdpi-finger/ic_tab_selected_contacts.png
new file mode 100644
index 0000000..b3e23b7
--- /dev/null
+++ b/res/drawable-mdpi-finger/ic_tab_selected_contacts.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/ic_tab_selected_dialer.png b/res/drawable-mdpi-finger/ic_tab_selected_dialer.png
new file mode 100644
index 0000000..71739a6
--- /dev/null
+++ b/res/drawable-mdpi-finger/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-finger/ic_tab_selected_friends_list.png
new file mode 100644
index 0000000..de2c4cc
--- /dev/null
+++ b/res/drawable-mdpi-finger/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-finger/ic_tab_selected_recent.png
new file mode 100644
index 0000000..a6cbc2a
--- /dev/null
+++ b/res/drawable-mdpi-finger/ic_tab_selected_recent.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/ic_tab_selected_starred.png b/res/drawable-mdpi-finger/ic_tab_selected_starred.png
new file mode 100644
index 0000000..089dfe9
--- /dev/null
+++ b/res/drawable-mdpi-finger/ic_tab_selected_starred.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/ic_tab_unselected_contacts.png b/res/drawable-mdpi-finger/ic_tab_unselected_contacts.png
new file mode 100644
index 0000000..b3f5ae0
--- /dev/null
+++ b/res/drawable-mdpi-finger/ic_tab_unselected_contacts.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/ic_tab_unselected_dialer.png b/res/drawable-mdpi-finger/ic_tab_unselected_dialer.png
new file mode 100644
index 0000000..f008813
--- /dev/null
+++ b/res/drawable-mdpi-finger/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-finger/ic_tab_unselected_friends_list.png
new file mode 100644
index 0000000..80736e9
--- /dev/null
+++ b/res/drawable-mdpi-finger/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-finger/ic_tab_unselected_recent.png
new file mode 100644
index 0000000..8f81688
--- /dev/null
+++ b/res/drawable-mdpi-finger/ic_tab_unselected_recent.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/ic_tab_unselected_starred.png b/res/drawable-mdpi-finger/ic_tab_unselected_starred.png
new file mode 100644
index 0000000..32fe67d
--- /dev/null
+++ b/res/drawable-mdpi-finger/ic_tab_unselected_starred.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/infobar_dark.9.png b/res/drawable-mdpi-finger/infobar_dark.9.png
new file mode 100644
index 0000000..45b2dcb
--- /dev/null
+++ b/res/drawable-mdpi-finger/infobar_dark.9.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/quickcontact_arrow_down.png b/res/drawable-mdpi-finger/quickcontact_arrow_down.png
new file mode 100644
index 0000000..3ba6c8c
--- /dev/null
+++ b/res/drawable-mdpi-finger/quickcontact_arrow_down.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/quickcontact_arrow_up.png b/res/drawable-mdpi-finger/quickcontact_arrow_up.png
new file mode 100644
index 0000000..1a6fa2e
--- /dev/null
+++ b/res/drawable-mdpi-finger/quickcontact_arrow_up.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/quickcontact_bottom_frame.9.png b/res/drawable-mdpi-finger/quickcontact_bottom_frame.9.png
new file mode 100644
index 0000000..6f84306
--- /dev/null
+++ b/res/drawable-mdpi-finger/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-finger/quickcontact_disambig_bottom_bg.9.png
new file mode 100644
index 0000000..bbc2075
--- /dev/null
+++ b/res/drawable-mdpi-finger/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-finger/quickcontact_disambig_checkbox_off.png
new file mode 100644
index 0000000..7e51863
--- /dev/null
+++ b/res/drawable-mdpi-finger/quickcontact_disambig_checkbox_off.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/quickcontact_disambig_checkbox_on.png b/res/drawable-mdpi-finger/quickcontact_disambig_checkbox_on.png
new file mode 100644
index 0000000..93c06aa
--- /dev/null
+++ b/res/drawable-mdpi-finger/quickcontact_disambig_checkbox_on.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/quickcontact_disambig_divider.9.png b/res/drawable-mdpi-finger/quickcontact_disambig_divider.9.png
new file mode 100644
index 0000000..8c35e8c
--- /dev/null
+++ b/res/drawable-mdpi-finger/quickcontact_disambig_divider.9.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/quickcontact_drop_shadow.9.png b/res/drawable-mdpi-finger/quickcontact_drop_shadow.9.png
new file mode 100644
index 0000000..2d20076
--- /dev/null
+++ b/res/drawable-mdpi-finger/quickcontact_drop_shadow.9.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/quickcontact_frame_divider_med.png b/res/drawable-mdpi-finger/quickcontact_frame_divider_med.png
new file mode 100644
index 0000000..3b24f95
--- /dev/null
+++ b/res/drawable-mdpi-finger/quickcontact_frame_divider_med.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/quickcontact_photo_frame.9.png b/res/drawable-mdpi-finger/quickcontact_photo_frame.9.png
new file mode 100644
index 0000000..a509c30
--- /dev/null
+++ b/res/drawable-mdpi-finger/quickcontact_photo_frame.9.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/quickcontact_slider_background.png b/res/drawable-mdpi-finger/quickcontact_slider_background.png
new file mode 100644
index 0000000..b6a9f91
--- /dev/null
+++ b/res/drawable-mdpi-finger/quickcontact_slider_background.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/quickcontact_slider_btn_normal.9.png b/res/drawable-mdpi-finger/quickcontact_slider_btn_normal.9.png
new file mode 100644
index 0000000..cf4f1e4
--- /dev/null
+++ b/res/drawable-mdpi-finger/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-finger/quickcontact_slider_btn_on.9.png
new file mode 100644
index 0000000..330f49b
--- /dev/null
+++ b/res/drawable-mdpi-finger/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-finger/quickcontact_slider_btn_pressed.9.png
new file mode 100644
index 0000000..d4916f5
--- /dev/null
+++ b/res/drawable-mdpi-finger/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-finger/quickcontact_slider_btn_selected.9.png
new file mode 100644
index 0000000..b910028
--- /dev/null
+++ b/res/drawable-mdpi-finger/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-finger/quickcontact_slider_grip_left.png
new file mode 100644
index 0000000..337b2fc
--- /dev/null
+++ b/res/drawable-mdpi-finger/quickcontact_slider_grip_left.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/quickcontact_slider_grip_right.png b/res/drawable-mdpi-finger/quickcontact_slider_grip_right.png
new file mode 100644
index 0000000..1e222c3
--- /dev/null
+++ b/res/drawable-mdpi-finger/quickcontact_slider_grip_right.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/quickcontact_slider_presence_active.png b/res/drawable-mdpi-finger/quickcontact_slider_presence_active.png
new file mode 100644
index 0000000..2d57813
--- /dev/null
+++ b/res/drawable-mdpi-finger/quickcontact_slider_presence_active.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/quickcontact_slider_presence_away.png b/res/drawable-mdpi-finger/quickcontact_slider_presence_away.png
new file mode 100644
index 0000000..22d014b
--- /dev/null
+++ b/res/drawable-mdpi-finger/quickcontact_slider_presence_away.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/quickcontact_slider_presence_busy.png b/res/drawable-mdpi-finger/quickcontact_slider_presence_busy.png
new file mode 100644
index 0000000..5734e00
--- /dev/null
+++ b/res/drawable-mdpi-finger/quickcontact_slider_presence_busy.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/quickcontact_slider_presence_inactive.png b/res/drawable-mdpi-finger/quickcontact_slider_presence_inactive.png
new file mode 100644
index 0000000..81731a8
--- /dev/null
+++ b/res/drawable-mdpi-finger/quickcontact_slider_presence_inactive.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/quickcontact_top_frame.9.png b/res/drawable-mdpi-finger/quickcontact_top_frame.9.png
new file mode 100644
index 0000000..6d43305
--- /dev/null
+++ b/res/drawable-mdpi-finger/quickcontact_top_frame.9.png
Binary files differ
diff --git a/res/drawable-finger/sym_action_add.png b/res/drawable-mdpi-finger/sym_action_add.png
similarity index 100%
rename from res/drawable-finger/sym_action_add.png
rename to res/drawable-mdpi-finger/sym_action_add.png
Binary files differ
diff --git a/res/drawable-finger/sym_action_map.png b/res/drawable-mdpi-finger/sym_action_map.png
similarity index 100%
rename from res/drawable-finger/sym_action_map.png
rename to res/drawable-mdpi-finger/sym_action_map.png
Binary files differ
diff --git a/res/drawable-finger/sym_action_organization.png b/res/drawable-mdpi-finger/sym_action_organization.png
similarity index 100%
rename from res/drawable-finger/sym_action_organization.png
rename to res/drawable-mdpi-finger/sym_action_organization.png
Binary files differ
diff --git a/res/drawable-finger/sym_action_view_contact.png b/res/drawable-mdpi-finger/sym_action_view_contact.png
similarity index 100%
rename from res/drawable-finger/sym_action_view_contact.png
rename to res/drawable-mdpi-finger/sym_action_view_contact.png
Binary files differ
diff --git a/res/drawable-finger/sym_note.png b/res/drawable-mdpi-finger/sym_note.png
similarity index 100%
rename from res/drawable-finger/sym_note.png
rename to res/drawable-mdpi-finger/sym_note.png
Binary files differ
diff --git a/res/drawable-finger/sym_ringtone.png b/res/drawable-mdpi-finger/sym_ringtone.png
similarity index 100%
rename from res/drawable-finger/sym_ringtone.png
rename to res/drawable-mdpi-finger/sym_ringtone.png
Binary files differ
diff --git a/res/drawable-finger/sym_send_to_voicemail.png b/res/drawable-mdpi-finger/sym_send_to_voicemail.png
similarity index 100%
rename from res/drawable-finger/sym_send_to_voicemail.png
rename to res/drawable-mdpi-finger/sym_send_to_voicemail.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/tab_focused.9.png b/res/drawable-mdpi-finger/tab_focused.9.png
new file mode 100644
index 0000000..6190694
--- /dev/null
+++ b/res/drawable-mdpi-finger/tab_focused.9.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/tab_focused_bottom.9.png b/res/drawable-mdpi-finger/tab_focused_bottom.9.png
new file mode 100644
index 0000000..0e3bdb0
--- /dev/null
+++ b/res/drawable-mdpi-finger/tab_focused_bottom.9.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/tab_left_arrow.png b/res/drawable-mdpi-finger/tab_left_arrow.png
new file mode 100644
index 0000000..e35d58d
--- /dev/null
+++ b/res/drawable-mdpi-finger/tab_left_arrow.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/tab_pressed.9.png b/res/drawable-mdpi-finger/tab_pressed.9.png
new file mode 100644
index 0000000..dbada51
--- /dev/null
+++ b/res/drawable-mdpi-finger/tab_pressed.9.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/tab_pressed_bottom.9.png b/res/drawable-mdpi-finger/tab_pressed_bottom.9.png
new file mode 100644
index 0000000..4bf95d5
--- /dev/null
+++ b/res/drawable-mdpi-finger/tab_pressed_bottom.9.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/tab_right_arrow.png b/res/drawable-mdpi-finger/tab_right_arrow.png
new file mode 100644
index 0000000..8acbba0
--- /dev/null
+++ b/res/drawable-mdpi-finger/tab_right_arrow.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/tab_selected.9.png b/res/drawable-mdpi-finger/tab_selected.9.png
new file mode 100644
index 0000000..b3c9fb1
--- /dev/null
+++ b/res/drawable-mdpi-finger/tab_selected.9.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/tab_selected_bottom.9.png b/res/drawable-mdpi-finger/tab_selected_bottom.9.png
new file mode 100644
index 0000000..f69f08e
--- /dev/null
+++ b/res/drawable-mdpi-finger/tab_selected_bottom.9.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/tab_unselected.9.png b/res/drawable-mdpi-finger/tab_unselected.9.png
new file mode 100644
index 0000000..a79fa2a
--- /dev/null
+++ b/res/drawable-mdpi-finger/tab_unselected.9.png
Binary files differ
diff --git a/res/drawable-finger/title_bar_shadow.9.png b/res/drawable-mdpi-finger/title_bar_shadow.9.png
similarity index 100%
rename from res/drawable-finger/title_bar_shadow.9.png
rename to res/drawable-mdpi-finger/title_bar_shadow.9.png
Binary files differ
diff --git a/res/drawable-mdpi/bg_infobar_new.9.png b/res/drawable-mdpi/bg_infobar_new.9.png
new file mode 100644
index 0000000..f3a83d4
--- /dev/null
+++ b/res/drawable-mdpi/bg_infobar_new.9.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_contacts_details.png b/res/drawable-mdpi/ic_contacts_details.png
new file mode 100644
index 0000000..7e71b85
--- /dev/null
+++ b/res/drawable-mdpi/ic_contacts_details.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_launcher_contacts.png b/res/drawable-mdpi/ic_launcher_contacts.png
new file mode 100644
index 0000000..08468fd
--- /dev/null
+++ b/res/drawable-mdpi/ic_launcher_contacts.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_launcher_folder_live_contacts.png b/res/drawable-mdpi/ic_launcher_folder_live_contacts.png
new file mode 100644
index 0000000..d49cc7b
--- /dev/null
+++ b/res/drawable-mdpi/ic_launcher_folder_live_contacts.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_launcher_folder_live_contacts_phone.png b/res/drawable-mdpi/ic_launcher_folder_live_contacts_phone.png
new file mode 100644
index 0000000..0127f84
--- /dev/null
+++ b/res/drawable-mdpi/ic_launcher_folder_live_contacts_phone.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_launcher_folder_live_contacts_starred.png b/res/drawable-mdpi/ic_launcher_folder_live_contacts_starred.png
new file mode 100644
index 0000000..8d56b31
--- /dev/null
+++ b/res/drawable-mdpi/ic_launcher_folder_live_contacts_starred.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_launcher_phone.png b/res/drawable-mdpi/ic_launcher_phone.png
new file mode 100644
index 0000000..724f94a
--- /dev/null
+++ b/res/drawable-mdpi/ic_launcher_phone.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_launcher_shortcut_contact.png b/res/drawable-mdpi/ic_launcher_shortcut_contact.png
new file mode 100644
index 0000000..20d359d
--- /dev/null
+++ b/res/drawable-mdpi/ic_launcher_shortcut_contact.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_launcher_shortcut_directdial.png b/res/drawable-mdpi/ic_launcher_shortcut_directdial.png
new file mode 100644
index 0000000..7081c08
--- /dev/null
+++ b/res/drawable-mdpi/ic_launcher_shortcut_directdial.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_launcher_shortcut_directmessage.png b/res/drawable-mdpi/ic_launcher_shortcut_directmessage.png
new file mode 100644
index 0000000..374c7c4
--- /dev/null
+++ b/res/drawable-mdpi/ic_launcher_shortcut_directmessage.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_menu_mark.png b/res/drawable-mdpi/ic_menu_mark.png
new file mode 100644
index 0000000..5e95da7
--- /dev/null
+++ b/res/drawable-mdpi/ic_menu_mark.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_menu_sources.png b/res/drawable-mdpi/ic_menu_sources.png
new file mode 100644
index 0000000..78cdaca
--- /dev/null
+++ b/res/drawable-mdpi/ic_menu_sources.png
Binary files differ
diff --git a/res/drawable-mdpi/sym_action_sms.png b/res/drawable-mdpi/sym_action_sms.png
new file mode 100644
index 0000000..f098f4a
--- /dev/null
+++ b/res/drawable-mdpi/sym_action_sms.png
Binary files differ
diff --git a/res/drawable-finger/ic_delete_phone_number.xml b/res/drawable/btn_dial_textfield.xml
similarity index 71%
rename from res/drawable-finger/ic_delete_phone_number.xml
rename to res/drawable/btn_dial_textfield.xml
index adfc0ce..de914cf 100644
--- a/res/drawable-finger/ic_delete_phone_number.xml
+++ b/res/drawable/btn_dial_textfield.xml
@@ -1,5 +1,5 @@
<?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.
@@ -15,11 +15,11 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_pressed="true"
- android:drawable="@drawable/ic_delete_phone_number_blk" />
+ <item android:state_pressed="true"
+ android:drawable="@drawable/btn_dial_textfield_pressed" />
<item android:state_focused="true"
- android:drawable="@drawable/ic_delete_phone_number_blk" />
+ android:drawable="@drawable/btn_dial_textfield_selected" />
<item
- android:drawable="@drawable/ic_delete_phone_number_wht" />
+ android:drawable="@drawable/btn_dial_textfield_normal" />
</selector>
diff --git a/res/drawable-finger/ic_delete_phone_number.xml b/res/drawable/btn_dial_textfield_active.xml
similarity index 71%
copy from res/drawable-finger/ic_delete_phone_number.xml
copy to res/drawable/btn_dial_textfield_active.xml
index adfc0ce..f3af301 100644
--- a/res/drawable-finger/ic_delete_phone_number.xml
+++ b/res/drawable/btn_dial_textfield_active.xml
@@ -1,5 +1,5 @@
<?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.
@@ -15,11 +15,11 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_pressed="true"
- android:drawable="@drawable/ic_delete_phone_number_blk" />
+ <item android:state_pressed="true"
+ android:drawable="@drawable/btn_dial_textfield_pressed" />
<item android:state_focused="true"
- android:drawable="@drawable/ic_delete_phone_number_blk" />
+ android:drawable="@drawable/btn_dial_textfield_selected" />
<item
- android:drawable="@drawable/ic_delete_phone_number_wht" />
+ android:drawable="@drawable/btn_dial_textfield_activated" />
</selector>
diff --git a/res/drawable-finger/ic_delete_phone_number.xml b/res/drawable/call_background.xml
similarity index 60%
copy from res/drawable-finger/ic_delete_phone_number.xml
copy to res/drawable/call_background.xml
index adfc0ce..fbc9b3c 100644
--- a/res/drawable-finger/ic_delete_phone_number.xml
+++ b/res/drawable/call_background.xml
@@ -1,5 +1,5 @@
<?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.
@@ -15,11 +15,12 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_pressed="true"
- android:drawable="@drawable/ic_delete_phone_number_blk" />
- <item android:state_focused="true"
- android:drawable="@drawable/ic_delete_phone_number_blk" />
- <item
- android:drawable="@drawable/ic_delete_phone_number_wht" />
-</selector>
+ <item android:state_window_focused="false"
+ android:drawable="@android:color/transparent" />
+ <item android:state_focused="false" android:state_pressed="true"
+ android:drawable="@*android:drawable/list_selector_background_transition" />
+ <item android:state_focused="false" android:state_pressed="false"
+ android:drawable="@android:drawable/screen_background_dark"/>
+
+</selector>
diff --git a/res/drawable/ic_launcher_contacts.png b/res/drawable/ic_launcher_contacts.png
deleted file mode 100644
index 826656f..0000000
--- a/res/drawable/ic_launcher_contacts.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/ic_menu_2sec_pause.png b/res/drawable/ic_menu_2sec_pause.png
new file mode 100644
index 0000000..dcaa5ff
--- /dev/null
+++ b/res/drawable/ic_menu_2sec_pause.png
Binary files differ
diff --git a/res/drawable/ic_menu_wait.png b/res/drawable/ic_menu_wait.png
new file mode 100644
index 0000000..c20457a
--- /dev/null
+++ b/res/drawable/ic_menu_wait.png
Binary files differ
diff --git a/res/drawable/quickcontact.9.png b/res/drawable/quickcontact.9.png
new file mode 100644
index 0000000..fa0b917
--- /dev/null
+++ b/res/drawable/quickcontact.9.png
Binary files differ
diff --git a/res/drawable/section_dark.9.png b/res/drawable/section_dark.9.png
new file mode 100644
index 0000000..5d1e1ae
--- /dev/null
+++ b/res/drawable/section_dark.9.png
Binary files differ
diff --git a/res/drawable/unknown_source.png b/res/drawable/unknown_source.png
new file mode 100644
index 0000000..356748f
--- /dev/null
+++ b/res/drawable/unknown_source.png
Binary files differ
diff --git a/res/layout-finger/all_tab_indicator.xml b/res/layout-finger/all_tab_indicator.xml
new file mode 100644
index 0000000..25294c4
--- /dev/null
+++ b/res/layout-finger/all_tab_indicator.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.
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="40dip"
+ android:layout_weight="1"
+ android:layout_marginLeft="-3dip"
+ android:layout_marginRight="-3dip"
+ android:minWidth="72dip"
+ android:background="@+drawable/tab_indicator_bg">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerInParent="true"
+ android:text="@string/all_tab_label"
+ android:textColor="@color/tab_indicator_text"
+ android:textSize="20dip"
+ />
+
+</RelativeLayout>
diff --git a/res/layout-finger/contact_card_layout.xml b/res/layout-finger/contact_card_layout.xml
new file mode 100644
index 0000000..6e70561
--- /dev/null
+++ b/res/layout-finger/contact_card_layout.xml
@@ -0,0 +1,53 @@
+<?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/card_root_view"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent">
+
+ <com.android.internal.widget.ContactHeaderWidget
+ android:id="@+id/contact_header_widget"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"/>
+
+ <ListView android:id="@+id/contact_data"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:background="@drawable/title_bar_shadow"
+ />
+
+ <ScrollView android:id="@android:id/empty"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:fillViewport="true"
+ >
+ <TextView android:id="@+id/emptyText"
+ android:layout_width="fill_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-finger/contact_options.xml b/res/layout-finger/contact_options.xml
new file mode 100644
index 0000000..e484324
--- /dev/null
+++ b/res/layout-finger/contact_options.xml
@@ -0,0 +1,31 @@
+<?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:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical"
+>
+
+ <include layout="@layout/edit_contact_entry_ringtone" android:id="@+id/ringtone" />
+ <View
+ android:layout_width="fill_parent"
+ android:layout_height="1dip"
+ android:background="?android:attr/listDivider"
+ />
+ <include layout="@layout/edit_contact_entry_voicemail" android:id="@+id/voicemail"/>
+
+</LinearLayout>
diff --git a/res/layout-finger/contacts_list_content_join.xml b/res/layout-finger/contacts_list_content_join.xml
new file mode 100644
index 0000000..b59c1b7
--- /dev/null
+++ b/res/layout-finger/contacts_list_content_join.xml
@@ -0,0 +1,70 @@
+<?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:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:background="@*android:drawable/title_bar_medium"
+ android:paddingLeft="5dip"
+ android:paddingRight="5dip"
+ android:gravity="center_vertical"
+ >
+
+ <ImageView
+ android:layout_width="48dip"
+ android:layout_height="48dip"
+ android:src="@drawable/ic_join"
+ android:gravity="center"
+ android:scaleType="fitCenter"
+ />
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:paddingLeft="10dip">
+ <TextView
+ android:layout_width="fill_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/join_contact_blurb"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="-2dip"
+ android:maxLines="2"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ />
+ </LinearLayout>
+ </LinearLayout>
+
+ <ListView android:id="@android:id/list"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:fastScrollEnabled="true"
+ />
+</LinearLayout>
+
diff --git a/res/layout-finger/contacts_list_item.xml b/res/layout-finger/contacts_list_item.xml
index 713ac33..61cc839 100644
--- a/res/layout-finger/contacts_list_item.xml
+++ b/res/layout-finger/contacts_list_item.xml
@@ -17,65 +17,115 @@
*/
-->
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
- android:layout_height="?android:attr/listPreferredItemHeight"
- android:paddingLeft="14dip"
- android:paddingRight="5dip"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
>
-
- <ImageView android:id="@+id/presence"
- android:layout_width="32dip"
- android:layout_height="32dip"
- android:layout_alignParentRight="true"
- android:layout_marginLeft="5dip"
- android:layout_centerVertical="true"
-
- android:gravity="center"
- android:scaleType="centerInside"
+ <include
+ android:id="@+id/header"
+ layout="@layout/list_section"
/>
- <TextView android:id="@+id/label"
- android:layout_width="wrap_content"
+ <RelativeLayout
+ android:layout_width="fill_parent"
+ android:layout_height="?android:attr/listPreferredItemHeight"
+ android:paddingLeft="14dip"
+ >
+
+ <LinearLayout android:id="@+id/right_side"
+ android:layout_width="wrap_content"
+ android:layout_height="fill_parent"
+ android:orientation="horizontal"
+ android:layout_marginLeft="11dip"
+ android:layout_alignParentRight="true">
+
+ <ImageView android:id="@+id/presence"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="5dip"
+ android:layout_marginRight="5dip"
+ android:padding="7dip"
+ android:layout_gravity="center_vertical"
+ android:scaleType="centerInside"
+ />
+
+ <LinearLayout android:id="@+id/call_view"
+ android:layout_width="wrap_content"
+ android:layout_height="fill_parent"
+ android:orientation="horizontal">
+
+ <View android:id="@+id/divider"
+ android:layout_width="1px"
+ android:layout_height="fill_parent"
+ android:layout_marginTop="5dip"
+ android:layout_marginBottom="5dip"
+ android:background="@drawable/divider_vertical_dark"
+ />
+
+ <com.android.contacts.ui.widget.DontPressWithParentImageView android:id="@+id/call_button"
+ android:layout_width="wrap_content"
+ android:layout_height="fill_parent"
+ android:paddingLeft="14dip"
+ android:paddingRight="14dip"
+ android:layout_centerVertical="true"
+ android:gravity="center"
+ android:src="@android:drawable/sym_action_call"
+ android:background="@drawable/call_background"
+ />
+
+ </LinearLayout>
+
+ </LinearLayout>
+
+ <TextView android:id="@+id/label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentBottom="true"
+ android:layout_marginBottom="8dip"
+ android:layout_marginTop="-8dip"
+
+ android:singleLine="true"
+ android:ellipsize="marquee"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textStyle="bold"
+ />
+
+ <TextView android:id="@+id/data"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="5dip"
+ android:layout_toRightOf="@id/label"
+ android:layout_alignBaseline="@id/label"
+ android:layout_toLeftOf="@id/right_side"
+ android:layout_alignWithParentIfMissing="true"
+
+ android:singleLine="true"
+ android:ellipsize="marquee"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ />
+
+ <TextView android:id="@+id/name"
+ android:layout_width="0dip"
+ android:layout_height="0dip"
+ android:layout_above="@id/label"
+ android:layout_alignWithParentIfMissing="true"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentLeft="true"
+ android:layout_toLeftOf="@id/right_side"
+ android:layout_marginBottom="1dip"
+
+ android:singleLine="true"
+ android:ellipsize="marquee"
+ android:gravity="center_vertical|left"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ />
+ </RelativeLayout>
+
+ <View android:id="@+id/list_divider"
+ android:layout_width="fill_parent"
android:layout_height="wrap_content"
- android:layout_alignParentLeft="true"
- android:layout_alignParentBottom="true"
- android:layout_marginBottom="8dip"
- android:layout_marginTop="-8dip"
-
- android:singleLine="true"
- android:textAppearance="?android:attr/textAppearanceSmall"
- android:textStyle="bold"
+ android:background="@*android:drawable/divider_horizontal_dark_opaque"
/>
-
- <TextView android:id="@+id/number"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginLeft="5dip"
- android:layout_toRightOf="@id/label"
- android:layout_alignBaseline="@id/label"
- android:layout_toLeftOf="@id/presence"
- android:layout_alignWithParentIfMissing="true"
-
- android:singleLine="true"
- android:ellipsize="marquee"
- android:textAppearance="?android:attr/textAppearanceSmall"
- />
-
- <TextView android:id="@+id/name"
- android:layout_width="0dip"
- android:layout_height="0dip"
- android:layout_above="@id/label"
- android:layout_alignWithParentIfMissing="true"
- android:layout_alignParentTop="true"
- android:layout_alignParentLeft="true"
- android:layout_toLeftOf="@id/presence"
- android:layout_marginBottom="1dip"
-
- android:singleLine="true"
- android:ellipsize="marquee"
- android:gravity="center_vertical|left"
- android:textAppearance="?android:attr/textAppearanceLarge"
- />
-
-</RelativeLayout>
+</LinearLayout>
diff --git a/res/layout-finger/contacts_list_item_photo.xml b/res/layout-finger/contacts_list_item_photo.xml
index c208b93..c9b4c1c 100644
--- a/res/layout-finger/contacts_list_item_photo.xml
+++ b/res/layout-finger/contacts_list_item_photo.xml
@@ -17,77 +17,129 @@
*/
-->
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
- android:layout_height="?android:attr/listPreferredItemHeight"
- android:paddingLeft="5dip"
- android:paddingRight="5dip"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
>
-
- <ImageView android:id="@+id/presence"
- android:layout_width="32dip"
- android:layout_height="32dip"
- android:layout_alignParentRight="true"
- android:layout_marginLeft="5dip"
- android:layout_centerVertical="true"
-
- android:gravity="center"
- android:scaleType="centerInside"
+ <include
+ android:id="@+id/header"
+ layout="@layout/list_section"
/>
- <ImageView android:id="@+id/photo"
- android:layout_width="54dip"
- android:layout_height="54dip"
- android:layout_alignParentLeft="true"
- android:layout_centerVertical="true"
- android:layout_marginRight="8dip"
+ <RelativeLayout
+ android:layout_width="fill_parent"
+ android:layout_height="?android:attr/listPreferredItemHeight"
+ android:paddingLeft="4dip"
+ >
- android:gravity="center"
- android:scaleType="fitCenter"
- android:background="@drawable/contact_picture_border_in_list"
- />
+ <LinearLayout android:id="@+id/right_side"
+ android:layout_width="wrap_content"
+ android:layout_height="fill_parent"
+ android:orientation="horizontal"
+ android:layout_marginLeft="11dip"
+ android:layout_alignParentRight="true">
- <TextView android:id="@+id/label"
- android:layout_width="wrap_content"
+ <ImageView android:id="@+id/presence"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="5dip"
+ android:layout_marginRight="5dip"
+ android:padding="7dip"
+ android:layout_gravity="center_vertical"
+ android:scaleType="centerInside"
+ />
+
+ <LinearLayout android:id="@+id/call_view"
+ android:layout_width="wrap_content"
+ android:layout_height="fill_parent"
+ android:orientation="horizontal">
+
+ <View android:id="@+id/divider"
+ android:layout_width="1px"
+ android:layout_height="fill_parent"
+ android:layout_marginTop="5dip"
+ android:layout_marginBottom="5dip"
+ android:background="@drawable/divider_vertical_dark"
+ />
+
+ <com.android.contacts.ui.widget.DontPressWithParentImageView android:id="@+id/call_button"
+ android:layout_width="wrap_content"
+ android:layout_height="fill_parent"
+ android:paddingLeft="14dip"
+ android:paddingRight="14dip"
+ android:layout_centerVertical="true"
+ android:gravity="center"
+ android:src="@android:drawable/sym_action_call"
+ android:background="@drawable/call_background"
+ />
+
+ </LinearLayout>
+ </LinearLayout>
+
+ <android.widget.QuickContactBadge android:id="@+id/photo"
+ android:layout_alignParentLeft="true"
+ android:layout_centerVertical="true"
+ android:layout_marginRight="8dip"
+ style="@*android:style/Widget.QuickContactBadge.WindowMedium" />
+ />
+
+ <ImageView android:id="@+id/noQuickContactPhoto"
+ android:layout_alignParentLeft="true"
+ android:layout_centerVertical="true"
+ android:layout_marginRight="8dip"
+ style="@*android:style/Widget.QuickContactBadge.WindowMedium"
+ android:background="@null" />
+ />
+
+ <TextView android:id="@+id/label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_toRightOf="@id/photo"
+ android:layout_alignParentBottom="true"
+ android:layout_marginBottom="8dip"
+ android:layout_marginTop="-10dip"
+
+ android:singleLine="true"
+ android:ellipsize="marquee"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textStyle="bold"
+ />
+
+ <TextView android:id="@+id/data"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="5dip"
+ android:layout_toRightOf="@id/label"
+ android:layout_toLeftOf="@id/right_side"
+ android:layout_alignBaseline="@id/label"
+ android:layout_alignWithParentIfMissing="true"
+
+ android:singleLine="true"
+ android:ellipsize="marquee"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ />
+
+ <TextView android:id="@+id/name"
+ android:layout_width="0dip"
+ android:layout_height="0dip"
+ android:layout_toRightOf="@id/photo"
+ android:layout_toLeftOf="@id/right_side"
+ android:layout_alignParentTop="true"
+ android:layout_above="@id/label"
+ android:layout_alignWithParentIfMissing="true"
+
+ android:singleLine="true"
+ android:ellipsize="marquee"
+ android:gravity="center_vertical|left"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ />
+
+ </RelativeLayout>
+
+ <View android:id="@+id/list_divider"
+ android:layout_width="fill_parent"
android:layout_height="wrap_content"
- android:layout_toRightOf="@id/photo"
- android:layout_alignParentBottom="true"
- android:layout_marginBottom="8dip"
- android:layout_marginTop="-10dip"
-
- android:singleLine="true"
- android:ellipsize="marquee"
- android:textAppearance="?android:attr/textAppearanceSmall"
- android:textStyle="bold"
+ android:background="@*android:drawable/divider_horizontal_dark_opaque"
/>
-
- <TextView android:id="@+id/number"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginLeft="5dip"
- android:layout_toRightOf="@id/label"
- android:layout_toLeftOf="@id/presence"
- android:layout_alignBaseline="@id/label"
- android:layout_alignWithParentIfMissing="true"
-
- android:singleLine="true"
- android:ellipsize="marquee"
- android:textAppearance="?android:attr/textAppearanceSmall"
- />
-
- <TextView android:id="@+id/name"
- android:layout_width="0dip"
- android:layout_height="0dip"
- android:layout_toRightOf="@id/photo"
- android:layout_toLeftOf="@id/presence"
- android:layout_alignParentTop="true"
- android:layout_above="@id/label"
- android:layout_alignWithParentIfMissing="true"
-
- android:singleLine="true"
- android:ellipsize="marquee"
- android:gravity="center_vertical|left"
- android:textAppearance="?android:attr/textAppearanceLarge"
- />
-
-</RelativeLayout>
+</LinearLayout>
diff --git a/res/layout-finger/contacts_list_show_all_item.xml b/res/layout-finger/contacts_list_show_all_item.xml
new file mode 100644
index 0000000..5937b9f
--- /dev/null
+++ b/res/layout-finger/contacts_list_show_all_item.xml
@@ -0,0 +1,39 @@
+<?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.
+ */
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+>
+ <TextView
+ android:layout_width="fill_parent"
+ android:layout_height="?android:attr/listPreferredItemHeight"
+ android:gravity="center_vertical|left"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:text="@string/showAllContactsJoinItem"
+ android:paddingLeft="14dip"
+ />
+
+ <View android:id="@+id/list_divider"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:background="@*android:drawable/divider_horizontal_dark_opaque"
+ />
+</LinearLayout>
diff --git a/res/layout-finger/create_new_contact.xml b/res/layout-finger/create_new_contact.xml
new file mode 100644
index 0000000..776c482
--- /dev/null
+++ b/res/layout-finger/create_new_contact.xml
@@ -0,0 +1,45 @@
+<?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:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?android:attr/listPreferredItemHeight"
+ android:orientation="horizontal"
+ android:paddingRight="6dip"
+ android:paddingLeft="6dip"
+ android:paddingTop="5dip"
+ android:paddingBottom="5dip"
+ android:gravity="center_vertical"
+ >
+ <ImageView android:id="@+id/addicon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="5dip"
+ android:layout_marginRight="11dip"
+ android:focusable="false"
+ android:src="@*android:drawable/sym_action_add"
+ android:scaleType="fitCenter"
+ />
+ <TextView android:id="@+id/title"
+ android:text="@string/pickerNewContactHeader"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textStyle="bold"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="2dip"
+ />
+</LinearLayout>
diff --git a/res/layout-finger/dialer_activity.xml b/res/layout-finger/dialer_activity.xml
index 242821b..29189c7 100644
--- a/res/layout-finger/dialer_activity.xml
+++ b/res/layout-finger/dialer_activity.xml
@@ -26,10 +26,7 @@
<TabWidget android:id="@android:id/tabs"
android:layout_width="fill_parent"
- android:layout_height="68dip"
- android:paddingLeft="1dip"
- android:paddingRight="1dip"
- android:paddingTop="4dip"
+ android:layout_height="wrap_content"
/>
<FrameLayout android:id="@android:id/tabcontent"
diff --git a/res/layout-finger/dialpad.xml b/res/layout-finger/dialpad.xml
index 8f367e5..82179db 100644
--- a/res/layout-finger/dialpad.xml
+++ b/res/layout-finger/dialpad.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.
@@ -14,25 +14,31 @@
limitations under the License.
-->
+<!-- Dialpad in the Contact app.
+ -->
+
<com.android.contacts.ButtonGridLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/dialpad"
- android:paddingLeft="16px"
- android:paddingRight="16px"
- android:layout_width="fill_parent"
+ android:paddingLeft="7dp"
+ android:paddingRight="7dp"
+ android:paddingTop="6dp"
+ android:paddingBottom="6dp"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
>
<ImageButton android:id="@+id/one"
- android:layout_width="96px"
- android:layout_height="76px"
- android:src="@drawable/dial_num_1"
+ android:layout_width="88dp"
+ android:layout_height="50dp"
+ android:src="@drawable/dial_num_1_no_vm"
android:background="@drawable/btn_dial"
android:soundEffectsEnabled="false"
android:contentDescription="@string/description_image_button_one"
/>
-
+
<ImageButton android:id="@+id/two"
- android:layout_width="96px"
- android:layout_height="76px"
+ android:layout_width="88dp"
+ android:layout_height="50dp"
android:src="@drawable/dial_num_2"
android:background="@drawable/btn_dial"
android:soundEffectsEnabled="false"
@@ -40,8 +46,8 @@
/>
<ImageButton android:id="@+id/three"
- android:layout_width="96px"
- android:layout_height="76px"
+ android:layout_width="88dp"
+ android:layout_height="50dp"
android:src="@drawable/dial_num_3"
android:background="@drawable/btn_dial"
android:soundEffectsEnabled="false"
@@ -49,17 +55,17 @@
/>
<ImageButton android:id="@+id/four"
- android:layout_width="96px"
- android:layout_height="76px"
+ android:layout_width="88dp"
+ android:layout_height="50dp"
android:src="@drawable/dial_num_4"
android:background="@drawable/btn_dial"
android:soundEffectsEnabled="false"
android:contentDescription="@string/description_image_button_four"
/>
-
+
<ImageButton android:id="@+id/five"
- android:layout_width="96px"
- android:layout_height="76px"
+ android:layout_width="88dp"
+ android:layout_height="50dp"
android:src="@drawable/dial_num_5"
android:background="@drawable/btn_dial"
android:soundEffectsEnabled="false"
@@ -67,8 +73,8 @@
/>
<ImageButton android:id="@+id/six"
- android:layout_width="96px"
- android:layout_height="76px"
+ android:layout_width="88dp"
+ android:layout_height="50dp"
android:src="@drawable/dial_num_6"
android:background="@drawable/btn_dial"
android:soundEffectsEnabled="false"
@@ -76,17 +82,17 @@
/>
<ImageButton android:id="@+id/seven"
- android:layout_width="96px"
- android:layout_height="76px"
+ android:layout_width="88dp"
+ android:layout_height="50dp"
android:src="@drawable/dial_num_7"
android:background="@drawable/btn_dial"
android:soundEffectsEnabled="false"
android:contentDescription="@string/description_image_button_seven"
/>
-
+
<ImageButton android:id="@+id/eight"
- android:layout_width="96px"
- android:layout_height="76px"
+ android:layout_width="88dp"
+ android:layout_height="50dp"
android:src="@drawable/dial_num_8"
android:background="@drawable/btn_dial"
android:soundEffectsEnabled="false"
@@ -94,8 +100,8 @@
/>
<ImageButton android:id="@+id/nine"
- android:layout_width="96px"
- android:layout_height="76px"
+ android:layout_width="88dp"
+ android:layout_height="50dp"
android:src="@drawable/dial_num_9"
android:background="@drawable/btn_dial"
android:soundEffectsEnabled="false"
@@ -103,17 +109,17 @@
/>
<ImageButton android:id="@+id/star"
- android:layout_width="96px"
- android:layout_height="76px"
+ android:layout_width="88dp"
+ android:layout_height="50dp"
android:src="@drawable/dial_num_star"
android:background="@drawable/btn_dial"
android:soundEffectsEnabled="false"
android:contentDescription="@string/description_image_button_star"
/>
-
+
<ImageButton android:id="@+id/zero"
- android:layout_width="96px"
- android:layout_height="76px"
+ android:layout_width="88dp"
+ android:layout_height="50dp"
android:src="@drawable/dial_num_0"
android:background="@drawable/btn_dial"
android:soundEffectsEnabled="false"
@@ -121,8 +127,8 @@
/>
<ImageButton android:id="@+id/pound"
- android:layout_width="96px"
- android:layout_height="76px"
+ android:layout_width="88dp"
+ android:layout_height="50dp"
android:src="@drawable/dial_num_pound"
android:background="@drawable/btn_dial"
android:soundEffectsEnabled="false"
diff --git a/res/layout-finger/display_child.xml b/res/layout-finger/display_child.xml
new file mode 100644
index 0000000..ed4856a
--- /dev/null
+++ b/res/layout-finger/display_child.xml
@@ -0,0 +1,71 @@
+<?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:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?android:attr/listPreferredItemHeight"
+ android:gravity="center_vertical"
+ android:paddingLeft="?android:attr/expandableListPreferredItemPaddingLeft"
+ android:paddingRight="?android:attr/scrollbarSize"
+>
+
+ <RelativeLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginRight="6dip"
+ android:layout_marginTop="6dip"
+ android:layout_marginBottom="6dip"
+ android:layout_weight="1"
+ android:duplicateParentState="true"
+ >
+
+ <TextView
+ android:id="@android:id/text1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:ellipsize="marquee"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:duplicateParentState="true"
+ />
+
+ <TextView
+ android:id="@android:id/text2"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@android:id/text1"
+ android:layout_alignLeft="@android:id/text1"
+ android:maxLines="2"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:duplicateParentState="true"
+ />
+
+ </RelativeLayout>
+
+ <CheckBox
+ android:id="@android:id/checkbox"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginRight="4dip"
+ android:focusable="false"
+ android:clickable="false"
+ android:gravity="center_vertical"
+ android:orientation="vertical"
+ android:duplicateParentState="true"
+ />
+
+</LinearLayout>
diff --git a/res/layout-finger/display_group.xml b/res/layout-finger/display_group.xml
new file mode 100644
index 0000000..7d36450
--- /dev/null
+++ b/res/layout-finger/display_group.xml
@@ -0,0 +1,55 @@
+<?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:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?android:attr/listPreferredItemHeight"
+ android:gravity="center_vertical"
+ android:paddingLeft="?android:attr/expandableListPreferredItemPaddingLeft"
+ android:paddingRight="?android:attr/scrollbarSize">
+
+ <RelativeLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginRight="6dip"
+ android:layout_marginTop="6dip"
+ android:layout_marginBottom="6dip"
+ android:layout_weight="1"
+ android:duplicateParentState="true">
+
+ <TextView
+ android:id="@android:id/text1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:ellipsize="marquee"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:duplicateParentState="true" />
+
+ <TextView
+ android:id="@android:id/text2"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@android:id/text1"
+ android:layout_alignLeft="@android:id/text1"
+ android:maxLines="2"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:duplicateParentState="true" />
+
+ </RelativeLayout>
+
+</LinearLayout>
diff --git a/res/layout-finger/display_header.xml b/res/layout-finger/display_header.xml
new file mode 100644
index 0000000..65f82aa
--- /dev/null
+++ b/res/layout-finger/display_header.xml
@@ -0,0 +1,66 @@
+<?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:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?android:attr/listPreferredItemHeight"
+ android:gravity="center_vertical"
+ android:paddingRight="?android:attr/scrollbarSize"
+>
+
+ <RelativeLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="20dip"
+ android:layout_marginRight="6dip"
+ android:layout_marginTop="6dip"
+ android:layout_marginBottom="6dip"
+ android:layout_weight="1"
+ >
+
+ <TextView
+ android:id="@android:id/text1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:ellipsize="marquee"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ />
+
+ <TextView
+ android:id="@android:id/text2"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@android:id/text1"
+ android:layout_alignLeft="@android:id/text1"
+ android:maxLines="2"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ />
+
+ </RelativeLayout>
+
+ <CheckBox
+ android:id="@android:id/checkbox"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginRight="4dip"
+ android:focusable="false"
+ android:clickable="false"
+ android:gravity="center_vertical"
+ android:orientation="vertical" />
+
+</LinearLayout>
diff --git a/res/layout-finger/edit_contact.xml b/res/layout-finger/edit_contact.xml
index a3a1849..e91f7e9 100644
--- a/res/layout-finger/edit_contact.xml
+++ b/res/layout-finger/edit_contact.xml
@@ -13,105 +13,110 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-
-<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
android:fillViewport="true">
- <LinearLayout
+ <ScrollView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
- android:orientation="vertical"
- >
-
- <LinearLayout android:id="@+id/banner"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:padding="0dip"
- android:gravity="center_vertical"
- android:baselineAligned="false"
- >
-
- <FrameLayout
- android:layout_width="76dip"
- android:layout_height="76dip"
- android:layout_marginTop="4dip"
- android:layout_marginLeft="6dip"
- android:layout_marginBottom="6dip"
- android:layout_marginRight="2dip"
- >
- <ImageView android:id="@+id/photoImage"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:clickable="true"
- android:focusable="true"
- android:src="@drawable/ic_menu_add_picture"
- android:scaleType="center"
- android:background="@drawable/btn_contact_picture"
- />
- </FrameLayout>
-
- <EditText android:id="@+id/name"
- android:layout_width="0dip"
- android:layout_weight="1"
- android:layout_height="wrap_content"
- android:layout_marginRight="?android:attr/scrollbarSize"
- android:gravity="center_vertical"
- android:inputType="textPersonName|textCapWords"
- android:hint="@string/ghostData_name"
- android:nextFocusDown="@id/data"
- />
-
- <ImageView android:id="@+id/star"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- />
- </LinearLayout>
-
- <!-- "Phonetic name" entry widget, visible only in certain locales -->
- <include layout="@layout/edit_phonetic_name"/>
+ android:layout_weight="1"
+ android:orientation="vertical">
<LinearLayout
android:layout_width="fill_parent"
- android:layout_height="0dip"
- android:layout_weight="1"
+ android:layout_height="fill_parent"
android:orientation="vertical"
- >
+ >
- <!-- The edit items -->
- <LinearLayout android:id="@+id/list"
+ <LinearLayout android:id="@+id/banner"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:padding="0dip"
+ android:gravity="center_vertical"
+ android:baselineAligned="false"
+ >
+
+ <FrameLayout
+ android:layout_width="76dip"
+ android:layout_height="76dip"
+ android:layout_marginTop="4dip"
+ android:layout_marginLeft="6dip"
+ android:layout_marginBottom="6dip"
+ android:layout_marginRight="2dip"
+ >
+ <ImageView android:id="@+id/photoImage"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:clickable="true"
+ android:focusable="true"
+ android:src="@drawable/ic_menu_add_picture"
+ android:scaleType="center"
+ android:background="@drawable/btn_contact_picture"
+ />
+ </FrameLayout>
+
+ <EditText android:id="@+id/name"
+ android:layout_width="0dip"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"
+ android:layout_marginRight="?android:attr/scrollbarSize"
+ android:gravity="center_vertical"
+ android:inputType="textPersonName|textCapWords"
+ android:hint="@string/ghostData_name"
+ android:nextFocusDown="@id/data"
+ />
+
+ <ImageView android:id="@+id/star"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ />
+ </LinearLayout>
+
+ <!-- "Phonetic name" entry widget, visible only in certain locales -->
+ <include layout="@layout/edit_phonetic_name"/>
+
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1"
android:orientation="vertical"
- />
+ >
+ <!-- The edit items -->
+ <LinearLayout android:id="@+id/list"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ />
+
+ </LinearLayout>
</LinearLayout>
+ </ScrollView>
- <LinearLayout
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- style="@android:style/ButtonBar"
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ style="@android:style/ButtonBar"
>
- <Button android:id="@+id/saveButton"
- android:layout_width="0dip"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:text="@string/menu_done"
- />
+ <Button android:id="@+id/saveButton"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="@string/menu_done"
+ />
- <Button android:id="@+id/discardButton"
- android:layout_width="0dip"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:text="@string/menu_doNotSave"
- />
+ <Button android:id="@+id/discardButton"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="@string/menu_doNotSave"
+ />
- </LinearLayout>
-
</LinearLayout>
-</ScrollView>
+</LinearLayout>
diff --git a/res/layout-finger/edit_contact_entry_group.xml b/res/layout-finger/edit_contact_entry_group.xml
new file mode 100644
index 0000000..b233ca8
--- /dev/null
+++ b/res/layout-finger/edit_contact_entry_group.xml
@@ -0,0 +1,69 @@
+<?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/entry_group"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="?android:attr/scrollbarSize"
+ android:minHeight="?android:attr/listPreferredItemHeight"
+ android:background="@android:drawable/list_selector_background"
+ android:orientation="horizontal"
+ android:gravity="center_vertical"
+ android:focusable="true"
+ android:clickable="true"
+ >
+
+ <RelativeLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="14dip"
+ android:layout_marginTop="6dip"
+ android:layout_marginBottom="6dip"
+ android:layout_weight="1"
+ android:duplicateParentState="true"
+ >
+
+ <TextView android:id="@+id/label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:ellipsize="marquee"
+ android:fadingEdge="horizontal"
+ android:duplicateParentState="true"
+ />
+
+ <TextView android:id="@+id/data"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@+id/label"
+ android:layout_alignLeft="@+id/label"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:singleLine="true"
+ android:ellipsize="marquee"
+ android:duplicateParentState="true"
+ />
+
+ </RelativeLayout>
+
+ <ImageView
+ style="@style/MoreButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ />
+
+</LinearLayout>
diff --git a/res/layout-finger/view_contact_name.xml b/res/layout-finger/empty.xml
similarity index 62%
copy from res/layout-finger/view_contact_name.xml
copy to res/layout-finger/empty.xml
index 126c69b..8e70c24 100644
--- a/res/layout-finger/view_contact_name.xml
+++ b/res/layout-finger/empty.xml
@@ -14,15 +14,7 @@
limitations under the License.
-->
-<!-- In the default locale, the "Name" field is a single TextView -->
-<TextView xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/name"
+<View xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@android:id/empty"
android:layout_width="0dip"
- android:layout_weight="1"
- android:layout_height="wrap_content"
- android:textAppearance="?android:attr/textAppearanceLarge"
- android:shadowColor="#BB000000"
- android:shadowRadius="2.75"
- android:maxLines="2"
- android:ellipsize="end"
- />
+ android:layout_height="0dip" />
diff --git a/res/layout-finger/list_item_text_icons.xml b/res/layout-finger/list_item_text_icons.xml
index 7f6ad1c..8d4b7a8 100644
--- a/res/layout-finger/list_item_text_icons.xml
+++ b/res/layout-finger/list_item_text_icons.xml
@@ -23,26 +23,17 @@
android:minHeight="?android:attr/listPreferredItemHeight"
android:orientation="horizontal"
android:paddingLeft="9dip"
- android:paddingRight="5dip"
android:gravity="center_vertical"
>
- <ImageView android:id="@+id/icon1"
- android:layout_width="32dip"
- android:layout_height="32dip"
- android:layout_marginRight="5dip"
- android:gravity="center"
- android:scaleType="centerInside"
- />
-
<LinearLayout
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginLeft="5dip"
+ android:orientation="vertical"
android:paddingTop="5dip"
android:paddingBottom="7dip"
- android:orientation="vertical"
android:gravity="center_vertical"
>
@@ -52,20 +43,73 @@
android:textAppearance="?android:attr/textAppearanceLarge"
/>
- <TextView android:id="@android:id/text2"
+ <LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ >
+
+ <TextView android:id="@android:id/text2"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ />
+
+ <ImageView android:id="@+id/primary_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:paddingLeft="3dip"
+ android:src="@drawable/ic_default_number"
+ />
+
+ </LinearLayout>
+
+ <TextView
+ android:id="@+id/footer"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
android:textAppearance="?android:attr/textAppearanceSmall"
- />
+ android:visibility="gone" />
</LinearLayout>
- <ImageView android:id="@+id/icon2"
- android:layout_width="32dip"
- android:layout_height="32dip"
+ <ImageView android:id="@+id/presence_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
android:layout_marginLeft="5dip"
android:gravity="center"
android:scaleType="centerInside"
/>
+ <ImageView android:id="@+id/action_icon"
+ android:layout_width="30dip"
+ android:layout_height="30dip"
+ android:layout_marginLeft="14dip"
+ android:layout_marginRight="14dip"
+ android:gravity="center"
+ android:scaleType="centerInside"
+ />
+
+ <View android:id="@+id/divider"
+ android:layout_width="1px"
+ android:layout_height="fill_parent"
+ android:layout_marginTop="5dip"
+ android:layout_marginBottom="5dip"
+ android:background="@drawable/divider_vertical_dark"
+ />
+
+ <ImageView android:id="@+id/secondary_action_button"
+ android:layout_width="wrap_content"
+ android:layout_height="fill_parent"
+ android:layout_centerVertical="true"
+ android:paddingLeft="14dip"
+ android:paddingRight="14dip"
+ android:gravity="center"
+ android:scaleType="center"
+ android:background="@android:drawable/list_selector_background"
+ />
+
</LinearLayout>
diff --git a/res/layout-finger/list_section.xml b/res/layout-finger/list_section.xml
new file mode 100644
index 0000000..06a9ffe
--- /dev/null
+++ b/res/layout-finger/list_section.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.
+-->
+
+<!-- Layout used for list section separators. -->
+<RelativeLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="25dip"
+ android:background="@*android:drawable/dark_header"
+ >
+ <TextView
+ android:id="@+id/header_text"
+ android:layout_width="56dip"
+ android:layout_height="wrap_content"
+ android:layout_centerVertical="true"
+ android:layout_alignParentLeft="true"
+ android:textStyle="bold"
+ android:textColor="@*android:color/dim_foreground_dark"
+ android:textSize="14sp"
+ android:gravity="center"
+ />
+</RelativeLayout>
diff --git a/res/layout-finger/list_separator.xml b/res/layout-finger/list_separator.xml
index 0c21541..2ab4859 100644
--- a/res/layout-finger/list_separator.xml
+++ b/res/layout-finger/list_separator.xml
@@ -17,4 +17,6 @@
<!-- Layout used for list separators. -->
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
style="?android:attr/listSeparatorTextViewStyle"
+ android:textColor="@*android:color/dim_foreground_dark"
+ android:gravity="center_horizontal"
/>
diff --git a/res/layout-finger/quickcontact.xml b/res/layout-finger/quickcontact.xml
new file mode 100644
index 0000000..13b5c20
--- /dev/null
+++ b/res/layout-finger/quickcontact.xml
@@ -0,0 +1,148 @@
+<?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.
+-->
+
+<RelativeLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:paddingLeft="@dimen/quickcontact_shadow_horiz"
+ android:paddingRight="@dimen/quickcontact_shadow_horiz"
+ android:background="@drawable/quickcontact_drop_shadow">
+
+ <FrameLayout
+ android:id="@+id/header"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="10dip">
+
+ <ViewStub
+ android:id="@+id/header_small"
+ android:inflatedId="@+id/header_small"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout="@layout/quickcontact_header_small" />
+
+ <ViewStub
+ android:id="@+id/header_medium"
+ android:inflatedId="@+id/header_medium"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout="@layout/quickcontact_header_med" />
+
+ <ViewStub
+ android:id="@+id/header_large"
+ android:inflatedId="@+id/header_large"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout="@layout/quickcontact_header_large" />
+
+ </FrameLayout>
+
+ <HorizontalScrollView
+ android:id="@+id/scroll"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/header"
+ android:fadingEdgeLength="0dip"
+ android:background="@drawable/quickcontact_slider_background"
+ android:scrollbars="none">
+
+ <LinearLayout
+ android:id="@+id/quickcontact"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingTop="4dip"
+ android:paddingBottom="4dip"
+ android:orientation="horizontal">
+
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/quickcontact_slider_grip_left" />
+
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/quickcontact_slider_grip_right" />
+
+ </LinearLayout>
+
+ </HorizontalScrollView>
+
+ <FrameLayout
+ android:id="@+id/footer"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/scroll"
+ android:background="@drawable/quickcontact_bottom_frame" />
+
+ <LinearLayout
+ android:id="@+id/footer_disambig"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/scroll"
+ android:background="@drawable/quickcontact_disambig_bottom_bg"
+ android:orientation="vertical"
+ android:visibility="gone">
+
+ <ListView
+ android:id="@android:id/list"
+ android:layout_width="fill_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1"
+ android:background="@color/quickcontact_disambig"
+ android:divider="@drawable/quickcontact_disambig_divider"
+ android:cacheColorHint="@null" />
+
+ <CheckBox
+ android:id="@android:id/checkbox"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="19dip"
+ android:layout_marginRight="19dip"
+ android:minHeight="60dip"
+ android:textColor="#f000"
+ android:textStyle="bold"
+ android:text="@string/quickcontact_remember_choice"
+ android:textAppearance="?android:attr/textAppearanceSmallInverse"
+ android:button="@drawable/quickcontact_disambig_checkbox" />
+
+ </LinearLayout>
+
+ <ImageView
+ android:id="@+id/arrow_up"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/quickcontact_arrow_up" />
+
+ <ImageView
+ android:id="@+id/arrow_down"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="-1dip"
+ android:layout_below="@id/footer"
+ android:src="@drawable/quickcontact_arrow_down" />
+
+ <ImageView
+ android:id="@+id/arrow_down_stub"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="-1dip"
+ android:layout_below="@id/footer_disambig"
+ android:visibility="invisible"
+ android:src="@drawable/quickcontact_arrow_down" />
+
+</RelativeLayout>
diff --git a/res/layout-finger/quickcontact_header_large.xml b/res/layout-finger/quickcontact_header_large.xml
new file mode 100644
index 0000000..2271ff1
--- /dev/null
+++ b/res/layout-finger/quickcontact_header_large.xml
@@ -0,0 +1,82 @@
+<?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/header_large"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="87dip"
+ android:background="@drawable/quickcontact_top_frame"
+ android:gravity="center_vertical"
+ android:orientation="horizontal">
+
+ <ImageView
+ android:id="@+id/photo"
+ android:layout_width="54dip"
+ android:layout_height="57dip"
+ android:layout_marginLeft="15dip"
+ android:background="@drawable/quickcontact_photo_frame" />
+
+ <LinearLayout
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:layout_marginLeft="15dip"
+ android:paddingRight="8dip"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/name"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:textColor="@*android:color/primary_text_light"
+ android:textStyle="bold"
+ android:textSize="18dip" />
+
+ <TextView
+ android:id="@+id/status"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:textColor="@*android:color/secondary_text_light"
+ android:textSize="15dip"
+ android:layout_marginTop="-3dip" />
+
+ <TextView
+ android:id="@+id/timestamp"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:textColor="@*android:color/secondary_text_light"
+ android:textSize="12dip"
+ android:layout_marginTop="-2dip" />
+
+ </LinearLayout>
+
+ <ImageView
+ android:id="@+id/presence"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginRight="15dip"
+ android:src="@drawable/quickcontact_slider_presence_active"
+ android:scaleType="centerInside" />
+
+</LinearLayout>
diff --git a/res/layout-finger/quickcontact_header_med.xml b/res/layout-finger/quickcontact_header_med.xml
new file mode 100644
index 0000000..c9ef2be
--- /dev/null
+++ b/res/layout-finger/quickcontact_header_med.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/header_medium"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="51dip"
+ android:background="@drawable/quickcontact_top_frame"
+ android:gravity="center_vertical"
+ android:orientation="horizontal">
+
+ <LinearLayout
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:layout_marginLeft="15dip"
+ android:layout_marginRight="15dip"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/status"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:textColor="@*android:color/primary_text_light"
+ android:textSize="15sp" />
+
+ <TextView
+ android:id="@+id/timestamp"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:textColor="@*android:color/secondary_text_light"
+ android:textSize="12sp"
+ android:layout_marginTop="-2dip" />
+
+ </LinearLayout>
+
+</LinearLayout>
diff --git a/res/layout-finger/view_contact_name.xml b/res/layout-finger/quickcontact_header_small.xml
similarity index 62%
copy from res/layout-finger/view_contact_name.xml
copy to res/layout-finger/quickcontact_header_small.xml
index 126c69b..3711dcc 100644
--- a/res/layout-finger/view_contact_name.xml
+++ b/res/layout-finger/quickcontact_header_small.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.
@@ -14,15 +14,10 @@
limitations under the License.
-->
-<!-- In the default locale, the "Name" field is a single TextView -->
-<TextView xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/name"
- android:layout_width="0dip"
- android:layout_weight="1"
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/header_small"
+ android:layout_width="fill_parent"
android:layout_height="wrap_content"
- android:textAppearance="?android:attr/textAppearanceLarge"
- android:shadowColor="#BB000000"
- android:shadowRadius="2.75"
- android:maxLines="2"
- android:ellipsize="end"
- />
+ android:background="@drawable/quickcontact_top_frame"
+ android:orientation="horizontal" />
diff --git a/res/layout-finger/quickcontact_item.xml b/res/layout-finger/quickcontact_item.xml
new file mode 100644
index 0000000..8580ac5
--- /dev/null
+++ b/res/layout-finger/quickcontact_item.xml
@@ -0,0 +1,28 @@
+<?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.
+-->
+
+<com.android.contacts.ui.widget.CheckableImageView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="59dip"
+ android:layout_height="51dip"
+ android:paddingLeft="12dip"
+ android:paddingRight="12dip"
+ android:paddingTop="8dip"
+ android:paddingBottom="8dip"
+ android:scaleType="centerInside"
+ android:focusable="true"
+ android:clickable="true"
+ android:background="@drawable/quickcontact_slider_btn" />
diff --git a/res/layout-ja-finger/view_contact_name.xml b/res/layout-finger/quickcontact_resolve_item.xml
old mode 100644
new mode 100755
similarity index 63%
rename from res/layout-ja-finger/view_contact_name.xml
rename to res/layout-finger/quickcontact_resolve_item.xml
index bd72bba..9156a59
--- a/res/layout-ja-finger/view_contact_name.xml
+++ b/res/layout-finger/quickcontact_resolve_item.xml
@@ -14,28 +14,27 @@
limitations under the License.
-->
-<!-- In Japanese-language locales, the "Name" field contains two separate
- TextViews: the name itself, and also the phonetic ("furigana") field. -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
android:orientation="vertical"
- android:layout_width="0dip"
- android:layout_weight="1"
- android:layout_height="wrap_content">
+ android:paddingLeft="30dip"
+ android:paddingRight="30dip"
+ android:minHeight="?android:attr/listPreferredItemHeight"
+ android:gravity="center_vertical">
- <TextView android:id="@+id/name"
+ <TextView
+ android:id="@android:id/text1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:textAppearance="?android:attr/textAppearanceLarge"
- android:shadowColor="#BB000000"
- android:shadowRadius="2.75"
- />
+ android:textStyle="bold"
+ android:textAppearance="?android:attr/textAppearanceMediumInverse" />
- <TextView android:id="@+id/phonetic_name"
+ <TextView
+ android:id="@android:id/text2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:textAppearance="?android:attr/textAppearanceSmall"
- android:shadowColor="#BB000000"
- android:shadowRadius="2.75"
- />
+ android:layout_marginTop="-4dip"
+ android:textAppearance="?android:attr/textAppearanceSmallInverse" />
</LinearLayout>
diff --git a/res/layout-finger/recent_calls_list_item.xml b/res/layout-finger/recent_calls_list_item.xml
index bab9ea1..5439ff3 100644
--- a/res/layout-finger/recent_calls_list_item.xml
+++ b/res/layout-finger/recent_calls_list_item.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,44 +17,44 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
- android:paddingLeft="0dip"
- android:paddingRight="12dip"
+ android:paddingLeft="7dip"
>
- <ImageView android:id="@+id/call_icon"
+ <com.android.contacts.ui.widget.DontPressWithParentImageView android:id="@+id/call_icon"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
-
android:paddingLeft="14dip"
- android:paddingRight="11dip"
-
+ android:paddingRight="14dip"
+ android:layout_alignParentRight="true"
+
android:gravity="center_vertical"
android:src="@android:drawable/sym_action_call"
- android:background="@android:drawable/list_selector_background"
+ android:background="@drawable/call_background"
/>
<View android:id="@+id/divider"
- android:layout_width="1dip"
+ android:layout_width="1px"
android:layout_height="fill_parent"
- android:layout_toRightOf="@id/call_icon"
- android:layout_marginRight="11dip"
-
+ android:layout_marginTop="5dip"
+ android:layout_marginBottom="5dip"
+ android:layout_toLeftOf="@id/call_icon"
+ android:layout_marginLeft="11dip"
android:background="@drawable/divider_vertical_dark"
/>
<ImageView android:id="@+id/call_type_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
+ android:layout_toLeftOf="@id/divider"
/>
-
+
<TextView android:id="@+id/date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_alignParentRight="true"
+ android:layout_toLeftOf="@id/divider"
android:layout_alignParentBottom="true"
- android:layout_marginBottom="9dip"
+ android:layout_marginBottom="8dip"
android:textAppearance="?android:attr/textAppearanceSmall"
android:singleLine="true"
@@ -63,7 +63,7 @@
<TextView android:id="@+id/label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_toRightOf="@id/divider"
+ android:layout_alignParentLeft="true"
android:layout_alignParentBottom="true"
android:layout_marginBottom="8dip"
android:layout_marginTop="-10dip"
@@ -88,19 +88,19 @@
android:textAppearance="?android:attr/textAppearanceSmall"
/>
-
<TextView android:id="@+id/line1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_toRightOf="@id/divider"
- android:layout_toLeftOf="@id/call_type_icon"
+ android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
+ android:layout_toLeftOf="@+id/call_type_icon"
android:layout_above="@id/label"
android:layout_alignWithParentIfMissing="true"
android:textAppearance="?android:attr/textAppearanceLarge"
android:singleLine="true"
+ android:ellipsize="marquee"
android:gravity="center_vertical"
/>
-
+
</RelativeLayout>
diff --git a/res/layout-finger/set_primary_checkbox.xml b/res/layout-finger/set_primary_checkbox.xml
new file mode 100644
index 0000000..bba8cf9
--- /dev/null
+++ b/res/layout-finger/set_primary_checkbox.xml
@@ -0,0 +1,32 @@
+<?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:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:paddingLeft="14dip"
+ android:paddingRight="15dip"
+ android:orientation="vertical">
+
+ <CheckBox
+ android:id="@+id/setPrimary"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:focusable="true"
+ android:clickable="true"
+ android:text="@string/make_primary"/>
+</LinearLayout>
diff --git a/res/layout-finger/split_aggregate_list_item.xml b/res/layout-finger/split_aggregate_list_item.xml
new file mode 100644
index 0000000..5bd4270
--- /dev/null
+++ b/res/layout-finger/split_aggregate_list_item.xml
@@ -0,0 +1,70 @@
+<?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.
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="?android:attr/listPreferredItemHeight"
+ android:paddingLeft="12dip"
+ android:paddingRight="12dip"
+>
+ <ImageView
+ android:id="@+id/sourceIcon"
+ android:layout_width="32dip"
+ android:layout_height="32dip"
+ android:layout_alignParentRight="true"
+ android:layout_alignParentTop="true"
+ android:layout_centerVertical="true"
+ android:layout_marginTop="15dip"
+ android:layout_marginLeft="5dip"
+ android:layout_marginRight="8dip"
+
+ android:gravity="center"
+ android:scaleType="centerInside"
+ />
+
+ <TextView android:id="@+id/additionalData"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_toLeftOf="@id/sourceIcon"
+ android:layout_alignParentBottom="true"
+ android:layout_alignParentLeft="true"
+ android:layout_alignWithParentIfMissing="true"
+ android:layout_marginBottom="8dip"
+ android:layout_marginTop="-8dip"
+
+ android:singleLine="true"
+ android:ellipsize="marquee"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ />
+
+ <TextView android:id="@+id/name"
+ android:layout_width="0dip"
+ android:layout_height="0dip"
+ android:layout_alignWithParentIfMissing="true"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentTop="true"
+ android:layout_above="@id/additionalData"
+ android:layout_toLeftOf="@id/sourceIcon"
+ android:layout_marginBottom="1dip"
+
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:singleLine="true"
+ android:ellipsize="marquee"
+ android:gravity="center_vertical|left"
+ />
+
+</RelativeLayout>
diff --git a/res/layout-finger/tab_account_name.xml b/res/layout-finger/tab_account_name.xml
new file mode 100644
index 0000000..dc99af1
--- /dev/null
+++ b/res/layout-finger/tab_account_name.xml
@@ -0,0 +1,29 @@
+<?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.
+-->
+
+<!-- looks like Widget.TextView.ListSeparator -->
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/account_name"
+ android:background="@drawable/bg_infobar_new"
+ android:layout_width="fill_parent"
+ android:layout_height="@dimen/account_name_height"
+ android:layout_below="@+id/tab_scroll_view"
+ android:textStyle="normal"
+ android:textColor="@*android:color/dim_foreground_dark"
+ android:textSize="12sp"
+ android:gravity="left|center_vertical"
+ android:paddingLeft="7dip"
+/>
diff --git a/res/layout-finger/tab_indicator.xml b/res/layout-finger/tab_indicator.xml
new file mode 100644
index 0000000..d43265c
--- /dev/null
+++ b/res/layout-finger/tab_indicator.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.
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/tab_height"
+ android:layout_weight="1"
+ android:layout_marginLeft="-3dip"
+ android:layout_marginRight="-3dip"
+ android:minWidth="72dip"
+ android:background="@+drawable/tab_indicator_bg">
+
+ <ImageView android:id="@+id/tab_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerInParent="true"
+ />
+
+ <TextView android:id="@+id/tab_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentBottom="true"
+ android:layout_centerHorizontal="true"
+ android:textColor="@+color/tab_indicator_text"
+ android:textSize="20dip"
+ />
+
+</RelativeLayout>
diff --git a/res/layout-finger/view_contact_name.xml b/res/layout-finger/tab_layout.xml
similarity index 61%
copy from res/layout-finger/view_contact_name.xml
copy to res/layout-finger/tab_layout.xml
index 126c69b..9a793c3 100644
--- a/res/layout-finger/view_contact_name.xml
+++ b/res/layout-finger/tab_layout.xml
@@ -14,15 +14,17 @@
limitations under the License.
-->
-<!-- In the default locale, the "Name" field is a single TextView -->
-<TextView xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/name"
- android:layout_width="0dip"
- android:layout_weight="1"
+<HorizontalScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/tab_scroll_view"
+ android:layout_width="fill_parent"
android:layout_height="wrap_content"
- android:textAppearance="?android:attr/textAppearanceLarge"
- android:shadowColor="#BB000000"
- android:shadowRadius="2.75"
- android:maxLines="2"
- android:ellipsize="end"
+ android:layout_alignParentLeft="true"
+ android:scrollbars="none"
+ android:fadingEdgeLength="0dip">
+
+ <com.android.contacts.TabStripView
+ android:id="@android:id/tabs"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
/>
+</HorizontalScrollView>
\ No newline at end of file
diff --git a/res/layout-finger/view_contact_name.xml b/res/layout-finger/tab_left_arrow.xml
similarity index 62%
copy from res/layout-finger/view_contact_name.xml
copy to res/layout-finger/tab_left_arrow.xml
index 126c69b..0ed2e57 100644
--- a/res/layout-finger/view_contact_name.xml
+++ b/res/layout-finger/tab_left_arrow.xml
@@ -14,15 +14,14 @@
limitations under the License.
-->
-<!-- In the default locale, the "Name" field is a single TextView -->
-<TextView xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/name"
- android:layout_width="0dip"
+<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/arrow"
+ android:layout_width="32dip"
+ android:layout_height="37dip"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentTop="true"
android:layout_weight="1"
- android:layout_height="wrap_content"
- android:textAppearance="?android:attr/textAppearanceLarge"
- android:shadowColor="#BB000000"
- android:shadowRadius="2.75"
- android:maxLines="2"
- android:ellipsize="end"
+ android:background="@drawable/tab_indicator_bg"
+ android:scaleType="centerInside"
+ android:src="@drawable/tab_left_arrow"
/>
diff --git a/res/layout-finger/view_contact_name.xml b/res/layout-finger/tab_right_arrow.xml
similarity index 62%
copy from res/layout-finger/view_contact_name.xml
copy to res/layout-finger/tab_right_arrow.xml
index 126c69b..de69d8e 100644
--- a/res/layout-finger/view_contact_name.xml
+++ b/res/layout-finger/tab_right_arrow.xml
@@ -14,15 +14,14 @@
limitations under the License.
-->
-<!-- In the default locale, the "Name" field is a single TextView -->
-<TextView xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/name"
- android:layout_width="0dip"
+<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/arrow"
+ android:layout_width="32dip"
+ android:layout_height="37dip"
+ android:layout_alignParentRight="true"
+ android:layout_alignParentTop="true"
android:layout_weight="1"
- android:layout_height="wrap_content"
- android:textAppearance="?android:attr/textAppearanceLarge"
- android:shadowColor="#BB000000"
- android:shadowRadius="2.75"
- android:maxLines="2"
- android:ellipsize="end"
+ android:background="@drawable/tab_indicator_bg"
+ android:scaleType="centerInside"
+ android:src="@drawable/tab_right_arrow"
/>
diff --git a/res/layout-finger/view_contact_name.xml b/res/layout-finger/total_contacts.xml
similarity index 64%
rename from res/layout-finger/view_contact_name.xml
rename to res/layout-finger/total_contacts.xml
index 126c69b..12badb6 100644
--- a/res/layout-finger/view_contact_name.xml
+++ b/res/layout-finger/total_contacts.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.
@@ -14,15 +14,14 @@
limitations under the License.
-->
-<!-- In the default locale, the "Name" field is a single TextView -->
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/name"
- android:layout_width="0dip"
- android:layout_weight="1"
- android:layout_height="wrap_content"
- android:textAppearance="?android:attr/textAppearanceLarge"
- android:shadowColor="#BB000000"
- android:shadowRadius="2.75"
- android:maxLines="2"
- android:ellipsize="end"
- />
+ android:id="@+id/totalContactsText"
+ android:layout_width="fill_parent"
+ android:layout_height="25dip"
+ android:textColor="#ffbfbfbf"
+ android:textSize="14sp"
+ android:textStyle="normal"
+ android:background="@drawable/infobar_dark"
+ android:paddingLeft="7dp"
+ android:gravity="center"
+/>
\ No newline at end of file
diff --git a/res/layout-finger/twelve_key_dialer.xml b/res/layout-finger/twelve_key_dialer.xml
index 071ca57..3550444 100644
--- a/res/layout-finger/twelve_key_dialer.xml
+++ b/res/layout-finger/twelve_key_dialer.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.
@@ -20,51 +20,38 @@
android:layout_height="fill_parent"
android:orientation="vertical"
>
- <LinearLayout android:id="@+id/digitsAndBackspace"
+
+ <!-- Text field above the keypad where the digits are displayed -->
+ <!-- TODO: Use a textAppearance to control the display of the number -->
+ <EditText android:id="@+id/digits"
android:layout_width="fill_parent"
- android:layout_height="66px"
- android:layout_marginTop="3px"
- android:layout_marginBottom="5px"
- android:layout_marginLeft="3px"
- android:layout_marginRight="3px"
- android:orientation="horizontal"
- >
-
- <EditText android:id="@+id/digits"
- android:layout_width="0dip"
- android:layout_weight="1"
- android:layout_height="66px"
- android:maxLines="1"
- android:scrollHorizontally="true"
- android:textSize="28sp"
- android:freezesText="true"
- android:background="@drawable/btn_dial_textfield"
- android:textColor="@color/dialer_button_text"
- android:focusableInTouchMode="false"
- android:nextFocusRight="@+id/backspace"
- />
-
- <ImageButton android:id="@+id/backspace"
- style="@android:style/Widget.Button.Inset"
- android:layout_width="wrap_content"
- android:layout_height="66px"
- android:background="@drawable/btn_dial_delete"
- android:src="@drawable/ic_delete_phone_number"
- android:gravity="center"
- />
-
- </LinearLayout>
+ android:layout_height="67dip"
+ android:layout_marginBottom="6dip"
+ android:gravity="center"
+ android:maxLines="1"
+ android:scrollHorizontally="true"
+ android:textSize="33sp"
+ android:freezesText="true"
+ android:background="@drawable/btn_dial_textfield"
+ android:textColor="@color/dialer_button_text"
+ android:focusableInTouchMode="true"
+ android:editable="true"
+ android:cursorVisible="false"
+ />
<!-- Keypad section -->
<include layout="@layout/dialpad" />
+ <!-- Horizontal row of buttons (Voicemail + DialButton + Delete.) -->
+ <include layout="@layout/voicemail_dial_delete" />
+
<!-- "Dialpad chooser" UI, shown only when the user brings up the
Dialer while a call is already in progress.
When this UI is visible, the other Dialer elements
(the textfield/button and the dialpad) are hidden. -->
<ListView android:id="@+id/dialpadChooser"
android:layout_width="fill_parent"
- android:layout_height="1dp"
+ android:layout_height="1dip"
android:layout_weight="1"
/>
diff --git a/res/layout-finger/view_contact.xml b/res/layout-finger/view_contact.xml
deleted file mode 100644
index 4a0f252..0000000
--- a/res/layout-finger/view_contact.xml
+++ /dev/null
@@ -1,71 +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="fill_parent"
- android:layout_height="fill_parent"
- android:orientation="vertical"
->
-
- <LinearLayout android:id="@+id/banner"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:background="@android:drawable/title_bar_tall"
- android:paddingRight="5dip"
- android:gravity="center_vertical"
- >
-
- <ImageView android:id="@+id/photo"
- style="?android:attr/imageWellStyle"
- android:layout_width="78dip"
- android:layout_height="78dip"
- android:layout_marginRight="7dip"
- android:layout_marginLeft="2dip"
- android:scaleType="fitCenter"
- android:background="@drawable/btn_contact_picture"
- />
-
- <!-- "Name" field is locale-specific. -->
- <include layout="@layout/view_contact_name"/>
-
- <CheckBox android:id="@+id/star"
- style="?android:attr/starStyle"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- />
-
- </LinearLayout>
-
- <FrameLayout
- android:layout_width="fill_parent"
- android:layout_height="0dip"
- android:layout_weight="1"
- >
-
- <View
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:background="@drawable/title_bar_shadow"
- />
-
- <ListView android:id="@android:id/list"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:scrollbarStyle="outsideOverlay"
- />
- </FrameLayout>
-</LinearLayout>
diff --git a/res/layout-finger/voicemail_dial_delete.xml b/res/layout-finger/voicemail_dial_delete.xml
new file mode 100644
index 0000000..1aa2ac4
--- /dev/null
+++ b/res/layout-finger/voicemail_dial_delete.xml
@@ -0,0 +1,61 @@
+<?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.
+-->
+
+<!-- Horizontal row of buttons (Voicemail + DialButton + Delete.) -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/voicemailAndDialAndDelete"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:layout_marginTop="6dip"
+ android:orientation="horizontal">
+
+ <!-- Onscreen "Voicemail" button.
+ The width is 75 (from the mocks) + 12 of padding from the
+ 9patch, total is 87.
+ -->
+ <ImageButton android:id="@+id/voicemailButton"
+ android:layout_width="87dip"
+ android:layout_height="50dip"
+ android:layout_gravity="center_vertical"
+ android:state_enabled="false"
+ android:background="@drawable/btn_dial_voicemail"
+ android:src="@drawable/ic_dial_action_voice_mail" />
+
+ <!-- Onscreen "Dial" button, used on all platforms by
+ default. Its usage can be disabled using resources (see
+ config.xml.) -->
+ <ImageButton android:id="@+id/dialButton"
+ android:layout_width="116dip"
+ android:layout_height="50dip"
+ android:layout_gravity="center_vertical"
+ android:state_enabled="false"
+ android:background="@drawable/btn_dial_action"
+ android:src="@drawable/ic_dial_action_call" />
+
+ <!-- Onscreen "Backspace/Delete" button
+ The width is 75 (from the mocks) + 12 of padding from the
+ 9patch, total is 87.
+ -->
+ <ImageButton android:id="@+id/deleteButton"
+ android:layout_width="87dip"
+ android:layout_height="50dip"
+ android:layout_gravity="center_vertical"
+ android:state_enabled="false"
+ android:background="@drawable/btn_dial_delete"
+ android:src="@drawable/ic_dial_action_delete" />
+</LinearLayout>
+
diff --git a/res/layout-land-finger/twelve_key_dialer.xml b/res/layout-land-finger/twelve_key_dialer.xml
index 2b436b8..8c66ff3 100644
--- a/res/layout-land-finger/twelve_key_dialer.xml
+++ b/res/layout-land-finger/twelve_key_dialer.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.
@@ -20,44 +20,26 @@
android:layout_height="fill_parent"
android:orientation="vertical"
>
- <LinearLayout android:id="@+id/digitsAndBackspace"
+
+ <!-- Text field above the keypad where the digits are displayed -->
+ <!-- TODO: Use a textAppearance to control the display of the number -->
+ <EditText android:id="@+id/digits"
android:layout_width="fill_parent"
- android:layout_height="66px"
- android:addStatesFromChildren="true"
- android:gravity="center_vertical"
- android:baselineAligned="false"
- android:layout_marginTop="3px"
- android:layout_marginBottom="5px"
- android:layout_marginLeft="3px"
- android:layout_marginRight="3px"
- >
+ android:layout_height="66dip"
+ android:layout_marginBottom="50dip"
+ android:layout_marginTop="1dip"
+ android:gravity="center"
+ android:maxLines="1"
+ android:scrollHorizontally="true"
+ android:textSize="28sp"
+ android:freezesText="true"
+ android:background="@drawable/btn_dial_textfield"
+ android:textColor="@color/dialer_button_text"
+ android:hint="@string/dialerKeyboardHintText"
+ />
- <EditText android:id="@+id/digits"
- android:layout_width="0dip"
- android:layout_weight="1"
- android:layout_height="66px"
- android:maxLines="1"
- android:scrollHorizontally="true"
- android:textSize="28sp"
- android:freezesText="true"
- android:background="@drawable/btn_dial_textfield"
- android:textColor="@color/dialer_button_text"
- android:hint="@string/dialerKeyboardHintText"
- />
-
- <!--
- The button goes at the right.
- -->
- <ImageButton android:id="@+id/backspace"
- style="@android:style/Widget.Button.Inset"
- android:src="@drawable/ic_delete_phone_number"
- android:layout_width="wrap_content"
- android:layout_height="66px"
- android:background="@drawable/btn_dial_delete"
- android:gravity="center"
- />
-
- </LinearLayout>
+ <!-- Horizontal row of buttons (Voicemail + DialButton + Delete.) -->
+ <include layout="@layout/voicemail_dial_delete" />
<!-- "Dialpad chooser" UI, shown only when the user brings up the
Dialer while a call is already in progress.
diff --git a/res/layout-long-finger/dialpad.xml b/res/layout-long-finger/dialpad.xml
new file mode 100644
index 0000000..af303fc
--- /dev/null
+++ b/res/layout-long-finger/dialpad.xml
@@ -0,0 +1,138 @@
+<?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.
+-->
+
+<!-- Dialpad in the Contact app.
+ Tall screen version with taller buttons.
+ -->
+
+<com.android.contacts.ButtonGridLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/dialpad"
+ android:paddingLeft="7dp"
+ android:paddingRight="7dp"
+ android:paddingTop="6dp"
+ android:paddingBottom="6dp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+>
+ <ImageButton android:id="@+id/one"
+ android:layout_width="88dp"
+ android:layout_height="58dp"
+ android:src="@drawable/dial_num_1_no_vm"
+ android:background="@drawable/btn_dial"
+ android:soundEffectsEnabled="false"
+ android:contentDescription="@string/description_image_button_one"
+ />
+
+ <ImageButton android:id="@+id/two"
+ android:layout_width="88dp"
+ android:layout_height="58dp"
+ android:src="@drawable/dial_num_2"
+ android:background="@drawable/btn_dial"
+ android:soundEffectsEnabled="false"
+ android:contentDescription="@string/description_image_button_two"
+ />
+
+ <ImageButton android:id="@+id/three"
+ android:layout_width="88dp"
+ android:layout_height="58dp"
+ android:src="@drawable/dial_num_3"
+ android:background="@drawable/btn_dial"
+ android:soundEffectsEnabled="false"
+ android:contentDescription="@string/description_image_button_three"
+ />
+
+ <ImageButton android:id="@+id/four"
+ android:layout_width="88dp"
+ android:layout_height="58dp"
+ android:src="@drawable/dial_num_4"
+ android:background="@drawable/btn_dial"
+ android:soundEffectsEnabled="false"
+ android:contentDescription="@string/description_image_button_four"
+ />
+
+ <ImageButton android:id="@+id/five"
+ android:layout_width="88dp"
+ android:layout_height="58dp"
+ android:src="@drawable/dial_num_5"
+ android:background="@drawable/btn_dial"
+ android:soundEffectsEnabled="false"
+ android:contentDescription="@string/description_image_button_five"
+ />
+
+ <ImageButton android:id="@+id/six"
+ android:layout_width="88dp"
+ android:layout_height="58dp"
+ android:src="@drawable/dial_num_6"
+ android:background="@drawable/btn_dial"
+ android:soundEffectsEnabled="false"
+ android:contentDescription="@string/description_image_button_six"
+ />
+
+ <ImageButton android:id="@+id/seven"
+ android:layout_width="88dp"
+ android:layout_height="58dp"
+ android:src="@drawable/dial_num_7"
+ android:background="@drawable/btn_dial"
+ android:soundEffectsEnabled="false"
+ android:contentDescription="@string/description_image_button_seven"
+ />
+
+ <ImageButton android:id="@+id/eight"
+ android:layout_width="88dp"
+ android:layout_height="58dp"
+ android:src="@drawable/dial_num_8"
+ android:background="@drawable/btn_dial"
+ android:soundEffectsEnabled="false"
+ android:contentDescription="@string/description_image_button_eight"
+ />
+
+ <ImageButton android:id="@+id/nine"
+ android:layout_width="88dp"
+ android:layout_height="58dp"
+ android:src="@drawable/dial_num_9"
+ android:background="@drawable/btn_dial"
+ android:soundEffectsEnabled="false"
+ android:contentDescription="@string/description_image_button_nine"
+ />
+
+ <ImageButton android:id="@+id/star"
+ android:layout_width="88dp"
+ android:layout_height="58dp"
+ android:src="@drawable/dial_num_star"
+ android:background="@drawable/btn_dial"
+ android:soundEffectsEnabled="false"
+ android:contentDescription="@string/description_image_button_star"
+ />
+
+ <ImageButton android:id="@+id/zero"
+ android:layout_width="88dp"
+ android:layout_height="58dp"
+ android:src="@drawable/dial_num_0"
+ android:background="@drawable/btn_dial"
+ android:soundEffectsEnabled="false"
+ android:contentDescription="@string/description_image_button_zero"
+ />
+
+ <ImageButton android:id="@+id/pound"
+ android:layout_width="88dp"
+ android:layout_height="58dp"
+ android:src="@drawable/dial_num_pound"
+ android:background="@drawable/btn_dial"
+ android:soundEffectsEnabled="false"
+ android:contentDescription="@string/description_image_button_pound"
+ />
+</com.android.contacts.ButtonGridLayout>
diff --git a/res/layout-long-finger/twelve_key_dialer.xml b/res/layout-long-finger/twelve_key_dialer.xml
new file mode 100644
index 0000000..d1d0f37
--- /dev/null
+++ b/res/layout-long-finger/twelve_key_dialer.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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/top"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical"
+>
+
+ <!-- Text field above the keypad where the digits are displayed -->
+ <!-- TODO: Use a textAppearance to control the display of the number -->
+ <EditText android:id="@+id/digits"
+ android:layout_width="fill_parent"
+ android:layout_height="74dip"
+ android:layout_marginBottom="20dip"
+ android:gravity="center"
+ android:maxLines="1"
+ android:scrollHorizontally="true"
+ android:textSize="34sp"
+ android:freezesText="true"
+ android:background="@drawable/btn_dial_textfield"
+ android:textColor="@color/dialer_button_text"
+ android:focusableInTouchMode="true"
+ android:editable="true"
+ android:cursorVisible="false"
+ />
+
+ <!-- Keypad section -->
+ <include layout="@layout/dialpad" />
+
+ <!-- Horizontal row of buttons (Voicemail + DialButton + Delete.) -->
+ <include layout="@layout/voicemail_dial_delete" />
+
+ <!-- "Dialpad chooser" UI, shown only when the user brings up the
+ Dialer while a call is already in progress.
+ When this UI is visible, the other Dialer elements
+ (the textfield/button and the dialpad) are hidden. -->
+ <ListView android:id="@+id/dialpadChooser"
+ android:layout_width="fill_parent"
+ android:layout_height="1dip"
+ android:layout_weight="1"
+ />
+
+</LinearLayout>
diff --git a/res/layout-long-finger/voicemail_dial_delete.xml b/res/layout-long-finger/voicemail_dial_delete.xml
new file mode 100644
index 0000000..58c482b
--- /dev/null
+++ b/res/layout-long-finger/voicemail_dial_delete.xml
@@ -0,0 +1,57 @@
+<?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.
+-->
+
+<!-- Horizontal row of buttons (Voicemail + DialButton + Delete.)
+ Tall screen version with taller buttons.
+ -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/voicemailAndDialAndDelete"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:layout_marginTop="24dip"
+ android:orientation="horizontal">
+
+ <!-- Onscreen "Voicemail" button -->
+ <ImageButton android:id="@+id/voicemailButton"
+ android:layout_width="87dip"
+ android:layout_height="58dip"
+ android:layout_gravity="center_vertical"
+ android:state_enabled="false"
+ android:background="@drawable/btn_dial_voicemail"
+ android:src="@drawable/ic_dial_action_voice_mail" />
+
+ <!-- Onscreen "Dial" button, used on all platforms by
+ default. Its usage can be disabled using resources (see
+ config.xml.) -->
+ <ImageButton android:id="@+id/dialButton"
+ android:layout_width="116dip"
+ android:layout_height="58dip"
+ android:layout_gravity="center_vertical"
+ android:state_enabled="false"
+ android:background="@drawable/btn_dial_action"
+ android:src="@drawable/ic_dial_action_call" />
+
+ <!-- Onscreen "Backspace/Delete" button -->
+ <ImageButton android:id="@+id/deleteButton"
+ android:layout_width="87dip"
+ android:layout_height="58dip"
+ android:layout_gravity="center_vertical"
+ android:state_enabled="false"
+ android:background="@drawable/btn_dial_delete"
+ android:src="@drawable/ic_dial_action_delete" />
+</LinearLayout>
+
diff --git a/res/layout-long-land-finger/twelve_key_dialer.xml b/res/layout-long-land-finger/twelve_key_dialer.xml
new file mode 100644
index 0000000..618792a
--- /dev/null
+++ b/res/layout-long-land-finger/twelve_key_dialer.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/top"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical"
+>
+
+ <!-- Text field above the keypad where the digits are displayed -->
+ <!-- TODO: Use a textAppearance to control the display of the number -->
+ <EditText android:id="@+id/digits"
+ android:layout_width="fill_parent"
+ android:layout_height="74dip"
+ android:layout_marginBottom="30dip"
+ android:layout_marginTop="1dip"
+ android:gravity="center"
+ android:maxLines="1"
+ android:scrollHorizontally="true"
+ android:textSize="34sp"
+ android:freezesText="true"
+ android:background="@drawable/btn_dial_textfield"
+ android:textColor="@color/dialer_button_text"
+ android:hint="@string/dialerKeyboardHintText"
+ />
+
+ <!-- Horizontal row of buttons (Voicemail + DialButton + Delete.) -->
+ <include layout="@layout/voicemail_dial_delete" />
+
+ <!-- "Dialpad chooser" UI, shown only when the user brings up the
+ Dialer while a call is already in progress.
+ When this UI is visible, the other Dialer elements
+ (the textfield and button) are hidden. -->
+ <ListView android:id="@+id/dialpadChooser"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:footerDividersEnabled="true"
+ />
+
+</LinearLayout>
diff --git a/res/layout/act_display_groups.xml b/res/layout/act_display_groups.xml
new file mode 100644
index 0000000..5ee93e7
--- /dev/null
+++ b/res/layout/act_display_groups.xml
@@ -0,0 +1,52 @@
+<?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="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical"
+ android:fillViewport="true">
+
+ <ExpandableListView
+ android:id="@android:id/list"
+ android:layout_width="fill_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1" />
+
+ <LinearLayout
+ android:layout_width="fill_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/act_edit.xml b/res/layout/act_edit.xml
new file mode 100644
index 0000000..e56ec20
--- /dev/null
+++ b/res/layout/act_edit.xml
@@ -0,0 +1,61 @@
+<?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="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical"
+>
+
+ <ScrollView
+ android:layout_width="fill_parent"
+ android:layout_height="1px"
+ android:layout_weight="1"
+ android:fillViewport="true"
+ >
+
+ <LinearLayout android:id="@+id/editors"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical"
+ />
+
+ </ScrollView>
+
+ <LinearLayout
+ android:layout_width="fill_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/item_contact_editor.xml b/res/layout/item_contact_editor.xml
new file mode 100644
index 0000000..c40352d
--- /dev/null
+++ b/res/layout/item_contact_editor.xml
@@ -0,0 +1,178 @@
+<?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.
+-->
+
+<!-- placed inside act_edit as tabcontent -->
+<com.android.contacts.ui.widget.ContactEditorView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+>
+
+ <!-- Left side color bar -->
+ <ImageView
+ android:id="@+id/color_bar"
+ android:layout_width="5dip"
+ android:layout_height="fill_parent"
+ />
+
+ <!-- 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="fill_parent"
+ >
+
+ <ImageView android:id="@+id/header_color_bar"
+ android:layout_width="fill_parent"
+ android:layout_height="2dip"
+ android:layout_marginBottom="5dip"
+ />
+
+ <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"
+
+ 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"
+
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textColor="?android:attr/textColorPrimary"
+ android:singleLine="true"
+ />
+
+ <View
+ android:layout_width="fill_parent"
+ android:layout_height="1px"
+ android:layout_alignParentBottom="true"
+
+ android:background="?android:attr/listDivider"
+ />
+
+ </RelativeLayout>
+
+ <FrameLayout
+ android:id="@+id/stub_photo"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:paddingLeft="12dip"
+ android:paddingTop="10dip">
+
+ <include
+ android:id="@+id/edit_photo"
+ layout="@layout/item_photo_editor" />
+
+ </FrameLayout>
+
+ <include
+ android:id="@+id/edit_name"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/stub_photo"
+ android:layout_marginTop="6dip"
+ android:layout_marginBottom="4dip"
+ layout="@layout/item_generic_editor" />
+
+ <TextView android:id="@+id/read_only_name"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="6dip"
+ android:layout_marginBottom="6dip"
+ android:layout_marginLeft="10dip"
+
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ />
+
+ <LinearLayout
+ android:id="@+id/sect_general"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ />
+
+ <View android:id="@+id/head_secondary_divider"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:background="?android:attr/listDivider" />
+
+ <TextView
+ android:id="@+id/head_secondary"
+ android:layout_width="fill_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="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical" />
+
+ <TextView
+ android:id="@+id/edit_read_only"
+ android:layout_width="fill_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"
+ />
+
+ </LinearLayout>
+
+</com.android.contacts.ui.widget.ContactEditorView>
diff --git a/res/drawable-finger/dial_num_1.xml b/res/layout/item_editor_field.xml
similarity index 65%
copy from res/drawable-finger/dial_num_1.xml
copy to res/layout/item_editor_field.xml
index 48737b2..1e77068 100644
--- a/res/drawable-finger/dial_num_1.xml
+++ b/res/layout/item_editor_field.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.
@@ -14,12 +14,7 @@
limitations under the License.
-->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_pressed="true"
- android:drawable="@drawable/dial_num_1_blk" />
- <item android:state_focused="true"
- android:drawable="@drawable/dial_num_1_blk" />
- <item
- android:drawable="@drawable/dial_num_1_wht" />
-</selector>
-
+<EditText
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content" />
diff --git a/res/layout/item_generic_editor.xml b/res/layout/item_generic_editor.xml
new file mode 100644
index 0000000..01fa980
--- /dev/null
+++ b/res/layout/item_generic_editor.xml
@@ -0,0 +1,59 @@
+<?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" />
+
+</com.android.contacts.ui.widget.GenericEditorView>
diff --git a/res/layout/item_kind_section.xml b/res/layout/item_kind_section.xml
new file mode 100644
index 0000000..73912aa
--- /dev/null
+++ b/res/layout/item_kind_section.xml
@@ -0,0 +1,71 @@
+<?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.
+-->
+
+<!-- the body surrounding all editors for a specific kind -->
+
+<com.android.contacts.ui.widget.KindSectionView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?android:attr/listPreferredItemHeight"
+ android:orientation="vertical">
+
+ <View
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:background="?android:attr/listDivider" />
+
+ <LinearLayout
+ android:id="@+id/kind_header"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:layout_marginLeft="14dip"
+ android:layout_marginTop="2dip"
+ android:layout_marginBottom="2dip"
+ android:layout_marginRight="?android:attr/scrollbarSize"
+ android:orientation="horizontal"
+ android:gravity="center_vertical"
+ android:focusable="true"
+ android:clickable="true">
+
+ <TextView
+ android:id="@+id/kind_title"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textColor="@color/kind_title"
+ android:singleLine="true"
+ android:ellipsize="marquee"
+ android:fadingEdge="horizontal" />
+
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:duplicateParentState="true"
+ style="@style/PlusButton" />
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/kind_editors"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:paddingBottom="6dip"
+ android:orientation="vertical" />
+
+</com.android.contacts.ui.widget.KindSectionView>
diff --git a/res/layout/item_photo_editor.xml b/res/layout/item_photo_editor.xml
new file mode 100644
index 0000000..7544439
--- /dev/null
+++ b/res/layout/item_photo_editor.xml
@@ -0,0 +1,27 @@
+<?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.
+-->
+
+<com.android.contacts.ui.widget.PhotoEditorView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="@dimen/edit_photo_size"
+ android:layout_height="@dimen/edit_photo_size"
+ android:clickable="true"
+ android:focusable="true"
+ android:src="@drawable/ic_menu_add_picture"
+ android:cropToPadding="true"
+ android:scaleType="center"
+ android:background="@drawable/btn_contact_picture"
+ android:gravity="center" />
diff --git a/res/menu/edit.xml b/res/menu/edit.xml
new file mode 100644
index 0000000..6fafbcf
--- /dev/null
+++ b/res/menu/edit.xml
@@ -0,0 +1,49 @@
+<?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_done"
+ android:alphabeticShortcut="\n"
+ android:icon="@android:drawable/ic_menu_save"
+ android:title="@string/menu_done" />
+
+ <item
+ android:id="@+id/menu_discard"
+ android:alphabeticShortcut="q"
+ android:icon="@android:drawable/ic_menu_close_clear_cancel"
+ android:title="@string/menu_doNotSave" />
+
+ <item
+ android:id="@+id/menu_add"
+ android:icon="@android:drawable/ic_menu_add"
+ android:title="@string/menu_newContact" />
+
+ <item
+ android:id="@+id/menu_delete"
+ android:icon="@android:drawable/ic_menu_delete"
+ android:title="@string/menu_deleteContact" />
+
+ <item
+ android:id="@+id/menu_split"
+ android:icon="@drawable/ic_menu_split"
+ android:title="@string/menu_splitAggregate" />
+
+ <item
+ android:id="@+id/menu_join"
+ android:icon="@drawable/ic_menu_merge"
+ android:title="@string/menu_joinAggregate" />
+</menu>
diff --git a/res/menu/list.xml b/res/menu/list.xml
new file mode 100644
index 0000000..b8f9b76
--- /dev/null
+++ b/res/menu/list.xml
@@ -0,0 +1,44 @@
+<?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_search"
+ android:icon="@android:drawable/ic_menu_search"
+ android:title="@string/menu_search" />
+
+ <item
+ android:id="@+id/menu_add"
+ android:icon="@android:drawable/ic_menu_add"
+ android:title="@string/menu_newContact"
+ android:alphabeticShortcut="n" />
+
+ <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/view.xml b/res/menu/view.xml
new file mode 100644
index 0000000..428434d
--- /dev/null
+++ b/res/menu/view.xml
@@ -0,0 +1,40 @@
+<?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_edit"
+ android:icon="@android:drawable/ic_menu_edit"
+ android:title="@string/menu_editContact"
+ android:alphabeticShortcut="e" />
+
+ <item
+ android:id="@+id/menu_share"
+ android:icon="@android:drawable/ic_menu_share"
+ android:title="@string/menu_share"
+ android:alphabeticShortcut="s" />
+
+ <item
+ android:id="@+id/menu_options"
+ android:icon="@drawable/ic_menu_mark"
+ android:title="@string/menu_contactOptions" />
+
+ <item
+ android:id="@+id/menu_delete"
+ android:icon="@android:drawable/ic_menu_delete"
+ android:title="@string/menu_deleteContact" />
+
+</menu>
diff --git a/res/values-cs/config.xml b/res/values-cs/config.xml
deleted file mode 100644
index 07b3c7e..0000000
--- a/res/values-cs/config.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for config_export_vcard_type (9145748078116159716) -->
- <skip />
- <string name="config_export_dir" msgid="222182743639478636">"/sdcard"</string>
- <!-- no translation found for config_export_file_prefix (3022868431158658122) -->
- <skip />
- <!-- no translation found for config_export_file_suffix (16505844221142195) -->
- <skip />
- <string name="config_export_file_extension" msgid="1758878818611339161">"vcf"</string>
- <!-- no translation found for config_export_extensions_to_consider (5095044502091950623) -->
- <skip />
-</resources>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index b7cc16e..79c7f19 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -40,11 +40,24 @@
<string name="menu_showBarcode" msgid="309973637178814132">"Zobrazit čárový kód"</string>
<string name="menu_editContact" msgid="3452858480713561396">"Upravit kontakt"</string>
<string name="menu_deleteContact" msgid="1916555454274101750">"Smazat kontakt"</string>
- <string name="menu_call" msgid="7359207953236681606">"Volat"</string>
- <string name="menu_sendSMS" msgid="5917289601666766617">"Odeslat SMS nebo MMS"</string>
+ <!-- no translation found for menu_call (3992595586042260618) -->
+ <skip />
+ <!-- no translation found for menu_sendSMS (5535886767547006515) -->
+ <skip />
<string name="menu_sendEmail" msgid="7293508859242926187">"Odeslat e-mail"</string>
<string name="menu_viewAddress" msgid="1814744325763202024">"Adresa na mapě"</string>
<string name="menu_makeDefaultNumber" msgid="4838759253316649534">"Nastavit jako výchozí číslo"</string>
+ <string name="menu_makeDefaultEmail" msgid="2599044610375789994">"Nastavit jako výchozí e-mail"</string>
+ <string name="menu_splitAggregate" msgid="7402548456225901170">"Oddělit"</string>
+ <string name="splitAggregate_title" msgid="2053462872948058798">"Oddělit kontakt"</string>
+ <string name="contactsSplitMessage" msgid="3439008547775199243">"Kontakt byl oddělen"</string>
+ <string name="menu_joinAggregate" msgid="5027981918265667970">"Spojit"</string>
+ <string name="titleJoinAggregate" msgid="6970566008563147202">"Připojit kontakt"</string>
+ <string name="separatorJoinAggregateSuggestions" msgid="5494823322479834017">"Doporučení"</string>
+ <string name="separatorJoinAggregateAll" msgid="7939932265026181043">"Všechny kontakty"</string>
+ <string name="contactsJoinedMessage" msgid="7208148163607047389">"Kontakty byly spojeny"</string>
+ <string name="menu_contactOptions" msgid="1957061455705020617">"Možnosti"</string>
+ <string name="contactOptionsTitle" msgid="8259347644090700915">"Možnosti"</string>
<string name="deleteConfirmation_title" msgid="6394309508930335204">"Smazat"</string>
<string name="deleteConfirmation" msgid="811706994761610640">"Tento kontakt bude smazán."</string>
<string name="menu_done" msgid="796017761764190697">"Hotovo"</string>
@@ -55,6 +68,9 @@
<string name="label_phonetic_name" msgid="2288082649573927286">"Foneticky"</string>
<string name="label_notes" msgid="8337354953278341042">"Poznámky"</string>
<string name="label_ringtone" msgid="8833166825330686244">"Vyzváněcí tón"</string>
+ <string name="label_groups" msgid="7304551384542859026">"Skupiny"</string>
+ <string name="group_list" msgid="8583361685440161307">", <xliff:g id="GROUPNAME">%s</xliff:g>"</string>
+ <string name="menu_viewGroup" msgid="1401851715586577551">"Upravit skupiny"</string>
<string name="ghostData_name" msgid="6490954238641157585">"Jméno a příjmení"</string>
<string name="ghostData_phonetic_name" msgid="7852749081984070902">"Jméno (foneticky)"</string>
<string name="ghostData_company" msgid="5414421120553765775">"Společnost"</string>
@@ -64,6 +80,7 @@
<string name="ghostData_phone" msgid="6963153888271466620">"Telefonní číslo"</string>
<string name="ghostData_email" msgid="6184537075551565919">"E-mailová adresa"</string>
<string name="ghostData_postal" msgid="652611650594951897">"Poštovní adresa"</string>
+ <string name="ghostData_group" msgid="4504591380347534114">"Zobrazit skupinu"</string>
<string name="invalidContactMessage" msgid="5816991830260044593">"Kontakt neexistuje."</string>
<string name="pickerNewContactHeader" msgid="7750705279843568147">"Vytvořit nový kontakt"</string>
<string name="selectLabel" msgid="4255424123394910733">"Vyberte štítek"</string>
@@ -80,7 +97,8 @@
<string name="photoPickerNotFoundText" msgid="431331662154342581">"V telefonu nejsou žádné fotografie."</string>
<string name="attachToContact" msgid="8820530304406066714">"Ikona kontaktu"</string>
<string name="customLabelPickerTitle" msgid="1081475101983255212">"Název vlastního štítku"</string>
- <string name="menu_displayGroup" msgid="4603480747214476335">"Zobrazit skupinu"</string>
+ <string name="menu_displayGroup" msgid="6022511302986343108">"Zobrazit skupiny"</string>
+ <string name="displayGroups" msgid="2645969886032719891">"Zobrazit skupiny"</string>
<string name="syncGroupPreference" msgid="9028361137161162861">"Upravit synchronizaci"</string>
<string name="importFromSim" msgid="8383900146531125319">"Importovat kontakty"</string>
<string name="send_to_voicemail_checkbox" msgid="9001686764070676353">"Odesílat hovory přímo do hlasové pošty"</string>
@@ -94,12 +112,23 @@
<string name="select_group_title" msgid="7955698611959835612">"Skupiny"</string>
<string name="groupEmpty" msgid="6661950109828194595">"Skupina <xliff:g id="GROUPNAME">%s</xliff:g> je prázdná."</string>
<string name="showAllGroups" msgid="5164410117611094297">"Všechny kontakty"</string>
+ <string name="showFilterPhones" msgid="4184858075465653970">"Pouze kontakty s telefonními čísly"</string>
+ <string name="showFilterPhonesDescrip" msgid="6644443248815191067">"Zobrazit pouze kontakty s telefonními čísly"</string>
+ <string name="headerContactGroups" msgid="1451383377487746789">"Skupiny kontaktů"</string>
+ <plurals name="groupDescrip">
+ <item quantity="other" msgid="3507881585720628389">"Počet kontaktů: <xliff:g id="COUNT">%0$d</xliff:g>"</item>
+ </plurals>
+ <plurals name="groupDescripPhones">
+ <item quantity="other" msgid="3816047547470490208">"Počet kontaktů: <xliff:g id="COUNT_0">%1$d</xliff:g>, z toho <xliff:g id="COUNTWITHPHONES">%2$d</xliff:g> s telefonními čísly"</item>
+ </plurals>
<string name="syncAllGroups" msgid="7512169081224806890">"Synchronizovat všechny kontakty"</string>
<string name="groupNameMyContacts" msgid="5696566165170977126">"Moje kontakty"</string>
<string name="groupNameWithPhones" msgid="6981588604041120497">"Kontakty s telefonním číslem"</string>
<string name="starredInAndroid" msgid="6495527538140213440">"Označené hvězdičkou v systému Android"</string>
<string name="contactCreatedToast" msgid="8740102129968688060">"Kontakt byl vytvořen."</string>
<string name="contactSavedToast" msgid="7152589189385441091">"Kontakt byl uložen."</string>
+ <!-- no translation found for contactSavedErrorToast (9189098776225004666) -->
+ <skip />
<string name="listSeparatorCallNumber" msgid="7115321894439629408">"Vytočit číslo"</string>
<string name="listSeparatorCallNumber_edit" msgid="2999167270900823334">"Telefonní čísla"</string>
<string name="listSeparatorSendSmsMms" msgid="2480944736714739967">"Odeslat SMS nebo MMS"</string>
@@ -110,9 +139,11 @@
<string name="listSeparatorMapAddress" msgid="7076401301758190804">"Adresa na mapě"</string>
<string name="listSeparatorMapAddress_edit" msgid="298711187672067985">"Poštovní adresa"</string>
<string name="listSeparatorOrganizations" msgid="7514083358440762504">"Organizace"</string>
+ <string name="listSeparatorGroups" msgid="1593940073983422450">"Skupiny"</string>
<string name="listSeparatorOtherInformation" msgid="7844959649638482329">"Ostatní informace"</string>
<string name="listSeparatorOtherInformation_edit" msgid="1326921768011367750">"Další možnosti"</string>
<string name="listSeparatorMore_edit" msgid="858454837482243176">"Další"</string>
+ <string name="socialStreamIconLabel" msgid="4367712449555075376">"Společenské"</string>
<string name="contactsIconLabel" msgid="7666609097606552806">"Kontakty"</string>
<string name="contactsFavoritesLabel" msgid="8417039765586853670">"Oblíbené"</string>
<string name="dialerIconLabel" msgid="7228520966699542850">"Vytáčení"</string>
@@ -128,6 +159,7 @@
<string name="recentCalls_deleteAll" msgid="6352364392762163704">"Vymazat hovory"</string>
<string name="recentCalls_empty" msgid="247053222448663107">"Záznam hovorů je prázdný."</string>
<string name="imei" msgid="3045126336951684285">"IMEI"</string>
+ <string name="meid" msgid="6210568493746275750">"MEID"</string>
<string name="voicemail" msgid="3851469869202611441">"Hlasová schránka"</string>
<string name="unknown" msgid="740067747858270469">"Neznámé číslo"</string>
<string name="private_num" msgid="6374339738119166953">"Soukromé číslo"</string>
@@ -138,7 +170,14 @@
<string name="simContacts_title" msgid="27341688347689769">"Kontakty na kartě SIM"</string>
<string name="contactsSyncPlug" msgid="7248276704957313698"><font fgcolor="#ffffffff">"Synchronizujte své kontakty Google!"</font>" "\n"Po synchronizaci telefonu budete mít kontaktní informace vždy u sebe."</string>
<string name="noContactsHelpText" msgid="2249195687463896364">"Nemáte žádné kontakty."\n\n"Chcete-li kontakty přidat, stiskněte tlačítko "<font fgcolor="#ffffffff"><b>"Menu"</b></font>" a vyberte možnost:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Nový kontakt"</b></font>", pokud chcete vytvořit nový kontakt"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importovat kontakty"</b></font>", pokud chcete přidat kontakty ze své karty SIM"\n</li>"."</string>
- <string name="noContactsHelpTextWithSync" msgid="4927701496550314555">"Nemáte žádné kontakty."\n\n"Chcete-li přidat kontakty, stiskněte tlačítko "<font fgcolor="#ffffffff"><b>"Menu"</b></font>" a vyberte možnost:"\n\n<li><font fgcolor="#ffffffff"><b>"Upravit synchronizované skupiny"</b></font>", pokud chcete přidat kontakty z nového nebo existujícího účtu Google"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Nový kontakt"</b></font>", pokud chcete vytvořit nový kontakt"\n</li>\n<li><font fgcolor="#ffffffff"><b>"Importovat kontakty"</b></font>", pokud chcete přidat kontakty ze své karty SIM"\n</li>"."</string>
+ <!-- no translation found for noContactsHelpTextWithSync (2179024855526352215) -->
+ <skip />
+ <!-- no translation found for noContactsNoSimHelpText (645951808197910024) -->
+ <skip />
+ <!-- no translation found for noContactsNoSimHelpTextWithSync (511505839941079550) -->
+ <skip />
+ <!-- no translation found for noFavoritesHelpText (1705381563293866510) -->
+ <skip />
<string name="seclectSyncGroups_title" msgid="1235432026231325655">"Vyberte skupiny, které chcete synchronizovat"</string>
<string name="liveFolder_all_label" msgid="1552523730090319259">"Všechny kontakty"</string>
<string name="liveFolder_favorites_label" msgid="2674341514070517105">"S hvězdičkou"</string>
@@ -173,6 +212,7 @@
<string name="favoritesFrquentSeparator" msgid="5007070838253932139">"Často volané"</string>
<string name="add_contact_dlg_title" msgid="2789046541229846116">"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>
+ <string name="all_tab_label" msgid="4003124364397916826">"Všechny"</string>
<string name="description_image_button_one" msgid="1740638037139856139">"jedna"</string>
<string name="description_image_button_two" msgid="5882638439003731308">"dvě"</string>
<string name="description_image_button_three" msgid="8709731759376015180">"tři"</string>
@@ -189,19 +229,27 @@
<string name="no_sdcard_message" msgid="7791906118138538259">"Nebyla zjištěna žádná karta SD"</string>
<string name="searching_vcard_title" msgid="4158580043290687793">"Vyhledávání karty VCard"</string>
<string name="select_import_type_title" msgid="2443742794103731022">"Odkud chcete kontakty importovat?"</string>
- <string name="import_from_sim" msgid="3362158539070179154">"Karta SIM"</string>
- <string name="import_from_sdcard" msgid="8854793070007530689">"Karta SD"</string>
- <string name="import_all_vcard_string" msgid="2788222288962542415">"Importovat všechny soubory VCard"</string>
- <string name="import_one_vcard_string" msgid="4618119296910253689">"Importovat jeden soubor karty VCard"</string>
+ <!-- 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 />
+ <string name="import_one_vcard_string" msgid="9059163467020328433">"Importovat jeden soubor vCard"</string>
+ <string name="import_multiple_vcard_string" msgid="3810226492811062392">"Importovat několik souborů vCard"</string>
+ <string name="import_all_vcard_string" msgid="5518136113853448474">"Importovat všechny soubory vCard"</string>
<string name="searching_vcard_message" msgid="2467573749792455605">"Vyhledávání dat karty VCard na kartě VCard"</string>
<string name="scanning_sdcard_failed_title" msgid="2788276113399585924">"Vyhledávání na kartě SD se nezdařilo"</string>
<string name="scanning_sdcard_failed_message" msgid="925361244069058117">"Vyhledávání na kartě SD se nezdařilo"</string>
- <string name="fail_reason_io_error" msgid="5922864781066136340">"Chyba V/V"</string>
- <string name="fail_reason_vcard_parse_error" msgid="2129476089250836277">"Analýza karty VCard se nezdařila"</string>
+ <string name="fail_reason_io_error" msgid="5949038573678667996">"Chyba V/V (Důvod: <xliff:g id="FAIL_REASON">%s</xliff:g>)"</string>
+ <string name="fail_reason_vcard_parse_error" msgid="8877496734269540489">"Analýza karty VCard se z neznámého důvodu nezdařila"</string>
+ <string name="fail_reason_vcard_not_supported_error" msgid="166568606246563552">"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="1489638537002538332">"Na kartě SD nebyl nalezen žádný soubor karty VCard"</string>
<string name="fail_reason_no_vcard_entry" msgid="3808734131680935043">"Pro daný výběr nebyla nalezena žádná položka karty VCard"</string>
+ <string name="fail_reason_failed_to_read_files" msgid="3659521123567134029">"Import jednoho nebo více souborů se nezdařil (%s)."</string>
<string name="select_vcard_title" msgid="4615933643517710543">"Výběr souboru VCard"</string>
<string name="select_vcard_message" msgid="7028772212789057858">"Vyberte soubor karty VCard k importu"</string>
+ <string name="progress_shower_message" msgid="5636525578293752526">"<xliff:g id="ACTION">%s</xliff:g>"\n"<xliff:g id="FILENAME">%s</xliff:g>"</string>
<string name="reading_vcard_title" msgid="430154704397375160">"Čtení karty VCard"</string>
<string name="reading_vcard_message" msgid="7470486051482460267">"Čtení souborů karty VCard"</string>
<string name="importing_vcard_message" msgid="2466665710812588738">"Probíhá import dat na kartě VCard"</string>
@@ -224,4 +272,104 @@
<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="1488865103809190124">"Přidat 2s pauzu"</string>
+ <string name="add_wait" msgid="8347184715316058465">"Přidat čekání"</string>
+ <string name="dial_button_label" msgid="7637725632722605863">"Vytočit"</string>
+ <!-- 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 />
+ <string name="quickcontact_missing_app" msgid="1181287640680679441">"Aplikace potřebná k provedení této akce nebyla nalezena"</string>
+ <string name="quickcontact_remember_choice" msgid="2858356075887897116">"Zapamatovat tuto volbu"</string>
+ <string name="quickcontact_missing_name" msgid="7806083820928955853">"Neznámé"</string>
+ <string name="menu_accounts" msgid="8499114602017077970">"Účty"</string>
+ <!-- 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">"Jméno"</string>
+ <string name="nicknameLabelsGroup" msgid="2891682101053358010">"Přezdívka"</string>
+ <string name="organizationLabelsGroup" msgid="2478611760751832035">"Organizace"</string>
+ <string name="websiteLabelsGroup" msgid="4202998982804009261">"Webové stránky"</string>
+ <string name="type_short_home" msgid="7770424864090605384">"D"</string>
+ <string name="type_short_mobile" msgid="1655473281466676216">"M"</string>
+ <string name="type_short_work" msgid="4925330752504537861">"P"</string>
+ <string name="type_short_pager" msgid="2613818970827594238">"P"</string>
+ <string name="type_short_other" msgid="5669407180177236769">"O"</string>
+ <string name="edit_secondary_collapse" msgid="1855127176548440583">"Sekundární podrobnosti"</string>
+ <string name="dialog_primary_name" msgid="5521591005692614833">"Primární jméno"</string>
+ <string name="dialog_new_contact_account" msgid="9044704073286262197">"Vytvořit kontakt na základě účtu"</string>
+ <string name="menu_sync_remove" msgid="3266725887008450161">"Odstranit synchronizovanou skupinu"</string>
+ <!-- no translation found for dialog_sync_add (8267045393119375803) -->
+ <skip />
+ <!-- no translation found for display_more_groups (2682547080423434170) -->
+ <skip />
+ <string name="display_ungrouped" msgid="4357317661311803984">"(Kontakty mimo skupinu)"</string>
+ <string name="display_warn_remove_ungrouped" msgid="2314043155909167610">"Odebráním skupiny <xliff:g id="GROUP">%s</xliff:g> ze synchronizace odeberete ze synchronizace také všechny kontakty mimo skupinu."</string>
+ <string name="account_phone" msgid="4025734638492419713">"Pouze v telefonu (nesynchronizováno)"</string>
+ <string name="label_email_display_name" msgid="5537802602754309600">"Zobrazované jméno"</string>
+ <string name="call_home" msgid="1990519474420545392">"Volat domů"</string>
+ <string name="call_mobile" msgid="7502236805487609178">"Volat mobil"</string>
+ <string name="call_work" msgid="5328785911463744028">"Volat do práce"</string>
+ <string name="call_fax_work" msgid="7467763592359059243">"Volat pracovní fax"</string>
+ <string name="call_fax_home" msgid="8342175628887571876">"Volat domácí fax"</string>
+ <string name="call_pager" msgid="9003902812293983281">"Volat pager"</string>
+ <string name="call_custom" msgid="7756571794763171802">"Volat kontakt <xliff:g id="CUSTOM">%s</xliff:g>"</string>
+ <string name="call_car" msgid="3280537320306436445">"Volat do auta"</string>
+ <string name="call_company_main" msgid="6105120947138711257">"Volat firmu (hlavní)"</string>
+ <string name="call_mms" msgid="6274041545876221437">"Volat MMS"</string>
+ <string name="call_radio" msgid="8296755876398357063">"Volat radiostanici"</string>
+ <string name="sms_home" msgid="7524332261493162995">"Poslat SMS domů"</string>
+ <string name="sms_mobile" msgid="5200107250451030769">"Poslat SMS na mobil"</string>
+ <string name="sms_work" msgid="2269624156655267740">"Poslat SMS do práce"</string>
+ <string name="sms_fax_work" msgid="8028189067816907075">"Poslat SMS na pracovní fax"</string>
+ <string name="sms_fax_home" msgid="9204042076306809634">"Poslat SMS na domácí fax"</string>
+ <string name="sms_pager" msgid="7730404569637015192">"Poslat SMS na pager"</string>
+ <string name="sms_other" msgid="5131921487474531617">"Poslat SMS na ostatní"</string>
+ <string name="sms_custom" msgid="5932736853732191825">"Poslat SMS na <xliff:g id="CUSTOM">%s</xliff:g>"</string>
+ <string name="sms_car" msgid="7444227058437359641">"Poslat SMS do auta"</string>
+ <string name="sms_company_main" msgid="118970873419678087">"Poslat SMS do firmy (hlavní)"</string>
+ <string name="sms_mms" msgid="4069352461380762677">"Poslat SMS na číslo MMS"</string>
+ <string name="sms_radio" msgid="3329166673433967820">"Poslat SMS na radiostanici"</string>
+ <string name="email_home" msgid="8573740658148184279">"E-mail domů"</string>
+ <string name="email_mobile" msgid="2042889209787989814">"E-mail na mobil"</string>
+ <string name="email_work" msgid="2807430017302722689">"E-mail do práce"</string>
+ <string name="email_other" msgid="8093933498541795832">"E-mail ostatní"</string>
+ <string name="email_custom" msgid="7548003991586214105">"E-mail <xliff:g id="CUSTOM">%s</xliff:g>"</string>
+ <string name="email" msgid="5668400997660065897">"E-mail"</string>
+ <string name="map_home" msgid="1243547733423343982">"Zobrazit adresu domů"</string>
+ <string name="map_work" msgid="1360474076921878088">"Zobrazit pracovní adresu"</string>
+ <string name="map_other" msgid="5560707927535653892">"Zobrazit ostatní adresy"</string>
+ <string name="map_custom" msgid="6184363799976265281">"Zobrazit adresu <xliff:g id="CUSTOM">%s</xliff:g>"</string>
+ <string name="chat_aim" msgid="2588492205291249142">"Chatovat pomocí AIM"</string>
+ <string name="chat_msn" msgid="8041633440091073484">"Chatovat pomocí Windows Live"</string>
+ <string name="chat_yahoo" msgid="6629211142719943666">"Chatovat pomocí Yahoo"</string>
+ <string name="chat_skype" msgid="1210045020427480566">"Chatovat pomocí Skype"</string>
+ <string name="chat_qq" msgid="4294637812847719693">"Chatovat pomocí QQ"</string>
+ <string name="chat_gtalk" msgid="981575737258117697">"Chatovat pomocí Google Talk"</string>
+ <string name="chat_icq" msgid="8438405386153745775">"Chatovat pomocí ICQ"</string>
+ <string name="chat_jabber" msgid="7561444230307829609">"Chatovat pomocí Jabberu"</string>
+ <string name="postal_street" msgid="8133143961580058972">"Ulice"</string>
+ <string name="postal_pobox" msgid="4431938829180269821">"Číslo poštovní schránky"</string>
+ <string name="postal_neighborhood" msgid="1450783874558956739">"Čtvrť"</string>
+ <string name="postal_city" msgid="6597491300084895548">"Město"</string>
+ <string name="postal_region" msgid="6045263193478437672">"Stát"</string>
+ <string name="postal_postcode" msgid="572136414136673751">"PSČ"</string>
+ <string name="postal_country" msgid="7638264508416368690">"Země"</string>
+ <string name="name_given" msgid="1687286314106019813">"Křestní jméno"</string>
+ <string name="name_family" msgid="3416695586119999058">"Příjmení"</string>
+ <string name="name_prefix" msgid="59756378548779822">"Titul před jménem"</string>
+ <string name="name_middle" msgid="8467433655992690326">"Další jméno"</string>
+ <string name="name_suffix" msgid="3855278445375651441">"Titul za jménem"</string>
+ <string name="name_phonetic_given" msgid="6853570431394449191">"Křestní jméno (foneticky)"</string>
+ <string name="name_phonetic_middle" msgid="8643721493320405200">"Další jméno (foneticky)"</string>
+ <string name="name_phonetic_family" msgid="462095502140180305">"Příjmení (foneticky)"</string>
</resources>
diff --git a/res/values-da/config.xml b/res/values-da/config.xml
deleted file mode 100644
index 07b3c7e..0000000
--- a/res/values-da/config.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for config_export_vcard_type (9145748078116159716) -->
- <skip />
- <string name="config_export_dir" msgid="222182743639478636">"/sdcard"</string>
- <!-- no translation found for config_export_file_prefix (3022868431158658122) -->
- <skip />
- <!-- no translation found for config_export_file_suffix (16505844221142195) -->
- <skip />
- <string name="config_export_file_extension" msgid="1758878818611339161">"vcf"</string>
- <!-- no translation found for config_export_extensions_to_consider (5095044502091950623) -->
- <skip />
-</resources>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index 1f9446d..1a3d060 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -40,11 +40,24 @@
<string name="menu_showBarcode" msgid="309973637178814132">"Vis stregkode"</string>
<string name="menu_editContact" msgid="3452858480713561396">"Rediger kontakt"</string>
<string name="menu_deleteContact" msgid="1916555454274101750">"Slet kontakt"</string>
- <string name="menu_call" msgid="7359207953236681606">"Opkald"</string>
- <string name="menu_sendSMS" msgid="5917289601666766617">"Send SMS/MMS"</string>
+ <!-- no translation found for menu_call (3992595586042260618) -->
+ <skip />
+ <!-- no translation found for menu_sendSMS (5535886767547006515) -->
+ <skip />
<string name="menu_sendEmail" msgid="7293508859242926187">"Send e-mail"</string>
<string name="menu_viewAddress" msgid="1814744325763202024">"Kortadresse"</string>
<string name="menu_makeDefaultNumber" msgid="4838759253316649534">"Gør til standardnummer"</string>
+ <string name="menu_makeDefaultEmail" msgid="2599044610375789994">"Benyt e-mail som standard"</string>
+ <string name="menu_splitAggregate" msgid="7402548456225901170">"Opdel"</string>
+ <string name="splitAggregate_title" msgid="2053462872948058798">"Opdel kontakt"</string>
+ <string name="contactsSplitMessage" msgid="3439008547775199243">"Opdeling af kontaktperson"</string>
+ <string name="menu_joinAggregate" msgid="5027981918265667970">"Føj til"</string>
+ <string name="titleJoinAggregate" msgid="6970566008563147202">"Føj til kontakt"</string>
+ <string name="separatorJoinAggregateSuggestions" msgid="5494823322479834017">"Forslag:"</string>
+ <string name="separatorJoinAggregateAll" msgid="7939932265026181043">"Alle kontakter"</string>
+ <string name="contactsJoinedMessage" msgid="7208148163607047389">"Tilføjede kontaktpersoner"</string>
+ <string name="menu_contactOptions" msgid="1957061455705020617">"Valgmuligheder ..."</string>
+ <string name="contactOptionsTitle" msgid="8259347644090700915">"Valgmuligheder ..."</string>
<string name="deleteConfirmation_title" msgid="6394309508930335204">"Slet"</string>
<string name="deleteConfirmation" msgid="811706994761610640">"Denne kontakt slettes"</string>
<string name="menu_done" msgid="796017761764190697">"Færdig"</string>
@@ -55,6 +68,9 @@
<string name="label_phonetic_name" msgid="2288082649573927286">"Fonetisk"</string>
<string name="label_notes" msgid="8337354953278341042">"Noter"</string>
<string name="label_ringtone" msgid="8833166825330686244">"Ringetone"</string>
+ <string name="label_groups" msgid="7304551384542859026">"Grupper"</string>
+ <string name="group_list" msgid="8583361685440161307">", <xliff:g id="GROUPNAME">%s</xliff:g>"</string>
+ <string name="menu_viewGroup" msgid="1401851715586577551">"Rediger grupper"</string>
<string name="ghostData_name" msgid="6490954238641157585">"Fornavn og efternavn"</string>
<string name="ghostData_phonetic_name" msgid="7852749081984070902">"Fonetisk navn"</string>
<string name="ghostData_company" msgid="5414421120553765775">"Virksomhed"</string>
@@ -64,6 +80,7 @@
<string name="ghostData_phone" msgid="6963153888271466620">"Telefonnummer"</string>
<string name="ghostData_email" msgid="6184537075551565919">"E-mail-adresse"</string>
<string name="ghostData_postal" msgid="652611650594951897">"Postadresse"</string>
+ <string name="ghostData_group" msgid="4504591380347534114">"Vis grupper"</string>
<string name="invalidContactMessage" msgid="5816991830260044593">"Kontakten eksisterer ikke."</string>
<string name="pickerNewContactHeader" msgid="7750705279843568147">"Opret ny kontakt"</string>
<string name="selectLabel" msgid="4255424123394910733">"Vælg etiket"</string>
@@ -80,7 +97,8 @@
<string name="photoPickerNotFoundText" msgid="431331662154342581">"Der er ingen tilgængelige billeder på telefonen."</string>
<string name="attachToContact" msgid="8820530304406066714">"Kontaktikon"</string>
<string name="customLabelPickerTitle" msgid="1081475101983255212">"Navn på tilpasset etiket"</string>
- <string name="menu_displayGroup" msgid="4603480747214476335">"Vis grupper"</string>
+ <string name="menu_displayGroup" msgid="6022511302986343108">"Vis grupper"</string>
+ <string name="displayGroups" msgid="2645969886032719891">"Vis grupper"</string>
<string name="syncGroupPreference" msgid="9028361137161162861">"Rediger grupper, synk."</string>
<string name="importFromSim" msgid="8383900146531125319">"Importer kontakter"</string>
<string name="send_to_voicemail_checkbox" msgid="9001686764070676353">"Send opkald direkte til voicemail"</string>
@@ -94,12 +112,23 @@
<string name="select_group_title" msgid="7955698611959835612">"Grupper"</string>
<string name="groupEmpty" msgid="6661950109828194595">"Din gruppe \"<xliff:g id="GROUPNAME">%s</xliff:g>\" er tom."</string>
<string name="showAllGroups" msgid="5164410117611094297">"Alle kontakter"</string>
+ <string name="showFilterPhones" msgid="4184858075465653970">"Kun kontaktpersoner med telefoner"</string>
+ <string name="showFilterPhonesDescrip" msgid="6644443248815191067">"Vis kun kontaktpersoner med telefonnumre"</string>
+ <string name="headerContactGroups" msgid="1451383377487746789">"Kontaktgrupper"</string>
+ <plurals name="groupDescrip">
+ <item quantity="other" msgid="3507881585720628389">"<xliff:g id="COUNT">%0$d</xliff:g> Kontakter"</item>
+ </plurals>
+ <plurals name="groupDescripPhones">
+ <item quantity="other" msgid="3816047547470490208">"<xliff:g id="COUNT_0">%1$d</xliff:g> kontaktpersoner, <xliff:g id="COUNTWITHPHONES">%2$d</xliff:g> med telefoner"</item>
+ </plurals>
<string name="syncAllGroups" msgid="7512169081224806890">"Synkroniser alle kontakter"</string>
<string name="groupNameMyContacts" msgid="5696566165170977126">"Mine kontakter"</string>
<string name="groupNameWithPhones" msgid="6981588604041120497">"Kontakter med telefonnumre"</string>
<string name="starredInAndroid" msgid="6495527538140213440">"Stjernemarkerede i Android"</string>
<string name="contactCreatedToast" msgid="8740102129968688060">"Kontakt oprettet."</string>
<string name="contactSavedToast" msgid="7152589189385441091">"Kontakten er gemt."</string>
+ <!-- no translation found for contactSavedErrorToast (9189098776225004666) -->
+ <skip />
<string name="listSeparatorCallNumber" msgid="7115321894439629408">"Ring til nummer"</string>
<string name="listSeparatorCallNumber_edit" msgid="2999167270900823334">"Telefonnumre"</string>
<string name="listSeparatorSendSmsMms" msgid="2480944736714739967">"Send SMS/MMS"</string>
@@ -110,9 +139,11 @@
<string name="listSeparatorMapAddress" msgid="7076401301758190804">"Kortadresse"</string>
<string name="listSeparatorMapAddress_edit" msgid="298711187672067985">"Postadresse"</string>
<string name="listSeparatorOrganizations" msgid="7514083358440762504">"Organisationer"</string>
+ <string name="listSeparatorGroups" msgid="1593940073983422450">"Grupper"</string>
<string name="listSeparatorOtherInformation" msgid="7844959649638482329">"Andre oplysninger"</string>
<string name="listSeparatorOtherInformation_edit" msgid="1326921768011367750">"Andre valgmuligheder"</string>
<string name="listSeparatorMore_edit" msgid="858454837482243176">"Flere"</string>
+ <string name="socialStreamIconLabel" msgid="4367712449555075376">"Sociale"</string>
<string name="contactsIconLabel" msgid="7666609097606552806">"Kontakter"</string>
<string name="contactsFavoritesLabel" msgid="8417039765586853670">"Favorit"</string>
<string name="dialerIconLabel" msgid="7228520966699542850">"Opkald"</string>
@@ -128,6 +159,7 @@
<string name="recentCalls_deleteAll" msgid="6352364392762163704">"Ryd opkaldsliste"</string>
<string name="recentCalls_empty" msgid="247053222448663107">"Opkaldsliste er tom."</string>
<string name="imei" msgid="3045126336951684285">"IMEI"</string>
+ <string name="meid" msgid="6210568493746275750">"MEID"</string>
<string name="voicemail" msgid="3851469869202611441">"Voicemail"</string>
<string name="unknown" msgid="740067747858270469">"Ukendte"</string>
<string name="private_num" msgid="6374339738119166953">"Privat nummer"</string>
@@ -138,7 +170,14 @@
<string name="simContacts_title" msgid="27341688347689769">"Kontakter på SIM-kort"</string>
<string name="contactsSyncPlug" msgid="7248276704957313698"><font fgcolor="#ffffffff">"Synkroniser dine Google-kontakter!"</font>\n"Når du har synkroniseret din telefon, bliver dine kontakter tilgængelige, uanset hvor du befinder dig."</string>
<string name="noContactsHelpText" msgid="2249195687463896364">"Du har ingen kontakter."\n\n"For at tilføje kontakter skal du trykke på "<font fgcolor="#ffffffff"><b>"Menu"</b></font>" og vælge:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Ny kontakt"</b></font>" for at oprette en ny kontakt fra bunden"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importer kontakter"</b></font>" for at tilføje kontakter fra dit SIM-kort"\n</li></string>
- <string name="noContactsHelpTextWithSync" msgid="4927701496550314555">"Du har ingen kontakter."\n\n"For at tilføje kontakter skal du trykke på "<font fgcolor="#ffffffff"><b>"Menu"</b></font>" og vælge:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Rediger grupper, der skal synkroniseres"</b></font>", for at tilføje fra en ny eller eksisterende Google-konto"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Ny kontakt"</b></font>" for at oprette en ny kontakt fra bunden"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importer kontakter"</b></font>" for at tilføje kontakter fra dit SIM-kort"\n</li></string>
+ <!-- no translation found for noContactsHelpTextWithSync (2179024855526352215) -->
+ <skip />
+ <!-- no translation found for noContactsNoSimHelpText (645951808197910024) -->
+ <skip />
+ <!-- no translation found for noContactsNoSimHelpTextWithSync (511505839941079550) -->
+ <skip />
+ <!-- no translation found for noFavoritesHelpText (1705381563293866510) -->
+ <skip />
<string name="seclectSyncGroups_title" msgid="1235432026231325655">"Vælg grupper, der skal synkroniseres"</string>
<string name="liveFolder_all_label" msgid="1552523730090319259">"Alle kontakter"</string>
<string name="liveFolder_favorites_label" msgid="2674341514070517105">"Stjernemarkerede"</string>
@@ -173,6 +212,7 @@
<string name="favoritesFrquentSeparator" msgid="5007070838253932139">"Ofte ringet til"</string>
<string name="add_contact_dlg_title" msgid="2789046541229846116">"Tilføj kontakt"</string>
<string name="add_contact_dlg_message_fmt" msgid="7986472669444326576">"Føj \"<xliff:g id="EMAIL">%s</xliff:g>\" til kontakter?"</string>
+ <string name="all_tab_label" msgid="4003124364397916826">"Alle"</string>
<string name="description_image_button_one" msgid="1740638037139856139">"et"</string>
<string name="description_image_button_two" msgid="5882638439003731308">"to"</string>
<string name="description_image_button_three" msgid="8709731759376015180">"tre"</string>
@@ -189,19 +229,27 @@
<string name="no_sdcard_message" msgid="7791906118138538259">"Der blev ikke fundet noget SD-kort"</string>
<string name="searching_vcard_title" msgid="4158580043290687793">"Søger efter VCard"</string>
<string name="select_import_type_title" msgid="2443742794103731022">"Hvor ønsker du at importere kontakter fra?"</string>
- <string name="import_from_sim" msgid="3362158539070179154">"SIM-kort"</string>
- <string name="import_from_sdcard" msgid="8854793070007530689">"SD-kort"</string>
- <string name="import_all_vcard_string" msgid="2788222288962542415">"Importer alle VCard-filer"</string>
- <string name="import_one_vcard_string" msgid="4618119296910253689">"Importer en VCard-fil"</string>
+ <!-- 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 />
+ <string name="import_one_vcard_string" msgid="9059163467020328433">"Importer en VCard-fil"</string>
+ <string name="import_multiple_vcard_string" msgid="3810226492811062392">"Importer flere VCard-filer"</string>
+ <string name="import_all_vcard_string" msgid="5518136113853448474">"Importer alle VCard-filer"</string>
<string name="searching_vcard_message" msgid="2467573749792455605">"Søger efter VCard-data på VCard"</string>
<string name="scanning_sdcard_failed_title" msgid="2788276113399585924">"Scanningen af SD-kortet mislykkedes"</string>
<string name="scanning_sdcard_failed_message" msgid="925361244069058117">"Scanningen af SD-kortet mislykkedes"</string>
- <string name="fail_reason_io_error" msgid="5922864781066136340">"I/O-fejl"</string>
- <string name="fail_reason_vcard_parse_error" msgid="2129476089250836277">"VCard kunne ikke parses"</string>
+ <string name="fail_reason_io_error" msgid="5949038573678667996">"I/O-fejl: (Årsag: \"<xliff:g id="FAIL_REASON">%s</xliff:g>\")"</string>
+ <string name="fail_reason_vcard_parse_error" msgid="8877496734269540489">"VCard kunne af en uventet årsag ikke parses"</string>
+ <string name="fail_reason_vcard_not_supported_error" msgid="166568606246563552">"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="1489638537002538332">"Der blev ikke fundet noget VCard på SD-kortet"</string>
<string name="fail_reason_no_vcard_entry" msgid="3808734131680935043">"Der blev ikke fundet nogen gyldig VCard-post til dit udvalg"</string>
+ <string name="fail_reason_failed_to_read_files" msgid="3659521123567134029">"En eller flere filer blev ikke importeret (%s)."</string>
<string name="select_vcard_title" msgid="4615933643517710543">"Vælg VCard-fil"</string>
<string name="select_vcard_message" msgid="7028772212789057858">"Vælg en VCard-fil, der skal importeres"</string>
+ <string name="progress_shower_message" msgid="5636525578293752526">"<xliff:g id="ACTION">%s</xliff:g>"\n"<xliff:g id="FILENAME">%s</xliff:g>"</string>
<string name="reading_vcard_title" msgid="430154704397375160">"Læser VCard"</string>
<string name="reading_vcard_message" msgid="7470486051482460267">"Læser VCard-fil(er)"</string>
<string name="importing_vcard_message" msgid="2466665710812588738">"Importerer VCard-data"</string>
@@ -224,4 +272,104 @@
<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="1488865103809190124">"Tilføj pause på 2 sek."</string>
+ <string name="add_wait" msgid="8347184715316058465">"Tilføj Vent"</string>
+ <string name="dial_button_label" msgid="7637725632722605863">"Ring til"</string>
+ <!-- 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 />
+ <string name="quickcontact_missing_app" msgid="1181287640680679441">"Der blev ikke fundet noget program til denne handling"</string>
+ <string name="quickcontact_remember_choice" msgid="2858356075887897116">"Husk dette valg"</string>
+ <string name="quickcontact_missing_name" msgid="7806083820928955853">"Ukendte"</string>
+ <string name="menu_accounts" msgid="8499114602017077970">"Konti"</string>
+ <!-- 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">"Navn"</string>
+ <string name="nicknameLabelsGroup" msgid="2891682101053358010">"Kaldenavn"</string>
+ <string name="organizationLabelsGroup" msgid="2478611760751832035">"Organisation"</string>
+ <string name="websiteLabelsGroup" msgid="4202998982804009261">"Websted"</string>
+ <string name="type_short_home" msgid="7770424864090605384">"H"</string>
+ <string name="type_short_mobile" msgid="1655473281466676216">"M"</string>
+ <string name="type_short_work" msgid="4925330752504537861">"O"</string>
+ <string name="type_short_pager" msgid="2613818970827594238">"P"</string>
+ <string name="type_short_other" msgid="5669407180177236769">"O"</string>
+ <string name="edit_secondary_collapse" msgid="1855127176548440583">"Sekundære detaljer"</string>
+ <string name="dialog_primary_name" msgid="5521591005692614833">"Primærnavn"</string>
+ <string name="dialog_new_contact_account" msgid="9044704073286262197">"Opret kontaktperson under konto"</string>
+ <string name="menu_sync_remove" msgid="3266725887008450161">"Fjern synkroniseringsgruppe"</string>
+ <!-- no translation found for dialog_sync_add (8267045393119375803) -->
+ <skip />
+ <!-- no translation found for display_more_groups (2682547080423434170) -->
+ <skip />
+ <string name="display_ungrouped" msgid="4357317661311803984">"(Kontaktpersoner uden grupper)"</string>
+ <string name="display_warn_remove_ungrouped" msgid="2314043155909167610">"Hvis \'<xliff:g id="GROUP">%s</xliff:g>\' fjernes fra synkronisering, vil kontaktpersoner, der ikke er i grupper, også fjernes fra synkroniseringen."</string>
+ <string name="account_phone" msgid="4025734638492419713">"Kun telefon (ikke synkroniseret)"</string>
+ <string name="label_email_display_name" msgid="5537802602754309600">"Vis navn"</string>
+ <string name="call_home" msgid="1990519474420545392">"Ring hjem"</string>
+ <string name="call_mobile" msgid="7502236805487609178">"Ring til mobil"</string>
+ <string name="call_work" msgid="5328785911463744028">"Ring til arbejde"</string>
+ <string name="call_fax_work" msgid="7467763592359059243">"Ring til arbejdsfax"</string>
+ <string name="call_fax_home" msgid="8342175628887571876">"Ring til hjemmefax"</string>
+ <string name="call_pager" msgid="9003902812293983281">"Ring til personsøger"</string>
+ <string name="call_custom" msgid="7756571794763171802">"Ring til <xliff:g id="CUSTOM">%s</xliff:g>"</string>
+ <string name="call_car" msgid="3280537320306436445">"Ring til bil"</string>
+ <string name="call_company_main" msgid="6105120947138711257">"Ring til arbejde (hovednummer)"</string>
+ <string name="call_mms" msgid="6274041545876221437">"Ring til MMS"</string>
+ <string name="call_radio" msgid="8296755876398357063">"Ring til radio"</string>
+ <string name="sms_home" msgid="7524332261493162995">"Sms til hjem"</string>
+ <string name="sms_mobile" msgid="5200107250451030769">"Sms til mobil"</string>
+ <string name="sms_work" msgid="2269624156655267740">"Sms til arbejde"</string>
+ <string name="sms_fax_work" msgid="8028189067816907075">"Sms til arbejdsfax"</string>
+ <string name="sms_fax_home" msgid="9204042076306809634">"Sms til hjemmefax"</string>
+ <string name="sms_pager" msgid="7730404569637015192">"Sms til personsøger"</string>
+ <string name="sms_other" msgid="5131921487474531617">"Sms til anden"</string>
+ <string name="sms_custom" msgid="5932736853732191825">"Tekst <xliff:g id="CUSTOM">%s</xliff:g>"</string>
+ <string name="sms_car" msgid="7444227058437359641">"Sms til bil"</string>
+ <string name="sms_company_main" msgid="118970873419678087">"Sms til arbejde (hovednummer)"</string>
+ <string name="sms_mms" msgid="4069352461380762677">"Sms til MMS"</string>
+ <string name="sms_radio" msgid="3329166673433967820">"Sms til radio"</string>
+ <string name="email_home" msgid="8573740658148184279">"E-mail til hjem"</string>
+ <string name="email_mobile" msgid="2042889209787989814">"E-mail til mobil"</string>
+ <string name="email_work" msgid="2807430017302722689">"E-mail til arbejde"</string>
+ <string name="email_other" msgid="8093933498541795832">"E-mail til anden"</string>
+ <string name="email_custom" msgid="7548003991586214105">"E-mail <xliff:g id="CUSTOM">%s</xliff:g>"</string>
+ <string name="email" msgid="5668400997660065897">"E-mail"</string>
+ <string name="map_home" msgid="1243547733423343982">"Vis hjemmeadresse"</string>
+ <string name="map_work" msgid="1360474076921878088">"Vis arbejdsadresse"</string>
+ <string name="map_other" msgid="5560707927535653892">"Vis anden adresse"</string>
+ <string name="map_custom" msgid="6184363799976265281">"Vis <xliff:g id="CUSTOM">%s</xliff:g> adresse"</string>
+ <string name="chat_aim" msgid="2588492205291249142">"Chat ved hjælp af AIM"</string>
+ <string name="chat_msn" msgid="8041633440091073484">"Chat ved hjælp af Windows Live"</string>
+ <string name="chat_yahoo" msgid="6629211142719943666">"Chat ved hjælp af Yahoo"</string>
+ <string name="chat_skype" msgid="1210045020427480566">"Chat ved hjælp af Skype"</string>
+ <string name="chat_qq" msgid="4294637812847719693">"Chat ved hjælp af QQ"</string>
+ <string name="chat_gtalk" msgid="981575737258117697">"Chat ved hjælp af Google Talk"</string>
+ <string name="chat_icq" msgid="8438405386153745775">"Chat ved hjælp af ICQ"</string>
+ <string name="chat_jabber" msgid="7561444230307829609">"Chat ved hjælp af Jabber"</string>
+ <string name="postal_street" msgid="8133143961580058972">"Gade"</string>
+ <string name="postal_pobox" msgid="4431938829180269821">"Postboks"</string>
+ <string name="postal_neighborhood" msgid="1450783874558956739">"Nabolag"</string>
+ <string name="postal_city" msgid="6597491300084895548">"By"</string>
+ <string name="postal_region" msgid="6045263193478437672">"Tilstand"</string>
+ <string name="postal_postcode" msgid="572136414136673751">"Postnummer"</string>
+ <string name="postal_country" msgid="7638264508416368690">"Land"</string>
+ <string name="name_given" msgid="1687286314106019813">"Fornavn"</string>
+ <string name="name_family" msgid="3416695586119999058">"Efternavn"</string>
+ <string name="name_prefix" msgid="59756378548779822">"Navnepræfiks"</string>
+ <string name="name_middle" msgid="8467433655992690326">"Mellemnavn"</string>
+ <string name="name_suffix" msgid="3855278445375651441">"Navnesuffiks"</string>
+ <string name="name_phonetic_given" msgid="6853570431394449191">"Fonetisk fornavn"</string>
+ <string name="name_phonetic_middle" msgid="8643721493320405200">"Fonetisk mellemnavn"</string>
+ <string name="name_phonetic_family" msgid="462095502140180305">"Fonetisk efternavn"</string>
</resources>
diff --git a/res/values-de/config.xml b/res/values-de/config.xml
deleted file mode 100644
index 07b3c7e..0000000
--- a/res/values-de/config.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for config_export_vcard_type (9145748078116159716) -->
- <skip />
- <string name="config_export_dir" msgid="222182743639478636">"/sdcard"</string>
- <!-- no translation found for config_export_file_prefix (3022868431158658122) -->
- <skip />
- <!-- no translation found for config_export_file_suffix (16505844221142195) -->
- <skip />
- <string name="config_export_file_extension" msgid="1758878818611339161">"vcf"</string>
- <!-- no translation found for config_export_extensions_to_consider (5095044502091950623) -->
- <skip />
-</resources>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index 0d7520c..e13626a 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -40,11 +40,24 @@
<string name="menu_showBarcode" msgid="309973637178814132">"Barcode anzeigen"</string>
<string name="menu_editContact" msgid="3452858480713561396">"Kontakt bearbeiten"</string>
<string name="menu_deleteContact" msgid="1916555454274101750">"Kontakt löschen"</string>
- <string name="menu_call" msgid="7359207953236681606">"Anruf"</string>
- <string name="menu_sendSMS" msgid="5917289601666766617">"SMS/MMS senden"</string>
+ <!-- no translation found for menu_call (3992595586042260618) -->
+ <skip />
+ <!-- no translation found for menu_sendSMS (5535886767547006515) -->
+ <skip />
<string name="menu_sendEmail" msgid="7293508859242926187">"E-Mail senden"</string>
<string name="menu_viewAddress" msgid="1814744325763202024">"Adresse auf der Karte"</string>
<string name="menu_makeDefaultNumber" msgid="4838759253316649534">"Als Standardnummer festlegen"</string>
+ <string name="menu_makeDefaultEmail" msgid="2599044610375789994">"Standard-E-Mail festlegen"</string>
+ <string name="menu_splitAggregate" msgid="7402548456225901170">"Teilen"</string>
+ <string name="splitAggregate_title" msgid="2053462872948058798">"Kontakt teilen"</string>
+ <string name="contactsSplitMessage" msgid="3439008547775199243">"Kontakt geteilt"</string>
+ <string name="menu_joinAggregate" msgid="5027981918265667970">"Anfügen"</string>
+ <string name="titleJoinAggregate" msgid="6970566008563147202">"Kontakt verknüpfen"</string>
+ <string name="separatorJoinAggregateSuggestions" msgid="5494823322479834017">"Vorschläge"</string>
+ <string name="separatorJoinAggregateAll" msgid="7939932265026181043">"Alle Kontakte"</string>
+ <string name="contactsJoinedMessage" msgid="7208148163607047389">"Kontakte wurden verknüpft."</string>
+ <string name="menu_contactOptions" msgid="1957061455705020617">"Optionen"</string>
+ <string name="contactOptionsTitle" msgid="8259347644090700915">"Optionen"</string>
<string name="deleteConfirmation_title" msgid="6394309508930335204">"Löschen"</string>
<string name="deleteConfirmation" msgid="811706994761610640">"Dieser Kontakt wird gelöscht."</string>
<string name="menu_done" msgid="796017761764190697">"Fertig"</string>
@@ -55,6 +68,9 @@
<string name="label_phonetic_name" msgid="2288082649573927286">"Phonetisch"</string>
<string name="label_notes" msgid="8337354953278341042">"Notizen"</string>
<string name="label_ringtone" msgid="8833166825330686244">"Klingelton"</string>
+ <string name="label_groups" msgid="7304551384542859026">"Gruppen"</string>
+ <string name="group_list" msgid="8583361685440161307">", <xliff:g id="GROUPNAME">%s</xliff:g>"</string>
+ <string name="menu_viewGroup" msgid="1401851715586577551">"Gruppen bearbeiten"</string>
<string name="ghostData_name" msgid="6490954238641157585">"Vor- und Nachname"</string>
<string name="ghostData_phonetic_name" msgid="7852749081984070902">"Phonetischer Name"</string>
<string name="ghostData_company" msgid="5414421120553765775">"Unternehmen"</string>
@@ -64,6 +80,7 @@
<string name="ghostData_phone" msgid="6963153888271466620">"Telefonnummer"</string>
<string name="ghostData_email" msgid="6184537075551565919">"E-Mail-Adresse"</string>
<string name="ghostData_postal" msgid="652611650594951897">"Postanschrift"</string>
+ <string name="ghostData_group" msgid="4504591380347534114">"Gruppe anzeigen"</string>
<string name="invalidContactMessage" msgid="5816991830260044593">"Dieser Kontakt existiert nicht."</string>
<string name="pickerNewContactHeader" msgid="7750705279843568147">"Neuen Kontakt erstellen"</string>
<string name="selectLabel" msgid="4255424123394910733">"Wählen Sie ein Label aus."</string>
@@ -80,7 +97,8 @@
<string name="photoPickerNotFoundText" msgid="431331662154342581">"Auf dem Telefon sind keine Bilder verfügbar."</string>
<string name="attachToContact" msgid="8820530304406066714">"Kontaktsymbol"</string>
<string name="customLabelPickerTitle" msgid="1081475101983255212">"Name des benutzerdef. Labels"</string>
- <string name="menu_displayGroup" msgid="4603480747214476335">"Gruppe anzeigen"</string>
+ <string name="menu_displayGroup" msgid="6022511302986343108">"Gruppen anzeigen"</string>
+ <string name="displayGroups" msgid="2645969886032719891">"Gruppen anzeigen"</string>
<string name="syncGroupPreference" msgid="9028361137161162861">"Sync-Gruppen bearb."</string>
<string name="importFromSim" msgid="8383900146531125319">"Kontakte importieren"</string>
<string name="send_to_voicemail_checkbox" msgid="9001686764070676353">"Anrufe direkt an Mailbox senden"</string>
@@ -94,12 +112,23 @@
<string name="select_group_title" msgid="7955698611959835612">"Gruppen"</string>
<string name="groupEmpty" msgid="6661950109828194595">"Ihre Gruppe \"<xliff:g id="GROUPNAME">%s</xliff:g>\" ist leer."</string>
<string name="showAllGroups" msgid="5164410117611094297">"Alle Kontakte"</string>
+ <string name="showFilterPhones" msgid="4184858075465653970">"Nur Kontakte mit Telefonen"</string>
+ <string name="showFilterPhonesDescrip" msgid="6644443248815191067">"Nur Kontakte mit Telefonnummern anzeigen"</string>
+ <string name="headerContactGroups" msgid="1451383377487746789">"Kontaktgruppen"</string>
+ <plurals name="groupDescrip">
+ <item quantity="other" msgid="3507881585720628389">"<xliff:g id="COUNT">%0$d</xliff:g> Kontakte"</item>
+ </plurals>
+ <plurals name="groupDescripPhones">
+ <item quantity="other" msgid="3816047547470490208">"<xliff:g id="COUNT_0">%1$d</xliff:g> Kontakte, <xliff:g id="COUNTWITHPHONES">%2$d</xliff:g> mit Telefon"</item>
+ </plurals>
<string name="syncAllGroups" msgid="7512169081224806890">"Alle Kontakte synchronisieren"</string>
<string name="groupNameMyContacts" msgid="5696566165170977126">"Meine Kontakte"</string>
<string name="groupNameWithPhones" msgid="6981588604041120497">"Kontakte mit Telefonnummern"</string>
<string name="starredInAndroid" msgid="6495527538140213440">"In Android markiert"</string>
<string name="contactCreatedToast" msgid="8740102129968688060">"Kontakt erstellt"</string>
<string name="contactSavedToast" msgid="7152589189385441091">"Kontakt gespeichert"</string>
+ <!-- no translation found for contactSavedErrorToast (9189098776225004666) -->
+ <skip />
<string name="listSeparatorCallNumber" msgid="7115321894439629408">"Rufnummer"</string>
<string name="listSeparatorCallNumber_edit" msgid="2999167270900823334">"Telefonnummern"</string>
<string name="listSeparatorSendSmsMms" msgid="2480944736714739967">"SMS/MMS senden"</string>
@@ -110,9 +139,11 @@
<string name="listSeparatorMapAddress" msgid="7076401301758190804">"Adresse in Maps"</string>
<string name="listSeparatorMapAddress_edit" msgid="298711187672067985">"Postanschrift"</string>
<string name="listSeparatorOrganizations" msgid="7514083358440762504">"Organisationen"</string>
+ <string name="listSeparatorGroups" msgid="1593940073983422450">"Gruppen"</string>
<string name="listSeparatorOtherInformation" msgid="7844959649638482329">"Mehr Informationen"</string>
<string name="listSeparatorOtherInformation_edit" msgid="1326921768011367750">"Sonstige Optionen"</string>
<string name="listSeparatorMore_edit" msgid="858454837482243176">"Mehr"</string>
+ <string name="socialStreamIconLabel" msgid="4367712449555075376">"Sozial"</string>
<string name="contactsIconLabel" msgid="7666609097606552806">"Kontakte"</string>
<string name="contactsFavoritesLabel" msgid="8417039765586853670">"Favoriten"</string>
<string name="dialerIconLabel" msgid="7228520966699542850">"Telefon"</string>
@@ -128,6 +159,7 @@
<string name="recentCalls_deleteAll" msgid="6352364392762163704">"Anrufliste löschen"</string>
<string name="recentCalls_empty" msgid="247053222448663107">"Anrufliste ist leer"</string>
<string name="imei" msgid="3045126336951684285">"IMEI"</string>
+ <string name="meid" msgid="6210568493746275750">"MEID"</string>
<string name="voicemail" msgid="3851469869202611441">"Mailbox"</string>
<string name="unknown" msgid="740067747858270469">"Unbekannt"</string>
<string name="private_num" msgid="6374339738119166953">"Private Nummer"</string>
@@ -138,7 +170,14 @@
<string name="simContacts_title" msgid="27341688347689769">"Kontakte auf SIM-Karte"</string>
<string name="contactsSyncPlug" msgid="7248276704957313698"><font fgcolor="#ffffffff">"Synchronisieren Sie Ihre Google-Kontakte!"</font>" "\n"Nach der Synchronisierung stehen Ihnen Ihre Kontakte überall zur Verfügung."</string>
<string name="noContactsHelpText" msgid="2249195687463896364">"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 wählen Sie Folgendes aus:"\n\n" "<li><font fgcolor="#ffffffff"><b></b>"Neuer Kontakt"</font>\n", um einen neuen Kontakt zu erstellen"</li>\n<li>" "<font fgcolor="#ffffffff"><b></b></font>"Kontakte importieren"\n</li>", um Kontakte von Ihrer SIM-Karte hinzuzufügen"</string>
- <string name="noContactsHelpTextWithSync" msgid="4927701496550314555">"Sie haben keine Kontakte."\n\n"Drücken Sie zum Hinzufügen die "<font fgcolor="#ffffffff"><b>"Menütaste"</b></font>" und wählen Sie"\n\n<li><font fgcolor="#ffffffff"><b>"Synchronisierungsgruppen bearbeiten"</b></font>", um Kontakte mit einem neuen oder bestehenden Google-Konto zu synchronisieren,"\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>"Kontakte importieren"</b></font>", um Kontakte von Ihrer SIM-Karte hinzuzufügen."\n</li></string>
+ <!-- no translation found for noContactsHelpTextWithSync (2179024855526352215) -->
+ <skip />
+ <!-- no translation found for noContactsNoSimHelpText (645951808197910024) -->
+ <skip />
+ <!-- no translation found for noContactsNoSimHelpTextWithSync (511505839941079550) -->
+ <skip />
+ <!-- no translation found for noFavoritesHelpText (1705381563293866510) -->
+ <skip />
<string name="seclectSyncGroups_title" msgid="1235432026231325655">"Zu synchronisierende Gruppen auswählen"</string>
<string name="liveFolder_all_label" msgid="1552523730090319259">"Alle Kontakte"</string>
<string name="liveFolder_favorites_label" msgid="2674341514070517105">"Markiert"</string>
@@ -173,6 +212,7 @@
<string name="favoritesFrquentSeparator" msgid="5007070838253932139">"Häufig angerufen"</string>
<string name="add_contact_dlg_title" msgid="2789046541229846116">"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>
+ <string name="all_tab_label" msgid="4003124364397916826">"Alle"</string>
<string name="description_image_button_one" msgid="1740638037139856139">"eins"</string>
<string name="description_image_button_two" msgid="5882638439003731308">"zwei"</string>
<string name="description_image_button_three" msgid="8709731759376015180">"drei"</string>
@@ -189,19 +229,27 @@
<string name="no_sdcard_message" msgid="7791906118138538259">"Keine SD-Karte gefunden"</string>
<string name="searching_vcard_title" msgid="4158580043290687793">"VCard wird gesucht."</string>
<string name="select_import_type_title" msgid="2443742794103731022">"Aus welcher Quelle möchten Sie Kontakte importieren?"</string>
- <string name="import_from_sim" msgid="3362158539070179154">"SIM-Karte"</string>
- <string name="import_from_sdcard" msgid="8854793070007530689">"SD-Karte"</string>
- <string name="import_all_vcard_string" msgid="2788222288962542415">"Alle VCard-Dateien importieren"</string>
- <string name="import_one_vcard_string" msgid="4618119296910253689">"Eine VCard-Datei importieren"</string>
+ <!-- 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 />
+ <string name="import_one_vcard_string" msgid="9059163467020328433">"Eine VCard-Datei importieren"</string>
+ <string name="import_multiple_vcard_string" msgid="3810226492811062392">"Mehrere VCard-Dateien importieren"</string>
+ <string name="import_all_vcard_string" msgid="5518136113853448474">"Alle VCard-Dateien importieren"</string>
<string name="searching_vcard_message" msgid="2467573749792455605">"VCard-Daten werden auf VCard gesucht."</string>
<string name="scanning_sdcard_failed_title" msgid="2788276113399585924">"Fehler beim Lesen der SD-Karte"</string>
<string name="scanning_sdcard_failed_message" msgid="925361244069058117">"Fehler beim Lesen der SD-Karte"</string>
- <string name="fail_reason_io_error" msgid="5922864781066136340">"E/A-Fehler"</string>
- <string name="fail_reason_vcard_parse_error" msgid="2129476089250836277">"Fehler beim Parsen der VCard"</string>
+ <string name="fail_reason_io_error" msgid="5949038573678667996">"E/A-Fehler (Grund: \"<xliff:g id="FAIL_REASON">%s</xliff:g>\")"</string>
+ <string name="fail_reason_vcard_parse_error" msgid="8877496734269540489">"Fehler beim Parsen der VCard aus einem unerwarteten Grund"</string>
+ <string name="fail_reason_vcard_not_supported_error" msgid="166568606246563552">"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="1489638537002538332">"Keine VCard-Datei auf SD-Karte gefunden"</string>
<string name="fail_reason_no_vcard_entry" msgid="3808734131680935043">"Es wurde kein gültiger VCard-Eintrag für Ihre Auswahl gefunden."</string>
+ <string name="fail_reason_failed_to_read_files" msgid="3659521123567134029">"Fehler beim Import einer oder mehrerer Dateien (%s)"</string>
<string name="select_vcard_title" msgid="4615933643517710543">"VCard-Datei auswählen"</string>
<string name="select_vcard_message" msgid="7028772212789057858">"Wählen Sie eine VCard-Datei für den Import aus."</string>
+ <string name="progress_shower_message" msgid="5636525578293752526">"<xliff:g id="ACTION">%s</xliff:g>"\n"<xliff:g id="FILENAME">%s</xliff:g>"</string>
<string name="reading_vcard_title" msgid="430154704397375160">"VCard wird gelesen."</string>
<string name="reading_vcard_message" msgid="7470486051482460267">"VCard-Dateien werden gelesen."</string>
<string name="importing_vcard_message" msgid="2466665710812588738">"VCard-Daten werden importiert."</string>
@@ -224,4 +272,104 @@
<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 Ihrer Kontakte"</string>
+ <string name="add_2sec_pause" msgid="1488865103809190124">"2 Sekunden Pause hinzufügen"</string>
+ <string name="add_wait" msgid="8347184715316058465">"Warten hinzufügen"</string>
+ <string name="dial_button_label" msgid="7637725632722605863">"Wählen"</string>
+ <!-- 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 />
+ <string name="quickcontact_missing_app" msgid="1181287640680679441">"Für diese Aktion wurde keine Anwendung gefunden."</string>
+ <string name="quickcontact_remember_choice" msgid="2858356075887897116">"Diese Auswahl speichern"</string>
+ <string name="quickcontact_missing_name" msgid="7806083820928955853">"Unbekannt"</string>
+ <string name="menu_accounts" msgid="8499114602017077970">"Konten"</string>
+ <!-- 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">"Name"</string>
+ <string name="nicknameLabelsGroup" msgid="2891682101053358010">"Alias"</string>
+ <string name="organizationLabelsGroup" msgid="2478611760751832035">"Firma/Organisation"</string>
+ <string name="websiteLabelsGroup" msgid="4202998982804009261">"Website"</string>
+ <string name="type_short_home" msgid="7770424864090605384">"P"</string>
+ <string name="type_short_mobile" msgid="1655473281466676216">"M"</string>
+ <string name="type_short_work" msgid="4925330752504537861">"A"</string>
+ <string name="type_short_pager" msgid="2613818970827594238">"P"</string>
+ <string name="type_short_other" msgid="5669407180177236769">"S"</string>
+ <string name="edit_secondary_collapse" msgid="1855127176548440583">"Sekundäre Details"</string>
+ <string name="dialog_primary_name" msgid="5521591005692614833">"Hauptname"</string>
+ <string name="dialog_new_contact_account" msgid="9044704073286262197">"Neuen Kontakt unter Konto erstellen"</string>
+ <string name="menu_sync_remove" msgid="3266725887008450161">"Synchronisierungsgruppe entfernen"</string>
+ <!-- no translation found for dialog_sync_add (8267045393119375803) -->
+ <skip />
+ <!-- no translation found for display_more_groups (2682547080423434170) -->
+ <skip />
+ <string name="display_ungrouped" msgid="4357317661311803984">"(Kontakte ohne Gruppierung)"</string>
+ <string name="display_warn_remove_ungrouped" msgid="2314043155909167610">"Wenn \"<xliff:g id="GROUP">%s</xliff:g>\" aus der Synchronisierung entfernt wird, werden auch alle nicht gruppierten Kontakte aus der Synchronisierung entfernt."</string>
+ <string name="account_phone" msgid="4025734638492419713">"Nur Telefon (nicht synchronisiert)"</string>
+ <string name="label_email_display_name" msgid="5537802602754309600">"Anzeigename"</string>
+ <string name="call_home" msgid="1990519474420545392">"Anruf (privat)"</string>
+ <string name="call_mobile" msgid="7502236805487609178">"Anruf (mobil)"</string>
+ <string name="call_work" msgid="5328785911463744028">"Anruf (Arbeit)"</string>
+ <string name="call_fax_work" msgid="7467763592359059243">"Anruf (Fax, Arbeit)"</string>
+ <string name="call_fax_home" msgid="8342175628887571876">"Faxanruf (privat)"</string>
+ <string name="call_pager" msgid="9003902812293983281">"Anruf (Pager)"</string>
+ <string name="call_custom" msgid="7756571794763171802">"<xliff:g id="CUSTOM">%s</xliff:g> anrufen"</string>
+ <string name="call_car" msgid="3280537320306436445">"Anruf (Auto)"</string>
+ <string name="call_company_main" msgid="6105120947138711257">"Anruf (Firmenhauptnummer)"</string>
+ <string name="call_mms" msgid="6274041545876221437">"Anruf (MMS)"</string>
+ <string name="call_radio" msgid="8296755876398357063">"Anruf (Mobilfunk)"</string>
+ <string name="sms_home" msgid="7524332261493162995">"Text (privat)"</string>
+ <string name="sms_mobile" msgid="5200107250451030769">"Text (mobil)"</string>
+ <string name="sms_work" msgid="2269624156655267740">"Text (Arbeit)"</string>
+ <string name="sms_fax_work" msgid="8028189067816907075">"Text (Fax, Arbeit)"</string>
+ <string name="sms_fax_home" msgid="9204042076306809634">"Text (Fax, privat)"</string>
+ <string name="sms_pager" msgid="7730404569637015192">"Text-Pager"</string>
+ <string name="sms_other" msgid="5131921487474531617">"Text (sonstige)"</string>
+ <string name="sms_custom" msgid="5932736853732191825">"Text <xliff:g id="CUSTOM">%s</xliff:g>"</string>
+ <string name="sms_car" msgid="7444227058437359641">"Text (Auto)"</string>
+ <string name="sms_company_main" msgid="118970873419678087">"Text (Firmenhauptnummer)"</string>
+ <string name="sms_mms" msgid="4069352461380762677">"Text (MMS)"</string>
+ <string name="sms_radio" msgid="3329166673433967820">"Text (Mobilfunk)"</string>
+ <string name="email_home" msgid="8573740658148184279">"E-Mail-Startseite"</string>
+ <string name="email_mobile" msgid="2042889209787989814">"E-Mail (mobil)"</string>
+ <string name="email_work" msgid="2807430017302722689">"E-Mail (Arbeit)"</string>
+ <string name="email_other" msgid="8093933498541795832">"E-Mail (sonstige)"</string>
+ <string name="email_custom" msgid="7548003991586214105">"E-Mail <xliff:g id="CUSTOM">%s</xliff:g>"</string>
+ <string name="email" msgid="5668400997660065897">"E-Mail"</string>
+ <string name="map_home" msgid="1243547733423343982">"Privatadresse anzeigen"</string>
+ <string name="map_work" msgid="1360474076921878088">"Arbeitsadresse anzeigen"</string>
+ <string name="map_other" msgid="5560707927535653892">"Sonstige Adressen anzeigen"</string>
+ <string name="map_custom" msgid="6184363799976265281">"<xliff:g id="CUSTOM">%s</xliff:g>-Adresse anzeigen"</string>
+ <string name="chat_aim" msgid="2588492205291249142">"Chat über AIM"</string>
+ <string name="chat_msn" msgid="8041633440091073484">"Chat über Windows Live"</string>
+ <string name="chat_yahoo" msgid="6629211142719943666">"Chat über Yahoo"</string>
+ <string name="chat_skype" msgid="1210045020427480566">"Chat über Skype"</string>
+ <string name="chat_qq" msgid="4294637812847719693">"Chat über QQ"</string>
+ <string name="chat_gtalk" msgid="981575737258117697">"Chat über Google Talk"</string>
+ <string name="chat_icq" msgid="8438405386153745775">"Chat über ICQ"</string>
+ <string name="chat_jabber" msgid="7561444230307829609">"Chat über Jabber"</string>
+ <string name="postal_street" msgid="8133143961580058972">"Straße"</string>
+ <string name="postal_pobox" msgid="4431938829180269821">"Postfach"</string>
+ <string name="postal_neighborhood" msgid="1450783874558956739">"Nachbarschaft"</string>
+ <string name="postal_city" msgid="6597491300084895548">"Stadt"</string>
+ <string name="postal_region" msgid="6045263193478437672">"Staat"</string>
+ <string name="postal_postcode" msgid="572136414136673751">"Postleitzahl"</string>
+ <string name="postal_country" msgid="7638264508416368690">"Land"</string>
+ <string name="name_given" msgid="1687286314106019813">"Vorname"</string>
+ <string name="name_family" msgid="3416695586119999058">"Nachname"</string>
+ <string name="name_prefix" msgid="59756378548779822">"Namenpräfix"</string>
+ <string name="name_middle" msgid="8467433655992690326">"Zweiter Vorname"</string>
+ <string name="name_suffix" msgid="3855278445375651441">"Namensuffix"</string>
+ <string name="name_phonetic_given" msgid="6853570431394449191">"Phonetischer Vorname"</string>
+ <string name="name_phonetic_middle" msgid="8643721493320405200">"Phonetischer zweiter Vorname"</string>
+ <string name="name_phonetic_family" msgid="462095502140180305">"Phonetischer Nachname"</string>
</resources>
diff --git a/res/values-el/config.xml b/res/values-el/config.xml
deleted file mode 100644
index 07b3c7e..0000000
--- a/res/values-el/config.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for config_export_vcard_type (9145748078116159716) -->
- <skip />
- <string name="config_export_dir" msgid="222182743639478636">"/sdcard"</string>
- <!-- no translation found for config_export_file_prefix (3022868431158658122) -->
- <skip />
- <!-- no translation found for config_export_file_suffix (16505844221142195) -->
- <skip />
- <string name="config_export_file_extension" msgid="1758878818611339161">"vcf"</string>
- <!-- no translation found for config_export_extensions_to_consider (5095044502091950623) -->
- <skip />
-</resources>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index a902563..71bb3bd 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -40,11 +40,24 @@
<string name="menu_showBarcode" msgid="309973637178814132">"Εμφάνιση γραμμωτού κώδικα"</string>
<string name="menu_editContact" msgid="3452858480713561396">"Επεξεργασία επαφής"</string>
<string name="menu_deleteContact" msgid="1916555454274101750">"Διαγραφή επαφής"</string>
- <string name="menu_call" msgid="7359207953236681606">"Κλήση"</string>
- <string name="menu_sendSMS" msgid="5917289601666766617">"Αποστολή SMS/MMS"</string>
+ <!-- no translation found for menu_call (3992595586042260618) -->
+ <skip />
+ <!-- no translation found for menu_sendSMS (5535886767547006515) -->
+ <skip />
<string name="menu_sendEmail" msgid="7293508859242926187">"Αποστολή μηνύματος ηλεκτρονικού ταχυδρομείου"</string>
<string name="menu_viewAddress" msgid="1814744325763202024">"Διεύθυνση στον χάρτη"</string>
<string name="menu_makeDefaultNumber" msgid="4838759253316649534">"Ορισμός προεπιλεγμένου αριθμού"</string>
+ <string name="menu_makeDefaultEmail" msgid="2599044610375789994">"Ορισμός διεύθυνσης ηλεκτρονικού ταχυδρομείου ως προεπιλογή"</string>
+ <string name="menu_splitAggregate" msgid="7402548456225901170">"Διαχωρισμός"</string>
+ <string name="splitAggregate_title" msgid="2053462872948058798">"Διαχωρισμός επαφής"</string>
+ <string name="contactsSplitMessage" msgid="3439008547775199243">"Διαχωρισμός επαφής"</string>
+ <string name="menu_joinAggregate" msgid="5027981918265667970">"Σύνδεση"</string>
+ <string name="titleJoinAggregate" msgid="6970566008563147202">"Σύνδεση επαφής"</string>
+ <string name="separatorJoinAggregateSuggestions" msgid="5494823322479834017">"Προτάσεις"</string>
+ <string name="separatorJoinAggregateAll" msgid="7939932265026181043">"Όλες οι επαφές"</string>
+ <string name="contactsJoinedMessage" msgid="7208148163607047389">"Οι επαφές συνδέθηκαν"</string>
+ <string name="menu_contactOptions" msgid="1957061455705020617">"Επιλογές"</string>
+ <string name="contactOptionsTitle" msgid="8259347644090700915">"Επιλογές"</string>
<string name="deleteConfirmation_title" msgid="6394309508930335204">"Διαγραφή"</string>
<string name="deleteConfirmation" msgid="811706994761610640">"Αυτή η επαφή θα διαγραφεί."</string>
<string name="menu_done" msgid="796017761764190697">"Τέλος"</string>
@@ -55,6 +68,9 @@
<string name="label_phonetic_name" msgid="2288082649573927286">"Φωνητική"</string>
<string name="label_notes" msgid="8337354953278341042">"Σημειώσεις"</string>
<string name="label_ringtone" msgid="8833166825330686244">"Ήχος κλήσης"</string>
+ <string name="label_groups" msgid="7304551384542859026">"Ομάδες"</string>
+ <string name="group_list" msgid="8583361685440161307">", <xliff:g id="GROUPNAME">%s</xliff:g>"</string>
+ <string name="menu_viewGroup" msgid="1401851715586577551">"Επεξεργασία ομάδων"</string>
<string name="ghostData_name" msgid="6490954238641157585">"Όνομα και επίθετο"</string>
<string name="ghostData_phonetic_name" msgid="7852749081984070902">"Φωνητικό όνομα"</string>
<string name="ghostData_company" msgid="5414421120553765775">"Εταιρεία"</string>
@@ -64,6 +80,7 @@
<string name="ghostData_phone" msgid="6963153888271466620">"Αριθμός τηλεφώνου"</string>
<string name="ghostData_email" msgid="6184537075551565919">"Διεύθυνση ηλεκτρονικού ταχυδρομείου"</string>
<string name="ghostData_postal" msgid="652611650594951897">"Ταχυδρομική διεύθυνση"</string>
+ <string name="ghostData_group" msgid="4504591380347534114">"Προβολή ομάδας"</string>
<string name="invalidContactMessage" msgid="5816991830260044593">"Η επαφή δεν υπάρχει."</string>
<string name="pickerNewContactHeader" msgid="7750705279843568147">"Δημιουργία νέας επαφής"</string>
<string name="selectLabel" msgid="4255424123394910733">"Επιλογή ετικέτας"</string>
@@ -80,7 +97,8 @@
<string name="photoPickerNotFoundText" msgid="431331662154342581">"Δεν υπάρχουν διαθέσιμες εικόνες στο τηλέφωνο."</string>
<string name="attachToContact" msgid="8820530304406066714">"Εικονίδιο επαφής"</string>
<string name="customLabelPickerTitle" msgid="1081475101983255212">"Προσαρμοσμένο όνομα ετικέτας"</string>
- <string name="menu_displayGroup" msgid="4603480747214476335">"Προβολή ομάδας"</string>
+ <string name="menu_displayGroup" msgid="6022511302986343108">"Προβολή ομάδων"</string>
+ <string name="displayGroups" msgid="2645969886032719891">"Προβολή ομάδων"</string>
<string name="syncGroupPreference" msgid="9028361137161162861">"Επεξ. ομάδ. συγχρ."</string>
<string name="importFromSim" msgid="8383900146531125319">"Εισαγωγή επαφών"</string>
<string name="send_to_voicemail_checkbox" msgid="9001686764070676353">"Αποστολή κλήσεων απευθείας στον αυτόματο τηλεφωνητή"</string>
@@ -94,12 +112,23 @@
<string name="select_group_title" msgid="7955698611959835612">"Ομάδες"</string>
<string name="groupEmpty" msgid="6661950109828194595">"Η ομάδα σας \"<xliff:g id="GROUPNAME">%s</xliff:g>\" είναι κενή."</string>
<string name="showAllGroups" msgid="5164410117611094297">"Όλες οι επαφές"</string>
+ <string name="showFilterPhones" msgid="4184858075465653970">"Μόνο επαφές με τηλέφωνα"</string>
+ <string name="showFilterPhonesDescrip" msgid="6644443248815191067">"Να προβάλλονται μόνο οι επαφές που διαθέτουν αριθμό τηλεφώνου"</string>
+ <string name="headerContactGroups" msgid="1451383377487746789">"Ομάδες επαφών"</string>
+ <plurals name="groupDescrip">
+ <item quantity="other" msgid="3507881585720628389">"<xliff:g id="COUNT">%0$d</xliff:g> επαφές"</item>
+ </plurals>
+ <plurals name="groupDescripPhones">
+ <item quantity="other" msgid="3816047547470490208">"<xliff:g id="COUNT_0">%1$d</xliff:g> επαφές, <xliff:g id="COUNTWITHPHONES">%2$d</xliff:g> με τηλέφωνα"</item>
+ </plurals>
<string name="syncAllGroups" msgid="7512169081224806890">"Συγχρονισμός όλων των επαφών"</string>
<string name="groupNameMyContacts" msgid="5696566165170977126">"Οι επαφές μου"</string>
<string name="groupNameWithPhones" msgid="6981588604041120497">"Επαφές με αριθμούς τηλεφώνου"</string>
<string name="starredInAndroid" msgid="6495527538140213440">"Με αστέρι στο Android"</string>
<string name="contactCreatedToast" msgid="8740102129968688060">"Η επαφή δημιουργήθηκε."</string>
<string name="contactSavedToast" msgid="7152589189385441091">"Η επαφή αποθηκεύτηκε."</string>
+ <!-- no translation found for contactSavedErrorToast (9189098776225004666) -->
+ <skip />
<string name="listSeparatorCallNumber" msgid="7115321894439629408">"Αριθμός κλήσης"</string>
<string name="listSeparatorCallNumber_edit" msgid="2999167270900823334">"Αριθμοί τηλεφώνου"</string>
<string name="listSeparatorSendSmsMms" msgid="2480944736714739967">"Αποστολή SMS/MMS"</string>
@@ -110,9 +139,11 @@
<string name="listSeparatorMapAddress" msgid="7076401301758190804">"Διεύθυνση στον χάρτη"</string>
<string name="listSeparatorMapAddress_edit" msgid="298711187672067985">"Διευθύνσεις ταχυδρομείου"</string>
<string name="listSeparatorOrganizations" msgid="7514083358440762504">"Εταιρείες"</string>
+ <string name="listSeparatorGroups" msgid="1593940073983422450">"Ομάδες"</string>
<string name="listSeparatorOtherInformation" msgid="7844959649638482329">"Άλλες πληροφορίες"</string>
<string name="listSeparatorOtherInformation_edit" msgid="1326921768011367750">"Άλλες επιλογές"</string>
<string name="listSeparatorMore_edit" msgid="858454837482243176">"Περισσότερα"</string>
+ <string name="socialStreamIconLabel" msgid="4367712449555075376">"Κοινωνικά"</string>
<string name="contactsIconLabel" msgid="7666609097606552806">"Επαφές"</string>
<string name="contactsFavoritesLabel" msgid="8417039765586853670">"Αγαπ."</string>
<string name="dialerIconLabel" msgid="7228520966699542850">"Πρόγρ. κλήσ."</string>
@@ -128,6 +159,7 @@
<string name="recentCalls_deleteAll" msgid="6352364392762163704">"Εκκαθάριση αρχείου καταγραφής κλήσεων"</string>
<string name="recentCalls_empty" msgid="247053222448663107">"Το αρχείο καταγραφής κλήσεων είναι κενό."</string>
<string name="imei" msgid="3045126336951684285">"Αριθμός ΙΜΕΙ"</string>
+ <string name="meid" msgid="6210568493746275750">"MEID"</string>
<string name="voicemail" msgid="3851469869202611441">"Αυτόματος τηλεφωνητής"</string>
<string name="unknown" msgid="740067747858270469">"Άγνωστος"</string>
<string name="private_num" msgid="6374339738119166953">"Απόκρυψη"</string>
@@ -138,7 +170,14 @@
<string name="simContacts_title" msgid="27341688347689769">"Επαφές στην κάρτα SIM"</string>
<string name="contactsSyncPlug" msgid="7248276704957313698"><font fgcolor="#ffffffff">"Συγχρονίστε τις επαφές σας Google!"</font>" "\n"Μετά το συγχρονισμό στο τηλέφωνό σας, οι επαφές σας θα είναι διαθέσιμες όπου κι αν πάτε.."</string>
<string name="noContactsHelpText" msgid="2249195687463896364">"Δεν έχετε επαφές."\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>" για να προσθέσετε επαφές από την κάρτα SIM"\n</li></string>
- <string name="noContactsHelpTextWithSync" msgid="4927701496550314555">"Δεν έχετε επαφές."\n\n"Για να προσθέσετε επαφές, πατήστε "<font fgcolor="#ffffffff"><b>"Μενού"</b></font>" και επιλέξτε:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Επεξεργασία ομάδων συγχρονισμού"</b></font>" για να προσθέσετε από ένα νέο ή υπάρχοντα λογαριασμό Google"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Νέα επαφή"</b></font>" για να δημιουργήσετε μια νέα επαφή από την αρχή"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Εισαγωγή επαφών"</b></font>" για να προσθέσετε επαφές από την κάρτα SIM"\n</li></string>
+ <!-- no translation found for noContactsHelpTextWithSync (2179024855526352215) -->
+ <skip />
+ <!-- no translation found for noContactsNoSimHelpText (645951808197910024) -->
+ <skip />
+ <!-- no translation found for noContactsNoSimHelpTextWithSync (511505839941079550) -->
+ <skip />
+ <!-- no translation found for noFavoritesHelpText (1705381563293866510) -->
+ <skip />
<string name="seclectSyncGroups_title" msgid="1235432026231325655">"Επιλογή ομάδων για συγχρονισμό"</string>
<string name="liveFolder_all_label" msgid="1552523730090319259">"Όλες οι επαφές"</string>
<string name="liveFolder_favorites_label" msgid="2674341514070517105">"Με αστέρι"</string>
@@ -173,6 +212,7 @@
<string name="favoritesFrquentSeparator" msgid="5007070838253932139">"Συχνές κλήσεις"</string>
<string name="add_contact_dlg_title" msgid="2789046541229846116">"Προσθήκη επαφής"</string>
<string name="add_contact_dlg_message_fmt" msgid="7986472669444326576">"Προσθήκη του \"<xliff:g id="EMAIL">%s</xliff:g>\" στις επαφές;"</string>
+ <string name="all_tab_label" msgid="4003124364397916826">"Όλα"</string>
<string name="description_image_button_one" msgid="1740638037139856139">"ένα"</string>
<string name="description_image_button_two" msgid="5882638439003731308">"δύο"</string>
<string name="description_image_button_three" msgid="8709731759376015180">"τρία"</string>
@@ -189,19 +229,27 @@
<string name="no_sdcard_message" msgid="7791906118138538259">"Δεν ανιχνεύθηκε κάρτα SD"</string>
<string name="searching_vcard_title" msgid="4158580043290687793">"Αναζήτηση VCard"</string>
<string name="select_import_type_title" msgid="2443742794103731022">"Από πού θέλετε να εισάγετε επαφές;"</string>
- <string name="import_from_sim" msgid="3362158539070179154">"Κάρτα SIM"</string>
- <string name="import_from_sdcard" msgid="8854793070007530689">"Κάρτα SD"</string>
- <string name="import_all_vcard_string" msgid="2788222288962542415">"Εισαγωγή όλων των αρχείων VCard"</string>
- <string name="import_one_vcard_string" msgid="4618119296910253689">"Εισαγωγή ενός αρχείου VCard"</string>
+ <!-- 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 />
+ <string name="import_one_vcard_string" msgid="9059163467020328433">"Εισαγωγή ενός αρχείου VCard"</string>
+ <string name="import_multiple_vcard_string" msgid="3810226492811062392">"Εισαγωγή πολλαπλών αρχείων vCard"</string>
+ <string name="import_all_vcard_string" msgid="5518136113853448474">"Εισαγωγή όλων των αρχείων vCard"</string>
<string name="searching_vcard_message" msgid="2467573749792455605">"Αναζήτηση δεδομένων VCard στην κάρτα SD"</string>
<string name="scanning_sdcard_failed_title" msgid="2788276113399585924">"Η σάρωση της κάρτας SD απέτυχε"</string>
<string name="scanning_sdcard_failed_message" msgid="925361244069058117">"Η σάρωση της κάρτας SD απέτυχε"</string>
- <string name="fail_reason_io_error" msgid="5922864781066136340">"Σφάλμα I/O"</string>
- <string name="fail_reason_vcard_parse_error" msgid="2129476089250836277">"Η ανάλυση του VCard απέτυχε"</string>
+ <string name="fail_reason_io_error" msgid="5949038573678667996">"Σφάλμα I/O (Αιτία: \"<xliff:g id="FAIL_REASON">%s</xliff:g>\")"</string>
+ <string name="fail_reason_vcard_parse_error" msgid="8877496734269540489">"Απέτυχε η ανάλυση του VCard λόγω απροσδόκητης αιτίας"</string>
+ <string name="fail_reason_vcard_not_supported_error" msgid="166568606246563552">"Αν και το VCard φαίνεται να βρίσκεται σε έγκυρη μορφή, ωστόσο, η ανάλυσή του απέτυχε εφόσον η τρέχουσα εφαρμογή δεν το υποστηρίζει"</string>
<string name="fail_reason_no_vcard_file" msgid="1489638537002538332">"Δεν βρέθηκε αρχείο VCard στην κάρτα SD"</string>
<string name="fail_reason_no_vcard_entry" msgid="3808734131680935043">"Δεν βρέθηκε έγκυρη καταχώρηση VCard για την επιλογή σας"</string>
+ <string name="fail_reason_failed_to_read_files" msgid="3659521123567134029">"Η εισαγωγή ενός ή περισσοτέρων αρχείων απέτυχε (%s)."</string>
<string name="select_vcard_title" msgid="4615933643517710543">"Επιλογή αρχείου VCard"</string>
<string name="select_vcard_message" msgid="7028772212789057858">"Επιλέξτε ένα αρχείο VCard για εισαγωγή"</string>
+ <string name="progress_shower_message" msgid="5636525578293752526">"<xliff:g id="ACTION">%s</xliff:g>"\n"<xliff:g id="FILENAME">%s</xliff:g>"</string>
<string name="reading_vcard_title" msgid="430154704397375160">"Ανάγνωση VCard"</string>
<string name="reading_vcard_message" msgid="7470486051482460267">"Ανάγνωση αρχείου (ων) VCard"</string>
<string name="importing_vcard_message" msgid="2466665710812588738">"Εισαγωγή δεδομένων VCard"</string>
@@ -224,4 +272,104 @@
<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="1488865103809190124">"Προσθήκη παύσης 2 δευτερολέπτων"</string>
+ <string name="add_wait" msgid="8347184715316058465">"Προσθήκη αναμονής"</string>
+ <string name="dial_button_label" msgid="7637725632722605863">"Κλήση"</string>
+ <!-- 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 />
+ <string name="quickcontact_missing_app" msgid="1181287640680679441">"Δεν υπάρχει εφαρμογή για τη διαχείριση αυτής της ενέργειας"</string>
+ <string name="quickcontact_remember_choice" msgid="2858356075887897116">"Διατήρηση αυτής της ρύθμισης"</string>
+ <string name="quickcontact_missing_name" msgid="7806083820928955853">"Άγνωστος"</string>
+ <string name="menu_accounts" msgid="8499114602017077970">"Λογαριασμοί"</string>
+ <!-- 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">"Όνομα"</string>
+ <string name="nicknameLabelsGroup" msgid="2891682101053358010">"Ψευδώνυμο"</string>
+ <string name="organizationLabelsGroup" msgid="2478611760751832035">"Οργανισμός"</string>
+ <string name="websiteLabelsGroup" msgid="4202998982804009261">"Ιστότοπος"</string>
+ <string name="type_short_home" msgid="7770424864090605384">"Ο"</string>
+ <string name="type_short_mobile" msgid="1655473281466676216">"Κ"</string>
+ <string name="type_short_work" msgid="4925330752504537861">"Ε"</string>
+ <string name="type_short_pager" msgid="2613818970827594238">"Β"</string>
+ <string name="type_short_other" msgid="5669407180177236769">"Α"</string>
+ <string name="edit_secondary_collapse" msgid="1855127176548440583">"Δευτερεύουσες λεπτομέρειες"</string>
+ <string name="dialog_primary_name" msgid="5521591005692614833">"Κύριο όνομα"</string>
+ <string name="dialog_new_contact_account" msgid="9044704073286262197">"Δημιουργία επαφής\nστον λογαριασμό"</string>
+ <string name="menu_sync_remove" msgid="3266725887008450161">"Κατάργηση ομάδας συγχρονισμού"</string>
+ <!-- no translation found for dialog_sync_add (8267045393119375803) -->
+ <skip />
+ <!-- no translation found for display_more_groups (2682547080423434170) -->
+ <skip />
+ <string name="display_ungrouped" msgid="4357317661311803984">"(Επαφές χωρίς ομαδοποίηση)"</string>
+ <string name="display_warn_remove_ungrouped" msgid="2314043155909167610">"Η κατάργηση της ομάδας \"<xliff:g id="GROUP">%s</xliff:g>\" από τον συγχρονισμό θα καταργήσει επίσης και τις επαφές χωρίς ομαδοποίηση από τον συγχρονισμό."</string>
+ <string name="account_phone" msgid="4025734638492419713">"Μόνο στο τηλέφωνο (Χωρίς συγχρονισμό)"</string>
+ <string name="label_email_display_name" msgid="5537802602754309600">"Προβολή ονόματος"</string>
+ <string name="call_home" msgid="1990519474420545392">"Κλήση οικίας"</string>
+ <string name="call_mobile" msgid="7502236805487609178">"Κλήση κινητής συσκευής"</string>
+ <string name="call_work" msgid="5328785911463744028">"Κλήση εργασίας"</string>
+ <string name="call_fax_work" msgid="7467763592359059243">"Κλήση φαξ εργασίας"</string>
+ <string name="call_fax_home" msgid="8342175628887571876">"Κλήση φαξ οικίας"</string>
+ <string name="call_pager" msgid="9003902812293983281">"Κλήση βομβητή"</string>
+ <string name="call_custom" msgid="7756571794763171802">"Κλήση <xliff:g id="CUSTOM">%s</xliff:g>"</string>
+ <string name="call_car" msgid="3280537320306436445">"Κλήση τηλεφώνου αυτοκινήτου"</string>
+ <string name="call_company_main" msgid="6105120947138711257">"Κλήση κύριας εταιρικής γραμμής"</string>
+ <string name="call_mms" msgid="6274041545876221437">"Κλήση MMS"</string>
+ <string name="call_radio" msgid="8296755876398357063">"Κλήση πομπού"</string>
+ <string name="sms_home" msgid="7524332261493162995">"Αποστολή μηνύματος κειμένου προς οικία"</string>
+ <string name="sms_mobile" msgid="5200107250451030769">"Αποστολή μηνύματος κειμένου προς κινητή συσκευή"</string>
+ <string name="sms_work" msgid="2269624156655267740">"Αποστολή μηνύματος κειμένου προς εργασία"</string>
+ <string name="sms_fax_work" msgid="8028189067816907075">"Αποστολή μηνύματος κειμένου προς φαξ εργασίας"</string>
+ <string name="sms_fax_home" msgid="9204042076306809634">"Αποστολή μηνύματος κειμένου προς φαξ οικίας"</string>
+ <string name="sms_pager" msgid="7730404569637015192">"Αποστολή μηνύματος κειμένου προς βομβητή"</string>
+ <string name="sms_other" msgid="5131921487474531617">"Αποστολή μηνύματος κειμένου προς άλλο"</string>
+ <string name="sms_custom" msgid="5932736853732191825">"Αποστολή μηνύματος κειμένου προς <xliff:g id="CUSTOM">%s</xliff:g>"</string>
+ <string name="sms_car" msgid="7444227058437359641">"Αποστολή μηνύματος κειμένου προς τηλέφωνο αυτοκινήτου"</string>
+ <string name="sms_company_main" msgid="118970873419678087">"Αποστολή μηνύματος κειμένου προς κύρια εταιρική γραμμή"</string>
+ <string name="sms_mms" msgid="4069352461380762677">"Αποστολή μηνύματος κειμένου ως MMS"</string>
+ <string name="sms_radio" msgid="3329166673433967820">"Αποστολή μηνύματος κειμένου προς πομπό"</string>
+ <string name="email_home" msgid="8573740658148184279">"Αποστολή μηνύματος ηλεκτρονικού ταχυδρομείου προς οικία"</string>
+ <string name="email_mobile" msgid="2042889209787989814">"Αποστολή μηνύματος ηλεκτρονικού ταχυδρομείου προς κινητή συσκευή"</string>
+ <string name="email_work" msgid="2807430017302722689">"Αποστολή μηνύματος ηλεκτρονικού ταχυδρομείου προς εργασία"</string>
+ <string name="email_other" msgid="8093933498541795832">"Αποστολή μηνύματος ηλεκτρονικού ταχυδρομείου προς άλλο"</string>
+ <string name="email_custom" msgid="7548003991586214105">"Αποστολή μηνύματος ηλεκτρονικού ταχυδρομείου προς <xliff:g id="CUSTOM">%s</xliff:g>"</string>
+ <string name="email" msgid="5668400997660065897">"Διεύθυνση ηλεκτρονικού ταχυδρομείου"</string>
+ <string name="map_home" msgid="1243547733423343982">"Προβολή διεύθυνσης οικίας"</string>
+ <string name="map_work" msgid="1360474076921878088">"Προβολή διεύθυνσης εργασίας"</string>
+ <string name="map_other" msgid="5560707927535653892">"Προβολή άλλων διευθύνσεων"</string>
+ <string name="map_custom" msgid="6184363799976265281">"Προβολή διεύθυνσης <xliff:g id="CUSTOM">%s</xliff:g>"</string>
+ <string name="chat_aim" msgid="2588492205291249142">"Συζήτηση μέσω AIM"</string>
+ <string name="chat_msn" msgid="8041633440091073484">"Συζήτηση μέσω Windows Live"</string>
+ <string name="chat_yahoo" msgid="6629211142719943666">"Συζήτηση μέσω Yahoo"</string>
+ <string name="chat_skype" msgid="1210045020427480566">"Συζήτηση μέσω Skype"</string>
+ <string name="chat_qq" msgid="4294637812847719693">"Συζήτηση μέσω QQ"</string>
+ <string name="chat_gtalk" msgid="981575737258117697">"Συζήτηση μέσω Google Talk"</string>
+ <string name="chat_icq" msgid="8438405386153745775">"Συζήτηση μέσω ICQ"</string>
+ <string name="chat_jabber" msgid="7561444230307829609">"Συζήτηση μέσω Jabber"</string>
+ <string name="postal_street" msgid="8133143961580058972">"Οδός"</string>
+ <string name="postal_pobox" msgid="4431938829180269821">"Ταχυδρομική θυρίδα"</string>
+ <string name="postal_neighborhood" msgid="1450783874558956739">"Γειτονιά"</string>
+ <string name="postal_city" msgid="6597491300084895548">"Πόλη"</string>
+ <string name="postal_region" msgid="6045263193478437672">"Πολιτεία"</string>
+ <string name="postal_postcode" msgid="572136414136673751">"Ταχυδρομικός κώδικας"</string>
+ <string name="postal_country" msgid="7638264508416368690">"Χώρα"</string>
+ <string name="name_given" msgid="1687286314106019813">"Όνομα"</string>
+ <string name="name_family" msgid="3416695586119999058">"Επίθετο"</string>
+ <string name="name_prefix" msgid="59756378548779822">"Πρόθεμα ονόματος"</string>
+ <string name="name_middle" msgid="8467433655992690326">"Πατρώνυμο"</string>
+ <string name="name_suffix" msgid="3855278445375651441">"Επίθημα ονόματος"</string>
+ <string name="name_phonetic_given" msgid="6853570431394449191">"Φωνητική μορφή ονόματος"</string>
+ <string name="name_phonetic_middle" msgid="8643721493320405200">"Φωνητική γραφή ονόματος πατρός"</string>
+ <string name="name_phonetic_family" msgid="462095502140180305">"Φωνητική γραφή επιθέτου"</string>
</resources>
diff --git a/res/values-es-rUS/config.xml b/res/values-es-rUS/config.xml
deleted file mode 100644
index 07b3c7e..0000000
--- a/res/values-es-rUS/config.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for config_export_vcard_type (9145748078116159716) -->
- <skip />
- <string name="config_export_dir" msgid="222182743639478636">"/sdcard"</string>
- <!-- no translation found for config_export_file_prefix (3022868431158658122) -->
- <skip />
- <!-- no translation found for config_export_file_suffix (16505844221142195) -->
- <skip />
- <string name="config_export_file_extension" msgid="1758878818611339161">"vcf"</string>
- <!-- no translation found for config_export_extensions_to_consider (5095044502091950623) -->
- <skip />
-</resources>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index c2bea2c..0cc1605 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -16,10 +16,10 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="contactsList" msgid="8661624236494819731">"Contactos"</string>
- <string name="launcherDialer" msgid="140610573639849799">"Marcador"</string>
+ <string name="launcherDialer" msgid="8636288196618486553">"Teléfono"</string>
<string name="shortcutContact" msgid="749243779392912958">"Contacto"</string>
- <string name="shortcutDialContact" msgid="7165340343023469996">"Marcado directo"</string>
- <string name="shortcutMessageContact" msgid="3025782962770298900">"Mensaje directo"</string>
+ <string name="shortcutDialContact" msgid="746622101599186779">"Marcado directo"</string>
+ <string name="shortcutMessageContact" msgid="2460337253595976198">"Mensaje directo"</string>
<string name="shortcutActivityTitle" msgid="6642877210643565436">"Seleccionar un acceso directo"</string>
<string name="callShortcutActivityTitle" msgid="6065749861423648991">"Seleccionar un número para la llamada"</string>
<string name="messageShortcutActivityTitle" msgid="3084542316620335911">"Seleccionar un número para el mensaje"</string>
@@ -40,12 +40,39 @@
<string name="menu_showBarcode" msgid="309973637178814132">"Mostrar código de barras"</string>
<string name="menu_editContact" msgid="3452858480713561396">"Editar contacto"</string>
<string name="menu_deleteContact" msgid="1916555454274101750">"Suprimir contacto"</string>
- <string name="menu_call" msgid="7359207953236681606">"Llamar a"</string>
- <string name="menu_sendSMS" msgid="5917289601666766617">"Enviar SMS//MMS"</string>
+ <string name="menu_call" msgid="3992595586042260618">"Llamar al contacto"</string>
+ <string name="menu_sendSMS" msgid="5535886767547006515">"Enviar texto al contacto"</string>
<string name="menu_sendEmail" msgid="7293508859242926187">"Enviar correo electrónico"</string>
<string name="menu_viewAddress" msgid="1814744325763202024">"Dirección del mapa"</string>
<string name="menu_makeDefaultNumber" msgid="4838759253316649534">"Crear número predeterminado"</string>
+ <string name="menu_makeDefaultEmail" msgid="2599044610375789994">"Crear correo electrónico predeterminado"</string>
+ <!-- no translation found for menu_splitAggregate (8368636463748691868) -->
+ <skip />
+ <string name="splitAggregate_title" msgid="2053462872948058798">"Dividir contacto"</string>
+ <!-- no translation found for contactsSplitMessage (5253490235863170269) -->
+ <skip />
+ <!-- no translation found for splitConfirmation_title (6716467920283502570) -->
+ <skip />
+ <!-- no translation found for splitConfirmation (1150797297503944823) -->
+ <skip />
+ <string name="menu_joinAggregate" msgid="5027981918265667970">"Unirse"</string>
+ <string name="menu_showSources" msgid="885215611438295455">"Mostrar fuentes"</string>
+ <string name="menu_hideSources" msgid="71367585820555477">"Ocultar fuentes"</string>
+ <string name="titleJoinAggregate" msgid="6970566008563147202">"Unirse al contacto"</string>
+ <string name="titleJoinContactDataWith" msgid="7684875775798635354">"Unirse a contactos"</string>
+ <string name="blurbJoinContactDataWith" msgid="995870557595050304">"Selecciona el contacto al que quieras unirte con <xliff:g id="NAME">%s</xliff:g>."</string>
+ <string name="showAllContactsJoinItem" msgid="2189695051430392383">"Mostrar todos los contactos"</string>
+ <string name="separatorJoinAggregateSuggestions" msgid="2831414448851313345">"Contactos sugeridos"</string>
+ <string name="separatorJoinAggregateAll" msgid="7939932265026181043">"Todos los contactos"</string>
+ <string name="contactsJoinedMessage" msgid="7208148163607047389">"Unión de contactos"</string>
+ <string name="menu_contactOptions" msgid="1957061455705020617">"Opciones"</string>
+ <string name="contactOptionsTitle" msgid="8259347644090700915">"Opciones"</string>
<string name="deleteConfirmation_title" msgid="6394309508930335204">"Suprimir"</string>
+ <!-- no translation found for readOnlyContactWarning (1390849295342594265) -->
+ <skip />
+ <!-- no translation found for readOnlyContactDeleteConfirmation (2137170726670196909) -->
+ <skip />
+ <string name="multipleContactDeleteConfirmation" msgid="938900978442960800">"Eliminar este contacto suprimirá la información de mútliples cuentas."</string>
<string name="deleteConfirmation" msgid="811706994761610640">"Este contacto se suprimirá."</string>
<string name="menu_done" msgid="796017761764190697">"Finalizado"</string>
<string name="menu_doNotSave" msgid="2174577548513895144">"Revertir"</string>
@@ -55,6 +82,9 @@
<string name="label_phonetic_name" msgid="2288082649573927286">"Fonético"</string>
<string name="label_notes" msgid="8337354953278341042">"Notas"</string>
<string name="label_ringtone" msgid="8833166825330686244">"Tono de llamada"</string>
+ <string name="label_groups" msgid="7304551384542859026">"Grupos"</string>
+ <string name="group_list" msgid="8583361685440161307">", <xliff:g id="GROUPNAME">%s</xliff:g>"</string>
+ <string name="menu_viewGroup" msgid="1401851715586577551">"Editar grupos"</string>
<string name="ghostData_name" msgid="6490954238641157585">"Primero y último"</string>
<string name="ghostData_phonetic_name" msgid="7852749081984070902">"Nombre fonético"</string>
<string name="ghostData_company" msgid="5414421120553765775">"Empresa"</string>
@@ -64,6 +94,7 @@
<string name="ghostData_phone" msgid="6963153888271466620">"Número de teléfono"</string>
<string name="ghostData_email" msgid="6184537075551565919">"Dirección de correo electrónico"</string>
<string name="ghostData_postal" msgid="652611650594951897">"Dirección postal"</string>
+ <string name="ghostData_group" msgid="4504591380347534114">"Mostrar grupo"</string>
<string name="invalidContactMessage" msgid="5816991830260044593">"El contacto no existe."</string>
<string name="pickerNewContactHeader" msgid="7750705279843568147">"Crear nuevo contacto"</string>
<string name="selectLabel" msgid="4255424123394910733">"Seleccionar etiqueta"</string>
@@ -80,7 +111,8 @@
<string name="photoPickerNotFoundText" msgid="431331662154342581">"No hay imágenes disponibles en el teléfono."</string>
<string name="attachToContact" msgid="8820530304406066714">"Ícono de contacto"</string>
<string name="customLabelPickerTitle" msgid="1081475101983255212">"Nombre personalizado de etiqueta"</string>
- <string name="menu_displayGroup" msgid="4603480747214476335">"Mostrar grupo"</string>
+ <string name="menu_displayGroup" msgid="5655505437727616553">"Mostrar opciones"</string>
+ <string name="displayGroups" msgid="2278964020773993336">"Mostrar opciones"</string>
<string name="syncGroupPreference" msgid="9028361137161162861">"Editar grupos sincroniz."</string>
<string name="importFromSim" msgid="8383900146531125319">"Importar contactos"</string>
<string name="send_to_voicemail_checkbox" msgid="9001686764070676353">"Enviar llamadas directamente al correo de voz"</string>
@@ -89,20 +121,33 @@
<string name="addPicture" msgid="1594679312161537678">"Agregar ícono"</string>
<string name="removePicture" msgid="3041230993155966350">"Eliminar ícono"</string>
<string name="noContacts" msgid="8579310973261953559">"No hay contactos."</string>
+ <string name="noMatchingContacts" msgid="4266283206853990471">"No se encontraron contactos coincidentes."</string>
<string name="noContactsWithPhoneNumbers" msgid="1605457050218824269">"No hay contactos con números de teléfono."</string>
<string name="noFavorites" msgid="812766386743315815">"No hay favoritos."</string>
<string name="select_group_title" msgid="7955698611959835612">"Grupos"</string>
<string name="groupEmpty" msgid="6661950109828194595">"Tu grupo \"<xliff:g id="GROUPNAME">%s</xliff:g>\" está vacío."</string>
<string name="showAllGroups" msgid="5164410117611094297">"Todos los contactos"</string>
+ <string name="showFilterPhones" msgid="4184858075465653970">"Sólo contactos con teléfonos"</string>
+ <string name="showFilterPhonesDescrip" msgid="6644443248815191067">"Mostrar sólo contactos que posean números de teléfono"</string>
+ <string name="headerContactGroups" msgid="2426134991932503843">"Elige contactos para mostrar"</string>
+ <plurals name="groupDescrip">
+ <item quantity="other" msgid="3507881585720628389">"<xliff:g id="COUNT">%0$d</xliff:g> contactos"</item>
+ </plurals>
+ <plurals name="groupDescripPhones">
+ <item quantity="other" msgid="3816047547470490208">"<xliff:g id="COUNT_0">%1$d</xliff:g> contactos, <xliff:g id="COUNTWITHPHONES">%2$d</xliff:g> con teléfonos"</item>
+ </plurals>
<string name="syncAllGroups" msgid="7512169081224806890">"Sincronizar todos los contactos"</string>
- <string name="groupNameMyContacts" msgid="5696566165170977126">"Mis contactos"</string>
+ <string name="groupNameMyContacts" msgid="277995505294105439">"Mis contactos"</string>
<string name="groupNameWithPhones" msgid="6981588604041120497">"Contactos con números de teléfono"</string>
<string name="starredInAndroid" msgid="6495527538140213440">"Marcado con asterisco en Android"</string>
+ <string name="savingContact" msgid="4075751076741924939">"Guardando contacto..."</string>
+ <string name="savingDisplayGroups" msgid="2133152192716475939">"Guardando opciones de visualización..."</string>
<string name="contactCreatedToast" msgid="8740102129968688060">"Contacto creado."</string>
<string name="contactSavedToast" msgid="7152589189385441091">"Contacto guardado."</string>
+ <string name="contactSavedErrorToast" msgid="9189098776225004666">"Error, no se han podido guardar las modificaciones de contacto."</string>
<string name="listSeparatorCallNumber" msgid="7115321894439629408">"Marcar número"</string>
<string name="listSeparatorCallNumber_edit" msgid="2999167270900823334">"Números de teléfono"</string>
- <string name="listSeparatorSendSmsMms" msgid="2480944736714739967">"Enviar SMS//MMS"</string>
+ <string name="listSeparatorSendSmsMms" msgid="5351657038532024412">"Enviar texto"</string>
<string name="listSeparatorSendEmail" msgid="5395590830304525721">"Enviar correo electrónico"</string>
<string name="listSeparatorSendEmail_edit" msgid="6859593624213634454">"Direcciones de correo electr."</string>
<string name="listSeparatorSendIm" msgid="2209260633185984105">"Enviar mensaje instantáneo"</string>
@@ -110,17 +155,31 @@
<string name="listSeparatorMapAddress" msgid="7076401301758190804">"Dirección del mapa"</string>
<string name="listSeparatorMapAddress_edit" msgid="298711187672067985">"Direcciones postales"</string>
<string name="listSeparatorOrganizations" msgid="7514083358440762504">"Organizaciones"</string>
+ <string name="listSeparatorGroups" msgid="1593940073983422450">"Grupos"</string>
<string name="listSeparatorOtherInformation" msgid="7844959649638482329">"Otra información"</string>
<string name="listSeparatorOtherInformation_edit" msgid="1326921768011367750">"Otras opciones"</string>
<string name="listSeparatorMore_edit" msgid="858454837482243176">"Más"</string>
+ <!-- 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 (2830107332033967280) -->
+ <!-- no translation found for listFoundAllContacts:other (7752927996850263152) -->
+ <!-- no translation found for listFoundAllContactsZero (5554368784319460828) -->
+ <skip />
+ <string name="socialStreamIconLabel" msgid="4367712449555075376">"Social"</string>
<string name="contactsIconLabel" msgid="7666609097606552806">"Contactos"</string>
<string name="contactsFavoritesLabel" msgid="8417039765586853670">"Favoritos"</string>
- <string name="dialerIconLabel" msgid="7228520966699542850">"Marcador"</string>
+ <string name="dialerIconLabel" msgid="6500826552823403796">"Teléfono"</string>
<string name="recentCallsIconLabel" msgid="1419116422359067949">"Regist. de llam."</string>
<string name="liveFolderAll" msgid="4789010460767506206">"Todos los contactos"</string>
<string name="liveFolderFavorites" msgid="3100957542927222282">"Contactos marcados con asterisco"</string>
<string name="liveFolderPhone" msgid="3739376066610926780">"Contactos con números de teléfono"</string>
- <string name="menu_sendTextMessage" msgid="455561537582270625">"Enviar mensaje SMS"</string>
+ <string name="menu_sendTextMessage" msgid="6937343460284499306">"Enviar mensaje de texto"</string>
<string name="recentCalls_callNumber" msgid="1756372533999226126">"Llamar a <xliff:g id="NAME">%s</xliff:g>"</string>
<string name="recentCalls_editNumberBeforeCall" msgid="7756171675833267857">"Editar número antes de llamar"</string>
<string name="recentCalls_addToContact" msgid="1429899535546487008">"Agregar a contactos"</string>
@@ -128,6 +187,7 @@
<string name="recentCalls_deleteAll" msgid="6352364392762163704">"Borrar registro de llamadas"</string>
<string name="recentCalls_empty" msgid="247053222448663107">"El registro de llamadas está vacío."</string>
<string name="imei" msgid="3045126336951684285">"IMEI"</string>
+ <string name="meid" msgid="6210568493746275750">"MEID"</string>
<string name="voicemail" msgid="3851469869202611441">"Correo de voz"</string>
<string name="unknown" msgid="740067747858270469">"Desconocido"</string>
<string name="private_num" msgid="6374339738119166953">"Número privado"</string>
@@ -137,10 +197,13 @@
<string name="simContacts_emptyLoading" msgid="6700035985448642408">"Cargando desde tarjeta SIM..."</string>
<string name="simContacts_title" msgid="27341688347689769">"Contactos de tarjeta SIM"</string>
<string name="contactsSyncPlug" msgid="7248276704957313698"><font fgcolor="#ffffffff">"¡Sincroniza tus contactos de Google!"</font>" "\n" Luego de sincronizar tu teléfono, tus contactos estarán disponibles para ti dondequiera que vayas."</string>
- <string name="noContactsHelpText" msgid="2249195687463896364">"No tienes ningún contacto."\n\n"Para agregar contactos, presiona "<font fgcolor="#ffffffff"><b>"Menú"</b></font>" y selecciona:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Nuevo contacto"</b></font>" para crear un contacto nuevo desde cero"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importar contactos"</b></font>" para agregar contactos desde tu tarjeta SIM"\n</li></string>
- <string name="noContactsHelpTextWithSync" msgid="4927701496550314555">"No tienes ningún contacto."\n\n"Para agregar contactos, presiona "<font fgcolor="#ffffffff"><b>"Menú"</b></font>" y selecciona:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Editar grupos sincronizados"</b></font>" para agregar desde una cuenta de Google nueva o existente"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Nuevo contacto"</b></font>" para crear un contacto nuevo desde cero"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importar contactos"</b></font>" para agregar contactos desde tu tarjeta SIM"\n</li></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="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="seclectSyncGroups_title" msgid="1235432026231325655">"Seleccionar grupos para sincronizar"</string>
- <string name="liveFolder_all_label" msgid="1552523730090319259">"Todos los contactos"</string>
+ <string name="liveFolder_all_label" msgid="5961411940473276616">"Todos los contactos"</string>
<string name="liveFolder_favorites_label" msgid="2674341514070517105">"Marcado con asterisco"</string>
<string name="liveFolder_phones_label" msgid="1709786878793436245">"Teléfonos"</string>
<string name="dialer_useDtmfDialpad" msgid="1707548397435075040">"Usar teclado de tonos del teléfono"</string>
@@ -171,8 +234,9 @@
<string name="returnCall" msgid="8171961914203617813">"Regresar llamada"</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="5007070838253932139">"Llamado con frecuencia"</string>
- <string name="add_contact_dlg_title" msgid="2789046541229846116">"Agregar contacto"</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>
+ <string name="all_tab_label" msgid="4003124364397916826">"Todos"</string>
<string name="description_image_button_one" msgid="1740638037139856139">"uno"</string>
<string name="description_image_button_two" msgid="5882638439003731308">"dos"</string>
<string name="description_image_button_three" msgid="8709731759376015180">"tres"</string>
@@ -185,43 +249,171 @@
<string name="description_image_button_star" msgid="3365919907520767866">"asterisco"</string>
<string name="description_image_button_zero" msgid="4133108949401820710">"cero"</string>
<string name="description_image_button_pound" msgid="3039765597595889230">"libra"</string>
- <string name="no_sdcard_title" msgid="6455416795090113715">"No hay tarjeta SD"</string>
- <string name="no_sdcard_message" msgid="7791906118138538259">"No se ha detectado ninguna tarjeta SD"</string>
- <string name="searching_vcard_title" msgid="4158580043290687793">"Buscando VCard"</string>
+ <string name="no_sdcard_title" msgid="5911758680339949273">"No hay tarjeta SD"</string>
+ <string name="no_sdcard_message" msgid="6019391476490445358">"No se ha detectado ninguna tarjeta SD"</string>
+ <string name="searching_vcard_title" msgid="4970508055399376813">"Buscando vCard"</string>
<string name="select_import_type_title" msgid="2443742794103731022">"¿De dónde quieres importar contactos?"</string>
- <string name="import_from_sim" msgid="3362158539070179154">"Tarjeta SIM"</string>
- <string name="import_from_sdcard" msgid="8854793070007530689">"Tarjeta SD"</string>
- <string name="import_all_vcard_string" msgid="2788222288962542415">"Importar todos los archivos de VCard"</string>
- <string name="import_one_vcard_string" msgid="4618119296910253689">"Importar un archivo de VCard"</string>
- <string name="searching_vcard_message" msgid="2467573749792455605">"Buscando datos VCard en VCard"</string>
- <string name="scanning_sdcard_failed_title" msgid="2788276113399585924">"No se ha podido explorar la Tarjeta SD"</string>
- <string name="scanning_sdcard_failed_message" msgid="925361244069058117">"No se ha podido explorar la Tarjeta SD"</string>
+ <string name="import_from_sim" msgid="3859272228033941659">"Importar de la tarjeta SIM"</string>
+ <string name="import_from_sdcard" msgid="8550360976693202816">"Importar de la tarjeta SD"</string>
+ <string name="export_to_sdcard" msgid="2597105442616166277">"Exportar a la tarjeta SD"</string>
+ <string name="import_one_vcard_string" msgid="9059163467020328433">"Importar un archivo de vCard"</string>
+ <string name="import_multiple_vcard_string" msgid="3810226492811062392">"Importar múltiples archivos de vCard"</string>
+ <string name="import_all_vcard_string" msgid="5518136113853448474">"Importar todos los archivos de vCard"</string>
+ <string name="searching_vcard_message" msgid="6917522333561434546">"Buscando datos de vCard en la tarjeta SD"</string>
+ <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_vcard_parse_error" msgid="2129476089250836277">"No se ha podido analizar VCard"</string>
- <string name="fail_reason_no_vcard_file" msgid="1489638537002538332">"No se ha encontrado un archivo de VCard en la Tarjeta SD"</string>
- <string name="fail_reason_no_vcard_entry" msgid="3808734131680935043">"No se ha encontrado ninguna entrada válida de VCard para tu selección"</string>
- <string name="select_vcard_title" msgid="4615933643517710543">"Seleccionar archivo de VCard"</string>
- <string name="select_vcard_message" msgid="7028772212789057858">"Seleccionar un archivo de VCard para importar"</string>
- <string name="reading_vcard_title" msgid="430154704397375160">"Leyendo VCard"</string>
- <string name="reading_vcard_message" msgid="7470486051482460267">"Leyendo archivo(s) de VCard"</string>
- <string name="importing_vcard_message" msgid="2466665710812588738">"Importando datos VCard"</string>
- <string name="reading_vcard_failed_title" msgid="5042366466147724748">"No se han podido leer los datos VCard"</string>
- <string name="reading_vcard_failed_message" msgid="780588344175743735">"No se han podido leer los datos VCard"\n"Motivo: \"<xliff:g id="FAIL_REASON">%s</xliff:g>\""</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_no_vcard_entry" msgid="4733290752474073143">"No se ha encontrado ninguna entrada válida de vCard para tu selección"</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="select_vcard_message" msgid="221189184212818304">"Selecciona un archivo de vCard para importar"</string>
+ <string name="progress_shower_message" msgid="5636525578293752526">"Segmento <xliff:g id="ACTION">%s</xliff:g>"\n"<xliff:g id="FILENAME">%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="importing_vcard_message" msgid="4046655384673753503">"Importando datos 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_failed_message" msgid="5684089173948287107">"No se ha podido leer vCard."\n"Motivo: \"<xliff:g id="FAIL_REASON">%s</xliff:g>\""</string>
<string name="reading_vcard_contacts" msgid="3066834102042012868">"<xliff:g id="CURRENT_NUMBER">%s</xliff:g> de <xliff:g id="TOTAL_NUMBER">%s</xliff:g> contactos"</string>
<string name="reading_vcard_files" msgid="34180143726972661">"<xliff:g id="CURRENT_NUMBER">%s</xliff:g> de <xliff:g id="TOTAL_NUMBER">%s</xliff:g> archivos"</string>
+ <string name="export_all_contacts" msgid="2873892623335194071">"Todos los contactos"</string>
+ <string name="export_phone_local_only" msgid="3380497955409896761">"Contactos guardados localmente"</string>
<string name="export_contact_list" msgid="3165097742175874384">"Exportar contactos"</string>
- <string name="confirm_export_title" msgid="1693047909433122854">"Confirmación para exportar"</string>
- <string name="confirm_export_message" msgid="63482084706768079">"¿Aceptas exportar tu lista de contactos a \"<xliff:g id="VCARD_FILENAME">%s</xliff:g>\"?"</string>
- <string name="exporting_contact_failed_title" msgid="1455264422455075858">"Ha ocurrido un error al exportar datos de contacto"</string>
- <string name="exporting_contact_failed_message" msgid="1426451081541603512">"Ha ocurrido un error al exportar los datos de contacto"\n"Motivo del error: \"<xliff:g id="FAIL_REASON">%s</xliff:g>\""</string>
- <string name="fail_reason_too_many_vcard" msgid="5416992255233341607">"Demasiados datos de VCard en la tarjeta SD"</string>
- <string name="fail_reason_too_long_filename" msgid="7105223965196949065">"Nombre de archivo demasiado largo (\"<xliff:g id="FILENAME">%s</xliff:g>\") es necesario"</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>
+ <string name="exporting_contact_failed_message" msgid="4151348002470298092">"No se han podido exportar los datos de contacto."\n"Motivo: \"<xliff:g id="FAIL_REASON">%s</xliff:g>\""</string>
+ <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="fail_reason_cannot_open_destination_dir" msgid="1739293936432987758">"No se ha podido abrir o crear el directorio de destino \"<xliff:g id="DIR_NAME">%s</xliff:g>\""</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>
<string name="fail_reason_error_occurred_during_export" msgid="2151165129433831202">"Se produjo un error durante la exportación: \"<xliff:g id="EXACT_REASON">%s</xliff:g>\""</string>
+ <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="2239503301380653777">"No hay ningún contacto exportable. Puedes elegir datos no exportables"</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">%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>
+ <string name="dial_button_label" msgid="7637725632722605863">"Marcar"</string>
+ <string name="call_disambig_title" msgid="1911302597959335178">"Llamar con"</string>
+ <string name="sms_disambig_title" msgid="4675399294513152364">"Texto con"</string>
+ <string name="make_primary" msgid="5829291915305113983">"Recuerda esta opción"</string>
+ <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="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>
+ <string name="menu_share" msgid="943789700636542260">"Compartir"</string>
+ <string name="share_via" msgid="563121028023030093">"Compartir un contacto a través de"</string>
+ <string name="share_error" msgid="4374508848981697170">"Este contacto no se puede compartir."</string>
+ <string name="nameLabelsGroup" msgid="2034640839640477827">"Nombre"</string>
+ <string name="nicknameLabelsGroup" msgid="2891682101053358010">"Apodo"</string>
+ <string name="organizationLabelsGroup" msgid="2478611760751832035">"Organización"</string>
+ <string name="websiteLabelsGroup" msgid="4202998982804009261">"Sitio web"</string>
+ <string name="eventLabelsGroup" msgid="8069912895912714412">"Evento"</string>
+ <string name="type_short_home" msgid="7770424864090605384">"H"</string>
+ <string name="type_short_mobile" msgid="1655473281466676216">"Lun."</string>
+ <string name="type_short_work" msgid="4925330752504537861">"Mié."</string>
+ <string name="type_short_pager" msgid="2613818970827594238">"P"</string>
+ <string name="type_short_other" msgid="5669407180177236769">"Oct."</string>
+ <string name="edit_read_only" msgid="8158629550655830981">"Este contacto es de sólo lectura"</string>
+ <string name="edit_secondary_collapse" msgid="5371618426594477103">"Más"</string>
+ <string name="dialog_primary_name" msgid="5521591005692614833">"Nombre principal"</string>
+ <string name="dialog_new_contact_account" msgid="9044704073286262197">"Crear contacto según la cuenta"</string>
+ <string name="menu_sync_remove" msgid="3266725887008450161">"Eliminar grupo de sincronización"</string>
+ <string name="dialog_sync_add" msgid="8267045393119375803">"Agregar grupo de sincronización"</string>
+ <string name="display_more_groups" msgid="2682547080423434170">"Más grupos…"</string>
+ <string name="display_ungrouped" msgid="4602580795576261158">"Todos los otros contactos"</string>
+ <string name="display_all_contacts" msgid="6846131371214707956">"Todos los contactos"</string>
+ <string name="display_warn_remove_ungrouped" msgid="2314043155909167610">"Al suprimir \"<xliff:g id="GROUP">%s</xliff:g>\" de sincronización también se suprimirá cualquier contacto no agrupado de la sincronización."</string>
+ <string name="account_phone" msgid="4025734638492419713">"Sólo telefónicamente (no sincronizado)"</string>
+ <string name="label_email_display_name" msgid="5537802602754309600">"Mostrar el nombre"</string>
+ <string name="call_custom" msgid="7756571794763171802">"Llamar a <xliff:g id="CUSTOM">%s</xliff:g>"</string>
+ <string name="call_home" msgid="1990519474420545392">"Llamar al hogar"</string>
+ <string name="call_mobile" msgid="7502236805487609178">"Llamar al celular"</string>
+ <string name="call_work" msgid="5328785911463744028">"Llamar al trabajo"</string>
+ <string name="call_fax_work" msgid="7467763592359059243">"Llamar al fax del trabajo"</string>
+ <string name="call_fax_home" msgid="8342175628887571876">"Llamar al fax personal"</string>
+ <string name="call_pager" msgid="9003902812293983281">"Llamar a localizador"</string>
+ <string name="call_other" msgid="5605584621798108205">"Llamar a otro"</string>
+ <string name="call_callback" msgid="1910165691349426858">"Llamar a devolución de llamada"</string>
+ <string name="call_car" msgid="3280537320306436445">"Llamar al auto"</string>
+ <string name="call_company_main" msgid="6105120947138711257">"Llamar empresa principal"</string>
+ <string name="call_isdn" msgid="1541590690193403411">"Llamar a ISDN"</string>
+ <string name="call_main" msgid="6082900571803441339">"Llamada principal"</string>
+ <string name="call_other_fax" msgid="7777261153532968503">"Llamar a otro fax"</string>
+ <string name="call_radio" msgid="8296755876398357063">"Llamar a la radio"</string>
+ <string name="call_telex" msgid="2223170774548648114">"Llamar a télex"</string>
+ <string name="call_tty_tdd" msgid="8951266948204379604">"Llamar a TTY/TDD"</string>
+ <string name="call_work_mobile" msgid="8707874281430105394">"Llamar al celular del trabajo"</string>
+ <string name="call_work_pager" msgid="3419348514157949008">"Llamar al localizador del trabajo"</string>
+ <string name="call_assistant" msgid="2141641383068514308">"Llamar a <xliff:g id="ASSISTANT">%s</xliff:g>"</string>
+ <string name="call_mms" msgid="6274041545876221437">"Llamar a MMS"</string>
+ <string name="sms_custom" msgid="5932736853732191825">"Texto <xliff:g id="CUSTOM">%s</xliff:g>"</string>
+ <string name="sms_home" msgid="7524332261493162995">"Enviar texto al hogar"</string>
+ <string name="sms_mobile" msgid="5200107250451030769">"Enviar texto a celular"</string>
+ <string name="sms_work" msgid="2269624156655267740">"Enviar texto al trabajo"</string>
+ <string name="sms_fax_work" msgid="8028189067816907075">"Enviar texto a fax laboral"</string>
+ <string name="sms_fax_home" msgid="9204042076306809634">"Enviar texto a fax personal"</string>
+ <string name="sms_pager" msgid="7730404569637015192">"Enviar texto a localizador"</string>
+ <string name="sms_other" msgid="5131921487474531617">"Enviar texto a otro"</string>
+ <string name="sms_callback" msgid="5004824430094288752">"Enviar texto a devolución de llamada"</string>
+ <string name="sms_car" msgid="7444227058437359641">"Enviar texto a auto"</string>
+ <string name="sms_company_main" msgid="118970873419678087">"Enviar texto a empresa principal"</string>
+ <string name="sms_isdn" msgid="8153785037515047845">"ISDN de texto"</string>
+ <string name="sms_main" msgid="8621625784504541679">"Texto principal"</string>
+ <string name="sms_other_fax" msgid="3930666870074006114">"Enviar texto a otro fax"</string>
+ <string name="sms_radio" msgid="3329166673433967820">"Enviar texto a radio"</string>
+ <string name="sms_telex" msgid="9034802430065267848">"Enviar texto a télex"</string>
+ <string name="sms_tty_tdd" msgid="6782284969132531532">"Enviar texto a TTY/TDD"</string>
+ <string name="sms_work_mobile" msgid="2459939960512702560">"Enviar texto al celular del trabajo"</string>
+ <string name="sms_work_pager" msgid="5566924423316960597">"Enviar texto a localizador del trabajo"</string>
+ <string name="sms_assistant" msgid="2773424339923116234">"Enviar texto a <xliff:g id="ASSISTANT">%s</xliff:g>"</string>
+ <string name="sms_mms" msgid="4069352461380762677">"Enviar texto MMS"</string>
+ <string name="email_home" msgid="8573740658148184279">"Correo electrónico personal"</string>
+ <string name="email_mobile" msgid="2042889209787989814">"Correo electrónico celular"</string>
+ <string name="email_work" msgid="2807430017302722689">"Correo electrónico laboral"</string>
+ <string name="email_other" msgid="8093933498541795832">"Correo electrónico otro"</string>
+ <string name="email_custom" msgid="7548003991586214105">"Correo electrónico <xliff:g id="CUSTOM">%s</xliff:g>"</string>
+ <string name="email" msgid="5668400997660065897">"Correo electrónico"</string>
+ <string name="map_home" msgid="1243547733423343982">"Ver dirección principal"</string>
+ <string name="map_work" msgid="1360474076921878088">"Ver dirección laboral"</string>
+ <string name="map_other" msgid="5560707927535653892">"Ver otra dirección"</string>
+ <string name="map_custom" msgid="6184363799976265281">"Ver <xliff:g id="CUSTOM">%s</xliff:g> dirección"</string>
+ <string name="chat_aim" msgid="2588492205291249142">"Chat mediante AIM"</string>
+ <string name="chat_msn" msgid="8041633440091073484">"Chat mediante Windows Live"</string>
+ <string name="chat_yahoo" msgid="6629211142719943666">"Chat mediante Yahoo"</string>
+ <string name="chat_skype" msgid="1210045020427480566">"Chat mediante Skype"</string>
+ <string name="chat_qq" msgid="4294637812847719693">"Chat mediante QQ"</string>
+ <string name="chat_gtalk" msgid="981575737258117697">"Chat mediante Google Talk"</string>
+ <string name="chat_icq" msgid="8438405386153745775">"Chat mediante ICQ"</string>
+ <string name="chat_jabber" msgid="7561444230307829609">"Chat mediante Jabber"</string>
+ <string name="chat" msgid="9025361898797412245">"Chat"</string>
+ <string name="postal_street" msgid="8133143961580058972">"Dirección postal"</string>
+ <string name="postal_pobox" msgid="4431938829180269821">"Apartado postal"</string>
+ <string name="postal_neighborhood" msgid="1450783874558956739">"Barrio"</string>
+ <string name="postal_city" msgid="6597491300084895548">"Ciudad"</string>
+ <string name="postal_region" msgid="6045263193478437672">"Estado"</string>
+ <string name="postal_postcode" msgid="572136414136673751">"Código POSTAL"</string>
+ <string name="postal_country" msgid="7638264508416368690">"País"</string>
+ <string name="name_given" msgid="1687286314106019813">"Nombre de pila"</string>
+ <string name="name_family" msgid="3416695586119999058">"Nombre familiar"</string>
+ <string name="name_prefix" msgid="59756378548779822">"Prefijo del nombre"</string>
+ <string name="name_middle" msgid="8467433655992690326">"Segundo nombre"</string>
+ <string name="name_suffix" msgid="3855278445375651441">"Sufijo del nombre"</string>
+ <string name="name_phonetic_given" msgid="6853570431394449191">"Nombre de pila fonético"</string>
+ <string name="name_phonetic_middle" msgid="8643721493320405200">"Segundo nombre fonético"</string>
+ <string name="name_phonetic_family" msgid="462095502140180305">"Nombre familiar fonético"</string>
+ <string name="split_label" msgid="8262112659919449087">"Dividir"</string>
+ <string name="split_explanation" msgid="1824739956426973592">"Transformar a estos datos en el contacto propio."</string>
+ <string name="account_name_format" msgid="4421123930035299208">"De <xliff:g id="SOURCE">%1$s</xliff:g> la cuenta: <xliff:g id="ACCOUNT">%2$s</xliff:g>"</string>
</resources>
diff --git a/res/values-es/config.xml b/res/values-es/config.xml
deleted file mode 100644
index 07b3c7e..0000000
--- a/res/values-es/config.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for config_export_vcard_type (9145748078116159716) -->
- <skip />
- <string name="config_export_dir" msgid="222182743639478636">"/sdcard"</string>
- <!-- no translation found for config_export_file_prefix (3022868431158658122) -->
- <skip />
- <!-- no translation found for config_export_file_suffix (16505844221142195) -->
- <skip />
- <string name="config_export_file_extension" msgid="1758878818611339161">"vcf"</string>
- <!-- no translation found for config_export_extensions_to_consider (5095044502091950623) -->
- <skip />
-</resources>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index 4da3c5e..3ea81a0 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -40,11 +40,24 @@
<string name="menu_showBarcode" msgid="309973637178814132">"Mostrar código de barras"</string>
<string name="menu_editContact" msgid="3452858480713561396">"Editar contacto"</string>
<string name="menu_deleteContact" msgid="1916555454274101750">"Eliminar contacto"</string>
- <string name="menu_call" msgid="7359207953236681606">"Llamar"</string>
- <string name="menu_sendSMS" msgid="5917289601666766617">"Enviar SMS/MMS"</string>
+ <!-- no translation found for menu_call (3992595586042260618) -->
+ <skip />
+ <!-- no translation found for menu_sendSMS (5535886767547006515) -->
+ <skip />
<string name="menu_sendEmail" msgid="7293508859242926187">"Enviar mensaje de correo electrónico"</string>
<string name="menu_viewAddress" msgid="1814744325763202024">"Dirección en mapa"</string>
<string name="menu_makeDefaultNumber" msgid="4838759253316649534">"Convertir en número predeterminado"</string>
+ <string name="menu_makeDefaultEmail" msgid="2599044610375789994">"Establecer como dirección de correo electrónico predeterminada"</string>
+ <string name="menu_splitAggregate" msgid="7402548456225901170">"Desagrupar"</string>
+ <string name="splitAggregate_title" msgid="2053462872948058798">"Desagrupar contacto"</string>
+ <string name="contactsSplitMessage" msgid="3439008547775199243">"Contacto desagrupado"</string>
+ <string name="menu_joinAggregate" msgid="5027981918265667970">"Agrupar"</string>
+ <string name="titleJoinAggregate" msgid="6970566008563147202">"Agrupar contacto"</string>
+ <string name="separatorJoinAggregateSuggestions" msgid="5494823322479834017">"Sugerencias"</string>
+ <string name="separatorJoinAggregateAll" msgid="7939932265026181043">"Todos los contactos"</string>
+ <string name="contactsJoinedMessage" msgid="7208148163607047389">"Contactos agrupados"</string>
+ <string name="menu_contactOptions" msgid="1957061455705020617">"Opciones"</string>
+ <string name="contactOptionsTitle" msgid="8259347644090700915">"Opciones"</string>
<string name="deleteConfirmation_title" msgid="6394309508930335204">"Suprimir"</string>
<string name="deleteConfirmation" msgid="811706994761610640">"El contacto se eliminará."</string>
<string name="menu_done" msgid="796017761764190697">"Listo"</string>
@@ -55,6 +68,9 @@
<string name="label_phonetic_name" msgid="2288082649573927286">"Pronunciación"</string>
<string name="label_notes" msgid="8337354953278341042">"Notas"</string>
<string name="label_ringtone" msgid="8833166825330686244">"Tono"</string>
+ <string name="label_groups" msgid="7304551384542859026">"Grupos"</string>
+ <string name="group_list" msgid="8583361685440161307">", <xliff:g id="GROUPNAME">%s</xliff:g>"</string>
+ <string name="menu_viewGroup" msgid="1401851715586577551">"Editar grupos"</string>
<string name="ghostData_name" msgid="6490954238641157585">"Nombre y apellido"</string>
<string name="ghostData_phonetic_name" msgid="7852749081984070902">"Transcripción fonética del nombre"</string>
<string name="ghostData_company" msgid="5414421120553765775">"Empresa"</string>
@@ -64,6 +80,7 @@
<string name="ghostData_phone" msgid="6963153888271466620">"Número de teléfono"</string>
<string name="ghostData_email" msgid="6184537075551565919">"Dirección de correo electrónico"</string>
<string name="ghostData_postal" msgid="652611650594951897">"Dirección postal"</string>
+ <string name="ghostData_group" msgid="4504591380347534114">"Mostrar grupo"</string>
<string name="invalidContactMessage" msgid="5816991830260044593">"Ese contacto no existe."</string>
<string name="pickerNewContactHeader" msgid="7750705279843568147">"Crear contacto nuevo"</string>
<string name="selectLabel" msgid="4255424123394910733">"Seleccionar etiqueta"</string>
@@ -80,7 +97,8 @@
<string name="photoPickerNotFoundText" msgid="431331662154342581">"No hay ninguna imagen disponible en el teléfono."</string>
<string name="attachToContact" msgid="8820530304406066714">"Icono de contacto"</string>
<string name="customLabelPickerTitle" msgid="1081475101983255212">"Nombre de etiqueta personalizada"</string>
- <string name="menu_displayGroup" msgid="4603480747214476335">"Mostrar grupo"</string>
+ <string name="menu_displayGroup" msgid="6022511302986343108">"Mostrar grupos"</string>
+ <string name="displayGroups" msgid="2645969886032719891">"Mostrar grupos"</string>
<string name="syncGroupPreference" msgid="9028361137161162861">"Editar grupos de sincronización"</string>
<string name="importFromSim" msgid="8383900146531125319">"Importar contactos"</string>
<string name="send_to_voicemail_checkbox" msgid="9001686764070676353">"Enviar llamadas directamente como mensajes de voz"</string>
@@ -94,12 +112,23 @@
<string name="select_group_title" msgid="7955698611959835612">"Grupos"</string>
<string name="groupEmpty" msgid="6661950109828194595">"El grupo \"<xliff:g id="GROUPNAME">%s</xliff:g>\" está vacío."</string>
<string name="showAllGroups" msgid="5164410117611094297">"Todos los contactos"</string>
+ <string name="showFilterPhones" msgid="4184858075465653970">"Solo contactos con teléfono"</string>
+ <string name="showFilterPhonesDescrip" msgid="6644443248815191067">"Mostrar solo contactos con números de teléfono"</string>
+ <string name="headerContactGroups" msgid="1451383377487746789">"Grupos de contactos"</string>
+ <plurals name="groupDescrip">
+ <item quantity="other" msgid="3507881585720628389">"<xliff:g id="COUNT">%0$d</xliff:g> contactos"</item>
+ </plurals>
+ <plurals name="groupDescripPhones">
+ <item quantity="other" msgid="3816047547470490208">"<xliff:g id="COUNT_0">%1$d</xliff:g> contactos, <xliff:g id="COUNTWITHPHONES">%2$d</xliff:g> con teléfonos"</item>
+ </plurals>
<string name="syncAllGroups" msgid="7512169081224806890">"Sincronizar todos los contactos"</string>
<string name="groupNameMyContacts" msgid="5696566165170977126">"Mis contactos"</string>
<string name="groupNameWithPhones" msgid="6981588604041120497">"Contactos con números de teléfono"</string>
<string name="starredInAndroid" msgid="6495527538140213440">"Destacados en Android"</string>
<string name="contactCreatedToast" msgid="8740102129968688060">"Se ha creado el contacto."</string>
<string name="contactSavedToast" msgid="7152589189385441091">"El contacto se ha guardado."</string>
+ <!-- no translation found for contactSavedErrorToast (9189098776225004666) -->
+ <skip />
<string name="listSeparatorCallNumber" msgid="7115321894439629408">"Marcar número"</string>
<string name="listSeparatorCallNumber_edit" msgid="2999167270900823334">"Números de teléfono"</string>
<string name="listSeparatorSendSmsMms" msgid="2480944736714739967">"Enviar SMS/MMS"</string>
@@ -110,9 +139,11 @@
<string name="listSeparatorMapAddress" msgid="7076401301758190804">"Dirección en mapa"</string>
<string name="listSeparatorMapAddress_edit" msgid="298711187672067985">"Direcciones postales"</string>
<string name="listSeparatorOrganizations" msgid="7514083358440762504">"Organizaciones"</string>
+ <string name="listSeparatorGroups" msgid="1593940073983422450">"Grupos"</string>
<string name="listSeparatorOtherInformation" msgid="7844959649638482329">"Otra información"</string>
<string name="listSeparatorOtherInformation_edit" msgid="1326921768011367750">"Otras opciones"</string>
<string name="listSeparatorMore_edit" msgid="858454837482243176">"Más"</string>
+ <string name="socialStreamIconLabel" msgid="4367712449555075376">"Sociedad"</string>
<string name="contactsIconLabel" msgid="7666609097606552806">"Contactos"</string>
<string name="contactsFavoritesLabel" msgid="8417039765586853670">"Favoritos"</string>
<string name="dialerIconLabel" msgid="7228520966699542850">"Llamar"</string>
@@ -128,6 +159,7 @@
<string name="recentCalls_deleteAll" msgid="6352364392762163704">"Borrar registro de llamadas"</string>
<string name="recentCalls_empty" msgid="247053222448663107">"El registro de llamadas está vacío."</string>
<string name="imei" msgid="3045126336951684285">"IMEI"</string>
+ <string name="meid" msgid="6210568493746275750">"MEID"</string>
<string name="voicemail" msgid="3851469869202611441">"Buzón de voz"</string>
<string name="unknown" msgid="740067747858270469">"Desconocidos"</string>
<string name="private_num" msgid="6374339738119166953">"Número privado"</string>
@@ -138,7 +170,14 @@
<string name="simContacts_title" msgid="27341688347689769">"Contactos de tarjeta SIM"</string>
<string name="contactsSyncPlug" msgid="7248276704957313698"><font fgcolor="#ffffffff">"Sincroniza tus contactos de Google"</font>" "\n"Después de sincronizarlos con tu teléfono, podrás acceder a tus contactos desde cualquier lugar."</string>
<string name="noContactsHelpText" msgid="2249195687463896364">"No tienes ningún contacto."\n\n"Si quieres añadir alguno, pulsa la tecla "<font fgcolor="#ffffffff"><b>"Menu"</b></font>" y selecciona:"\n\n<li><font fgcolor="#ffffffff"><b>"Contacto nuevo"</b></font>" para añadir un contacto manualmente"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importar contactos"</b></font>" para añadirlos desde tu tarjeta SIM"\n</li></string>
- <string name="noContactsHelpTextWithSync" msgid="4927701496550314555">"No tienes ningún contacto."\n\n"Si quieres añadir alguno, pulsa la tecla de menú"<font fgcolor="#ffffffff"><b></b></font>" y selecciona:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Editar grupos de sincronización"</b></font>" para añadirlos desde una cuenta de Google nueva o ya existente"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Contacto nuevo"</b></font>" para añadir un contacto manualmente"\n</li>\n<li><font fgcolor="#ffffffff"><b>"Importar contactos"</b></font>" para añadirlos desde la tarjeta SIM"\n</li></string>
+ <!-- no translation found for noContactsHelpTextWithSync (2179024855526352215) -->
+ <skip />
+ <!-- no translation found for noContactsNoSimHelpText (645951808197910024) -->
+ <skip />
+ <!-- no translation found for noContactsNoSimHelpTextWithSync (511505839941079550) -->
+ <skip />
+ <!-- no translation found for noFavoritesHelpText (1705381563293866510) -->
+ <skip />
<string name="seclectSyncGroups_title" msgid="1235432026231325655">"Seleccionar grupos para la sincronización"</string>
<string name="liveFolder_all_label" msgid="1552523730090319259">"Todos los contactos"</string>
<string name="liveFolder_favorites_label" msgid="2674341514070517105">"Destacados"</string>
@@ -173,6 +212,7 @@
<string name="favoritesFrquentSeparator" msgid="5007070838253932139">"Más llamados"</string>
<string name="add_contact_dlg_title" msgid="2789046541229846116">"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>
+ <string name="all_tab_label" msgid="4003124364397916826">"Todo"</string>
<string name="description_image_button_one" msgid="1740638037139856139">"uno"</string>
<string name="description_image_button_two" msgid="5882638439003731308">"dos"</string>
<string name="description_image_button_three" msgid="8709731759376015180">"tres"</string>
@@ -189,19 +229,27 @@
<string name="no_sdcard_message" msgid="7791906118138538259">"No se ha detectado ninguna tarjeta SD."</string>
<string name="searching_vcard_title" msgid="4158580043290687793">"Buscando vCard"</string>
<string name="select_import_type_title" msgid="2443742794103731022">"¿De dónde quieres importar contactos?"</string>
- <string name="import_from_sim" msgid="3362158539070179154">"Tarjeta SIM"</string>
- <string name="import_from_sdcard" msgid="8854793070007530689">"Tarjeta SD"</string>
- <string name="import_all_vcard_string" msgid="2788222288962542415">"Importar todos los archivos de vCard"</string>
- <string name="import_one_vcard_string" msgid="4618119296910253689">"Importar un archivo de vCard"</string>
+ <!-- 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 />
+ <string name="import_one_vcard_string" msgid="9059163467020328433">"Importar un archivo de vCard"</string>
+ <string name="import_multiple_vcard_string" msgid="3810226492811062392">"Importar varios archivos de vCard"</string>
+ <string name="import_all_vcard_string" msgid="5518136113853448474">"Importar todos los archivos de vCard"</string>
<string name="searching_vcard_message" msgid="2467573749792455605">"Buscando datos de vCard en vCard..."</string>
<string name="scanning_sdcard_failed_title" msgid="2788276113399585924">"Error al buscar en tarjeta SD"</string>
<string name="scanning_sdcard_failed_message" msgid="925361244069058117">"Error al buscar en tarjeta SD"</string>
- <string name="fail_reason_io_error" msgid="5922864781066136340">"Error de E/S"</string>
- <string name="fail_reason_vcard_parse_error" msgid="2129476089250836277">"Error al analizar vCard"</string>
+ <string name="fail_reason_io_error" msgid="5949038573678667996">"Error de E/S (motivo: \"<xliff:g id="FAIL_REASON">%s</xliff:g>\")"</string>
+ <string name="fail_reason_vcard_parse_error" msgid="8877496734269540489">"No se ha podido analizar el archivo VCard debido a un error inesperado."</string>
+ <string name="fail_reason_vcard_not_supported_error" msgid="166568606246563552">"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="1489638537002538332">"No se ha encontrado ningún archivo de vCard en la tarjeta SD."</string>
<string name="fail_reason_no_vcard_entry" msgid="3808734131680935043">"No se ha encontrado ninguna entrada de vCard válida para la selección realizada."</string>
+ <string name="fail_reason_failed_to_read_files" msgid="3659521123567134029">"No ha sido posible importar uno o varios archivos (%s)."</string>
<string name="select_vcard_title" msgid="4615933643517710543">"Seleccionar archivo de vCard"</string>
<string name="select_vcard_message" msgid="7028772212789057858">"Selecciona un archivo de vCard para la operación de importación."</string>
+ <string name="progress_shower_message" msgid="5636525578293752526">"<xliff:g id="ACTION">%s</xliff:g>"\n"<xliff:g id="FILENAME">%s</xliff:g>"</string>
<string name="reading_vcard_title" msgid="430154704397375160">"Leyendo vCard"</string>
<string name="reading_vcard_message" msgid="7470486051482460267">"Leyendo archivos de vCard..."</string>
<string name="importing_vcard_message" msgid="2466665710812588738">"Importando datos de vCard..."</string>
@@ -224,4 +272,104 @@
<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="1488865103809190124">"Añadir pausa de dos segundos"</string>
+ <string name="add_wait" msgid="8347184715316058465">"Añadir espera"</string>
+ <string name="dial_button_label" msgid="7637725632722605863">"Llamar"</string>
+ <!-- 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 />
+ <string name="quickcontact_missing_app" msgid="1181287640680679441">"No se ha encontrado ninguna aplicación que pueda realizar esta acción."</string>
+ <string name="quickcontact_remember_choice" msgid="2858356075887897116">"Recordar esta opción"</string>
+ <string name="quickcontact_missing_name" msgid="7806083820928955853">"Desconocido"</string>
+ <string name="menu_accounts" msgid="8499114602017077970">"Cuentas"</string>
+ <!-- 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">"Nombre"</string>
+ <string name="nicknameLabelsGroup" msgid="2891682101053358010">"Seudónimo"</string>
+ <string name="organizationLabelsGroup" msgid="2478611760751832035">"Organización"</string>
+ <string name="websiteLabelsGroup" msgid="4202998982804009261">"Sitio web"</string>
+ <string name="type_short_home" msgid="7770424864090605384">"H"</string>
+ <string name="type_short_mobile" msgid="1655473281466676216">"M"</string>
+ <string name="type_short_work" msgid="4925330752504537861">"W"</string>
+ <string name="type_short_pager" msgid="2613818970827594238">"P"</string>
+ <string name="type_short_other" msgid="5669407180177236769">"O"</string>
+ <string name="edit_secondary_collapse" msgid="1855127176548440583">"Información secundaria"</string>
+ <string name="dialog_primary_name" msgid="5521591005692614833">"Nombre principal"</string>
+ <string name="dialog_new_contact_account" msgid="9044704073286262197">"Crear contacto en la cuenta"</string>
+ <string name="menu_sync_remove" msgid="3266725887008450161">"Eliminar grupo de sincronización"</string>
+ <!-- no translation found for dialog_sync_add (8267045393119375803) -->
+ <skip />
+ <!-- no translation found for display_more_groups (2682547080423434170) -->
+ <skip />
+ <string name="display_ungrouped" msgid="4357317661311803984">"(Contactos no agrupados)"</string>
+ <string name="display_warn_remove_ungrouped" msgid="2314043155909167610">"Si eliminas \"<xliff:g id="GROUP">%s</xliff:g>\" de la sincronización, también se eliminarán todos los contactos no agrupados."</string>
+ <string name="account_phone" msgid="4025734638492419713">"Solo en el teléfono (no sincronizado)"</string>
+ <string name="label_email_display_name" msgid="5537802602754309600">"Nombre de presentación"</string>
+ <string name="call_home" msgid="1990519474420545392">"Llamar a casa"</string>
+ <string name="call_mobile" msgid="7502236805487609178">"Llamar al móvil"</string>
+ <string name="call_work" msgid="5328785911463744028">"Llamar al trabajo"</string>
+ <string name="call_fax_work" msgid="7467763592359059243">"Llamar al fax del trabajo"</string>
+ <string name="call_fax_home" msgid="8342175628887571876">"Llamar a fax de casa"</string>
+ <string name="call_pager" msgid="9003902812293983281">"Llamar a buscapersonas"</string>
+ <string name="call_custom" msgid="7756571794763171802">"Llamar a <xliff:g id="CUSTOM">%s</xliff:g>"</string>
+ <string name="call_car" msgid="3280537320306436445">"Llamar al coche"</string>
+ <string name="call_company_main" msgid="6105120947138711257">"Llamar al teléfono principal de la empresa"</string>
+ <string name="call_mms" msgid="6274041545876221437">"Llamar a MMS"</string>
+ <string name="call_radio" msgid="8296755876398357063">"Llamar a la radio"</string>
+ <string name="sms_home" msgid="7524332261493162995">"Enviar un mensaje de texto a casa"</string>
+ <string name="sms_mobile" msgid="5200107250451030769">"Enviar SMS a móvil"</string>
+ <string name="sms_work" msgid="2269624156655267740">"Enviar SMS a trabajo"</string>
+ <string name="sms_fax_work" msgid="8028189067816907075">"Enviar un mensaje de texto al fax del trabajo"</string>
+ <string name="sms_fax_home" msgid="9204042076306809634">"Enviar un mensaje de texto al fax de casa"</string>
+ <string name="sms_pager" msgid="7730404569637015192">"Enviar un mensaje de texto al buscapersonas"</string>
+ <string name="sms_other" msgid="5131921487474531617">"Enviar un mensaje de texto a otro"</string>
+ <string name="sms_custom" msgid="5932736853732191825">"Enviar un mensaje de texto a <xliff:g id="CUSTOM">%s</xliff:g>"</string>
+ <string name="sms_car" msgid="7444227058437359641">"Enviar un mensaje de texto al coche"</string>
+ <string name="sms_company_main" msgid="118970873419678087">"Enviar un mensaje de texto al teléfono principal de la empresa"</string>
+ <string name="sms_mms" msgid="4069352461380762677">"Enviar un mensaje de texto a MMS"</string>
+ <string name="sms_radio" msgid="3329166673433967820">"Enviar un mensaje de texto a la radio"</string>
+ <string name="email_home" msgid="8573740658148184279">"Enviar email a casa"</string>
+ <string name="email_mobile" msgid="2042889209787989814">"Enviar email al móvil"</string>
+ <string name="email_work" msgid="2807430017302722689">"Enviar email a trabajo"</string>
+ <string name="email_other" msgid="8093933498541795832">"Enviar email a otro"</string>
+ <string name="email_custom" msgid="7548003991586214105">"Correo electrónico <xliff:g id="CUSTOM">%s</xliff:g>"</string>
+ <string name="email" msgid="5668400997660065897">"Correo electrónico"</string>
+ <string name="map_home" msgid="1243547733423343982">"Ver dirección de casa"</string>
+ <string name="map_work" msgid="1360474076921878088">"Ver dirección del trabajo"</string>
+ <string name="map_other" msgid="5560707927535653892">"Ver otras direcciones"</string>
+ <string name="map_custom" msgid="6184363799976265281">"Ver dirección de <xliff:g id="CUSTOM">%s</xliff:g>"</string>
+ <string name="chat_aim" msgid="2588492205291249142">"Chatear con MI de AOL"</string>
+ <string name="chat_msn" msgid="8041633440091073484">"Chatear con Windows Live"</string>
+ <string name="chat_yahoo" msgid="6629211142719943666">"Chatear con Yahoo!"</string>
+ <string name="chat_skype" msgid="1210045020427480566">"Chatear con Skype"</string>
+ <string name="chat_qq" msgid="4294637812847719693">"Chatear con QQ"</string>
+ <string name="chat_gtalk" msgid="981575737258117697">"Chatear con Google Talk"</string>
+ <string name="chat_icq" msgid="8438405386153745775">"Chatear con ICQ"</string>
+ <string name="chat_jabber" msgid="7561444230307829609">"Chatear con Jabber"</string>
+ <string name="postal_street" msgid="8133143961580058972">"Calle"</string>
+ <string name="postal_pobox" msgid="4431938829180269821">"Apartado postal"</string>
+ <string name="postal_neighborhood" msgid="1450783874558956739">"Vecindario"</string>
+ <string name="postal_city" msgid="6597491300084895548">"Ciudad"</string>
+ <string name="postal_region" msgid="6045263193478437672">"Provincia/Estado"</string>
+ <string name="postal_postcode" msgid="572136414136673751">"Código postal"</string>
+ <string name="postal_country" msgid="7638264508416368690">"País"</string>
+ <string name="name_given" msgid="1687286314106019813">"Nombre de pila"</string>
+ <string name="name_family" msgid="3416695586119999058">"Apellidos"</string>
+ <string name="name_prefix" msgid="59756378548779822">"Prefijo del nombre"</string>
+ <string name="name_middle" msgid="8467433655992690326">"Segundo nombre"</string>
+ <string name="name_suffix" msgid="3855278445375651441">"Sufijo del nombre"</string>
+ <string name="name_phonetic_given" msgid="6853570431394449191">"Nombre de pila fonético"</string>
+ <string name="name_phonetic_middle" msgid="8643721493320405200">"Segundo nombre fonético"</string>
+ <string name="name_phonetic_family" msgid="462095502140180305">"Apellido fonético"</string>
</resources>
diff --git a/res/values-fr/config.xml b/res/values-fr/config.xml
deleted file mode 100644
index 07b3c7e..0000000
--- a/res/values-fr/config.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for config_export_vcard_type (9145748078116159716) -->
- <skip />
- <string name="config_export_dir" msgid="222182743639478636">"/sdcard"</string>
- <!-- no translation found for config_export_file_prefix (3022868431158658122) -->
- <skip />
- <!-- no translation found for config_export_file_suffix (16505844221142195) -->
- <skip />
- <string name="config_export_file_extension" msgid="1758878818611339161">"vcf"</string>
- <!-- no translation found for config_export_extensions_to_consider (5095044502091950623) -->
- <skip />
-</resources>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index e22a29a..0aba70f 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -40,11 +40,24 @@
<string name="menu_showBarcode" msgid="309973637178814132">"Afficher le code-barres"</string>
<string name="menu_editContact" msgid="3452858480713561396">"Modifier le contact"</string>
<string name="menu_deleteContact" msgid="1916555454274101750">"Supprimer le contact"</string>
- <string name="menu_call" msgid="7359207953236681606">"Appel"</string>
- <string name="menu_sendSMS" msgid="5917289601666766617">"Envoyer un SMS/MMS"</string>
+ <!-- no translation found for menu_call (3992595586042260618) -->
+ <skip />
+ <!-- no translation found for menu_sendSMS (5535886767547006515) -->
+ <skip />
<string name="menu_sendEmail" msgid="7293508859242926187">"Envoyer un e-mail"</string>
<string name="menu_viewAddress" msgid="1814744325763202024">"Adresse sur un plan"</string>
<string name="menu_makeDefaultNumber" msgid="4838759253316649534">"Numéro téléphone par défaut"</string>
+ <string name="menu_makeDefaultEmail" msgid="2599044610375789994">"E-mail par défaut"</string>
+ <string name="menu_splitAggregate" msgid="7402548456225901170">"Scinder"</string>
+ <string name="splitAggregate_title" msgid="2053462872948058798">"Scinder le contact"</string>
+ <string name="contactsSplitMessage" msgid="3439008547775199243">"Contact scindé"</string>
+ <string name="menu_joinAggregate" msgid="5027981918265667970">"Joindre"</string>
+ <string name="titleJoinAggregate" msgid="6970566008563147202">"Joindre le contact"</string>
+ <string name="separatorJoinAggregateSuggestions" msgid="5494823322479834017">"Suggestions"</string>
+ <string name="separatorJoinAggregateAll" msgid="7939932265026181043">"Tous les contacts"</string>
+ <string name="contactsJoinedMessage" msgid="7208148163607047389">"Contacts joints"</string>
+ <string name="menu_contactOptions" msgid="1957061455705020617">"Options"</string>
+ <string name="contactOptionsTitle" msgid="8259347644090700915">"Options"</string>
<string name="deleteConfirmation_title" msgid="6394309508930335204">"Supprimer"</string>
<string name="deleteConfirmation" msgid="811706994761610640">"Ce contact sera supprimé."</string>
<string name="menu_done" msgid="796017761764190697">"OK"</string>
@@ -55,6 +68,9 @@
<string name="label_phonetic_name" msgid="2288082649573927286">"Prononciation phonétique"</string>
<string name="label_notes" msgid="8337354953278341042">"Notes"</string>
<string name="label_ringtone" msgid="8833166825330686244">"Sonnerie"</string>
+ <string name="label_groups" msgid="7304551384542859026">"Groupes"</string>
+ <string name="group_list" msgid="8583361685440161307">", <xliff:g id="GROUPNAME">%s</xliff:g>"</string>
+ <string name="menu_viewGroup" msgid="1401851715586577551">"Modifier les groupes"</string>
<string name="ghostData_name" msgid="6490954238641157585">"Nom et prénom"</string>
<string name="ghostData_phonetic_name" msgid="7852749081984070902">"Nom phonétique"</string>
<string name="ghostData_company" msgid="5414421120553765775">"Société"</string>
@@ -64,6 +80,7 @@
<string name="ghostData_phone" msgid="6963153888271466620">"Numéro téléphone"</string>
<string name="ghostData_email" msgid="6184537075551565919">"Adresse e-mail"</string>
<string name="ghostData_postal" msgid="652611650594951897">"Adresse postale"</string>
+ <string name="ghostData_group" msgid="4504591380347534114">"Afficher le groupe"</string>
<string name="invalidContactMessage" msgid="5816991830260044593">"Ce contact n\'existe pas."</string>
<string name="pickerNewContactHeader" msgid="7750705279843568147">"Créer un nouveau contact"</string>
<string name="selectLabel" msgid="4255424123394910733">"Sélectionnez un libellé"</string>
@@ -80,7 +97,8 @@
<string name="photoPickerNotFoundText" msgid="431331662154342581">"Aucune image disponible sur le téléphone."</string>
<string name="attachToContact" msgid="8820530304406066714">"Icône de contact"</string>
<string name="customLabelPickerTitle" msgid="1081475101983255212">"Libellé personnalisé"</string>
- <string name="menu_displayGroup" msgid="4603480747214476335">"Afficher les groupes"</string>
+ <string name="menu_displayGroup" msgid="6022511302986343108">"Afficher les groupes"</string>
+ <string name="displayGroups" msgid="2645969886032719891">"Afficher les groupes"</string>
<string name="syncGroupPreference" msgid="9028361137161162861">"Param. sync. groupes"</string>
<string name="importFromSim" msgid="8383900146531125319">"Importer des contacts"</string>
<string name="send_to_voicemail_checkbox" msgid="9001686764070676353">"Renvoyer les appels directement vers la messagerie vocale."</string>
@@ -94,12 +112,23 @@
<string name="select_group_title" msgid="7955698611959835612">"Groupes"</string>
<string name="groupEmpty" msgid="6661950109828194595">"Votre groupe \"<xliff:g id="GROUPNAME">%s</xliff:g>\" est vide."</string>
<string name="showAllGroups" msgid="5164410117611094297">"Tous les contacts"</string>
+ <string name="showFilterPhones" msgid="4184858075465653970">"Uniquement les contacts avec un téléphone"</string>
+ <string name="showFilterPhonesDescrip" msgid="6644443248815191067">"Afficher uniquement les contacts avec un numéro de téléphone"</string>
+ <string name="headerContactGroups" msgid="1451383377487746789">"Groupes de contacts"</string>
+ <plurals name="groupDescrip">
+ <item quantity="other" msgid="3507881585720628389">"<xliff:g id="COUNT">%0$d</xliff:g> contacts"</item>
+ </plurals>
+ <plurals name="groupDescripPhones">
+ <item quantity="other" msgid="3816047547470490208">"<xliff:g id="COUNT_0">%1$d</xliff:g> contacts, dont <xliff:g id="COUNTWITHPHONES">%2$d</xliff:g> avec un téléphone"</item>
+ </plurals>
<string name="syncAllGroups" msgid="7512169081224806890">"Synchroniser tous les contacts"</string>
<string name="groupNameMyContacts" msgid="5696566165170977126">"Mes contacts"</string>
<string name="groupNameWithPhones" msgid="6981588604041120497">"Contacts avec des n° de téléphone"</string>
<string name="starredInAndroid" msgid="6495527538140213440">"Suivis dans Android"</string>
<string name="contactCreatedToast" msgid="8740102129968688060">"Contact créé."</string>
<string name="contactSavedToast" msgid="7152589189385441091">"Contact enregistré."</string>
+ <!-- no translation found for contactSavedErrorToast (9189098776225004666) -->
+ <skip />
<string name="listSeparatorCallNumber" msgid="7115321894439629408">"Composer le numéro"</string>
<string name="listSeparatorCallNumber_edit" msgid="2999167270900823334">"Numéros de téléphone"</string>
<string name="listSeparatorSendSmsMms" msgid="2480944736714739967">"Envoyer un SMS/MMS"</string>
@@ -110,9 +139,11 @@
<string name="listSeparatorMapAddress" msgid="7076401301758190804">"Adresse sur un plan"</string>
<string name="listSeparatorMapAddress_edit" msgid="298711187672067985">"Adresses postales"</string>
<string name="listSeparatorOrganizations" msgid="7514083358440762504">"Organisations"</string>
+ <string name="listSeparatorGroups" msgid="1593940073983422450">"Groupes"</string>
<string name="listSeparatorOtherInformation" msgid="7844959649638482329">"Autres informations"</string>
<string name="listSeparatorOtherInformation_edit" msgid="1326921768011367750">"Autres options"</string>
<string name="listSeparatorMore_edit" msgid="858454837482243176">"Plus"</string>
+ <string name="socialStreamIconLabel" msgid="4367712449555075376">"Social"</string>
<string name="contactsIconLabel" msgid="7666609097606552806">"Contacts"</string>
<string name="contactsFavoritesLabel" msgid="8417039765586853670">"Favoris"</string>
<string name="dialerIconLabel" msgid="7228520966699542850">"Appeler"</string>
@@ -128,6 +159,7 @@
<string name="recentCalls_deleteAll" msgid="6352364392762163704">"Effacer tous les appels"</string>
<string name="recentCalls_empty" msgid="247053222448663107">"Aucun appel."</string>
<string name="imei" msgid="3045126336951684285">"Code IMEI"</string>
+ <string name="meid" msgid="6210568493746275750">"MEID"</string>
<string name="voicemail" msgid="3851469869202611441">"Messagerie vocale"</string>
<string name="unknown" msgid="740067747858270469">"Inconnu"</string>
<string name="private_num" msgid="6374339738119166953">"Numéro privée"</string>
@@ -138,7 +170,14 @@
<string name="simContacts_title" msgid="27341688347689769">"Contacts de carte SIM"</string>
<string name="contactsSyncPlug" msgid="7248276704957313698"><font fgcolor="#ffffffff">"Synchronisez vos contacts Google !"</font>" "\n"Vos contacts seront disponibles, où que vous soyez, dès que vous aurez synchronisé votre téléphone."</string>
<string name="noContactsHelpText" msgid="2249195687463896364">"Vous n\'avez aucun contact."\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>"Nouveau contact"</b></font>" pour créer un nouveau contact ;"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importer des contacts"</b></font>" pour ajouter des contacts à partir de votre carte SIM."\n</li></string>
- <string name="noContactsHelpTextWithSync" msgid="4927701496550314555">"Vous n\'avez aucun contact."\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>"Param. sync. groupes"</b></font>" pour ajouter un contact à partir d\'un nouveau compte Google ou d\'un compte existant ;"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Nouveau contact"</b></font>" pour créer un nouveau contact ;"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importer des contacts"</b></font>" pour ajouter des contacts à partir de votre carte SIM."\n</li></string>
+ <!-- no translation found for noContactsHelpTextWithSync (2179024855526352215) -->
+ <skip />
+ <!-- no translation found for noContactsNoSimHelpText (645951808197910024) -->
+ <skip />
+ <!-- no translation found for noContactsNoSimHelpTextWithSync (511505839941079550) -->
+ <skip />
+ <!-- no translation found for noFavoritesHelpText (1705381563293866510) -->
+ <skip />
<string name="seclectSyncGroups_title" msgid="1235432026231325655">"Sélectionner les groupes à synchroniser"</string>
<string name="liveFolder_all_label" msgid="1552523730090319259">"Tous les contacts"</string>
<string name="liveFolder_favorites_label" msgid="2674341514070517105">"Suivis"</string>
@@ -173,6 +212,7 @@
<string name="favoritesFrquentSeparator" msgid="5007070838253932139">"Fréquemment appelés"</string>
<string name="add_contact_dlg_title" msgid="2789046541229846116">"Ajouter un contact"</string>
<string name="add_contact_dlg_message_fmt" msgid="7986472669444326576">"Ajouter \"<xliff:g id="EMAIL">%s</xliff:g>\" aux contacts ?"</string>
+ <string name="all_tab_label" msgid="4003124364397916826">"Tout"</string>
<string name="description_image_button_one" msgid="1740638037139856139">"un"</string>
<string name="description_image_button_two" msgid="5882638439003731308">"deux"</string>
<string name="description_image_button_three" msgid="8709731759376015180">"trois"</string>
@@ -189,19 +229,27 @@
<string name="no_sdcard_message" msgid="7791906118138538259">"Aucune carte SD n\'a été détectée."</string>
<string name="searching_vcard_title" msgid="4158580043290687793">"Recherche des données VCard"</string>
<string name="select_import_type_title" msgid="2443742794103731022">"À partir de quel support souhaitez-vous importer les contacts ?"</string>
- <string name="import_from_sim" msgid="3362158539070179154">"Carte SIM"</string>
- <string name="import_from_sdcard" msgid="8854793070007530689">"Carte SD"</string>
- <string name="import_all_vcard_string" msgid="2788222288962542415">"Importer tous les fichiers VCard"</string>
- <string name="import_one_vcard_string" msgid="4618119296910253689">"Importer un fichier VCard"</string>
+ <!-- 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 />
+ <string name="import_one_vcard_string" msgid="9059163467020328433">"Importer un fichier vCard"</string>
+ <string name="import_multiple_vcard_string" msgid="3810226492811062392">"Importer plusieurs fichiers vCard"</string>
+ <string name="import_all_vcard_string" msgid="5518136113853448474">"Importer tous les fichiers vCard"</string>
<string name="searching_vcard_message" msgid="2467573749792455605">"Recherche de données VCard sur la VCard"</string>
<string name="scanning_sdcard_failed_title" msgid="2788276113399585924">"Échec de l\'analyse de la carte SD"</string>
<string name="scanning_sdcard_failed_message" msgid="925361244069058117">"Échec de l\'analyse de la carte SD"</string>
- <string name="fail_reason_io_error" msgid="5922864781066136340">"Erreur d\'E/S"</string>
- <string name="fail_reason_vcard_parse_error" msgid="2129476089250836277">"Échec de l\'analyse des données VCard"</string>
+ <string name="fail_reason_io_error" msgid="5949038573678667996">"Erreur d\'E/S (Raison : \"<xliff:g id="FAIL_REASON">%s</xliff:g>\")"</string>
+ <string name="fail_reason_vcard_parse_error" msgid="8877496734269540489">"Échec de l\'analyse des données VCard pour une raison inattendue"</string>
+ <string name="fail_reason_vcard_not_supported_error" msgid="166568606246563552">"É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="1489638537002538332">"Aucun fichier VCard n\'a été trouvé sur la carte SD."</string>
<string name="fail_reason_no_vcard_entry" msgid="3808734131680935043">"Aucune entrée VCard valide n\'a été trouvée pour votre sélection."</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="select_vcard_title" msgid="4615933643517710543">"Sélectionner un fichier VCard"</string>
<string name="select_vcard_message" msgid="7028772212789057858">"Sélectionnez un fichier VCard à importer."</string>
+ <string name="progress_shower_message" msgid="5636525578293752526">"<xliff:g id="ACTION">%s</xliff:g>"\n"<xliff:g id="FILENAME">%s</xliff:g>"</string>
<string name="reading_vcard_title" msgid="430154704397375160">"Lecture des données VCard"</string>
<string name="reading_vcard_message" msgid="7470486051482460267">"Lecture des fichiers VCard"</string>
<string name="importing_vcard_message" msgid="2466665710812588738">"Importation des données VCard"</string>
@@ -224,4 +272,104 @@
<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="1488865103809190124">"Ajouter une pause de 2 s"</string>
+ <string name="add_wait" msgid="8347184715316058465">"Ajouter Attendre"</string>
+ <string name="dial_button_label" msgid="7637725632722605863">"Composer"</string>
+ <!-- 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 />
+ <string name="quickcontact_missing_app" msgid="1181287640680679441">"Aucune application pour gérer cette action"</string>
+ <string name="quickcontact_remember_choice" msgid="2858356075887897116">"Mémoriser ce choix"</string>
+ <string name="quickcontact_missing_name" msgid="7806083820928955853">"Inconnu"</string>
+ <string name="menu_accounts" msgid="8499114602017077970">"Comptes"</string>
+ <!-- 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">"Nom"</string>
+ <string name="nicknameLabelsGroup" msgid="2891682101053358010">"Pseudo"</string>
+ <string name="organizationLabelsGroup" msgid="2478611760751832035">"Organisation"</string>
+ <string name="websiteLabelsGroup" msgid="4202998982804009261">"Site Web"</string>
+ <string name="type_short_home" msgid="7770424864090605384">"D"</string>
+ <string name="type_short_mobile" msgid="1655473281466676216">"M"</string>
+ <string name="type_short_work" msgid="4925330752504537861">"B"</string>
+ <string name="type_short_pager" msgid="2613818970827594238">"T"</string>
+ <string name="type_short_other" msgid="5669407180177236769">"A"</string>
+ <string name="edit_secondary_collapse" msgid="1855127176548440583">"Détails secondaires"</string>
+ <string name="dialog_primary_name" msgid="5521591005692614833">"Nom principal"</string>
+ <string name="dialog_new_contact_account" msgid="9044704073286262197">"Créer un contact sous le compte"</string>
+ <string name="menu_sync_remove" msgid="3266725887008450161">"Supprimer le groupe de synchronisation"</string>
+ <!-- no translation found for dialog_sync_add (8267045393119375803) -->
+ <skip />
+ <!-- no translation found for display_more_groups (2682547080423434170) -->
+ <skip />
+ <string name="display_ungrouped" msgid="4357317661311803984">"(Contacts non groupés)"</string>
+ <string name="display_warn_remove_ungrouped" msgid="2314043155909167610">"Le retrait du groupe \"<xliff:g id="GROUP">%s</xliff:g>\" de la synchronisation entraîne également la suppression de tous les contacts non groupés de la synchronisation."</string>
+ <string name="account_phone" msgid="4025734638492419713">"Téléphone uniquement (sans synchronisation)"</string>
+ <string name="label_email_display_name" msgid="5537802602754309600">"Nom affiché"</string>
+ <string name="call_home" msgid="1990519474420545392">"Appeler domicile"</string>
+ <string name="call_mobile" msgid="7502236805487609178">"Appeler le mobile"</string>
+ <string name="call_work" msgid="5328785911463744028">"Appeler bureau"</string>
+ <string name="call_fax_work" msgid="7467763592359059243">"Composer télécopie (bureau)"</string>
+ <string name="call_fax_home" msgid="8342175628887571876">"Composer fax (domicile)"</string>
+ <string name="call_pager" msgid="9003902812293983281">"Appeler téléavertisseur"</string>
+ <string name="call_custom" msgid="7756571794763171802">"Appeler <xliff:g id="CUSTOM">%s</xliff:g>"</string>
+ <string name="call_car" msgid="3280537320306436445">"Appeler voiture"</string>
+ <string name="call_company_main" msgid="6105120947138711257">"Appeler société (principal)"</string>
+ <string name="call_mms" msgid="6274041545876221437">"Appeler MMS"</string>
+ <string name="call_radio" msgid="8296755876398357063">"Appeler par signal radio"</string>
+ <string name="sms_home" msgid="7524332261493162995">"Envoyer un SMS au domicile"</string>
+ <string name="sms_mobile" msgid="5200107250451030769">"Envoyer un SMS vers mobile"</string>
+ <string name="sms_work" msgid="2269624156655267740">"Envoyer un SMS au bureau"</string>
+ <string name="sms_fax_work" msgid="8028189067816907075">"Envoyer un SMS vers télécopie (bureau)"</string>
+ <string name="sms_fax_home" msgid="9204042076306809634">"Envoyer un SMS vers télécopie (domicile)"</string>
+ <string name="sms_pager" msgid="7730404569637015192">"Envoyer un SMS vers téléavertisseur"</string>
+ <string name="sms_other" msgid="5131921487474531617">"Envoyer un SMS à autre"</string>
+ <string name="sms_custom" msgid="5932736853732191825">"Envoyer un SMS à <xliff:g id="CUSTOM">%s</xliff:g>"</string>
+ <string name="sms_car" msgid="7444227058437359641">"Envoyer un SMS vers voiture"</string>
+ <string name="sms_company_main" msgid="118970873419678087">"Envoyer un SMS à société (principal)"</string>
+ <string name="sms_mms" msgid="4069352461380762677">"Envoyer un MMS"</string>
+ <string name="sms_radio" msgid="3329166673433967820">"Envoyer un SMS vers radio"</string>
+ <string name="email_home" msgid="8573740658148184279">"Envoyer un e-mail au domicile"</string>
+ <string name="email_mobile" msgid="2042889209787989814">"Envoyer un e-mail sur le mobile"</string>
+ <string name="email_work" msgid="2807430017302722689">"Envoyer un e-mail au bureau"</string>
+ <string name="email_other" msgid="8093933498541795832">"Envoyer un e-mail à autre adresse"</string>
+ <string name="email_custom" msgid="7548003991586214105">"Envoyer un e-mail à <xliff:g id="CUSTOM">%s</xliff:g>"</string>
+ <string name="email" msgid="5668400997660065897">"Envoyer un e-mail"</string>
+ <string name="map_home" msgid="1243547733423343982">"Afficher l\'adresse personnelle"</string>
+ <string name="map_work" msgid="1360474076921878088">"Afficher l\'adresse professionnelle"</string>
+ <string name="map_other" msgid="5560707927535653892">"Afficher autre adresse"</string>
+ <string name="map_custom" msgid="6184363799976265281">"Afficher adresse <xliff:g id="CUSTOM">%s</xliff:g>"</string>
+ <string name="chat_aim" msgid="2588492205291249142">"Chatter via AIM"</string>
+ <string name="chat_msn" msgid="8041633440091073484">"Chatter via Windows Live"</string>
+ <string name="chat_yahoo" msgid="6629211142719943666">"Chatter via Yahoo"</string>
+ <string name="chat_skype" msgid="1210045020427480566">"Chatter via Skype"</string>
+ <string name="chat_qq" msgid="4294637812847719693">"Chatter via QQ"</string>
+ <string name="chat_gtalk" msgid="981575737258117697">"Chatter via Google Talk"</string>
+ <string name="chat_icq" msgid="8438405386153745775">"Chatter via ICQ"</string>
+ <string name="chat_jabber" msgid="7561444230307829609">"Chatter via Jabber"</string>
+ <string name="postal_street" msgid="8133143961580058972">"Rue"</string>
+ <string name="postal_pobox" msgid="4431938829180269821">"Boîte postale"</string>
+ <string name="postal_neighborhood" msgid="1450783874558956739">"Voisinage"</string>
+ <string name="postal_city" msgid="6597491300084895548">"Ville"</string>
+ <string name="postal_region" msgid="6045263193478437672">"État"</string>
+ <string name="postal_postcode" msgid="572136414136673751">"Code postal"</string>
+ <string name="postal_country" msgid="7638264508416368690">"Pays"</string>
+ <string name="name_given" msgid="1687286314106019813">"Prénom"</string>
+ <string name="name_family" msgid="3416695586119999058">"Nom de famille"</string>
+ <string name="name_prefix" msgid="59756378548779822">"Préfixe du nom"</string>
+ <string name="name_middle" msgid="8467433655992690326">"2e prénom"</string>
+ <string name="name_suffix" msgid="3855278445375651441">"Suffixe du nom"</string>
+ <string name="name_phonetic_given" msgid="6853570431394449191">"Phonétique du prénom"</string>
+ <string name="name_phonetic_middle" msgid="8643721493320405200">"Phonétique 2e prénom"</string>
+ <string name="name_phonetic_family" msgid="462095502140180305">"Phonétique du nom de famille"</string>
</resources>
diff --git a/res/values-it/config.xml b/res/values-it/config.xml
deleted file mode 100644
index ccc13dd..0000000
--- a/res/values-it/config.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for config_export_vcard_type (9145748078116159716) -->
- <skip />
- <string name="config_export_dir" msgid="222182743639478636">"/schedasd"</string>
- <!-- no translation found for config_export_file_prefix (3022868431158658122) -->
- <skip />
- <!-- no translation found for config_export_file_suffix (16505844221142195) -->
- <skip />
- <string name="config_export_file_extension" msgid="1758878818611339161">"vcf"</string>
- <!-- no translation found for config_export_extensions_to_consider (5095044502091950623) -->
- <skip />
-</resources>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index 45a2395..9e28302 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -40,11 +40,24 @@
<string name="menu_showBarcode" msgid="309973637178814132">"Mostra codice a barre"</string>
<string name="menu_editContact" msgid="3452858480713561396">"Modifica contatto"</string>
<string name="menu_deleteContact" msgid="1916555454274101750">"Elimina contatto"</string>
- <string name="menu_call" msgid="7359207953236681606">"Chiama"</string>
- <string name="menu_sendSMS" msgid="5917289601666766617">"Invia SMS/MMS"</string>
+ <!-- no translation found for menu_call (3992595586042260618) -->
+ <skip />
+ <!-- no translation found for menu_sendSMS (5535886767547006515) -->
+ <skip />
<string name="menu_sendEmail" msgid="7293508859242926187">"Invia email"</string>
<string name="menu_viewAddress" msgid="1814744325763202024">"Indirizzo su mappa"</string>
<string name="menu_makeDefaultNumber" msgid="4838759253316649534">"Rendi numero predefinito"</string>
+ <string name="menu_makeDefaultEmail" msgid="2599044610375789994">"Rendi ind. email predefinito"</string>
+ <string name="menu_splitAggregate" msgid="7402548456225901170">"Dividi"</string>
+ <string name="splitAggregate_title" msgid="2053462872948058798">"Dividi contatto"</string>
+ <string name="contactsSplitMessage" msgid="3439008547775199243">"Contatto diviso"</string>
+ <string name="menu_joinAggregate" msgid="5027981918265667970">"Unisci"</string>
+ <string name="titleJoinAggregate" msgid="6970566008563147202">"Unisci contatto"</string>
+ <string name="separatorJoinAggregateSuggestions" msgid="5494823322479834017">"Suggerimenti"</string>
+ <string name="separatorJoinAggregateAll" msgid="7939932265026181043">"Tutti i contatti"</string>
+ <string name="contactsJoinedMessage" msgid="7208148163607047389">"Contatti uniti"</string>
+ <string name="menu_contactOptions" msgid="1957061455705020617">"Opzioni"</string>
+ <string name="contactOptionsTitle" msgid="8259347644090700915">"Opzioni"</string>
<string name="deleteConfirmation_title" msgid="6394309508930335204">"Elimina"</string>
<string name="deleteConfirmation" msgid="811706994761610640">"Il contatto verrà eliminato."</string>
<string name="menu_done" msgid="796017761764190697">"Salva"</string>
@@ -55,6 +68,9 @@
<string name="label_phonetic_name" msgid="2288082649573927286">"Fonetica"</string>
<string name="label_notes" msgid="8337354953278341042">"Note"</string>
<string name="label_ringtone" msgid="8833166825330686244">"Suoneria"</string>
+ <string name="label_groups" msgid="7304551384542859026">"Gruppi"</string>
+ <string name="group_list" msgid="8583361685440161307">", <xliff:g id="GROUPNAME">%s</xliff:g>"</string>
+ <string name="menu_viewGroup" msgid="1401851715586577551">"Modifica gruppi"</string>
<string name="ghostData_name" msgid="6490954238641157585">"Nome e cognome"</string>
<string name="ghostData_phonetic_name" msgid="7852749081984070902">"Nome fonetico"</string>
<string name="ghostData_company" msgid="5414421120553765775">"Società"</string>
@@ -64,6 +80,7 @@
<string name="ghostData_phone" msgid="6963153888271466620">"Numero di telefono"</string>
<string name="ghostData_email" msgid="6184537075551565919">"Indirizzo email"</string>
<string name="ghostData_postal" msgid="652611650594951897">"Indirizzo postale"</string>
+ <string name="ghostData_group" msgid="4504591380347534114">"Visualizza gruppo"</string>
<string name="invalidContactMessage" msgid="5816991830260044593">"Il contatto non esiste."</string>
<string name="pickerNewContactHeader" msgid="7750705279843568147">"Crea nuovo contatto"</string>
<string name="selectLabel" msgid="4255424123394910733">"Seleziona etichetta"</string>
@@ -80,7 +97,8 @@
<string name="photoPickerNotFoundText" msgid="431331662154342581">"Nessuna foto disponibile."</string>
<string name="attachToContact" msgid="8820530304406066714">"Icona del contatto"</string>
<string name="customLabelPickerTitle" msgid="1081475101983255212">"Nome etichetta personalizzata"</string>
- <string name="menu_displayGroup" msgid="4603480747214476335">"Visualizza gruppo"</string>
+ <string name="menu_displayGroup" msgid="6022511302986343108">"Visualizza gruppi"</string>
+ <string name="displayGroups" msgid="2645969886032719891">"Visualizza gruppi"</string>
<string name="syncGroupPreference" msgid="9028361137161162861">"Modifica sincr. gruppi"</string>
<string name="importFromSim" msgid="8383900146531125319">"Importa contatti"</string>
<string name="send_to_voicemail_checkbox" msgid="9001686764070676353">"Inoltra chiamate direttamente alla segreteria"</string>
@@ -94,12 +112,23 @@
<string name="select_group_title" msgid="7955698611959835612">"Gruppi"</string>
<string name="groupEmpty" msgid="6661950109828194595">"Il gruppo \"<xliff:g id="GROUPNAME">%s</xliff:g>\" è vuoto."</string>
<string name="showAllGroups" msgid="5164410117611094297">"Tutti i contatti"</string>
+ <string name="showFilterPhones" msgid="4184858075465653970">"Solo contatti con numeri di telefono"</string>
+ <string name="showFilterPhonesDescrip" msgid="6644443248815191067">"Visualizza solo i contatti con numeri di telefono"</string>
+ <string name="headerContactGroups" msgid="1451383377487746789">"Gruppi di contatti"</string>
+ <plurals name="groupDescrip">
+ <item quantity="other" msgid="3507881585720628389">"<xliff:g id="COUNT">%0$d</xliff:g> contatti"</item>
+ </plurals>
+ <plurals name="groupDescripPhones">
+ <item quantity="other" msgid="3816047547470490208">"<xliff:g id="COUNT_0">%1$d</xliff:g> contatti, <xliff:g id="COUNTWITHPHONES">%2$d</xliff:g> con numeri di telefono"</item>
+ </plurals>
<string name="syncAllGroups" msgid="7512169081224806890">"Sincronizza tutti i contatti"</string>
<string name="groupNameMyContacts" msgid="5696566165170977126">"I miei contatti"</string>
<string name="groupNameWithPhones" msgid="6981588604041120497">"Contatti con numeri di telefono"</string>
<string name="starredInAndroid" msgid="6495527538140213440">"Speciali in Android"</string>
<string name="contactCreatedToast" msgid="8740102129968688060">"Contatto creato."</string>
<string name="contactSavedToast" msgid="7152589189385441091">"Contatto salvato."</string>
+ <!-- no translation found for contactSavedErrorToast (9189098776225004666) -->
+ <skip />
<string name="listSeparatorCallNumber" msgid="7115321894439629408">"Componi numero"</string>
<string name="listSeparatorCallNumber_edit" msgid="2999167270900823334">"Numeri di telefono"</string>
<string name="listSeparatorSendSmsMms" msgid="2480944736714739967">"Invia SMS/MMS"</string>
@@ -110,9 +139,11 @@
<string name="listSeparatorMapAddress" msgid="7076401301758190804">"Indirizzo su mappa"</string>
<string name="listSeparatorMapAddress_edit" msgid="298711187672067985">"Indirizzi postali"</string>
<string name="listSeparatorOrganizations" msgid="7514083358440762504">"Organizzazioni"</string>
+ <string name="listSeparatorGroups" msgid="1593940073983422450">"Gruppi"</string>
<string name="listSeparatorOtherInformation" msgid="7844959649638482329">"Altre informazioni"</string>
<string name="listSeparatorOtherInformation_edit" msgid="1326921768011367750">"Altre opzioni"</string>
<string name="listSeparatorMore_edit" msgid="858454837482243176">"Altro"</string>
+ <string name="socialStreamIconLabel" msgid="4367712449555075376">"Sociale"</string>
<string name="contactsIconLabel" msgid="7666609097606552806">"Contatti"</string>
<string name="contactsFavoritesLabel" msgid="8417039765586853670">"Preferiti"</string>
<string name="dialerIconLabel" msgid="7228520966699542850">"Telefono"</string>
@@ -128,6 +159,7 @@
<string name="recentCalls_deleteAll" msgid="6352364392762163704">"Cancella registro chiamate"</string>
<string name="recentCalls_empty" msgid="247053222448663107">"Il registro chiamate è vuoto."</string>
<string name="imei" msgid="3045126336951684285">"IMEI"</string>
+ <string name="meid" msgid="6210568493746275750">"MEID"</string>
<string name="voicemail" msgid="3851469869202611441">"Segreteria"</string>
<string name="unknown" msgid="740067747858270469">"Sconosciuto"</string>
<string name="private_num" msgid="6374339738119166953">"Numero privato"</string>
@@ -138,7 +170,14 @@
<string name="simContacts_title" msgid="27341688347689769">"Contatti SIM"</string>
<string name="contactsSyncPlug" msgid="7248276704957313698"><font fgcolor="#ffffffff">"Sincronizza i tuoi contatti Google."</font>" "\n"Dopo la sincronizzazione con il telefono, i tuoi contatti saranno sempre a tua disposizione."</string>
<string name="noContactsHelpText" msgid="2249195687463896364">"Non è presente alcun contatto."\n\n"Per aggiungere contatti, premi "<font fgcolor="#ffffffff"><b>"Menu"</b></font>" e seleziona:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Nuovo contatto"</b></font>" per creare un contatto nuovo"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importa contatti"</b></font>" per aggiungere contatti dalla SIM"\n</li></string>
- <string name="noContactsHelpTextWithSync" msgid="4927701496550314555">"Non è presente alcun contatto."\n\n"Per aggiungere contatti, premi "<font fgcolor="#ffffffff"><b>" Menu"</b></font>" e seleziona:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Modifica sincr. gruppi"</b></font>" per aggiungere contatti da un account Google nuovo o esistente"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Nuovo contatto"</b></font>" per creare un contatto nuovo"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importa contatti"</b></font>" per aggiungere contatti dalla SIM"\n</li></string>
+ <!-- no translation found for noContactsHelpTextWithSync (2179024855526352215) -->
+ <skip />
+ <!-- no translation found for noContactsNoSimHelpText (645951808197910024) -->
+ <skip />
+ <!-- no translation found for noContactsNoSimHelpTextWithSync (511505839941079550) -->
+ <skip />
+ <!-- no translation found for noFavoritesHelpText (1705381563293866510) -->
+ <skip />
<string name="seclectSyncGroups_title" msgid="1235432026231325655">"Seleziona i gruppi da sincronizzare"</string>
<string name="liveFolder_all_label" msgid="1552523730090319259">"Tutti i contatti"</string>
<string name="liveFolder_favorites_label" msgid="2674341514070517105">"Speciali"</string>
@@ -173,6 +212,7 @@
<string name="favoritesFrquentSeparator" msgid="5007070838253932139">"Numeri più chiamati"</string>
<string name="add_contact_dlg_title" msgid="2789046541229846116">"Aggiungi contatto"</string>
<string name="add_contact_dlg_message_fmt" msgid="7986472669444326576">"Aggiungere \"<xliff:g id="EMAIL">%s</xliff:g>\" ai contatti?"</string>
+ <string name="all_tab_label" msgid="4003124364397916826">"Tutti"</string>
<string name="description_image_button_one" msgid="1740638037139856139">"uno"</string>
<string name="description_image_button_two" msgid="5882638439003731308">"due"</string>
<string name="description_image_button_three" msgid="8709731759376015180">"tre"</string>
@@ -189,19 +229,27 @@
<string name="no_sdcard_message" msgid="7791906118138538259">"Nessuna scheda SD rilevata"</string>
<string name="searching_vcard_title" msgid="4158580043290687793">"Ricerca VCard"</string>
<string name="select_import_type_title" msgid="2443742794103731022">"Da dove desideri importare i contatti?"</string>
- <string name="import_from_sim" msgid="3362158539070179154">"Scheda SIM"</string>
- <string name="import_from_sdcard" msgid="8854793070007530689">"Scheda SD"</string>
- <string name="import_all_vcard_string" msgid="2788222288962542415">"Importa tutti i file VCard"</string>
- <string name="import_one_vcard_string" msgid="4618119296910253689">"Importa un file VCard"</string>
+ <!-- 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 />
+ <string name="import_one_vcard_string" msgid="9059163467020328433">"Importa un file vCard"</string>
+ <string name="import_multiple_vcard_string" msgid="3810226492811062392">"Importa più file vCard"</string>
+ <string name="import_all_vcard_string" msgid="5518136113853448474">"Importa tutti i file vCard"</string>
<string name="searching_vcard_message" msgid="2467573749792455605">"Ricerca dati VCard su VCard"</string>
<string name="scanning_sdcard_failed_title" msgid="2788276113399585924">"Analisi scheda SD non riuscita"</string>
<string name="scanning_sdcard_failed_message" msgid="925361244069058117">"Analisi scheda SD non riuscita"</string>
- <string name="fail_reason_io_error" msgid="5922864781066136340">"Errore I/O"</string>
- <string name="fail_reason_vcard_parse_error" msgid="2129476089250836277">"Analisi VCard non riuscita"</string>
+ <string name="fail_reason_io_error" msgid="5949038573678667996">"Errore I/O (Motivo: \"<xliff:g id="FAIL_REASON">%s</xliff:g>\")"</string>
+ <string name="fail_reason_vcard_parse_error" msgid="8877496734269540489">"Impossibile analizzare la VCard per motivi imprevisti"</string>
+ <string name="fail_reason_vcard_not_supported_error" msgid="166568606246563552">"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="1489638537002538332">"Nessun file VCard trovato sulla scheda SD"</string>
<string name="fail_reason_no_vcard_entry" msgid="3808734131680935043">"Nessuna voce VCard valida trovata per la tua selezione"</string>
+ <string name="fail_reason_failed_to_read_files" msgid="3659521123567134029">"Impossibile importare uno o più file (%s)."</string>
<string name="select_vcard_title" msgid="4615933643517710543">"Seleziona file VCard"</string>
<string name="select_vcard_message" msgid="7028772212789057858">"Seleziona un file VCard da importare"</string>
+ <string name="progress_shower_message" msgid="5636525578293752526">"<xliff:g id="ACTION">%s</xliff:g>"\n"<xliff:g id="FILENAME">%s</xliff:g>"</string>
<string name="reading_vcard_title" msgid="430154704397375160">"Lettura VCard"</string>
<string name="reading_vcard_message" msgid="7470486051482460267">"Lettura file VCard"</string>
<string name="importing_vcard_message" msgid="2466665710812588738">"Importazione dati VCard"</string>
@@ -224,4 +272,104 @@
<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="1488865103809190124">"Aggiungi Pausa 2 sec"</string>
+ <string name="add_wait" msgid="8347184715316058465">"Aggiungi Attesa"</string>
+ <string name="dial_button_label" msgid="7637725632722605863">"Componi"</string>
+ <!-- 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 />
+ <string name="quickcontact_missing_app" msgid="1181287640680679441">"Nessuna applicazione trovata in grado di gestire questa azione"</string>
+ <string name="quickcontact_remember_choice" msgid="2858356075887897116">"Memorizza questa scelta"</string>
+ <string name="quickcontact_missing_name" msgid="7806083820928955853">"Sconosciuto"</string>
+ <string name="menu_accounts" msgid="8499114602017077970">"Account"</string>
+ <!-- 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">"Nome"</string>
+ <string name="nicknameLabelsGroup" msgid="2891682101053358010">"Nickname"</string>
+ <string name="organizationLabelsGroup" msgid="2478611760751832035">"Organizzazione"</string>
+ <string name="websiteLabelsGroup" msgid="4202998982804009261">"Sito web"</string>
+ <string name="type_short_home" msgid="7770424864090605384">"H"</string>
+ <string name="type_short_mobile" msgid="1655473281466676216">"M"</string>
+ <string name="type_short_work" msgid="4925330752504537861">"W"</string>
+ <string name="type_short_pager" msgid="2613818970827594238">"P"</string>
+ <string name="type_short_other" msgid="5669407180177236769">"O"</string>
+ <string name="edit_secondary_collapse" msgid="1855127176548440583">"Dettagli secondari"</string>
+ <string name="dialog_primary_name" msgid="5521591005692614833">"Nome principale"</string>
+ <string name="dialog_new_contact_account" msgid="9044704073286262197">"Crea contatto sotto account"</string>
+ <string name="menu_sync_remove" msgid="3266725887008450161">"Rimuovi gruppo sinc."</string>
+ <!-- no translation found for dialog_sync_add (8267045393119375803) -->
+ <skip />
+ <!-- no translation found for display_more_groups (2682547080423434170) -->
+ <skip />
+ <string name="display_ungrouped" msgid="4357317661311803984">"(Contatti separati)"</string>
+ <string name="display_warn_remove_ungrouped" msgid="2314043155909167610">"Se rimuovi \"<xliff:g id="GROUP">%s</xliff:g>\" dalla sincronizzazione, verranno rimossi anche tutti i contatti separati."</string>
+ <string name="account_phone" msgid="4025734638492419713">"Solo su telefono (non sincronizzato)"</string>
+ <string name="label_email_display_name" msgid="5537802602754309600">"Nome visualizzato"</string>
+ <string name="call_home" msgid="1990519474420545392">"Chiama n. casa"</string>
+ <string name="call_mobile" msgid="7502236805487609178">"Chiama n. cellulare"</string>
+ <string name="call_work" msgid="5328785911463744028">"Chiama n. ufficio"</string>
+ <string name="call_fax_work" msgid="7467763592359059243">"Chiama n. fax ufficio"</string>
+ <string name="call_fax_home" msgid="8342175628887571876">"Chiama n. fax casa"</string>
+ <string name="call_pager" msgid="9003902812293983281">"Chiama n. cercapersone"</string>
+ <string name="call_custom" msgid="7756571794763171802">"Chiama n. <xliff:g id="CUSTOM">%s</xliff:g>"</string>
+ <string name="call_car" msgid="3280537320306436445">"Chiama n. automobile"</string>
+ <string name="call_company_main" msgid="6105120947138711257">"Chiama n. azienda, principale"</string>
+ <string name="call_mms" msgid="6274041545876221437">"Chiama n. MMS"</string>
+ <string name="call_radio" msgid="8296755876398357063">"Chiama n. radio"</string>
+ <string name="sms_home" msgid="7524332261493162995">"Invia SMS a n. casa"</string>
+ <string name="sms_mobile" msgid="5200107250451030769">"Invia SMS a n. cellulare"</string>
+ <string name="sms_work" msgid="2269624156655267740">"Invia SMS a n. ufficio"</string>
+ <string name="sms_fax_work" msgid="8028189067816907075">"Invia SMS a n. fax ufficio"</string>
+ <string name="sms_fax_home" msgid="9204042076306809634">"Invia SMS a n. fax casa"</string>
+ <string name="sms_pager" msgid="7730404569637015192">"Invia SMS a n. cercapersone"</string>
+ <string name="sms_other" msgid="5131921487474531617">"Invia SMS ad altro"</string>
+ <string name="sms_custom" msgid="5932736853732191825">"Invia SMS a n. <xliff:g id="CUSTOM">%s</xliff:g>"</string>
+ <string name="sms_car" msgid="7444227058437359641">"Invia SMS a n. automobile"</string>
+ <string name="sms_company_main" msgid="118970873419678087">"Invia SMS a n. azienda, principale"</string>
+ <string name="sms_mms" msgid="4069352461380762677">"Invia SMS a n. MMS"</string>
+ <string name="sms_radio" msgid="3329166673433967820">"Invia SMS a n. segnale cellulare"</string>
+ <string name="email_home" msgid="8573740658148184279">"Invia email a ind. casa"</string>
+ <string name="email_mobile" msgid="2042889209787989814">"Invia email a ind. cellulare"</string>
+ <string name="email_work" msgid="2807430017302722689">"Invia email a ind. ufficio"</string>
+ <string name="email_other" msgid="8093933498541795832">"Invia email ad altro"</string>
+ <string name="email_custom" msgid="7548003991586214105">"Invia email a ind. <xliff:g id="CUSTOM">%s</xliff:g>"</string>
+ <string name="email" msgid="5668400997660065897">"Email"</string>
+ <string name="map_home" msgid="1243547733423343982">"Visualizza indirizzo di casa"</string>
+ <string name="map_work" msgid="1360474076921878088">"Visualizza indirizzo ufficio"</string>
+ <string name="map_other" msgid="5560707927535653892">"Visualizza altro indirizzo"</string>
+ <string name="map_custom" msgid="6184363799976265281">"Visualizza indirizzo <xliff:g id="CUSTOM">%s</xliff:g>"</string>
+ <string name="chat_aim" msgid="2588492205291249142">"Chatta tramite AIM"</string>
+ <string name="chat_msn" msgid="8041633440091073484">"Chatta tramite Windows Live"</string>
+ <string name="chat_yahoo" msgid="6629211142719943666">"Chatta tramite Yahoo"</string>
+ <string name="chat_skype" msgid="1210045020427480566">"Chatta tramite Skype"</string>
+ <string name="chat_qq" msgid="4294637812847719693">"Chatta tramite QQ"</string>
+ <string name="chat_gtalk" msgid="981575737258117697">"Chatta tramite Google Talk"</string>
+ <string name="chat_icq" msgid="8438405386153745775">"Chatta tramite ICQ"</string>
+ <string name="chat_jabber" msgid="7561444230307829609">"Chatta tramite Jabber"</string>
+ <string name="postal_street" msgid="8133143961580058972">"Indirizzo postale"</string>
+ <string name="postal_pobox" msgid="4431938829180269821">"Casella postale"</string>
+ <string name="postal_neighborhood" msgid="1450783874558956739">"Zona"</string>
+ <string name="postal_city" msgid="6597491300084895548">"Città"</string>
+ <string name="postal_region" msgid="6045263193478437672">"Provincia"</string>
+ <string name="postal_postcode" msgid="572136414136673751">"Codice postale"</string>
+ <string name="postal_country" msgid="7638264508416368690">"Paese"</string>
+ <string name="name_given" msgid="1687286314106019813">"Nome fornito"</string>
+ <string name="name_family" msgid="3416695586119999058">"Cognome"</string>
+ <string name="name_prefix" msgid="59756378548779822">"Prefisso nome"</string>
+ <string name="name_middle" msgid="8467433655992690326">"Secondo nome"</string>
+ <string name="name_suffix" msgid="3855278445375651441">"Suffisso nome"</string>
+ <string name="name_phonetic_given" msgid="6853570431394449191">"Nome fonetico fornito"</string>
+ <string name="name_phonetic_middle" msgid="8643721493320405200">"Secondo nome fonetico"</string>
+ <string name="name_phonetic_family" msgid="462095502140180305">"Cognome fonetico"</string>
</resources>
diff --git a/res/values-ja/config.xml b/res/values-ja/config.xml
deleted file mode 100644
index 07b3c7e..0000000
--- a/res/values-ja/config.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for config_export_vcard_type (9145748078116159716) -->
- <skip />
- <string name="config_export_dir" msgid="222182743639478636">"/sdcard"</string>
- <!-- no translation found for config_export_file_prefix (3022868431158658122) -->
- <skip />
- <!-- no translation found for config_export_file_suffix (16505844221142195) -->
- <skip />
- <string name="config_export_file_extension" msgid="1758878818611339161">"vcf"</string>
- <!-- no translation found for config_export_extensions_to_consider (5095044502091950623) -->
- <skip />
-</resources>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index 301bc4a..b6abc9a 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -40,11 +40,24 @@
<string name="menu_showBarcode" msgid="309973637178814132">"バーコードを表示"</string>
<string name="menu_editContact" msgid="3452858480713561396">"連絡先を編集"</string>
<string name="menu_deleteContact" msgid="1916555454274101750">"連絡先を削除"</string>
- <string name="menu_call" msgid="7359207953236681606">"発信"</string>
- <string name="menu_sendSMS" msgid="5917289601666766617">"SMSを送信"</string>
+ <!-- no translation found for menu_call (3992595586042260618) -->
+ <skip />
+ <!-- no translation found for menu_sendSMS (5535886767547006515) -->
+ <skip />
<string name="menu_sendEmail" msgid="7293508859242926187">"メールを送信"</string>
<string name="menu_viewAddress" msgid="1814744325763202024">"地図でみる"</string>
<string name="menu_makeDefaultNumber" msgid="4838759253316649534">"メインの番号に設定する"</string>
+ <string name="menu_makeDefaultEmail" msgid="2599044610375789994">"既定のメールに設定"</string>
+ <string name="menu_splitAggregate" msgid="7402548456225901170">"分割"</string>
+ <string name="splitAggregate_title" msgid="2053462872948058798">"連絡先を分割"</string>
+ <string name="contactsSplitMessage" msgid="3439008547775199243">"連絡先を分割しました"</string>
+ <string name="menu_joinAggregate" msgid="5027981918265667970">"統合"</string>
+ <string name="titleJoinAggregate" msgid="6970566008563147202">"連絡先を統合"</string>
+ <string name="separatorJoinAggregateSuggestions" msgid="5494823322479834017">"候補"</string>
+ <string name="separatorJoinAggregateAll" msgid="7939932265026181043">"すべての連絡先"</string>
+ <string name="contactsJoinedMessage" msgid="7208148163607047389">"連絡先を結合しました"</string>
+ <string name="menu_contactOptions" msgid="1957061455705020617">"オプション"</string>
+ <string name="contactOptionsTitle" msgid="8259347644090700915">"オプション"</string>
<string name="deleteConfirmation_title" msgid="6394309508930335204">"削除"</string>
<string name="deleteConfirmation" msgid="811706994761610640">"この連絡先を削除します。"</string>
<string name="menu_done" msgid="796017761764190697">"完了"</string>
@@ -55,6 +68,9 @@
<string name="label_phonetic_name" msgid="2288082649573927286">"読み"</string>
<string name="label_notes" msgid="8337354953278341042">"メモ"</string>
<string name="label_ringtone" msgid="8833166825330686244">"着信音"</string>
+ <string name="label_groups" msgid="7304551384542859026">"グループ"</string>
+ <string name="group_list" msgid="8583361685440161307">"、<xliff:g id="GROUPNAME">%s</xliff:g>"</string>
+ <string name="menu_viewGroup" msgid="1401851715586577551">"グループを編集"</string>
<string name="ghostData_name" msgid="6490954238641157585">"名前"</string>
<string name="ghostData_phonetic_name" msgid="7852749081984070902">"よみがな"</string>
<string name="ghostData_company" msgid="5414421120553765775">"会社"</string>
@@ -64,6 +80,7 @@
<string name="ghostData_phone" msgid="6963153888271466620">"電話番号"</string>
<string name="ghostData_email" msgid="6184537075551565919">"メールアドレス"</string>
<string name="ghostData_postal" msgid="652611650594951897">"住所"</string>
+ <string name="ghostData_group" msgid="4504591380347534114">"表示グループ"</string>
<string name="invalidContactMessage" msgid="5816991830260044593">"この連絡先は削除されました"</string>
<string name="pickerNewContactHeader" msgid="7750705279843568147">"連絡先を新規登録"</string>
<string name="selectLabel" msgid="4255424123394910733">"選択してください"</string>
@@ -80,7 +97,8 @@
<string name="photoPickerNotFoundText" msgid="431331662154342581">"画像がありません。"</string>
<string name="attachToContact" msgid="8820530304406066714">"連絡先のアイコン"</string>
<string name="customLabelPickerTitle" msgid="1081475101983255212">"新しいラベル名"</string>
- <string name="menu_displayGroup" msgid="4603480747214476335">"グループを表示"</string>
+ <string name="menu_displayGroup" msgid="6022511302986343108">"グループを表示"</string>
+ <string name="displayGroups" msgid="2645969886032719891">"グループを表示"</string>
<string name="syncGroupPreference" msgid="9028361137161162861">"同期する範囲"</string>
<string name="importFromSim" msgid="8383900146531125319">"連絡先をインポート"</string>
<string name="send_to_voicemail_checkbox" msgid="9001686764070676353">"ボイスメールに自動転送する"</string>
@@ -94,12 +112,23 @@
<string name="select_group_title" msgid="7955698611959835612">"グループ"</string>
<string name="groupEmpty" msgid="6661950109828194595">"「<xliff:g id="GROUPNAME">%s</xliff:g>」グループには何も登録されていません。"</string>
<string name="showAllGroups" msgid="5164410117611094297">"すべての連絡先"</string>
+ <string name="showFilterPhones" msgid="4184858075465653970">"電話番号のある連絡先のみ"</string>
+ <string name="showFilterPhonesDescrip" msgid="6644443248815191067">"電話番号のある連絡先のみ表示"</string>
+ <string name="headerContactGroups" msgid="1451383377487746789">"連絡先グループ"</string>
+ <plurals name="groupDescrip">
+ <item quantity="other" msgid="3507881585720628389">"<xliff:g id="COUNT">%0$d</xliff:g>件の連絡先"</item>
+ </plurals>
+ <plurals name="groupDescripPhones">
+ <item quantity="other" msgid="3816047547470490208">"<xliff:g id="COUNT_0">%1$d</xliff:g>件の連絡先、<xliff:g id="COUNTWITHPHONES">%2$d</xliff:g>件電話番号あり"</item>
+ </plurals>
<string name="syncAllGroups" msgid="7512169081224806890">"全連絡先を同期"</string>
<string name="groupNameMyContacts" msgid="5696566165170977126">"Myコンタクト"</string>
<string name="groupNameWithPhones" msgid="6981588604041120497">"電話番号のある連絡先"</string>
<string name="starredInAndroid" msgid="6495527538140213440">"Androidでスター付きの連絡先"</string>
<string name="contactCreatedToast" msgid="8740102129968688060">"連絡先を作成しました。"</string>
<string name="contactSavedToast" msgid="7152589189385441091">"連絡先を保存しました。"</string>
+ <!-- no translation found for contactSavedErrorToast (9189098776225004666) -->
+ <skip />
<string name="listSeparatorCallNumber" msgid="7115321894439629408">"発信"</string>
<string name="listSeparatorCallNumber_edit" msgid="2999167270900823334">"電話番号"</string>
<string name="listSeparatorSendSmsMms" msgid="2480944736714739967">"SMSを送信"</string>
@@ -110,9 +139,11 @@
<string name="listSeparatorMapAddress" msgid="7076401301758190804">"地図でみる"</string>
<string name="listSeparatorMapAddress_edit" msgid="298711187672067985">"住所"</string>
<string name="listSeparatorOrganizations" msgid="7514083358440762504">"所属"</string>
+ <string name="listSeparatorGroups" msgid="1593940073983422450">"グループ"</string>
<string name="listSeparatorOtherInformation" msgid="7844959649638482329">"その他の情報"</string>
<string name="listSeparatorOtherInformation_edit" msgid="1326921768011367750">"その他のオプション"</string>
<string name="listSeparatorMore_edit" msgid="858454837482243176">"開く"</string>
+ <string name="socialStreamIconLabel" msgid="4367712449555075376">"ソーシャル"</string>
<string name="contactsIconLabel" msgid="7666609097606552806">"連絡先"</string>
<string name="contactsFavoritesLabel" msgid="8417039765586853670">"お気入り"</string>
<string name="dialerIconLabel" msgid="7228520966699542850">"電話"</string>
@@ -128,6 +159,7 @@
<string name="recentCalls_deleteAll" msgid="6352364392762163704">"通話履歴を全件消去"</string>
<string name="recentCalls_empty" msgid="247053222448663107">"通話履歴なし"</string>
<string name="imei" msgid="3045126336951684285">"IMEI(端末識別番号)"</string>
+ <string name="meid" msgid="6210568493746275750">"MEID"</string>
<string name="voicemail" msgid="3851469869202611441">"ボイスメール"</string>
<string name="unknown" msgid="740067747858270469">"通知不可能"</string>
<string name="private_num" msgid="6374339738119166953">"非通知"</string>
@@ -138,7 +170,14 @@
<string name="simContacts_title" msgid="27341688347689769">"SIMカードの連絡先"</string>
<string name="contactsSyncPlug" msgid="7248276704957313698"><font fgcolor="#ffffffff">"Googleアカウントの連絡先同期"</font>" "\n"Googleサービスで使っている連絡先をこの携帯電話でも使えるようになります。"</string>
<string name="noContactsHelpText" msgid="2249195687463896364">"連絡先が登録されていません。"\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>": 連絡先をSIMカードおよびSDカードから取り込みます"\n</li></string>
- <string name="noContactsHelpTextWithSync" msgid="4927701496550314555">"連絡先が登録されていません。"\n\n<font fgcolor="#ffffffff"><b>"MENU"</b></font>"キーを押して登録方法を選択してください:"\n\n<li><font fgcolor="#ffffffff"><b>"同期する範囲"</b></font>": Googleアカウントから連絡先を読み込んで追加します"\n</li>\n<li><font fgcolor="#ffffffff"><b>"連絡先を新規登録"</b></font>": 連絡先を入力して新規作成します"\n</li>\n<li><font fgcolor="#ffffffff"><b>"連絡先をインポート"</b></font>": 連絡先をSIMカードから取り込みます"\n</li></string>
+ <!-- no translation found for noContactsHelpTextWithSync (2179024855526352215) -->
+ <skip />
+ <!-- no translation found for noContactsNoSimHelpText (645951808197910024) -->
+ <skip />
+ <!-- no translation found for noContactsNoSimHelpTextWithSync (511505839941079550) -->
+ <skip />
+ <!-- no translation found for noFavoritesHelpText (1705381563293866510) -->
+ <skip />
<string name="seclectSyncGroups_title" msgid="1235432026231325655">"同期するグループを選択"</string>
<string name="liveFolder_all_label" msgid="1552523730090319259">"すべての連絡先"</string>
<string name="liveFolder_favorites_label" msgid="2674341514070517105">"スター付き"</string>
@@ -173,6 +212,7 @@
<string name="favoritesFrquentSeparator" msgid="5007070838253932139">"よく使う連絡先"</string>
<string name="add_contact_dlg_title" msgid="2789046541229846116">"連絡先を追加"</string>
<string name="add_contact_dlg_message_fmt" msgid="7986472669444326576">"<xliff:g id="EMAIL">%s</xliff:g> を連絡先に追加しますか?"</string>
+ <string name="all_tab_label" msgid="4003124364397916826">"すべて"</string>
<string name="description_image_button_one" msgid="1740638037139856139">"1"</string>
<string name="description_image_button_two" msgid="5882638439003731308">"2"</string>
<string name="description_image_button_three" msgid="8709731759376015180">"3"</string>
@@ -189,19 +229,27 @@
<string name="no_sdcard_message" msgid="7791906118138538259">"SDカードを検出できませんでした"</string>
<string name="searching_vcard_title" msgid="4158580043290687793">"VCardを検索中"</string>
<string name="select_import_type_title" msgid="2443742794103731022">"連絡先のインポート元を選択してください"</string>
- <string name="import_from_sim" msgid="3362158539070179154">"SIMカード"</string>
- <string name="import_from_sdcard" msgid="8854793070007530689">"SDカード"</string>
- <string name="import_all_vcard_string" msgid="2788222288962542415">"すべてのVCardファイルをインポートする"</string>
- <string name="import_one_vcard_string" msgid="4618119296910253689">"VCardファイルを1つインポートする"</string>
+ <!-- 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 />
+ <string name="import_one_vcard_string" msgid="9059163467020328433">"vCardファイルを1つインポート"</string>
+ <string name="import_multiple_vcard_string" msgid="3810226492811062392">"複数のvCardファイルをインポート"</string>
+ <string name="import_all_vcard_string" msgid="5518136113853448474">"すべてのvCardファイルをインポート"</string>
<string name="searching_vcard_message" msgid="2467573749792455605">"VCardのVCardデータを検索しています"</string>
<string name="scanning_sdcard_failed_title" msgid="2788276113399585924">"SDカードのスキャン失敗"</string>
<string name="scanning_sdcard_failed_message" msgid="925361244069058117">"SDカードのスキャンに失敗しました"</string>
- <string name="fail_reason_io_error" msgid="5922864781066136340">"送受信エラー"</string>
- <string name="fail_reason_vcard_parse_error" msgid="2129476089250836277">"VCardの解析に失敗しました"</string>
+ <string name="fail_reason_io_error" msgid="5949038573678667996">"送受信エラー(理由:「<xliff:g id="FAIL_REASON">%s</xliff:g>」)"</string>
+ <string name="fail_reason_vcard_parse_error" msgid="8877496734269540489">"予期しない原因によりvCardの解析に失敗しました。"</string>
+ <string name="fail_reason_vcard_not_supported_error" msgid="166568606246563552">"vCardの解析に失敗しました。正しいフォーマットですが、現在サポートされていません。"</string>
<string name="fail_reason_no_vcard_file" msgid="1489638537002538332">"SDカードでVCardファイルが見つかりません"</string>
<string name="fail_reason_no_vcard_entry" msgid="3808734131680935043">"選択した内容の有効なVCardエントリが見つかりません"</string>
+ <string name="fail_reason_failed_to_read_files" msgid="3659521123567134029">"1つ以上のファイルをインポートできませんでした(%s)。"</string>
<string name="select_vcard_title" msgid="4615933643517710543">"VCardファイルの選択"</string>
<string name="select_vcard_message" msgid="7028772212789057858">"インポートするVCardファイルを選択してください"</string>
+ <string name="progress_shower_message" msgid="5636525578293752526">"<xliff:g id="ACTION">%s</xliff:g>"\n"<xliff:g id="FILENAME">%s</xliff:g>"</string>
<string name="reading_vcard_title" msgid="430154704397375160">"VCardを読み取り中"</string>
<string name="reading_vcard_message" msgid="7470486051482460267">"VCardファイルを読み取り中"</string>
<string name="importing_vcard_message" msgid="2466665710812588738">"VCardデータをインポート中"</string>
@@ -224,4 +272,104 @@
<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="1488865103809190124">"2秒間の停止を追加"</string>
+ <string name="add_wait" msgid="8347184715316058465">"着信を追加"</string>
+ <string name="dial_button_label" msgid="7637725632722605863">"発信"</string>
+ <!-- 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 />
+ <string name="quickcontact_missing_app" msgid="1181287640680679441">"この操作を行うアプリケーションが見つかりません"</string>
+ <string name="quickcontact_remember_choice" msgid="2858356075887897116">"この選択を保存"</string>
+ <string name="quickcontact_missing_name" msgid="7806083820928955853">"不明"</string>
+ <string name="menu_accounts" msgid="8499114602017077970">"アカウント"</string>
+ <!-- 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">"名前"</string>
+ <string name="nicknameLabelsGroup" msgid="2891682101053358010">"ニックネーム"</string>
+ <string name="organizationLabelsGroup" msgid="2478611760751832035">"所属"</string>
+ <string name="websiteLabelsGroup" msgid="4202998982804009261">"ウェブサイト"</string>
+ <string name="type_short_home" msgid="7770424864090605384">"自宅"</string>
+ <string name="type_short_mobile" msgid="1655473281466676216">"携帯"</string>
+ <string name="type_short_work" msgid="4925330752504537861">"勤務先"</string>
+ <string name="type_short_pager" msgid="2613818970827594238">"ポケベル"</string>
+ <string name="type_short_other" msgid="5669407180177236769">"その他"</string>
+ <string name="edit_secondary_collapse" msgid="1855127176548440583">"その他の情報"</string>
+ <string name="dialog_primary_name" msgid="5521591005692614833">"メインの名前"</string>
+ <string name="dialog_new_contact_account" msgid="9044704073286262197">"アカウントに連絡先を作成"</string>
+ <string name="menu_sync_remove" msgid="3266725887008450161">"同期グループを削除"</string>
+ <!-- no translation found for dialog_sync_add (8267045393119375803) -->
+ <skip />
+ <!-- no translation found for display_more_groups (2682547080423434170) -->
+ <skip />
+ <string name="display_ungrouped" msgid="4357317661311803984">"(グループに含まれない連絡先)"</string>
+ <string name="display_warn_remove_ungrouped" msgid="2314043155909167610">"「<xliff:g id="GROUP">%s</xliff:g>」を同期から除外すると、グループに含まれない連絡先もすべて同期から除外されます。"</string>
+ <string name="account_phone" msgid="4025734638492419713">"電話のみ(非同期)"</string>
+ <string name="label_email_display_name" msgid="5537802602754309600">"表示名"</string>
+ <string name="call_home" msgid="1990519474420545392">"自宅に発信"</string>
+ <string name="call_mobile" msgid="7502236805487609178">"携帯電話に発信"</string>
+ <string name="call_work" msgid="5328785911463744028">"勤務先に発信"</string>
+ <string name="call_fax_work" msgid="7467763592359059243">"勤務先FAXに発信"</string>
+ <string name="call_fax_home" msgid="8342175628887571876">"自宅のFAXに発信"</string>
+ <string name="call_pager" msgid="9003902812293983281">"ポケベルに発信"</string>
+ <string name="call_custom" msgid="7756571794763171802">"<xliff:g id="CUSTOM">%s</xliff:g>に発信"</string>
+ <string name="call_car" msgid="3280537320306436445">"クルマに発信"</string>
+ <string name="call_company_main" msgid="6105120947138711257">"会社代表番号に発信"</string>
+ <string name="call_mms" msgid="6274041545876221437">"MMSに発信"</string>
+ <string name="call_radio" msgid="8296755876398357063">"無線に発信"</string>
+ <string name="sms_home" msgid="7524332261493162995">"自宅にSMS"</string>
+ <string name="sms_mobile" msgid="5200107250451030769">"携帯にSMS"</string>
+ <string name="sms_work" msgid="2269624156655267740">"勤務先にSMS"</string>
+ <string name="sms_fax_work" msgid="8028189067816907075">"勤務先FAXにSMS"</string>
+ <string name="sms_fax_home" msgid="9204042076306809634">"自宅FAXにSMS"</string>
+ <string name="sms_pager" msgid="7730404569637015192">"ポケベルにSMS"</string>
+ <string name="sms_other" msgid="5131921487474531617">"その他にSMS"</string>
+ <string name="sms_custom" msgid="5932736853732191825">"<xliff:g id="CUSTOM">%s</xliff:g>にSMS"</string>
+ <string name="sms_car" msgid="7444227058437359641">"クルマにSMS"</string>
+ <string name="sms_company_main" msgid="118970873419678087">"会社代表番号にSMS"</string>
+ <string name="sms_mms" msgid="4069352461380762677">"MMSにSMS"</string>
+ <string name="sms_radio" msgid="3329166673433967820">"無線にSMS"</string>
+ <string name="email_home" msgid="8573740658148184279">"自宅にメール"</string>
+ <string name="email_mobile" msgid="2042889209787989814">"携帯電話にメール"</string>
+ <string name="email_work" msgid="2807430017302722689">"勤務先にメール"</string>
+ <string name="email_other" msgid="8093933498541795832">"その他にメール"</string>
+ <string name="email_custom" msgid="7548003991586214105">"<xliff:g id="CUSTOM">%s</xliff:g>にメール"</string>
+ <string name="email" msgid="5668400997660065897">"メール"</string>
+ <string name="map_home" msgid="1243547733423343982">"自宅の住所を表示"</string>
+ <string name="map_work" msgid="1360474076921878088">"勤務先の住所を表示"</string>
+ <string name="map_other" msgid="5560707927535653892">"その他の住所を表示"</string>
+ <string name="map_custom" msgid="6184363799976265281">"<xliff:g id="CUSTOM">%s</xliff:g>の住所を表示"</string>
+ <string name="chat_aim" msgid="2588492205291249142">"AIMでチャット"</string>
+ <string name="chat_msn" msgid="8041633440091073484">"Windows Liveでチャット"</string>
+ <string name="chat_yahoo" msgid="6629211142719943666">"Yahooでチャット"</string>
+ <string name="chat_skype" msgid="1210045020427480566">"Skypeでチャット"</string>
+ <string name="chat_qq" msgid="4294637812847719693">"QQでチャット"</string>
+ <string name="chat_gtalk" msgid="981575737258117697">"Googleトークでチャット"</string>
+ <string name="chat_icq" msgid="8438405386153745775">"ICQでチャット"</string>
+ <string name="chat_jabber" msgid="7561444230307829609">"Jabberでチャット"</string>
+ <string name="postal_street" msgid="8133143961580058972">"番地"</string>
+ <string name="postal_pobox" msgid="4431938829180269821">"私書箱"</string>
+ <string name="postal_neighborhood" msgid="1450783874558956739">"近所"</string>
+ <string name="postal_city" msgid="6597491300084895548">"市区町村"</string>
+ <string name="postal_region" msgid="6045263193478437672">"都道府県"</string>
+ <string name="postal_postcode" msgid="572136414136673751">"郵便番号"</string>
+ <string name="postal_country" msgid="7638264508416368690">"国"</string>
+ <string name="name_given" msgid="1687286314106019813">"名"</string>
+ <string name="name_family" msgid="3416695586119999058">"姓"</string>
+ <string name="name_prefix" msgid="59756378548779822">"敬称(名前の前)"</string>
+ <string name="name_middle" msgid="8467433655992690326">"ミドルネーム"</string>
+ <string name="name_suffix" msgid="3855278445375651441">"敬称(名前の後)"</string>
+ <string name="name_phonetic_given" msgid="6853570431394449191">"名のフリガナ"</string>
+ <string name="name_phonetic_middle" msgid="8643721493320405200">"ミドルネームのフリガナ"</string>
+ <string name="name_phonetic_family" msgid="462095502140180305">"姓のフリガナ"</string>
</resources>
diff --git a/res/values-ko/config.xml b/res/values-ko/config.xml
deleted file mode 100644
index 07b3c7e..0000000
--- a/res/values-ko/config.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for config_export_vcard_type (9145748078116159716) -->
- <skip />
- <string name="config_export_dir" msgid="222182743639478636">"/sdcard"</string>
- <!-- no translation found for config_export_file_prefix (3022868431158658122) -->
- <skip />
- <!-- no translation found for config_export_file_suffix (16505844221142195) -->
- <skip />
- <string name="config_export_file_extension" msgid="1758878818611339161">"vcf"</string>
- <!-- no translation found for config_export_extensions_to_consider (5095044502091950623) -->
- <skip />
-</resources>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index 4e7d080..f07aec0 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -40,11 +40,24 @@
<string name="menu_showBarcode" msgid="309973637178814132">"바코드 표시"</string>
<string name="menu_editContact" msgid="3452858480713561396">"연락처 수정"</string>
<string name="menu_deleteContact" msgid="1916555454274101750">"연락처 삭제"</string>
- <string name="menu_call" msgid="7359207953236681606">"통화"</string>
- <string name="menu_sendSMS" msgid="5917289601666766617">"SMS/MMS 보내기"</string>
+ <!-- no translation found for menu_call (3992595586042260618) -->
+ <skip />
+ <!-- no translation found for menu_sendSMS (5535886767547006515) -->
+ <skip />
<string name="menu_sendEmail" msgid="7293508859242926187">"이메일 보내기"</string>
<string name="menu_viewAddress" msgid="1814744325763202024">"지도상의 주소"</string>
<string name="menu_makeDefaultNumber" msgid="4838759253316649534">"기본 번호로 설정"</string>
+ <string name="menu_makeDefaultEmail" msgid="2599044610375789994">"기본 이메일로 설정"</string>
+ <string name="menu_splitAggregate" msgid="7402548456225901170">"분할"</string>
+ <string name="splitAggregate_title" msgid="2053462872948058798">"연락처 분할"</string>
+ <string name="contactsSplitMessage" msgid="3439008547775199243">"연락처 분할"</string>
+ <string name="menu_joinAggregate" msgid="5027981918265667970">"결합"</string>
+ <string name="titleJoinAggregate" msgid="6970566008563147202">"연락처 결합"</string>
+ <string name="separatorJoinAggregateSuggestions" msgid="5494823322479834017">"제안"</string>
+ <string name="separatorJoinAggregateAll" msgid="7939932265026181043">"모든 연락처"</string>
+ <string name="contactsJoinedMessage" msgid="7208148163607047389">"연락처 결합"</string>
+ <string name="menu_contactOptions" msgid="1957061455705020617">"옵션"</string>
+ <string name="contactOptionsTitle" msgid="8259347644090700915">"옵션"</string>
<string name="deleteConfirmation_title" msgid="6394309508930335204">"삭제"</string>
<string name="deleteConfirmation" msgid="811706994761610640">"연락처가 삭제됩니다."</string>
<string name="menu_done" msgid="796017761764190697">"완료"</string>
@@ -55,6 +68,9 @@
<string name="label_phonetic_name" msgid="2288082649573927286">"소리나는 대로"</string>
<string name="label_notes" msgid="8337354953278341042">"메모"</string>
<string name="label_ringtone" msgid="8833166825330686244">"벨소리"</string>
+ <string name="label_groups" msgid="7304551384542859026">"그룹"</string>
+ <string name="group_list" msgid="8583361685440161307">", <xliff:g id="GROUPNAME">%s</xliff:g>"</string>
+ <string name="menu_viewGroup" msgid="1401851715586577551">"그룹 수정"</string>
<string name="ghostData_name" msgid="6490954238641157585">"이름"</string>
<string name="ghostData_phonetic_name" msgid="7852749081984070902">"이름(소리나는 대로)"</string>
<string name="ghostData_company" msgid="5414421120553765775">"회사"</string>
@@ -64,6 +80,7 @@
<string name="ghostData_phone" msgid="6963153888271466620">"전화번호"</string>
<string name="ghostData_email" msgid="6184537075551565919">"이메일 주소"</string>
<string name="ghostData_postal" msgid="652611650594951897">"주소"</string>
+ <string name="ghostData_group" msgid="4504591380347534114">"그룹 표시"</string>
<string name="invalidContactMessage" msgid="5816991830260044593">"연락처가 없습니다."</string>
<string name="pickerNewContactHeader" msgid="7750705279843568147">"새 연락처 만들기"</string>
<string name="selectLabel" msgid="4255424123394910733">"라벨 선택"</string>
@@ -80,7 +97,8 @@
<string name="photoPickerNotFoundText" msgid="431331662154342581">"휴대전화에 사진이 없습니다."</string>
<string name="attachToContact" msgid="8820530304406066714">"연락처 아이콘"</string>
<string name="customLabelPickerTitle" msgid="1081475101983255212">"라벨 이름 맞춤 설정"</string>
- <string name="menu_displayGroup" msgid="4603480747214476335">"그룹 표시"</string>
+ <string name="menu_displayGroup" msgid="6022511302986343108">"그룹 표시"</string>
+ <string name="displayGroups" msgid="2645969886032719891">"그룹 표시"</string>
<string name="syncGroupPreference" msgid="9028361137161162861">"동기화 그룹 수정"</string>
<string name="importFromSim" msgid="8383900146531125319">"주소록 가져오기"</string>
<string name="send_to_voicemail_checkbox" msgid="9001686764070676353">"수신전화를 바로 음성메일로 보내기"</string>
@@ -94,12 +112,23 @@
<string name="select_group_title" msgid="7955698611959835612">"그룹"</string>
<string name="groupEmpty" msgid="6661950109828194595">"\'<xliff:g id="GROUPNAME">%s</xliff:g>\' 그룹이 비어 있습니다."</string>
<string name="showAllGroups" msgid="5164410117611094297">"모든 주소록"</string>
+ <string name="showFilterPhones" msgid="4184858075465653970">"전화가 있는 연락처만"</string>
+ <string name="showFilterPhonesDescrip" msgid="6644443248815191067">"전화번호가 있는 연락처만 표시"</string>
+ <string name="headerContactGroups" msgid="1451383377487746789">"연락처 그룹"</string>
+ <plurals name="groupDescrip">
+ <item quantity="other" msgid="3507881585720628389">"연락처 <xliff:g id="COUNT">%0$d</xliff:g>개"</item>
+ </plurals>
+ <plurals name="groupDescripPhones">
+ <item quantity="other" msgid="3816047547470490208">"연락처 <xliff:g id="COUNT_0">%1$d</xliff:g>개, 전화가 있는 연락처 <xliff:g id="COUNTWITHPHONES">%2$d</xliff:g>개"</item>
+ </plurals>
<string name="syncAllGroups" msgid="7512169081224806890">"모든 주소록 동기화"</string>
<string name="groupNameMyContacts" msgid="5696566165170977126">"내 주소록"</string>
<string name="groupNameWithPhones" msgid="6981588604041120497">"전화번호가 포함된 주소록"</string>
<string name="starredInAndroid" msgid="6495527538140213440">"Android 중요주소록"</string>
<string name="contactCreatedToast" msgid="8740102129968688060">"연락처를 만들었습니다."</string>
<string name="contactSavedToast" msgid="7152589189385441091">"연락처를 저장했습니다."</string>
+ <!-- no translation found for contactSavedErrorToast (9189098776225004666) -->
+ <skip />
<string name="listSeparatorCallNumber" msgid="7115321894439629408">"전화걸기"</string>
<string name="listSeparatorCallNumber_edit" msgid="2999167270900823334">"전화번호"</string>
<string name="listSeparatorSendSmsMms" msgid="2480944736714739967">"SMS/MMS 보내기"</string>
@@ -110,9 +139,11 @@
<string name="listSeparatorMapAddress" msgid="7076401301758190804">"지도상의 주소"</string>
<string name="listSeparatorMapAddress_edit" msgid="298711187672067985">"주소"</string>
<string name="listSeparatorOrganizations" msgid="7514083358440762504">"조직"</string>
+ <string name="listSeparatorGroups" msgid="1593940073983422450">"그룹"</string>
<string name="listSeparatorOtherInformation" msgid="7844959649638482329">"기타 정보"</string>
<string name="listSeparatorOtherInformation_edit" msgid="1326921768011367750">"기타 옵션"</string>
<string name="listSeparatorMore_edit" msgid="858454837482243176">"더보기"</string>
+ <string name="socialStreamIconLabel" msgid="4367712449555075376">"소셜"</string>
<string name="contactsIconLabel" msgid="7666609097606552806">"주소록"</string>
<string name="contactsFavoritesLabel" msgid="8417039765586853670">"즐겨찾기"</string>
<string name="dialerIconLabel" msgid="7228520966699542850">"다이얼러"</string>
@@ -128,6 +159,7 @@
<string name="recentCalls_deleteAll" msgid="6352364392762163704">"통화기록 지우기"</string>
<string name="recentCalls_empty" msgid="247053222448663107">"통화기록이 없습니다."</string>
<string name="imei" msgid="3045126336951684285">"IMEI"</string>
+ <string name="meid" msgid="6210568493746275750">"MEID"</string>
<string name="voicemail" msgid="3851469869202611441">"음성메일"</string>
<string name="unknown" msgid="740067747858270469">"알 수 없음"</string>
<string name="private_num" msgid="6374339738119166953">"비공개 번호"</string>
@@ -138,7 +170,14 @@
<string name="simContacts_title" msgid="27341688347689769">"SIM 카드 주소록"</string>
<string name="contactsSyncPlug" msgid="7248276704957313698"><font fgcolor="#ffffffff">"Google 주소록 동기화"</font>" "\n"휴대전화에 동기화하면 어디서나 주소록을 사용할 수 있습니다."</string>
<string name="noContactsHelpText" msgid="2249195687463896364">"주소록이 없습니다. "\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>" - SIM 카드에서 주소록을 추가하려는 경우"\n</li></string>
- <string name="noContactsHelpTextWithSync" msgid="4927701496550314555">"주소록이 없습니다."\n\n"주소록을 추가하려면 "<font fgcolor="#ffffffff"><b>"메뉴"</b></font>"를 누르고 다음을 선택하세요. "\n" "\n<li><font fgcolor="#ffffffff"><b>"동기화 그룹 수정"</b></font>" - 새로운 또는 기존 Google 계정에서 추가하려는 경우"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"새 연락처"</b></font>" - 새 연락처를 만들려는 경우"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"주소록 가져오기"</b></font>" - SIM 카드에서 주소록을 추가하려는 경우"\n</li></string>
+ <!-- no translation found for noContactsHelpTextWithSync (2179024855526352215) -->
+ <skip />
+ <!-- no translation found for noContactsNoSimHelpText (645951808197910024) -->
+ <skip />
+ <!-- no translation found for noContactsNoSimHelpTextWithSync (511505839941079550) -->
+ <skip />
+ <!-- no translation found for noFavoritesHelpText (1705381563293866510) -->
+ <skip />
<string name="seclectSyncGroups_title" msgid="1235432026231325655">"동기화할 그룹 선택"</string>
<string name="liveFolder_all_label" msgid="1552523730090319259">"모든 주소록"</string>
<string name="liveFolder_favorites_label" msgid="2674341514070517105">"중요 주소록"</string>
@@ -173,6 +212,7 @@
<string name="favoritesFrquentSeparator" msgid="5007070838253932139">"자주 통화한 목록"</string>
<string name="add_contact_dlg_title" msgid="2789046541229846116">"연락처 추가"</string>
<string name="add_contact_dlg_message_fmt" msgid="7986472669444326576">"\'<xliff:g id="EMAIL">%s</xliff:g>\'을(를) 주소록에 추가하겠습니까?"</string>
+ <string name="all_tab_label" msgid="4003124364397916826">"모두"</string>
<string name="description_image_button_one" msgid="1740638037139856139">"1"</string>
<string name="description_image_button_two" msgid="5882638439003731308">"2"</string>
<string name="description_image_button_three" msgid="8709731759376015180">"3"</string>
@@ -189,19 +229,27 @@
<string name="no_sdcard_message" msgid="7791906118138538259">"SD 카드가 발견되지 않았습니다."</string>
<string name="searching_vcard_title" msgid="4158580043290687793">"VCard 검색"</string>
<string name="select_import_type_title" msgid="2443742794103731022">"어디에서 주소록을 가져오시겠습니까?"</string>
- <string name="import_from_sim" msgid="3362158539070179154">"SIM 카드"</string>
- <string name="import_from_sdcard" msgid="8854793070007530689">"SD 카드"</string>
- <string name="import_all_vcard_string" msgid="2788222288962542415">"모든 VCard 파일 가져오기"</string>
- <string name="import_one_vcard_string" msgid="4618119296910253689">"VCard 파일 한 개 가져오기"</string>
+ <!-- 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 />
+ <string name="import_one_vcard_string" msgid="9059163467020328433">"vCard 파일 한 개 가져오기"</string>
+ <string name="import_multiple_vcard_string" msgid="3810226492811062392">"vCard 파일 여러 개 가져오기"</string>
+ <string name="import_all_vcard_string" msgid="5518136113853448474">"모든 vCard 파일 가져오기"</string>
<string name="searching_vcard_message" msgid="2467573749792455605">"VCard에서 VCard 데이터 검색 중"</string>
<string name="scanning_sdcard_failed_title" msgid="2788276113399585924">"SD 카드 스캔 실패"</string>
<string name="scanning_sdcard_failed_message" msgid="925361244069058117">"SD 카드 스캔 실패"</string>
- <string name="fail_reason_io_error" msgid="5922864781066136340">"I/O 오류"</string>
- <string name="fail_reason_vcard_parse_error" msgid="2129476089250836277">"VCard 구문분석에 실패했습니다."</string>
+ <string name="fail_reason_io_error" msgid="5949038573678667996">"I/O 오류(이유: \'<xliff:g id="FAIL_REASON">%s</xliff:g>\')"</string>
+ <string name="fail_reason_vcard_parse_error" msgid="8877496734269540489">"예상치 못한 이유 때문에 VCard 구문 분석에 실패했습니다."</string>
+ <string name="fail_reason_vcard_not_supported_error" msgid="166568606246563552">"올바른 형식처럼 보이지만 현재 구현 환경에서는 VCard를 지원하지 않기 때문에 VCard 구문 분석에 실패했습니다."</string>
<string name="fail_reason_no_vcard_file" msgid="1489638537002538332">"SD 카드에 VCard 파일이 없습니다."</string>
<string name="fail_reason_no_vcard_entry" msgid="3808734131680935043">"선택한 항목에 유효한 VCard 주소가 없습니다."</string>
+ <string name="fail_reason_failed_to_read_files" msgid="3659521123567134029">"하나 이상의 파일을 가져오지 못했습니다(%s)."</string>
<string name="select_vcard_title" msgid="4615933643517710543">"VCard 파일 선택"</string>
<string name="select_vcard_message" msgid="7028772212789057858">"가져올 VCard 파일을 선택하세요."</string>
+ <string name="progress_shower_message" msgid="5636525578293752526">"<xliff:g id="ACTION">%s</xliff:g>"\n"<xliff:g id="FILENAME">%s</xliff:g>"</string>
<string name="reading_vcard_title" msgid="430154704397375160">"VCard 읽는 중"</string>
<string name="reading_vcard_message" msgid="7470486051482460267">"VCard 파일 읽는 중"</string>
<string name="importing_vcard_message" msgid="2466665710812588738">"VCard 데이터 가져오기"</string>
@@ -224,4 +272,104 @@
<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="1488865103809190124">"2초 간 일시 정지 추가"</string>
+ <string name="add_wait" msgid="8347184715316058465">"대기 시간 추가"</string>
+ <string name="dial_button_label" msgid="7637725632722605863">"전화걸기"</string>
+ <!-- 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 />
+ <string name="quickcontact_missing_app" msgid="1181287640680679441">"이 작업을 처리하는 응용프로그램을 찾을 수 없습니다."</string>
+ <string name="quickcontact_remember_choice" msgid="2858356075887897116">"이 선택사항 저장"</string>
+ <string name="quickcontact_missing_name" msgid="7806083820928955853">"알 수 없음"</string>
+ <string name="menu_accounts" msgid="8499114602017077970">"계정"</string>
+ <!-- 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">"이름"</string>
+ <string name="nicknameLabelsGroup" msgid="2891682101053358010">"닉네임"</string>
+ <string name="organizationLabelsGroup" msgid="2478611760751832035">"조직"</string>
+ <string name="websiteLabelsGroup" msgid="4202998982804009261">"웹사이트"</string>
+ <string name="type_short_home" msgid="7770424864090605384">"H"</string>
+ <string name="type_short_mobile" msgid="1655473281466676216">"M"</string>
+ <string name="type_short_work" msgid="4925330752504537861">"W"</string>
+ <string name="type_short_pager" msgid="2613818970827594238">"P"</string>
+ <string name="type_short_other" msgid="5669407180177236769">"O"</string>
+ <string name="edit_secondary_collapse" msgid="1855127176548440583">"보조 세부정보"</string>
+ <string name="dialog_primary_name" msgid="5521591005692614833">"기본 이름"</string>
+ <string name="dialog_new_contact_account" msgid="9044704073286262197">"계정에서 연락처 만들기"</string>
+ <string name="menu_sync_remove" msgid="3266725887008450161">"동기화 그룹 삭제"</string>
+ <!-- no translation found for dialog_sync_add (8267045393119375803) -->
+ <skip />
+ <!-- no translation found for display_more_groups (2682547080423434170) -->
+ <skip />
+ <string name="display_ungrouped" msgid="4357317661311803984">"(그룹화되지 않은 연락처)"</string>
+ <string name="display_warn_remove_ungrouped" msgid="2314043155909167610">"\'<xliff:g id="GROUP">%s</xliff:g>\'을(를) 동기화에서 제거하면 그룹화되지 않은 연락처도 동기화에서 제거됩니다."</string>
+ <string name="account_phone" msgid="4025734638492419713">"전화기 전용(동기화되지 않음)"</string>
+ <string name="label_email_display_name" msgid="5537802602754309600">"이름 표시"</string>
+ <string name="call_home" msgid="1990519474420545392">"집으로 전화걸기"</string>
+ <string name="call_mobile" msgid="7502236805487609178">"휴대전화로 전화걸기"</string>
+ <string name="call_work" msgid="5328785911463744028">"직장으로 전화걸기"</string>
+ <string name="call_fax_work" msgid="7467763592359059243">"직장 팩스로 전화걸기"</string>
+ <string name="call_fax_home" msgid="8342175628887571876">"집 팩스로 전화걸기"</string>
+ <string name="call_pager" msgid="9003902812293983281">"호출기로 전화걸기"</string>
+ <string name="call_custom" msgid="7756571794763171802">"<xliff:g id="CUSTOM">%s</xliff:g>(으)로 전화걸기"</string>
+ <string name="call_car" msgid="3280537320306436445">"카폰으로 전화걸기"</string>
+ <string name="call_company_main" msgid="6105120947138711257">"회사 기본전화로 전화걸기"</string>
+ <string name="call_mms" msgid="6274041545876221437">"MMS로 전화걸기"</string>
+ <string name="call_radio" msgid="8296755876398357063">"무선통신으로 전화걸기"</string>
+ <string name="sms_home" msgid="7524332261493162995">"집으로 문자 보내기"</string>
+ <string name="sms_mobile" msgid="5200107250451030769">"휴대전화로 문자 보내기"</string>
+ <string name="sms_work" msgid="2269624156655267740">"직장으로 문자 보내기"</string>
+ <string name="sms_fax_work" msgid="8028189067816907075">"직장 팩스로 문자 보내기"</string>
+ <string name="sms_fax_home" msgid="9204042076306809634">"집 팩스로 문자 보내기"</string>
+ <string name="sms_pager" msgid="7730404569637015192">"호출기로 문자 보내기"</string>
+ <string name="sms_other" msgid="5131921487474531617">"기타 연락처로 문자 보내기"</string>
+ <string name="sms_custom" msgid="5932736853732191825">"문자(<xliff:g id="CUSTOM">%s</xliff:g>)"</string>
+ <string name="sms_car" msgid="7444227058437359641">"카폰으로 문자 보내기"</string>
+ <string name="sms_company_main" msgid="118970873419678087">"회사 기본전화로 문자 보내기"</string>
+ <string name="sms_mms" msgid="4069352461380762677">"MMS로 문자 보내기"</string>
+ <string name="sms_radio" msgid="3329166673433967820">"무선통신으로 문자 보내기"</string>
+ <string name="email_home" msgid="8573740658148184279">"집으로 이메일 보내기"</string>
+ <string name="email_mobile" msgid="2042889209787989814">"모바일로 이메일 보내기"</string>
+ <string name="email_work" msgid="2807430017302722689">"직장으로 이메일 보내기"</string>
+ <string name="email_other" msgid="8093933498541795832">"기타 연락처로 이메일 보내기"</string>
+ <string name="email_custom" msgid="7548003991586214105">"이메일(<xliff:g id="CUSTOM">%s</xliff:g>)"</string>
+ <string name="email" msgid="5668400997660065897">"이메일"</string>
+ <string name="map_home" msgid="1243547733423343982">"집 주소 보기"</string>
+ <string name="map_work" msgid="1360474076921878088">"직장 주소 보기"</string>
+ <string name="map_other" msgid="5560707927535653892">"기타 주소 보기"</string>
+ <string name="map_custom" msgid="6184363799976265281">"<xliff:g id="CUSTOM">%s</xliff:g> 주소 보기"</string>
+ <string name="chat_aim" msgid="2588492205291249142">"AIM으로 채팅"</string>
+ <string name="chat_msn" msgid="8041633440091073484">"Windows Live로 채팅"</string>
+ <string name="chat_yahoo" msgid="6629211142719943666">"Yahoo로 채팅"</string>
+ <string name="chat_skype" msgid="1210045020427480566">"Skype로 채팅"</string>
+ <string name="chat_qq" msgid="4294637812847719693">"QQ로 채팅"</string>
+ <string name="chat_gtalk" msgid="981575737258117697">"Google 토크로 채팅"</string>
+ <string name="chat_icq" msgid="8438405386153745775">"ICQ로 채팅"</string>
+ <string name="chat_jabber" msgid="7561444230307829609">"Jabber로 채팅"</string>
+ <string name="postal_street" msgid="8133143961580058972">"번지"</string>
+ <string name="postal_pobox" msgid="4431938829180269821">"사서함"</string>
+ <string name="postal_neighborhood" msgid="1450783874558956739">"인근 지역"</string>
+ <string name="postal_city" msgid="6597491300084895548">"구/군/시"</string>
+ <string name="postal_region" msgid="6045263193478437672">"시/도"</string>
+ <string name="postal_postcode" msgid="572136414136673751">"우편번호"</string>
+ <string name="postal_country" msgid="7638264508416368690">"국가"</string>
+ <string name="name_given" msgid="1687286314106019813">"이름"</string>
+ <string name="name_family" msgid="3416695586119999058">"성"</string>
+ <string name="name_prefix" msgid="59756378548779822">"이름 접두어"</string>
+ <string name="name_middle" msgid="8467433655992690326">"이름"</string>
+ <string name="name_suffix" msgid="3855278445375651441">"이름 접미어"</string>
+ <string name="name_phonetic_given" msgid="6853570431394449191">"이름(소리나는 대로)"</string>
+ <string name="name_phonetic_middle" msgid="8643721493320405200">"이름(소리나는 대로)"</string>
+ <string name="name_phonetic_family" msgid="462095502140180305">"성(소리나는 대로)"</string>
</resources>
diff --git a/res/values-nb/config.xml b/res/values-nb/config.xml
deleted file mode 100644
index 07b3c7e..0000000
--- a/res/values-nb/config.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for config_export_vcard_type (9145748078116159716) -->
- <skip />
- <string name="config_export_dir" msgid="222182743639478636">"/sdcard"</string>
- <!-- no translation found for config_export_file_prefix (3022868431158658122) -->
- <skip />
- <!-- no translation found for config_export_file_suffix (16505844221142195) -->
- <skip />
- <string name="config_export_file_extension" msgid="1758878818611339161">"vcf"</string>
- <!-- no translation found for config_export_extensions_to_consider (5095044502091950623) -->
- <skip />
-</resources>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index 7272d68..e04b8da 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -40,11 +40,24 @@
<string name="menu_showBarcode" msgid="309973637178814132">"Vis strekkode"</string>
<string name="menu_editContact" msgid="3452858480713561396">"Rediger kontakt"</string>
<string name="menu_deleteContact" msgid="1916555454274101750">"Slett kontakt"</string>
- <string name="menu_call" msgid="7359207953236681606">"Ring"</string>
- <string name="menu_sendSMS" msgid="5917289601666766617">"Send SMS/MMS"</string>
+ <!-- no translation found for menu_call (3992595586042260618) -->
+ <skip />
+ <!-- no translation found for menu_sendSMS (5535886767547006515) -->
+ <skip />
<string name="menu_sendEmail" msgid="7293508859242926187">"Send e-post"</string>
<string name="menu_viewAddress" msgid="1814744325763202024">"Se i kart"</string>
<string name="menu_makeDefaultNumber" msgid="4838759253316649534">"Gjør til foretrukket nummer"</string>
+ <string name="menu_makeDefaultEmail" msgid="2599044610375789994">"Bruk som standard e-post"</string>
+ <string name="menu_splitAggregate" msgid="7402548456225901170">"Del"</string>
+ <string name="splitAggregate_title" msgid="2053462872948058798">"Del kontakt"</string>
+ <string name="contactsSplitMessage" msgid="3439008547775199243">"Kontakt delt"</string>
+ <string name="menu_joinAggregate" msgid="5027981918265667970">"Foren"</string>
+ <string name="titleJoinAggregate" msgid="6970566008563147202">"Foren kontakt"</string>
+ <string name="separatorJoinAggregateSuggestions" msgid="5494823322479834017">"Forslag"</string>
+ <string name="separatorJoinAggregateAll" msgid="7939932265026181043">"Alle kontakter"</string>
+ <string name="contactsJoinedMessage" msgid="7208148163607047389">"Kontaktene er forent"</string>
+ <string name="menu_contactOptions" msgid="1957061455705020617">"Alternativer"</string>
+ <string name="contactOptionsTitle" msgid="8259347644090700915">"Alternativer"</string>
<string name="deleteConfirmation_title" msgid="6394309508930335204">"Slett"</string>
<string name="deleteConfirmation" msgid="811706994761610640">"Denne kontakten vil bli slettet."</string>
<string name="menu_done" msgid="796017761764190697">"Lagre"</string>
@@ -55,6 +68,9 @@
<string name="label_phonetic_name" msgid="2288082649573927286">"Fonetisk"</string>
<string name="label_notes" msgid="8337354953278341042">"Notater"</string>
<string name="label_ringtone" msgid="8833166825330686244">"Ringetone"</string>
+ <string name="label_groups" msgid="7304551384542859026">"Grupper"</string>
+ <string name="group_list" msgid="8583361685440161307">", <xliff:g id="GROUPNAME">%s</xliff:g>"</string>
+ <string name="menu_viewGroup" msgid="1401851715586577551">"Rediger grupper"</string>
<string name="ghostData_name" msgid="6490954238641157585">"For- og etternavn"</string>
<string name="ghostData_phonetic_name" msgid="7852749081984070902">"Fonetisk navn"</string>
<string name="ghostData_company" msgid="5414421120553765775">"Firma"</string>
@@ -64,6 +80,7 @@
<string name="ghostData_phone" msgid="6963153888271466620">"Telefonnummer"</string>
<string name="ghostData_email" msgid="6184537075551565919">"E-postadresse"</string>
<string name="ghostData_postal" msgid="652611650594951897">"Postadresse"</string>
+ <string name="ghostData_group" msgid="4504591380347534114">"Vis gruppe"</string>
<string name="invalidContactMessage" msgid="5816991830260044593">"Kontakten finnes ikke."</string>
<string name="pickerNewContactHeader" msgid="7750705279843568147">"Opprett ny kontakt"</string>
<string name="selectLabel" msgid="4255424123394910733">"Velg etikett"</string>
@@ -80,7 +97,8 @@
<string name="photoPickerNotFoundText" msgid="431331662154342581">"Det er ingen bilder på telefonen."</string>
<string name="attachToContact" msgid="8820530304406066714">"Kontaktikon"</string>
<string name="customLabelPickerTitle" msgid="1081475101983255212">"Egendefinert etikett"</string>
- <string name="menu_displayGroup" msgid="4603480747214476335">"Vis gruppe"</string>
+ <string name="menu_displayGroup" msgid="6022511302986343108">"Vis grupper"</string>
+ <string name="displayGroups" msgid="2645969886032719891">"Vis grupper"</string>
<string name="syncGroupPreference" msgid="9028361137161162861">"Rediger synkr. grp."</string>
<string name="importFromSim" msgid="8383900146531125319">"Importer kontakter"</string>
<string name="send_to_voicemail_checkbox" msgid="9001686764070676353">"Send anrop direkte til telefonsvarer."</string>
@@ -94,12 +112,23 @@
<string name="select_group_title" msgid="7955698611959835612">"Grupper"</string>
<string name="groupEmpty" msgid="6661950109828194595">"Gruppen «<xliff:g id="GROUPNAME">%s</xliff:g>» er tom."</string>
<string name="showAllGroups" msgid="5164410117611094297">"Alle kontakter"</string>
+ <string name="showFilterPhones" msgid="4184858075465653970">"Kun kontakter med telefon"</string>
+ <string name="showFilterPhonesDescrip" msgid="6644443248815191067">"Vis kun kontakter med telefonnummer"</string>
+ <string name="headerContactGroups" msgid="1451383377487746789">"Kontakt grupper"</string>
+ <plurals name="groupDescrip">
+ <item quantity="other" msgid="3507881585720628389">"<xliff:g id="COUNT">%0$d</xliff:g> kontakter"</item>
+ </plurals>
+ <plurals name="groupDescripPhones">
+ <item quantity="other" msgid="3816047547470490208">"<xliff:g id="COUNT_0">%1$d</xliff:g> kontakter, <xliff:g id="COUNTWITHPHONES">%2$d</xliff:g> med telefoner"</item>
+ </plurals>
<string name="syncAllGroups" msgid="7512169081224806890">"Synkroniser alle kontakter"</string>
<string name="groupNameMyContacts" msgid="5696566165170977126">"Mine kontakter"</string>
<string name="groupNameWithPhones" msgid="6981588604041120497">"Kontakter med telefonnummer"</string>
<string name="starredInAndroid" msgid="6495527538140213440">"Stjernemerket i Android"</string>
<string name="contactCreatedToast" msgid="8740102129968688060">"Kontakt opprettet."</string>
<string name="contactSavedToast" msgid="7152589189385441091">"Kontakt lagret."</string>
+ <!-- no translation found for contactSavedErrorToast (9189098776225004666) -->
+ <skip />
<string name="listSeparatorCallNumber" msgid="7115321894439629408">"Ring nummer"</string>
<string name="listSeparatorCallNumber_edit" msgid="2999167270900823334">"Telefonnummer"</string>
<string name="listSeparatorSendSmsMms" msgid="2480944736714739967">"Send SMS/MMS"</string>
@@ -110,9 +139,11 @@
<string name="listSeparatorMapAddress" msgid="7076401301758190804">"Se i kart"</string>
<string name="listSeparatorMapAddress_edit" msgid="298711187672067985">"Postadresser"</string>
<string name="listSeparatorOrganizations" msgid="7514083358440762504">"Organisasjoner"</string>
+ <string name="listSeparatorGroups" msgid="1593940073983422450">"Grupper"</string>
<string name="listSeparatorOtherInformation" msgid="7844959649638482329">"Annen informasjon"</string>
<string name="listSeparatorOtherInformation_edit" msgid="1326921768011367750">"Andre alternativer"</string>
<string name="listSeparatorMore_edit" msgid="858454837482243176">"Mer"</string>
+ <string name="socialStreamIconLabel" msgid="4367712449555075376">"Sosial"</string>
<string name="contactsIconLabel" msgid="7666609097606552806">"Alle"</string>
<string name="contactsFavoritesLabel" msgid="8417039765586853670">"Vanlige"</string>
<string name="dialerIconLabel" msgid="7228520966699542850">"Telefon"</string>
@@ -128,6 +159,7 @@
<string name="recentCalls_deleteAll" msgid="6352364392762163704">"Tøm anropslogg"</string>
<string name="recentCalls_empty" msgid="247053222448663107">"Anropsloggen er tom."</string>
<string name="imei" msgid="3045126336951684285">"IMEI"</string>
+ <string name="meid" msgid="6210568493746275750">"MEID"</string>
<string name="voicemail" msgid="3851469869202611441">"Telefonsvarer"</string>
<string name="unknown" msgid="740067747858270469">"Ukjent"</string>
<string name="private_num" msgid="6374339738119166953">"Skjult nummer"</string>
@@ -138,7 +170,14 @@
<string name="simContacts_title" msgid="27341688347689769">"Kontakter på SIM-kort"</string>
<string name="contactsSyncPlug" msgid="7248276704957313698"><font fgcolor="#ffffffff">"Synkroniser Google-kontaktene dine!"</font>" "\n"Etter å ha synkronisert telefonen vil kontaktene dine bli tilgjengelig overalt."</string>
<string name="noContactsHelpText" msgid="2249195687463896364">"Du har ingen kontakter."\n\n"For å legge til kontakter, trykk "<font fgcolor="#ffffffff"><b>"menyknappen"</b></font>" og velg:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Ny kontakt"</b></font>" for å lage en ny kontakt fra grunnen av"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importer kontakter"</b></font>" for å legge til kontakter fra SIM-kortet"\n</li></string>
- <string name="noContactsHelpTextWithSync" msgid="4927701496550314555">"Du har ingen kontakter."\n\n"For å legge til kontakter, trykk "<font fgcolor="#ffffffff"><b>"menyknappen"</b></font>" og velg:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Rediger synkroniserte grupper"</b></font>" for å legge til fra en ny eller eksisterende Google-konto"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Ny kontakt"</b></font>" for å lage en ny kontakt fra grunnen av"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importer kontakter"</b></font>" for å legge til kontakter fra SIM-kortet"\n</li></string>
+ <!-- no translation found for noContactsHelpTextWithSync (2179024855526352215) -->
+ <skip />
+ <!-- no translation found for noContactsNoSimHelpText (645951808197910024) -->
+ <skip />
+ <!-- no translation found for noContactsNoSimHelpTextWithSync (511505839941079550) -->
+ <skip />
+ <!-- no translation found for noFavoritesHelpText (1705381563293866510) -->
+ <skip />
<string name="seclectSyncGroups_title" msgid="1235432026231325655">"Velg grupper som skal synkroniseres"</string>
<string name="liveFolder_all_label" msgid="1552523730090319259">"Alle kontakter"</string>
<string name="liveFolder_favorites_label" msgid="2674341514070517105">"Med stjerne"</string>
@@ -173,6 +212,7 @@
<string name="favoritesFrquentSeparator" msgid="5007070838253932139">"Ofte ringt"</string>
<string name="add_contact_dlg_title" msgid="2789046541229846116">"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>
+ <string name="all_tab_label" msgid="4003124364397916826">"Alle"</string>
<string name="description_image_button_one" msgid="1740638037139856139">"en"</string>
<string name="description_image_button_two" msgid="5882638439003731308">"to"</string>
<string name="description_image_button_three" msgid="8709731759376015180">"tre"</string>
@@ -189,19 +229,27 @@
<string name="no_sdcard_message" msgid="7791906118138538259">"Fant ikke noe minnekort"</string>
<string name="searching_vcard_title" msgid="4158580043290687793">"Leter etter VCard"</string>
<string name="select_import_type_title" msgid="2443742794103731022">"Hvor ønsker du å hente kontakter fra?"</string>
- <string name="import_from_sim" msgid="3362158539070179154">"SIM-kort"</string>
- <string name="import_from_sdcard" msgid="8854793070007530689">"Minnekort"</string>
- <string name="import_all_vcard_string" msgid="2788222288962542415">"Importer alle VCard-filer"</string>
- <string name="import_one_vcard_string" msgid="4618119296910253689">"Importer én VCard-filer"</string>
+ <!-- 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 />
+ <string name="import_one_vcard_string" msgid="9059163467020328433">"Importer én VCard-fil"</string>
+ <string name="import_multiple_vcard_string" msgid="3810226492811062392">"Importer flere vCard-filer"</string>
+ <string name="import_all_vcard_string" msgid="5518136113853448474">"Importer alle vCard-filer"</string>
<string name="searching_vcard_message" msgid="2467573749792455605">"Leter etter VCard-data på minnekort"</string>
<string name="scanning_sdcard_failed_title" msgid="2788276113399585924">"Kunne ikke scanne minnekort"</string>
<string name="scanning_sdcard_failed_message" msgid="925361244069058117">"Kunne ikke scanne minnekort"</string>
- <string name="fail_reason_io_error" msgid="5922864781066136340">"Inn/ut-feil"</string>
- <string name="fail_reason_vcard_parse_error" msgid="2129476089250836277">"Klarte ikke å tolke VCard"</string>
+ <string name="fail_reason_io_error" msgid="5949038573678667996">"Inn/ut-feil (årsak: «<xliff:g id="FAIL_REASON">%s</xliff:g>»)"</string>
+ <string name="fail_reason_vcard_parse_error" msgid="8877496734269540489">"Kan ikke analysere VCard av uventet årsak"</string>
+ <string name="fail_reason_vcard_not_supported_error" msgid="166568606246563552">"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="1489638537002538332">"Fant ingen VCard-filer på minnekortet"</string>
<string name="fail_reason_no_vcard_entry" msgid="3808734131680935043">"Fant ingen VCard-filer for valget ditt"</string>
+ <string name="fail_reason_failed_to_read_files" msgid="3659521123567134029">"Importeringen av én eller flere filer mislyktes (%s)"</string>
<string name="select_vcard_title" msgid="4615933643517710543">"Velg VCard-fil"</string>
<string name="select_vcard_message" msgid="7028772212789057858">"Velg en VCard-fil som skal importeres"</string>
+ <string name="progress_shower_message" msgid="5636525578293752526">"<xliff:g id="ACTION">%s</xliff:g>"\n"<xliff:g id="FILENAME">%s</xliff:g>"</string>
<string name="reading_vcard_title" msgid="430154704397375160">"Leser VCard"</string>
<string name="reading_vcard_message" msgid="7470486051482460267">"Leser VCard-fil(er)"</string>
<string name="importing_vcard_message" msgid="2466665710812588738">"Importerer VCard-data"</string>
@@ -224,4 +272,105 @@
<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="1488865103809190124">"Legg til pause på 2 sek."</string>
+ <string name="add_wait" msgid="8347184715316058465">"Legg til Vent"</string>
+ <string name="dial_button_label" msgid="7637725632722605863">"Ring"</string>
+ <!-- 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 />
+ <string name="quickcontact_missing_app" msgid="1181287640680679441">"Ingen programmer som kan utføre denne handlingen ble funnet"</string>
+ <string name="quickcontact_remember_choice" msgid="2858356075887897116">"Husk dette valget"</string>
+ <string name="quickcontact_missing_name" msgid="7806083820928955853">"Ukjent"</string>
+ <string name="menu_accounts" msgid="8499114602017077970">"Kontoer"</string>
+ <!-- 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">"Navn"</string>
+ <string name="nicknameLabelsGroup" msgid="2891682101053358010">"Kallenavn"</string>
+ <string name="organizationLabelsGroup" msgid="2478611760751832035">"Organisering"</string>
+ <string name="websiteLabelsGroup" msgid="4202998982804009261">"Nettsted"</string>
+ <string name="type_short_home" msgid="7770424864090605384">"H"</string>
+ <string name="type_short_mobile" msgid="1655473281466676216">"M"</string>
+ <string name="type_short_work" msgid="4925330752504537861">"W"</string>
+ <string name="type_short_pager" msgid="2613818970827594238">"P"</string>
+ <string name="type_short_other" msgid="5669407180177236769">"O"</string>
+ <string name="edit_secondary_collapse" msgid="1855127176548440583">"Andre detaljer"</string>
+ <string name="dialog_primary_name" msgid="5521591005692614833">"Hovednavn"</string>
+ <string name="dialog_new_contact_account" msgid="9044704073286262197">"Opprett kontakt under konto"</string>
+ <string name="menu_sync_remove" msgid="3266725887008450161">"Fjern synkronisert gruppe"</string>
+ <!-- no translation found for dialog_sync_add (8267045393119375803) -->
+ <skip />
+ <!-- no translation found for display_more_groups (2682547080423434170) -->
+ <skip />
+ <string name="display_ungrouped" msgid="4357317661311803984">"(Ugrupperte kontakter)"</string>
+ <string name="display_warn_remove_ungrouped" msgid="2314043155909167610">"Hvis «<xliff:g id="GROUP">%s</xliff:g>» fjernes·fra synkroniseringen, vil også alle ugrupperte kontakter fjernes fra synkroniseringen."</string>
+ <string name="account_phone" msgid="4025734638492419713">"Kun telefon (usynkronisert)"</string>
+ <string name="label_email_display_name" msgid="5537802602754309600">"Visningsnavn"</string>
+ <string name="call_home" msgid="1990519474420545392">"Ring (privat)"</string>
+ <string name="call_mobile" msgid="7502236805487609178">"Ring mobil"</string>
+ <string name="call_work" msgid="5328785911463744028">"Ring (arbeid)"</string>
+ <string name="call_fax_work" msgid="7467763592359059243">"Anrop faks (arbeid)"</string>
+ <string name="call_fax_home" msgid="8342175628887571876">"Anrop faks (privat)"</string>
+ <string name="call_pager" msgid="9003902812293983281">"Ring personsøker"</string>
+ <string name="call_custom" msgid="7756571794763171802">"Ring <xliff:g id="CUSTOM">%s</xliff:g>"</string>
+ <string name="call_car" msgid="3280537320306436445">"Ring (bil)"</string>
+ <string name="call_company_main" msgid="6105120947138711257">"Ring firma (sentralbord)"</string>
+ <string name="call_mms" msgid="6274041545876221437">"Ring MMS"</string>
+ <string name="call_radio" msgid="8296755876398357063">"Ring (radio)"</string>
+ <string name="sms_home" msgid="7524332261493162995">"Send SMS (privat)"</string>
+ <string name="sms_mobile" msgid="5200107250451030769">"Send SMS (mobil)"</string>
+ <string name="sms_work" msgid="2269624156655267740">"Send SMS (arbeid)"</string>
+ <string name="sms_fax_work" msgid="8028189067816907075">"Send SMS til faks (arbeid)"</string>
+ <string name="sms_fax_home" msgid="9204042076306809634">"Send SMS til faks (privat)"</string>
+ <string name="sms_pager" msgid="7730404569637015192">"Send SMS (personsøker)"</string>
+ <string name="sms_other" msgid="5131921487474531617">"Send SMS (annet)"</string>
+ <string name="sms_custom" msgid="5932736853732191825">"Send SMS (<xliff:g id="CUSTOM">%s</xliff:g>)"</string>
+ <string name="sms_car" msgid="7444227058437359641">"Send SMS (bil)"</string>
+ <string name="sms_company_main" msgid="118970873419678087">"Send SMS til firma (sentralbord)"</string>
+ <string name="sms_mms" msgid="4069352461380762677">"Send MMS"</string>
+ <string name="sms_radio" msgid="3329166673433967820">"Send SMS (radio)"</string>
+ <string name="email_home" msgid="8573740658148184279">"Send e-post (privat)"</string>
+ <string name="email_mobile" msgid="2042889209787989814">"Skriv e-post (mobil)"</string>
+ <string name="email_work" msgid="2807430017302722689">"Send e-post (arbeid)"</string>
+ <string name="email_other" msgid="8093933498541795832">"Send e-post (annet)"</string>
+ <string name="email_custom" msgid="7548003991586214105">"Send e-post (<xliff:g id="CUSTOM">%s</xliff:g>)"</string>
+ <string name="email" msgid="5668400997660065897">"E-post"</string>
+ <string name="map_home" msgid="1243547733423343982">"Vis privat adresse"</string>
+ <string name="map_work" msgid="1360474076921878088">"Vis jobbadresse"</string>
+ <string name="map_other" msgid="5560707927535653892">"Vis andre adresser"</string>
+ <string name="map_custom" msgid="6184363799976265281">"Vis <xliff:g id="CUSTOM">%s</xliff:g> adresse"</string>
+ <string name="chat_aim" msgid="2588492205291249142">"Nettprat med AIM"</string>
+ <string name="chat_msn" msgid="8041633440091073484">"Nettprat med Windows Live"</string>
+ <string name="chat_yahoo" msgid="6629211142719943666">"Nettprat med Yahoo"</string>
+ <string name="chat_skype" msgid="1210045020427480566">"Nettprat med Skype"</string>
+ <string name="chat_qq" msgid="4294637812847719693">"Nettprat med QQ"</string>
+ <string name="chat_gtalk" msgid="981575737258117697">"Nettprat med Google Talk"</string>
+ <string name="chat_icq" msgid="8438405386153745775">"Nettprat med ICQ"</string>
+ <string name="chat_jabber" msgid="7561444230307829609">"Nettprat med Jabber"</string>
+ <string name="postal_street" msgid="8133143961580058972">"Gate"</string>
+ <string name="postal_pobox" msgid="4431938829180269821">"Postboks"</string>
+ <string name="postal_neighborhood" msgid="1450783874558956739">"Nabolag"</string>
+ <string name="postal_city" msgid="6597491300084895548">"Poststed"</string>
+ <string name="postal_region" msgid="6045263193478437672">"Poststed"</string>
+ <string name="postal_postcode" msgid="572136414136673751">"Postnummer"</string>
+ <string name="postal_country" msgid="7638264508416368690">"Land"</string>
+ <string name="name_given" msgid="1687286314106019813">"Fornavn"</string>
+ <string name="name_family" msgid="3416695586119999058">"Etternavn"</string>
+ <string name="name_prefix" msgid="59756378548779822">"Første del av navn"</string>
+ <string name="name_middle" msgid="8467433655992690326">"Mellomnavn"</string>
+ <!-- no translation found for name_suffix (3855278445375651441) -->
+ <skip />
+ <string name="name_phonetic_given" msgid="6853570431394449191">"Fonetisk fornavn"</string>
+ <string name="name_phonetic_middle" msgid="8643721493320405200">"Fonetisk mellomnavn"</string>
+ <string name="name_phonetic_family" msgid="462095502140180305">"Fonetisk etternavn"</string>
</resources>
diff --git a/res/values-nl/config.xml b/res/values-nl/config.xml
deleted file mode 100644
index 07b3c7e..0000000
--- a/res/values-nl/config.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for config_export_vcard_type (9145748078116159716) -->
- <skip />
- <string name="config_export_dir" msgid="222182743639478636">"/sdcard"</string>
- <!-- no translation found for config_export_file_prefix (3022868431158658122) -->
- <skip />
- <!-- no translation found for config_export_file_suffix (16505844221142195) -->
- <skip />
- <string name="config_export_file_extension" msgid="1758878818611339161">"vcf"</string>
- <!-- no translation found for config_export_extensions_to_consider (5095044502091950623) -->
- <skip />
-</resources>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index 08c2f86..e3a6ac8 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -40,11 +40,24 @@
<string name="menu_showBarcode" msgid="309973637178814132">"Barcode weergeven"</string>
<string name="menu_editContact" msgid="3452858480713561396">"Contact bewerken"</string>
<string name="menu_deleteContact" msgid="1916555454274101750">"Contact verwijderen"</string>
- <string name="menu_call" msgid="7359207953236681606">"Bellen"</string>
- <string name="menu_sendSMS" msgid="5917289601666766617">"SMS/MMS verzenden"</string>
+ <!-- no translation found for menu_call (3992595586042260618) -->
+ <skip />
+ <!-- no translation found for menu_sendSMS (5535886767547006515) -->
+ <skip />
<string name="menu_sendEmail" msgid="7293508859242926187">"E-mail verzenden"</string>
<string name="menu_viewAddress" msgid="1814744325763202024">"Adres op kaart weergeven"</string>
<string name="menu_makeDefaultNumber" msgid="4838759253316649534">"Als standaardnummer instellen"</string>
+ <string name="menu_makeDefaultEmail" msgid="2599044610375789994">"Instellen als standaard e-mailadres"</string>
+ <string name="menu_splitAggregate" msgid="7402548456225901170">"Splitsen"</string>
+ <string name="splitAggregate_title" msgid="2053462872948058798">"Contact splitsen"</string>
+ <string name="contactsSplitMessage" msgid="3439008547775199243">"Contact gesplitst"</string>
+ <string name="menu_joinAggregate" msgid="5027981918265667970">"Samenvoegen"</string>
+ <string name="titleJoinAggregate" msgid="6970566008563147202">"Contact samenvoegen"</string>
+ <string name="separatorJoinAggregateSuggestions" msgid="5494823322479834017">"Suggesties"</string>
+ <string name="separatorJoinAggregateAll" msgid="7939932265026181043">"Alle contacten"</string>
+ <string name="contactsJoinedMessage" msgid="7208148163607047389">"Contacten zijn samengevoegd"</string>
+ <string name="menu_contactOptions" msgid="1957061455705020617">"Opties"</string>
+ <string name="contactOptionsTitle" msgid="8259347644090700915">"Opties"</string>
<string name="deleteConfirmation_title" msgid="6394309508930335204">"Verwijderen"</string>
<string name="deleteConfirmation" msgid="811706994761610640">"Dit contact wordt verwijderd."</string>
<string name="menu_done" msgid="796017761764190697">"Gereed"</string>
@@ -55,6 +68,9 @@
<string name="label_phonetic_name" msgid="2288082649573927286">"Fonetisch"</string>
<string name="label_notes" msgid="8337354953278341042">"Opmerkingen"</string>
<string name="label_ringtone" msgid="8833166825330686244">"Beltoon"</string>
+ <string name="label_groups" msgid="7304551384542859026">"Groepen"</string>
+ <string name="group_list" msgid="8583361685440161307">", <xliff:g id="GROUPNAME">%s</xliff:g>"</string>
+ <string name="menu_viewGroup" msgid="1401851715586577551">"Groepen bewerken"</string>
<string name="ghostData_name" msgid="6490954238641157585">"Voor- en achternaam"</string>
<string name="ghostData_phonetic_name" msgid="7852749081984070902">"Fonetisch gespelde naam"</string>
<string name="ghostData_company" msgid="5414421120553765775">"Bedrijf"</string>
@@ -64,6 +80,7 @@
<string name="ghostData_phone" msgid="6963153888271466620">"Telefoonnummer"</string>
<string name="ghostData_email" msgid="6184537075551565919">"E-mailadres"</string>
<string name="ghostData_postal" msgid="652611650594951897">"Postadres"</string>
+ <string name="ghostData_group" msgid="4504591380347534114">"Groep weergeven"</string>
<string name="invalidContactMessage" msgid="5816991830260044593">"Het contact bestaat niet."</string>
<string name="pickerNewContactHeader" msgid="7750705279843568147">"Nieuw contact maken"</string>
<string name="selectLabel" msgid="4255424123394910733">"Label selecteren"</string>
@@ -80,7 +97,8 @@
<string name="photoPickerNotFoundText" msgid="431331662154342581">"Er zijn geen foto\'s beschikbaar op de telefoon."</string>
<string name="attachToContact" msgid="8820530304406066714">"Pictogram voor contact"</string>
<string name="customLabelPickerTitle" msgid="1081475101983255212">"Aangepaste labelnaam"</string>
- <string name="menu_displayGroup" msgid="4603480747214476335">"Groep weergeven"</string>
+ <string name="menu_displayGroup" msgid="6022511302986343108">"Groepen weergeven"</string>
+ <string name="displayGroups" msgid="2645969886032719891">"Groepen weergeven"</string>
<string name="syncGroupPreference" msgid="9028361137161162861">"Sync.groepen bewerken"</string>
<string name="importFromSim" msgid="8383900146531125319">"Contacten importeren"</string>
<string name="send_to_voicemail_checkbox" msgid="9001686764070676353">"Oproepen rechtstreeks naar voicemail verzenden"</string>
@@ -94,12 +112,23 @@
<string name="select_group_title" msgid="7955698611959835612">"Groepen"</string>
<string name="groupEmpty" msgid="6661950109828194595">"De groep \'<xliff:g id="GROUPNAME">%s</xliff:g>\' is leeg."</string>
<string name="showAllGroups" msgid="5164410117611094297">"Alle contacten"</string>
+ <string name="showFilterPhones" msgid="4184858075465653970">"Alleen contacten met telefoonnummers"</string>
+ <string name="showFilterPhonesDescrip" msgid="6644443248815191067">"Alleen contacten weergeven met telefoonnummers"</string>
+ <string name="headerContactGroups" msgid="1451383377487746789">"Contactgroepen"</string>
+ <plurals name="groupDescrip">
+ <item quantity="other" msgid="3507881585720628389">"<xliff:g id="COUNT">%0$d</xliff:g> contacten"</item>
+ </plurals>
+ <plurals name="groupDescripPhones">
+ <item quantity="other" msgid="3816047547470490208">"<xliff:g id="COUNT_0">%1$d</xliff:g> contacten, <xliff:g id="COUNTWITHPHONES">%2$d</xliff:g> met telefoonnummers"</item>
+ </plurals>
<string name="syncAllGroups" msgid="7512169081224806890">"Alle contacten synchroniseren"</string>
<string name="groupNameMyContacts" msgid="5696566165170977126">"Mijn contacten"</string>
<string name="groupNameWithPhones" msgid="6981588604041120497">"Contacten met telefoonnummers"</string>
<string name="starredInAndroid" msgid="6495527538140213440">"Met ster in Android"</string>
<string name="contactCreatedToast" msgid="8740102129968688060">"Contact is gemaakt."</string>
<string name="contactSavedToast" msgid="7152589189385441091">"Contact opgeslagen."</string>
+ <!-- no translation found for contactSavedErrorToast (9189098776225004666) -->
+ <skip />
<string name="listSeparatorCallNumber" msgid="7115321894439629408">"Nummer bellen"</string>
<string name="listSeparatorCallNumber_edit" msgid="2999167270900823334">"Telefoonnummers"</string>
<string name="listSeparatorSendSmsMms" msgid="2480944736714739967">"SMS/MMS verzenden"</string>
@@ -110,9 +139,11 @@
<string name="listSeparatorMapAddress" msgid="7076401301758190804">"Adres op kaart weergeven"</string>
<string name="listSeparatorMapAddress_edit" msgid="298711187672067985">"Postadressen"</string>
<string name="listSeparatorOrganizations" msgid="7514083358440762504">"Organisaties"</string>
+ <string name="listSeparatorGroups" msgid="1593940073983422450">"Groepen"</string>
<string name="listSeparatorOtherInformation" msgid="7844959649638482329">"Overige informatie"</string>
<string name="listSeparatorOtherInformation_edit" msgid="1326921768011367750">"Andere opties"</string>
<string name="listSeparatorMore_edit" msgid="858454837482243176">"Meer"</string>
+ <string name="socialStreamIconLabel" msgid="4367712449555075376">"Sociaal"</string>
<string name="contactsIconLabel" msgid="7666609097606552806">"Contact"</string>
<string name="contactsFavoritesLabel" msgid="8417039765586853670">"Favoriet"</string>
<string name="dialerIconLabel" msgid="7228520966699542850">"Telefoon"</string>
@@ -128,6 +159,7 @@
<string name="recentCalls_deleteAll" msgid="6352364392762163704">"Gesprekken wissen"</string>
<string name="recentCalls_empty" msgid="247053222448663107">"Gesprekken is leeg"</string>
<string name="imei" msgid="3045126336951684285">"IMEI"</string>
+ <string name="meid" msgid="6210568493746275750">"MEID"</string>
<string name="voicemail" msgid="3851469869202611441">"Voicemail"</string>
<string name="unknown" msgid="740067747858270469">"Onbekend"</string>
<string name="private_num" msgid="6374339738119166953">"Privénummer"</string>
@@ -138,7 +170,14 @@
<string name="simContacts_title" msgid="27341688347689769">"Contacten op SIM-kaart"</string>
<string name="contactsSyncPlug" msgid="7248276704957313698"><font fgcolor="#ffffffff">"Synchroniseer uw Google-contacten!"</font>" "\n"Zodra uw telefoon is gesynchroniseerd, heeft u uw contacten altijd ter beschikking."</string>
<string name="noContactsHelpText" msgid="2249195687463896364">"U heeft geen contacten."\n\n"Als u contacten wilt toevoegen, drukt u op "<font fgcolor="#ffffffff"><b>"Menu"</b></font>" en selecteert u:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Nieuw contact"</b></font>" om een nieuw contact te maken"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Contacten importeren"</b></font>" om contacten vanaf uw SIM-kaart toe te voegen"\n</li></string>
- <string name="noContactsHelpTextWithSync" msgid="4927701496550314555">"U heeft geen contacten."\n\n"Als u contacten wilt toevoegen, klikt u op "<font fgcolor="#ffffffff"><b>"Menu"</b></font>" en selecteert u:"\n\n<li><font fgcolor="#ffffffff"><b>"\'Synchronisatiegroepen bewerken\'"</b></font>" om contacten toe te voegen uit een nieuw of bestaand Google-account"\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>"\'Contacten importeren\'"</b></font>" om contacten te importeren vanaf uw SIM-kaart"\n</li></string>
+ <!-- no translation found for noContactsHelpTextWithSync (2179024855526352215) -->
+ <skip />
+ <!-- no translation found for noContactsNoSimHelpText (645951808197910024) -->
+ <skip />
+ <!-- no translation found for noContactsNoSimHelpTextWithSync (511505839941079550) -->
+ <skip />
+ <!-- no translation found for noFavoritesHelpText (1705381563293866510) -->
+ <skip />
<string name="seclectSyncGroups_title" msgid="1235432026231325655">"Groepen selecteren om te synchroniseren"</string>
<string name="liveFolder_all_label" msgid="1552523730090319259">"Alle contacten"</string>
<string name="liveFolder_favorites_label" msgid="2674341514070517105">"Met ster"</string>
@@ -173,6 +212,7 @@
<string name="favoritesFrquentSeparator" msgid="5007070838253932139">"Vaak gebeld"</string>
<string name="add_contact_dlg_title" msgid="2789046541229846116">"Contact toevoegen"</string>
<string name="add_contact_dlg_message_fmt" msgid="7986472669444326576">"\"<xliff:g id="EMAIL">%s</xliff:g>\" toevoegen aan contacten?"</string>
+ <string name="all_tab_label" msgid="4003124364397916826">"Alles"</string>
<string name="description_image_button_one" msgid="1740638037139856139">"één"</string>
<string name="description_image_button_two" msgid="5882638439003731308">"twee"</string>
<string name="description_image_button_three" msgid="8709731759376015180">"drie"</string>
@@ -189,19 +229,27 @@
<string name="no_sdcard_message" msgid="7791906118138538259">"Geen SD-kaart gedetecteerd"</string>
<string name="searching_vcard_title" msgid="4158580043290687793">"Zoeken naar VCard"</string>
<string name="select_import_type_title" msgid="2443742794103731022">"Waarvandaan wilt u contacten importeren?"</string>
- <string name="import_from_sim" msgid="3362158539070179154">"SIM-kaart"</string>
- <string name="import_from_sdcard" msgid="8854793070007530689">"SD-kaart"</string>
- <string name="import_all_vcard_string" msgid="2788222288962542415">"Alle VCard-bestanden importeren"</string>
- <string name="import_one_vcard_string" msgid="4618119296910253689">"Eén VCard-bestand importeren"</string>
+ <!-- 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 />
+ <string name="import_one_vcard_string" msgid="9059163467020328433">"Eén vCard-bestand importeren"</string>
+ <string name="import_multiple_vcard_string" msgid="3810226492811062392">"Meerdere vCard-bestanden importeren"</string>
+ <string name="import_all_vcard_string" msgid="5518136113853448474">"Alle vCard-bestanden importeren"</string>
<string name="searching_vcard_message" msgid="2467573749792455605">"Zoeken naar VCard-gegevens op VCard"</string>
<string name="scanning_sdcard_failed_title" msgid="2788276113399585924">"Scannen van SD-kaart is mislukt"</string>
<string name="scanning_sdcard_failed_message" msgid="925361244069058117">"Scannen van SD-kaart is mislukt"</string>
- <string name="fail_reason_io_error" msgid="5922864781066136340">"I/O-fout"</string>
- <string name="fail_reason_vcard_parse_error" msgid="2129476089250836277">"Parseren van VCard is mislukt"</string>
+ <string name="fail_reason_io_error" msgid="5949038573678667996">"I/O-fout: (Reden: \'<xliff:g id="FAIL_REASON">%s</xliff:g>\')"</string>
+ <string name="fail_reason_vcard_parse_error" msgid="8877496734269540489">"Kan vCard niet parseren wegens onbekende reden"</string>
+ <string name="fail_reason_vcard_not_supported_error" msgid="166568606246563552">"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="1489638537002538332">"Geen VCard-bestand gevonden op SD-kaart"</string>
<string name="fail_reason_no_vcard_entry" msgid="3808734131680935043">"Geen geldig VCard-item gevonden voor uw selectie"</string>
+ <string name="fail_reason_failed_to_read_files" msgid="3659521123567134029">"Een of meer bestanden kunnen niet worden geïmporteerd (%s)"</string>
<string name="select_vcard_title" msgid="4615933643517710543">"VCard-bestand selecteren"</string>
<string name="select_vcard_message" msgid="7028772212789057858">"Selecteer een VCard-bestand om te importeren"</string>
+ <string name="progress_shower_message" msgid="5636525578293752526">"<xliff:g id="ACTION">%s</xliff:g>"\n"<xliff:g id="FILENAME">%s</xliff:g>"</string>
<string name="reading_vcard_title" msgid="430154704397375160">"VCard lezen"</string>
<string name="reading_vcard_message" msgid="7470486051482460267">"VCard-bestand(en) lezen"</string>
<string name="importing_vcard_message" msgid="2466665710812588738">"VCard-gegevens importeren"</string>
@@ -224,4 +272,104 @@
<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="1488865103809190124">"Pauze van 2 seconden toevoegen"</string>
+ <string name="add_wait" msgid="8347184715316058465">"Wachten toevoegen"</string>
+ <string name="dial_button_label" msgid="7637725632722605863">"Bellen"</string>
+ <!-- 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 />
+ <string name="quickcontact_missing_app" msgid="1181287640680679441">"Er is geen toepassing gevonden om deze actie uit te voeren"</string>
+ <string name="quickcontact_remember_choice" msgid="2858356075887897116">"Deze keuze onthouden"</string>
+ <string name="quickcontact_missing_name" msgid="7806083820928955853">"Onbekend"</string>
+ <string name="menu_accounts" msgid="8499114602017077970">"Accounts"</string>
+ <!-- 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">"Naam"</string>
+ <string name="nicknameLabelsGroup" msgid="2891682101053358010">"Bijnaam"</string>
+ <string name="organizationLabelsGroup" msgid="2478611760751832035">"Organisatie"</string>
+ <string name="websiteLabelsGroup" msgid="4202998982804009261">"Website"</string>
+ <string name="type_short_home" msgid="7770424864090605384">"T"</string>
+ <string name="type_short_mobile" msgid="1655473281466676216">"M"</string>
+ <string name="type_short_work" msgid="4925330752504537861">"W"</string>
+ <string name="type_short_pager" msgid="2613818970827594238">"P"</string>
+ <string name="type_short_other" msgid="5669407180177236769">"O"</string>
+ <string name="edit_secondary_collapse" msgid="1855127176548440583">"Secundaire gegevens"</string>
+ <string name="dialog_primary_name" msgid="5521591005692614833">"Primaire naam"</string>
+ <string name="dialog_new_contact_account" msgid="9044704073286262197">"Contact in account maken"</string>
+ <string name="menu_sync_remove" msgid="3266725887008450161">"Synchronisatiegroep verwijderen"</string>
+ <!-- no translation found for dialog_sync_add (8267045393119375803) -->
+ <skip />
+ <!-- no translation found for display_more_groups (2682547080423434170) -->
+ <skip />
+ <string name="display_ungrouped" msgid="4357317661311803984">"(Contacten die niet in groepen zijn opgenomen)"</string>
+ <string name="display_warn_remove_ungrouped" msgid="2314043155909167610">"Als u \'<xliff:g id="GROUP">%s</xliff:g>\' verwijdert uit de synchronisatie, worden ook contacten die niet bij een groep horen uit de synchronisatie verwijderd."</string>
+ <string name="account_phone" msgid="4025734638492419713">"Alleen voor telefoon (niet gesynchroniseerd)"</string>
+ <string name="label_email_display_name" msgid="5537802602754309600">"Weergavenaam"</string>
+ <string name="call_home" msgid="1990519474420545392">"Bellen naar huis"</string>
+ <string name="call_mobile" msgid="7502236805487609178">"Bellen naar mobiel"</string>
+ <string name="call_work" msgid="5328785911463744028">"Bellen naar werk"</string>
+ <string name="call_fax_work" msgid="7467763592359059243">"Bellen naar fax werk"</string>
+ <string name="call_fax_home" msgid="8342175628887571876">"Bellen naar huisfax"</string>
+ <string name="call_pager" msgid="9003902812293983281">"Bellen naar pager"</string>
+ <string name="call_custom" msgid="7756571794763171802">"<xliff:g id="CUSTOM">%s</xliff:g> bellen"</string>
+ <string name="call_car" msgid="3280537320306436445">"Bellen naar autotelefoon"</string>
+ <string name="call_company_main" msgid="6105120947138711257">"Bellen naar hoofdkantoor"</string>
+ <string name="call_mms" msgid="6274041545876221437">"Bellen via MMS"</string>
+ <string name="call_radio" msgid="8296755876398357063">"Bellen naar radio"</string>
+ <string name="sms_home" msgid="7524332261493162995">"Sms\'en naar huis"</string>
+ <string name="sms_mobile" msgid="5200107250451030769">"Sms\'en naar mobiel"</string>
+ <string name="sms_work" msgid="2269624156655267740">"Sms\'en naar werk"</string>
+ <string name="sms_fax_work" msgid="8028189067816907075">"Sms\'en naar fax werk"</string>
+ <string name="sms_fax_home" msgid="9204042076306809634">"Sms\'en naar huisfax"</string>
+ <string name="sms_pager" msgid="7730404569637015192">"Sms\'en naar pager"</string>
+ <string name="sms_other" msgid="5131921487474531617">"Sms\'en naar overig"</string>
+ <string name="sms_custom" msgid="5932736853732191825">"Sms\'en naar <xliff:g id="CUSTOM">%s</xliff:g>"</string>
+ <string name="sms_car" msgid="7444227058437359641">"Sms\'en naar autotelefoon"</string>
+ <string name="sms_company_main" msgid="118970873419678087">"Sms\'en naar hoofdkantoor"</string>
+ <string name="sms_mms" msgid="4069352461380762677">"Sms\'en via MMS"</string>
+ <string name="sms_radio" msgid="3329166673433967820">"Sms\'en naar radio"</string>
+ <string name="email_home" msgid="8573740658148184279">"E-mailen naar huis"</string>
+ <string name="email_mobile" msgid="2042889209787989814">"E-mailen naar mobiel"</string>
+ <string name="email_work" msgid="2807430017302722689">"E-mailen naar werk"</string>
+ <string name="email_other" msgid="8093933498541795832">"E-mailen naar overig"</string>
+ <string name="email_custom" msgid="7548003991586214105">"E-mailadres <xliff:g id="CUSTOM">%s</xliff:g>"</string>
+ <string name="email" msgid="5668400997660065897">"E-mail"</string>
+ <string name="map_home" msgid="1243547733423343982">"Thuisadres weergeven"</string>
+ <string name="map_work" msgid="1360474076921878088">"Werkadres weergeven"</string>
+ <string name="map_other" msgid="5560707927535653892">"Andere adressen weergeven"</string>
+ <string name="map_custom" msgid="6184363799976265281">"<xliff:g id="CUSTOM">%s</xliff:g> adres weergeven"</string>
+ <string name="chat_aim" msgid="2588492205291249142">"Chatten via AIM"</string>
+ <string name="chat_msn" msgid="8041633440091073484">"Chatten via Windows Live"</string>
+ <string name="chat_yahoo" msgid="6629211142719943666">"Chatten via Yahoo"</string>
+ <string name="chat_skype" msgid="1210045020427480566">"Chatten via Skype"</string>
+ <string name="chat_qq" msgid="4294637812847719693">"Chatten via QQ"</string>
+ <string name="chat_gtalk" msgid="981575737258117697">"Chatten via Google Talk"</string>
+ <string name="chat_icq" msgid="8438405386153745775">"Chatten via ICQ"</string>
+ <string name="chat_jabber" msgid="7561444230307829609">"Chatten via Jabber"</string>
+ <string name="postal_street" msgid="8133143961580058972">"Straat"</string>
+ <string name="postal_pobox" msgid="4431938829180269821">"Postbus"</string>
+ <string name="postal_neighborhood" msgid="1450783874558956739">"Wijk"</string>
+ <string name="postal_city" msgid="6597491300084895548">"Plaats"</string>
+ <string name="postal_region" msgid="6045263193478437672">"Staat"</string>
+ <string name="postal_postcode" msgid="572136414136673751">"Postcode"</string>
+ <string name="postal_country" msgid="7638264508416368690">"Land"</string>
+ <string name="name_given" msgid="1687286314106019813">"Roepnaam"</string>
+ <string name="name_family" msgid="3416695586119999058">"Achternaam"</string>
+ <string name="name_prefix" msgid="59756378548779822">"Voorvoegsel"</string>
+ <string name="name_middle" msgid="8467433655992690326">"Tweede voornaam"</string>
+ <string name="name_suffix" msgid="3855278445375651441">"Achtervoegsel van naam"</string>
+ <string name="name_phonetic_given" msgid="6853570431394449191">"Fonetische roepnaam"</string>
+ <string name="name_phonetic_middle" msgid="8643721493320405200">"Fonetische tweede voornaam"</string>
+ <string name="name_phonetic_family" msgid="462095502140180305">"Fonetische achternaam"</string>
</resources>
diff --git a/res/values-pl/config.xml b/res/values-pl/config.xml
deleted file mode 100644
index 07b3c7e..0000000
--- a/res/values-pl/config.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for config_export_vcard_type (9145748078116159716) -->
- <skip />
- <string name="config_export_dir" msgid="222182743639478636">"/sdcard"</string>
- <!-- no translation found for config_export_file_prefix (3022868431158658122) -->
- <skip />
- <!-- no translation found for config_export_file_suffix (16505844221142195) -->
- <skip />
- <string name="config_export_file_extension" msgid="1758878818611339161">"vcf"</string>
- <!-- no translation found for config_export_extensions_to_consider (5095044502091950623) -->
- <skip />
-</resources>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index 8719d15..06523ca 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -40,11 +40,24 @@
<string name="menu_showBarcode" msgid="309973637178814132">"Pokaż kod kreskowy"</string>
<string name="menu_editContact" msgid="3452858480713561396">"Edytuj kontakt"</string>
<string name="menu_deleteContact" msgid="1916555454274101750">"Usuń kontakt"</string>
- <string name="menu_call" msgid="7359207953236681606">"Zadzwoń"</string>
- <string name="menu_sendSMS" msgid="5917289601666766617">"Wyślij SMS/MMS"</string>
+ <!-- no translation found for menu_call (3992595586042260618) -->
+ <skip />
+ <!-- no translation found for menu_sendSMS (5535886767547006515) -->
+ <skip />
<string name="menu_sendEmail" msgid="7293508859242926187">"Wyślij e-mail"</string>
<string name="menu_viewAddress" msgid="1814744325763202024">"Pokaż adres na mapie"</string>
<string name="menu_makeDefaultNumber" msgid="4838759253316649534">"Ustaw ten numer jako domyślny"</string>
+ <string name="menu_makeDefaultEmail" msgid="2599044610375789994">"Ustaw jako domyślny adres e-mail"</string>
+ <string name="menu_splitAggregate" msgid="7402548456225901170">"Podziel"</string>
+ <string name="splitAggregate_title" msgid="2053462872948058798">"Podziel kontakt"</string>
+ <string name="contactsSplitMessage" msgid="3439008547775199243">"Kontakt został podzielony"</string>
+ <string name="menu_joinAggregate" msgid="5027981918265667970">"Połącz"</string>
+ <string name="titleJoinAggregate" msgid="6970566008563147202">"Połącz kontakt"</string>
+ <string name="separatorJoinAggregateSuggestions" msgid="5494823322479834017">"Propozycje"</string>
+ <string name="separatorJoinAggregateAll" msgid="7939932265026181043">"Wszystkie kontakty"</string>
+ <string name="contactsJoinedMessage" msgid="7208148163607047389">"Kontakty zostały połączone"</string>
+ <string name="menu_contactOptions" msgid="1957061455705020617">"Opcje"</string>
+ <string name="contactOptionsTitle" msgid="8259347644090700915">"Opcje"</string>
<string name="deleteConfirmation_title" msgid="6394309508930335204">"Usuń"</string>
<string name="deleteConfirmation" msgid="811706994761610640">"Ten kontakt zostanie usunięty."</string>
<string name="menu_done" msgid="796017761764190697">"Gotowe"</string>
@@ -55,6 +68,9 @@
<string name="label_phonetic_name" msgid="2288082649573927286">"Fonetycznie"</string>
<string name="label_notes" msgid="8337354953278341042">"Notatki"</string>
<string name="label_ringtone" msgid="8833166825330686244">"Dzwonek"</string>
+ <string name="label_groups" msgid="7304551384542859026">"Grupy"</string>
+ <string name="group_list" msgid="8583361685440161307">", <xliff:g id="GROUPNAME">%s</xliff:g>"</string>
+ <string name="menu_viewGroup" msgid="1401851715586577551">"Edytuj grupy"</string>
<string name="ghostData_name" msgid="6490954238641157585">"Imię i nazwisko"</string>
<string name="ghostData_phonetic_name" msgid="7852749081984070902">"Nazwisko (fonetycznie)"</string>
<string name="ghostData_company" msgid="5414421120553765775">"Firma"</string>
@@ -64,6 +80,7 @@
<string name="ghostData_phone" msgid="6963153888271466620">"Numer telefonu"</string>
<string name="ghostData_email" msgid="6184537075551565919">"Adres e-mail"</string>
<string name="ghostData_postal" msgid="652611650594951897">"Adres pocztowy"</string>
+ <string name="ghostData_group" msgid="4504591380347534114">"Wyświetl grupę"</string>
<string name="invalidContactMessage" msgid="5816991830260044593">"Kontakt nie istnieje."</string>
<string name="pickerNewContactHeader" msgid="7750705279843568147">"Utwórz nowy kontakt"</string>
<string name="selectLabel" msgid="4255424123394910733">"Wybierz etykietę"</string>
@@ -80,7 +97,8 @@
<string name="photoPickerNotFoundText" msgid="431331662154342581">"W telefonie brak dostępnych zdjęć."</string>
<string name="attachToContact" msgid="8820530304406066714">"Ikona kontaktu"</string>
<string name="customLabelPickerTitle" msgid="1081475101983255212">"Nazwa etykiety niestandardowej"</string>
- <string name="menu_displayGroup" msgid="4603480747214476335">"Wyświetl grupę"</string>
+ <string name="menu_displayGroup" msgid="6022511302986343108">"Wyświetl grupy"</string>
+ <string name="displayGroups" msgid="2645969886032719891">"Wyświetl grupy"</string>
<string name="syncGroupPreference" msgid="9028361137161162861">"Synchronizacja grup"</string>
<string name="importFromSim" msgid="8383900146531125319">"Importuj kontakty"</string>
<string name="send_to_voicemail_checkbox" msgid="9001686764070676353">"Przekieruj połączenia bezpośrednio na pocztę głosową"</string>
@@ -94,12 +112,23 @@
<string name="select_group_title" msgid="7955698611959835612">"Grupy"</string>
<string name="groupEmpty" msgid="6661950109828194595">"Grupa „<xliff:g id="GROUPNAME">%s</xliff:g>” jest pusta."</string>
<string name="showAllGroups" msgid="5164410117611094297">"Wszystkie kontakty"</string>
+ <string name="showFilterPhones" msgid="4184858075465653970">"Tylko kontakty z numerami telefonów"</string>
+ <string name="showFilterPhonesDescrip" msgid="6644443248815191067">"Wyświetlaj tylko kontakty zawierające numery telefonów"</string>
+ <string name="headerContactGroups" msgid="1451383377487746789">"Grupy kontaktów"</string>
+ <plurals name="groupDescrip">
+ <item quantity="other" msgid="3507881585720628389">"Liczba kontaktów: <xliff:g id="COUNT">%0$d</xliff:g>"</item>
+ </plurals>
+ <plurals name="groupDescripPhones">
+ <item quantity="other" msgid="3816047547470490208">"Liczba kontaktów: <xliff:g id="COUNT_0">%1$d</xliff:g>, liczba kontaktów z numerami telefonów: <xliff:g id="COUNTWITHPHONES">%2$d</xliff:g>"</item>
+ </plurals>
<string name="syncAllGroups" msgid="7512169081224806890">"Synchronizuj wszystkie kontakty"</string>
<string name="groupNameMyContacts" msgid="5696566165170977126">"Moje kontakty"</string>
<string name="groupNameWithPhones" msgid="6981588604041120497">"Kontakty z numerami telefonu"</string>
<string name="starredInAndroid" msgid="6495527538140213440">"Z gwiazdką w systemie Android"</string>
<string name="contactCreatedToast" msgid="8740102129968688060">"Utworzono kontakt."</string>
<string name="contactSavedToast" msgid="7152589189385441091">"Kontakt został zapisany."</string>
+ <!-- no translation found for contactSavedErrorToast (9189098776225004666) -->
+ <skip />
<string name="listSeparatorCallNumber" msgid="7115321894439629408">"Wybierz numer"</string>
<string name="listSeparatorCallNumber_edit" msgid="2999167270900823334">"Numery telefonów"</string>
<string name="listSeparatorSendSmsMms" msgid="2480944736714739967">"Wyślij SMS/MMS"</string>
@@ -110,9 +139,11 @@
<string name="listSeparatorMapAddress" msgid="7076401301758190804">"Pokaż adres na mapie"</string>
<string name="listSeparatorMapAddress_edit" msgid="298711187672067985">"Adresy pocztowe"</string>
<string name="listSeparatorOrganizations" msgid="7514083358440762504">"Organizacje"</string>
+ <string name="listSeparatorGroups" msgid="1593940073983422450">"Grupy"</string>
<string name="listSeparatorOtherInformation" msgid="7844959649638482329">"Inne informacje"</string>
<string name="listSeparatorOtherInformation_edit" msgid="1326921768011367750">"Inne opcje"</string>
<string name="listSeparatorMore_edit" msgid="858454837482243176">"Więcej"</string>
+ <string name="socialStreamIconLabel" msgid="4367712449555075376">"Społeczeństwo"</string>
<string name="contactsIconLabel" msgid="7666609097606552806">"Kontakty"</string>
<string name="contactsFavoritesLabel" msgid="8417039765586853670">"Ulubione"</string>
<string name="dialerIconLabel" msgid="7228520966699542850">"Telefon"</string>
@@ -128,6 +159,7 @@
<string name="recentCalls_deleteAll" msgid="6352364392762163704">"Wyczyść rejestr połączeń"</string>
<string name="recentCalls_empty" msgid="247053222448663107">"Rejestr połączeń jest pusty."</string>
<string name="imei" msgid="3045126336951684285">"IMEI"</string>
+ <string name="meid" msgid="6210568493746275750">"Numer MEID"</string>
<string name="voicemail" msgid="3851469869202611441">"Poczta głosowa"</string>
<string name="unknown" msgid="740067747858270469">"Nieznane"</string>
<string name="private_num" msgid="6374339738119166953">"Numer prywatny"</string>
@@ -138,7 +170,14 @@
<string name="simContacts_title" msgid="27341688347689769">"Kontakty z karty SIM"</string>
<string name="contactsSyncPlug" msgid="7248276704957313698"><font fgcolor="#ffffffff">"Zsynchronizuj kontakty Google!"</font>" "\n"Po zsynchronizowaniu telefonu zawsze będziesz mieć kontakty pod ręką."</string>
<string name="noContactsHelpText" msgid="2249195687463896364">"Nie masz żadnych kontaktów."\n\n"Aby dodać kontakty, wybierz "<font fgcolor="#ffffffff"><b>"Menu"</b></font>" i wybierz polecenie:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Nowy kontakt"</b></font>" w celu utworzenia nowego kontaktu od początku"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importuj kontakty"</b></font>" w celu dodania kontaktów z karty SIM"\n</li></string>
- <string name="noContactsHelpTextWithSync" msgid="4927701496550314555">"Nie masz żadnych kontaktów."\n\n"Aby dodać kontakty, naciśnij "<font fgcolor="#ffffffff"><b>"Menu"</b></font>" i wybierz polecenie:"\n\n" "<li><font fgcolor="#ffffffff"><b>"Synchronizacja grup"</b></font>" w celu dodania kontaktów z nowego lub istniejącego konta Google"\n</li>\n" "<li><font fgcolor="#ffffffff"><b>"Nowy kontakt"</b></font>" w celu utworzenia nowego kontaktu od początku"\n</li>\n" "<li><font fgcolor="#ffffffff"><b>"Importuj kontakty"</b></font>" w celu dodania kontaktów z karty SIM"\n</li></string>
+ <!-- no translation found for noContactsHelpTextWithSync (2179024855526352215) -->
+ <skip />
+ <!-- no translation found for noContactsNoSimHelpText (645951808197910024) -->
+ <skip />
+ <!-- no translation found for noContactsNoSimHelpTextWithSync (511505839941079550) -->
+ <skip />
+ <!-- no translation found for noFavoritesHelpText (1705381563293866510) -->
+ <skip />
<string name="seclectSyncGroups_title" msgid="1235432026231325655">"Wybierz grupy do zsynchronizowania"</string>
<string name="liveFolder_all_label" msgid="1552523730090319259">"Wszystkie kontakty"</string>
<string name="liveFolder_favorites_label" msgid="2674341514070517105">"Oznaczone gwiazdką"</string>
@@ -173,6 +212,7 @@
<string name="favoritesFrquentSeparator" msgid="5007070838253932139">"Częste połączenia"</string>
<string name="add_contact_dlg_title" msgid="2789046541229846116">"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>
+ <string name="all_tab_label" msgid="4003124364397916826">"Wszystkie"</string>
<string name="description_image_button_one" msgid="1740638037139856139">"jeden"</string>
<string name="description_image_button_two" msgid="5882638439003731308">"dwa"</string>
<string name="description_image_button_three" msgid="8709731759376015180">"trzy"</string>
@@ -189,19 +229,27 @@
<string name="no_sdcard_message" msgid="7791906118138538259">"Nie wykryto karty SD"</string>
<string name="searching_vcard_title" msgid="4158580043290687793">"Wyszukiwanie danych VCard"</string>
<string name="select_import_type_title" msgid="2443742794103731022">"Skąd chcesz zaimportować kontakty?"</string>
- <string name="import_from_sim" msgid="3362158539070179154">"Karta SIM"</string>
- <string name="import_from_sdcard" msgid="8854793070007530689">"Karta SD"</string>
- <string name="import_all_vcard_string" msgid="2788222288962542415">"Importuj wszystkie pliki VCard"</string>
- <string name="import_one_vcard_string" msgid="4618119296910253689">"Zaimportuj jeden plik VCard"</string>
+ <!-- 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 />
+ <string name="import_one_vcard_string" msgid="9059163467020328433">"Importuj jeden plik vCard"</string>
+ <string name="import_multiple_vcard_string" msgid="3810226492811062392">"Importuj wiele plików vCard"</string>
+ <string name="import_all_vcard_string" msgid="5518136113853448474">"Importuj wszystkie pliki vCard"</string>
<string name="searching_vcard_message" msgid="2467573749792455605">"Wyszukiwanie danych VCard w pliku VCard"</string>
<string name="scanning_sdcard_failed_title" msgid="2788276113399585924">"Skanowanie karty SD nie powiodło się"</string>
<string name="scanning_sdcard_failed_message" msgid="925361244069058117">"Skanowanie karty SD nie powiodło się"</string>
- <string name="fail_reason_io_error" msgid="5922864781066136340">"Błąd wejścia/wyjścia"</string>
- <string name="fail_reason_vcard_parse_error" msgid="2129476089250836277">"Analiza danych VCard nie powiodła się"</string>
+ <string name="fail_reason_io_error" msgid="5949038573678667996">"Błąd wejścia/wyjścia (przyczyna: „<xliff:g id="FAIL_REASON">%s</xliff:g>”)"</string>
+ <string name="fail_reason_vcard_parse_error" msgid="8877496734269540489">"Nie można zanalizować pliku vCard z nieoczekiwanego powodu."</string>
+ <string name="fail_reason_vcard_not_supported_error" msgid="166568606246563552">"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="1489638537002538332">"Na karcie SD nie znaleziono żadnego pliku VCard"</string>
<string name="fail_reason_no_vcard_entry" msgid="3808734131680935043">"W wybranym zakresie nie znaleziono poprawnej pozycji VCard"</string>
+ <string name="fail_reason_failed_to_read_files" msgid="3659521123567134029">"Nie można zaimportować co najmniej jednego pliku (%s)."</string>
<string name="select_vcard_title" msgid="4615933643517710543">"Wybierz plik VCard"</string>
<string name="select_vcard_message" msgid="7028772212789057858">"Wybierz plik VCard do zaimportowania"</string>
+ <string name="progress_shower_message" msgid="5636525578293752526">"<xliff:g id="ACTION">%s</xliff:g>"\n"<xliff:g id="FILENAME">%s</xliff:g>"</string>
<string name="reading_vcard_title" msgid="430154704397375160">"Odczytywanie danych VCard"</string>
<string name="reading_vcard_message" msgid="7470486051482460267">"Odczytywanie plików VCard"</string>
<string name="importing_vcard_message" msgid="2466665710812588738">"Importowanie danych VCard"</string>
@@ -224,4 +272,104 @@
<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="1488865103809190124">"Dodaj 2-sekundową pauzę"</string>
+ <string name="add_wait" msgid="8347184715316058465">"Dodaj oczekiwanie"</string>
+ <string name="dial_button_label" msgid="7637725632722605863">"Wybierz numer"</string>
+ <!-- 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 />
+ <string name="quickcontact_missing_app" msgid="1181287640680679441">"Nie znaleziono aplikacji do obsługi tej akcji"</string>
+ <string name="quickcontact_remember_choice" msgid="2858356075887897116">"Zapamiętaj ten wybór"</string>
+ <string name="quickcontact_missing_name" msgid="7806083820928955853">"Nieznane"</string>
+ <string name="menu_accounts" msgid="8499114602017077970">"Konta"</string>
+ <!-- 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">"Nazwa"</string>
+ <string name="nicknameLabelsGroup" msgid="2891682101053358010">"Pseudonim"</string>
+ <string name="organizationLabelsGroup" msgid="2478611760751832035">"Organizacja"</string>
+ <string name="websiteLabelsGroup" msgid="4202998982804009261">"Adres witryny"</string>
+ <string name="type_short_home" msgid="7770424864090605384">"D"</string>
+ <string name="type_short_mobile" msgid="1655473281466676216">"K"</string>
+ <string name="type_short_work" msgid="4925330752504537861">"S"</string>
+ <string name="type_short_pager" msgid="2613818970827594238">"P"</string>
+ <string name="type_short_other" msgid="5669407180177236769">"I"</string>
+ <string name="edit_secondary_collapse" msgid="1855127176548440583">"Dodatkowe informacje szczegółowe"</string>
+ <string name="dialog_primary_name" msgid="5521591005692614833">"Nazwa główna"</string>
+ <string name="dialog_new_contact_account" msgid="9044704073286262197">"Utwórz kontakt na koncie"</string>
+ <string name="menu_sync_remove" msgid="3266725887008450161">"Usuń grupę synchronizacji"</string>
+ <!-- no translation found for dialog_sync_add (8267045393119375803) -->
+ <skip />
+ <!-- no translation found for display_more_groups (2682547080423434170) -->
+ <skip />
+ <string name="display_ungrouped" msgid="4357317661311803984">"(Kontakty rozgrupowane)"</string>
+ <string name="display_warn_remove_ungrouped" msgid="2314043155909167610">"Usunięcie grupy „<xliff:g id="GROUP">%s</xliff:g>” z ustawień synchronizacji spowoduje również usunięcie wszelkich rozgrupowanych kontaktów z tych ustawień."</string>
+ <string name="account_phone" msgid="4025734638492419713">"Tylko telefon (brak synchronizacji)"</string>
+ <string name="label_email_display_name" msgid="5537802602754309600">"Nazwa wyświetlana"</string>
+ <string name="call_home" msgid="1990519474420545392">"Telefon – domowy"</string>
+ <string name="call_mobile" msgid="7502236805487609178">"Telefon – komórkowy"</string>
+ <string name="call_work" msgid="5328785911463744028">"Telefon – służbowy"</string>
+ <string name="call_fax_work" msgid="7467763592359059243">"Telefon – faks służbowy"</string>
+ <string name="call_fax_home" msgid="8342175628887571876">"Telefon – faks domowy"</string>
+ <string name="call_pager" msgid="9003902812293983281">"Telefon – pager"</string>
+ <string name="call_custom" msgid="7756571794763171802">"Telefon – <xliff:g id="CUSTOM">%s</xliff:g>"</string>
+ <string name="call_car" msgid="3280537320306436445">"Telefon – samochód"</string>
+ <string name="call_company_main" msgid="6105120947138711257">"Telefon – firmowy główny"</string>
+ <string name="call_mms" msgid="6274041545876221437">"Telefon – wiadomość MMS"</string>
+ <string name="call_radio" msgid="8296755876398357063">"Telefon – radio"</string>
+ <string name="sms_home" msgid="7524332261493162995">"Tekst – domowy"</string>
+ <string name="sms_mobile" msgid="5200107250451030769">"Tekst – komórkowy"</string>
+ <string name="sms_work" msgid="2269624156655267740">"Tekst – służbowy"</string>
+ <string name="sms_fax_work" msgid="8028189067816907075">"Tekst – faks służbowy"</string>
+ <string name="sms_fax_home" msgid="9204042076306809634">"Tekst – faks domowy"</string>
+ <string name="sms_pager" msgid="7730404569637015192">"Tekst – pager"</string>
+ <string name="sms_other" msgid="5131921487474531617">"Tekst – inny"</string>
+ <string name="sms_custom" msgid="5932736853732191825">"Tekst – <xliff:g id="CUSTOM">%s</xliff:g>"</string>
+ <string name="sms_car" msgid="7444227058437359641">"Tekst – samochód"</string>
+ <string name="sms_company_main" msgid="118970873419678087">"Tekst – firmowy główny"</string>
+ <string name="sms_mms" msgid="4069352461380762677">"Tekst – wiadomość MMS"</string>
+ <string name="sms_radio" msgid="3329166673433967820">"Tekst – radio"</string>
+ <string name="email_home" msgid="8573740658148184279">"Adres e-mail – domowy"</string>
+ <string name="email_mobile" msgid="2042889209787989814">"Adres e-mail – komórkowy"</string>
+ <string name="email_work" msgid="2807430017302722689">"Adres e-mail – służbowy"</string>
+ <string name="email_other" msgid="8093933498541795832">"Adres e-mail – inny"</string>
+ <string name="email_custom" msgid="7548003991586214105">"Adres e-mail – <xliff:g id="CUSTOM">%s</xliff:g>"</string>
+ <string name="email" msgid="5668400997660065897">"Adres e-mail"</string>
+ <string name="map_home" msgid="1243547733423343982">"Wyświetl adres domowy"</string>
+ <string name="map_work" msgid="1360474076921878088">"Wyświetl adres służbowy"</string>
+ <string name="map_other" msgid="5560707927535653892">"Wyświetl inny adres"</string>
+ <string name="map_custom" msgid="6184363799976265281">"Wyświetl adres: <xliff:g id="CUSTOM">%s</xliff:g>"</string>
+ <string name="chat_aim" msgid="2588492205291249142">"Czat w AIM"</string>
+ <string name="chat_msn" msgid="8041633440091073484">"Czat w Windows Live"</string>
+ <string name="chat_yahoo" msgid="6629211142719943666">"Czat w Yahoo"</string>
+ <string name="chat_skype" msgid="1210045020427480566">"Czat w Skype"</string>
+ <string name="chat_qq" msgid="4294637812847719693">"Czat w QQ"</string>
+ <string name="chat_gtalk" msgid="981575737258117697">"Czat w Google Talk"</string>
+ <string name="chat_icq" msgid="8438405386153745775">"Czat w ICQ"</string>
+ <string name="chat_jabber" msgid="7561444230307829609">"Czat w Jabberze"</string>
+ <string name="postal_street" msgid="8133143961580058972">"Ulica"</string>
+ <string name="postal_pobox" msgid="4431938829180269821">"Skrytka pocztowa"</string>
+ <string name="postal_neighborhood" msgid="1450783874558956739">"Okolica"</string>
+ <string name="postal_city" msgid="6597491300084895548">"Miasto"</string>
+ <string name="postal_region" msgid="6045263193478437672">"Województwo"</string>
+ <string name="postal_postcode" msgid="572136414136673751">"Kod pocztowy"</string>
+ <string name="postal_country" msgid="7638264508416368690">"Kraj"</string>
+ <string name="name_given" msgid="1687286314106019813">"Imię"</string>
+ <string name="name_family" msgid="3416695586119999058">"Nazwisko"</string>
+ <string name="name_prefix" msgid="59756378548779822">"Przedrostek nazwiska"</string>
+ <string name="name_middle" msgid="8467433655992690326">"Drugie imię"</string>
+ <string name="name_suffix" msgid="3855278445375651441">"Przyrostek nazwiska"</string>
+ <string name="name_phonetic_given" msgid="6853570431394449191">"Imię (fonetycznie)"</string>
+ <string name="name_phonetic_middle" msgid="8643721493320405200">"Drugie imię (fonetycznie)"</string>
+ <string name="name_phonetic_family" msgid="462095502140180305">"Nazwisko (fonetycznie)"</string>
</resources>
diff --git a/res/values-pt-rPT/config.xml b/res/values-pt-rPT/config.xml
deleted file mode 100644
index 07b3c7e..0000000
--- a/res/values-pt-rPT/config.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for config_export_vcard_type (9145748078116159716) -->
- <skip />
- <string name="config_export_dir" msgid="222182743639478636">"/sdcard"</string>
- <!-- no translation found for config_export_file_prefix (3022868431158658122) -->
- <skip />
- <!-- no translation found for config_export_file_suffix (16505844221142195) -->
- <skip />
- <string name="config_export_file_extension" msgid="1758878818611339161">"vcf"</string>
- <!-- no translation found for config_export_extensions_to_consider (5095044502091950623) -->
- <skip />
-</resources>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index 538bcde..3de0004 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -40,11 +40,24 @@
<string name="menu_showBarcode" msgid="309973637178814132">"Mostrar código de barras"</string>
<string name="menu_editContact" msgid="3452858480713561396">"Editar contacto"</string>
<string name="menu_deleteContact" msgid="1916555454274101750">"Eliminar contacto"</string>
- <string name="menu_call" msgid="7359207953236681606">"Chamada"</string>
- <string name="menu_sendSMS" msgid="5917289601666766617">"Enviar SMS/MMS"</string>
+ <!-- no translation found for menu_call (3992595586042260618) -->
+ <skip />
+ <!-- no translation found for menu_sendSMS (5535886767547006515) -->
+ <skip />
<string name="menu_sendEmail" msgid="7293508859242926187">"Enviar e-mail"</string>
<string name="menu_viewAddress" msgid="1814744325763202024">"Endereço no mapa"</string>
<string name="menu_makeDefaultNumber" msgid="4838759253316649534">"Utilizar como número predefinido"</string>
+ <string name="menu_makeDefaultEmail" msgid="2599044610375789994">"Predefinir e-mail"</string>
+ <string name="menu_splitAggregate" msgid="7402548456225901170">"Separar"</string>
+ <string name="splitAggregate_title" msgid="2053462872948058798">"Separar contacto"</string>
+ <string name="contactsSplitMessage" msgid="3439008547775199243">"Contacto separado"</string>
+ <string name="menu_joinAggregate" msgid="5027981918265667970">"Associar"</string>
+ <string name="titleJoinAggregate" msgid="6970566008563147202">"Associar contacto"</string>
+ <string name="separatorJoinAggregateSuggestions" msgid="5494823322479834017">"Sugestões"</string>
+ <string name="separatorJoinAggregateAll" msgid="7939932265026181043">"Todos os contactos"</string>
+ <string name="contactsJoinedMessage" msgid="7208148163607047389">"Contactos associados"</string>
+ <string name="menu_contactOptions" msgid="1957061455705020617">"Opções"</string>
+ <string name="contactOptionsTitle" msgid="8259347644090700915">"Opções"</string>
<string name="deleteConfirmation_title" msgid="6394309508930335204">"Eliminar"</string>
<string name="deleteConfirmation" msgid="811706994761610640">"Este contacto será eliminado."</string>
<string name="menu_done" msgid="796017761764190697">"Concluído"</string>
@@ -55,6 +68,9 @@
<string name="label_phonetic_name" msgid="2288082649573927286">"Fonético"</string>
<string name="label_notes" msgid="8337354953278341042">"Notas"</string>
<string name="label_ringtone" msgid="8833166825330686244">"Toque"</string>
+ <string name="label_groups" msgid="7304551384542859026">"Grupos"</string>
+ <string name="group_list" msgid="8583361685440161307">", <xliff:g id="GROUPNAME">%s</xliff:g>"</string>
+ <string name="menu_viewGroup" msgid="1401851715586577551">"Editar grupos"</string>
<string name="ghostData_name" msgid="6490954238641157585">"Primeiro e último"</string>
<string name="ghostData_phonetic_name" msgid="7852749081984070902">"Nome fonético"</string>
<string name="ghostData_company" msgid="5414421120553765775">"Empresa"</string>
@@ -64,6 +80,7 @@
<string name="ghostData_phone" msgid="6963153888271466620">"Número de telefone"</string>
<string name="ghostData_email" msgid="6184537075551565919">"Endereço de e-mail"</string>
<string name="ghostData_postal" msgid="652611650594951897">"Endereço postal"</string>
+ <string name="ghostData_group" msgid="4504591380347534114">"Mostrar grupo"</string>
<string name="invalidContactMessage" msgid="5816991830260044593">"O contacto não existe."</string>
<string name="pickerNewContactHeader" msgid="7750705279843568147">"Criar novo contacto"</string>
<string name="selectLabel" msgid="4255424123394910733">"Seleccionar etiqueta"</string>
@@ -80,7 +97,8 @@
<string name="photoPickerNotFoundText" msgid="431331662154342581">"Não existem imagens disponíveis no telefone."</string>
<string name="attachToContact" msgid="8820530304406066714">"Ícone de contacto"</string>
<string name="customLabelPickerTitle" msgid="1081475101983255212">"Nome da etiqueta personalizada"</string>
- <string name="menu_displayGroup" msgid="4603480747214476335">"Mostrar grupos"</string>
+ <string name="menu_displayGroup" msgid="6022511302986343108">"Grupos a apresentar"</string>
+ <string name="displayGroups" msgid="2645969886032719891">"Grupos a apresentar"</string>
<string name="syncGroupPreference" msgid="9028361137161162861">"Editar grupos sincronizados"</string>
<string name="importFromSim" msgid="8383900146531125319">"Importar contactos"</string>
<string name="send_to_voicemail_checkbox" msgid="9001686764070676353">"Enviar as chamadas directamente para o correio de voz"</string>
@@ -94,12 +112,23 @@
<string name="select_group_title" msgid="7955698611959835612">"Grupos"</string>
<string name="groupEmpty" msgid="6661950109828194595">"O seu grupo \"<xliff:g id="GROUPNAME">%s</xliff:g>\" está vazio."</string>
<string name="showAllGroups" msgid="5164410117611094297">"Todos os contactos"</string>
+ <string name="showFilterPhones" msgid="4184858075465653970">"Apenas contactos com telefones"</string>
+ <string name="showFilterPhonesDescrip" msgid="6644443248815191067">"Apresentar apenas contactos que tenham números de telefone"</string>
+ <string name="headerContactGroups" msgid="1451383377487746789">"Grupos de contacto"</string>
+ <plurals name="groupDescrip">
+ <item quantity="other" msgid="3507881585720628389">"<xliff:g id="COUNT">%0$d</xliff:g> contactos"</item>
+ </plurals>
+ <plurals name="groupDescripPhones">
+ <item quantity="other" msgid="3816047547470490208">"<xliff:g id="COUNT_0">%1$d</xliff:g> contactos, <xliff:g id="COUNTWITHPHONES">%2$d</xliff:g> com telefones"</item>
+ </plurals>
<string name="syncAllGroups" msgid="7512169081224806890">"Sincronizar todos os contactos"</string>
<string name="groupNameMyContacts" msgid="5696566165170977126">"Os meus contactos"</string>
<string name="groupNameWithPhones" msgid="6981588604041120497">"Contactos com números de telefone"</string>
<string name="starredInAndroid" msgid="6495527538140213440">"Marcado com estrela no Android"</string>
<string name="contactCreatedToast" msgid="8740102129968688060">"Contacto criado."</string>
<string name="contactSavedToast" msgid="7152589189385441091">"Contacto guardado."</string>
+ <!-- no translation found for contactSavedErrorToast (9189098776225004666) -->
+ <skip />
<string name="listSeparatorCallNumber" msgid="7115321894439629408">"Marcar número"</string>
<string name="listSeparatorCallNumber_edit" msgid="2999167270900823334">"Números de telefone"</string>
<string name="listSeparatorSendSmsMms" msgid="2480944736714739967">"Enviar SMS/MMS"</string>
@@ -110,9 +139,11 @@
<string name="listSeparatorMapAddress" msgid="7076401301758190804">"Endereço no mapa"</string>
<string name="listSeparatorMapAddress_edit" msgid="298711187672067985">"Endereços postais"</string>
<string name="listSeparatorOrganizations" msgid="7514083358440762504">"Organizações"</string>
+ <string name="listSeparatorGroups" msgid="1593940073983422450">"Grupos"</string>
<string name="listSeparatorOtherInformation" msgid="7844959649638482329">"Outras informações"</string>
<string name="listSeparatorOtherInformation_edit" msgid="1326921768011367750">"Outras opções"</string>
<string name="listSeparatorMore_edit" msgid="858454837482243176">"Mais"</string>
+ <string name="socialStreamIconLabel" msgid="4367712449555075376">"Social"</string>
<string name="contactsIconLabel" msgid="7666609097606552806">"Contactos"</string>
<string name="contactsFavoritesLabel" msgid="8417039765586853670">"Favoritos"</string>
<string name="dialerIconLabel" msgid="7228520966699542850">"Marcador"</string>
@@ -128,6 +159,7 @@
<string name="recentCalls_deleteAll" msgid="6352364392762163704">"Limpar registo de chamadas"</string>
<string name="recentCalls_empty" msgid="247053222448663107">"O registo de chamadas está vazio."</string>
<string name="imei" msgid="3045126336951684285">"IMEI"</string>
+ <string name="meid" msgid="6210568493746275750">"MEID"</string>
<string name="voicemail" msgid="3851469869202611441">"Correio de voz"</string>
<string name="unknown" msgid="740067747858270469">"Desconhecido"</string>
<string name="private_num" msgid="6374339738119166953">"Número particular"</string>
@@ -138,7 +170,14 @@
<string name="simContacts_title" msgid="27341688347689769">"Contactos no cartão SIM"</string>
<string name="contactsSyncPlug" msgid="7248276704957313698"><font fgcolor="#ffffffff">"Sincronize os seus contactos do Google!"</font>" "\n"Depois de efectuar a sincronização para o seu telefone, os seus contactos ficarão disponíveis onde quer que esteja."</string>
<string name="noContactsHelpText" msgid="2249195687463896364">"Não tem contactos."\n\n"Para adicionar contactos, prima "<font fgcolor="#ffffffff"><b>"Menu"</b></font>" e seleccione:"\n\n<li><font fgcolor="#ffffffff"><b>"Novo contacto"</b></font>" para criar um novo contacto desde o princípio"\n</li>"."\n<li><font fgcolor="#ffffffff"><b>"Importar contactos"</b></font>" para adicionar contactos a partir do cartão SIM"\n</li></string>
- <string name="noContactsHelpTextWithSync" msgid="4927701496550314555">"Não tem nenhum contacto."\n\n"Para adicionar contactos, prima "<font fgcolor="#ffffffff"><b>"Menu"</b></font>" e seleccione:"\n\n<li><font fgcolor="#ffffffff"><b>"Editar grupos sincronizados"</b></font>" para adicionar a partir de uma Conta Google nova ou já existente"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Novo contacto"</b></font>" para criar um novo contacto desde o início"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importar contactos"</b></font>" para adicionar contactos a partir do cartão SIM"\n</li></string>
+ <!-- no translation found for noContactsHelpTextWithSync (2179024855526352215) -->
+ <skip />
+ <!-- no translation found for noContactsNoSimHelpText (645951808197910024) -->
+ <skip />
+ <!-- no translation found for noContactsNoSimHelpTextWithSync (511505839941079550) -->
+ <skip />
+ <!-- no translation found for noFavoritesHelpText (1705381563293866510) -->
+ <skip />
<string name="seclectSyncGroups_title" msgid="1235432026231325655">"Seleccionar grupos para sincronizar"</string>
<string name="liveFolder_all_label" msgid="1552523730090319259">"Todos os contactos"</string>
<string name="liveFolder_favorites_label" msgid="2674341514070517105">"Marcado com estrela"</string>
@@ -173,6 +212,7 @@
<string name="favoritesFrquentSeparator" msgid="5007070838253932139">"Números de marcação frequente"</string>
<string name="add_contact_dlg_title" msgid="2789046541229846116">"Adicionar contacto"</string>
<string name="add_contact_dlg_message_fmt" msgid="7986472669444326576">"Adicionar \"<xliff:g id="EMAIL">%s</xliff:g>\" aos contactos?"</string>
+ <string name="all_tab_label" msgid="4003124364397916826">"Todos"</string>
<string name="description_image_button_one" msgid="1740638037139856139">"um"</string>
<string name="description_image_button_two" msgid="5882638439003731308">"dois"</string>
<string name="description_image_button_three" msgid="8709731759376015180">"três"</string>
@@ -189,19 +229,27 @@
<string name="no_sdcard_message" msgid="7791906118138538259">"Não foi detectado nenhum cartão SD"</string>
<string name="searching_vcard_title" msgid="4158580043290687793">"A procurar VCard"</string>
<string name="select_import_type_title" msgid="2443742794103731022">"De onde pretende importar contactos?"</string>
- <string name="import_from_sim" msgid="3362158539070179154">"Cartão SIM"</string>
- <string name="import_from_sdcard" msgid="8854793070007530689">"Cartão SD"</string>
- <string name="import_all_vcard_string" msgid="2788222288962542415">"Importar todos os ficheiros VCard"</string>
- <string name="import_one_vcard_string" msgid="4618119296910253689">"Importar um ficheiro VCard"</string>
+ <!-- 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 />
+ <string name="import_one_vcard_string" msgid="9059163467020328433">"Importar um ficheiro VCard"</string>
+ <string name="import_multiple_vcard_string" msgid="3810226492811062392">"Importar vários ficheiros vCard"</string>
+ <string name="import_all_vcard_string" msgid="5518136113853448474">"Importar todos os ficheiros VCard"</string>
<string name="searching_vcard_message" msgid="2467573749792455605">"A procurar dados VCard no VCard"</string>
<string name="scanning_sdcard_failed_title" msgid="2788276113399585924">"Falha na análise do cartão SD"</string>
<string name="scanning_sdcard_failed_message" msgid="925361244069058117">"Falha na análise do cartão SD"</string>
- <string name="fail_reason_io_error" msgid="5922864781066136340">"Erro de E/S"</string>
- <string name="fail_reason_vcard_parse_error" msgid="2129476089250836277">"Falha na análise do VCard"</string>
+ <string name="fail_reason_io_error" msgid="5949038573678667996">"Erro de E/S (Motivo: \"<xliff:g id="FAIL_REASON">%s</xliff:g>\")"</string>
+ <string name="fail_reason_vcard_parse_error" msgid="8877496734269540489">"Falha na análise do VCard por um motivo inesperado"</string>
+ <string name="fail_reason_vcard_not_supported_error" msgid="166568606246563552">"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="1489638537002538332">"Não foi encontrado nenhum ficheiro VCard no cartão SD"</string>
<string name="fail_reason_no_vcard_entry" msgid="3808734131680935043">"Não foi encontrada nenhuma entrada VCard válida para a sua selecção"</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="select_vcard_title" msgid="4615933643517710543">"Seleccionar ficheiro VCard"</string>
<string name="select_vcard_message" msgid="7028772212789057858">"Seleccione um ficheiro VCard para importar"</string>
+ <string name="progress_shower_message" msgid="5636525578293752526">"<xliff:g id="ACTION">%s</xliff:g>"\n"<xliff:g id="FILENAME">%s</xliff:g>"</string>
<string name="reading_vcard_title" msgid="430154704397375160">"A ler VCard"</string>
<string name="reading_vcard_message" msgid="7470486051482460267">"A ler ficheiro(s) VCard"</string>
<string name="importing_vcard_message" msgid="2466665710812588738">"A importar dados VCard"</string>
@@ -224,4 +272,104 @@
<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="1488865103809190124">"Adicionar Pausa de 2 seg"</string>
+ <string name="add_wait" msgid="8347184715316058465">"Adicionar Esperar"</string>
+ <string name="dial_button_label" msgid="7637725632722605863">"Marcar"</string>
+ <!-- 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 />
+ <string name="quickcontact_missing_app" msgid="1181287640680679441">"Não foram encontradas aplicações para executar esta acção"</string>
+ <string name="quickcontact_remember_choice" msgid="2858356075887897116">"Memorizar esta escolha"</string>
+ <string name="quickcontact_missing_name" msgid="7806083820928955853">"Desconhecido"</string>
+ <string name="menu_accounts" msgid="8499114602017077970">"Contas"</string>
+ <!-- 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">"Nome"</string>
+ <string name="nicknameLabelsGroup" msgid="2891682101053358010">"Alcunha"</string>
+ <string name="organizationLabelsGroup" msgid="2478611760751832035">"Organização"</string>
+ <string name="websiteLabelsGroup" msgid="4202998982804009261">"Web site"</string>
+ <string name="type_short_home" msgid="7770424864090605384">"R"</string>
+ <string name="type_short_mobile" msgid="1655473281466676216">"T"</string>
+ <string name="type_short_work" msgid="4925330752504537861">"E"</string>
+ <string name="type_short_pager" msgid="2613818970827594238">"P"</string>
+ <string name="type_short_other" msgid="5669407180177236769">"O"</string>
+ <string name="edit_secondary_collapse" msgid="1855127176548440583">"Detalhes secundários"</string>
+ <string name="dialog_primary_name" msgid="5521591005692614833">"Nome principal"</string>
+ <string name="dialog_new_contact_account" msgid="9044704073286262197">"Criar contacto subordinado à conta"</string>
+ <string name="menu_sync_remove" msgid="3266725887008450161">"Remover grupo de sincronização"</string>
+ <!-- no translation found for dialog_sync_add (8267045393119375803) -->
+ <skip />
+ <!-- no translation found for display_more_groups (2682547080423434170) -->
+ <skip />
+ <string name="display_ungrouped" msgid="4357317661311803984">"(Contactos desagrupados)"</string>
+ <string name="display_warn_remove_ungrouped" msgid="2314043155909167610">"Ao remover \"<xliff:g id="GROUP">%s</xliff:g>\" da sincronização, removerá também quaisquer contactos não agrupados."</string>
+ <string name="account_phone" msgid="4025734638492419713">"Apenas telefone (não sincronizado)"</string>
+ <string name="label_email_display_name" msgid="5537802602754309600">"Nome a apresentar"</string>
+ <string name="call_home" msgid="1990519474420545392">"Ligar para residência"</string>
+ <string name="call_mobile" msgid="7502236805487609178">"Ligar para telemóvel"</string>
+ <string name="call_work" msgid="5328785911463744028">"Ligar para emprego"</string>
+ <string name="call_fax_work" msgid="7467763592359059243">"Ligar para fax do emprego"</string>
+ <string name="call_fax_home" msgid="8342175628887571876">"Ligar para o fax da residência"</string>
+ <string name="call_pager" msgid="9003902812293983281">"Ligar para pager"</string>
+ <string name="call_custom" msgid="7756571794763171802">"Ligar para <xliff:g id="CUSTOM">%s</xliff:g>"</string>
+ <string name="call_car" msgid="3280537320306436445">"Ligar para automóvel"</string>
+ <string name="call_company_main" msgid="6105120947138711257">"Ligar para telefone principal da empresa"</string>
+ <string name="call_mms" msgid="6274041545876221437">"Ligar por MMS"</string>
+ <string name="call_radio" msgid="8296755876398357063">"Ligar para rádio"</string>
+ <string name="sms_home" msgid="7524332261493162995">"Enviar SMS para residência"</string>
+ <string name="sms_mobile" msgid="5200107250451030769">"Enviar SMS para telemóvel"</string>
+ <string name="sms_work" msgid="2269624156655267740">"Enviar SMS para emprego"</string>
+ <string name="sms_fax_work" msgid="8028189067816907075">"Enviar SMS para fax do emprego"</string>
+ <string name="sms_fax_home" msgid="9204042076306809634">"Enviar SMS para fax da residência"</string>
+ <string name="sms_pager" msgid="7730404569637015192">"Enviar SMS para pager"</string>
+ <string name="sms_other" msgid="5131921487474531617">"Enviar SMS para outros"</string>
+ <string name="sms_custom" msgid="5932736853732191825">"Enviar SMS a <xliff:g id="CUSTOM">%s</xliff:g>"</string>
+ <string name="sms_car" msgid="7444227058437359641">"Enviar SMS para automóvel"</string>
+ <string name="sms_company_main" msgid="118970873419678087">"Enviar SMS para telefone principal da empresa"</string>
+ <string name="sms_mms" msgid="4069352461380762677">"Enviar MMS"</string>
+ <string name="sms_radio" msgid="3329166673433967820">"Enviar SMS para rádio"</string>
+ <string name="email_home" msgid="8573740658148184279">"Enviar e-mail para residência"</string>
+ <string name="email_mobile" msgid="2042889209787989814">"Enviar e-mail para telemóvel"</string>
+ <string name="email_work" msgid="2807430017302722689">"Enviar e-mail para emprego"</string>
+ <string name="email_other" msgid="8093933498541795832">"Enviar e-mail para outros"</string>
+ <string name="email_custom" msgid="7548003991586214105">"Enviar e-mail a <xliff:g id="CUSTOM">%s</xliff:g>"</string>
+ <string name="email" msgid="5668400997660065897">"E-mail"</string>
+ <string name="map_home" msgid="1243547733423343982">"Ver endereço da residência"</string>
+ <string name="map_work" msgid="1360474076921878088">"Ver endereço do emprego"</string>
+ <string name="map_other" msgid="5560707927535653892">"Ver outros endereços"</string>
+ <string name="map_custom" msgid="6184363799976265281">"Ver endereço de <xliff:g id="CUSTOM">%s</xliff:g>"</string>
+ <string name="chat_aim" msgid="2588492205291249142">"Chat utilizando AIM"</string>
+ <string name="chat_msn" msgid="8041633440091073484">"Chat utilizando Windows Live"</string>
+ <string name="chat_yahoo" msgid="6629211142719943666">"Chat utilizando Yahoo"</string>
+ <string name="chat_skype" msgid="1210045020427480566">"Chat utilizando Skype"</string>
+ <string name="chat_qq" msgid="4294637812847719693">"Chat utilizando QQ"</string>
+ <string name="chat_gtalk" msgid="981575737258117697">"Chat através do Google Talk"</string>
+ <string name="chat_icq" msgid="8438405386153745775">"Chat utilizando ICQ"</string>
+ <string name="chat_jabber" msgid="7561444230307829609">"Chat utilizando Jabber"</string>
+ <string name="postal_street" msgid="8133143961580058972">"Rua"</string>
+ <string name="postal_pobox" msgid="4431938829180269821">"Apartado"</string>
+ <string name="postal_neighborhood" msgid="1450783874558956739">"Vizinhança"</string>
+ <string name="postal_city" msgid="6597491300084895548">"Cidade"</string>
+ <string name="postal_region" msgid="6045263193478437672">"Região"</string>
+ <string name="postal_postcode" msgid="572136414136673751">"Código postal"</string>
+ <string name="postal_country" msgid="7638264508416368690">"País"</string>
+ <string name="name_given" msgid="1687286314106019813">"Nome atribuído"</string>
+ <string name="name_family" msgid="3416695586119999058">"Apelido"</string>
+ <string name="name_prefix" msgid="59756378548779822">"Prefixo do nome"</string>
+ <string name="name_middle" msgid="8467433655992690326">"Segundo nome"</string>
+ <string name="name_suffix" msgid="3855278445375651441">"Sufixo do nome"</string>
+ <string name="name_phonetic_given" msgid="6853570431394449191">"Fonética do nome atribuído"</string>
+ <string name="name_phonetic_middle" msgid="8643721493320405200">"Fonética do segundo nome"</string>
+ <string name="name_phonetic_family" msgid="462095502140180305">"Fonética do apelido"</string>
</resources>
diff --git a/res/values-pt/config.xml b/res/values-pt/config.xml
deleted file mode 100644
index 07b3c7e..0000000
--- a/res/values-pt/config.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for config_export_vcard_type (9145748078116159716) -->
- <skip />
- <string name="config_export_dir" msgid="222182743639478636">"/sdcard"</string>
- <!-- no translation found for config_export_file_prefix (3022868431158658122) -->
- <skip />
- <!-- no translation found for config_export_file_suffix (16505844221142195) -->
- <skip />
- <string name="config_export_file_extension" msgid="1758878818611339161">"vcf"</string>
- <!-- no translation found for config_export_extensions_to_consider (5095044502091950623) -->
- <skip />
-</resources>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index 228d028..6438046 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -40,11 +40,24 @@
<string name="menu_showBarcode" msgid="309973637178814132">"Mostrar código de barras"</string>
<string name="menu_editContact" msgid="3452858480713561396">"Editar contato"</string>
<string name="menu_deleteContact" msgid="1916555454274101750">"Excluir contato"</string>
- <string name="menu_call" msgid="7359207953236681606">"Chamar"</string>
- <string name="menu_sendSMS" msgid="5917289601666766617">"Enviar SMS/MMS"</string>
+ <!-- no translation found for menu_call (3992595586042260618) -->
+ <skip />
+ <!-- no translation found for menu_sendSMS (5535886767547006515) -->
+ <skip />
<string name="menu_sendEmail" msgid="7293508859242926187">"Enviar e-mail"</string>
<string name="menu_viewAddress" msgid="1814744325763202024">"Endereço no mapa"</string>
<string name="menu_makeDefaultNumber" msgid="4838759253316649534">"Criar número padrão"</string>
+ <string name="menu_makeDefaultEmail" msgid="2599044610375789994">"Tornar o e-mail padrão"</string>
+ <string name="menu_splitAggregate" msgid="7402548456225901170">"Dividir"</string>
+ <string name="splitAggregate_title" msgid="2053462872948058798">"Dividir contato"</string>
+ <string name="contactsSplitMessage" msgid="3439008547775199243">"Divisão de contato"</string>
+ <string name="menu_joinAggregate" msgid="5027981918265667970">"Participar"</string>
+ <string name="titleJoinAggregate" msgid="6970566008563147202">"Integrar contato"</string>
+ <string name="separatorJoinAggregateSuggestions" msgid="5494823322479834017">"Sugestões"</string>
+ <string name="separatorJoinAggregateAll" msgid="7939932265026181043">"Todos os contatos"</string>
+ <string name="contactsJoinedMessage" msgid="7208148163607047389">"Contatos que entraram"</string>
+ <string name="menu_contactOptions" msgid="1957061455705020617">"Opções"</string>
+ <string name="contactOptionsTitle" msgid="8259347644090700915">"Opções"</string>
<string name="deleteConfirmation_title" msgid="6394309508930335204">"Excluir"</string>
<string name="deleteConfirmation" msgid="811706994761610640">"Esse evento será excluído."</string>
<string name="menu_done" msgid="796017761764190697">"Concluído"</string>
@@ -55,6 +68,9 @@
<string name="label_phonetic_name" msgid="2288082649573927286">"Fonética"</string>
<string name="label_notes" msgid="8337354953278341042">"Observações"</string>
<string name="label_ringtone" msgid="8833166825330686244">"Toque"</string>
+ <string name="label_groups" msgid="7304551384542859026">"Grupos"</string>
+ <string name="group_list" msgid="8583361685440161307">", <xliff:g id="GROUPNAME">%s</xliff:g>"</string>
+ <string name="menu_viewGroup" msgid="1401851715586577551">"Editar grupos"</string>
<string name="ghostData_name" msgid="6490954238641157585">"Nome e sobrenome"</string>
<string name="ghostData_phonetic_name" msgid="7852749081984070902">"Nome fonético"</string>
<string name="ghostData_company" msgid="5414421120553765775">"Empresa"</string>
@@ -64,6 +80,7 @@
<string name="ghostData_phone" msgid="6963153888271466620">"Número de telefone"</string>
<string name="ghostData_email" msgid="6184537075551565919">"Endereço de e-mail"</string>
<string name="ghostData_postal" msgid="652611650594951897">"Endereço postal"</string>
+ <string name="ghostData_group" msgid="4504591380347534114">"Exibir grupo"</string>
<string name="invalidContactMessage" msgid="5816991830260044593">"O contato não existe."</string>
<string name="pickerNewContactHeader" msgid="7750705279843568147">"Criar novo contato"</string>
<string name="selectLabel" msgid="4255424123394910733">"Selecionar marcador"</string>
@@ -80,7 +97,8 @@
<string name="photoPickerNotFoundText" msgid="431331662154342581">"Nenhuma imagem disponível no telefone."</string>
<string name="attachToContact" msgid="8820530304406066714">"Ícone de contato"</string>
<string name="customLabelPickerTitle" msgid="1081475101983255212">"Personalizar nome do marcador"</string>
- <string name="menu_displayGroup" msgid="4603480747214476335">"Exibir grupo"</string>
+ <string name="menu_displayGroup" msgid="6022511302986343108">"Exibir grupos"</string>
+ <string name="displayGroups" msgid="2645969886032719891">"Exibir grupos"</string>
<string name="syncGroupPreference" msgid="9028361137161162861">"Editar grupos sincronizados"</string>
<string name="importFromSim" msgid="8383900146531125319">"Importar contatos"</string>
<string name="send_to_voicemail_checkbox" msgid="9001686764070676353">"Enviar chamadas diretamente para o correio de voz"</string>
@@ -94,12 +112,23 @@
<string name="select_group_title" msgid="7955698611959835612">"Grupos"</string>
<string name="groupEmpty" msgid="6661950109828194595">"Seu grupo \"<xliff:g id="GROUPNAME">%s</xliff:g>\" está vazio."</string>
<string name="showAllGroups" msgid="5164410117611094297">"Todos os contatos"</string>
+ <string name="showFilterPhones" msgid="4184858075465653970">"Apenas contatos com telefones"</string>
+ <string name="showFilterPhonesDescrip" msgid="6644443248815191067">"Exibir apenas os contatos com números de telefone"</string>
+ <string name="headerContactGroups" msgid="1451383377487746789">"Grupos de contato"</string>
+ <plurals name="groupDescrip">
+ <item quantity="other" msgid="3507881585720628389">"<xliff:g id="COUNT">%0$d</xliff:g> contatos"</item>
+ </plurals>
+ <plurals name="groupDescripPhones">
+ <item quantity="other" msgid="3816047547470490208">"<xliff:g id="COUNT_0">%1$d</xliff:g> contatos, <xliff:g id="COUNTWITHPHONES">%2$d</xliff:g> com telefones"</item>
+ </plurals>
<string name="syncAllGroups" msgid="7512169081224806890">"Sincronizar todos os contatos"</string>
<string name="groupNameMyContacts" msgid="5696566165170977126">"Meus contatos"</string>
<string name="groupNameWithPhones" msgid="6981588604041120497">"Contatos com números de telefone"</string>
<string name="starredInAndroid" msgid="6495527538140213440">"Com estrela no Android"</string>
<string name="contactCreatedToast" msgid="8740102129968688060">"Contato criado."</string>
<string name="contactSavedToast" msgid="7152589189385441091">"Contato salvo."</string>
+ <!-- no translation found for contactSavedErrorToast (9189098776225004666) -->
+ <skip />
<string name="listSeparatorCallNumber" msgid="7115321894439629408">"Discar número"</string>
<string name="listSeparatorCallNumber_edit" msgid="2999167270900823334">"Números de telefone"</string>
<string name="listSeparatorSendSmsMms" msgid="2480944736714739967">"Enviar SMS/MMS"</string>
@@ -110,9 +139,11 @@
<string name="listSeparatorMapAddress" msgid="7076401301758190804">"Endereço no mapa"</string>
<string name="listSeparatorMapAddress_edit" msgid="298711187672067985">"Endereços postais"</string>
<string name="listSeparatorOrganizations" msgid="7514083358440762504">"Organizações"</string>
+ <string name="listSeparatorGroups" msgid="1593940073983422450">"Grupos"</string>
<string name="listSeparatorOtherInformation" msgid="7844959649638482329">"Outras informações"</string>
<string name="listSeparatorOtherInformation_edit" msgid="1326921768011367750">"Outras opções"</string>
<string name="listSeparatorMore_edit" msgid="858454837482243176">"Mais"</string>
+ <string name="socialStreamIconLabel" msgid="4367712449555075376">"Social"</string>
<string name="contactsIconLabel" msgid="7666609097606552806">"Contatos"</string>
<string name="contactsFavoritesLabel" msgid="8417039765586853670">"Favoritos"</string>
<string name="dialerIconLabel" msgid="7228520966699542850">"Discador"</string>
@@ -128,6 +159,7 @@
<string name="recentCalls_deleteAll" msgid="6352364392762163704">"Limpar registro de chamadas"</string>
<string name="recentCalls_empty" msgid="247053222448663107">"O registro de chamadas está vazio."</string>
<string name="imei" msgid="3045126336951684285">"IMEI"</string>
+ <string name="meid" msgid="6210568493746275750">"MEID"</string>
<string name="voicemail" msgid="3851469869202611441">"Correio de voz"</string>
<string name="unknown" msgid="740067747858270469">"Desconhecido"</string>
<string name="private_num" msgid="6374339738119166953">"Número privado"</string>
@@ -138,7 +170,14 @@
<string name="simContacts_title" msgid="27341688347689769">"Contatos do cartão SIM"</string>
<string name="contactsSyncPlug" msgid="7248276704957313698"><font fgcolor="#ffffffff">" Sincronize seus contatos do Google!"</font>" "\n"Após sincronizar para o seu telefone, seus contatos estarão disponíveis em qualquer lugar que você vá."</string>
<string name="noContactsHelpText" msgid="2249195687463896364">"Não há nenhum contato."\n\n"Para adicionar contatos, pressione "<font fgcolor="#ffffffff"><b>"Menu"</b></font>" e selecione:"\n" "\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 contatos"</b></font>" para adicionar do seu cartão SIM "\n</li></string>
- <string name="noContactsHelpTextWithSync" msgid="4927701496550314555">"Não há nenhum contato."\n\n"Para adicionar contatos, pressione "<font fgcolor="#ffffffff"><b>"Menu"</b></font>" e selecione:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Editar grupos sincronizados"</b></font>" para adicionar a partir de uma conta do Google nova ou já existente"\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 contatos"</b></font>" para adicionar contatos do seu cartão SIM "\n</li></string>
+ <!-- no translation found for noContactsHelpTextWithSync (2179024855526352215) -->
+ <skip />
+ <!-- no translation found for noContactsNoSimHelpText (645951808197910024) -->
+ <skip />
+ <!-- no translation found for noContactsNoSimHelpTextWithSync (511505839941079550) -->
+ <skip />
+ <!-- no translation found for noFavoritesHelpText (1705381563293866510) -->
+ <skip />
<string name="seclectSyncGroups_title" msgid="1235432026231325655">"Selecionar grupos para sincronizar"</string>
<string name="liveFolder_all_label" msgid="1552523730090319259">"Todos os contatos"</string>
<string name="liveFolder_favorites_label" msgid="2674341514070517105">"Com estrela"</string>
@@ -173,6 +212,7 @@
<string name="favoritesFrquentSeparator" msgid="5007070838253932139">"Frequentemente chamado"</string>
<string name="add_contact_dlg_title" msgid="2789046541229846116">"Adicionar contato"</string>
<string name="add_contact_dlg_message_fmt" msgid="7986472669444326576">"Adicionar \"<xliff:g id="EMAIL">%s</xliff:g>\" aos contatos?"</string>
+ <string name="all_tab_label" msgid="4003124364397916826">"Todos"</string>
<string name="description_image_button_one" msgid="1740638037139856139">"um"</string>
<string name="description_image_button_two" msgid="5882638439003731308">"dois"</string>
<string name="description_image_button_three" msgid="8709731759376015180">"três"</string>
@@ -189,19 +229,27 @@
<string name="no_sdcard_message" msgid="7791906118138538259">"Nenhum cartão SD detectado"</string>
<string name="searching_vcard_title" msgid="4158580043290687793">"Pesquisando VCard"</string>
<string name="select_import_type_title" msgid="2443742794103731022">"De onde você gostaria de importar contatos?"</string>
- <string name="import_from_sim" msgid="3362158539070179154">"Cartão SIM"</string>
- <string name="import_from_sdcard" msgid="8854793070007530689">"Cartão SD"</string>
- <string name="import_all_vcard_string" msgid="2788222288962542415">"Importar todos os arquivos do VCard"</string>
- <string name="import_one_vcard_string" msgid="4618119296910253689">"Importar um arquivo do VCard"</string>
+ <!-- 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 />
+ <string name="import_one_vcard_string" msgid="9059163467020328433">"Importar um arquivo do vCard"</string>
+ <string name="import_multiple_vcard_string" msgid="3810226492811062392">"Importar vários arquivos do vCard"</string>
+ <string name="import_all_vcard_string" msgid="5518136113853448474">"Importar todos os arquivos do vCard"</string>
<string name="searching_vcard_message" msgid="2467573749792455605">"Procurando dados do VCard no VCard"</string>
<string name="scanning_sdcard_failed_title" msgid="2788276113399585924">"Falha na verificação do Cartão SD"</string>
<string name="scanning_sdcard_failed_message" msgid="925361244069058117">"Falha na verificação do Cartão SD"</string>
- <string name="fail_reason_io_error" msgid="5922864781066136340">"Erro E/S"</string>
- <string name="fail_reason_vcard_parse_error" msgid="2129476089250836277">"Falha ao analisar o VCard"</string>
+ <string name="fail_reason_io_error" msgid="5949038573678667996">"Erro de E/S (Razão: \"<xliff:g id="FAIL_REASON">%s</xliff:g>\")"</string>
+ <string name="fail_reason_vcard_parse_error" msgid="8877496734269540489">"Falha ao analisar o VCard por algum motivo inesperado"</string>
+ <string name="fail_reason_vcard_not_supported_error" msgid="166568606246563552">"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="1489638537002538332">"Nenhum arquivo de VCard encontrado no Cartão SD"</string>
<string name="fail_reason_no_vcard_entry" msgid="3808734131680935043">"Nenhuma entrada válida no VCard foi encontrada para sua seleção"</string>
+ <string name="fail_reason_failed_to_read_files" msgid="3659521123567134029">"Falha na importação de um ou mais arquivos (%s)."</string>
<string name="select_vcard_title" msgid="4615933643517710543">"Selecionar arquivo do VCard"</string>
<string name="select_vcard_message" msgid="7028772212789057858">"Selecione um arquivo do VCard para importar"</string>
+ <string name="progress_shower_message" msgid="5636525578293752526">"<xliff:g id="ACTION">%s</xliff:g>"\n"<xliff:g id="FILENAME">%s</xliff:g>"</string>
<string name="reading_vcard_title" msgid="430154704397375160">"Lendo VCard"</string>
<string name="reading_vcard_message" msgid="7470486051482460267">"Lendo os arquivos do VCard"</string>
<string name="importing_vcard_message" msgid="2466665710812588738">"Importando os dados do VCard"</string>
@@ -224,4 +272,104 @@
<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="1488865103809190124">"Adicionar pausa de 2 segundos"</string>
+ <string name="add_wait" msgid="8347184715316058465">"Adicionar espera"</string>
+ <string name="dial_button_label" msgid="7637725632722605863">"Discar"</string>
+ <!-- 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 />
+ <string name="quickcontact_missing_app" msgid="1181287640680679441">"Nenhum aplicativo foi encontrado para executar esta ação."</string>
+ <string name="quickcontact_remember_choice" msgid="2858356075887897116">"Lembrar desta escolha"</string>
+ <string name="quickcontact_missing_name" msgid="7806083820928955853">"Desconhecido"</string>
+ <string name="menu_accounts" msgid="8499114602017077970">"Contas"</string>
+ <!-- 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">"Nome"</string>
+ <string name="nicknameLabelsGroup" msgid="2891682101053358010">"Apelido"</string>
+ <string name="organizationLabelsGroup" msgid="2478611760751832035">"Organização"</string>
+ <string name="websiteLabelsGroup" msgid="4202998982804009261">"Site"</string>
+ <string name="type_short_home" msgid="7770424864090605384">"H"</string>
+ <string name="type_short_mobile" msgid="1655473281466676216">"M"</string>
+ <string name="type_short_work" msgid="4925330752504537861">"W"</string>
+ <string name="type_short_pager" msgid="2613818970827594238">"P"</string>
+ <string name="type_short_other" msgid="5669407180177236769">"O"</string>
+ <string name="edit_secondary_collapse" msgid="1855127176548440583">"Detalhes secundários"</string>
+ <string name="dialog_primary_name" msgid="5521591005692614833">"Nome principal"</string>
+ <string name="dialog_new_contact_account" msgid="9044704073286262197">"Criar contato na conta"</string>
+ <string name="menu_sync_remove" msgid="3266725887008450161">"Remover sincronização do grupo"</string>
+ <!-- no translation found for dialog_sync_add (8267045393119375803) -->
+ <skip />
+ <!-- no translation found for display_more_groups (2682547080423434170) -->
+ <skip />
+ <string name="display_ungrouped" msgid="4357317661311803984">"(Contatos não agrupados)"</string>
+ <string name="display_warn_remove_ungrouped" msgid="2314043155909167610">"A remoção de \'<xliff:g id="GROUP">%s</xliff:g>\' da sincronização também removerá todos os contatos não agrupados da sincronização."</string>
+ <string name="account_phone" msgid="4025734638492419713">"Somente telefone (não sincronizado)"</string>
+ <string name="label_email_display_name" msgid="5537802602754309600">"Nome de exibição"</string>
+ <string name="call_home" msgid="1990519474420545392">"Chamar residência"</string>
+ <string name="call_mobile" msgid="7502236805487609178">"Chamar celular"</string>
+ <string name="call_work" msgid="5328785911463744028">"Chamar o trabalho"</string>
+ <string name="call_fax_work" msgid="7467763592359059243">"Chamar fax comercial"</string>
+ <string name="call_fax_home" msgid="8342175628887571876">"Chamar fax residencial"</string>
+ <string name="call_pager" msgid="9003902812293983281">"Chamar pager"</string>
+ <string name="call_custom" msgid="7756571794763171802">"Chamar <xliff:g id="CUSTOM">%s</xliff:g>"</string>
+ <string name="call_car" msgid="3280537320306436445">"Chamar carro"</string>
+ <string name="call_company_main" msgid="6105120947138711257">"Chamar empresa (principal)"</string>
+ <string name="call_mms" msgid="6274041545876221437">"Chamar MMS"</string>
+ <string name="call_radio" msgid="8296755876398357063">"Chamar rádio"</string>
+ <string name="sms_home" msgid="7524332261493162995">"Texto (residencial)"</string>
+ <string name="sms_mobile" msgid="5200107250451030769">"Enviar mensagem de texto para celular"</string>
+ <string name="sms_work" msgid="2269624156655267740">"Texto (comercial)"</string>
+ <string name="sms_fax_work" msgid="8028189067816907075">"Texto de fax (comercial)"</string>
+ <string name="sms_fax_home" msgid="9204042076306809634">"Texto de fax (residencial)"</string>
+ <string name="sms_pager" msgid="7730404569637015192">"Enviar mensagem de texto para o pager"</string>
+ <string name="sms_other" msgid="5131921487474531617">"Enviar mensagem de texto para outras pessoas"</string>
+ <string name="sms_custom" msgid="5932736853732191825">"Enviar mensagem de texto para <xliff:g id="CUSTOM">%s</xliff:g>"</string>
+ <string name="sms_car" msgid="7444227058437359641">"Enviar mensagem de texto para o carro"</string>
+ <string name="sms_company_main" msgid="118970873419678087">"Enviar mensagem de texto para empresa (principal)"</string>
+ <string name="sms_mms" msgid="4069352461380762677">"Enviar texto MMS"</string>
+ <string name="sms_radio" msgid="3329166673433967820">"Enviar mensagem de texto para o rádio"</string>
+ <string name="email_home" msgid="8573740658148184279">"E-mail (residencial)"</string>
+ <string name="email_mobile" msgid="2042889209787989814">"Enviar e-mail para celular"</string>
+ <string name="email_work" msgid="2807430017302722689">"E-mail (comercial)"</string>
+ <string name="email_other" msgid="8093933498541795832">"Enviar e-mail para outras pessoas"</string>
+ <string name="email_custom" msgid="7548003991586214105">"Enviar e-mail para <xliff:g id="CUSTOM">%s</xliff:g>"</string>
+ <string name="email" msgid="5668400997660065897">"E-mail"</string>
+ <string name="map_home" msgid="1243547733423343982">"Ver endereço residencial"</string>
+ <string name="map_work" msgid="1360474076921878088">"Ver endereço comercial"</string>
+ <string name="map_other" msgid="5560707927535653892">"Ver outro endereço"</string>
+ <string name="map_custom" msgid="6184363799976265281">"Visualizar endereço de <xliff:g id="CUSTOM">%s</xliff:g>"</string>
+ <string name="chat_aim" msgid="2588492205291249142">"Bater papo usando o AIM"</string>
+ <string name="chat_msn" msgid="8041633440091073484">"Bater papo usando o Windows Live"</string>
+ <string name="chat_yahoo" msgid="6629211142719943666">"Bater papo usando o Yahoo"</string>
+ <string name="chat_skype" msgid="1210045020427480566">"Bater papo usando o Skype"</string>
+ <string name="chat_qq" msgid="4294637812847719693">"Bater papo usando o QQ"</string>
+ <string name="chat_gtalk" msgid="981575737258117697">"Bater papo usando o Google Talk"</string>
+ <string name="chat_icq" msgid="8438405386153745775">"Bater papo usando o ICQ"</string>
+ <string name="chat_jabber" msgid="7561444230307829609">"Bater papo usando o Jabber"</string>
+ <string name="postal_street" msgid="8133143961580058972">"Rua"</string>
+ <string name="postal_pobox" msgid="4431938829180269821">"Caixa postal"</string>
+ <string name="postal_neighborhood" msgid="1450783874558956739">"Vizinhança"</string>
+ <string name="postal_city" msgid="6597491300084895548">"Cidade"</string>
+ <string name="postal_region" msgid="6045263193478437672">"Estado"</string>
+ <string name="postal_postcode" msgid="572136414136673751">"CEP"</string>
+ <string name="postal_country" msgid="7638264508416368690">"País"</string>
+ <string name="name_given" msgid="1687286314106019813">"Nome"</string>
+ <string name="name_family" msgid="3416695586119999058">"Sobrenome"</string>
+ <string name="name_prefix" msgid="59756378548779822">"Prefixo do nome"</string>
+ <string name="name_middle" msgid="8467433655992690326">"Nome do meio"</string>
+ <string name="name_suffix" msgid="3855278445375651441">"Sufixo do nome"</string>
+ <string name="name_phonetic_given" msgid="6853570431394449191">"Nome fonético"</string>
+ <string name="name_phonetic_middle" msgid="8643721493320405200">"Nome do meio fonético"</string>
+ <string name="name_phonetic_family" msgid="462095502140180305">"Sobrenome fonético"</string>
</resources>
diff --git a/res/values-ru/config.xml b/res/values-ru/config.xml
deleted file mode 100644
index 07b3c7e..0000000
--- a/res/values-ru/config.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for config_export_vcard_type (9145748078116159716) -->
- <skip />
- <string name="config_export_dir" msgid="222182743639478636">"/sdcard"</string>
- <!-- no translation found for config_export_file_prefix (3022868431158658122) -->
- <skip />
- <!-- no translation found for config_export_file_suffix (16505844221142195) -->
- <skip />
- <string name="config_export_file_extension" msgid="1758878818611339161">"vcf"</string>
- <!-- no translation found for config_export_extensions_to_consider (5095044502091950623) -->
- <skip />
-</resources>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index 2075cca..76256f4 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -40,11 +40,24 @@
<string name="menu_showBarcode" msgid="309973637178814132">"Показать штрих-код"</string>
<string name="menu_editContact" msgid="3452858480713561396">"Изменить контакт"</string>
<string name="menu_deleteContact" msgid="1916555454274101750">"Удалить контакт"</string>
- <string name="menu_call" msgid="7359207953236681606">"Позвонить"</string>
- <string name="menu_sendSMS" msgid="5917289601666766617">"Отправить SMS/MMS"</string>
+ <!-- no translation found for menu_call (3992595586042260618) -->
+ <skip />
+ <!-- no translation found for menu_sendSMS (5535886767547006515) -->
+ <skip />
<string name="menu_sendEmail" msgid="7293508859242926187">"Отправить письмо"</string>
<string name="menu_viewAddress" msgid="1814744325763202024">"Адрес на карте"</string>
<string name="menu_makeDefaultNumber" msgid="4838759253316649534">"Сделать номером по умолчанию"</string>
+ <string name="menu_makeDefaultEmail" msgid="2599044610375789994">"Использовать этот адрес по умолчанию"</string>
+ <string name="menu_splitAggregate" msgid="7402548456225901170">"Разделить"</string>
+ <string name="splitAggregate_title" msgid="2053462872948058798">"Разделить контакт"</string>
+ <string name="contactsSplitMessage" msgid="3439008547775199243">"Разделение контакта"</string>
+ <string name="menu_joinAggregate" msgid="5027981918265667970">"Присоединить"</string>
+ <string name="titleJoinAggregate" msgid="6970566008563147202">"Присоединить контакт"</string>
+ <string name="separatorJoinAggregateSuggestions" msgid="5494823322479834017">"Предложения"</string>
+ <string name="separatorJoinAggregateAll" msgid="7939932265026181043">"Все контакты"</string>
+ <string name="contactsJoinedMessage" msgid="7208148163607047389">"Контакты присоединились"</string>
+ <string name="menu_contactOptions" msgid="1957061455705020617">"Параметры"</string>
+ <string name="contactOptionsTitle" msgid="8259347644090700915">"Параметры"</string>
<string name="deleteConfirmation_title" msgid="6394309508930335204">"Удалить"</string>
<string name="deleteConfirmation" msgid="811706994761610640">"Этот контакт будет удален."</string>
<string name="menu_done" msgid="796017761764190697">"Готово"</string>
@@ -55,6 +68,9 @@
<string name="label_phonetic_name" msgid="2288082649573927286">"Произношение"</string>
<string name="label_notes" msgid="8337354953278341042">"Примечания"</string>
<string name="label_ringtone" msgid="8833166825330686244">"Мелодия"</string>
+ <string name="label_groups" msgid="7304551384542859026">"Группы"</string>
+ <string name="group_list" msgid="8583361685440161307">", <xliff:g id="GROUPNAME">%s</xliff:g>"</string>
+ <string name="menu_viewGroup" msgid="1401851715586577551">"Изменить группы"</string>
<string name="ghostData_name" msgid="6490954238641157585">"Имя и фамилия"</string>
<string name="ghostData_phonetic_name" msgid="7852749081984070902">"Произношение имени"</string>
<string name="ghostData_company" msgid="5414421120553765775">"Компания"</string>
@@ -64,6 +80,7 @@
<string name="ghostData_phone" msgid="6963153888271466620">"Номер телефона"</string>
<string name="ghostData_email" msgid="6184537075551565919">"Адрес электронной почты"</string>
<string name="ghostData_postal" msgid="652611650594951897">"Почтовый адрес"</string>
+ <string name="ghostData_group" msgid="4504591380347534114">"Показать группу"</string>
<string name="invalidContactMessage" msgid="5816991830260044593">"Контакт не существует."</string>
<string name="pickerNewContactHeader" msgid="7750705279843568147">"Создать новый контакт"</string>
<string name="selectLabel" msgid="4255424123394910733">"Выбор ярлыка"</string>
@@ -80,7 +97,8 @@
<string name="photoPickerNotFoundText" msgid="431331662154342581">"В телефоне нет картинок."</string>
<string name="attachToContact" msgid="8820530304406066714">"Значок контакта"</string>
<string name="customLabelPickerTitle" msgid="1081475101983255212">"Особая метка"</string>
- <string name="menu_displayGroup" msgid="4603480747214476335">"Показать группу"</string>
+ <string name="menu_displayGroup" msgid="6022511302986343108">"Показать группы"</string>
+ <string name="displayGroups" msgid="2645969886032719891">"Показать группы"</string>
<string name="syncGroupPreference" msgid="9028361137161162861">"Изменить группы синхронизации"</string>
<string name="importFromSim" msgid="8383900146531125319">"Импортировать контакты"</string>
<string name="send_to_voicemail_checkbox" msgid="9001686764070676353">"Направлять вызовы в голосовую почту"</string>
@@ -94,12 +112,23 @@
<string name="select_group_title" msgid="7955698611959835612">"Группы"</string>
<string name="groupEmpty" msgid="6661950109828194595">"Группа \"<xliff:g id="GROUPNAME">%s</xliff:g>\" пуста."</string>
<string name="showAllGroups" msgid="5164410117611094297">"Все контакты"</string>
+ <string name="showFilterPhones" msgid="4184858075465653970">"Только контакты с телефонными номерами"</string>
+ <string name="showFilterPhonesDescrip" msgid="6644443248815191067">"Отображать только контакты, содержащие номера телефонов"</string>
+ <string name="headerContactGroups" msgid="1451383377487746789">"Группы контактов"</string>
+ <plurals name="groupDescrip">
+ <item quantity="other" msgid="3507881585720628389">"Контактов: <xliff:g id="COUNT">%0$d</xliff:g>"</item>
+ </plurals>
+ <plurals name="groupDescripPhones">
+ <item quantity="other" msgid="3816047547470490208">"Контактов: <xliff:g id="COUNT_0">%1$d</xliff:g>; с тел. номерами: <xliff:g id="COUNTWITHPHONES">%2$d</xliff:g>"</item>
+ </plurals>
<string name="syncAllGroups" msgid="7512169081224806890">"Синхронизировать все контакты"</string>
<string name="groupNameMyContacts" msgid="5696566165170977126">"Мои контакты"</string>
<string name="groupNameWithPhones" msgid="6981588604041120497">"Контакты с номерами телефонов"</string>
<string name="starredInAndroid" msgid="6495527538140213440">"Помеченные в Android"</string>
<string name="contactCreatedToast" msgid="8740102129968688060">"Контакт создан."</string>
<string name="contactSavedToast" msgid="7152589189385441091">"Контакт сохранен."</string>
+ <!-- no translation found for contactSavedErrorToast (9189098776225004666) -->
+ <skip />
<string name="listSeparatorCallNumber" msgid="7115321894439629408">"Набрать номер"</string>
<string name="listSeparatorCallNumber_edit" msgid="2999167270900823334">"Номера телефонов"</string>
<string name="listSeparatorSendSmsMms" msgid="2480944736714739967">"Отправить SMS/MMS"</string>
@@ -110,9 +139,11 @@
<string name="listSeparatorMapAddress" msgid="7076401301758190804">"Адрес на карте"</string>
<string name="listSeparatorMapAddress_edit" msgid="298711187672067985">"Почтовые адреса"</string>
<string name="listSeparatorOrganizations" msgid="7514083358440762504">"Организации"</string>
+ <string name="listSeparatorGroups" msgid="1593940073983422450">"Группы"</string>
<string name="listSeparatorOtherInformation" msgid="7844959649638482329">"Другая информация"</string>
<string name="listSeparatorOtherInformation_edit" msgid="1326921768011367750">"Другие параметры"</string>
<string name="listSeparatorMore_edit" msgid="858454837482243176">"Добавить"</string>
+ <string name="socialStreamIconLabel" msgid="4367712449555075376">"Социальные"</string>
<string name="contactsIconLabel" msgid="7666609097606552806">"Контакты"</string>
<string name="contactsFavoritesLabel" msgid="8417039765586853670">"Избранные"</string>
<string name="dialerIconLabel" msgid="7228520966699542850">"Телефон"</string>
@@ -128,6 +159,7 @@
<string name="recentCalls_deleteAll" msgid="6352364392762163704">"Очистить список вызовов"</string>
<string name="recentCalls_empty" msgid="247053222448663107">"Список вызовов пуст."</string>
<string name="imei" msgid="3045126336951684285">"IMEI"</string>
+ <string name="meid" msgid="6210568493746275750">"MEID"</string>
<string name="voicemail" msgid="3851469869202611441">"Голосовая почта"</string>
<string name="unknown" msgid="740067747858270469">"Неизвестно"</string>
<string name="private_num" msgid="6374339738119166953">"Частный номер"</string>
@@ -138,7 +170,14 @@
<string name="simContacts_title" msgid="27341688347689769">"Контакты на SIM-карте"</string>
<string name="contactsSyncPlug" msgid="7248276704957313698"><font fgcolor="#ffffffff">"Синхронизируйте контакты Google."</font>" "\n"После синхронизации с телефоном ваши контакты будут доступны отовсюду."</string>
<string name="noContactsHelpText" msgid="2249195687463896364">"У вас нет контактов."\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>", чтобы добавить контакты с SIM-карты"\n</li></string>
- <string name="noContactsHelpTextWithSync" msgid="4927701496550314555">"У вас нет контактов."\n\n"Чтобы добавить контакты, нажмите "<font fgcolor="#ffffffff"><b>"Меню"</b></font>" и выберите:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Изменить группы синхронизации"</b></font>", чтобы добавить контакт из имеющегося или нового аккаунта Google"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Новый контакт"</b></font>", чтобы создать новый контакт с нуля"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Импортировать контакты"</b></font>", чтобы добавить контакты с SIM-карты"\n</li></string>
+ <!-- no translation found for noContactsHelpTextWithSync (2179024855526352215) -->
+ <skip />
+ <!-- no translation found for noContactsNoSimHelpText (645951808197910024) -->
+ <skip />
+ <!-- no translation found for noContactsNoSimHelpTextWithSync (511505839941079550) -->
+ <skip />
+ <!-- no translation found for noFavoritesHelpText (1705381563293866510) -->
+ <skip />
<string name="seclectSyncGroups_title" msgid="1235432026231325655">"Выбрать группы для синхронизации"</string>
<string name="liveFolder_all_label" msgid="1552523730090319259">"Все контакты"</string>
<string name="liveFolder_favorites_label" msgid="2674341514070517105">"Помеченные"</string>
@@ -173,6 +212,7 @@
<string name="favoritesFrquentSeparator" msgid="5007070838253932139">"Часто вызываемые"</string>
<string name="add_contact_dlg_title" msgid="2789046541229846116">"Добление контакта"</string>
<string name="add_contact_dlg_message_fmt" msgid="7986472669444326576">"Добавить в контакты <xliff:g id="EMAIL">%s</xliff:g>?"</string>
+ <string name="all_tab_label" msgid="4003124364397916826">"Все"</string>
<string name="description_image_button_one" msgid="1740638037139856139">"один"</string>
<string name="description_image_button_two" msgid="5882638439003731308">"два"</string>
<string name="description_image_button_three" msgid="8709731759376015180">"три"</string>
@@ -189,19 +229,27 @@
<string name="no_sdcard_message" msgid="7791906118138538259">"SD-карта не обнаружена"</string>
<string name="searching_vcard_title" msgid="4158580043290687793">"Поиск VCard"</string>
<string name="select_import_type_title" msgid="2443742794103731022">"Откуда импортировать контакты?"</string>
- <string name="import_from_sim" msgid="3362158539070179154">"SIM-карта"</string>
- <string name="import_from_sdcard" msgid="8854793070007530689">"SD-карта"</string>
- <string name="import_all_vcard_string" msgid="2788222288962542415">"Импорт всех файлов VCard"</string>
- <string name="import_one_vcard_string" msgid="4618119296910253689">"Импортировать один файл VCard"</string>
+ <!-- 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 />
+ <string name="import_one_vcard_string" msgid="9059163467020328433">"Импорт одного файла VCard"</string>
+ <string name="import_multiple_vcard_string" msgid="3810226492811062392">"Импорт нескольких файлов vCard"</string>
+ <string name="import_all_vcard_string" msgid="5518136113853448474">"Импорт всех файлов VCard"</string>
<string name="searching_vcard_message" msgid="2467573749792455605">"Поиск данных VCard в файле VCard"</string>
<string name="scanning_sdcard_failed_title" msgid="2788276113399585924">"Не удалось сканировать SD-карту"</string>
<string name="scanning_sdcard_failed_message" msgid="925361244069058117">"Не удалось сканировать SD-карту"</string>
- <string name="fail_reason_io_error" msgid="5922864781066136340">"Ошибка ввода-вывода"</string>
- <string name="fail_reason_vcard_parse_error" msgid="2129476089250836277">"Не удается выполнить синтаксический анализ VCard"</string>
+ <string name="fail_reason_io_error" msgid="5949038573678667996">"Ошибка ввода-вывода: \"<xliff:g id="FAIL_REASON">%s</xliff:g>\""</string>
+ <string name="fail_reason_vcard_parse_error" msgid="8877496734269540489">"Не удается выполнить синтаксический анализ VCard по неизвестной причине"</string>
+ <string name="fail_reason_vcard_not_supported_error" msgid="166568606246563552">"Не удается выполнить синтаксический анализ VCard. Формат файла является допустимым, однако не поддерживается в текущей реализации."</string>
<string name="fail_reason_no_vcard_file" msgid="1489638537002538332">"На SD-карте не найдены файлы VCard"</string>
<string name="fail_reason_no_vcard_entry" msgid="3808734131680935043">"Нет подходящих записей VCard"</string>
+ <string name="fail_reason_failed_to_read_files" msgid="3659521123567134029">"Не удалось импортировать один или несколько файлов (%s)."</string>
<string name="select_vcard_title" msgid="4615933643517710543">"Выбор файла VCard"</string>
<string name="select_vcard_message" msgid="7028772212789057858">"Выберите файл VCard для импорта"</string>
+ <string name="progress_shower_message" msgid="5636525578293752526">"<xliff:g id="ACTION">%s</xliff:g>"\n"<xliff:g id="FILENAME">%s</xliff:g>"</string>
<string name="reading_vcard_title" msgid="430154704397375160">"Чтение VCard"</string>
<string name="reading_vcard_message" msgid="7470486051482460267">"Чтение файлов VCard"</string>
<string name="importing_vcard_message" msgid="2466665710812588738">"Импорт данных VCard"</string>
@@ -224,4 +272,104 @@
<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="1488865103809190124">"Добавить двухсекундную паузу"</string>
+ <string name="add_wait" msgid="8347184715316058465">"Добавить ожидание"</string>
+ <string name="dial_button_label" msgid="7637725632722605863">"Набор"</string>
+ <!-- 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 />
+ <string name="quickcontact_missing_app" msgid="1181287640680679441">"Отсутствует приложение для обработки этого действия"</string>
+ <string name="quickcontact_remember_choice" msgid="2858356075887897116">"Запомнить выбранное"</string>
+ <string name="quickcontact_missing_name" msgid="7806083820928955853">"Неизвестно"</string>
+ <string name="menu_accounts" msgid="8499114602017077970">"Аккаунты"</string>
+ <!-- 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">"Имя"</string>
+ <string name="nicknameLabelsGroup" msgid="2891682101053358010">"Псевдоним"</string>
+ <string name="organizationLabelsGroup" msgid="2478611760751832035">"Организация"</string>
+ <string name="websiteLabelsGroup" msgid="4202998982804009261">"Веб-сайт"</string>
+ <string name="type_short_home" msgid="7770424864090605384">"Д"</string>
+ <string name="type_short_mobile" msgid="1655473281466676216">"М"</string>
+ <string name="type_short_work" msgid="4925330752504537861">"Р"</string>
+ <string name="type_short_pager" msgid="2613818970827594238">"П"</string>
+ <string name="type_short_other" msgid="5669407180177236769">"Др"</string>
+ <string name="edit_secondary_collapse" msgid="1855127176548440583">"Дополнительные подробности"</string>
+ <string name="dialog_primary_name" msgid="5521591005692614833">"Основное имя"</string>
+ <string name="dialog_new_contact_account" msgid="9044704073286262197">"Создать контакт в аккаунте"</string>
+ <string name="menu_sync_remove" msgid="3266725887008450161">"Удалить группу синхронизации"</string>
+ <!-- no translation found for dialog_sync_add (8267045393119375803) -->
+ <skip />
+ <!-- no translation found for display_more_groups (2682547080423434170) -->
+ <skip />
+ <string name="display_ungrouped" msgid="4357317661311803984">"(Несгруппированные контакты)"</string>
+ <string name="display_warn_remove_ungrouped" msgid="2314043155909167610">"Удаление группы \"<xliff:g id="GROUP">%s</xliff:g>\" из синхронизации также приведет к удалению любых несгруппированных контактов из синхронизации."</string>
+ <string name="account_phone" msgid="4025734638492419713">"Только телефон (несинхронизирован)"</string>
+ <string name="label_email_display_name" msgid="5537802602754309600">"Показать имя"</string>
+ <string name="call_home" msgid="1990519474420545392">"Позвонить (дом.)"</string>
+ <string name="call_mobile" msgid="7502236805487609178">"Позвонить (моб.)"</string>
+ <string name="call_work" msgid="5328785911463744028">"Позвонить (раб.)"</string>
+ <string name="call_fax_work" msgid="7467763592359059243">"Позвонить (раб. факс)"</string>
+ <string name="call_fax_home" msgid="8342175628887571876">"Позвонить (дом. факс)"</string>
+ <string name="call_pager" msgid="9003902812293983281">"Позвонить (пейджер)"</string>
+ <string name="call_custom" msgid="7756571794763171802">"Позвонить (<xliff:g id="CUSTOM">%s</xliff:g>)"</string>
+ <string name="call_car" msgid="3280537320306436445">"Позвонить (автомоб.)"</string>
+ <string name="call_company_main" msgid="6105120947138711257">"Позвонить (раб., осн.)"</string>
+ <string name="call_mms" msgid="6274041545876221437">"Позвонить (MMS)"</string>
+ <string name="call_radio" msgid="8296755876398357063">"Позвонить (радиотел.)"</string>
+ <string name="sms_home" msgid="7524332261493162995">"SMS (дом.)"</string>
+ <string name="sms_mobile" msgid="5200107250451030769">"SMS (моб.)"</string>
+ <string name="sms_work" msgid="2269624156655267740">"SMS (дом.)"</string>
+ <string name="sms_fax_work" msgid="8028189067816907075">"SMS (раб. факс)"</string>
+ <string name="sms_fax_home" msgid="9204042076306809634">"SMS (дом. факс)"</string>
+ <string name="sms_pager" msgid="7730404569637015192">"SMS (пейджер)"</string>
+ <string name="sms_other" msgid="5131921487474531617">"SMS (другое)"</string>
+ <string name="sms_custom" msgid="5932736853732191825">"SMS (<xliff:g id="CUSTOM">%s</xliff:g>)"</string>
+ <string name="sms_car" msgid="7444227058437359641">"SMS (автомоб.)"</string>
+ <string name="sms_company_main" msgid="118970873419678087">"SMS (раб., осн.)"</string>
+ <string name="sms_mms" msgid="4069352461380762677">"MMS"</string>
+ <string name="sms_radio" msgid="3329166673433967820">"SMS (радиотел.)"</string>
+ <string name="email_home" msgid="8573740658148184279">"Эл. почта (дом.)"</string>
+ <string name="email_mobile" msgid="2042889209787989814">"Эл. почта (моб.)"</string>
+ <string name="email_work" msgid="2807430017302722689">"Эл. почта (раб.)"</string>
+ <string name="email_other" msgid="8093933498541795832">"Эл. почта (другое)"</string>
+ <string name="email_custom" msgid="7548003991586214105">"Эл. почта (<xliff:g id="CUSTOM">%s</xliff:g>)"</string>
+ <string name="email" msgid="5668400997660065897">"Эл. почта"</string>
+ <string name="map_home" msgid="1243547733423343982">"Просмотреть домашний адрес"</string>
+ <string name="map_work" msgid="1360474076921878088">"Просмотреть рабочий адрес"</string>
+ <string name="map_other" msgid="5560707927535653892">"Просмотреть другой адрес"</string>
+ <string name="map_custom" msgid="6184363799976265281">"Просмотр адреса: <xliff:g id="CUSTOM">%s</xliff:g>"</string>
+ <string name="chat_aim" msgid="2588492205291249142">"Чат через AIM"</string>
+ <string name="chat_msn" msgid="8041633440091073484">"Чат через Windows Live"</string>
+ <string name="chat_yahoo" msgid="6629211142719943666">"Чат через Yahoo"</string>
+ <string name="chat_skype" msgid="1210045020427480566">"Чат через Skype"</string>
+ <string name="chat_qq" msgid="4294637812847719693">"Чат через QQ"</string>
+ <string name="chat_gtalk" msgid="981575737258117697">"Чат через Google Talk"</string>
+ <string name="chat_icq" msgid="8438405386153745775">"Чат через ICQ"</string>
+ <string name="chat_jabber" msgid="7561444230307829609">"Чат через Jabber"</string>
+ <string name="postal_street" msgid="8133143961580058972">"Улица"</string>
+ <string name="postal_pobox" msgid="4431938829180269821">"Почтовый ящик"</string>
+ <string name="postal_neighborhood" msgid="1450783874558956739">"Окружение"</string>
+ <string name="postal_city" msgid="6597491300084895548">"Город"</string>
+ <string name="postal_region" msgid="6045263193478437672">"Регион"</string>
+ <string name="postal_postcode" msgid="572136414136673751">"Почтовый индекс"</string>
+ <string name="postal_country" msgid="7638264508416368690">"Страна"</string>
+ <string name="name_given" msgid="1687286314106019813">"Имя"</string>
+ <string name="name_family" msgid="3416695586119999058">"Фамилия"</string>
+ <string name="name_prefix" msgid="59756378548779822">"Префикс имени"</string>
+ <string name="name_middle" msgid="8467433655992690326">"Отчество"</string>
+ <string name="name_suffix" msgid="3855278445375651441">"Суффикс имени"</string>
+ <string name="name_phonetic_given" msgid="6853570431394449191">"Имя (фонетическая запись)"</string>
+ <string name="name_phonetic_middle" msgid="8643721493320405200">"Отчество (фонетическая запись)"</string>
+ <string name="name_phonetic_family" msgid="462095502140180305">"Фамилия (фонетическая запись)"</string>
</resources>
diff --git a/res/values-sv/config.xml b/res/values-sv/config.xml
deleted file mode 100644
index 07b3c7e..0000000
--- a/res/values-sv/config.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for config_export_vcard_type (9145748078116159716) -->
- <skip />
- <string name="config_export_dir" msgid="222182743639478636">"/sdcard"</string>
- <!-- no translation found for config_export_file_prefix (3022868431158658122) -->
- <skip />
- <!-- no translation found for config_export_file_suffix (16505844221142195) -->
- <skip />
- <string name="config_export_file_extension" msgid="1758878818611339161">"vcf"</string>
- <!-- no translation found for config_export_extensions_to_consider (5095044502091950623) -->
- <skip />
-</resources>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index 5e659e0..728fbca 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -40,11 +40,24 @@
<string name="menu_showBarcode" msgid="309973637178814132">"Visa streckkod"</string>
<string name="menu_editContact" msgid="3452858480713561396">"Redigera kontakt"</string>
<string name="menu_deleteContact" msgid="1916555454274101750">"Ta bort kontakt"</string>
- <string name="menu_call" msgid="7359207953236681606">"Ring"</string>
- <string name="menu_sendSMS" msgid="5917289601666766617">"Skicka SMS/MMS"</string>
+ <!-- no translation found for menu_call (3992595586042260618) -->
+ <skip />
+ <!-- no translation found for menu_sendSMS (5535886767547006515) -->
+ <skip />
<string name="menu_sendEmail" msgid="7293508859242926187">"Skicka e-post"</string>
<string name="menu_viewAddress" msgid="1814744325763202024">"Visa adress på karta"</string>
<string name="menu_makeDefaultNumber" msgid="4838759253316649534">"Använd som standardnummer"</string>
+ <string name="menu_makeDefaultEmail" msgid="2599044610375789994">"Använd som standardadress"</string>
+ <string name="menu_splitAggregate" msgid="7402548456225901170">"Dela"</string>
+ <string name="splitAggregate_title" msgid="2053462872948058798">"Avdela kontakt"</string>
+ <string name="contactsSplitMessage" msgid="3439008547775199243">"Kontakten har avdelats"</string>
+ <string name="menu_joinAggregate" msgid="5027981918265667970">"Gå med"</string>
+ <string name="titleJoinAggregate" msgid="6970566008563147202">"Anslut kontakt"</string>
+ <string name="separatorJoinAggregateSuggestions" msgid="5494823322479834017">"Förslag"</string>
+ <string name="separatorJoinAggregateAll" msgid="7939932265026181043">"Alla kontakter"</string>
+ <string name="contactsJoinedMessage" msgid="7208148163607047389">"Deltagande kontakter"</string>
+ <string name="menu_contactOptions" msgid="1957061455705020617">"Alternativ"</string>
+ <string name="contactOptionsTitle" msgid="8259347644090700915">"Alternativ"</string>
<string name="deleteConfirmation_title" msgid="6394309508930335204">"Ta bort"</string>
<string name="deleteConfirmation" msgid="811706994761610640">"Kontakten kommer att tas bort."</string>
<string name="menu_done" msgid="796017761764190697">"Färdig"</string>
@@ -55,6 +68,9 @@
<string name="label_phonetic_name" msgid="2288082649573927286">"Fonetiskt"</string>
<string name="label_notes" msgid="8337354953278341042">"Anteckningar"</string>
<string name="label_ringtone" msgid="8833166825330686244">"Ringsignal"</string>
+ <string name="label_groups" msgid="7304551384542859026">"Grupper"</string>
+ <string name="group_list" msgid="8583361685440161307">", <xliff:g id="GROUPNAME">%s</xliff:g>"</string>
+ <string name="menu_viewGroup" msgid="1401851715586577551">"Redigera grupper"</string>
<string name="ghostData_name" msgid="6490954238641157585">"För- och efternamn"</string>
<string name="ghostData_phonetic_name" msgid="7852749081984070902">"Fonetiskt namn"</string>
<string name="ghostData_company" msgid="5414421120553765775">"Företag"</string>
@@ -64,6 +80,7 @@
<string name="ghostData_phone" msgid="6963153888271466620">"Telefonnummer"</string>
<string name="ghostData_email" msgid="6184537075551565919">"E-postadress"</string>
<string name="ghostData_postal" msgid="652611650594951897">"Postadress"</string>
+ <string name="ghostData_group" msgid="4504591380347534114">"Visa grupp"</string>
<string name="invalidContactMessage" msgid="5816991830260044593">"Kontakten finns inte."</string>
<string name="pickerNewContactHeader" msgid="7750705279843568147">"Skapa ny kontakt"</string>
<string name="selectLabel" msgid="4255424123394910733">"Välj etikett"</string>
@@ -80,7 +97,8 @@
<string name="photoPickerNotFoundText" msgid="431331662154342581">"Det finns inga bilder på telefonen."</string>
<string name="attachToContact" msgid="8820530304406066714">"Kontaktikon"</string>
<string name="customLabelPickerTitle" msgid="1081475101983255212">"Anpassat etikettsnamn"</string>
- <string name="menu_displayGroup" msgid="4603480747214476335">"Visa grupper"</string>
+ <string name="menu_displayGroup" msgid="6022511302986343108">"Visa grupper"</string>
+ <string name="displayGroups" msgid="2645969886032719891">"Visa grupper"</string>
<string name="syncGroupPreference" msgid="9028361137161162861">"Redigera synkgrupper"</string>
<string name="importFromSim" msgid="8383900146531125319">"Importera kontakter"</string>
<string name="send_to_voicemail_checkbox" msgid="9001686764070676353">"Skicka samtal direkt till röstbrevlåda"</string>
@@ -94,12 +112,23 @@
<string name="select_group_title" msgid="7955698611959835612">"Grupper"</string>
<string name="groupEmpty" msgid="6661950109828194595">"Din grupp, <xliff:g id="GROUPNAME">%s</xliff:g>, är tom."</string>
<string name="showAllGroups" msgid="5164410117611094297">"Alla kontakter"</string>
+ <string name="showFilterPhones" msgid="4184858075465653970">"Bara kontakter med telefoner"</string>
+ <string name="showFilterPhonesDescrip" msgid="6644443248815191067">"Visa bara kontakter med telefonnummer"</string>
+ <string name="headerContactGroups" msgid="1451383377487746789">"Kontaktgrupper"</string>
+ <plurals name="groupDescrip">
+ <item quantity="other" msgid="3507881585720628389">"<xliff:g id="COUNT">%0$d</xliff:g> kontakter"</item>
+ </plurals>
+ <plurals name="groupDescripPhones">
+ <item quantity="other" msgid="3816047547470490208">"<xliff:g id="COUNT_0">%1$d</xliff:g> kontakter, <xliff:g id="COUNTWITHPHONES">%2$d</xliff:g> med telefon"</item>
+ </plurals>
<string name="syncAllGroups" msgid="7512169081224806890">"Synka alla kontakter"</string>
<string name="groupNameMyContacts" msgid="5696566165170977126">"Mina kontakter"</string>
<string name="groupNameWithPhones" msgid="6981588604041120497">"Kontakter med telefonnummer"</string>
<string name="starredInAndroid" msgid="6495527538140213440">"Stjärnmärkta i Android"</string>
<string name="contactCreatedToast" msgid="8740102129968688060">"Kontakt skapad."</string>
<string name="contactSavedToast" msgid="7152589189385441091">"Kontakt sparad."</string>
+ <!-- no translation found for contactSavedErrorToast (9189098776225004666) -->
+ <skip />
<string name="listSeparatorCallNumber" msgid="7115321894439629408">"Ring upp"</string>
<string name="listSeparatorCallNumber_edit" msgid="2999167270900823334">"Telefonnummer"</string>
<string name="listSeparatorSendSmsMms" msgid="2480944736714739967">"Skicka SMS/MMS"</string>
@@ -110,9 +139,11 @@
<string name="listSeparatorMapAddress" msgid="7076401301758190804">"Visa adress på karta"</string>
<string name="listSeparatorMapAddress_edit" msgid="298711187672067985">"Postadress"</string>
<string name="listSeparatorOrganizations" msgid="7514083358440762504">"Organisationer"</string>
+ <string name="listSeparatorGroups" msgid="1593940073983422450">"Grupper"</string>
<string name="listSeparatorOtherInformation" msgid="7844959649638482329">"Övrig information"</string>
<string name="listSeparatorOtherInformation_edit" msgid="1326921768011367750">"Övriga alternativ"</string>
<string name="listSeparatorMore_edit" msgid="858454837482243176">"Mer"</string>
+ <string name="socialStreamIconLabel" msgid="4367712449555075376">"Socialt"</string>
<string name="contactsIconLabel" msgid="7666609097606552806">"Kontakter"</string>
<string name="contactsFavoritesLabel" msgid="8417039765586853670">"Favoriter"</string>
<string name="dialerIconLabel" msgid="7228520966699542850">"Samtal"</string>
@@ -128,6 +159,7 @@
<string name="recentCalls_deleteAll" msgid="6352364392762163704">"Rensa samtalshistorik"</string>
<string name="recentCalls_empty" msgid="247053222448663107">"Samtalshistoriken är tom."</string>
<string name="imei" msgid="3045126336951684285">"IMEI-kod"</string>
+ <string name="meid" msgid="6210568493746275750">"MEID"</string>
<string name="voicemail" msgid="3851469869202611441">"Röstbrevlåda"</string>
<string name="unknown" msgid="740067747858270469">"Okänd"</string>
<string name="private_num" msgid="6374339738119166953">"Privat nummer"</string>
@@ -138,7 +170,14 @@
<string name="simContacts_title" msgid="27341688347689769">"Kontakter från SIM-kort"</string>
<string name="contactsSyncPlug" msgid="7248276704957313698"><font fgcolor="#ffffffff">"Synka dina Google-kontakter!"</font>" "\n"När du har synkat med telefonen kan du använda dina kontakter var du än är."</string>
<string name="noContactsHelpText" msgid="2249195687463896364">"Du har inga kontakter."\n\n"Om du vill lägga till kontakter trycker du på "<font fgcolor="#ffffffff"><b>"Meny"</b></font>" och väljer:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Ny kontakt"</b></font>" om du vill skapa en helt ny kontakt"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importera kontakter"</b></font>" om du vill lägga till kontakter från SIM-kortet"\n</li></string>
- <string name="noContactsHelpTextWithSync" msgid="4927701496550314555">"Du har inga kontakter."\n\n"Om du vill lägga till kontakter trycker du på "<font fgcolor="#ffffffff"><b>"Meny"</b></font>" och väljer:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Redigera synkgrupper"</b></font>" om du vill lägga till från ett nytt eller befintligt Google-konto"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Ny kontakt"</b></font>" om du vill lägga till en helt ny kontakt"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Importera kontakter"</b></font>" om du vill lägga till kontakter från SIM-kortet"\n</li></string>
+ <!-- no translation found for noContactsHelpTextWithSync (2179024855526352215) -->
+ <skip />
+ <!-- no translation found for noContactsNoSimHelpText (645951808197910024) -->
+ <skip />
+ <!-- no translation found for noContactsNoSimHelpTextWithSync (511505839941079550) -->
+ <skip />
+ <!-- no translation found for noFavoritesHelpText (1705381563293866510) -->
+ <skip />
<string name="seclectSyncGroups_title" msgid="1235432026231325655">"Välj grupper att synka"</string>
<string name="liveFolder_all_label" msgid="1552523730090319259">"Alla kontakter"</string>
<string name="liveFolder_favorites_label" msgid="2674341514070517105">"Stjärnmärkt"</string>
@@ -173,6 +212,7 @@
<string name="favoritesFrquentSeparator" msgid="5007070838253932139">"Ringer ofta"</string>
<string name="add_contact_dlg_title" msgid="2789046541229846116">"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>
+ <string name="all_tab_label" msgid="4003124364397916826">"Alla"</string>
<string name="description_image_button_one" msgid="1740638037139856139">"ett"</string>
<string name="description_image_button_two" msgid="5882638439003731308">"två"</string>
<string name="description_image_button_three" msgid="8709731759376015180">"tre"</string>
@@ -189,19 +229,27 @@
<string name="no_sdcard_message" msgid="7791906118138538259">"Inget SD-kort hittades"</string>
<string name="searching_vcard_title" msgid="4158580043290687793">"Söker efter VCard"</string>
<string name="select_import_type_title" msgid="2443742794103731022">"Varifrån vill du importera dina kontakter?"</string>
- <string name="import_from_sim" msgid="3362158539070179154">"SIM-kort"</string>
- <string name="import_from_sdcard" msgid="8854793070007530689">"SD-kort"</string>
- <string name="import_all_vcard_string" msgid="2788222288962542415">"Importera alla VCard-filer"</string>
- <string name="import_one_vcard_string" msgid="4618119296910253689">"Importera en VCard-fil"</string>
+ <!-- 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 />
+ <string name="import_one_vcard_string" msgid="9059163467020328433">"Importera en vCard-fil"</string>
+ <string name="import_multiple_vcard_string" msgid="3810226492811062392">"Importera flera vCard-filer"</string>
+ <string name="import_all_vcard_string" msgid="5518136113853448474">"Importera alla vCard-filer"</string>
<string name="searching_vcard_message" msgid="2467573749792455605">"Söker efter VCard-data på VCard"</string>
<string name="scanning_sdcard_failed_title" msgid="2788276113399585924">"Det gick inte att läsa SD-kort"</string>
<string name="scanning_sdcard_failed_message" msgid="925361244069058117">"Det gick inte att skanna SD-kort"</string>
- <string name="fail_reason_io_error" msgid="5922864781066136340">"I/O-fel"</string>
- <string name="fail_reason_vcard_parse_error" msgid="2129476089250836277">"Det gick inte att analysera VCard"</string>
+ <string name="fail_reason_io_error" msgid="5949038573678667996">"I/O-fel (orsak: \"<xliff:g id="FAIL_REASON">%s</xliff:g>\")"</string>
+ <string name="fail_reason_vcard_parse_error" msgid="8877496734269540489">"Det gick inte att analysera VCard på grund av ett oväntat fel"</string>
+ <string name="fail_reason_vcard_not_supported_error" msgid="166568606246563552">"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="1489638537002538332">"Ingen VCard-fil hittades på SD-kortet"</string>
<string name="fail_reason_no_vcard_entry" msgid="3808734131680935043">"Det finns ingen VCard-post i den valda filen"</string>
+ <string name="fail_reason_failed_to_read_files" msgid="3659521123567134029">"En eller flera filer kunde inte importeras: (%s)."</string>
<string name="select_vcard_title" msgid="4615933643517710543">"Välj VCard-fil"</string>
<string name="select_vcard_message" msgid="7028772212789057858">"Välj en VCard-fil att importera"</string>
+ <string name="progress_shower_message" msgid="5636525578293752526">"<xliff:g id="ACTION">%s</xliff:g>"\n"<xliff:g id="FILENAME">%s</xliff:g>"</string>
<string name="reading_vcard_title" msgid="430154704397375160">"Läser VCard"</string>
<string name="reading_vcard_message" msgid="7470486051482460267">"Läser VCard-filer"</string>
<string name="importing_vcard_message" msgid="2466665710812588738">"Importera VCard-data"</string>
@@ -224,4 +272,104 @@
<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="1488865103809190124">"Lägg till en paus på 2 sek."</string>
+ <string name="add_wait" msgid="8347184715316058465">"Lägg till väntetid"</string>
+ <string name="dial_button_label" msgid="7637725632722605863">"Ring"</string>
+ <!-- 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 />
+ <string name="quickcontact_missing_app" msgid="1181287640680679441">"Inget program som kan hantera åtgärden hittades"</string>
+ <string name="quickcontact_remember_choice" msgid="2858356075887897116">"Kom ihåg det här valet"</string>
+ <string name="quickcontact_missing_name" msgid="7806083820928955853">"Okänd"</string>
+ <string name="menu_accounts" msgid="8499114602017077970">"Konton"</string>
+ <!-- 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">"Namn"</string>
+ <string name="nicknameLabelsGroup" msgid="2891682101053358010">"Smeknamn"</string>
+ <string name="organizationLabelsGroup" msgid="2478611760751832035">"Organisation"</string>
+ <string name="websiteLabelsGroup" msgid="4202998982804009261">"Webbplats"</string>
+ <string name="type_short_home" msgid="7770424864090605384">"H"</string>
+ <string name="type_short_mobile" msgid="1655473281466676216">"M"</string>
+ <string name="type_short_work" msgid="4925330752504537861">"A"</string>
+ <string name="type_short_pager" msgid="2613818970827594238">"P"</string>
+ <string name="type_short_other" msgid="5669407180177236769">"A"</string>
+ <string name="edit_secondary_collapse" msgid="1855127176548440583">"Sekundära detaljer"</string>
+ <string name="dialog_primary_name" msgid="5521591005692614833">"Primärt namn"</string>
+ <string name="dialog_new_contact_account" msgid="9044704073286262197">"Skapa kontakt under konto"</string>
+ <string name="menu_sync_remove" msgid="3266725887008450161">"Ta bort synkgrupp"</string>
+ <!-- no translation found for dialog_sync_add (8267045393119375803) -->
+ <skip />
+ <!-- no translation found for display_more_groups (2682547080423434170) -->
+ <skip />
+ <string name="display_ungrouped" msgid="4357317661311803984">"(kontakter utanför grupper)"</string>
+ <string name="display_warn_remove_ungrouped" msgid="2314043155909167610">"Om du tar bort \"<xliff:g id="GROUP">%s</xliff:g>\" från synkroniseringen tas även alla kontakter som inte tillhör grupper bort från synkroniseringen."</string>
+ <string name="account_phone" msgid="4025734638492419713">"Bara telefon (osynkad)"</string>
+ <string name="label_email_display_name" msgid="5537802602754309600">"Visningsnamn"</string>
+ <string name="call_home" msgid="1990519474420545392">"Ring hem"</string>
+ <string name="call_mobile" msgid="7502236805487609178">"Ring mobilen"</string>
+ <string name="call_work" msgid="5328785911463744028">"Ring – arbete"</string>
+ <string name="call_fax_work" msgid="7467763592359059243">"Ring – arbetsfax"</string>
+ <string name="call_fax_home" msgid="8342175628887571876">"Ring upp hemfax"</string>
+ <string name="call_pager" msgid="9003902812293983281">"Ring personsökare"</string>
+ <string name="call_custom" msgid="7756571794763171802">"Ring <xliff:g id="CUSTOM">%s</xliff:g>"</string>
+ <string name="call_car" msgid="3280537320306436445">"Ring bilen"</string>
+ <string name="call_company_main" msgid="6105120947138711257">"Ring företagets växel"</string>
+ <string name="call_mms" msgid="6274041545876221437">"Samtal – MMS"</string>
+ <string name="call_radio" msgid="8296755876398357063">"Ring radio"</string>
+ <string name="sms_home" msgid="7524332261493162995">"SMS – hem"</string>
+ <string name="sms_mobile" msgid="5200107250451030769">"SMS – mobil"</string>
+ <string name="sms_work" msgid="2269624156655267740">"SMS – arbete"</string>
+ <string name="sms_fax_work" msgid="8028189067816907075">"SMS – arbetsfax"</string>
+ <string name="sms_fax_home" msgid="9204042076306809634">"SMS – hemfax"</string>
+ <string name="sms_pager" msgid="7730404569637015192">"SMS – personsökare"</string>
+ <string name="sms_other" msgid="5131921487474531617">"SMS – annan"</string>
+ <string name="sms_custom" msgid="5932736853732191825">"Skicka SMS till <xliff:g id="CUSTOM">%s</xliff:g>"</string>
+ <string name="sms_car" msgid="7444227058437359641">"SMS – bil"</string>
+ <string name="sms_company_main" msgid="118970873419678087">"SMS – jobbväxel"</string>
+ <string name="sms_mms" msgid="4069352461380762677">"Skicka MMS"</string>
+ <string name="sms_radio" msgid="3329166673433967820">"SMS – radio"</string>
+ <string name="email_home" msgid="8573740658148184279">"E-postadress – hem"</string>
+ <string name="email_mobile" msgid="2042889209787989814">"Skicka e-post till mobil"</string>
+ <string name="email_work" msgid="2807430017302722689">"E-postadress – arbete"</string>
+ <string name="email_other" msgid="8093933498541795832">"Skicka e-post till annan"</string>
+ <string name="email_custom" msgid="7548003991586214105">"Skicka e-post till <xliff:g id="CUSTOM">%s</xliff:g>"</string>
+ <string name="email" msgid="5668400997660065897">"E-post"</string>
+ <string name="map_home" msgid="1243547733423343982">"Visa hemadress"</string>
+ <string name="map_work" msgid="1360474076921878088">"Visa jobbadress"</string>
+ <string name="map_other" msgid="5560707927535653892">"Visa annan adress"</string>
+ <string name="map_custom" msgid="6184363799976265281">"Visa adress, <xliff:g id="CUSTOM">%s</xliff:g>"</string>
+ <string name="chat_aim" msgid="2588492205291249142">"Chatta med AIM"</string>
+ <string name="chat_msn" msgid="8041633440091073484">"Chatta med Windows Live"</string>
+ <string name="chat_yahoo" msgid="6629211142719943666">"Chatta med Yahoo"</string>
+ <string name="chat_skype" msgid="1210045020427480566">"Chatta med Skype"</string>
+ <string name="chat_qq" msgid="4294637812847719693">"Chatta med QQ"</string>
+ <string name="chat_gtalk" msgid="981575737258117697">"Chatta med Google Talk"</string>
+ <string name="chat_icq" msgid="8438405386153745775">"Chatta med ICQ"</string>
+ <string name="chat_jabber" msgid="7561444230307829609">"Chatta med Jabber"</string>
+ <string name="postal_street" msgid="8133143961580058972">"Gata"</string>
+ <string name="postal_pobox" msgid="4431938829180269821">"Postbox"</string>
+ <string name="postal_neighborhood" msgid="1450783874558956739">"Område"</string>
+ <string name="postal_city" msgid="6597491300084895548">"Stad"</string>
+ <string name="postal_region" msgid="6045263193478437672">"Stat"</string>
+ <string name="postal_postcode" msgid="572136414136673751">"Postnummer"</string>
+ <string name="postal_country" msgid="7638264508416368690">"Land"</string>
+ <string name="name_given" msgid="1687286314106019813">"Förnamn"</string>
+ <string name="name_family" msgid="3416695586119999058">"Efternamn"</string>
+ <string name="name_prefix" msgid="59756378548779822">"Namnprefix"</string>
+ <string name="name_middle" msgid="8467433655992690326">"Mellannamn"</string>
+ <string name="name_suffix" msgid="3855278445375651441">"Namnsuffix"</string>
+ <string name="name_phonetic_given" msgid="6853570431394449191">"Fonetiskt förnamn"</string>
+ <string name="name_phonetic_middle" msgid="8643721493320405200">"Fonetiskt mellannamn"</string>
+ <string name="name_phonetic_family" msgid="462095502140180305">"Fonetiskt efternamn"</string>
</resources>
diff --git a/res/values-tr/config.xml b/res/values-tr/config.xml
deleted file mode 100644
index 07b3c7e..0000000
--- a/res/values-tr/config.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for config_export_vcard_type (9145748078116159716) -->
- <skip />
- <string name="config_export_dir" msgid="222182743639478636">"/sdcard"</string>
- <!-- no translation found for config_export_file_prefix (3022868431158658122) -->
- <skip />
- <!-- no translation found for config_export_file_suffix (16505844221142195) -->
- <skip />
- <string name="config_export_file_extension" msgid="1758878818611339161">"vcf"</string>
- <!-- no translation found for config_export_extensions_to_consider (5095044502091950623) -->
- <skip />
-</resources>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index 2d99eaa..1df1655 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -40,11 +40,24 @@
<string name="menu_showBarcode" msgid="309973637178814132">"Barkodu göster"</string>
<string name="menu_editContact" msgid="3452858480713561396">"Kişiyi düzenle"</string>
<string name="menu_deleteContact" msgid="1916555454274101750">"Kişiyi sil"</string>
- <string name="menu_call" msgid="7359207953236681606">"Çağrı"</string>
- <string name="menu_sendSMS" msgid="5917289601666766617">"SMS/MMS gönder"</string>
+ <!-- no translation found for menu_call (3992595586042260618) -->
+ <skip />
+ <!-- no translation found for menu_sendSMS (5535886767547006515) -->
+ <skip />
<string name="menu_sendEmail" msgid="7293508859242926187">"E-posta gönder"</string>
<string name="menu_viewAddress" msgid="1814744325763202024">"Adresi haritada göster"</string>
<string name="menu_makeDefaultNumber" msgid="4838759253316649534">"Varsayılan numara yap"</string>
+ <string name="menu_makeDefaultEmail" msgid="2599044610375789994">"Varsayılan e-posta yap"</string>
+ <string name="menu_splitAggregate" msgid="7402548456225901170">"Ayır"</string>
+ <string name="splitAggregate_title" msgid="2053462872948058798">"Kişiyi ayır"</string>
+ <string name="contactsSplitMessage" msgid="3439008547775199243">"Kişi ayrıldı"</string>
+ <string name="menu_joinAggregate" msgid="5027981918265667970">"Katıl"</string>
+ <string name="titleJoinAggregate" msgid="6970566008563147202">"Kişilere katıl"</string>
+ <string name="separatorJoinAggregateSuggestions" msgid="5494823322479834017">"Öneriler"</string>
+ <string name="separatorJoinAggregateAll" msgid="7939932265026181043">"Tüm kişiler"</string>
+ <string name="contactsJoinedMessage" msgid="7208148163607047389">"Katılan kişiler"</string>
+ <string name="menu_contactOptions" msgid="1957061455705020617">"Seçenekler"</string>
+ <string name="contactOptionsTitle" msgid="8259347644090700915">"Seçenekler"</string>
<string name="deleteConfirmation_title" msgid="6394309508930335204">"Sil"</string>
<string name="deleteConfirmation" msgid="811706994761610640">"Bu kişi silinecek."</string>
<string name="menu_done" msgid="796017761764190697">"Bitti"</string>
@@ -55,6 +68,9 @@
<string name="label_phonetic_name" msgid="2288082649573927286">"Fonetik"</string>
<string name="label_notes" msgid="8337354953278341042">"Notlar"</string>
<string name="label_ringtone" msgid="8833166825330686244">"Zil sesi"</string>
+ <string name="label_groups" msgid="7304551384542859026">"Gruplar"</string>
+ <string name="group_list" msgid="8583361685440161307">", <xliff:g id="GROUPNAME">%s</xliff:g>"</string>
+ <string name="menu_viewGroup" msgid="1401851715586577551">"Grupları düzenle"</string>
<string name="ghostData_name" msgid="6490954238641157585">"Adı ve Soyadı"</string>
<string name="ghostData_phonetic_name" msgid="7852749081984070902">"Fonetik adı"</string>
<string name="ghostData_company" msgid="5414421120553765775">"Şirket"</string>
@@ -64,6 +80,7 @@
<string name="ghostData_phone" msgid="6963153888271466620">"Telefon numarası"</string>
<string name="ghostData_email" msgid="6184537075551565919">"E-posta adresi"</string>
<string name="ghostData_postal" msgid="652611650594951897">"Posta adresi"</string>
+ <string name="ghostData_group" msgid="4504591380347534114">"Grubu görüntüle"</string>
<string name="invalidContactMessage" msgid="5816991830260044593">"Kişi mevcut değil."</string>
<string name="pickerNewContactHeader" msgid="7750705279843568147">"Yeni kişi oluştur"</string>
<string name="selectLabel" msgid="4255424123394910733">"Etiketi seç"</string>
@@ -80,7 +97,8 @@
<string name="photoPickerNotFoundText" msgid="431331662154342581">"Telefonda hiçbir resim yok."</string>
<string name="attachToContact" msgid="8820530304406066714">"Kişi simgesi"</string>
<string name="customLabelPickerTitle" msgid="1081475101983255212">"Özel etiket adı"</string>
- <string name="menu_displayGroup" msgid="4603480747214476335">"Grubu görüntüle"</string>
+ <string name="menu_displayGroup" msgid="6022511302986343108">"Grupları görüntüle"</string>
+ <string name="displayGroups" msgid="2645969886032719891">"Grupları görüntüle"</string>
<string name="syncGroupPreference" msgid="9028361137161162861">"Senk. grupları düzenle"</string>
<string name="importFromSim" msgid="8383900146531125319">"Kişileri içe aktar"</string>
<string name="send_to_voicemail_checkbox" msgid="9001686764070676353">"Çağrıları doğrudan sesli mesaja gönder"</string>
@@ -94,12 +112,23 @@
<string name="select_group_title" msgid="7955698611959835612">"Gruplar"</string>
<string name="groupEmpty" msgid="6661950109828194595">"\"<xliff:g id="GROUPNAME">%s</xliff:g>\" grubunuz boş."</string>
<string name="showAllGroups" msgid="5164410117611094297">"Tüm kişiler"</string>
+ <string name="showFilterPhones" msgid="4184858075465653970">"Yalnızca telefonu olan kişiler"</string>
+ <string name="showFilterPhonesDescrip" msgid="6644443248815191067">"Yalnızca telefon numarası olan kişileri görüntüle"</string>
+ <string name="headerContactGroups" msgid="1451383377487746789">"Kişi grupları"</string>
+ <plurals name="groupDescrip">
+ <item quantity="other" msgid="3507881585720628389">"<xliff:g id="COUNT">%0$d</xliff:g> kişi"</item>
+ </plurals>
+ <plurals name="groupDescripPhones">
+ <item quantity="other" msgid="3816047547470490208">"<xliff:g id="COUNT_0">%1$d</xliff:g> kişi, telefonu olan <xliff:g id="COUNTWITHPHONES">%2$d</xliff:g> kişi"</item>
+ </plurals>
<string name="syncAllGroups" msgid="7512169081224806890">"Tüm kişileri senkronize et"</string>
<string name="groupNameMyContacts" msgid="5696566165170977126">"Kişilerim"</string>
<string name="groupNameWithPhones" msgid="6981588604041120497">"Telefon numarası olan kişiler"</string>
<string name="starredInAndroid" msgid="6495527538140213440">"Android\'de yıldızlı"</string>
<string name="contactCreatedToast" msgid="8740102129968688060">"Kişi oluşturuldu."</string>
<string name="contactSavedToast" msgid="7152589189385441091">"Kişi kaydedildi."</string>
+ <!-- no translation found for contactSavedErrorToast (9189098776225004666) -->
+ <skip />
<string name="listSeparatorCallNumber" msgid="7115321894439629408">"Numarayı çevir"</string>
<string name="listSeparatorCallNumber_edit" msgid="2999167270900823334">"Telefon numaraları"</string>
<string name="listSeparatorSendSmsMms" msgid="2480944736714739967">"SMS/MMS gönder"</string>
@@ -110,9 +139,11 @@
<string name="listSeparatorMapAddress" msgid="7076401301758190804">"Adresi haritada göster"</string>
<string name="listSeparatorMapAddress_edit" msgid="298711187672067985">"Posta adresleri"</string>
<string name="listSeparatorOrganizations" msgid="7514083358440762504">"Kuruluşlar"</string>
+ <string name="listSeparatorGroups" msgid="1593940073983422450">"Gruplar"</string>
<string name="listSeparatorOtherInformation" msgid="7844959649638482329">"Diğer bilgiler"</string>
<string name="listSeparatorOtherInformation_edit" msgid="1326921768011367750">"Diğer seçenekler"</string>
<string name="listSeparatorMore_edit" msgid="858454837482243176">"Diğer"</string>
+ <string name="socialStreamIconLabel" msgid="4367712449555075376">"Sosyal"</string>
<string name="contactsIconLabel" msgid="7666609097606552806">"Kişiler"</string>
<string name="contactsFavoritesLabel" msgid="8417039765586853670">"Sık Kullanılanlar"</string>
<string name="dialerIconLabel" msgid="7228520966699542850">"Çevirici"</string>
@@ -128,6 +159,7 @@
<string name="recentCalls_deleteAll" msgid="6352364392762163704">"Çağrı kaydını temizle"</string>
<string name="recentCalls_empty" msgid="247053222448663107">"Çağrı kaydı boş."</string>
<string name="imei" msgid="3045126336951684285">"IMEI"</string>
+ <string name="meid" msgid="6210568493746275750">"MEID"</string>
<string name="voicemail" msgid="3851469869202611441">"Sesli Mesaj"</string>
<string name="unknown" msgid="740067747858270469">"Bilinmiyor"</string>
<string name="private_num" msgid="6374339738119166953">"Özel numara"</string>
@@ -138,7 +170,14 @@
<string name="simContacts_title" msgid="27341688347689769">"SIM kart kişileri"</string>
<string name="contactsSyncPlug" msgid="7248276704957313698"><font fgcolor="#ffffffff">"Google kişilerinizi senkronize edin!"</font>" "\n"Telefonunuzu senkronize ettikten sonra, kişilerinize gittiğiniz her yerden ulaşabilirsiniz."</string>
<string name="noContactsHelpText" msgid="2249195687463896364">"Hiçbir kişiye sahip değilsiniz."\n\n"Kişi eklemek için "<font fgcolor="#ffffffff"><b>"Menü"</b></font>"\'ye basın ve şunu seçin:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Yeni kişi"</b></font>" (yeni bir kişi oluşturmak için)"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Kişileri içe aktar"</b></font>" (kişileri SIM kartınızdan almak için)"\n</li></string>
- <string name="noContactsHelpTextWithSync" msgid="4927701496550314555">"Hiçbir kişiniz yok."\n\n"Kişi eklemek için "<font fgcolor="#ffffffff"><b>"Menü"</b></font>"\'ye basın ve şunu seçin:"\n" "\n<li><font fgcolor="#ffffffff"><b>"Senk. grupları düzenle"</b></font>" (yeni veya mevcut bir Google hesabından eklemek için )"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Yeni kişi"</b></font>" (yeni bir kişi oluşturmak için)"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"Kişileri içe aktar"</b></font>" (kişileri SIM kartınızdan eklemek için)"\n</li></string>
+ <!-- no translation found for noContactsHelpTextWithSync (2179024855526352215) -->
+ <skip />
+ <!-- no translation found for noContactsNoSimHelpText (645951808197910024) -->
+ <skip />
+ <!-- no translation found for noContactsNoSimHelpTextWithSync (511505839941079550) -->
+ <skip />
+ <!-- no translation found for noFavoritesHelpText (1705381563293866510) -->
+ <skip />
<string name="seclectSyncGroups_title" msgid="1235432026231325655">"Senkronize edilecek grupları seç"</string>
<string name="liveFolder_all_label" msgid="1552523730090319259">"Tüm Kişiler"</string>
<string name="liveFolder_favorites_label" msgid="2674341514070517105">"Yıldızlı"</string>
@@ -173,6 +212,7 @@
<string name="favoritesFrquentSeparator" msgid="5007070838253932139">"Sık aranan"</string>
<string name="add_contact_dlg_title" msgid="2789046541229846116">"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>
+ <string name="all_tab_label" msgid="4003124364397916826">"Tümü"</string>
<string name="description_image_button_one" msgid="1740638037139856139">"bir"</string>
<string name="description_image_button_two" msgid="5882638439003731308">"iki"</string>
<string name="description_image_button_three" msgid="8709731759376015180">"üç"</string>
@@ -189,19 +229,27 @@
<string name="no_sdcard_message" msgid="7791906118138538259">"Hiçbir SD Kart tespit edilmedi"</string>
<string name="searching_vcard_title" msgid="4158580043290687793">"VCard aranıyor"</string>
<string name="select_import_type_title" msgid="2443742794103731022">"Kişilerin nereden içe aktarılmasını istiyorsunuz?"</string>
- <string name="import_from_sim" msgid="3362158539070179154">"SIM Kart"</string>
- <string name="import_from_sdcard" msgid="8854793070007530689">"SD Kart"</string>
- <string name="import_all_vcard_string" msgid="2788222288962542415">"Tüm VCard dosyalarını içe aktar"</string>
- <string name="import_one_vcard_string" msgid="4618119296910253689">"Bir VCard dosyası içe aktar"</string>
+ <!-- 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 />
+ <string name="import_one_vcard_string" msgid="9059163467020328433">"Bir vCard dosyasını içe aktar"</string>
+ <string name="import_multiple_vcard_string" msgid="3810226492811062392">"Birden fazla vCard dosyasını içe aktar"</string>
+ <string name="import_all_vcard_string" msgid="5518136113853448474">"Tüm vCard dosyalarını içe aktar"</string>
<string name="searching_vcard_message" msgid="2467573749792455605">"VCard üzerinde VCard verileri aranıyor"</string>
<string name="scanning_sdcard_failed_title" msgid="2788276113399585924">"SD kartın taranması başarısız oldu"</string>
<string name="scanning_sdcard_failed_message" msgid="925361244069058117">"SD kartın taranması başarısız oldu"</string>
- <string name="fail_reason_io_error" msgid="5922864781066136340">"G/Ç Hatası"</string>
- <string name="fail_reason_vcard_parse_error" msgid="2129476089250836277">"VCard ayrıştırma başarısız"</string>
+ <string name="fail_reason_io_error" msgid="5949038573678667996">"G/Ç Hatası (Sebep: \"<xliff:g id="FAIL_REASON">%s</xliff:g>\")"</string>
+ <string name="fail_reason_vcard_parse_error" msgid="8877496734269540489">"Beklenmedik bir nedenden dolayı VCard ayrıştırılamadı"</string>
+ <string name="fail_reason_vcard_not_supported_error" msgid="166568606246563552">"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="1489638537002538332">"SD Kartta hiçbir VCard dosyası bulunamadı"</string>
<string name="fail_reason_no_vcard_entry" msgid="3808734131680935043">"Seçiminiz için geçerli VCard kaydı bulunamadı"</string>
+ <string name="fail_reason_failed_to_read_files" msgid="3659521123567134029">"İçe aktarılamayan bir veya birden fazla dosya (%s)."</string>
<string name="select_vcard_title" msgid="4615933643517710543">"VCard dosyasını seç"</string>
<string name="select_vcard_message" msgid="7028772212789057858">"Lütfen içe aktarılacak bir VCard dosyası seçin"</string>
+ <string name="progress_shower_message" msgid="5636525578293752526">"<xliff:g id="ACTION">%s</xliff:g>"\n"<xliff:g id="FILENAME">%s</xliff:g>"</string>
<string name="reading_vcard_title" msgid="430154704397375160">"VCard okunuyor"</string>
<string name="reading_vcard_message" msgid="7470486051482460267">"VCard dosyaları okunuyor"</string>
<string name="importing_vcard_message" msgid="2466665710812588738">"VCard verileri içe aktarılıyor"</string>
@@ -224,4 +272,104 @@
<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="1488865103809190124">"2 Saniyelik Duraklama Ekle"</string>
+ <string name="add_wait" msgid="8347184715316058465">"Bekleme Ekle"</string>
+ <string name="dial_button_label" msgid="7637725632722605863">"Çevir"</string>
+ <!-- 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 />
+ <string name="quickcontact_missing_app" msgid="1181287640680679441">"Bu eylemi gerçekleştirecek bir uygulama bulunamadı"</string>
+ <string name="quickcontact_remember_choice" msgid="2858356075887897116">"Bu tercihi anımsa"</string>
+ <string name="quickcontact_missing_name" msgid="7806083820928955853">"Bilinmiyor"</string>
+ <string name="menu_accounts" msgid="8499114602017077970">"Hesaplar"</string>
+ <!-- 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">"Ad"</string>
+ <string name="nicknameLabelsGroup" msgid="2891682101053358010">"Takma ad"</string>
+ <string name="organizationLabelsGroup" msgid="2478611760751832035">"Kuruluş"</string>
+ <string name="websiteLabelsGroup" msgid="4202998982804009261">"Web sitesi"</string>
+ <string name="type_short_home" msgid="7770424864090605384">"E"</string>
+ <string name="type_short_mobile" msgid="1655473281466676216">"M"</string>
+ <string name="type_short_work" msgid="4925330752504537861">"İ"</string>
+ <string name="type_short_pager" msgid="2613818970827594238">"Ç"</string>
+ <string name="type_short_other" msgid="5669407180177236769">"D"</string>
+ <string name="edit_secondary_collapse" msgid="1855127176548440583">"İkincil ayrıntılar"</string>
+ <string name="dialog_primary_name" msgid="5521591005692614833">"Ön ad"</string>
+ <string name="dialog_new_contact_account" msgid="9044704073286262197">"Hesap altında kişi oluştur"</string>
+ <string name="menu_sync_remove" msgid="3266725887008450161">"Senkronize grubu kaldır"</string>
+ <!-- no translation found for dialog_sync_add (8267045393119375803) -->
+ <skip />
+ <!-- no translation found for display_more_groups (2682547080423434170) -->
+ <skip />
+ <string name="display_ungrouped" msgid="4357317661311803984">"(Gruplandırılmamış kişiler)"</string>
+ <string name="display_warn_remove_ungrouped" msgid="2314043155909167610">"\'<xliff:g id="GROUP">%s</xliff:g>\' grubunu senkronizasyondan kaldırmak, gruplandırılmamış tüm kişileri de senkronizasyondan kaldırır."</string>
+ <string name="account_phone" msgid="4025734638492419713">"Yalnızca telefon (senkronize edilmemiş)"</string>
+ <string name="label_email_display_name" msgid="5537802602754309600">"Adı görüntüle"</string>
+ <string name="call_home" msgid="1990519474420545392">"Evi ara"</string>
+ <string name="call_mobile" msgid="7502236805487609178">"Mobil cihazı ara"</string>
+ <string name="call_work" msgid="5328785911463744028">"Ara (iş)"</string>
+ <string name="call_fax_work" msgid="7467763592359059243">"Ara (iş faksı)"</string>
+ <string name="call_fax_home" msgid="8342175628887571876">"Ara (ev faksı)"</string>
+ <string name="call_pager" msgid="9003902812293983281">"Çağrı cihazını ara"</string>
+ <string name="call_custom" msgid="7756571794763171802">"Ara (<xliff:g id="CUSTOM">%s</xliff:g>)"</string>
+ <string name="call_car" msgid="3280537320306436445">"Ara (araç)"</string>
+ <string name="call_company_main" msgid="6105120947138711257">"Ara (şirket merkezi)"</string>
+ <string name="call_mms" msgid="6274041545876221437">"Ara (MMS)"</string>
+ <string name="call_radio" msgid="8296755876398357063">"Ara (telsiz)"</string>
+ <string name="sms_home" msgid="7524332261493162995">"SMS (ev)"</string>
+ <string name="sms_mobile" msgid="5200107250451030769">"Metin (mobil)"</string>
+ <string name="sms_work" msgid="2269624156655267740">"Metin (iş)"</string>
+ <string name="sms_fax_work" msgid="8028189067816907075">"Metin (iş faksı)"</string>
+ <string name="sms_fax_home" msgid="9204042076306809634">"Metin (ev faksı)"</string>
+ <string name="sms_pager" msgid="7730404569637015192">"Metin (çağrı cihazı)"</string>
+ <string name="sms_other" msgid="5131921487474531617">"Metin (diğer)"</string>
+ <string name="sms_custom" msgid="5932736853732191825">"Metin <xliff:g id="CUSTOM">%s</xliff:g>"</string>
+ <string name="sms_car" msgid="7444227058437359641">"Metin (araç)"</string>
+ <string name="sms_company_main" msgid="118970873419678087">"Metin (şirket merkezi)"</string>
+ <string name="sms_mms" msgid="4069352461380762677">"Metin (MMS)"</string>
+ <string name="sms_radio" msgid="3329166673433967820">"Metin (telsiz)"</string>
+ <string name="email_home" msgid="8573740658148184279">"E-posta (ev)"</string>
+ <string name="email_mobile" msgid="2042889209787989814">"E-posta gönder (mobil)"</string>
+ <string name="email_work" msgid="2807430017302722689">"E-posta (iş)"</string>
+ <string name="email_other" msgid="8093933498541795832">"E-posta gönder (diğer)"</string>
+ <string name="email_custom" msgid="7548003991586214105">"E-posta <xliff:g id="CUSTOM">%s</xliff:g>"</string>
+ <string name="email" msgid="5668400997660065897">"E-posta"</string>
+ <string name="map_home" msgid="1243547733423343982">"Ev adresini görüntüle"</string>
+ <string name="map_work" msgid="1360474076921878088">"İş adresini görüntüle"</string>
+ <string name="map_other" msgid="5560707927535653892">"Diğer adresi görüntüle"</string>
+ <string name="map_custom" msgid="6184363799976265281">"<xliff:g id="CUSTOM">%s</xliff:g> adresini görüntüle"</string>
+ <string name="chat_aim" msgid="2588492205291249142">"AIM kullanarak sohbet et"</string>
+ <string name="chat_msn" msgid="8041633440091073484">"Windows Live kullanarak sohbet et"</string>
+ <string name="chat_yahoo" msgid="6629211142719943666">"Yahoo kullanarak sohbet et"</string>
+ <string name="chat_skype" msgid="1210045020427480566">"Skype kullanarak sohbet et"</string>
+ <string name="chat_qq" msgid="4294637812847719693">"QQ kullanarak sohbet et"</string>
+ <string name="chat_gtalk" msgid="981575737258117697">"Google Talk kullanarak sohbet et"</string>
+ <string name="chat_icq" msgid="8438405386153745775">"ICQ kullanarak sohbet et"</string>
+ <string name="chat_jabber" msgid="7561444230307829609">"Jabber kullanarak sohbet et"</string>
+ <string name="postal_street" msgid="8133143961580058972">"Cadde"</string>
+ <string name="postal_pobox" msgid="4431938829180269821">"Posta kutusu"</string>
+ <string name="postal_neighborhood" msgid="1450783874558956739">"Mahalle"</string>
+ <string name="postal_city" msgid="6597491300084895548">"Şehir"</string>
+ <string name="postal_region" msgid="6045263193478437672">"Eyalet"</string>
+ <string name="postal_postcode" msgid="572136414136673751">"Alan kodu"</string>
+ <string name="postal_country" msgid="7638264508416368690">"Ülke"</string>
+ <string name="name_given" msgid="1687286314106019813">"Vaftiz adı"</string>
+ <string name="name_family" msgid="3416695586119999058">"Soyadı"</string>
+ <string name="name_prefix" msgid="59756378548779822">"Ad öneki"</string>
+ <string name="name_middle" msgid="8467433655992690326">"İkinci ad"</string>
+ <string name="name_suffix" msgid="3855278445375651441">"Ad soneki"</string>
+ <string name="name_phonetic_given" msgid="6853570431394449191">"Fonetik vaftiz adı"</string>
+ <string name="name_phonetic_middle" msgid="8643721493320405200">"Fonetik ikinci ad"</string>
+ <string name="name_phonetic_family" msgid="462095502140180305">"Fonetik soyadı"</string>
</resources>
diff --git a/res/values-zh-rCN/config.xml b/res/values-zh-rCN/config.xml
deleted file mode 100644
index 07b3c7e..0000000
--- a/res/values-zh-rCN/config.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for config_export_vcard_type (9145748078116159716) -->
- <skip />
- <string name="config_export_dir" msgid="222182743639478636">"/sdcard"</string>
- <!-- no translation found for config_export_file_prefix (3022868431158658122) -->
- <skip />
- <!-- no translation found for config_export_file_suffix (16505844221142195) -->
- <skip />
- <string name="config_export_file_extension" msgid="1758878818611339161">"vcf"</string>
- <!-- no translation found for config_export_extensions_to_consider (5095044502091950623) -->
- <skip />
-</resources>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index a7ef548..cf30983 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -40,11 +40,24 @@
<string name="menu_showBarcode" msgid="309973637178814132">"显示条形码"</string>
<string name="menu_editContact" msgid="3452858480713561396">"编辑联系人"</string>
<string name="menu_deleteContact" msgid="1916555454274101750">"删除联系人"</string>
- <string name="menu_call" msgid="7359207953236681606">"呼叫"</string>
- <string name="menu_sendSMS" msgid="5917289601666766617">"发送短信/彩信"</string>
+ <!-- no translation found for menu_call (3992595586042260618) -->
+ <skip />
+ <!-- no translation found for menu_sendSMS (5535886767547006515) -->
+ <skip />
<string name="menu_sendEmail" msgid="7293508859242926187">"发送电子邮件"</string>
<string name="menu_viewAddress" msgid="1814744325763202024">"地图地址"</string>
<string name="menu_makeDefaultNumber" msgid="4838759253316649534">"设置默认号码"</string>
+ <string name="menu_makeDefaultEmail" msgid="2599044610375789994">"设置为默认电子邮件"</string>
+ <string name="menu_splitAggregate" msgid="7402548456225901170">"分离"</string>
+ <string name="splitAggregate_title" msgid="2053462872948058798">"分离联系人"</string>
+ <string name="contactsSplitMessage" msgid="3439008547775199243">"已分离联系人"</string>
+ <string name="menu_joinAggregate" msgid="5027981918265667970">"合并"</string>
+ <string name="titleJoinAggregate" msgid="6970566008563147202">"合并联系人"</string>
+ <string name="separatorJoinAggregateSuggestions" msgid="5494823322479834017">"建议"</string>
+ <string name="separatorJoinAggregateAll" msgid="7939932265026181043">"所有联系人"</string>
+ <string name="contactsJoinedMessage" msgid="7208148163607047389">"已合并联系人"</string>
+ <string name="menu_contactOptions" msgid="1957061455705020617">"选项"</string>
+ <string name="contactOptionsTitle" msgid="8259347644090700915">"选项"</string>
<string name="deleteConfirmation_title" msgid="6394309508930335204">"删除"</string>
<string name="deleteConfirmation" msgid="811706994761610640">"将会删除此联系人。"</string>
<string name="menu_done" msgid="796017761764190697">"完成"</string>
@@ -55,6 +68,9 @@
<string name="label_phonetic_name" msgid="2288082649573927286">"拼音"</string>
<string name="label_notes" msgid="8337354953278341042">"备注"</string>
<string name="label_ringtone" msgid="8833166825330686244">"铃声"</string>
+ <string name="label_groups" msgid="7304551384542859026">"群组"</string>
+ <string name="group_list" msgid="8583361685440161307">"、<xliff:g id="GROUPNAME">%s</xliff:g>"</string>
+ <string name="menu_viewGroup" msgid="1401851715586577551">"编辑群组"</string>
<string name="ghostData_name" msgid="6490954238641157585">"姓名"</string>
<string name="ghostData_phonetic_name" msgid="7852749081984070902">"姓名拼音"</string>
<string name="ghostData_company" msgid="5414421120553765775">"公司"</string>
@@ -64,6 +80,7 @@
<string name="ghostData_phone" msgid="6963153888271466620">"电话号码"</string>
<string name="ghostData_email" msgid="6184537075551565919">"电子邮件地址"</string>
<string name="ghostData_postal" msgid="652611650594951897">"邮政地址"</string>
+ <string name="ghostData_group" msgid="4504591380347534114">"显示群组"</string>
<string name="invalidContactMessage" msgid="5816991830260044593">"联系人不存在。"</string>
<string name="pickerNewContactHeader" msgid="7750705279843568147">"新建联系人"</string>
<string name="selectLabel" msgid="4255424123394910733">"选择标签"</string>
@@ -80,7 +97,8 @@
<string name="photoPickerNotFoundText" msgid="431331662154342581">"手机上没有图片。"</string>
<string name="attachToContact" msgid="8820530304406066714">"联系人图标"</string>
<string name="customLabelPickerTitle" msgid="1081475101983255212">"自定义标签名称"</string>
- <string name="menu_displayGroup" msgid="4603480747214476335">"显示群组"</string>
+ <string name="menu_displayGroup" msgid="6022511302986343108">"显示群组"</string>
+ <string name="displayGroups" msgid="2645969886032719891">"显示群组"</string>
<string name="syncGroupPreference" msgid="9028361137161162861">"编辑同步群组"</string>
<string name="importFromSim" msgid="8383900146531125319">"导入联系人"</string>
<string name="send_to_voicemail_checkbox" msgid="9001686764070676353">"直接将呼叫发送到语音信箱"</string>
@@ -94,12 +112,23 @@
<string name="select_group_title" msgid="7955698611959835612">"群组"</string>
<string name="groupEmpty" msgid="6661950109828194595">"“<xliff:g id="GROUPNAME">%s</xliff:g>”群组为空。"</string>
<string name="showAllGroups" msgid="5164410117611094297">"所有联系人"</string>
+ <string name="showFilterPhones" msgid="4184858075465653970">"仅显示有电话号码的联系人"</string>
+ <string name="showFilterPhonesDescrip" msgid="6644443248815191067">"仅显示有电话号码的联系人"</string>
+ <string name="headerContactGroups" msgid="1451383377487746789">"联系人群组"</string>
+ <plurals name="groupDescrip">
+ <item quantity="other" msgid="3507881585720628389">"<xliff:g id="COUNT">%0$d</xliff:g> 位联系人"</item>
+ </plurals>
+ <plurals name="groupDescripPhones">
+ <item quantity="other" msgid="3816047547470490208">"共 <xliff:g id="COUNT_0">%1$d</xliff:g> 位联系人,其中 <xliff:g id="COUNTWITHPHONES">%2$d</xliff:g> 位有电话号码"</item>
+ </plurals>
<string name="syncAllGroups" msgid="7512169081224806890">"同步所有联系人"</string>
<string name="groupNameMyContacts" msgid="5696566165170977126">"我的联系人"</string>
<string name="groupNameWithPhones" msgid="6981588604041120497">"具有手机号码的联系人"</string>
<string name="starredInAndroid" msgid="6495527538140213440">"Android 中加星标的联系人"</string>
<string name="contactCreatedToast" msgid="8740102129968688060">"已创建联系人。"</string>
<string name="contactSavedToast" msgid="7152589189385441091">"联系人已保存。"</string>
+ <!-- no translation found for contactSavedErrorToast (9189098776225004666) -->
+ <skip />
<string name="listSeparatorCallNumber" msgid="7115321894439629408">"拨打电话"</string>
<string name="listSeparatorCallNumber_edit" msgid="2999167270900823334">"电话号码"</string>
<string name="listSeparatorSendSmsMms" msgid="2480944736714739967">"发送短信/彩信"</string>
@@ -110,9 +139,11 @@
<string name="listSeparatorMapAddress" msgid="7076401301758190804">"地图地址"</string>
<string name="listSeparatorMapAddress_edit" msgid="298711187672067985">"邮政地址"</string>
<string name="listSeparatorOrganizations" msgid="7514083358440762504">"组织"</string>
+ <string name="listSeparatorGroups" msgid="1593940073983422450">"群组"</string>
<string name="listSeparatorOtherInformation" msgid="7844959649638482329">"其他信息"</string>
<string name="listSeparatorOtherInformation_edit" msgid="1326921768011367750">"其他选项"</string>
<string name="listSeparatorMore_edit" msgid="858454837482243176">"更多"</string>
+ <string name="socialStreamIconLabel" msgid="4367712449555075376">"社交"</string>
<string name="contactsIconLabel" msgid="7666609097606552806">"联系人"</string>
<string name="contactsFavoritesLabel" msgid="8417039765586853670">"收藏"</string>
<string name="dialerIconLabel" msgid="7228520966699542850">"拨号器"</string>
@@ -128,6 +159,7 @@
<string name="recentCalls_deleteAll" msgid="6352364392762163704">"清除通话记录"</string>
<string name="recentCalls_empty" msgid="247053222448663107">"通话记录为空。"</string>
<string name="imei" msgid="3045126336951684285">"IMEI"</string>
+ <string name="meid" msgid="6210568493746275750">"MEID"</string>
<string name="voicemail" msgid="3851469869202611441">"语音信箱"</string>
<string name="unknown" msgid="740067747858270469">"未知"</string>
<string name="private_num" msgid="6374339738119166953">"私人号码"</string>
@@ -138,7 +170,14 @@
<string name="simContacts_title" msgid="27341688347689769">"SIM 卡联系人"</string>
<string name="contactsSyncPlug" msgid="7248276704957313698"><font fgcolor="#ffffffff">"同步您的 Google 联系人!"</font>" "\n"将您的联系人同步到您的手机,然后您在任何地方都可以与他们联系。"</string>
<string name="noContactsHelpText" msgid="2249195687463896364">"您没有任何联系人。"\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>"以从您的 SIM 卡添加联系人"\n</li></string>
- <string name="noContactsHelpTextWithSync" msgid="4927701496550314555">"您没有任何联系人。"\n\n"要添加联系人,请按 "<font fgcolor="#ffffffff"><b>"MENU"</b></font>" 并选择:"\n" "\n<li><font fgcolor="#ffffffff"><b>"编辑同步群组"</b></font>"以从新的或现有的 Google 帐户添加"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"新建联系人"</b></font>"以从头开始创建新联系人"\n</li>" "\n<li><font fgcolor="#ffffffff"><b>"导入联系人"</b></font>"以从您的 SIM 卡添加联系人"\n</li></string>
+ <!-- no translation found for noContactsHelpTextWithSync (2179024855526352215) -->
+ <skip />
+ <!-- no translation found for noContactsNoSimHelpText (645951808197910024) -->
+ <skip />
+ <!-- no translation found for noContactsNoSimHelpTextWithSync (511505839941079550) -->
+ <skip />
+ <!-- no translation found for noFavoritesHelpText (1705381563293866510) -->
+ <skip />
<string name="seclectSyncGroups_title" msgid="1235432026231325655">"选择要同步的群组"</string>
<string name="liveFolder_all_label" msgid="1552523730090319259">"所有联系人"</string>
<string name="liveFolder_favorites_label" msgid="2674341514070517105">"已加星标"</string>
@@ -173,6 +212,7 @@
<string name="favoritesFrquentSeparator" msgid="5007070838253932139">"经常呼叫"</string>
<string name="add_contact_dlg_title" msgid="2789046541229846116">"添加联系人"</string>
<string name="add_contact_dlg_message_fmt" msgid="7986472669444326576">"将“<xliff:g id="EMAIL">%s</xliff:g>”添加到联系人?"</string>
+ <string name="all_tab_label" msgid="4003124364397916826">"全部"</string>
<string name="description_image_button_one" msgid="1740638037139856139">"一"</string>
<string name="description_image_button_two" msgid="5882638439003731308">"二"</string>
<string name="description_image_button_three" msgid="8709731759376015180">"三"</string>
@@ -189,19 +229,27 @@
<string name="no_sdcard_message" msgid="7791906118138538259">"未检测到 SD 卡"</string>
<string name="searching_vcard_title" msgid="4158580043290687793">"正在搜索 VCard"</string>
<string name="select_import_type_title" msgid="2443742794103731022">"您希望从何处导入联系人?"</string>
- <string name="import_from_sim" msgid="3362158539070179154">"SIM 卡"</string>
- <string name="import_from_sdcard" msgid="8854793070007530689">"SD 卡"</string>
- <string name="import_all_vcard_string" msgid="2788222288962542415">"导入所有 VCard 文件"</string>
- <string name="import_one_vcard_string" msgid="4618119296910253689">"导入一个 VCard 文件"</string>
+ <!-- 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 />
+ <string name="import_one_vcard_string" msgid="9059163467020328433">"导入一个 vCard 文件"</string>
+ <string name="import_multiple_vcard_string" msgid="3810226492811062392">"导入多个 vCard 文件"</string>
+ <string name="import_all_vcard_string" msgid="5518136113853448474">"导入所有 vCard 文件"</string>
<string name="searching_vcard_message" msgid="2467573749792455605">"正在搜索 VCard 上的 VCard 数据"</string>
<string name="scanning_sdcard_failed_title" msgid="2788276113399585924">"扫描 SD 卡失败"</string>
<string name="scanning_sdcard_failed_message" msgid="925361244069058117">"扫描 SD 卡失败"</string>
- <string name="fail_reason_io_error" msgid="5922864781066136340">"I/O 错误"</string>
- <string name="fail_reason_vcard_parse_error" msgid="2129476089250836277">"解析 VCard 失败"</string>
+ <string name="fail_reason_io_error" msgid="5949038573678667996">"I/O 错误(原因:“<xliff:g id="FAIL_REASON">%s</xliff:g>”)"</string>
+ <string name="fail_reason_vcard_parse_error" msgid="8877496734269540489">"由于某些意外原因而无法解析 vCard"</string>
+ <string name="fail_reason_vcard_not_supported_error" msgid="166568606246563552">"虽然 vCard 似乎使用了有效的格式,但系统无法对其进行解析,因为当前的实现方案不支持该格式。"</string>
<string name="fail_reason_no_vcard_file" msgid="1489638537002538332">"未在 SD 卡上找到 VCard 文件"</string>
<string name="fail_reason_no_vcard_entry" msgid="3808734131680935043">"未找到与所选内容对应的有效 VCard 条目"</string>
+ <string name="fail_reason_failed_to_read_files" msgid="3659521123567134029">"一个或多个文件导入失败 (%s)。"</string>
<string name="select_vcard_title" msgid="4615933643517710543">"选择 VCard 文件"</string>
<string name="select_vcard_message" msgid="7028772212789057858">"请选择要导入的 VCard 文件"</string>
+ <string name="progress_shower_message" msgid="5636525578293752526">"<xliff:g id="ACTION">%s</xliff:g>"\n"<xliff:g id="FILENAME">%s</xliff:g>"</string>
<string name="reading_vcard_title" msgid="430154704397375160">"正在读取 VCard"</string>
<string name="reading_vcard_message" msgid="7470486051482460267">"正在读取 VCard 文件"</string>
<string name="importing_vcard_message" msgid="2466665710812588738">"正在导入 VCard 数据"</string>
@@ -224,4 +272,104 @@
<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="1488865103809190124">"暂停时间延长 2 秒"</string>
+ <string name="add_wait" msgid="8347184715316058465">"延长等待时间"</string>
+ <string name="dial_button_label" msgid="7637725632722605863">"拨打"</string>
+ <!-- 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 />
+ <string name="quickcontact_missing_app" msgid="1181287640680679441">"未找到可处理此操作的应用程序"</string>
+ <string name="quickcontact_remember_choice" msgid="2858356075887897116">"记住此选择"</string>
+ <string name="quickcontact_missing_name" msgid="7806083820928955853">"未知"</string>
+ <string name="menu_accounts" msgid="8499114602017077970">"帐户"</string>
+ <!-- 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">"名称"</string>
+ <string name="nicknameLabelsGroup" msgid="2891682101053358010">"昵称"</string>
+ <string name="organizationLabelsGroup" msgid="2478611760751832035">"组织"</string>
+ <string name="websiteLabelsGroup" msgid="4202998982804009261">"网站"</string>
+ <string name="type_short_home" msgid="7770424864090605384">"H"</string>
+ <string name="type_short_mobile" msgid="1655473281466676216">"M"</string>
+ <string name="type_short_work" msgid="4925330752504537861">"W"</string>
+ <string name="type_short_pager" msgid="2613818970827594238">"P"</string>
+ <string name="type_short_other" msgid="5669407180177236769">"O"</string>
+ <string name="edit_secondary_collapse" msgid="1855127176548440583">"附属详情"</string>
+ <string name="dialog_primary_name" msgid="5521591005692614833">"主名称"</string>
+ <string name="dialog_new_contact_account" msgid="9044704073286262197">"在帐户下创建联系人"</string>
+ <string name="menu_sync_remove" msgid="3266725887008450161">"删除同步群组"</string>
+ <!-- no translation found for dialog_sync_add (8267045393119375803) -->
+ <skip />
+ <!-- no translation found for display_more_groups (2682547080423434170) -->
+ <skip />
+ <string name="display_ungrouped" msgid="4357317661311803984">"(未分组联系人)"</string>
+ <string name="display_warn_remove_ungrouped" msgid="2314043155909167610">"如果从同步中删除群组“<xliff:g id="GROUP">%s</xliff:g>”,则会同时删除其中所有未分组的联系人。"</string>
+ <string name="account_phone" msgid="4025734638492419713">"仅保存在手机中(不同步)"</string>
+ <string name="label_email_display_name" msgid="5537802602754309600">"显示名称"</string>
+ <string name="call_home" msgid="1990519474420545392">"呼叫住宅"</string>
+ <string name="call_mobile" msgid="7502236805487609178">"呼叫手机"</string>
+ <string name="call_work" msgid="5328785911463744028">"呼叫单位"</string>
+ <string name="call_fax_work" msgid="7467763592359059243">"呼叫单位传真"</string>
+ <string name="call_fax_home" msgid="8342175628887571876">"呼叫住宅传真"</string>
+ <string name="call_pager" msgid="9003902812293983281">"呼叫寻呼机"</string>
+ <string name="call_custom" msgid="7756571794763171802">"呼叫<xliff:g id="CUSTOM">%s</xliff:g>"</string>
+ <string name="call_car" msgid="3280537320306436445">"呼叫车载电话"</string>
+ <string name="call_company_main" msgid="6105120947138711257">"呼叫公司总机"</string>
+ <string name="call_mms" msgid="6274041545876221437">"语音彩信"</string>
+ <string name="call_radio" msgid="8296755876398357063">"呼叫无线装置"</string>
+ <string name="sms_home" msgid="7524332261493162995">"向住宅发送文本消息"</string>
+ <string name="sms_mobile" msgid="5200107250451030769">"向手机发送文本消息"</string>
+ <string name="sms_work" msgid="2269624156655267740">"向单位发送文本消息"</string>
+ <string name="sms_fax_work" msgid="8028189067816907075">"向单位传真发送文本消息"</string>
+ <string name="sms_fax_home" msgid="9204042076306809634">"向住宅传真发送文本消息"</string>
+ <string name="sms_pager" msgid="7730404569637015192">"向寻呼机发送文字消息"</string>
+ <string name="sms_other" msgid="5131921487474531617">"向其他通讯工具发送文本消息"</string>
+ <string name="sms_custom" msgid="5932736853732191825">"向<xliff:g id="CUSTOM">%s</xliff:g>发送文本消息"</string>
+ <string name="sms_car" msgid="7444227058437359641">"向车载电话发送文本消息"</string>
+ <string name="sms_company_main" msgid="118970873419678087">"向公司总机发送文本消息"</string>
+ <string name="sms_mms" msgid="4069352461380762677">"文本彩信"</string>
+ <string name="sms_radio" msgid="3329166673433967820">"向无线装置发送文本消息"</string>
+ <string name="email_home" msgid="8573740658148184279">"向住宅发送电子邮件"</string>
+ <string name="email_mobile" msgid="2042889209787989814">"向手机发送电子邮件"</string>
+ <string name="email_work" msgid="2807430017302722689">"向单位发送电子邮件"</string>
+ <string name="email_other" msgid="8093933498541795832">"向其他地址发送电子邮件"</string>
+ <string name="email_custom" msgid="7548003991586214105">"向<xliff:g id="CUSTOM">%s</xliff:g>发送电子邮件"</string>
+ <string name="email" msgid="5668400997660065897">"电子邮件"</string>
+ <string name="map_home" msgid="1243547733423343982">"查看住宅地址"</string>
+ <string name="map_work" msgid="1360474076921878088">"查看单位地址"</string>
+ <string name="map_other" msgid="5560707927535653892">"查看其他地址"</string>
+ <string name="map_custom" msgid="6184363799976265281">"查看<xliff:g id="CUSTOM">%s</xliff:g>地址"</string>
+ <string name="chat_aim" msgid="2588492205291249142">"使用 AIM 聊天"</string>
+ <string name="chat_msn" msgid="8041633440091073484">"使用 Windows Live 聊天"</string>
+ <string name="chat_yahoo" msgid="6629211142719943666">"使用雅虎软件聊天"</string>
+ <string name="chat_skype" msgid="1210045020427480566">"使用 Skype 聊天"</string>
+ <string name="chat_qq" msgid="4294637812847719693">"使用 QQ 聊天"</string>
+ <string name="chat_gtalk" msgid="981575737258117697">"使用 Google Talk 聊天"</string>
+ <string name="chat_icq" msgid="8438405386153745775">"使用 ICQ 聊天"</string>
+ <string name="chat_jabber" msgid="7561444230307829609">"使用 Jabber 聊天"</string>
+ <string name="postal_street" msgid="8133143961580058972">"街道"</string>
+ <string name="postal_pobox" msgid="4431938829180269821">"信箱"</string>
+ <string name="postal_neighborhood" msgid="1450783874558956739">"街区"</string>
+ <string name="postal_city" msgid="6597491300084895548">"城市"</string>
+ <string name="postal_region" msgid="6045263193478437672">"省/直辖市/自治区"</string>
+ <string name="postal_postcode" msgid="572136414136673751">"邮政编码"</string>
+ <string name="postal_country" msgid="7638264508416368690">"国家/地区"</string>
+ <string name="name_given" msgid="1687286314106019813">"名字"</string>
+ <string name="name_family" msgid="3416695586119999058">"姓氏"</string>
+ <string name="name_prefix" msgid="59756378548779822">"名称前缀"</string>
+ <string name="name_middle" msgid="8467433655992690326">"中间名"</string>
+ <string name="name_suffix" msgid="3855278445375651441">"名称后缀"</string>
+ <string name="name_phonetic_given" msgid="6853570431394449191">"名字拼音"</string>
+ <string name="name_phonetic_middle" msgid="8643721493320405200">"中间名拼音"</string>
+ <string name="name_phonetic_family" msgid="462095502140180305">"姓氏拼音"</string>
</resources>
diff --git a/res/values-zh-rTW/config.xml b/res/values-zh-rTW/config.xml
deleted file mode 100644
index 07b3c7e..0000000
--- a/res/values-zh-rTW/config.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for config_export_vcard_type (9145748078116159716) -->
- <skip />
- <string name="config_export_dir" msgid="222182743639478636">"/sdcard"</string>
- <!-- no translation found for config_export_file_prefix (3022868431158658122) -->
- <skip />
- <!-- no translation found for config_export_file_suffix (16505844221142195) -->
- <skip />
- <string name="config_export_file_extension" msgid="1758878818611339161">"vcf"</string>
- <!-- no translation found for config_export_extensions_to_consider (5095044502091950623) -->
- <skip />
-</resources>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index c980084..df5a614 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -40,11 +40,24 @@
<string name="menu_showBarcode" msgid="309973637178814132">"顯示條碼"</string>
<string name="menu_editContact" msgid="3452858480713561396">"編輯聯絡人"</string>
<string name="menu_deleteContact" msgid="1916555454274101750">"刪除聯絡人"</string>
- <string name="menu_call" msgid="7359207953236681606">"通話"</string>
- <string name="menu_sendSMS" msgid="5917289601666766617">"傳送 SMS/MMS"</string>
+ <!-- no translation found for menu_call (3992595586042260618) -->
+ <skip />
+ <!-- no translation found for menu_sendSMS (5535886767547006515) -->
+ <skip />
<string name="menu_sendEmail" msgid="7293508859242926187">"傳送電子郵件"</string>
<string name="menu_viewAddress" msgid="1814744325763202024">"在地圖上顯示地址"</string>
<string name="menu_makeDefaultNumber" msgid="4838759253316649534">"設為預設號碼"</string>
+ <string name="menu_makeDefaultEmail" msgid="2599044610375789994">"設為預設電子郵件"</string>
+ <string name="menu_splitAggregate" msgid="7402548456225901170">"分割"</string>
+ <string name="splitAggregate_title" msgid="2053462872948058798">"分割聯絡人"</string>
+ <string name="contactsSplitMessage" msgid="3439008547775199243">"已分割聯絡人"</string>
+ <string name="menu_joinAggregate" msgid="5027981918265667970">"合併"</string>
+ <string name="titleJoinAggregate" msgid="6970566008563147202">"合併聯絡人"</string>
+ <string name="separatorJoinAggregateSuggestions" msgid="5494823322479834017">"建議"</string>
+ <string name="separatorJoinAggregateAll" msgid="7939932265026181043">"所有聯絡人"</string>
+ <string name="contactsJoinedMessage" msgid="7208148163607047389">"已合併聯絡人"</string>
+ <string name="menu_contactOptions" msgid="1957061455705020617">"選項"</string>
+ <string name="contactOptionsTitle" msgid="8259347644090700915">"選項"</string>
<string name="deleteConfirmation_title" msgid="6394309508930335204">"刪除"</string>
<string name="deleteConfirmation" msgid="811706994761610640">"刪除此聯絡人?"</string>
<string name="menu_done" msgid="796017761764190697">"完成"</string>
@@ -55,6 +68,9 @@
<string name="label_phonetic_name" msgid="2288082649573927286">"拼音"</string>
<string name="label_notes" msgid="8337354953278341042">"附註"</string>
<string name="label_ringtone" msgid="8833166825330686244">"鈴聲"</string>
+ <string name="label_groups" msgid="7304551384542859026">"群組"</string>
+ <string name="group_list" msgid="8583361685440161307">"、<xliff:g id="GROUPNAME">%s</xliff:g>"</string>
+ <string name="menu_viewGroup" msgid="1401851715586577551">"編輯群組"</string>
<string name="ghostData_name" msgid="6490954238641157585">"姓名"</string>
<string name="ghostData_phonetic_name" msgid="7852749081984070902">"姓名拼音"</string>
<string name="ghostData_company" msgid="5414421120553765775">"公司"</string>
@@ -64,6 +80,7 @@
<string name="ghostData_phone" msgid="6963153888271466620">"電話號碼"</string>
<string name="ghostData_email" msgid="6184537075551565919">"電子郵件地址"</string>
<string name="ghostData_postal" msgid="652611650594951897">"郵寄地址"</string>
+ <string name="ghostData_group" msgid="4504591380347534114">"顯示群組"</string>
<string name="invalidContactMessage" msgid="5816991830260044593">"聯絡人不存在。"</string>
<string name="pickerNewContactHeader" msgid="7750705279843568147">"建立新聯絡人"</string>
<string name="selectLabel" msgid="4255424123394910733">"選取標籤"</string>
@@ -80,7 +97,8 @@
<string name="photoPickerNotFoundText" msgid="431331662154342581">"手機上沒有相片 。"</string>
<string name="attachToContact" msgid="8820530304406066714">"聯絡人圖示"</string>
<string name="customLabelPickerTitle" msgid="1081475101983255212">"自訂標籤名稱"</string>
- <string name="menu_displayGroup" msgid="4603480747214476335">"顯示群組"</string>
+ <string name="menu_displayGroup" msgid="6022511302986343108">"顯示群組"</string>
+ <string name="displayGroups" msgid="2645969886032719891">"顯示群組"</string>
<string name="syncGroupPreference" msgid="9028361137161162861">"編輯同步處理群組"</string>
<string name="importFromSim" msgid="8383900146531125319">"匯入聯絡人"</string>
<string name="send_to_voicemail_checkbox" msgid="9001686764070676353">"直接將來電轉到語音信箱"</string>
@@ -94,12 +112,23 @@
<string name="select_group_title" msgid="7955698611959835612">"群組"</string>
<string name="groupEmpty" msgid="6661950109828194595">"您的「<xliff:g id="GROUPNAME">%s</xliff:g>」群組是空的。"</string>
<string name="showAllGroups" msgid="5164410117611094297">"所有聯絡人"</string>
+ <string name="showFilterPhones" msgid="4184858075465653970">"僅顯示有電話號碼的聯絡人"</string>
+ <string name="showFilterPhonesDescrip" msgid="6644443248815191067">"僅顯示有電話號碼的聯絡人"</string>
+ <string name="headerContactGroups" msgid="1451383377487746789">"聯絡人群組"</string>
+ <plurals name="groupDescrip">
+ <item quantity="other" msgid="3507881585720628389">"<xliff:g id="COUNT">%0$d</xliff:g> 位聯絡人"</item>
+ </plurals>
+ <plurals name="groupDescripPhones">
+ <item quantity="other" msgid="3816047547470490208">"<xliff:g id="COUNT_0">%1$d</xliff:g> 位聯絡人,<xliff:g id="COUNTWITHPHONES">%2$d</xliff:g> 組電話號碼"</item>
+ </plurals>
<string name="syncAllGroups" msgid="7512169081224806890">"同步處理所有聯絡人"</string>
<string name="groupNameMyContacts" msgid="5696566165170977126">"我的聯絡人"</string>
<string name="groupNameWithPhones" msgid="6981588604041120497">"包含電話號碼的聯絡人資訊"</string>
<string name="starredInAndroid" msgid="6495527538140213440">"在 Android 中標有星號的聯絡人"</string>
<string name="contactCreatedToast" msgid="8740102129968688060">"已建立聯絡人。"</string>
<string name="contactSavedToast" msgid="7152589189385441091">"聯絡人已儲存。"</string>
+ <!-- no translation found for contactSavedErrorToast (9189098776225004666) -->
+ <skip />
<string name="listSeparatorCallNumber" msgid="7115321894439629408">"撥打號碼"</string>
<string name="listSeparatorCallNumber_edit" msgid="2999167270900823334">"電話號碼"</string>
<string name="listSeparatorSendSmsMms" msgid="2480944736714739967">"傳送 SMS/MMS"</string>
@@ -110,9 +139,11 @@
<string name="listSeparatorMapAddress" msgid="7076401301758190804">"在地圖上顯示地址"</string>
<string name="listSeparatorMapAddress_edit" msgid="298711187672067985">"聯絡地址"</string>
<string name="listSeparatorOrganizations" msgid="7514083358440762504">"組織"</string>
+ <string name="listSeparatorGroups" msgid="1593940073983422450">"群組"</string>
<string name="listSeparatorOtherInformation" msgid="7844959649638482329">"其他資訊"</string>
<string name="listSeparatorOtherInformation_edit" msgid="1326921768011367750">"其他選項"</string>
<string name="listSeparatorMore_edit" msgid="858454837482243176">"更多"</string>
+ <string name="socialStreamIconLabel" msgid="4367712449555075376">"社交"</string>
<string name="contactsIconLabel" msgid="7666609097606552806">"聯絡人"</string>
<string name="contactsFavoritesLabel" msgid="8417039765586853670">"我的最愛"</string>
<string name="dialerIconLabel" msgid="7228520966699542850">"撥號"</string>
@@ -128,6 +159,7 @@
<string name="recentCalls_deleteAll" msgid="6352364392762163704">"清除通話記錄"</string>
<string name="recentCalls_empty" msgid="247053222448663107">"無通話記錄。"</string>
<string name="imei" msgid="3045126336951684285">"IMEI"</string>
+ <string name="meid" msgid="6210568493746275750">"MEID"</string>
<string name="voicemail" msgid="3851469869202611441">"語音留言"</string>
<string name="unknown" msgid="740067747858270469">"未知的"</string>
<string name="private_num" msgid="6374339738119166953">"私人號碼"</string>
@@ -138,7 +170,14 @@
<string name="simContacts_title" msgid="27341688347689769">"SIM 卡聯絡人"</string>
<string name="contactsSyncPlug" msgid="7248276704957313698"><font fgcolor="#ffffffff">" 同步處理您的 Google 聯絡人!"</font>" "\n" 與手機同步處理後,無論您走到哪,都可存取聯絡人資訊。"</string>
<string name="noContactsHelpText" msgid="2249195687463896364">"您沒有聯絡人。"\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>"]:加入 SIM 卡中的聯絡人"\n</li></string>
- <string name="noContactsHelpTextWithSync" msgid="4927701496550314555">"您沒有聯絡人。"\n\n"如要新增聯絡人,請按 ["<font fgcolor="#ffffffff"><b></b>"選單"</font>"] 並選取下列選項:"\n\n" "<li>"["<font fgcolor="#ffffffff"><b></b>"編輯同步處理群組"</font>"]:加入新的或既有的 Google 帳戶聯絡人"\n</li>\n" "<li>"["<font fgcolor="#ffffffff"><b></b>"新增聯絡人"</font>"]:建立新聯絡人 "\n</li>\n" "<li>"["<font fgcolor="#ffffffff"><b></b>"匯入聯絡人"</font>"]:加入 SIM 卡中的聯絡人"\n</li></string>
+ <!-- no translation found for noContactsHelpTextWithSync (2179024855526352215) -->
+ <skip />
+ <!-- no translation found for noContactsNoSimHelpText (645951808197910024) -->
+ <skip />
+ <!-- no translation found for noContactsNoSimHelpTextWithSync (511505839941079550) -->
+ <skip />
+ <!-- no translation found for noFavoritesHelpText (1705381563293866510) -->
+ <skip />
<string name="seclectSyncGroups_title" msgid="1235432026231325655">"選取要同步處理的群組"</string>
<string name="liveFolder_all_label" msgid="1552523730090319259">"全部聯絡人"</string>
<string name="liveFolder_favorites_label" msgid="2674341514070517105">"星號標記"</string>
@@ -173,6 +212,7 @@
<string name="favoritesFrquentSeparator" msgid="5007070838253932139">"常用聯絡人"</string>
<string name="add_contact_dlg_title" msgid="2789046541229846116">"新增聯絡人"</string>
<string name="add_contact_dlg_message_fmt" msgid="7986472669444326576">"要將「<xliff:g id="EMAIL">%s</xliff:g>」新增為聯絡人嗎?"</string>
+ <string name="all_tab_label" msgid="4003124364397916826">"全部"</string>
<string name="description_image_button_one" msgid="1740638037139856139">"1"</string>
<string name="description_image_button_two" msgid="5882638439003731308">"2"</string>
<string name="description_image_button_three" msgid="8709731759376015180">"3"</string>
@@ -189,19 +229,27 @@
<string name="no_sdcard_message" msgid="7791906118138538259">"未偵測到 SD 卡"</string>
<string name="searching_vcard_title" msgid="4158580043290687793">"正在搜尋 VCard"</string>
<string name="select_import_type_title" msgid="2443742794103731022">"您要從哪裡匯入聯絡人?"</string>
- <string name="import_from_sim" msgid="3362158539070179154">"SIM 卡"</string>
- <string name="import_from_sdcard" msgid="8854793070007530689">"SD 卡"</string>
- <string name="import_all_vcard_string" msgid="2788222288962542415">"匯入所有 VCard 檔案"</string>
- <string name="import_one_vcard_string" msgid="4618119296910253689">"匯入一個 VCard 檔案"</string>
+ <!-- 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 />
+ <string name="import_one_vcard_string" msgid="9059163467020328433">"匯入一個 vCard 檔案"</string>
+ <string name="import_multiple_vcard_string" msgid="3810226492811062392">"匯入多個 vCard 檔案"</string>
+ <string name="import_all_vcard_string" msgid="5518136113853448474">"匯入所有 vCard 檔案"</string>
<string name="searching_vcard_message" msgid="2467573749792455605">"正在搜尋 VCard 資料"</string>
<string name="scanning_sdcard_failed_title" msgid="2788276113399585924">"無法掃描 SD 卡"</string>
<string name="scanning_sdcard_failed_message" msgid="925361244069058117">"無法掃描 SD 卡"</string>
- <string name="fail_reason_io_error" msgid="5922864781066136340">"I/O 錯誤"</string>
- <string name="fail_reason_vcard_parse_error" msgid="2129476089250836277">"無法剖析 VCard"</string>
+ <string name="fail_reason_io_error" msgid="5949038573678667996">"I/O 錯誤 (原因:「<xliff:g id="FAIL_REASON">%s</xliff:g>」)"</string>
+ <string name="fail_reason_vcard_parse_error" msgid="8877496734269540489">"發生不明情形,無法剖析 VCard 檔案"</string>
+ <string name="fail_reason_vcard_not_supported_error" msgid="166568606246563552">"VCard 格式正確,但目前實作系統不支援此格式,因此無法剖析"</string>
<string name="fail_reason_no_vcard_file" msgid="1489638537002538332">"SD 卡上沒有 VCard 檔案"</string>
<string name="fail_reason_no_vcard_entry" msgid="3808734131680935043">"您所選的檔案沒有有效的 VCard 項目"</string>
+ <string name="fail_reason_failed_to_read_files" msgid="3659521123567134029">"無法匯入一或多個檔案 (%s)。"</string>
<string name="select_vcard_title" msgid="4615933643517710543">"選取 VCard 檔案"</string>
<string name="select_vcard_message" msgid="7028772212789057858">"請選取要匯入的 VCard 檔案"</string>
+ <string name="progress_shower_message" msgid="5636525578293752526">"<xliff:g id="ACTION">%s</xliff:g>"\n"<xliff:g id="FILENAME">%s</xliff:g>"</string>
<string name="reading_vcard_title" msgid="430154704397375160">"讀取 VCard"</string>
<string name="reading_vcard_message" msgid="7470486051482460267">"正在讀取 VCard 檔案"</string>
<string name="importing_vcard_message" msgid="2466665710812588738">"正在匯入 VCard 資料"</string>
@@ -224,4 +272,104 @@
<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="1488865103809190124">"新增 2 秒暫停功能"</string>
+ <string name="add_wait" msgid="8347184715316058465">"新增插播功能"</string>
+ <string name="dial_button_label" msgid="7637725632722605863">"撥號"</string>
+ <!-- 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 />
+ <string name="quickcontact_missing_app" msgid="1181287640680679441">"找不到可以處理這個動作的應用程式"</string>
+ <string name="quickcontact_remember_choice" msgid="2858356075887897116">"記住這個選擇"</string>
+ <string name="quickcontact_missing_name" msgid="7806083820928955853">"不明"</string>
+ <string name="menu_accounts" msgid="8499114602017077970">"帳戶"</string>
+ <!-- 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">"姓名"</string>
+ <string name="nicknameLabelsGroup" msgid="2891682101053358010">"暱稱"</string>
+ <string name="organizationLabelsGroup" msgid="2478611760751832035">"機構"</string>
+ <string name="websiteLabelsGroup" msgid="4202998982804009261">"網站"</string>
+ <string name="type_short_home" msgid="7770424864090605384">"住家"</string>
+ <string name="type_short_mobile" msgid="1655473281466676216">"行動"</string>
+ <string name="type_short_work" msgid="4925330752504537861">"工作"</string>
+ <string name="type_short_pager" msgid="2613818970827594238">"呼叫器"</string>
+ <string name="type_short_other" msgid="5669407180177236769">"其他"</string>
+ <string name="edit_secondary_collapse" msgid="1855127176548440583">"次要詳細資料"</string>
+ <string name="dialog_primary_name" msgid="5521591005692614833">"主要名稱"</string>
+ <string name="dialog_new_contact_account" msgid="9044704073286262197">"在帳戶下建立聯絡人"</string>
+ <string name="menu_sync_remove" msgid="3266725887008450161">"移除同步處理群組"</string>
+ <!-- no translation found for dialog_sync_add (8267045393119375803) -->
+ <skip />
+ <!-- no translation found for display_more_groups (2682547080423434170) -->
+ <skip />
+ <string name="display_ungrouped" msgid="4357317661311803984">"(未分組聯絡人)"</string>
+ <string name="display_warn_remove_ungrouped" msgid="2314043155909167610">"如果從同步處理群組中移除「<xliff:g id="GROUP">%s</xliff:g>」群組,系統也會從同步處理群組中移除所有未分組的聯絡人。"</string>
+ <string name="account_phone" msgid="4025734638492419713">"僅儲存於手機 (不會同步處理)"</string>
+ <string name="label_email_display_name" msgid="5537802602754309600">"顯示名稱"</string>
+ <string name="call_home" msgid="1990519474420545392">"去電住家電話"</string>
+ <string name="call_mobile" msgid="7502236805487609178">"去電行動裝置"</string>
+ <string name="call_work" msgid="5328785911463744028">"去電公司電話"</string>
+ <string name="call_fax_work" msgid="7467763592359059243">"去電公司傳真"</string>
+ <string name="call_fax_home" msgid="8342175628887571876">"去電住家傳真"</string>
+ <string name="call_pager" msgid="9003902812293983281">"去電呼叫器"</string>
+ <string name="call_custom" msgid="7756571794763171802">"去電<xliff:g id="CUSTOM">%s</xliff:g>"</string>
+ <string name="call_car" msgid="3280537320306436445">"去電汽車電話"</string>
+ <string name="call_company_main" msgid="6105120947138711257">"去電公司代表號"</string>
+ <string name="call_mms" msgid="6274041545876221437">"去電 MMS"</string>
+ <string name="call_radio" msgid="8296755876398357063">"去電無線電"</string>
+ <string name="sms_home" msgid="7524332261493162995">"傳送簡訊至住家電話"</string>
+ <string name="sms_mobile" msgid="5200107250451030769">"傳送簡訊至行動裝置"</string>
+ <string name="sms_work" msgid="2269624156655267740">"傳送簡訊至公司電話"</string>
+ <string name="sms_fax_work" msgid="8028189067816907075">"傳送簡訊至公司傳真"</string>
+ <string name="sms_fax_home" msgid="9204042076306809634">"傳送簡訊至住家傳真"</string>
+ <string name="sms_pager" msgid="7730404569637015192">"傳送簡訊至呼叫器"</string>
+ <string name="sms_other" msgid="5131921487474531617">"傳送簡訊至其他裝置"</string>
+ <string name="sms_custom" msgid="5932736853732191825">"傳送簡訊至<xliff:g id="CUSTOM">%s</xliff:g>"</string>
+ <string name="sms_car" msgid="7444227058437359641">"傳送簡訊至汽車電話"</string>
+ <string name="sms_company_main" msgid="118970873419678087">"傳送簡訊至公司代表號"</string>
+ <string name="sms_mms" msgid="4069352461380762677">"傳送 MMS 簡訊"</string>
+ <string name="sms_radio" msgid="3329166673433967820">"傳送簡訊至無線電"</string>
+ <string name="email_home" msgid="8573740658148184279">"傳送電子郵件至住家電子郵件地址"</string>
+ <string name="email_mobile" msgid="2042889209787989814">"傳送電子郵件至行動裝置"</string>
+ <string name="email_work" msgid="2807430017302722689">"傳送電子郵件至公司電子郵件地址"</string>
+ <string name="email_other" msgid="8093933498541795832">"傳送電子郵件至其他電子郵件地址"</string>
+ <string name="email_custom" msgid="7548003991586214105">"傳送電子郵件至<xliff:g id="CUSTOM">%s</xliff:g>"</string>
+ <string name="email" msgid="5668400997660065897">"傳送電子郵件"</string>
+ <string name="map_home" msgid="1243547733423343982">"檢視住家地址"</string>
+ <string name="map_work" msgid="1360474076921878088">"檢視公司地址"</string>
+ <string name="map_other" msgid="5560707927535653892">"檢視其他地址"</string>
+ <string name="map_custom" msgid="6184363799976265281">"檢視<xliff:g id="CUSTOM">%s</xliff:g>地址"</string>
+ <string name="chat_aim" msgid="2588492205291249142">"使用 AIM 進行即時通訊"</string>
+ <string name="chat_msn" msgid="8041633440091073484">"使用 Windows Live 進行即時通訊"</string>
+ <string name="chat_yahoo" msgid="6629211142719943666">"使用 Yahoo 進行即時通訊"</string>
+ <string name="chat_skype" msgid="1210045020427480566">"使用 Skype 進行即時通訊"</string>
+ <string name="chat_qq" msgid="4294637812847719693">"使用 QQ 進行即時通訊"</string>
+ <string name="chat_gtalk" msgid="981575737258117697">"使用 Google Talk 進行即時通訊"</string>
+ <string name="chat_icq" msgid="8438405386153745775">"使用 ICQ 進行即時通訊"</string>
+ <string name="chat_jabber" msgid="7561444230307829609">"使用 Jabber 進行即時通訊"</string>
+ <string name="postal_street" msgid="8133143961580058972">"街道"</string>
+ <string name="postal_pobox" msgid="4431938829180269821">"郵政信箱"</string>
+ <string name="postal_neighborhood" msgid="1450783874558956739">"鄰"</string>
+ <string name="postal_city" msgid="6597491300084895548">"鄉/鎮/市/區"</string>
+ <string name="postal_region" msgid="6045263193478437672">"州/省"</string>
+ <string name="postal_postcode" msgid="572136414136673751">"郵遞區號"</string>
+ <string name="postal_country" msgid="7638264508416368690">"國家/地區"</string>
+ <string name="name_given" msgid="1687286314106019813">"名字"</string>
+ <string name="name_family" msgid="3416695586119999058">"姓氏"</string>
+ <string name="name_prefix" msgid="59756378548779822">"姓名前稱銜"</string>
+ <string name="name_middle" msgid="8467433655992690326">"中間名"</string>
+ <string name="name_suffix" msgid="3855278445375651441">"姓名後稱銜"</string>
+ <string name="name_phonetic_given" msgid="6853570431394449191">"名字 (拼音)"</string>
+ <string name="name_phonetic_middle" msgid="8643721493320405200">"中間名 (拼音)"</string>
+ <string name="name_phonetic_family" msgid="462095502140180305">"姓氏 (拼音)"</string>
</resources>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
new file mode 100644
index 0000000..661634b
--- /dev/null
+++ b/res/values/attrs.xml
@@ -0,0 +1,40 @@
+<?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.
+-->
+
+<resources>
+ <!-- Maps a contact kind or mime-type to a RemoteViews XML formatter -->
+ <declare-styleable name="Mapping">
+ <!-- Mime-type handled by this mapping -->
+ <attr name="mimeType" format="string" />
+ <!-- RemoteViews XML that should be used to format this data -->
+ <attr name="remoteViews" format="reference" />
+ <!-- Icon that should be used to represent this data -->
+ <attr name="icon" format="reference" />
+ <!-- Column in data table to summarize this data -->
+ <attr name="summaryColumn" format="string" />
+ <!-- Column in data table to show details of this data -->
+ <attr name="detailColumn" format="string" />
+ </declare-styleable>
+
+ <declare-styleable name="EdgeTriggerView">
+ <attr name="edgeWidth" format="dimension" />
+ <attr name="listenEdges">
+ <flag name="left" value="0x01" />
+ <flag name="right" value="0x02" />
+ </attr>
+ </declare-styleable>
+
+</resources>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index fb4019d..880c269 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.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,4 +17,7 @@
<resources>
<color name="textColorIconOverlay">#fff</color>
<color name="textColorIconOverlayShadow">#000</color>
+ <color name="sect_secondary">#4fff</color>
+ <color name="quickcontact_disambig">#f2f2f2</color>
+ <color name="quickcontact_disambig_divider">#afafaf</color>
</resources>
diff --git a/res/values/config.xml b/res/values/config.xml
index d7a3be4..93d19e9 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -19,7 +19,7 @@
<resources>
<!-- Flag indicating whether Contacts app is allowed to import contacts from SDCard -->
- <bool name="config_allow_import_from_sdcard">false</bool>
+ <bool name="config_allow_import_from_sdcard">true</bool>
<!-- If true, all vcard files are imported from SDCard without asking a user.
If not, dialog shows to let the user to select whether all vcard files are imported or not.
If the user selects "not", then the application ask the user to select a file.-->
@@ -29,27 +29,37 @@
skipped and the importer asks the user to choose one vcard file.
If config_import_all_vcard_from_sdcard_automatically is set true, this configuration
is ignored. -->
- <bool name="config_allow_users_select_all_vcard_import">false</bool>
+ <bool name="config_allow_users_select_all_vcard_import">true</bool>
<!-- Flag indicating whether Contacts app is allowed to export contacts to SDCard -->
- <bool name="config_allow_export_to_sdcard">false</bool>
+ <bool name="config_allow_export_to_sdcard">true</bool>
- <!-- The type of VCard for export. Without specifying this, generic VCard will be emitted.
- If you want to let the app emit VCard specific to some vendor (like DoCoMo),
- please specify the type.-->
- <string name="config_export_vcard_type"></string>
+ <!-- If true, enable vibration (haptic feedback) for dialer key presses.
+ The pattern is set on a per-platform basis using config_virtualKeyVibePattern.
+ TODO: If enough users are annoyed by this, we might eventually
+ need to make it a user preference rather than a per-platform
+ resource. -->
+ <bool name="config_enable_dialer_key_vibration">true</bool>
+
+ <!-- The type of vcard for improt. If the vcard importer cannot guess the exact type
+ of a vCard type, the improter uses this type. -->
+ <string name="config_import_vcard_type" translatable="false">default</string>
+
+ <!-- The type of VCard for export. If you want to let the app emit vCard which is
+ specific to some vendor (like DoCoMo), specify this type (e.g. "docomo") -->
+ <string name="config_export_vcard_type" translatable="false">default</string>
<!-- Directory in which exported VCard file is stored -->
- <string name="config_export_dir">/sdcard</string>
+ <string name="config_export_dir" translatable="false">/sdcard</string>
<!-- Prefix of exported VCard file -->
- <string name="config_export_file_prefix"></string>
+ <string name="config_export_file_prefix" translatable="false"></string>
<!-- Suffix of exported VCard file. Attached before an extension -->
- <string name="config_export_file_suffix"></string>
+ <string name="config_export_file_suffix" translatable="false"></string>
<!-- Extension for exported VCard files -->
- <string name="config_export_file_extension">vcf</string>
+ <string name="config_export_file_extension" translatable="false">vcf</string>
<!-- Minimum number of exported VCard file index -->
<integer name="config_export_file_min_index">1</integer>
@@ -61,5 +71,12 @@
config_export_extension. e.g. If "aaa" is added to here and 00001.vcf and 00002.aaa
exist in a target directory, 00003.vcf becomes a next file name candidate.
Without this configuration, 00002.vcf becomes the candidate.-->
- <string name="config_export_extensions_to_consider"></string>
+ <string name="config_export_extensions_to_consider" translatable="false"></string>
+
+ <!-- If true, show an onscreen "Dial" button in the dialer.
+ In practice this is used on all platforms even the ones with hard SEND/END
+ keys, but for maximum flexibility it's controlled by a flag here
+ (which can be overridden on a per-product basis.) -->
+ <bool name="config_show_onscreen_dial_button">true</bool>
+
</resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
new file mode 100644
index 0000000..a82f5a9
--- /dev/null
+++ b/res/values/dimens.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>
+ <dimen name="quickcontact_shadow_horiz">30dip</dimen>
+ <dimen name="quickcontact_shadow_vert">37dip</dimen>
+ <dimen name="quickcontact_shadow_touch">20dip</dimen>
+
+ <dimen name="edit_photo_size">76dip</dimen>
+
+ <!-- The height of the ScrollingTabWidget -->
+ <dimen name="tab_height">40dip</dimen>
+ <dimen name="account_name_height">25dip</dimen>
+</resources>
diff --git a/res/values/ids.xml b/res/values/ids.xml
index fb1fe41..5e287ad 100644
--- a/res/values/ids.xml
+++ b/res/values/ids.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,4 +17,39 @@
<resources>
<!-- The EditText for entries in the EditContactActivity -->
<item type="id" name="data" />
+
+ <item type="id" name="header_phones" />
+
+ <item type="id" name="dialog_delete" />
+ <item type="id" name="dialog_photo" />
+ <item type="id" name="dialog_name" />
+ <item type="id" name="dialog_label" />
+ <item type="id" name="dialog_label_custom" />
+
+ <item type="id" name="dialog_sync_add" />
+
+ <item type="id" name="dialog_import_export" />
+
+ <!-- For 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_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 ExportVCard -->
+ <item type="id" name="dialog_export_confirmation" />
+ <item type="id" name="dialog_exporting_vcard" />
+ <item type="id" name="dialog_fail_to_export_with_reason" />
+
</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index c6add99..4e311f9 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.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.
@@ -21,7 +21,7 @@
<!-- Title for the activity that dials the phone. This is the name
used in the Launcher icon. -->
- <string name="launcherDialer">Dialer</string>
+ <string name="launcherDialer">Phone</string>
<!-- Name of activity that allows users to create shortcuts on the home screen to a contact.
This shows up in a list of things like bookmark, folder, music playlist, etc -->
@@ -29,21 +29,21 @@
<!-- Name of activity that allows users to create shortcuts on the home screen to dial a contact.
This shows up in a list of things like bookmark, folder, music playlist, etc -->
- <string name="shortcutDialContact">Direct Dial</string>
+ <string name="shortcutDialContact">Direct dial</string>
<!-- Name of activity that allows users to create shortcuts on the home screen to message (SMS) a contact.
This shows up in a list of things like bookmark, folder, music playlist, etc -->
- <string name="shortcutMessageContact">Direct Message</string>
+ <string name="shortcutMessageContact">Direct message</string>
<!-- Activity title when the user is selecting a contact for a shortcut. -->
<string name="shortcutActivityTitle">Choose a contact shortcut</string>
-
+
<!-- Activity title when the user is selecting a contact for a direct dial shortcut. -->
<string name="callShortcutActivityTitle">Choose a number to call</string>
-
+
<!-- Activity title when the user is selecting a contact for a direct message shortcut. -->
<string name="messageShortcutActivityTitle">Choose a number to message</string>
-
+
<!-- Title for the activity that shows only starred contacts -->
<string name="starredList">Starred</string>
@@ -56,11 +56,11 @@
<!-- The title bar when viewing the contact details activity -->
<string name="viewContactTitle">Contact details</string>
-
+
<!-- The description presented to the user in the Intent choose when there are multiple activities that allow
viewing a contact. This string represents the built in way to view the contact. -->
<string name="viewContactDesription">View contact</string>
-
+
<!-- The description presented to the user in the Intent choose when there are multiple activities that allow
editing a contact. This string represents the built in way to edit the contact. -->
<string name="editContactDescription">Edit contact</string>
@@ -100,10 +100,10 @@
<string name="menu_deleteContact">Delete contact</string>
<!-- Menu item used to call a specific contact when viewing the details of that contact. -->
- <string name="menu_call">Call</string>
+ <string name="menu_call">Call contact</string>
<!-- Menu item used to send an SMS or MMS message to a specific phone number or a contacts default phone number -->
- <string name="menu_sendSMS">Send SMS/MMS</string>
+ <string name="menu_sendSMS">Text contact</string>
<!-- Menu item used to send an email message to a specific email address -->
<string name="menu_sendEmail">Send email</string>
@@ -115,10 +115,74 @@
try to call a contact without specifying a specific number. -->
<string name="menu_makeDefaultNumber">Make default number</string>
+ <!-- Menu item that makes an email address the default for a contact. The default email used
+ when you try to email a contact without specifying a specific address. -->
+ <string name="menu_makeDefaultEmail">Make default email</string>
+
+ <!-- Menu item that splits an item from the contact detail into a separate aggregate -->
+ <string name="menu_splitAggregate">Separate</string>
+
+ <!-- Dialog title for the list of constituent contacts in the split aggregate dialog. -->
+ <string name="splitAggregate_title">Split contact</string>
+
+ <!-- Toast shown after a contact has been split from an aggregate by a user action -->
+ <string name="contactsSplitMessage">Contacts separated</string>
+
+ <!-- Title of the confirmation dialog for separating contacts into multiple instances -->
+ <string name="splitConfirmation_title">Separate Contact</string>
+
+ <!-- Confirmation dialog for separating contacts into multiple instances -->
+ <string name="splitConfirmation">Are you sure you want to separate this single contact
+ into multiple contacts: one for each set of contact information that was joined into it?
+ </string>
+
+ <!-- Menu item that joins an aggregate with another aggregate -->
+ <string name="menu_joinAggregate">Join</string>
+
+ <!-- Menu item to toggle the tabs that show where the contact data comes from. -->
+ <string name="menu_showSources">Show sources</string>
+ <string name="menu_hideSources">Hide sources</string>
+
+ <!-- Activity title for the Join Contact list -->
+ <string name="titleJoinAggregate">Join contact</string>
+
+ <!-- Heading of the Join Contact screen -->
+ <string name="titleJoinContactDataWith">Join contacts</string>
+
+ <!-- Info blurb on the Join Contact screen -->
+ <string name="blurbJoinContactDataWith">Select the contact you want to join with <xliff:g id="name">%s</xliff:g>.</string>
+
+ <!-- An item in the Join Contact activity that opens up the full contact A-Z list -->
+ <string name="showAllContactsJoinItem">Show all contacts</string>
+
+ <!-- List separator for the Join Contact list: Suggestions -->
+ <string name="separatorJoinAggregateSuggestions">Suggested contacts</string>
+
+ <!-- List separator for the Join Contact list: A-Z -->
+ <string name="separatorJoinAggregateAll">All contacts</string>
+
+ <!-- Toast shown after two contacts have been joined by a user action -->
+ <string name="contactsJoinedMessage">Contacts joined</string>
+
+ <!-- Menu item that opens the Options activity for a given contact -->
+ <string name="menu_contactOptions">Options</string>
+
+ <!-- Title for the Options activity for a given contact -->
+ <string name="contactOptionsTitle">Options</string>
+
<!-- Confirmation dialog title after users selects to delete a contact. -->
<string name="deleteConfirmation_title">Delete</string>
- <!-- Confirmation dialog contents after users selects to delete a contact. -->
+ <!-- Warning dialog contents after users selects to delete a ReadOnly contact. -->
+ <string name="readOnlyContactWarning">You cannot delete contacts from read-only accounts, but you can hide them in your contacts lists.</string>
+
+ <!-- Warning dialog contents after users selects to delete a contact with ReadOnly and Writable sources. -->
+ <string name="readOnlyContactDeleteConfirmation">This contact contains information from multiple accounts. Information from read-only accounts will be hidden in your contacts lists, not deleted.</string>
+
+ <!-- Warning dialog contents after users selects to delete a contact with multiple Writable sources. -->
+ <string name="multipleContactDeleteConfirmation">Deleting this contact will delete information from multiple accounts.</string>
+
+ <!-- Confirmation dialog contents after users selects to delete a Writable contact. -->
<string name="deleteConfirmation">This contact will be deleted.</string>
<!-- Menu item to indicate you are done editing a contact and want to save the changes you've made -->
@@ -145,6 +209,15 @@
<!-- The label describing the custom ringtone for a contact -->
<string name="label_ringtone">Ringtone</string>
+ <!-- The label for a list of all the groups that the contact is associated with -->
+ <string name="label_groups">Groups</string>
+
+ <!-- Provides a delimeter in a list of groups. For example, "g1, g2" has ", g2" generated using this string -->
+ <string name="group_list">, <xliff:g id="groupName">%s</xliff:g></string>
+
+ <!-- Menu item used to send user to the edit contact screen -->
+ <string name="menu_viewGroup">Edit groups</string>
+
<!-- Hint text for the contact name when editing -->
<string name="ghostData_name">First and Last</string>
@@ -172,6 +245,9 @@
<!-- Hint text for the postal address field when editing -->
<string name="ghostData_postal">Postal address</string>
+ <!-- Hint text for the group field when editing -->
+ <string name="ghostData_group">Display group</string>
+
<!-- Message displayed in a toast when you try to view the details of a contact that
for some reason doesn't exist anymore. -->
<string name="invalidContactMessage">The contact does not exist.</string>
@@ -222,11 +298,14 @@
<string name="customLabelPickerTitle">Custom label name</string>
<!-- The menu item to open the list of groups to display -->
- <string name="menu_displayGroup">Display group</string>
+ <string name="menu_displayGroup">Display options</string>
+
+ <!-- Title of activity that lets user pick which contact groups to display -->
+ <string name="displayGroups">Display options</string>
<!-- The menu item that leads to the settings for contact syncing -->
<string name="syncGroupPreference">Edit sync groups</string>
-
+
<!-- The menu item that launches the SIM card import activity -->
<string name="importFromSim">Import contacts</string>
@@ -248,6 +327,9 @@
<!-- The text displayed when the contacts list is empty while displaying all contacts -->
<string name="noContacts">No contacts.</string>
+ <!-- The text displayed when the contacts list is empty while displaying results after searching contacts -->
+ <string name="noMatchingContacts">No matching contacts found.</string>
+
<!-- The text displayed when the contacts list is empty while displaying only contacts that have phone numbers -->
<string name="noContactsWithPhoneNumbers">No contacts with phone numbers.</string>
@@ -257,31 +339,59 @@
<!-- Title for group selection dialog. The dialog contains a list of contact groups that the
user can pick from, indicating they only want to see the contacts in that group. -->
<string name="select_group_title">Groups</string>
-
+
<!-- The text displayed when the contacts list is empty while displaying a single group of contacts -->
<string name="groupEmpty">Your \"<xliff:g id="groupName">%s</xliff:g>\" group is empty.</string>
<!-- The description of all groups when asking the user what they want to display -->
<string name="showAllGroups">All contacts</string>
+ <!-- The title of the filter to only show contacts with phone numbers -->
+ <string name="showFilterPhones">Only contacts with phones</string>
+
+ <!-- The description of the filter to only show contacts with phone numbers -->
+ <string name="showFilterPhonesDescrip">Only display contacts that have phone numbers</string>
+
+ <!-- The header over the list of all contacts groups -->
+ <string name="headerContactGroups">Choose contacts to display</string>
+
+ <!-- The description of a group with the total number of contacts -->
+ <plurals name="groupDescrip">
+ <item quantity="other"><xliff:g id="count">%0$d</xliff:g> contacts</item>
+ </plurals>
+
+ <!-- The description of a group with the total number of contacts, and the total number of contacts with phone numbers -->
+ <plurals name="groupDescripPhones">
+ <item quantity="other"><xliff:g id="count">%1$d</xliff:g> contacts, <xliff:g id="countWithPhones">%2$d</xliff:g> with phones</item>
+ </plurals>
+
<!-- The setting to sync all contacts from the server -->
<string name="syncAllGroups">Sync all contacts</string>
<!-- The name of the system "My Contacts" group. This should be kept in sync with the web UI -->
- <string name="groupNameMyContacts">My Contacts</string>
-
+ <string name="groupNameMyContacts">My contacts</string>
+
<!-- The group type that displays only contacts with phone numbers -->
<string name="groupNameWithPhones">Contacts with phone numbers</string>
-
+
<!-- The group type that displays "Starred in Android" contacts -->
<string name="starredInAndroid">Starred in Android</string>
-
+
+ <!-- Displayed in a spinner dialog after the user creates a contact and it's being saved to the database -->
+ <string name="savingContact">Saving contact\u2026</string>
+
+ <!-- Displayed in a spinner dialog as user changes to display options are saved -->
+ <string name="savingDisplayGroups">Saving display options\u2026</string>
+
<!-- Toast displayed when a contact is created -->
<string name="contactCreatedToast">Contact created.</string>
-
+
<!-- Toast displayed when a contact is saved -->
<string name="contactSavedToast">Contact saved.</string>
+ <!-- Toast displayed when saving a contact failed -->
+ <string name="contactSavedErrorToast">Error, unable to save contact changes.</string>
+
<!-- Separator in the contact details list describing that the items below it will place a call when clicked -->
<string name="listSeparatorCallNumber">Dial number</string>
@@ -289,7 +399,7 @@
<string name="listSeparatorCallNumber_edit">Phone numbers</string>
<!-- Separator in the contact details list describing that the items below it will send an SMS/MMS to a phone number -->
- <string name="listSeparatorSendSmsMms">Send SMS/MMS</string>
+ <string name="listSeparatorSendSmsMms">Send text</string>
<!-- Separator in the contact details list describing that the items below it will send an email -->
<string name="listSeparatorSendEmail">Send email</string>
@@ -312,6 +422,9 @@
<!-- Separator in the contact details list describing that the items below are non-actionable organization information -->
<string name="listSeparatorOrganizations">Organizations</string>
+ <!-- Separator in the contact details list describing that the items below are non-actionable group information -->
+ <string name="listSeparatorGroups">Groups</string>
+
<!-- Separator in the contact details list describing that the items below are random other non-actionable information about a contact -->
<string name="listSeparatorOtherInformation">Other information</string>
@@ -321,6 +434,36 @@
<!-- Section header in the Edit Contacts screen for the "Add more items" button -->
<string name="listSeparatorMore_edit">More</string>
+ <!-- Displayed at the top of the contacts showing the total number of contacts visible when "Only contacts with phones" is selected -->
+ <plurals name="listTotalPhoneContacts">
+ <item quantity="one">Displaying 1 contact with phone number</item>
+ <item quantity="other">Displaying <xliff:g id="count">%d</xliff:g> contacts with phone numbers</item>
+ </plurals>
+
+ <!-- Displayed at the top of the contacts showing the zero as total number of contacts visible when "Only contacts with phones" is selected -->
+ <string name="listTotalPhoneContactsZero">No visible contacts with phone numbers</string>
+
+ <!-- Displayed at the top of the contacts showing the total number of contacts visible when "Only contacts with phones" not selected -->
+ <plurals name="listTotalAllContacts">
+ <item quantity="one">Displaying 1 contact</item>
+ <item quantity="other">Displaying <xliff:g id="count">%d</xliff:g> contacts</item>
+ </plurals>
+
+ <!-- Displayed at the top of the contacts showing the zero total number of contacts visible when "Only contacts with phones" not selected -->
+ <string name="listTotalAllContactsZero">No visible contacts</string>
+
+ <!-- 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>
+ </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>
+
+ <!-- The description text for the social activity stream tab. Space is limited for this string, so the shorter the better -->
+ <string name="socialStreamIconLabel">Social</string>
+
<!-- The description text for the contacts tab. Space is limited for this string, so the shorter the better -->
<string name="contactsIconLabel">Contacts</string>
@@ -328,7 +471,7 @@
<string name="contactsFavoritesLabel">Favorites</string>
<!-- The description text for the dialer tab. Space is limited for this string, so the shorter the better -->
- <string name="dialerIconLabel">Dialer</string>
+ <string name="dialerIconLabel">Phone</string>
<!-- The description text for the call log tab. Space is limited for this string, so the shorter the better -->
<string name="recentCallsIconLabel">Call log</string>
@@ -337,7 +480,7 @@
<string name="liveFolderPhone">Contacts with phone numbers</string>
<!-- Menu item used to send an SMS or MMS message to a phone number -->
- <string name="menu_sendTextMessage">Send SMS message</string>
+ <string name="menu_sendTextMessage">Send text message</string>
<!-- Menu item used to call a contact from the call log -->
<string name="recentCalls_callNumber">Call <xliff:g id="name">%s</xliff:g></string>
@@ -360,6 +503,9 @@
<!-- The title of a dialog that displays the IMEI of the phone -->
<string name="imei">IMEI</string>
+ <!-- The title of a dialog that displays the MEID of the CDMA phone -->
+ <string name="meid">MEID</string>
+
<!-- String used for displaying calls to the voicemail number in the call log -->
<string name="voicemail">Voicemail</string>
@@ -368,7 +514,7 @@
<!-- String used to display calls from private numbers in the call log -->
<string name="private_num">Private number</string>
-
+
<!-- String used to display calls from pay phone in the call log -->
<string name="payphone">Pay phone</string>
@@ -392,30 +538,55 @@
<string name="contactsSyncPlug"><font fgcolor="#ffffffff">Sync your Google contacts!</font>
\nAfter syncing to your phone, your contacts will be available to you wherever you go.</string>
+
<!-- Displayed full screen when the user has no contacts and they are displaying the My Contacts group, and contact syncing is disabled -->
- <string name="noContactsHelpText">"You don't have any contacts.\n\nTo add contacts, press <font fgcolor="#ffffffff"><b>Menu</b></font> and select:\n
+ <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 contacts</b></font> to add contacts from your SIM card\n</li>"
+ \n<li><font fgcolor="#ffffffff"><b>Import/Export</b></font>\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 -->
- <string name="noContactsHelpTextWithSync">"You don't have any contacts.\n\nTo add contacts, press <font fgcolor="#ffffffff"><b>Menu</b></font> and select:\n
- \n<li><font fgcolor="#ffffffff"><b>Edit sync groups</b></font> to add from a new or existing Google account\n</li>
+ <string name="noContactsHelpTextWithSync">"You don't have any contacts to display. (If you just added an account, it can take a few minutes to sync contacts.)\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>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 contacts</b></font> to add contacts from your SIM card\n</li>"
+ \n<li><font fgcolor="#ffffffff"><b>Import/Export</b></font>\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>"
+ </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) -->
+ <string name="noContactsNoSimHelpTextWithSync">"You don't have any contacts to display. (If you just added an account, it can take a few minutes to sync contacts.)\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>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>"
+ </string>
+
+ <!-- Displayed full screen when the user has no favorites and they are displaying the favorites tab -->
+ <string name="noFavoritesHelpText">"You don't have any favorites.\n\nTo add a contact to your list of favorites:\n
+ <li>Touch the <b>Contacts</b> tab\n</li>
+ \n<li>Touch the contact you want to add to your favorites\n</li>
+ \n<li>Touch the star next to the contact\'s name\n</li>"
</string>
<!-- Activity title for the activity that lets the user choose which groups of contacts to sync from the server -->
<string name="seclectSyncGroups_title">Select groups to sync</string>
<!-- Live folder label for all contacts -->
- <string name="liveFolder_all_label">All Contacts</string>
+ <string name="liveFolder_all_label">All contacts</string>
<!-- Live folder label for only starred contacts -->
<string name="liveFolder_favorites_label">Starred</string>
<!-- Live folder label for all contacts with phone numbers -->
- <string name="liveFolder_phones_label">Phones</string>
+ <string name="liveFolder_phones_label">Phones</string>
<!-- Item label: jump to the in-call DTMF dialpad.
(Part of a list of options shown in the dialer when another call
@@ -443,7 +614,7 @@
<!-- Title for incoming call details screen -->
<string name="type_incoming">Incoming call</string>
-
+
<!-- Title for outgoing call details screen -->
<string name="type_outgoing">Outgoing call</string>
@@ -485,13 +656,13 @@
<!-- Description for incoming calls going to voice mail vs. not -->
<string name="actionIncomingCall">Incoming calls</string>
-
+
<!-- Detail text for incoming calls going to voice mail -->
<string name="detailIncomingCallsGoToVoicemail">Will be sent directly to voicemail</string>
<!-- Description of what the contact specific ringtone is set to -->
<string name="detailsRingtone">Set to <xliff:g id="ringtone_name" example="Flutey Phone">%s</xliff:g></string>
-
+
<!-- Action string for calling back a number in the call log -->
<string name="callBack">Call back</string>
@@ -508,11 +679,14 @@
<string name="favoritesFrquentSeparator">Frequently called</string>
<!-- Dialog title when prompting before creating a contact -->
- <string name="add_contact_dlg_title">Add Contact</string>
+ <string name="add_contact_dlg_title">Add contact</string>
<!-- Dialog message when prompting before creating a contact. Includes
the email address, e.g. "Add xyz@foo.com to contacts?" -->
<string name="add_contact_dlg_message_fmt">Add \"<xliff:g id="email">%s</xliff:g>\" to contacts?</string>
+ <!-- Label for the all data tab in the view and edit card -->
+ <string name="all_tab_label">All</string>
+
<!-- Content description values -->
<!-- String describing the image on ImageButton one
@@ -600,70 +774,91 @@
<string name="description_image_button_pound">pound</string>
<!-- Dialog title shown when SD Card does not exist -->
- <string name="no_sdcard_title">No SD Card</string>
+ <string name="no_sdcard_title">No SD card</string>
<!-- Dialog message shown when SDcard does not exist -->
- <string name="no_sdcard_message">No SD Card detected</string>
+ <string name="no_sdcard_message">No SD card detected</string>
<!-- Dialog title shown when searching VCard data from SD Card -->
- <string name="searching_vcard_title">Searching for VCard</string>
+ <string name="searching_vcard_title">Searching for vCard</string>
<!-- Dialog title shown when asking a user whether import contact data from SIM or SD Card -->
<string name="select_import_type_title">Where would you like to import contacts from?</string>
<!-- Action string for selecting SIM for importing contacts -->
- <string name="import_from_sim">SIM Card</string>
+ <string name="import_from_sim">Import from SIM card</string>
<!-- Action string for selecting SD Card for importing contacts -->
- <string name="import_from_sdcard">SD Card</string>
+ <string name="import_from_sdcard">Import from SD card</string>
- <!-- "Import all VCard files" -->
- <string name="import_all_vcard_string">Import all VCard files</string>
+ <!-- Action that exports all contacts to SD Card -->
+ <string name="export_to_sdcard">Export to SD card</string>
- <!-- "Import one VCard file" -->
- <string name="import_one_vcard_string">Import one VCard file</string>
+ <!-- "Import one vCard file" -->
+ <string name="import_one_vcard_string">Import one vCard file</string>
+
+ <!-- "Import more than one vCard -->
+ <string name="import_multiple_vcard_string">Import multiple vCard files</string>
+
+ <!-- "Import all vCard files" -->
+ <string name="import_all_vcard_string">Import all vCard files</string>
<!-- Dialog message shown when searching VCard data from SD Card -->
- <string name="searching_vcard_message">Searching for VCard data on VCard</string>
+ <string name="searching_vcard_message">Searching for vCard data on SD card</string>
<!-- Dialog title shown when searching VCard data failed. -->
- <string name="scanning_sdcard_failed_title">Scanning SD Card failed</string>
+ <string name="scanning_sdcard_failed_title">Scanning SD card failed</string>
<!-- Dialog message shown when searching VCard data failed. -->
- <string name="scanning_sdcard_failed_message">Scanning SD Card failed</string>
+ <string name="scanning_sdcard_failed_message">Scanning SD card failed (Reason: \"<xliff:g id="fail_reason">%s</xliff:g>\")</string>
<!-- The failed reason: "I/O Error" -->
<string name="fail_reason_io_error">I/O Error</string>
<!-- The failed reason: "Failed to parse VCard data" -->
- <string name="fail_reason_vcard_parse_error">Failed to parse VCard</string>
+ <string name="fail_reason_vcard_parse_error">Failed to parse vCard for unexpected reason</string>
+
+ <!-- The failed reason: "The VCard is not supported right now, but may be supported in the future" -->
+ <string name="fail_reason_vcard_not_supported_error">Failed to parse vCard though it seems in valid format, since the current implementation does not support it</string>
<!-- The failed reason: "There is no VCard file" -->
- <string name="fail_reason_no_vcard_file">No VCard file found on SD Card</string>
+ <string name="fail_reason_no_vcard_file">No vCard file found on the SD card</string>
<!-- The failed reason: "There is no valid VCard entry in the file(s)" -->
- <string name="fail_reason_no_vcard_entry">No valid VCard entry found for your selection</string>
+ <string name="fail_reason_no_vcard_entry">No valid vCard entry found for your selection</string>
+
+ <!-- The failed reason: "One or more files failed to be imported. (<a list of file names>)" -->
+ <string name="fail_reason_failed_to_read_files">One or more files failed to be imported (%s).</string>
+
+ <!-- The failed reason: "Unknown error". This message 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 -->
- <string name="select_vcard_title">Select VCard file</string>
+ <string name="select_vcard_title">Select vCard file</string>
<!-- Dialog message shown when a user is asked to choose VCard file -->
- <string name="select_vcard_message">Please select a VCard file to import</string>
+ <string name="select_vcard_message">Please select a vCard file to import</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">%s</xliff:g>\n<xliff:g id="filename" example="foo.vcf">%s</xliff:g></string>
<!-- Dialog title shown when reading VCard data -->
- <string name="reading_vcard_title">Reading VCard</string>
+ <string name="reading_vcard_title">Reading vCard</string>
<!-- Dialog message shown when reading a VCard file -->
- <string name="reading_vcard_message">Reading VCard file(s)</string>
+ <string name="reading_vcard_message">Reading vCard file(s)</string>
<!-- Dialog message shown when importing VCard data into local database -->
- <string name="importing_vcard_message">Importing VCard data</string>
+ <string name="importing_vcard_message">Importing vCard data</string>
<!-- Dialog title shown when reading VCard data failed -->
- <string name="reading_vcard_failed_title">Reading of VCard data has failed</string>
+ <string name="reading_vcard_failed_title">Reading of vCard data has failed</string>
<!-- Dialog message shown when reading VCard data failed -->
- <string name="reading_vcard_failed_message">VCard data could not be read\nReason for failure: \"<xliff:g id="fail_reason">%s</xliff:g>\"</string>
+ <string name="reading_vcard_failed_message">Could not read vCard.\nReason for failure: \"<xliff:g id="fail_reason">%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">%s</xliff:g> of <xliff:g id="total_number">%s</xliff:g> contacts</string>
@@ -671,26 +866,35 @@
<!-- 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">%s</xliff:g> of <xliff:g id="total_number">%s</xliff:g> files</string>
+ <!-- The message for exporting all contacts in the phone, without caring its Account -->
+ <string name="export_all_contacts">All contacts</string>
+
+ <!-- The message for exporting only contacts is the phone locally, which are not associated with any account -->
+ <string name="export_phone_local_only">Locally stored contacts</string>
+
<!-- The menu item that launches VCard export activity -->
<string name="export_contact_list">Export contacts</string>
-
+
<!-- Dialog title shown when a user confirms whether he/she export Contact data -->
- <string name="confirm_export_title">Confirmation for export</string>
-
+ <string name="confirm_export_title">Confirm export</string>
+
<!-- Dialog message shown when a user confirms whether he/she export Contact data -->
- <string name="confirm_export_message">Is it ok to export your contact list to \"<xliff:g id="vcard_filename">%s</xliff:g>\"?</string>
-
+ <string name="confirm_export_message">Are you sure you want to export your contact list to \"<xliff:g id="vcard_filename">%s</xliff:g>\"?</string>
+
<!-- Dialog title shown when exporting Contact data failed -->
- <string name="exporting_contact_failed_title">Exporting contact data has failed</string>
-
+ <string name="exporting_contact_failed_title">Failed to export contact data</string>
+
<!-- Dialog message shown when exporting Contact data failed -->
- <string name="exporting_contact_failed_message">Exporting contact data has failed\nReason for failure: \"<xliff:g id="fail_reason">%s</xliff:g>\"</string>
+ <string name="exporting_contact_failed_message">Failed to export contact data.\nReason for failure: \"<xliff:g id="fail_reason">%s</xliff:g>\"</string>
+
+ <!-- The failed reason: "There is no exportable contact" -->
+ <string name="fail_reason_no_exportable_contact">There is no exportable contact</string>
<!-- The failed reason: "Too many vcard files on the SD Card" -->
- <string name="fail_reason_too_many_vcard">Too many VCard data on the SD Card</string>
+ <string name="fail_reason_too_many_vcard">Too many vCard files on the SD card</string>
<!-- The failed reason: "Too long filename". This error usually does not happen. -->
- <string name="fail_reason_too_long_filename">Too long filename is required (\"<xliff:g id="filename">%s</xliff:g>\")</string>
+ <string name="fail_reason_too_long_filename">Required filename is too long (\"<xliff:g id="filename">%s</xliff:g>\")</string>
<!-- The failed reason: "Cannot open or create the destination directory" -->
<string name="fail_reason_cannot_open_destination_dir">Cannot open or create the destination directory\"<xliff:g id="dir_name">%s</xliff:g>\"</string>
@@ -700,19 +904,314 @@
<!-- Message shown when the application is exporting contact data outside -->
<string name="exporting_contact_list_message">Exporting contact data to \"<xliff:g id="file_name">%s</xliff:g>\"</string>
-
+
<!-- The failed reason: "Could not initialize the exporter" -->
<string name="fail_reason_could_not_initialize_exporter">Could not initialize the exporter: \"<xliff:g id="exact_reason">%s</xliff:g>\"</string>
-
+
<!-- The failed reason: "Error occured during export" -->
<string name="fail_reason_error_occurred_during_export">Error occured during export: \"<xliff:g id="exact_reason">%s</xliff:g>\"</string>
-
+
+ <!-- The error reason the vCard composer emits: "Failed to get database information" -->
+ <string name="composer_failed_to_get_database_infomation">Failed to get database information</string>
+
+ <!-- The error reason the vCard composer emits: "There is no exportable contact. You might choose un-exportable data" -->
+ <string name="composer_has_no_exportable_contact">There is no exportable contact. You might choose un-exportable data</string>
+
+ <!-- The error reason the vCard composer emits: "The vCard composer object is not correctly initialized" -->
+ <string name="composer_not_initialized">The vCard composer is not correctly initialized</string>
+
<!-- The failed reason: "Could not open a specific file" -->
<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">%s</xliff:g> of <xliff:g id="total_number">%s</xliff:g> contacts</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>
-</resources>
\ No newline at end of file
+
+ <!-- Menu items for dialpad options as part of Pause and Wait ftr -->
+ <string name="add_2sec_pause">Add 2-sec pause</string>
+ <string name="add_wait">Add wait</string>
+
+ <!-- Label for onscreen "Dial" button -->
+ <string name="dial_button_label">Dial</string>
+
+ <!-- Title for the call disambiguation dialog -->
+ <string name="call_disambig_title">Call using</string>
+
+ <!-- Title for the sms disambiguation dialog -->
+ <string name="sms_disambig_title">Text using</string>
+
+ <!-- Message next to disamgiguation dialog check box -->
+ <string name="make_primary">Remember this choice</string>
+
+ <!-- Shown as a toast when the user taps on a Fast-Track icon, and no application
+ was found that could perform the selected action -->
+ <string name="quickcontact_missing_app">No application found to handle this action</string>
+
+ <!-- Shown as the checkbox label that, when checked, will store remember the
+ selected choice and shortcut to it in the future. For example, this would
+ make a selected phone number the default. -->
+ <string name="quickcontact_remember_choice">Remember this choice</string>
+
+ <!-- Shown as the header name for a person when the name is missing or unknown. -->
+ <string name="quickcontact_missing_name">Unknown</string>
+
+ <!-- The menu item to open the list of accounts -->
+ <string name="menu_accounts">Accounts</string>
+
+ <!-- The menu item to bulk import or bulk export contacts from SIM card or SD card. -->
+ <string name="menu_import_export">Import/Export</string>
+
+ <!-- Dialog title when selecting the bulk operation to perform from a list. -->
+ <string name="dialog_import_export">Import/Export contacts</string>
+
+ <!-- The menu item to share the currently viewed contact -->
+ <string name="menu_share">Share</string>
+
+ <!-- Dialog title when picking the application to share a contact with. -->
+ <string name="share_via">Share contact via</string>
+
+ <!-- Toast indicating that sharing a contact has failed. -->
+ <string name="share_error">This contact cannot be shared.</string>
+
+ <!-- Header that expands to list all name types when editing a structured name of a contact -->
+ <string name="nameLabelsGroup">Name</string>
+ <!-- Header that expands to list all nickname types when editing a nickname of a contact -->
+ <string name="nicknameLabelsGroup">Nickname</string>
+ <!-- Header that expands to list all organization types when editing an organization of a contact -->
+ <string name="organizationLabelsGroup">Organization</string>
+ <!-- Header that expands to list all website types when editing a website of a contact -->
+ <string name="websiteLabelsGroup">Website</string>
+ <!-- Header that expands to list all event types when editing an event of a contact -->
+ <string name="eventLabelsGroup">Event</string>
+
+ <!-- Single-character overlay for home phone numbers when creating desktop shortcuts -->
+ <string name="type_short_home">H</string>
+ <!-- Single-character overlay for mobile phone numbers when creating desktop shortcuts -->
+ <string name="type_short_mobile">M</string>
+ <!-- Single-character overlay for work phone numbers when creating desktop shortcuts -->
+ <string name="type_short_work">W</string>
+ <!-- Single-character overlay for pager phone numbers when creating desktop shortcuts -->
+ <string name="type_short_pager">P</string>
+ <!-- Single-character overlay for other phone numbers when creating desktop shortcuts -->
+ <string name="type_short_other">O</string>
+
+ <!-- In edit dialog, shown if the contact is marked as being read-only -->
+ <string name="edit_read_only">This contact is read-only</string>
+
+ <!-- Shown as the header title over a collapsible section that, by default, hides
+ secondary contact detail edit fields, such as birthday. -->
+ <string name="edit_secondary_collapse">More</string>
+
+ <string name="dialog_primary_name">Primary name</string>
+ <string name="dialog_new_contact_account">Create contact under account</string>
+
+ <string name="menu_sync_remove">Remove sync group</string>
+ <string name="dialog_sync_add">Add sync group</string>
+ <string name="display_more_groups">More groups\u2026</string>
+
+ <!-- List title for a special contacts group that covers all contacts.-->
+ <string name="display_ungrouped">All Other Contacts</string>
+
+ <!-- List title for a special contacts group that covers all contacts that
+ aren't members of any other group. -->
+ <string name="display_all_contacts">All Contacts</string>
+
+ <!-- Warning message given to users just before they remove a currently syncing
+ group that would also cause all ungrouped contacts to stop syncing. -->
+ <string name="display_warn_remove_ungrouped">Removing \'<xliff:g id="group" example="Starred">%s</xliff:g>\' from sync will also remove any ungrouped contacts from sync.</string>
+
+ <!-- Title for data source when creating or editing a contact that doesn't
+ belong to a specific account. This contact will only exist on the phone
+ and will not be synced. -->
+ <string name="account_phone">Phone-only (unsynced)</string>
+
+ <!-- The label describing the display name field of an Email address, allowing the
+ user to give a specific name to describe this address. -->
+ <string name="label_email_display_name">Display name</string>
+
+ <!-- Action string for calling a custom phone number -->
+ <string name="call_custom">Call <xliff:g id="custom">%s</xliff:g></string>
+ <!-- Action string for calling a home phone number -->
+ <string name="call_home">Call home</string>
+ <!-- Action string for calling a mobile phone number -->
+ <string name="call_mobile">Call mobile</string>
+ <!-- Action string for calling a work phone number -->
+ <string name="call_work">Call work</string>
+ <!-- Action string for calling a work fax phone number -->
+ <string name="call_fax_work">Call work fax</string>
+ <!-- Action string for calling a home fax phone number -->
+ <string name="call_fax_home">Call home fax</string>
+ <!-- Action string for calling a pager phone number -->
+ <string name="call_pager">Call pager</string>
+ <!-- Action string for calling an other phone number -->
+ <string name="call_other">Call other</string>
+ <!-- Action string for calling a callback number -->
+ <string name="call_callback">Call callback</string>
+ <!-- Action string for calling a car phone number -->
+ <string name="call_car">Call car</string>
+ <!-- Action string for calling a company main phone number -->
+ <string name="call_company_main">Call company main</string>
+ <!-- Action string for calling a ISDN phone number -->
+ <string name="call_isdn">Call ISDN</string>
+ <!-- Action string for calling a main phone number -->
+ <string name="call_main">Call main</string>
+ <!-- Action string for calling an other fax phone number -->
+ <string name="call_other_fax">Call other fax</string>
+ <!-- Action string for calling a radio phone number -->
+ <string name="call_radio">Call radio</string>
+ <!-- Action string for calling a Telex phone number -->
+ <string name="call_telex">Call telex</string>
+ <!-- Action string for calling a TTY/TDD phone number -->
+ <string name="call_tty_tdd">Call TTY/TDD</string>
+ <!-- Action string for calling a work mobile phone number -->
+ <string name="call_work_mobile">Call work mobile</string>
+ <!-- Action string for calling a work pager phone number -->
+ <string name="call_work_pager">Call work pager</string>
+ <!-- Action string for calling an assistant phone number -->
+ <string name="call_assistant">Call <xliff:g id="assistant">%s</xliff:g></string>
+ <!-- Action string for calling a MMS phone number -->
+ <string name="call_mms">Call MMS</string>
+
+ <!-- Action string for sending an SMS to a custom phone number -->
+ <string name="sms_custom">Text <xliff:g id="custom">%s</xliff:g></string>
+ <!-- Action string for sending an SMS to a home phone number -->
+ <string name="sms_home">Text home</string>
+ <!-- Action string for sending an SMS to a mobile phone number -->
+ <string name="sms_mobile">Text mobile</string>
+ <!-- Action string for sending an SMS to a work phone number -->
+ <string name="sms_work">Text work</string>
+ <!-- Action string for sending an SMS to a work fax phone number -->
+ <string name="sms_fax_work">Text work fax</string>
+ <!-- Action string for sending an SMS to a home fax phone number -->
+ <string name="sms_fax_home">Text home fax</string>
+ <!-- Action string for sending an SMS to a pager phone number -->
+ <string name="sms_pager">Text pager</string>
+ <!-- Action string for sending an SMS to an other phone number -->
+ <string name="sms_other">Text other</string>
+ <!-- Action string for sending an SMS to a callback number -->
+ <string name="sms_callback">Text callback</string>
+ <!-- Action string for sending an SMS to a car phone number -->
+ <string name="sms_car">Text car</string>
+ <!-- Action string for sending an SMS to a company main phone number -->
+ <string name="sms_company_main">Text company main</string>
+ <!-- Action string for sending an SMS to a ISDN phone number -->
+ <string name="sms_isdn">Text ISDN</string>
+ <!-- Action string for sending an SMS to a main phone number -->
+ <string name="sms_main">Text main</string>
+ <!-- Action string for sending an SMS to an other fax phone number -->
+ <string name="sms_other_fax">Text other fax</string>
+ <!-- Action string for sending an SMS to a radio phone number -->
+ <string name="sms_radio">Text radio</string>
+ <!-- Action string for sending an SMS to a Telex phone number -->
+ <string name="sms_telex">Text telex</string>
+ <!-- Action string for sending an SMS to a TTY/TDD phone number -->
+ <string name="sms_tty_tdd">Text TTY/TDD</string>
+ <!-- Action string for sending an SMS to a work mobile phone number -->
+ <string name="sms_work_mobile">Text work mobile</string>
+ <!-- Action string for sending an SMS to a work pager phone number -->
+ <string name="sms_work_pager">Text work pager</string>
+ <!-- Action string for sending an SMS to an assistant phone number -->
+ <string name="sms_assistant">Text <xliff:g id="assistant">%s</xliff:g></string>
+ <!-- Action string for sending an SMS to a MMS phone number -->
+ <string name="sms_mms">Text MMS</string>
+
+ <!-- Action string for sending an email to a home email address -->
+ <string name="email_home">Email home</string>
+ <!-- Action string for sending an email to a mobile email address -->
+ <string name="email_mobile">Email mobile</string>
+ <!-- Action string for sending an email to a work email address -->
+ <string name="email_work">Email work</string>
+ <!-- Action string for sending an email to an other email address -->
+ <string name="email_other">Email other</string>
+ <!-- Action string for sending an email to a custom email address -->
+ <string name="email_custom">Email <xliff:g id="custom">%s</xliff:g></string>
+
+ <!-- Generic action string for sending an email -->
+ <string name="email">Email</string>
+
+ <!-- Action string for viewing a home postal address -->
+ <string name="map_home">View home address</string>
+ <!-- Action string for viewing a work postal address -->
+ <string name="map_work">View work address</string>
+ <!-- Action string for viewing an other postal address -->
+ <string name="map_other">View other address</string>
+ <!-- Action string for viewing a custom postal address -->
+ <string name="map_custom">View <xliff:g id="custom">%s</xliff:g> address</string>
+
+ <!-- Action string for starting an IM chat with the AIM protocol -->
+ <string name="chat_aim">Chat using AIM</string>
+ <!-- Action string for starting an IM chat with the MSN or Windows Live protocol -->
+ <string name="chat_msn">Chat using Windows Live</string>
+ <!-- Action string for starting an IM chat with the Yahoo protocol -->
+ <string name="chat_yahoo">Chat using Yahoo</string>
+ <!-- Action string for starting an IM chat with the Skype protocol -->
+ <string name="chat_skype">Chat using Skype</string>
+ <!-- Action string for starting an IM chat with the QQ protocol -->
+ <string name="chat_qq">Chat using QQ</string>
+ <!-- Action string for starting an IM chat with the Google Talk protocol -->
+ <string name="chat_gtalk">Chat using Google Talk</string>
+ <!-- Action string for starting an IM chat with the ICQ protocol -->
+ <string name="chat_icq">Chat using ICQ</string>
+ <!-- Action string for starting an IM chat with the Jabber protocol -->
+ <string name="chat_jabber">Chat using Jabber</string>
+
+ <!-- Generic action string for starting an IM chat -->
+ <string name="chat">Chat</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 -->
+ <string name="postal_pobox">PO box</string>
+ <!-- Field title for the neighborhood of a structured postal address of a contact -->
+ <string name="postal_neighborhood">Neighborhood</string>
+ <!-- Field title for the city of a structured postal address of a contact -->
+ <string name="postal_city">City</string>
+ <!-- Field title for the region, or state, of a structured postal address of a contact -->
+ <string name="postal_region">State</string>
+ <!-- Field title for the postal code of a structured postal address of a contact -->
+ <string name="postal_postcode">ZIP code</string>
+ <!-- Field title for the country of a structured postal address of a contact -->
+ <string name="postal_country">Country</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 -->
+ <string name="name_family">Family name</string>
+ <!-- Field title for the prefix name of a contact -->
+ <string name="name_prefix">Name prefix</string>
+ <!-- Field title for the middle name of a contact -->
+ <string name="name_middle">Middle name</string>
+ <!-- Field title for the suffix name of a contact -->
+ <string name="name_suffix">Name suffix</string>
+ <!-- Field title for the phonetic given name of a contact -->
+ <string name="name_phonetic_given">Phonetic given name</string>
+ <!-- Field title for the phonetic middle name of a contact -->
+ <string name="name_phonetic_middle">Phonetic middle name</string>
+ <!-- Field title for the phonetic family name of a contact -->
+ <string name="name_phonetic_family">Phonetic family name</string>
+
+ <!-- The title for the action to remove a contact from an aggregated contact -->
+ <string name="split_label">Split</string>
+ <!-- The explanation of what "split" will do. This needs word-smithing. -->
+ <string name="split_explanation">Make this data its own contact.</string>
+
+ <!-- Formatting string for account name -->
+ <string name="account_name_format">From <xliff:g id="source" example="Gmail">%1$s</xliff:g> account: <xliff:g id="account" example="user@gmail.com">%2$s</xliff:g></string>
+
+ <!-- String describing which account type a contact came from when editing it -->
+ <string name="account_type_format"><xliff:g id="source" example="Gmail">%1$s</xliff:g> contact</string>
+
+ <!-- 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>
+
+ <!-- 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>
+
+ <!-- Text used to explain that a contact cannot be edited since the data is read only -->
+ <string name="contact_read_only"><xliff:g id="source" example="Gmail">%1$s</xliff:g> contact information is not editable on this device.</string>
+
+ <!-- Text describing that a contact has no information available other than name and photo -->
+ <string name="no_contact_details">No additional information for this contact</string>
+</resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 998da21..8c5ae35 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.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.
@@ -15,6 +15,10 @@
-->
<resources>
+ <style name="DialtactsTheme" parent="@android:Theme">
+ <item name="android:windowNoTitle">true</item>
+ <item name="android:windowContentOverlay">@null</item>
+ </style>
<style name="MinusButton">
<item name="android:background">@drawable/btn_circle</item>
@@ -44,4 +48,41 @@
<item name="android:windowIsFloating">true</item>
</style>
+ <style name="FullyTranslucent" parent="android:Theme.Translucent.NoTitleBar">
+ <item name="android:windowContentOverlay">@null</item>
+ </style>
+
+ <style name="FullyTranslucent.QuickContact">
+ <!-- This is a hack because we want to be able to animate away the
+ QuickContact window, and we close its containing activity at the
+ same time. So put in a dummy animation so this guy sticks around
+ while the fast track window is animating. -->
+ <item name="android:windowAnimationStyle">@style/DummyAnimation</item>
+ </style>
+
+ <style name="QuickContact">
+ <item name="android:windowFrame">@null</item>
+ <item name="android:windowBackground">@android:color/transparent</item>
+ <item name="android:windowIsFloating">true</item>
+ <item name="android:windowContentOverlay">@null</item>
+ <!-- TODO: create our own animation style in framework -->
+ <!--
+ <item name="android:windowAnimationStyle">@*android:style/Animation.ZoomButtons</item>
+ -->
+ </style>
+
+ <style name="QuickContactAboveAnimation">
+ <item name="android:windowEnterAnimation">@anim/quickcontact_above_enter</item>
+ <item name="android:windowExitAnimation">@anim/quickcontact_above_exit</item>
+ </style>
+
+ <style name="QuickContactBelowAnimation">
+ <item name="android:windowEnterAnimation">@anim/quickcontact_below_enter</item>
+ <item name="android:windowExitAnimation">@anim/quickcontact_below_exit</item>
+ </style>
+
+ <style name="DummyAnimation">
+ <item name="android:windowExitAnimation">@anim/dummy_animation</item>
+ </style>
+
</resources>
diff --git a/res/xml/searchable.xml b/res/xml/searchable.xml
index 0deed7e..af8b3ef 100644
--- a/res/xml/searchable.xml
+++ b/res/xml/searchable.xml
@@ -25,9 +25,9 @@
android:searchMode="queryRewriteFromText"
android:includeInGlobalSearch="true"
- android:searchSuggestAuthority="contacts"
+ android:searchSuggestAuthority="com.android.contacts"
android:searchSuggestIntentAction="android.provider.Contacts.SEARCH_SUGGESTION_CLICKED"
- android:searchSuggestIntentData="content://contacts/people"
+ android:searchSuggestIntentData="content://com.android.contacts/contacts"
android:searchSettingsDescription="@string/search_settings_description"
>
diff --git a/src/com/android/contacts/AttachImage.java b/src/com/android/contacts/AttachImage.java
index 8c91722..6970842 100644
--- a/src/com/android/contacts/AttachImage.java
+++ b/src/com/android/contacts/AttachImage.java
@@ -16,15 +16,32 @@
package com.android.contacts;
+import com.google.android.collect.Maps;
+
import android.app.Activity;
+import android.content.ContentProviderOperation;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
import android.content.Intent;
+import android.content.OperationApplicationException;
+import android.database.Cursor;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
-import android.provider.Contacts;
-import android.provider.Contacts.People;
+import android.os.RemoteException;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.RawContacts;
+import android.provider.ContactsContract.CommonDataKinds.Photo;
+import android.widget.Toast;
+
+import com.android.contacts.model.ExchangeSource;
+import com.android.contacts.model.GoogleSource;
import java.io.ByteArrayOutputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
/**
* Provides an external interface for other applications to attach images
@@ -36,36 +53,62 @@
private static final int REQUEST_PICK_CONTACT = 1;
private static final int REQUEST_CROP_PHOTO = 2;
- private static final String CONTACT_URI_KEY = "contact_uri";
+ private static final String RAW_CONTACT_URIS_KEY = "raw_contact_uris";
public AttachImage() {
}
- Uri mContactUri;
+ private Long[] mRawContactIds;
+
+ private ContentResolver mContentResolver;
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
if (icicle != null) {
- mContactUri = icicle.getParcelable(CONTACT_URI_KEY);
+ mRawContactIds = toClassArray(icicle.getLongArray(RAW_CONTACT_URIS_KEY));
} else {
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
- intent.setType(People.CONTENT_ITEM_TYPE);
+ intent.setType(Contacts.CONTENT_ITEM_TYPE);
startActivityForResult(intent, REQUEST_PICK_CONTACT);
}
+
+ mContentResolver = getContentResolver();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
- if (mContactUri != null) {
- outState.putParcelable(CONTACT_URI_KEY, mContactUri);
+ if (mRawContactIds != null && mRawContactIds.length != 0) {
+ outState.putLongArray(RAW_CONTACT_URIS_KEY, toPrimativeArray(mRawContactIds));
}
}
+ private static long[] toPrimativeArray(Long[] in) {
+ if (in == null) {
+ return null;
+ }
+ long[] out = new long[in.length];
+ for (int i = 0; i < in.length; i++) {
+ out[i] = in[i];
+ }
+ return out;
+ }
+
+ private static Long[] toClassArray(long[] in) {
+ if (in == null) {
+ return null;
+ }
+ Long[] out = new Long[in.length];
+ for (int i = 0; i < in.length; i++) {
+ out[i] = in[i];
+ }
+ return out;
+ }
+
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent result) {
if (resultCode != RESULT_OK) {
@@ -74,7 +117,6 @@
}
if (requestCode == REQUEST_PICK_CONTACT) {
- mContactUri = result.getData();
// A contact was picked. Launch the cropper to get face detection, the right size, etc.
// TODO: get these values from constants somewhere
Intent myIntent = getIntent();
@@ -89,18 +131,120 @@
intent.putExtra("outputY", 96);
intent.putExtra("return-data", true);
startActivityForResult(intent, REQUEST_CROP_PHOTO);
+
+ // while they're cropping, convert the contact into a raw_contact
+ final long contactId = ContentUris.parseId(result.getData());
+ final ArrayList<Long> rawContactIdsList = ContactsUtils.queryForAllRawContactIds(
+ mContentResolver, contactId);
+ mRawContactIds = new Long[rawContactIdsList.size()];
+ mRawContactIds = rawContactIdsList.toArray(mRawContactIds);
+
+ if (mRawContactIds == null || rawContactIdsList.isEmpty()) {
+ Toast.makeText(this, R.string.contactSavedErrorToast, Toast.LENGTH_LONG).show();
+ }
} else if (requestCode == REQUEST_CROP_PHOTO) {
final Bundle extras = result.getExtras();
- if (extras != null) {
+ if (extras != null && mRawContactIds != null) {
Bitmap photo = extras.getParcelable("data");
if (photo != null) {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
photo.compress(Bitmap.CompressFormat.JPEG, 75, stream);
- Contacts.People.setPhotoData(getContentResolver(), mContactUri,
- stream.toByteArray());
+
+ final ContentValues imageValues = new ContentValues();
+ imageValues.put(Photo.PHOTO, stream.toByteArray());
+ imageValues.put(RawContacts.Data.IS_SUPER_PRIMARY, 1);
+
+ // attach the photo to every raw contact
+ for (Long rawContactId : mRawContactIds) {
+
+ // exchange and google only allow one image, so do an update rather than insert
+ boolean shouldUpdate = false;
+
+ final Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI,
+ rawContactId);
+ final Uri rawContactDataUri = Uri.withAppendedPath(rawContactUri,
+ RawContacts.Data.CONTENT_DIRECTORY);
+ insertPhoto(imageValues, rawContactDataUri, true);
+ }
}
}
finish();
}
}
+
+ /**
+ * Inserts a photo on the raw contact.
+ * @param values the photo values
+ * @param assertAccount if true, will check to verify if the account is Google or exchange,
+ * no photos exist (Google and exchange only take one picture)
+ */
+ private void insertPhoto(ContentValues values, Uri rawContactDataUri,
+ boolean assertAccount) {
+
+ ArrayList<ContentProviderOperation> operations =
+ new ArrayList<ContentProviderOperation>();
+
+ if (assertAccount) {
+ // make sure for Google and exchange, no pictures exist
+ operations.add(ContentProviderOperation.newAssertQuery(rawContactDataUri)
+ .withSelection(Photo.MIMETYPE + "=? AND "
+ + RawContacts.ACCOUNT_TYPE + " IN (?,?)",
+ new String[] {Photo.CONTENT_ITEM_TYPE, GoogleSource.ACCOUNT_TYPE,
+ ExchangeSource.ACCOUNT_TYPE})
+ .withExpectedCount(0).build());
+ }
+
+ // insert the photo
+ values.put(Photo.MIMETYPE, Photo.CONTENT_ITEM_TYPE);
+ operations.add(ContentProviderOperation.newInsert(rawContactDataUri)
+ .withValues(values).build());
+
+ try {
+ mContentResolver.applyBatch(ContactsContract.AUTHORITY, operations);
+ } catch (RemoteException e) {
+ throw new IllegalStateException("Problem querying raw_contacts/data", e);
+ } catch (OperationApplicationException e) {
+ // the account doesn't allow multiple photos, so update
+ if (assertAccount) {
+ updatePhoto(values, rawContactDataUri, false);
+ } else {
+ throw new IllegalStateException("Problem inserting photo into raw_contacts/data", e);
+ }
+ }
+ }
+
+ /**
+ * Tries to update the photo on the raw_contact. If no photo exists, and allowInsert == true,
+ * then will try to {@link #updatePhoto(ContentValues, boolean)}
+ */
+ private void updatePhoto(ContentValues values, Uri rawContactDataUri,
+ boolean allowInsert) {
+ ArrayList<ContentProviderOperation> operations =
+ new ArrayList<ContentProviderOperation>();
+
+ values.remove(Photo.MIMETYPE);
+
+ // check that a photo exists
+ operations.add(ContentProviderOperation.newAssertQuery(rawContactDataUri)
+ .withSelection(Photo.MIMETYPE + "=?", new String[] {
+ Photo.CONTENT_ITEM_TYPE
+ }).withExpectedCount(1).build());
+
+ // update that photo
+ operations.add(ContentProviderOperation.newUpdate(rawContactDataUri).withSelection(Photo.MIMETYPE + "=?", new String[] {
+ Photo.CONTENT_ITEM_TYPE}).withValues(values).build());
+
+ try {
+ mContentResolver.applyBatch(ContactsContract.AUTHORITY, operations);
+ } catch (RemoteException e) {
+ throw new IllegalStateException("Problem querying raw_contacts/data", e);
+ } catch (OperationApplicationException e) {
+ if (allowInsert) {
+ // they deleted the photo between insert and update, so insert one
+ insertPhoto(values, rawContactDataUri, false);
+ } else {
+ throw new IllegalStateException("Problem inserting photo raw_contacts/data", e);
+ }
+ }
+ }
}
diff --git a/src/com/android/contacts/ButtonGridLayout.java b/src/com/android/contacts/ButtonGridLayout.java
index e3431b1..b8936e3 100644
--- a/src/com/android/contacts/ButtonGridLayout.java
+++ b/src/com/android/contacts/ButtonGridLayout.java
@@ -18,14 +18,41 @@
import android.content.Context;
import android.util.AttributeSet;
+import android.view.View.MeasureSpec;
import android.view.View;
import android.view.ViewGroup;
-import android.view.View.MeasureSpec;
+/**
+ * Create a 4x3 grid of dial buttons.
+ *
+ * It was easier and more efficient to do it this way than use
+ * standard layouts. It's perfectly fine (and actually encouraged) to
+ * use custom layouts rather than piling up standard layouts.
+ *
+ * The horizontal and vertical spacings between buttons are controlled
+ * by the amount of padding (attributes on the ButtonGridLayout element):
+ * - horizontal = left + right padding and
+ * - vertical = top + bottom padding.
+ *
+ * This class assumes that all the buttons have the same size.
+ *
+ * Invocation: onMeasure is called first by the framework to know our
+ * size. Then onLayout is invoked to layout the buttons.
+ */
+// TODO: Blindly layout the buttons w/o checking if we overrun the
+// bottom-right corner.
public class ButtonGridLayout extends ViewGroup {
+ private final int COLUMNS = 3;
+ private final int ROWS = 4;
- private final int mColumns = 3;
-
+ // Width and height of a button
+ private int mButtonWidth;
+ private int mButtonHeight;
+
+ // Width and height of a button + padding.
+ private int mWidthInc;
+ private int mHeightInc;
+
public ButtonGridLayout(Context context) {
super(context);
}
@@ -37,60 +64,45 @@
public ButtonGridLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
-
+
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ int i = 0;
int y = mPaddingTop;
- final int rows = getRows();
- final View child0 = getChildAt(0);
- final int yInc = (getHeight() - mPaddingTop - mPaddingBottom) / rows;
- final int xInc = (getWidth() - mPaddingLeft - mPaddingRight) / mColumns;
- final int childWidth = child0.getMeasuredWidth();
- final int childHeight = child0.getMeasuredHeight();
- final int xOffset = (xInc - childWidth) / 2;
- final int yOffset = (yInc - childHeight) / 2;
-
- for (int row = 0; row < rows; row++) {
+ for (int row = 0; row < ROWS; row++) {
int x = mPaddingLeft;
- for (int col = 0; col < mColumns; col++) {
- int cell = row * mColumns + col;
- if (cell >= getChildCount()) {
- break;
- }
- View child = getChildAt(cell);
- child.layout(x + xOffset, y + yOffset,
- x + xOffset + childWidth,
- y + yOffset + childHeight);
- x += xInc;
- }
- y += yInc;
- }
- }
+ for (int col = 0; col < COLUMNS; col++) {
+ View child = getChildAt(i);
- private int getRows() {
- return (getChildCount() + mColumns - 1) / mColumns;
- }
-
+ child.layout(x, y, x + mButtonWidth, y + mButtonHeight);
+
+ x += mWidthInc;
+ i++;
+ }
+ y += mHeightInc;
+ }
+ }
+
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- int width = mPaddingLeft + mPaddingRight;
- int height = mPaddingTop + mPaddingBottom;
-
// Measure the first child and get it's size
View child = getChildAt(0);
child.measure(MeasureSpec.UNSPECIFIED , MeasureSpec.UNSPECIFIED);
- int childWidth = child.getMeasuredWidth();
- int childHeight = child.getMeasuredHeight();
+
// Make sure the other children are measured as well, to initialize
for (int i = 1; i < getChildCount(); i++) {
- getChildAt(0).measure(MeasureSpec.UNSPECIFIED , MeasureSpec.UNSPECIFIED);
+ getChildAt(i).measure(MeasureSpec.UNSPECIFIED , MeasureSpec.UNSPECIFIED);
}
- // All cells are going to be the size of the first child
- width += mColumns * childWidth;
- height += getRows() * childHeight;
-
- width = resolveSize(width, widthMeasureSpec);
- height = resolveSize(height, heightMeasureSpec);
+
+ // Store these to be reused in onLayout.
+ mButtonWidth = child.getMeasuredWidth();
+ mButtonHeight = child.getMeasuredHeight();
+ mWidthInc = mButtonWidth + mPaddingLeft + mPaddingRight;
+ mHeightInc = mButtonHeight + mPaddingTop + mPaddingBottom;
+
+ final int width = resolveSize(COLUMNS * mWidthInc, widthMeasureSpec);
+ final int height = resolveSize(ROWS * mHeightInc, heightMeasureSpec);
+
setMeasuredDimension(width, height);
}
diff --git a/src/com/android/contacts/CallDetailActivity.java b/src/com/android/contacts/CallDetailActivity.java
index d7d888c..b2baa2c 100644
--- a/src/com/android/contacts/CallDetailActivity.java
+++ b/src/com/android/contacts/CallDetailActivity.java
@@ -28,10 +28,10 @@
import android.net.Uri;
import android.os.Bundle;
import android.provider.CallLog;
-import android.provider.Contacts;
import android.provider.CallLog.Calls;
-import android.provider.Contacts.People;
-import android.provider.Contacts.Phones;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.PhoneLookup;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.Contacts.Intents.Insert;
import android.telephony.PhoneNumberUtils;
import android.telephony.TelephonyManager;
@@ -63,28 +63,28 @@
private TextView mCallDuration;
private String mNumber = null;
-
+
/* package */ LayoutInflater mInflater;
/* package */ Resources mResources;
-
+
static final String[] CALL_LOG_PROJECTION = new String[] {
CallLog.Calls.DATE,
CallLog.Calls.DURATION,
CallLog.Calls.NUMBER,
CallLog.Calls.TYPE,
};
-
+
static final int DATE_COLUMN_INDEX = 0;
static final int DURATION_COLUMN_INDEX = 1;
static final int NUMBER_COLUMN_INDEX = 2;
static final int CALL_TYPE_COLUMN_INDEX = 3;
-
+
static final String[] PHONES_PROJECTION = new String[] {
- Phones.PERSON_ID,
- Phones.DISPLAY_NAME,
- Phones.TYPE,
- Phones.LABEL,
- Phones.NUMBER,
+ PhoneLookup._ID,
+ PhoneLookup.DISPLAY_NAME,
+ PhoneLookup.TYPE,
+ PhoneLookup.LABEL,
+ PhoneLookup.NUMBER,
};
static final int COLUMN_INDEX_ID = 0;
static final int COLUMN_INDEX_NAME = 1;
@@ -100,15 +100,15 @@
mInflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
mResources = getResources();
-
+
mCallType = (TextView) findViewById(R.id.type);
mCallTypeIcon = (ImageView) findViewById(R.id.icon);
mCallTime = (TextView) findViewById(R.id.time);
mCallDuration = (TextView) findViewById(R.id.duration);
-
+
getListView().setOnItemClickListener(this);
}
-
+
@Override
public void onResume() {
super.onResume();
@@ -130,13 +130,13 @@
}
}
}
-
+
return super.onKeyDown(keyCode, event);
}
-
+
/**
* Update user interface with details of given call.
- *
+ *
* @param callUri Uri into {@link CallLog.Calls}
*/
private void updateData(Uri callUri) {
@@ -149,13 +149,13 @@
long date = callCursor.getLong(DATE_COLUMN_INDEX);
long duration = callCursor.getLong(DURATION_COLUMN_INDEX);
int callType = callCursor.getInt(CALL_TYPE_COLUMN_INDEX);
-
+
// Pull out string in format [relative], [date]
CharSequence dateClause = DateUtils.formatDateRange(this, date, date,
DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_DATE |
DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_SHOW_YEAR);
mCallTime.setText(dateClause);
-
+
// Set the duration
if (callType == Calls.MISSED_TYPE) {
mCallDuration.setVisibility(View.GONE);
@@ -163,7 +163,7 @@
mCallDuration.setVisibility(View.VISIBLE);
mCallDuration.setText(formatDuration(duration));
}
-
+
// Set the call type icon and caption
String callText = null;
switch (callType) {
@@ -172,43 +172,44 @@
mCallType.setText(R.string.type_incoming);
callText = getString(R.string.callBack);
break;
-
+
case Calls.OUTGOING_TYPE:
mCallTypeIcon.setImageResource(R.drawable.ic_call_log_header_outgoing_call);
mCallType.setText(R.string.type_outgoing);
callText = getString(R.string.callAgain);
break;
-
+
case Calls.MISSED_TYPE:
mCallTypeIcon.setImageResource(R.drawable.ic_call_log_header_missed_call);
mCallType.setText(R.string.type_missed);
callText = getString(R.string.returnCall);
break;
}
-
+
if (mNumber.equals(CallerInfo.UNKNOWN_NUMBER) ||
mNumber.equals(CallerInfo.PRIVATE_NUMBER)) {
// List is empty, let the empty view show instead.
TextView emptyText = (TextView) findViewById(R.id.emptyText);
if (emptyText != null) {
- emptyText.setText(mNumber.equals(CallerInfo.PRIVATE_NUMBER)
+ emptyText.setText(mNumber.equals(CallerInfo.PRIVATE_NUMBER)
? R.string.private_num : R.string.unknown);
}
} else {
// Perform a reverse-phonebook lookup to find the PERSON_ID
String callLabel = null;
Uri personUri = null;
- Uri phoneUri = Uri.withAppendedPath(Phones.CONTENT_FILTER_URL, Uri.encode(mNumber));
+ Uri phoneUri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI,
+ Uri.encode(mNumber));
Cursor phonesCursor = resolver.query(phoneUri, PHONES_PROJECTION, null, null, null);
try {
if (phonesCursor != null && phonesCursor.moveToFirst()) {
long personId = phonesCursor.getLong(COLUMN_INDEX_ID);
personUri = ContentUris.withAppendedId(
- Contacts.People.CONTENT_URI, personId);
+ Contacts.CONTENT_URI, personId);
callText = getString(R.string.recentCalls_callNumber,
phonesCursor.getString(COLUMN_INDEX_NAME));
mNumber = phonesCursor.getString(COLUMN_INDEX_NUMBER);
- callLabel = Phones.getDisplayLabel(this,
+ callLabel = Phone.getDisplayLabel(this,
phonesCursor.getInt(COLUMN_INDEX_TYPE),
phonesCursor.getString(COLUMN_INDEX_LABEL)).toString();
} else {
@@ -217,10 +218,10 @@
} finally {
if (phonesCursor != null) phonesCursor.close();
}
-
+
// Build list of various available actions
List<ViewEntry> actions = new ArrayList<ViewEntry>();
-
+
Intent callIntent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
Uri.fromParts("tel", mNumber, null));
ViewEntry entry = new ViewEntry(android.R.drawable.sym_action_call, callText,
@@ -228,12 +229,12 @@
entry.number = mNumber;
entry.label = callLabel;
actions.add(entry);
-
+
Intent smsIntent = new Intent(Intent.ACTION_SENDTO,
Uri.fromParts("sms", mNumber, null));
actions.add(new ViewEntry(R.drawable.sym_action_sms,
getString(R.string.menu_sendTextMessage), smsIntent));
-
+
// Let user view contact details if they exist, otherwise add option
// to create new contact from this number.
if (personUri != null) {
@@ -242,12 +243,12 @@
getString(R.string.menu_viewContact), viewIntent));
} else {
Intent createIntent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
- createIntent.setType(People.CONTENT_ITEM_TYPE);
+ createIntent.setType(Contacts.CONTENT_ITEM_TYPE);
createIntent.putExtra(Insert.PHONE, mNumber);
actions.add(new ViewEntry(R.drawable.sym_action_add,
getString(R.string.recentCalls_addToContact), createIntent));
}
-
+
ViewAdapter adapter = new ViewAdapter(this, actions);
setListAdapter(adapter);
}
@@ -284,7 +285,7 @@
public Intent intent = null;
public String label = null;
public String number = null;
-
+
public ViewEntry(int icon, String text, Intent intent) {
this.icon = icon;
this.text = text;
@@ -293,16 +294,16 @@
}
static final class ViewAdapter extends BaseAdapter {
-
+
private final List<ViewEntry> mActions;
-
+
private final LayoutInflater mInflater;
-
+
public ViewAdapter(Context context, List<ViewEntry> actions) {
mActions = actions;
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
-
+
public int getCount() {
return mActions.size();
}
@@ -314,7 +315,7 @@
public long getItemId(int position) {
return position;
}
-
+
public View getView(int position, View convertView, ViewGroup parent) {
// Make sure we have a valid convertView to start with
if (convertView == null) {
@@ -324,7 +325,7 @@
// Fill action with icon and text.
ViewEntry entry = mActions.get(position);
convertView.setTag(entry);
-
+
ImageView icon = (ImageView) convertView.findViewById(R.id.icon);
TextView text = (TextView) convertView.findViewById(android.R.id.text1);
@@ -338,7 +339,7 @@
line2.setVisibility(View.GONE);
} else {
line2.setVisibility(View.VISIBLE);
-
+
TextView label = (TextView) convertView.findViewById(R.id.label);
if (labelEmpty) {
label.setVisibility(View.GONE);
@@ -350,11 +351,11 @@
TextView number = (TextView) convertView.findViewById(R.id.number);
number.setText(entry.number);
}
-
+
return convertView;
}
}
-
+
public void onItemClick(AdapterView parent, View view, int position, long id) {
// Handle passing action off to correct handler.
if (view.getTag() instanceof ViewEntry) {
@@ -363,5 +364,5 @@
startActivity(entry.intent);
}
}
- }
+ }
}
diff --git a/src/com/android/contacts/Collapser.java b/src/com/android/contacts/Collapser.java
new file mode 100644
index 0000000..3872dfd
--- /dev/null
+++ b/src/com/android/contacts/Collapser.java
@@ -0,0 +1,80 @@
+/*
+ * 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 java.util.HashMap;
+import java.util.Iterator;
+import java.util.ArrayList;
+
+/**
+ * Class used for collapsing data items into groups of similar items. The data items that should be
+ * collapsible should implement the Collapsible interface. The class also contains a utility
+ * function that takes an ArrayList of items and returns a list of the same items collapsed into
+ * groups.
+ */
+public final class Collapser {
+
+ /*
+ * This utility class cannot be instantiated.
+ */
+ private Collapser() {}
+
+ /*
+ * Interface implemented by data types that can be collapsed into groups of similar data. This
+ * can be used for example to collapse similar contact data items into a single item.
+ */
+ public interface Collapsible<T> {
+ public boolean collapseWith(T t);
+ public boolean shouldCollapseWith(T t);
+ }
+
+ /**
+ * 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
+ * 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.
+ */
+ public static <T extends Collapsible<T>> void collapseList(ArrayList<T> list) {
+
+ int listSize = list.size();
+
+ for (int i = 0; i < listSize; i++) {
+ T iItem = list.get(i);
+ if (iItem != null) {
+ for (int j = i + 1; j < listSize; j++) {
+ T jItem = list.get(j);
+ if (jItem != null) {
+ if (iItem.shouldCollapseWith(jItem)) {
+ iItem.collapseWith(jItem);
+ list.set(j, null);
+ }
+ }
+ }
+ }
+ }
+
+ // Remove the null items
+ Iterator<T> itr = list.iterator();
+ while (itr.hasNext()) {
+ if (itr.next() == null) {
+ itr.remove();
+ }
+ }
+
+ }
+}
diff --git a/src/com/android/contacts/ContactEntryAdapter.java b/src/com/android/contacts/ContactEntryAdapter.java
index c5b7ccf..9f11165 100644
--- a/src/com/android/contacts/ContactEntryAdapter.java
+++ b/src/com/android/contacts/ContactEntryAdapter.java
@@ -19,8 +19,8 @@
import android.content.Context;
import android.net.Uri;
import android.os.Parcel;
-import android.provider.Contacts.Organizations;
-import android.provider.Contacts.People;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Data;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -31,83 +31,6 @@
public abstract class ContactEntryAdapter<E extends ContactEntryAdapter.Entry>
extends BaseAdapter {
- public static final String[] CONTACT_PROJECTION = new String[] {
- People._ID, // 0
- People.NAME, // 1
- People.NOTES, // 2
- People.PRIMARY_PHONE_ID, // 3
- People.PRESENCE_STATUS, // 4
- People.STARRED, // 5
- People.CUSTOM_RINGTONE, // 6
- People.SEND_TO_VOICEMAIL, // 7
- People.PHONETIC_NAME, // 8
- };
- public static final int CONTACT_ID_COLUMN = 0;
- public static final int CONTACT_NAME_COLUMN = 1;
- public static final int CONTACT_NOTES_COLUMN = 2;
- public static final int CONTACT_PREFERRED_PHONE_COLUMN = 3;
- public static final int CONTACT_SERVER_STATUS_COLUMN = 4;
- public static final int CONTACT_STARRED_COLUMN = 5;
- public static final int CONTACT_CUSTOM_RINGTONE_COLUMN = 6;
- public static final int CONTACT_SEND_TO_VOICEMAIL_COLUMN = 7;
- public static final int CONTACT_PHONETIC_NAME_COLUMN = 8;
-
- public static final String[] PHONES_PROJECTION = new String[] {
- People.Phones._ID, // 0
- People.Phones.NUMBER, // 1
- People.Phones.TYPE, // 2
- People.Phones.LABEL, // 3
- People.Phones.ISPRIMARY, // 4
- };
- public static final int PHONES_ID_COLUMN = 0;
- public static final int PHONES_NUMBER_COLUMN = 1;
- public static final int PHONES_TYPE_COLUMN = 2;
- public static final int PHONES_LABEL_COLUMN = 3;
- public static final int PHONES_ISPRIMARY_COLUMN = 4;
-
- public static final String[] METHODS_PROJECTION = new String[] {
- People.ContactMethods._ID, // 0
- People.ContactMethods.KIND, // 1
- People.ContactMethods.DATA, // 2
- People.ContactMethods.TYPE, // 3
- People.ContactMethods.LABEL, // 4
- People.ContactMethods.ISPRIMARY, // 5
- People.ContactMethods.AUX_DATA, // 6
- };
- public static final String[] METHODS_WITH_PRESENCE_PROJECTION = new String[] {
- People.ContactMethods._ID, // 0
- People.ContactMethods.KIND, // 1
- People.ContactMethods.DATA, // 2
- People.ContactMethods.TYPE, // 3
- People.ContactMethods.LABEL, // 4
- People.ContactMethods.ISPRIMARY, // 5
- People.ContactMethods.AUX_DATA, // 6
- People.PRESENCE_STATUS, // 7
- };
- public static final int METHODS_ID_COLUMN = 0;
- public static final int METHODS_KIND_COLUMN = 1;
- public static final int METHODS_DATA_COLUMN = 2;
- public static final int METHODS_TYPE_COLUMN = 3;
- public static final int METHODS_LABEL_COLUMN = 4;
- public static final int METHODS_ISPRIMARY_COLUMN = 5;
- public static final int METHODS_AUX_DATA_COLUMN = 6;
- public static final int METHODS_STATUS_COLUMN = 7;
-
- public static final String[] ORGANIZATIONS_PROJECTION = new String[] {
- Organizations._ID, // 0
- Organizations.TYPE, // 1
- Organizations.LABEL, // 2
- Organizations.COMPANY, // 3
- Organizations.TITLE, // 4
- Organizations.ISPRIMARY, // 5
- };
- public static final int ORGANIZATIONS_ID_COLUMN = 0;
- public static final int ORGANIZATIONS_TYPE_COLUMN = 1;
- public static final int ORGANIZATIONS_LABEL_COLUMN = 2;
- public static final int ORGANIZATIONS_COMPANY_COLUMN = 3;
- public static final int ORGANIZATIONS_TITLE_COLUMN = 4;
- public static final int ORGANIZATIONS_ISPRIMARY_COLUMN = 5;
-
protected ArrayList<ArrayList<E>> mSections;
protected LayoutInflater mInflater;
protected Context mContext;
@@ -117,42 +40,39 @@
* Base class for adapter entries.
*/
public static class Entry {
- /** Details from the person table */
- public static final int KIND_CONTACT = -1;
- /** Synthesized phone entry that will send an SMS instead of call the number */
- public static final int KIND_SMS = -2;
- /** A section separator */
- public static final int KIND_SEPARATOR = -3;
-
+ 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 int kind;
-
+ 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.writeInt(kind);
+ p.writeString(mimetype);
}
-
+
/**
* Helper for making subclasses parcelable.
*/
protected void readFromParcel(Parcel p) {
+ type = p.readInt();
label = p.readString();
data = p.readString();
uri = p.readParcelable(null);
id = p.readLong();
maxLines = p.readInt();
- kind = p.readInt();
+ mimetype = p.readString();
}
}
@@ -165,7 +85,7 @@
/**
* Resets the section data.
- *
+ *
* @param sections the section data
*/
public final void setSections(ArrayList<ArrayList<E>> sections, boolean separators) {
@@ -176,7 +96,7 @@
/**
* 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
@@ -252,7 +172,7 @@
/**
* 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
@@ -277,7 +197,7 @@
/**
* Get the count of entries in all sections
- *
+ *
* @param sections the list of sections
* @return the count of entries in all sections
*/
@@ -325,7 +245,7 @@
/**
* Create a new view for an entry.
- *
+ *
* @parent the parent ViewGroup
* @return the newly created view
*/
@@ -333,7 +253,7 @@
/**
* Binds the data from an entry to a view.
- *
+ *
* @param view the view to display the entry in
* @param entry the data to bind
*/
diff --git a/src/com/android/contacts/ContactOptionsActivity.java b/src/com/android/contacts/ContactOptionsActivity.java
new file mode 100644
index 0000000..f93ddf8
--- /dev/null
+++ b/src/com/android/contacts/ContactOptionsActivity.java
@@ -0,0 +1,202 @@
+/*
+ * 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.Activity;
+import android.content.ContentValues;
+import android.content.Intent;
+import android.database.Cursor;
+import android.media.Ringtone;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.ContactsContract.Contacts;
+import android.util.Log;
+import android.view.View;
+import android.widget.CheckBox;
+import android.widget.TextView;
+
+/**
+ * An activity for selecting options for a given contact: custom ringtone and send-to-voicemail.
+ */
+public class ContactOptionsActivity extends Activity implements View.OnClickListener {
+
+ private static final String TAG = "ContactOptionsActivity";
+
+ private static final String[] AGGREGATES_PROJECTION = new String[] {
+ Contacts.CUSTOM_RINGTONE, Contacts.SEND_TO_VOICEMAIL
+ };
+
+ private static final int COL_CUSTOM_RINGTONE = 0;
+ private static final int COL_SEND_TO_VOICEMAIL = 1;
+
+ /** The launch code when picking a ringtone */
+ private static final int RINGTONE_PICKED = 3023;
+
+ private String mCustomRingtone;
+ private boolean mSendToVoicemail;
+ private TextView mRingtoneTitle;
+ private CheckBox mSendToVoicemailCheckbox;
+
+ private Uri mLookupUri;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mLookupUri = getIntent().getData();
+
+ setContentView(R.layout.contact_options);
+
+ View ringtoneLayout = findViewById(R.id.ringtone);
+ ringtoneLayout.setOnClickListener(this);
+ TextView label = (TextView)findViewById(R.id.label);
+ label.setText(getString(R.string.label_ringtone));
+
+ mRingtoneTitle = (TextView)ringtoneLayout.findViewById(R.id.data);
+
+ View sendToVoicemailLayout = findViewById(R.id.voicemail);
+ sendToVoicemailLayout.setOnClickListener(this);
+ label = (TextView)sendToVoicemailLayout.findViewById(R.id.label);
+ label.setText(getString(R.string.actionIncomingCall));
+
+ mSendToVoicemailCheckbox = (CheckBox)sendToVoicemailLayout.findViewById(R.id.checkbox);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ if (!loadData()) {
+ finish();
+ }
+
+ updateView();
+ }
+
+ private void updateView() {
+ if (mCustomRingtone == null) {
+ mRingtoneTitle.setText(getString(R.string.default_ringtone));
+ } else {
+ Uri ringtoneUri = Uri.parse(mCustomRingtone);
+ Ringtone ringtone = RingtoneManager.getRingtone(this, ringtoneUri);
+ if (ringtone == null) {
+ Log.w(TAG, "ringtone's URI doesn't resolve to a Ringtone");
+ return;
+ }
+ mRingtoneTitle.setText(ringtone.getTitle(this));
+ }
+
+ mSendToVoicemailCheckbox.setChecked(mSendToVoicemail);
+ }
+
+ public void onClick(View v) {
+ switch (v.getId()) {
+ case R.id.ringtone: {
+ doPickRingtone();
+ break;
+ }
+ case R.id.voicemail: {
+ doToggleSendToVoicemail();
+ break;
+ }
+ }
+ }
+
+ private void doPickRingtone() {
+
+ Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER);
+ // Allow user to pick 'Default'
+ intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true);
+ // Show only ringtones
+ intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_RINGTONE);
+ // Don't show 'Silent'
+ intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, false);
+
+ Uri ringtoneUri;
+ if (mCustomRingtone != null) {
+ ringtoneUri = Uri.parse(mCustomRingtone);
+ } else {
+ // Otherwise pick default ringtone Uri so that something is selected.
+ ringtoneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE);
+ }
+
+ // Put checkmark next to the current ringtone for this contact
+ intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, ringtoneUri);
+
+ // Launch!
+ startActivityForResult(intent, RINGTONE_PICKED);
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (resultCode != RESULT_OK) {
+ return;
+ }
+
+ switch (requestCode) {
+ case RINGTONE_PICKED: {
+ Uri pickedUri = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI);
+ handleRingtonePicked(pickedUri);
+ break;
+ }
+ }
+ }
+
+ private void handleRingtonePicked(Uri pickedUri) {
+ if (pickedUri == null || RingtoneManager.isDefault(pickedUri)) {
+ mCustomRingtone = null;
+ } else {
+ mCustomRingtone = pickedUri.toString();
+ }
+ saveData();
+ updateView();
+ }
+
+ private void doToggleSendToVoicemail() {
+ mSendToVoicemailCheckbox.toggle();
+ mSendToVoicemail = mSendToVoicemailCheckbox.isChecked();
+ saveData();
+ updateView();
+ }
+
+ private boolean loadData() {
+ Cursor c =
+ getContentResolver().query(mLookupUri, AGGREGATES_PROJECTION, null, null, null);
+ try {
+ if (!c.moveToFirst()) {
+ return false;
+ }
+
+ mCustomRingtone = c.getString(COL_CUSTOM_RINGTONE);
+ mSendToVoicemail = c.getInt(COL_SEND_TO_VOICEMAIL) != 0;
+
+ } finally {
+ c.close();
+ }
+ return true;
+ }
+
+ private void saveData() {
+ ContentValues values = new ContentValues(2);
+ values.put(Contacts.CUSTOM_RINGTONE, mCustomRingtone);
+ values.put(Contacts.SEND_TO_VOICEMAIL, mSendToVoicemail);
+ getContentResolver().update(mLookupUri, values, null, null);
+ }
+}
+
+
diff --git a/src/com/android/contacts/ContactsGroupSyncSelector.java b/src/com/android/contacts/ContactsGroupSyncSelector.java
deleted file mode 100644
index e1fbdf1..0000000
--- a/src/com/android/contacts/ContactsGroupSyncSelector.java
+++ /dev/null
@@ -1,265 +0,0 @@
-/*
- * 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;
-
-import com.google.android.googlelogin.GoogleLoginServiceConstants;
-import com.google.android.googlelogin.GoogleLoginServiceHelper;
-
-import android.app.ListActivity;
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Intent;
-import android.database.Cursor;
-import android.os.Bundle;
-import android.provider.Contacts;
-import android.provider.Gmail;
-import android.provider.Contacts.Groups;
-import android.provider.Contacts.Settings;
-import android.text.TextUtils;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ArrayAdapter;
-import android.widget.ListView;
-
-import java.util.ArrayList;
-
-public final class ContactsGroupSyncSelector extends ListActivity implements View.OnClickListener {
-
- private static final String[] PROJECTION = new String[] {
- Groups._ID, // 0
- Groups.NAME, // 1
- Groups.SHOULD_SYNC, // 2
- Groups.SYSTEM_ID, // 3
- };
- private static final int COLUMN_INDEX_ID = 0;
- private static final int COLUMN_INDEX_NAME = 1;
- private static final int COLUMN_INDEX_SHOULD_SYNC = 2;
- private static final int COLUMN_INDEX_SYSTEM_ID = 3;
-
- private static final int SUBACTIVITY_GET_ACCOUNT = 1;
-
- ArrayList<Boolean> mChecked;
- ArrayList<Long> mGroupIds;
- boolean mSyncAllGroups;
-
- private final class GroupsAdapter extends ArrayAdapter<CharSequence> {
- public GroupsAdapter(ArrayList<CharSequence> items) {
- super(ContactsGroupSyncSelector.this,
- android.R.layout.simple_list_item_checked,
- android.R.id.text1, items);
- }
-
- @Override
- public boolean areAllItemsEnabled() {
- return mSyncAllGroups;
- }
-
- @Override
- public boolean isEnabled(int pos) {
- if (mSyncAllGroups && pos != 0) {
- return false;
- } else {
- return true;
- }
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- View v = super.getView(position, convertView, parent);
- if (mSyncAllGroups && position != 0) {
- v.setEnabled(false);
- } else {
- v.setEnabled(true);
- }
- return v;
- }
- }
-
- /**
- * Handles clicks on the list items
- */
- @Override
- protected void onListItemClick(ListView list, View view, int position, long id) {
- boolean isChecked = list.isItemChecked(position);
- mChecked.set(position, isChecked);
- if (position == 0) {
- mSyncAllGroups = isChecked;
- adjustChecks();
- }
- }
-
- /**
- * Handles clicks on the OK and cancel buttons
- */
- public void onClick(View view) {
- switch (view.getId()) {
- case R.id.cancel: {
- finish();
- break;
- }
-
- case R.id.ok: {
- // The list isn't setup yet, so just return without doing anything.
- if (mChecked == null) {
- finish();
- return;
- }
-
- final ContentResolver resolver = getContentResolver();
- if (mSyncAllGroups) {
- // For now we only support a single account and the UI doesn't know what
- // the account name is, so we're using a global setting for SYNC_EVERYTHING.
- // Some day when we add multiple accounts to the UI this should use the per
- // account setting.
- Settings.setSetting(resolver, null, Settings.SYNC_EVERYTHING, "1");
- } else {
- ContentValues values = new ContentValues();
- int count = mChecked.size();
- for (int i = 1; i < count; i++) {
- values.clear();
- values.put(Groups.SHOULD_SYNC, mChecked.get(i));
- resolver.update(
- ContentUris.withAppendedId(Groups.CONTENT_URI, mGroupIds.get(i)),
- values, null, null);
- }
- // For now we only support a single account and the UI doesn't know what
- // the account name is, so we're using a global setting for SYNC_EVERYTHING.
- // Some day when we add multiple accounts to the UI this should use the per
- // account setting.
- Settings.setSetting(resolver, null, Settings.SYNC_EVERYTHING, "0");
- }
- finish();
- break;
- }
- }
- }
-
- @Override
- protected void onCreate(Bundle savedState) {
- super.onCreate(savedState);
-
- // Only look for an account on first run.
- if (savedState == null) {
- // This will request a Gmail account and if none are present, it will
- // invoke SetupWizard to login or create one. The result is returned
- // through onActivityResult().
- Bundle bundle = new Bundle();
- bundle.putCharSequence("optional_message", getText(R.string.contactsSyncPlug));
- GoogleLoginServiceHelper.getCredentials(this, SUBACTIVITY_GET_ACCOUNT,
- bundle, GoogleLoginServiceConstants.PREFER_HOSTED, Gmail.GMAIL_AUTH_SERVICE,
- true);
- }
-
- setContentView(R.layout.sync_settings);
-
- findViewById(R.id.ok).setOnClickListener(this);
- findViewById(R.id.cancel).setOnClickListener(this);
-
- getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
- }
-
- @Override
- protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
- super.onActivityResult(requestCode, resultCode, intent);
- if (requestCode == SUBACTIVITY_GET_ACCOUNT) {
- if (resultCode == RESULT_OK) {
- // There is an account setup, build the group list
- buildItems();
- adjustChecks();
- } else {
- finish();
- }
- }
- }
-
- private void buildItems() {
- final ContentResolver resolver = getContentResolver();
- Cursor cursor = resolver.query(Groups.CONTENT_URI, PROJECTION, null, null, Groups.NAME);
- if (cursor != null) {
- try {
- int count = cursor.getCount() + 1; // add 1 for "sync all"
- ArrayList<CharSequence> items = new ArrayList<CharSequence>(count);
- ArrayList<Boolean> checked = new ArrayList<Boolean>(count);
- ArrayList<Long> groupIds = new ArrayList<Long>(count);
-
- // The first item in the list is always "sync all"
- items.add(getString(R.string.syncAllGroups));
- checked.add(mSyncAllGroups);
- groupIds.add(Long.valueOf(0)); // dummy entry
-
- while (cursor.moveToNext()) {
- String name = cursor.getString(COLUMN_INDEX_NAME);
- String systemId = cursor.isNull(COLUMN_INDEX_SYSTEM_ID) ?
- null : cursor.getString(COLUMN_INDEX_SYSTEM_ID);
- if (systemId == null || !Groups.GROUP_MY_CONTACTS.equals(systemId)) {
- // Localize the "Starred in Android" string which we get from the server
- // side.
- if (Groups.GROUP_ANDROID_STARRED.equals(name)) {
- name = getString(R.string.starredInAndroid);
- }
- items.add(name);
- checked.add(cursor.getInt(COLUMN_INDEX_SHOULD_SYNC) != 0);
- groupIds.add(cursor.getLong(COLUMN_INDEX_ID));
- } else {
- // If My Contacts is around it wants to be the second list entry
- items.add(1, getString(R.string.groupNameMyContacts));
- checked.add(1, cursor.getInt(COLUMN_INDEX_SHOULD_SYNC) != 0);
- groupIds.add(1, cursor.getLong(COLUMN_INDEX_ID));
- }
- }
- mChecked = checked;
- mGroupIds = groupIds;
- mSyncAllGroups = getShouldSyncEverything(resolver);
-
- // Setup the adapter
- setListAdapter(new GroupsAdapter(items));
- } finally {
- cursor.close();
- }
- }
- }
-
- private void adjustChecks() {
- final ListView list = getListView();
- if (mSyncAllGroups) {
- int count = list.getCount();
- for (int i = 0; i < count; i++) {
- list.setItemChecked(i, true);
- }
- } else {
- ArrayList<Boolean> checked = mChecked;
- int count = list.getCount();
- for (int i = 0; i < count; i++) {
- list.setItemChecked(i, checked.get(i));
- }
- }
- }
-
- private static boolean getShouldSyncEverything(ContentResolver cr) {
- // For now we only support a single account and the UI doesn't know what
- // the account name is, so we're using a global setting for SYNC_EVERYTHING.
- // Some day when we add multiple accounts to the UI this should use the per
- // account setting.
- String value = Contacts.Settings.getSetting(cr, null, Contacts.Settings.SYNC_EVERYTHING);
- if (value == null) {
- // If nothing is set yet we default to syncing everything
- return true;
- }
- return !TextUtils.isEmpty(value) && !"0".equals(value);
- }
-}
diff --git a/src/com/android/contacts/ContactsListActivity.java b/src/com/android/contacts/ContactsListActivity.java
index 02c70d2..c549c9d 100644
--- a/src/com/android/contacts/ContactsListActivity.java
+++ b/src/com/android/contacts/ContactsListActivity.java
@@ -16,8 +16,17 @@
package com.android.contacts;
+import com.android.contacts.model.ContactsSource;
+import com.android.contacts.model.Sources;
+import com.android.contacts.ui.DisplayGroupsActivity;
+import com.android.contacts.ui.DisplayGroupsActivity.Prefs;
+import com.android.contacts.util.AccountSelectionUtil;
+import com.android.contacts.util.Constants;
+
+import android.accounts.Account;
import android.app.Activity;
import android.app.AlertDialog;
+import android.app.Dialog;
import android.app.ListActivity;
import android.app.SearchManager;
import android.content.AsyncQueryHandler;
@@ -26,13 +35,13 @@
import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
-import android.content.IContentProvider;
-import android.content.ISyncAdapter;
import android.content.Intent;
import android.content.SharedPreferences;
+import android.content.UriMatcher;
import android.content.res.Resources;
import android.database.CharArrayBuffer;
import android.database.Cursor;
+import android.database.MatrixCursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
@@ -42,59 +51,91 @@
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.Message;
import android.os.Parcelable;
-import android.os.RemoteException;
import android.preference.PreferenceManager;
-import android.provider.Contacts;
+import android.provider.ContactsContract;
+import android.provider.Settings;
import android.provider.Contacts.ContactMethods;
-import android.provider.Contacts.Groups;
-import android.provider.Contacts.Intents;
import android.provider.Contacts.People;
+import android.provider.Contacts.PeopleColumns;
import android.provider.Contacts.Phones;
-import android.provider.Contacts.Presence;
-import android.provider.Contacts.Intents.Insert;
-import android.provider.Contacts.Intents.UI;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.Intents;
+import android.provider.ContactsContract.Presence;
+import android.provider.ContactsContract.RawContacts;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+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.TextUtils;
import android.util.Log;
-import android.util.SparseArray;
import android.view.ContextMenu;
+import android.view.ContextThemeWrapper;
import android.view.Gravity;
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.inputmethod.InputMethodManager;
+import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.AlphabetIndexer;
+import android.widget.ArrayAdapter;
import android.widget.Filter;
import android.widget.ImageView;
import android.widget.ListView;
+import android.widget.QuickContactBadge;
import android.widget.ResourceCursorAdapter;
import android.widget.SectionIndexer;
import android.widget.TextView;
+import android.widget.AbsListView.OnScrollListener;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
import java.util.Locale;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/*TODO(emillar) I commented most of the code that deals with modes and filtering. It should be
+ * brought back in as we add back that functionality.
+ */
+
/**
* Displays a list of contacts. Usually is embedded into the ContactsActivity.
*/
-public final class ContactsListActivity extends ListActivity
- implements View.OnCreateContextMenuListener, DialogInterface.OnClickListener {
+@SuppressWarnings("deprecation")
+public class ContactsListActivity extends ListActivity implements
+ View.OnCreateContextMenuListener, View.OnClickListener {
+
+ public static class JoinContactActivity extends ContactsListActivity {
+
+ }
+
private static final String TAG = "ContactsListActivity";
- private static final boolean ENABLE_ACTION_ICON_OVERLAYS = false;
+ private static final boolean ENABLE_ACTION_ICON_OVERLAYS = true;
private static final String LIST_STATE_KEY = "liststate";
private static final String FOCUS_KEY = "focused";
-
+
static final int MENU_ITEM_VIEW_CONTACT = 1;
static final int MENU_ITEM_CALL = 2;
static final int MENU_ITEM_EDIT_BEFORE_CALL = 3;
@@ -104,15 +145,39 @@
static final int MENU_ITEM_DELETE = 7;
static final int MENU_ITEM_TOGGLE_STAR = 8;
- public static final int MENU_SEARCH = 1;
- public static final int MENU_DIALER = 9;
- public static final int MENU_NEW_CONTACT = 10;
- public static final int MENU_DISPLAY_GROUP = 11;
- public static final int MENU_IMPORT_CONTACTS = 12;
- public static final int MENU_EXPORT_CONTACTS = 13;
-
private static final int SUBACTIVITY_NEW_CONTACT = 1;
-
+ private static final int SUBACTIVITY_VIEW_CONTACT = 2;
+ private static final int SUBACTIVITY_DISPLAY_GROUP = 3;
+
+ /**
+ * 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";
+
/** Mask for picker mode */
static final int MODE_MASK_PICKER = 0x80000000;
/** Mask for no presence mode */
@@ -123,177 +188,175 @@
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;
- /** Show members of the "Contacts" group */
- static final int MODE_GROUP = 5;
- /** Show all contacts sorted alphabetically */
- static final int MODE_ALL_CONTACTS = 10;
- /** Show all contacts with phone numbers, sorted alphabetically */
- static final int MODE_WITH_PHONES = 15;
+ /** 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;
+ static final int MODE_STARRED = 20 | MODE_MASK_SHOW_PHOTOS;
/** Show frequently contacted contacts */
- static final int MODE_FREQUENT = 30;
+ 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;
+ 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;
+ 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;
+ 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_SHOW_PHOTOS
+ | 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_SHOW_PHOTOS | 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;
/** 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_NO_FILTER;
+ static final int MODE_QUERY = 60 | 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_NO_FILTER | MODE_MASK_PICKER;
- static final int DEFAULT_MODE = MODE_ALL_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;
- /**
- * The type of data to display in the main contacts list.
- */
- static final String PREF_DISPLAY_TYPE = "display_system_group";
+ /** Maximum number of suggestions shown for joining aggregates */
+ static final int MAX_SUGGESTIONS = 4;
- /** Unknown display type. */
- static final int DISPLAY_TYPE_UNKNOWN = -1;
- /** Display all contacts */
- static final int DISPLAY_TYPE_ALL = 0;
- /** Display all contacts that have phone numbers */
- static final int DISPLAY_TYPE_ALL_WITH_PHONES = 1;
- /** Display a system group */
- static final int DISPLAY_TYPE_SYSTEM_GROUP = 2;
- /** Display a user group */
- static final int DISPLAY_TYPE_USER_GROUP = 3;
+ static final String NAME_COLUMN = Contacts.DISPLAY_NAME;
+ //static final String SORT_STRING = People.SORT_STRING;
- /**
- * Info about what to display. If {@link #PREF_DISPLAY_TYPE}
- * is {@link #DISPLAY_TYPE_SYSTEM_GROUP} then this will be the system id.
- * If {@link #PREF_DISPLAY_TYPE} is {@link #DISPLAY_TYPE_USER_GROUP} then this will
- * be the group name.
- */
- static final String PREF_DISPLAY_INFO = "display_group";
-
-
- static final String NAME_COLUMN = People.DISPLAY_NAME;
- static final String SORT_STRING = People.SORT_STRING;
-
- static final String[] CONTACTS_PROJECTION = new String[] {
- People._ID, // 0
- NAME_COLUMN, // 1
- People.NUMBER, // 2
- People.TYPE, // 3
- People.LABEL, // 4
- People.STARRED, // 5
- People.PRIMARY_PHONE_ID, // 6
- People.PRIMARY_EMAIL_ID, // 7
- People.PRESENCE_STATUS, // 8
- SORT_STRING, // 9
+ static final String[] CONTACTS_SUMMARY_PROJECTION = new String[] {
+ Contacts._ID, // 0
+ Contacts.DISPLAY_NAME, // 1
+ Contacts.STARRED, //2
+ Contacts.TIMES_CONTACTED, //3
+ Contacts.CONTACT_PRESENCE, //4
+ Contacts.PHOTO_ID, //5
+ Contacts.LOOKUP_KEY, //6
+ Contacts.HAS_PHONE_NUMBER, //7
};
-
- static final String[] SIMPLE_CONTACTS_PROJECTION = new String[] {
- People._ID, // 0
- NAME_COLUMN, // 1
+ static final String[] CONTACTS_SUMMARY_PROJECTION_FROM_EMAIL = new String[] {
+ Contacts._ID, // 0
+ Contacts.DISPLAY_NAME, // 1
+ Contacts.STARRED, //2
+ Contacts.TIMES_CONTACTED, //3
+ Contacts.CONTACT_PRESENCE, //4
+ Contacts.PHOTO_ID, //5
+ Contacts.LOOKUP_KEY, //6
+ // email lookup doesn't included HAS_PHONE_NUMBER OR LOOKUP_KEY in projection
};
-
- static final String[] STREQUENT_PROJECTION = new String[] {
+ static final String[] LEGACY_PEOPLE_PROJECTION = new String[] {
People._ID, // 0
- NAME_COLUMN, // 1
- People.NUMBER, // 2
- People.TYPE, // 3
- People.LABEL, // 4
- People.STARRED, // 5
- People.PRIMARY_PHONE_ID, // 6
- People.PRIMARY_EMAIL_ID, // 7
- People.PRESENCE_STATUS, // 8
- "photo_data", // 9
- People.TIMES_CONTACTED, // 10 (not displayed, but required for the order by to work)
+ People.DISPLAY_NAME, // 1
+ People.STARRED, //2
+ PeopleColumns.TIMES_CONTACTED, //3
+ People.PRESENCE_STATUS, //4
};
+ static final int SUMMARY_ID_COLUMN_INDEX = 0;
+ static final int SUMMARY_NAME_COLUMN_INDEX = 1;
+ static final int SUMMARY_STARRED_COLUMN_INDEX = 2;
+ static final int SUMMARY_TIMES_CONTACTED_COLUMN_INDEX = 3;
+ static final int SUMMARY_PRESENCE_STATUS_COLUMN_INDEX = 4;
+ static final int SUMMARY_PHOTO_ID_COLUMN_INDEX = 5;
+ static final int SUMMARY_LOOKUP_KEY = 6;
+ static final int SUMMARY_HAS_PHONE_COLUMN_INDEX = 7;
static final String[] PHONES_PROJECTION = new String[] {
- Phones._ID, // 0
- NAME_COLUMN, // 1
- Phones.NUMBER, // 2
- Phones.TYPE, // 3
- Phones.LABEL, // 4
- Phones.STARRED, // 5
- Phones.PERSON_ID, // 6
+ 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 String[] CONTACT_METHODS_PROJECTION = new String[] {
- ContactMethods._ID, // 0
- NAME_COLUMN, // 1
- ContactMethods.DATA, // 2
- ContactMethods.TYPE, // 3
- ContactMethods.LABEL, // 4
- ContactMethods.STARRED, // 5
- ContactMethods.PERSON_ID, // 6
- };
-
- static final int ID_COLUMN_INDEX = 0;
- static final int NAME_COLUMN_INDEX = 1;
- static final int NUMBER_COLUMN_INDEX = 2;
- static final int DATA_COLUMN_INDEX = 2;
- static final int TYPE_COLUMN_INDEX = 3;
- static final int LABEL_COLUMN_INDEX = 4;
- static final int STARRED_COLUMN_INDEX = 5;
- static final int PRIMARY_PHONE_ID_COLUMN_INDEX = 6;
- static final int PRIMARY_EMAIL_ID_COLUMN_INDEX = 7;
- static final int SERVER_STATUS_COLUMN_INDEX = 8;
- static final int PHOTO_COLUMN_INDEX = 9;
- static final int SORT_STRING_INDEX = 9;
-
- static final int PHONES_PERSON_ID_INDEX = 6;
- static final int SIMPLE_CONTACTS_PERSON_ID_INDEX = 0;
-
- static final int DISPLAY_GROUP_INDEX_ALL_CONTACTS = 0;
- static final int DISPLAY_GROUP_INDEX_ALL_CONTACTS_WITH_PHONES = 1;
- static final int DISPLAY_GROUP_INDEX_MY_CONTACTS = 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;
- private static final String[] GROUPS_PROJECTION = new String[] {
- Groups.SYSTEM_ID, // 0
- Groups.NAME, // 1
- };
- private static final int GROUPS_COLUMN_INDEX_SYSTEM_ID = 0;
- private static final int GROUPS_COLUMN_INDEX_NAME = 1;
-
- static final String GROUP_WITH_PHONES = "android_smartgroup_phone";
+ static final String KEY_PICKER_MODE = "picker_mode";
- ContactItemListAdapter mAdapter;
+ private ContactItemListAdapter mAdapter;
- int mMode = DEFAULT_MODE;
- // The current display group
- private String mDisplayInfo;
- private int mDisplayType;
- // The current list of display groups, during selection from menu
- private CharSequence[] mDisplayGroups;
- // If true position 2 in mDisplayGroups is the MyContacts group
- private boolean mDisplayGroupsIncludesMyContacts = false;
+ int mMode = MODE_DEFAULT;
- private int mDisplayGroupOriginalSelection;
- private int mDisplayGroupCurrentSelection;
-
private QueryHandler mQueryHandler;
- private String mQuery;
- private Uri mGroupFilterUri;
- private Uri mGroupUri;
private boolean mJustCreated;
private boolean mSyncEnabled;
+ private Uri mSelectedContactUri;
- /**
- * Cursor row index that holds reference back to {@link People#_ID}, such as
- * {@link ContactMethods#PERSON_ID}. Used when responding to a
- * {@link Intent#ACTION_SEARCH} in mode {@link #MODE_QUERY_PICK_TO_VIEW}.
- */
- private int mQueryPersonIdIndex;
+// 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.
@@ -302,8 +365,9 @@
private boolean mListHasFocus;
private String mShortcutAction;
- private boolean mDefaultMode = false;
-
+
+ private int mScrollState;
+
/**
* Internal query type when in mode {@link #MODE_QUERY_PICK_TO_VIEW}.
*/
@@ -312,52 +376,44 @@
private static final int QUERY_MODE_NONE = -1;
private static final int QUERY_MODE_MAILTO = 1;
private static final int QUERY_MODE_TEL = 2;
-
+
/**
* Data to use when in mode {@link #MODE_QUERY_PICK_TO_VIEW}. Usually
* provided by scheme-specific part of incoming {@link Intent#getData()}.
*/
private String mQueryData;
-
- private Handler mHandler = new Handler();
- private class ImportTypeSelectedListener implements DialogInterface.OnClickListener {
- public static final int IMPORT_FROM_SIM = 0;
- public static final int IMPORT_FROM_SDCARD = 1;
+ private static final String CLAUSE_ONLY_VISIBLE = Contacts.IN_VISIBLE_GROUP + "=1";
+ private static final String CLAUSE_ONLY_PHONES = Contacts.HAS_PHONE_NUMBER + "=1";
- private int mIndex;
+ /**
+ * In the {@link #MODE_JOIN} determines whether we display a list item with the label
+ * "Show all contacts" or actually show all contacts
+ */
+ private boolean mJoinModeShowAllContacts;
- public ImportTypeSelectedListener() {
- mIndex = IMPORT_FROM_SIM;
- }
+ /**
+ * The ID of the special item described above.
+ */
+ private static final long JOIN_MODE_SHOW_ALL_CONTACTS_ID = -2;
- public void onClick(DialogInterface dialog, int which) {
- if (which == DialogInterface.BUTTON_POSITIVE) {
- if (mIndex == IMPORT_FROM_SIM) {
- doImportFromSim();
- } else {
- doImportFromSDCard();
- }
- } else if (which == DialogInterface.BUTTON_NEGATIVE) {
+ // Uri matcher for contact id
+ private static final int CONTACTS_ID = 1001;
+ private static final UriMatcher sContactsIdMatcher;
- } else {
- mIndex = which;
- }
- }
+ private static ExecutorService sImageFetchThreadPool;
+
+ static {
+ sContactsIdMatcher = new UriMatcher(UriMatcher.NO_MATCH);
+ sContactsIdMatcher.addURI(ContactsContract.AUTHORITY, "contacts/#", CONTACTS_ID);
}
private class DeleteClickListener implements DialogInterface.OnClickListener {
- private Uri mUri;
-
- public DeleteClickListener(Uri uri) {
- mUri = uri;
- }
-
public void onClick(DialogInterface dialog, int which) {
- getContentResolver().delete(mUri, null, null);
+ getContentResolver().delete(mSelectedContactUri, null, null);
}
}
-
+
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
@@ -366,18 +422,17 @@
final Intent intent = getIntent();
// Allow the title to be set to a custom String using an extra on the intent
- String title = intent.getStringExtra(Contacts.Intents.UI.TITLE_EXTRA_KEY);
+ String title = intent.getStringExtra(UI.TITLE_EXTRA_KEY);
if (title != null) {
setTitle(title);
}
-
+
final String action = intent.getAction();
mMode = MODE_UNKNOWN;
-
- setContentView(R.layout.contacts_list_content);
+ Log.i(TAG, "Called with action: " + action);
if (UI.LIST_DEFAULT.equals(action)) {
- mDefaultMode = true;
+ 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)) {
@@ -387,9 +442,10 @@
finish();
return;
}
- buildUserGroupUris(groupName);
+ buildUserGroupUri(groupName);
} else if (UI.LIST_ALL_CONTACTS_ACTION.equals(action)) {
- mMode = MODE_ALL_CONTACTS;
+ mMode = MODE_CUSTOM;
+ mDisplayOnlyPhones = false;
} else if (UI.LIST_STARRED_ACTION.equals(action)) {
mMode = MODE_STARRED;
} else if (UI.LIST_FREQUENT_ACTION.equals(action)) {
@@ -397,17 +453,24 @@
} else if (UI.LIST_STREQUENT_ACTION.equals(action)) {
mMode = MODE_STREQUENT;
} else if (UI.LIST_CONTACTS_WITH_PHONES_ACTION.equals(action)) {
- mMode = MODE_WITH_PHONES;
+ 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 (People.CONTENT_TYPE.equals(type)) {
+ if (Contacts.CONTENT_TYPE.equals(type)) {
mMode = MODE_PICK_CONTACT;
- } else if (Phones.CONTENT_TYPE.equals(type)) {
+ } 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 (ContactMethods.CONTENT_POSTAL_TYPE.equals(type)) {
+ } 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 (intent.getComponent().getClassName().equals("alias.DialShortcut")) {
@@ -425,13 +488,20 @@
}
} else if (Intent.ACTION_GET_CONTENT.equals(action)) {
final String type = intent.resolveType(this);
- if (People.CONTENT_ITEM_TYPE.equals(type)) {
+ if (Contacts.CONTENT_ITEM_TYPE.equals(type)) {
mMode = MODE_PICK_OR_CREATE_CONTACT;
- } else if (Phones.CONTENT_ITEM_TYPE.equals(type)) {
+ } else if (Phone.CONTENT_ITEM_TYPE.equals(type)) {
mMode = MODE_PICK_PHONE;
- } else if (ContactMethods.CONTENT_POSTAL_ITEM_TYPE.equals(type)) {
+ } 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)) {
+ 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)) {
@@ -446,7 +516,7 @@
finish();
return;
}
-
+
// See if search request has extras to specify query
if (intent.hasExtra(Insert.EMAIL)) {
mMode = MODE_QUERY_PICK_TO_VIEW;
@@ -459,18 +529,33 @@
} else {
// Otherwise handle the more normal search case
mMode = MODE_QUERY;
+ mQueryData = getIntent().getStringExtra(SearchManager.QUERY);
}
// 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))) {
- newIntent = new Intent(Intent.ACTION_CALL_PRIVILEGED, intent.getData());
+ 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, intent.getData());
+ newIntent = new Intent(Intent.ACTION_VIEW, data);
}
startActivity(newIntent);
finish();
@@ -481,40 +566,67 @@
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, People.CONTENT_URI);
+ 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)) {
+ 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 = DEFAULT_MODE;
+ mMode = MODE_DEFAULT;
+ }
+
+ 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 {
+ setContentView(R.layout.contacts_list_content);
}
// Setup the UI
final ListView list = getListView();
+
+ // 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.setFocusable(true);
list.setOnCreateContextMenuListener(this);
if ((mMode & MODE_MASK_NO_FILTER) != MODE_MASK_NO_FILTER) {
list.setTextFilterEnabled(true);
- }
+ }
if ((mMode & MODE_MASK_CREATE_NEW) != 0) {
// Add the header for creating a new contact
final LayoutInflater inflater = getLayoutInflater();
- View header = inflater.inflate(android.R.layout.simple_list_item_1, list, false);
- TextView text = (TextView) header.findViewById(android.R.id.text1);
- text.setText(R.string.pickerNewContactHeader);
+ View header = inflater.inflate(R.layout.create_new_contact, list, false);
list.addHeaderView(header);
}
// Set the proper empty string
setEmptyText();
-
+
mAdapter = new ContactItemListAdapter(this);
setListAdapter(mAdapter);
+ getListView().setOnScrollListener(mAdapter);
// We manually save/restore the listview state
list.setSaveEnabled(false);
@@ -522,80 +634,101 @@
mQueryHandler = new QueryHandler(this);
mJustCreated = true;
- // Check to see if sync is enabled
- final ContentResolver resolver = getContentResolver();
- IContentProvider provider = resolver.acquireProvider(Contacts.CONTENT_URI);
- if (provider == null) {
- // No contacts provider, bail.
- finish();
- return;
+ // TODO(jham) redesign this
+ mSyncEnabled = true;
+// // Check to see if sync is enabled
+// final ContentResolver resolver = getContentResolver();
+// IContentProvider provider = resolver.acquireProvider(Contacts.CONTENT_URI);
+// if (provider == null) {
+// // No contacts provider, bail.
+// finish();
+// return;
+// }
+//
+// try {
+// ISyncAdapter sa = provider.getSyncAdapter();
+// mSyncEnabled = sa != null;
+// } catch (RemoteException e) {
+// mSyncEnabled = false;
+// } finally {
+// resolver.releaseProvider(provider);
+// }
+ }
+
+ 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();
+ }
}
- try {
- ISyncAdapter sa = provider.getSyncAdapter();
- mSyncEnabled = sa != null;
- } catch (RemoteException e) {
- mSyncEnabled = false;
- } finally {
- resolver.releaseProvider(provider);
+ if (contactName == null) {
+ contactName = "";
+ }
+
+ return contactName;
+ }
+
+ private int[] mLocation = new int[2];
+ private Rect mRect = new Rect();
+
+ /** {@inheritDoc} */
+ public void onClick(View v) {
+ if (v.getId() == R.id.call_button) {
+ final int position = (Integer) v.getTag();
+ Cursor c = (Cursor) mAdapter.getItem(position);
+ if (c != null) {
+ callContact(c);
+ }
}
}
private void setEmptyText() {
+ if (mMode == MODE_JOIN_CONTACT) {
+ return;
+ }
+
TextView empty = (TextView) findViewById(R.id.emptyText);
- // Center the text by default
- int gravity = Gravity.CENTER;
- switch (mMode) {
- case MODE_GROUP:
- if (Groups.GROUP_MY_CONTACTS.equals(mDisplayInfo)) {
- if (mSyncEnabled) {
- empty.setText(getText(R.string.noContactsHelpTextWithSync));
- } else {
- empty.setText(getText(R.string.noContactsHelpText));
- }
- gravity = Gravity.NO_GRAVITY;
+ int gravity = Gravity.NO_GRAVITY;
+
+ if (mDisplayOnlyPhones) {
+ empty.setText(getText(R.string.noContactsWithPhoneNumbers));
+ gravity = Gravity.CENTER;
+ } else if (mMode == MODE_STREQUENT || mMode == MODE_STARRED) {
+ empty.setText(getText(R.string.noFavoritesHelpText));
+ } else if (mMode == MODE_QUERY) {
+ empty.setText(getText(R.string.noMatchingContacts));
+ } else {
+ boolean hasSim = ((TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE))
+ .hasIccCard();
+
+ if (hasSim) {
+ if (mSyncEnabled) {
+ empty.setText(getText(R.string.noContactsHelpTextWithSync));
} else {
- empty.setText(getString(R.string.groupEmpty, mDisplayInfo));
+ empty.setText(getText(R.string.noContactsHelpText));
}
- break;
-
- case MODE_STARRED:
- case MODE_STREQUENT:
- case MODE_FREQUENT:
- empty.setText(getText(R.string.noFavorites));
- break;
-
- case MODE_WITH_PHONES:
- empty.setText(getText(R.string.noContactsWithPhoneNumbers));
- break;
-
- default:
- empty.setText(getText(R.string.noContacts));
- break;
+ } else {
+ if (mSyncEnabled) {
+ empty.setText(getText(R.string.noContactsNoSimHelpTextWithSync));
+ } else {
+ empty.setText(getText(R.string.noContactsNoSimHelpText));
+ }
+ }
}
empty.setGravity(gravity);
}
- /**
- * Builds the URIs to query when displaying a user group
- *
- * @param groupName the group being displayed
- */
- private void buildUserGroupUris(String groupName) {
- mGroupFilterUri = Uri.parse("content://contacts/groups/name/" + groupName
- + "/members/filter/");
- mGroupUri = Uri.parse("content://contacts/groups/name/" + groupName + "/members");
- }
-
- /**
- * Builds the URIs to query when displaying a system group
- *
- * @param systemId the system group's ID
- */
- private void buildSystemGroupUris(String systemId) {
- mGroupFilterUri = Uri.parse("content://contacts/groups/system_id/" + systemId
- + "/members/filter/");
- mGroupUri = Uri.parse("content://contacts/groups/system_id/" + systemId + "/members");
+ private void buildUserGroupUri(String group) {
+ mGroupUri = Uri.withAppendedPath(Contacts.CONTENT_GROUP_URI, group);
}
/**
@@ -604,65 +737,9 @@
private void setDefaultMode() {
// Load the preferences
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
-
- // Lookup the group to display
- mDisplayType = prefs.getInt(PREF_DISPLAY_TYPE, DISPLAY_TYPE_UNKNOWN);
- switch (mDisplayType) {
- case DISPLAY_TYPE_ALL_WITH_PHONES: {
- mMode = MODE_WITH_PHONES;
- mDisplayInfo = null;
- break;
- }
- case DISPLAY_TYPE_SYSTEM_GROUP: {
- String systemId = prefs.getString(
- PREF_DISPLAY_INFO, null);
- if (!TextUtils.isEmpty(systemId)) {
- // Display the selected system group
- mMode = MODE_GROUP;
- buildSystemGroupUris(systemId);
- mDisplayInfo = systemId;
- } else {
- // No valid group is present, display everything
- mMode = MODE_WITH_PHONES;
- mDisplayInfo = null;
- mDisplayType = DISPLAY_TYPE_ALL;
- }
- break;
- }
-
- case DISPLAY_TYPE_USER_GROUP: {
- String displayGroup = prefs.getString(
- PREF_DISPLAY_INFO, null);
- if (!TextUtils.isEmpty(displayGroup)) {
- // Display the selected user group
- mMode = MODE_GROUP;
- buildUserGroupUris(displayGroup);
- mDisplayInfo = displayGroup;
- } else {
- // No valid group is present, display everything
- mMode = MODE_WITH_PHONES;
- mDisplayInfo = null;
- mDisplayType = DISPLAY_TYPE_ALL;
- }
- break;
- }
-
- case DISPLAY_TYPE_ALL: {
- mMode = MODE_ALL_CONTACTS;
- mDisplayInfo = null;
- break;
- }
-
- default: {
- // We don't know what to display, default to My Contacts
- mMode = MODE_GROUP;
- mDisplayType = DISPLAY_TYPE_SYSTEM_GROUP;
- buildSystemGroupUris(Groups.GROUP_MY_CONTACTS);
- mDisplayInfo = Groups.GROUP_MY_CONTACTS;
- break;
- }
- }
+ mDisplayOnlyPhones = prefs.getBoolean(Prefs.DISPLAY_ONLY_PHONES,
+ Prefs.DISPLAY_ONLY_PHONES_DEFAULT);
// Update the empty text view with the proper string, as the group may have changed
setEmptyText();
@@ -672,17 +749,18 @@
protected void onResume() {
super.onResume();
+ mScrollState = OnScrollListener.SCROLL_STATE_IDLE;
boolean runQuery = true;
Activity parent = getParent();
-
+
// Do this before setting the filter. The filter thread relies
// on some state that is initialized in setDefaultMode
- if (mDefaultMode) {
+ 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 (parent != null && parent instanceof DialtactsActivity) {
String filterText = ((DialtactsActivity) parent).getAndClearFilterText();
@@ -702,7 +780,7 @@
}
mJustCreated = false;
}
-
+
@Override
protected void onRestart() {
super.onRestart();
@@ -717,15 +795,6 @@
((ContactItemListAdapter) getListAdapter()).onContentChanged();
}
}
-
- private void updateGroup() {
- if (mDefaultMode) {
- setDefaultMode();
- }
-
- // Calling requery here may cause an ANR, so always do the async query
- startQuery();
- }
@Override
protected void onSaveInstanceState(Bundle icicle) {
@@ -751,7 +820,9 @@
// be there and show up while the new query is happening. After the async query finished
// in response to onRestart() setLoading(false) will be called.
mAdapter.setLoading(true);
+ mAdapter.setSuggestionsCursor(null);
mAdapter.changeCursor(null);
+ mAdapter.clearImageFetching();
if (mMode == MODE_QUERY) {
// Make sure the search box is closed
@@ -762,160 +833,220 @@
@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;
}
- // Search
- menu.add(0, MENU_SEARCH, 0, R.string.menu_search)
- .setIcon(android.R.drawable.ic_menu_search);
-
- // New contact
- menu.add(0, MENU_NEW_CONTACT, 0, R.string.menu_newContact)
- .setIcon(android.R.drawable.ic_menu_add)
- .setIntent(new Intent(Intents.Insert.ACTION, People.CONTENT_URI))
- .setAlphabeticShortcut('n');
-
- // Display group
- if (mDefaultMode) {
- menu.add(0, MENU_DISPLAY_GROUP, 0, R.string.menu_displayGroup)
- .setIcon(com.android.internal.R.drawable.ic_menu_allfriends);
- }
-
- // Sync settings
- if (mSyncEnabled) {
- Intent syncIntent = new Intent(Intent.ACTION_VIEW);
- syncIntent.setClass(this, ContactsGroupSyncSelector.class);
- menu.add(0, 0, 0, R.string.syncGroupPreference)
- .setIcon(com.android.internal.R.drawable.ic_menu_refresh)
- .setIntent(syncIntent);
- }
-
- // Contacts import (SIM/SDCard)
- menu.add(0, MENU_IMPORT_CONTACTS, 0, R.string.importFromSim)
- .setIcon(R.drawable.ic_menu_import_contact);
-
- if (getResources().getBoolean(R.bool.config_allow_export_to_sdcard)) {
- menu.add(0, MENU_EXPORT_CONTACTS, 0, R.string.export_contact_list)
- .setIcon(R.drawable.ic_menu_export_contact);
- }
-
- return super.onCreateOptionsMenu(menu);
+ MenuInflater inflater = getMenuInflater();
+ inflater.inflate(R.menu.list, menu);
+ return true;
}
- /*
- * Implements the handler for display group selection.
- */
- public void onClick(DialogInterface dialogInterface, int which) {
- if (which == DialogInterface.BUTTON_POSITIVE) {
- // The OK button was pressed
- if (mDisplayGroupOriginalSelection != mDisplayGroupCurrentSelection) {
- // Set the group to display
- if (mDisplayGroupCurrentSelection == DISPLAY_GROUP_INDEX_ALL_CONTACTS) {
- // Display all
- mDisplayType = DISPLAY_TYPE_ALL;
- mDisplayInfo = null;
- } else if (mDisplayGroupCurrentSelection
- == DISPLAY_GROUP_INDEX_ALL_CONTACTS_WITH_PHONES) {
- // Display all with phone numbers
- mDisplayType = DISPLAY_TYPE_ALL_WITH_PHONES;
- mDisplayInfo = null;
- } else if (mDisplayGroupsIncludesMyContacts &&
- mDisplayGroupCurrentSelection == DISPLAY_GROUP_INDEX_MY_CONTACTS) {
- mDisplayType = DISPLAY_TYPE_SYSTEM_GROUP;
- mDisplayInfo = Groups.GROUP_MY_CONTACTS;
- } else {
- mDisplayType = DISPLAY_TYPE_USER_GROUP;
- mDisplayInfo = mDisplayGroups[mDisplayGroupCurrentSelection].toString();
- }
-
- // Save the changes to the preferences
- SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
- prefs.edit()
- .putInt(PREF_DISPLAY_TYPE, mDisplayType)
- .putString(PREF_DISPLAY_INFO, mDisplayInfo)
- .commit();
-
- // Update the display state
- updateGroup();
- }
- } else {
- // A list item was selected, cache the position
- mDisplayGroupCurrentSelection = which;
- }
+ @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 MENU_DISPLAY_GROUP:
- AlertDialog.Builder builder = new AlertDialog.Builder(this)
- .setTitle(R.string.select_group_title)
- .setPositiveButton(android.R.string.ok, this)
- .setNegativeButton(android.R.string.cancel, null);
-
- setGroupEntries(builder);
-
- builder.show();
+ case R.id.menu_display_groups: {
+ final Intent intent = new Intent(this, DisplayGroupsActivity.class);
+ startActivityForResult(intent, SUBACTIVITY_DISPLAY_GROUP);
return true;
-
- case MENU_SEARCH:
+ }
+ case R.id.menu_search: {
startSearch(null, false, null, false);
return true;
-
- case MENU_IMPORT_CONTACTS:
- if (getResources().getBoolean(R.bool.config_allow_import_from_sdcard)) {
- ImportTypeSelectedListener listener =
- new ImportTypeSelectedListener();
- AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this)
- .setTitle(R.string.select_import_type_title)
- .setPositiveButton(android.R.string.ok, listener)
- .setNegativeButton(android.R.string.cancel, null);
- dialogBuilder.setSingleChoiceItems(new String[] {
- getString(R.string.import_from_sim),
- getString(R.string.import_from_sdcard)},
- ImportTypeSelectedListener.IMPORT_FROM_SIM, listener);
- dialogBuilder.show();
- } else {
- doImportFromSim();
- }
+ }
+ case R.id.menu_add: {
+ final Intent intent = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI);
+ startActivity(intent);
return true;
-
- case MENU_EXPORT_CONTACTS:
- handleExportContacts();
+ }
+ 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;
}
- private void doImportFromSim() {
- Intent importIntent = new Intent(Intent.ACTION_VIEW);
- importIntent.setType("vnd.android.cursor.item/sim-contact");
- importIntent.setClassName("com.android.phone", "com.android.phone.SimContacts");
- startActivity(importIntent);
+ @Override
+ protected Dialog onCreateDialog(int id) {
+ 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);
}
- private void doImportFromSDCard() {
- Intent intent = new Intent(this, ImportVCardActivity.class);
- startActivity(intent);
+ /**
+ * 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);
+ }
+
+ 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;
+ }
+ 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 handleExportContacts() {
- VCardExporter exporter = new VCardExporter(ContactsListActivity.this, mHandler);
- exporter.startExportVCardToSdCard();
+ 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) {
- // Contact was created, pass it back
returnPickerResult(null, data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME),
data.getData(), 0);
}
+ 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;
}
}
@@ -941,35 +1072,27 @@
return;
}
long id = info.id;
- Uri personUri = ContentUris.withAppendedId(People.CONTENT_URI, 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(NAME_COLUMN_INDEX));
+ menu.setHeaderTitle(cursor.getString(SUMMARY_NAME_COLUMN_INDEX));
// View contact details
menu.add(0, MENU_ITEM_VIEW_CONTACT, 0, R.string.menu_viewContact)
- .setIntent(new Intent(Intent.ACTION_VIEW, personUri));
+ .setIntent(new Intent(Intent.ACTION_VIEW, contactUri));
- // Calling contact
- long phoneId = cursor.getLong(PRIMARY_PHONE_ID_COLUMN_INDEX);
- if (phoneId > 0) {
- // Get the display label for the number
- CharSequence label = cursor.getString(LABEL_COLUMN_INDEX);
- int type = cursor.getInt(TYPE_COLUMN_INDEX);
- label = Phones.getDisplayLabel(this, type, label);
- Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
- ContentUris.withAppendedId(Phones.CONTENT_URI, phoneId));
- menu.add(0, MENU_ITEM_CALL, 0, String.format(getString(R.string.menu_callNumber), label))
- .setIntent(intent);
-
+ 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, R.string.menu_sendSMS)
- .setIntent(new Intent(Intent.ACTION_SENDTO,
- Uri.fromParts("sms", cursor.getString(NUMBER_COLUMN_INDEX), null)));
+ menu.add(0, MENU_ITEM_SEND_SMS, 0, getString(R.string.menu_sendSMS));
}
// Star toggling
- int starState = cursor.getInt(STARRED_COLUMN_INDEX);
+ int starState = cursor.getInt(SUMMARY_STARRED_COLUMN_INDEX);
if (starState == 0) {
menu.add(0, MENU_ITEM_TOGGLE_STAR, 0, R.string.menu_addStar);
} else {
@@ -978,7 +1101,7 @@
// Contact editing
menu.add(0, MENU_ITEM_EDIT, 0, R.string.menu_editContact)
- .setIntent(new Intent(Intent.ACTION_EDIT, personUri));
+ .setIntent(new Intent(Intent.ACTION_EDIT, rawContactUri));
menu.add(0, MENU_ITEM_DELETE, 0, R.string.menu_deleteContact);
}
@@ -998,25 +1121,25 @@
case MENU_ITEM_TOGGLE_STAR: {
// Toggle the star
ContentValues values = new ContentValues(1);
- values.put(People.STARRED, cursor.getInt(STARRED_COLUMN_INDEX) == 0 ? 1 : 0);
- Uri personUri = ContentUris.withAppendedId(People.CONTENT_URI,
- cursor.getInt(ID_COLUMN_INDEX));
- getContentResolver().update(personUri, values, null, null);
+ 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: {
- // Get confirmation
- Uri uri = ContentUris.withAppendedId(People.CONTENT_URI,
- cursor.getLong(ID_COLUMN_INDEX));
- //TODO make this dialog persist across screen rotations
- new AlertDialog.Builder(ContactsListActivity.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(uri))
- .show();
+ mSelectedContactUri = getContactUri(info.position);
+ doContactDelete();
return true;
}
}
@@ -1033,22 +1156,11 @@
}
break;
}
-
case KeyEvent.KEYCODE_DEL: {
- Object o = getListView().getSelectedItem();
- if (o != null) {
- Cursor cursor = (Cursor) o;
- Uri uri = ContentUris.withAppendedId(People.CONTENT_URI,
- cursor.getLong(ID_COLUMN_INDEX));
- //TODO make this dialog persist across screen rotations
- new AlertDialog.Builder(ContactsListActivity.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(uri))
- .setCancelable(false)
- .show();
+ final int position = getListView().getSelectedItemPosition();
+ if (position != ListView.INVALID_POSITION) {
+ mSelectedContactUri = getContactUri(position);
+ doContactDelete();
return true;
}
break;
@@ -1058,6 +1170,46 @@
return super.onKeyDown(keyCode, event);
}
+ /**
+ * Prompt the user before deleting the given {@link Contacts} entry.
+ */
+ protected void doContactDelete() {
+ mReadOnlySourcesCnt = 0;
+ mWritableSourcesCnt = 0;
+ mWritableRawContactIds.clear();
+
+ if (mSelectedContactUri != null) {
+ Cursor c = getContentResolver().query(RawContacts.CONTENT_URI, RAW_CONTACTS_PROJECTION,
+ RawContacts.CONTACT_ID + "=" + ContentUris.parseId(mSelectedContactUri), null,
+ null);
+ Sources sources = Sources.getInstance(ContactsListActivity.this);
+ if (c != null) {
+ 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);
+ }
+ }
+ }
+ c.close();
+ 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);
+ }
+ }
+ }
+
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
// Hide soft keyboard, if visible
@@ -1068,75 +1220,89 @@
if (mMode == MODE_INSERT_OR_EDIT_CONTACT) {
Intent intent;
if (position == 0) {
- // Insert
- intent = new Intent(Intent.ACTION_INSERT, People.CONTENT_URI);
+ intent = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI);
} else {
- // Edit
- intent = new Intent(Intent.ACTION_EDIT,
- ContentUris.withAppendedId(People.CONTENT_URI, id));
+ // Edit. adjusting position by subtracting header view count.
+ position -= getListView().getHeaderViewsCount();
+ final Uri uri = getSelectedUri(position);
+ intent = new Intent(Intent.ACTION_EDIT, uri);
}
intent.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
- final Bundle extras = getIntent().getExtras();
- if (extras != null) {
- intent.putExtras(extras);
+ Bundle extras = getIntent().getExtras();
+
+ if (extras == null) {
+ extras = new Bundle();
}
+ intent.putExtras(extras);
+ extras.putBoolean(KEY_PICKER_MODE, (mMode & MODE_MASK_PICKER) == MODE_MASK_PICKER);
+
startActivity(intent);
finish();
} else if (id != -1) {
+ // Subtract one if we have Create Contact at the top
+ if ((mMode & MODE_MASK_CREATE_NEW) != 0) {
+ position--;
+ }
+ final Uri uri = getSelectedUri(position);
if ((mMode & MODE_MASK_PICKER) == 0) {
- Intent intent = new Intent(Intent.ACTION_VIEW,
- ContentUris.withAppendedId(People.CONTENT_URI, id));
- startActivity(intent);
+ final Intent intent = new Intent(Intent.ACTION_VIEW, uri);
+ startActivityForResult(intent, SUBACTIVITY_VIEW_CONTACT);
+ } else if (mMode == MODE_JOIN_CONTACT) {
+ if (id == JOIN_MODE_SHOW_ALL_CONTACTS_ID) {
+ mJoinModeShowAllContacts = false;
+ startQuery();
+ } else {
+ returnPickerResult(null, null, uri, id);
+ }
} else if (mMode == MODE_QUERY_PICK_TO_VIEW) {
// Started with query that should launch to view contact
- Cursor c = (Cursor) mAdapter.getItem(position);
- long personId = c.getLong(mQueryPersonIdIndex);
- Intent intent = new Intent(Intent.ACTION_VIEW,
- ContentUris.withAppendedId(People.CONTENT_URI, personId));
+ final Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
finish();
- } else if (mMode == MODE_PICK_CONTACT
- || mMode == MODE_PICK_OR_CREATE_CONTACT) {
- Uri uri = ContentUris.withAppendedId(People.CONTENT_URI, id);
+ } else if (mMode == MODE_PICK_CONTACT
+ || mMode == MODE_PICK_OR_CREATE_CONTACT
+ || mMode == MODE_LEGACY_PICK_PERSON
+ || mMode == MODE_LEGACY_PICK_OR_CREATE_PERSON) {
if (mShortcutAction != null) {
- // Subtract one if we have Create Contact at the top
- Cursor c = (Cursor) mAdapter.getItem(position
- - (mMode == MODE_PICK_OR_CREATE_CONTACT? 1:0));
- returnPickerResult(c, c.getString(NAME_COLUMN_INDEX), uri, id);
+ Cursor c = (Cursor) mAdapter.getItem(position);
+ returnPickerResult(c, c.getString(SUMMARY_NAME_COLUMN_INDEX), uri, id);
} else {
returnPickerResult(null, null, uri, id);
}
} else if (mMode == MODE_PICK_PHONE) {
- Uri uri = ContentUris.withAppendedId(Phones.CONTENT_URI, id);
if (mShortcutAction != null) {
Cursor c = (Cursor) mAdapter.getItem(position);
- returnPickerResult(c, c.getString(NAME_COLUMN_INDEX), uri, id);
+ returnPickerResult(c, c.getString(PHONE_DISPLAY_NAME_COLUMN_INDEX), uri, id);
} else {
returnPickerResult(null, null, uri, id);
}
- } else if (mMode == MODE_PICK_POSTAL) {
- setResult(RESULT_OK, new Intent().setData(
- ContentUris.withAppendedId(ContactMethods.CONTENT_URI, id)));
- finish();
+ } else if (mMode == MODE_PICK_POSTAL
+ || mMode == MODE_LEGACY_PICK_POSTAL
+ || mMode == MODE_LEGACY_PICK_PHONE) {
+ returnPickerResult(null, null, uri, id);
}
} else if ((mMode & MODE_MASK_CREATE_NEW) == MODE_MASK_CREATE_NEW
&& position == 0) {
- Intent newContact = new Intent(Intents.Insert.ACTION, People.CONTENT_URI);
+ Intent newContact = new Intent(Intents.Insert.ACTION, Contacts.CONTENT_URI);
startActivityForResult(newContact, SUBACTIVITY_NEW_CONTACT);
} else {
signalError();
}
}
+ /**
+ * @param uri 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 uri, long id) {
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(mShortcutAction, uri);
- final Bitmap icon = People.loadContactPhoto(this, uri, 0, null);
+ final Bitmap icon = loadContactPhoto(id, null);
if (icon != null) {
intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, icon);
} else {
@@ -1146,27 +1312,26 @@
}
} else {
// This is a direct dial or sms shortcut.
- String number = c.getString(DATA_COLUMN_INDEX);
- int type = c.getInt(TYPE_COLUMN_INDEX);
+ 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 = "tel";
+ scheme = Constants.SCHEME_TEL;
resid = R.drawable.badge_action_call;
} else {
- scheme = "smsto";
+ 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);
-
- // Find the People._ID for this phone number
- final long personId = c.getLong(PHONES_PERSON_ID_INDEX);
- Uri personUri = ContentUris.withAppendedId(People.CONTENT_URI, personId);
+
+ // Find the Contacts._ID for this phone number
+ long contactId = c.getLong(PHONE_CONTACT_ID_COLUMN_INDEX);
intent.putExtra(Intent.EXTRA_SHORTCUT_ICON,
- generatePhoneNumberIcon(personUri, type, resid));
-
+ generatePhoneNumberIcon(contactId, type, resid));
}
shortcutIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
@@ -1182,16 +1347,16 @@
* 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 personUri The person the phone number belongs to
+ * @param contactId 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 personUri, int type, int actionResId) {
+ private Bitmap generatePhoneNumberIcon(long contactId, int type, int actionResId) {
final Resources r = getResources();
boolean drawPhoneOverlay = true;
- Bitmap photo = People.loadContactPhoto(this, personUri, 0, null);
+ Bitmap photo = loadContactPhoto(contactId, null);
if (photo == null) {
// If there isn't a photo use the generic phone action icon instead
Bitmap phoneIcon = getPhoneActionIcon(r, actionResId);
@@ -1219,24 +1384,24 @@
// Create an overlay for the phone number type
String overlay = null;
switch (type) {
- case Phones.TYPE_HOME:
- overlay = "H";
+ case Phone.TYPE_HOME:
+ overlay = getString(R.string.type_short_home);
break;
- case Phones.TYPE_MOBILE:
- overlay = "M";
+ case Phone.TYPE_MOBILE:
+ overlay = getString(R.string.type_short_mobile);
break;
- case Phones.TYPE_WORK:
- overlay = "W";
+ case Phone.TYPE_WORK:
+ overlay = getString(R.string.type_short_work);
break;
- case Phones.TYPE_PAGER:
- overlay = "P";
+ case Phone.TYPE_PAGER:
+ overlay = getString(R.string.type_short_pager);
break;
- case Phones.TYPE_OTHER:
- overlay = "O";
+ case Phone.TYPE_OTHER:
+ overlay = getString(R.string.type_short_other);
break;
}
if (overlay != null) {
@@ -1252,7 +1417,7 @@
if (ENABLE_ACTION_ICON_OVERLAYS && drawPhoneOverlay) {
Bitmap phoneIcon = getPhoneActionIcon(r, actionResId);
if (phoneIcon != null) {
- src.set(0,0, phoneIcon.getWidth(),phoneIcon.getHeight());
+ src.set(0, 0, phoneIcon.getWidth(), phoneIcon.getHeight());
int iconWidth = icon.getWidth();
dst.set(iconWidth - 20, -1, iconWidth, 19);
canvas.drawBitmap(phoneIcon, src, dst, photoPaint);
@@ -1278,30 +1443,197 @@
return null;
}
}
-
- String[] getProjection() {
- switch (mMode) {
- case MODE_GROUP:
- case MODE_ALL_CONTACTS:
- case MODE_WITH_PHONES:
- case MODE_PICK_CONTACT:
- case MODE_PICK_OR_CREATE_CONTACT:
- case MODE_QUERY:
- case MODE_STARRED:
+
+ Uri getUriToQuery() {
+ switch(mMode) {
+ case MODE_JOIN_CONTACT:
+ return getJoinSuggestionsUri(null);
case MODE_FREQUENT:
+ case MODE_STARRED:
+ case MODE_DEFAULT:
case MODE_INSERT_OR_EDIT_CONTACT:
- return CONTACTS_PROJECTION;
-
- case MODE_STREQUENT:
- return STREQUENT_PROJECTION;
-
- case MODE_PICK_PHONE:
- return PHONES_PROJECTION;
-
- case MODE_PICK_POSTAL:
- return CONTACT_METHODS_PROJECTION;
+ case MODE_PICK_CONTACT:
+ case MODE_PICK_OR_CREATE_CONTACT:{
+ return Contacts.CONTENT_URI;
+ }
+ 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 Phone.CONTENT_URI;
+ }
+ case MODE_LEGACY_PICK_PHONE: {
+ return Phones.CONTENT_URI;
+ }
+ case MODE_PICK_POSTAL: {
+ return 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(mQueryData));
+ } else if (mQueryMode == QUERY_MODE_TEL) {
+ return Uri.withAppendedPath(Phone.CONTENT_FILTER_URI, Uri.encode(mQueryData));
+ }
+ }
+ case MODE_QUERY: {
+ return getContactFilterUri(mQueryData);
+ }
+ case MODE_GROUP: {
+ return mGroupUri;
+ }
+ default: {
+ throw new IllegalStateException("Can't generate URI: Unsupported Mode.");
+ }
}
- return null;
+ }
+
+ /**
+ * 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);
+ 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);
+ 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: {
+ 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_QUERY:
+ case MODE_DEFAULT:
+ case MODE_INSERT_OR_EDIT_CONTACT:
+ case MODE_GROUP:
+ case MODE_PICK_CONTACT:
+ case MODE_PICK_OR_CREATE_CONTACT: {
+ return CONTACTS_SUMMARY_PROJECTION;
+ }
+ case MODE_LEGACY_PICK_PERSON:
+ case MODE_LEGACY_PICK_OR_CREATE_PERSON: {
+ return LEGACY_PEOPLE_PROJECTION ;
+ }
+ 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(long contactId, BitmapFactory.Options options) {
+ Cursor cursor = null;
+ Bitmap bm = null;
+ try {
+ Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
+ 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();
+ }
+ }
+ return bm;
+ }
+
+ /**
+ * Return the selection arguments for a default query based on
+ * {@link #mDisplayAll} and {@link #mDisplayOnlyPhones} flags.
+ */
+ private String getContactSelection() {
+ if (mDisplayOnlyPhones) {
+ return CLAUSE_ONLY_VISIBLE + " AND " + CLAUSE_ONLY_PHONES;
+ } else {
+ return CLAUSE_ONLY_VISIBLE;
+ }
+ }
+
+ private Uri getContactFilterUri(String filter) {
+ if (!TextUtils.isEmpty(filter)) {
+ return Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, Uri.encode(filter));
+ } else {
+ return Contacts.CONTENT_URI;
+ }
}
private Uri getPeopleFilterUri(String filter) {
@@ -1312,178 +1644,214 @@
}
}
+ 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 static String getSortOrder(String[] projectionType) {
- if (Locale.getDefault().equals(Locale.JAPAN) &&
- projectionType == CONTACTS_PROJECTION) {
+ /* if (Locale.getDefault().equals(Locale.JAPAN) &&
+ projectionType == AGGREGATES_PRIMARY_PHONE_PROJECTION) {
return SORT_STRING + " ASC";
} else {
return NAME_COLUMN + " COLLATE LOCALIZED ASC";
- }
+ } */
+
+ return NAME_COLUMN + " COLLATE LOCALIZED ASC";
}
-
+
void startQuery() {
mAdapter.setLoading(true);
-
+
// Cancel any pending queries
mQueryHandler.cancelOperation(QUERY_TOKEN);
+ mQueryHandler.setLoadingJoinSuggestions(false);
+
+ String[] projection = getProjectionForQuery();
+ 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:
mQueryHandler.startQuery(QUERY_TOKEN, null,
- mGroupUri, CONTACTS_PROJECTION, null, null,
- getSortOrder(CONTACTS_PROJECTION));
+ uri, projection, getContactSelection(), null,
+ getSortOrder(projection));
break;
- case MODE_ALL_CONTACTS:
+ case MODE_DEFAULT:
case MODE_PICK_CONTACT:
case MODE_PICK_OR_CREATE_CONTACT:
case MODE_INSERT_OR_EDIT_CONTACT:
- mQueryHandler.startQuery(QUERY_TOKEN, null, People.CONTENT_URI, CONTACTS_PROJECTION,
- null, null, getSortOrder(CONTACTS_PROJECTION));
+ mQueryHandler.startQuery(QUERY_TOKEN, null, uri,
+ projection, getContactSelection(), null,
+ getSortOrder(projection));
break;
- case MODE_WITH_PHONES:
- mQueryHandler.startQuery(QUERY_TOKEN, null, People.CONTENT_URI, CONTACTS_PROJECTION,
- People.PRIMARY_PHONE_ID + " IS NOT NULL", null,
- getSortOrder(CONTACTS_PROJECTION));
+ case MODE_LEGACY_PICK_PERSON:
+ case MODE_LEGACY_PICK_OR_CREATE_PERSON:
+ mQueryHandler.startQuery(QUERY_TOKEN, null, uri,
+ projection, null, null,
+ getSortOrder(projection));
break;
case MODE_QUERY: {
- mQuery = getIntent().getStringExtra(SearchManager.QUERY);
- mQueryHandler.startQuery(QUERY_TOKEN, null, getPeopleFilterUri(mQuery),
- CONTACTS_PROJECTION, null, null,
- getSortOrder(CONTACTS_PROJECTION));
+ mQueryHandler.startQuery(QUERY_TOKEN, null, uri,
+ projection, null, null,
+ getSortOrder(projection));
break;
}
-
+
case MODE_QUERY_PICK_TO_VIEW: {
- if (mQueryMode == QUERY_MODE_MAILTO) {
- // Find all contacts with the given search string as either
- // an E-mail or IM address.
- mQueryPersonIdIndex = SIMPLE_CONTACTS_PERSON_ID_INDEX;
- Uri uri = Uri.withAppendedPath(People.WITH_EMAIL_OR_IM_FILTER_URI,
- Uri.encode(mQueryData));
- mQueryHandler.startQuery(QUERY_TOKEN, null,
- uri, SIMPLE_CONTACTS_PROJECTION, null, null,
- getSortOrder(CONTACTS_PROJECTION));
-
- } else if (mQueryMode == QUERY_MODE_TEL) {
- mQueryPersonIdIndex = PHONES_PERSON_ID_INDEX;
- mQueryHandler.startQuery(QUERY_TOKEN, null,
- Uri.withAppendedPath(Phones.CONTENT_FILTER_URL, mQueryData),
- PHONES_PROJECTION, null, null,
- getSortOrder(PHONES_PROJECTION));
- }
+ mQueryHandler.startQuery(QUERY_TOKEN, null, uri, projection, null, null,
+ getSortOrder(projection));
break;
}
-
+
case MODE_STARRED:
- mQueryHandler.startQuery(QUERY_TOKEN, null, People.CONTENT_URI,
- CONTACTS_PROJECTION,
- People.STARRED + "=1", null, getSortOrder(CONTACTS_PROJECTION));
+ mQueryHandler.startQuery(QUERY_TOKEN, null, uri,
+ projection, Contacts.STARRED + "=1", null,
+ getSortOrder(projection));
break;
case MODE_FREQUENT:
- mQueryHandler.startQuery(QUERY_TOKEN, null,
- People.CONTENT_URI, CONTACTS_PROJECTION,
- People.TIMES_CONTACTED + " > 0", null,
- People.TIMES_CONTACTED + " DESC, " + getSortOrder(CONTACTS_PROJECTION));
+ 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.withAppendedPath(People.CONTENT_URI, "strequent"), STREQUENT_PROJECTION,
- null, null, null);
+ mQueryHandler.startQuery(QUERY_TOKEN, null, uri, projection, null, null, null);
break;
case MODE_PICK_PHONE:
- mQueryHandler.startQuery(QUERY_TOKEN, null, Phones.CONTENT_URI, PHONES_PROJECTION,
- null, null, getSortOrder(PHONES_PROJECTION));
+ case MODE_LEGACY_PICK_PHONE:
+ mQueryHandler.startQuery(QUERY_TOKEN, null, uri,
+ projection, null, null, getSortOrder(projection));
break;
case MODE_PICK_POSTAL:
- mQueryHandler.startQuery(QUERY_TOKEN, null, ContactMethods.CONTENT_URI,
- CONTACT_METHODS_PROJECTION,
- ContactMethods.KIND + "=" + Contacts.KIND_POSTAL, null,
- getSortOrder(CONTACT_METHODS_PROJECTION));
+ mQueryHandler.startQuery(QUERY_TOKEN, null, uri,
+ projection, null, null, getSortOrder(projection));
+ break;
+
+ case MODE_LEGACY_PICK_POSTAL:
+ mQueryHandler.startQuery(QUERY_TOKEN, null, uri,
+ projection,
+ ContactMethods.KIND + "=" + android.provider.Contacts.KIND_POSTAL, null,
+ getSortOrder(projection));
+ 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) {
final ContentResolver resolver = getContentResolver();
- switch (mMode) {
- case MODE_GROUP: {
- Uri uri;
- if (TextUtils.isEmpty(filter)) {
- uri = mGroupUri;
- } else {
- uri = Uri.withAppendedPath(mGroupFilterUri, Uri.encode(filter));
- }
- return resolver.query(uri, CONTACTS_PROJECTION, null, null,
- getSortOrder(CONTACTS_PROJECTION));
- }
+ String[] projection = getProjectionForQuery();
- case MODE_ALL_CONTACTS:
+ switch (mMode) {
+ case MODE_DEFAULT:
case MODE_PICK_CONTACT:
case MODE_PICK_OR_CREATE_CONTACT:
case MODE_INSERT_OR_EDIT_CONTACT: {
- return resolver.query(getPeopleFilterUri(filter), CONTACTS_PROJECTION, null, null,
- getSortOrder(CONTACTS_PROJECTION));
+ return resolver.query(getContactFilterUri(filter), projection,
+ getContactSelection(), null, getSortOrder(projection));
}
- case MODE_WITH_PHONES: {
- return resolver.query(getPeopleFilterUri(filter), CONTACTS_PROJECTION,
- People.PRIMARY_PHONE_ID + " IS NOT NULL", null,
- getSortOrder(CONTACTS_PROJECTION));
+ case MODE_LEGACY_PICK_PERSON:
+ case MODE_LEGACY_PICK_OR_CREATE_PERSON: {
+ return resolver.query(getPeopleFilterUri(filter), projection, null, null,
+ getSortOrder(projection));
}
case MODE_STARRED: {
- return resolver.query(getPeopleFilterUri(filter), CONTACTS_PROJECTION,
- People.STARRED + "=1", null, getSortOrder(CONTACTS_PROJECTION));
+ return resolver.query(getContactFilterUri(filter), projection,
+ Contacts.STARRED + "=1", null,
+ getSortOrder(projection));
}
case MODE_FREQUENT: {
- return resolver.query(getPeopleFilterUri(filter), CONTACTS_PROJECTION,
- People.TIMES_CONTACTED + " > 0", null,
- People.TIMES_CONTACTED + " DESC, " + getSortOrder(CONTACTS_PROJECTION));
-
+ 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(People.CONTENT_URI, "strequent/filter/"
- + Uri.encode(filter));
+ uri = Uri.withAppendedPath(Contacts.CONTENT_STREQUENT_FILTER_URI,
+ Uri.encode(filter));
} else {
- uri = Uri.withAppendedPath(People.CONTENT_URI, "strequent");
+ uri = Contacts.CONTENT_STREQUENT_URI;
}
- return resolver.query(uri, STREQUENT_PROJECTION, null, null, null);
+ return resolver.query(uri, projection, null, null, null);
}
case MODE_PICK_PHONE: {
- Uri uri;
+ Uri uri = getUriToQuery();
if (!TextUtils.isEmpty(filter)) {
- uri = Uri.withAppendedPath(Phones.CONTENT_URI, "filter_name/"
- + Uri.encode(filter));
- } else {
- uri = Phones.CONTENT_URI;
+ uri = Uri.withAppendedPath(Phone.CONTENT_FILTER_URI, Uri.encode(filter));
}
- return resolver.query(uri, PHONES_PROJECTION, null, null,
- getSortOrder(PHONES_PROJECTION));
+ return resolver.query(uri, projection, null, 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
@@ -1492,23 +1860,86 @@
ListView list = getListView();
if (list.hasFocus()) {
Cursor cursor = (Cursor) list.getSelectedItem();
- if (cursor != null) {
- long phoneId = cursor.getLong(PRIMARY_PHONE_ID_COLUMN_INDEX);
- if (phoneId == 0) {
- // There is no phone number.
- signalError();
- return false;
- }
- Uri uri = ContentUris.withAppendedId(Phones.CONTENT_URI, phoneId);
- Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED, uri);
- startActivity(intent);
- return true;
+ 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) {
+ 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;
}
return false;
}
+ 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},
+ Data.MIMETYPE + "=?", new String[] {Phone.CONTENT_ITEM_TYPE}, null);
+ if (c != null && c.moveToFirst()) {
+ return c;
+ }
+ return null;
+ }
+
/**
* Signal an error to the user.
*/
@@ -1525,98 +1956,53 @@
return (Cursor) listView.getAdapter().getItem(index);
}
- private void setGroupEntries(AlertDialog.Builder builder) {
- boolean syncEverything;
- // For now we only support a single account and the UI doesn't know what
- // the account name is, so we're using a global setting for SYNC_EVERYTHING.
- // Some day when we add multiple accounts to the UI this should use the per
- // account setting.
- String value = Contacts.Settings.getSetting(getContentResolver(), null,
- Contacts.Settings.SYNC_EVERYTHING);
- if (value == null) {
- // If nothing is set yet we default to syncing everything
- syncEverything = true;
- } else {
- syncEverything = !TextUtils.isEmpty(value) && !"0".equals(value);
- }
-
- Cursor cursor;
- if (!syncEverything) {
- cursor = getContentResolver().query(Groups.CONTENT_URI, GROUPS_PROJECTION,
- Groups.SHOULD_SYNC + " != 0", null, Groups.DEFAULT_SORT_ORDER);
- } else {
- cursor = getContentResolver().query(Groups.CONTENT_URI, GROUPS_PROJECTION,
- null, null, Groups.DEFAULT_SORT_ORDER);
- }
- try {
- ArrayList<CharSequence> groups = new ArrayList<CharSequence>();
- ArrayList<CharSequence> prefStrings = new ArrayList<CharSequence>();
-
- // Add All Contacts
- groups.add(DISPLAY_GROUP_INDEX_ALL_CONTACTS, getString(R.string.showAllGroups));
- prefStrings.add("");
-
- // Add Contacts with phones
- groups.add(DISPLAY_GROUP_INDEX_ALL_CONTACTS_WITH_PHONES,
- getString(R.string.groupNameWithPhones));
- prefStrings.add(GROUP_WITH_PHONES);
-
- int currentIndex = DISPLAY_GROUP_INDEX_ALL_CONTACTS;
- while (cursor.moveToNext()) {
- String systemId = cursor.getString(GROUPS_COLUMN_INDEX_SYSTEM_ID);
- String name = cursor.getString(GROUPS_COLUMN_INDEX_NAME);
- if (cursor.isNull(GROUPS_COLUMN_INDEX_SYSTEM_ID)
- && !Groups.GROUP_MY_CONTACTS.equals(systemId)) {
- // All groups that aren't My Contacts, since that one is localized on the phone
-
- // Localize the "Starred in Android" string which we get from the server side.
- if (Groups.GROUP_ANDROID_STARRED.equals(name)) {
- name = getString(R.string.starredInAndroid);
- }
- groups.add(name);
- if (name.equals(mDisplayInfo)) {
- currentIndex = groups.size() - 1;
- }
- } else {
- // The My Contacts group
- groups.add(DISPLAY_GROUP_INDEX_MY_CONTACTS,
- getString(R.string.groupNameMyContacts));
- if (mDisplayType == DISPLAY_TYPE_SYSTEM_GROUP
- && Groups.GROUP_MY_CONTACTS.equals(mDisplayInfo)) {
- currentIndex = DISPLAY_GROUP_INDEX_MY_CONTACTS;
- }
- mDisplayGroupsIncludesMyContacts = true;
- }
- }
- if (mMode == MODE_ALL_CONTACTS) {
- currentIndex = DISPLAY_GROUP_INDEX_ALL_CONTACTS;
- } else if (mMode == MODE_WITH_PHONES) {
- currentIndex = DISPLAY_GROUP_INDEX_ALL_CONTACTS_WITH_PHONES;
- }
- mDisplayGroups = groups.toArray(new CharSequence[groups.size()]);
- builder.setSingleChoiceItems(mDisplayGroups, currentIndex, this);
- mDisplayGroupOriginalSelection = currentIndex;
- } finally {
- cursor.close();
- }
- }
-
- private static final class QueryHandler extends AsyncQueryHandler {
- private final WeakReference<ContactsListActivity> mActivity;
+ 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.mQueryData),
+ CONTACTS_SUMMARY_PROJECTION,
+ Contacts._ID + " != " + activity.mQueryAggregateId
+ + " AND " + CLAUSE_ONLY_VISIBLE, null,
+ getSortOrder(CONTACTS_SUMMARY_PROJECTION));
+ return;
+ }
+
+ cursor = activity.getShowAllContactsLabelCursor(CONTACTS_SUMMARY_PROJECTION);
+ }
+
activity.mAdapter.setLoading(false);
- activity.getListView().clearTextFilter();
+ activity.getListView().clearTextFilter();
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);
@@ -1633,63 +2019,219 @@
}
final static class ContactListItemCache {
+ public View header;
+ public TextView headerText;
+ public View divider;
public TextView nameView;
+ public View callView;
+ public ImageView callButton;
public CharArrayBuffer nameBuffer = new CharArrayBuffer(128);
public TextView labelView;
public CharArrayBuffer labelBuffer = new CharArrayBuffer(128);
- public TextView numberView;
- public CharArrayBuffer numberBuffer = new CharArrayBuffer(128);
+ public TextView dataView;
+ public CharArrayBuffer dataBuffer = new CharArrayBuffer(128);
public ImageView presenceView;
- public ImageView photoView;
+ public QuickContactBadge photoView;
+ public ImageView nonQuickContactPhotoView;
}
- private final class ContactItemListAdapter extends ResourceCursorAdapter
- implements SectionIndexer {
+ final static class PhotoInfo {
+ public int position;
+ public long photoId;
+
+ public PhotoInfo(int position, long photoId) {
+ this.position = position;
+ this.photoId = photoId;
+ }
+ public QuickContactBadge photoView;
+ }
+
+ private final class ContactItemListAdapter extends ResourceCursorAdapter
+ implements SectionIndexer, OnScrollListener {
private SectionIndexer mIndexer;
private String mAlphabet;
private boolean mLoading = true;
private CharSequence mUnknownNameText;
- private CharSequence[] mLocalizedLabels;
private boolean mDisplayPhotos = false;
- private SparseArray<SoftReference<Bitmap>> mBitmapCache = null;
+ private boolean mDisplayCallButton = false;
+ private boolean mDisplayAdditionalData = true;
+ private HashMap<Long, SoftReference<Bitmap>> mBitmapCache = null;
+ private HashSet<ImageView> mItemsMissingImages = null;
private int mFrequentSeparatorPos = ListView.INVALID_POSITION;
+ private boolean mDisplaySectionHeaders = true;
+ private int[] mSectionPositions;
+ private Cursor mSuggestionsCursor;
+ private int mSuggestionsCursorCount;
+ private ImageFetchHandler mHandler;
+ private ImageDbFetcher mImageFetcher;
+ private static final int FETCH_IMAGE_MSG = 1;
public ContactItemListAdapter(Context context) {
super(context, R.layout.contacts_list_item, null, false);
-
+
+ mHandler = new ImageFetchHandler();
mAlphabet = context.getString(com.android.internal.R.string.fast_scroll_alphabet);
-
+
mUnknownNameText = context.getText(android.R.string.unknownName);
switch (mMode) {
+ case MODE_LEGACY_PICK_POSTAL:
case MODE_PICK_POSTAL:
- mLocalizedLabels = EditContactActivity.getLabelsForKind(mContext,
- Contacts.KIND_POSTAL);
+ mDisplaySectionHeaders = false;
+ break;
+ case MODE_LEGACY_PICK_PHONE:
+ case MODE_PICK_PHONE:
+ mDisplaySectionHeaders = false;
break;
default:
- mLocalizedLabels = EditContactActivity.getLabelsForKind(mContext,
- Contacts.KIND_PHONE);
break;
}
-
+
+ // 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 (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;
setViewResource(R.layout.contacts_list_item_photo);
- mBitmapCache = new SparseArray<SoftReference<Bitmap>>();
+ mBitmapCache = new HashMap<Long, SoftReference<Bitmap>>();
+ mItemsMissingImages = new HashSet<ImageView>();
+ }
+
+ if (mMode == MODE_STREQUENT || mMode == MODE_FREQUENT) {
+ mDisplaySectionHeaders = false;
}
}
- private SectionIndexer getNewIndexer(Cursor cursor) {
- if (Locale.getDefault().getLanguage().equals(Locale.JAPAN.getLanguage())) {
- return new JapaneseContactListIndexer(cursor, SORT_STRING_INDEX);
- } else {
- return new AlphabetIndexer(cursor, NAME_COLUMN_INDEX, mAlphabet);
+ private class ImageFetchHandler extends Handler {
+
+ @Override
+ public void handleMessage(Message message) {
+ if (ContactsListActivity.this.isFinishing()) {
+ return;
+ }
+ switch(message.what) {
+ case FETCH_IMAGE_MSG: {
+ final ImageView imageView = (ImageView) message.obj;
+ if (imageView == null) {
+ break;
+ }
+
+ final PhotoInfo info = (PhotoInfo)imageView.getTag();
+ if (info == null) {
+ break;
+ }
+
+ final long photoId = info.photoId;
+ if (photoId == 0) {
+ break;
+ }
+
+ SoftReference<Bitmap> photoRef = mBitmapCache.get(photoId);
+ if (photoRef == null) {
+ break;
+ }
+ Bitmap photo = photoRef.get();
+ if (photo == null) {
+ mBitmapCache.remove(photoId);
+ break;
+ }
+
+ // Make sure the photoId on this image view has not changed
+ // while we were loading the image.
+ synchronized (imageView) {
+ final PhotoInfo updatedInfo = (PhotoInfo)imageView.getTag();
+ long currentPhotoId = updatedInfo.photoId;
+ if (currentPhotoId == photoId) {
+ imageView.setImageBitmap(photo);
+ mItemsMissingImages.remove(imageView);
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ public void clearImageFecthing() {
+ removeMessages(FETCH_IMAGE_MSG);
}
}
-
+
+ private class ImageDbFetcher implements Runnable {
+ long mPhotoId;
+ private ImageView mImageView;
+
+ public ImageDbFetcher(long photoId, ImageView imageView) {
+ this.mPhotoId = photoId;
+ this.mImageView = imageView;
+ }
+
+ public void run() {
+ if (ContactsListActivity.this.isFinishing()) {
+ return;
+ }
+
+ if (Thread.currentThread().interrupted()) {
+ // shutdown has been called.
+ return;
+ }
+ Bitmap photo = null;
+ try {
+ photo = ContactsUtils.loadContactPhoto(mContext, mPhotoId, null);
+ } catch (OutOfMemoryError e) {
+ // Not enough memory for the photo, do nothing.
+ }
+
+ if (photo == null) {
+ return;
+ }
+
+ mBitmapCache.put(mPhotoId, new SoftReference<Bitmap>(photo));
+
+ if (Thread.currentThread().interrupted()) {
+ // shutdown has been called.
+ return;
+ }
+
+ // Update must happen on UI thread
+ Message msg = new Message();
+ msg.what = FETCH_IMAGE_MSG;
+ msg.obj = mImageView;
+ mHandler.sendMessage(msg);
+ }
+ }
+
+ public void setSuggestionsCursor(Cursor cursor) {
+ if (mSuggestionsCursor != null) {
+ mSuggestionsCursor.close();
+ }
+ mSuggestionsCursor = cursor;
+ mSuggestionsCursorCount = cursor == null ? 0 : cursor.getCount();
+ }
+
+ private SectionIndexer getNewIndexer(Cursor cursor) {
+ /* if (Locale.getDefault().getLanguage().equals(Locale.JAPAN.getLanguage())) {
+ return new JapaneseContactListIndexer(cursor, SORT_STRING_INDEX);
+ } else { */
+ return new AlphabetIndexer(cursor, SUMMARY_NAME_COLUMN_INDEX, mAlphabet);
+ /* } */
+ }
+
/**
* 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.
+ * block the UI thread for a long time.
*/
@Override
protected void onContentChanged() {
@@ -1703,7 +2245,7 @@
startQuery();
}
}
-
+
public void setLoading(boolean loading) {
mLoading = loading;
}
@@ -1726,7 +2268,13 @@
@Override
public int getItemViewType(int position) {
- if (position == mFrequentSeparatorPos) {
+ if (position == 0 && (mMode & MODE_MASK_SHOW_NUMBER_OF_CONTACTS) != 0) {
+ return IGNORE_ITEM_VIEW_TYPE;
+ }
+ if (isShowAllContactsItemPosition(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;
}
@@ -1740,39 +2288,126 @@
"this should only be called when the cursor is valid");
}
- // Handle the separator specially
- if (position == mFrequentSeparatorPos) {
+ // handle the total contacts item
+ if (position == 0 && (mMode & MODE_MASK_SHOW_NUMBER_OF_CONTACTS) != 0) {
+ return getTotalContactCountView(parent);
+ }
+
+ if (isShowAllContactsItemPosition(position)) {
LayoutInflater inflater =
- (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ return inflater.inflate(R.layout.contacts_list_show_all_item, parent, false);
+ }
+
+ // Handle the separator specially
+ int separatorId = getSeparatorId(position);
+ if (separatorId != 0) {
+ LayoutInflater inflater =
+ (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
TextView view = (TextView) inflater.inflate(R.layout.list_separator, parent, false);
- view.setText(R.string.favoritesFrquentSeparator);
+ view.setText(separatorId);
return view;
}
- if (!mCursor.moveToPosition(getRealPosition(position))) {
+ 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);
}
-
+
View v;
if (convertView == null) {
- v = newView(mContext, mCursor, parent);
+ v = newView(mContext, cursor, parent);
} else {
v = convertView;
}
- bindView(v, mContext, mCursor);
+ bindView(v, mContext, cursor);
+ bindSectionHeader(v, realPosition, mDisplaySectionHeaders && !showingSuggestion);
return v;
}
+ private View getTotalContactCountView(ViewGroup parent) {
+ final LayoutInflater inflater = getLayoutInflater();
+ TextView totalContacts = (TextView) inflater.inflate(R.layout.total_contacts,
+ parent, false);
+
+ String text;
+ int count = getRealCount();
+
+ if (mMode == MODE_QUERY || !TextUtils.isEmpty(getListView().getTextFilter())) {
+ text = getQuantityText(count, R.string.listFoundAllContactsZero,
+ R.plurals.listFoundAllContacts);
+ } 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 totalContacts;
+ }
+
+ // TODO: fix PluralRules to handle zero correctly and use Resources.getQuantityText directly
+ private 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);
+ }
+ }
+
+ private boolean isShowAllContactsItemPosition(int position) {
+ return mMode == MODE_JOIN_CONTACT && mJoinModeShowAllContacts
+ && mSuggestionsCursorCount != 0 && position == mSuggestionsCursorCount + 2;
+ }
+
+ 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 View view = super.newView(context, cursor, parent);
final ContactListItemCache cache = new ContactListItemCache();
+ cache.header = view.findViewById(R.id.header);
+ cache.headerText = (TextView)view.findViewById(R.id.header_text);
+ cache.divider = view.findViewById(R.id.list_divider);
cache.nameView = (TextView) view.findViewById(R.id.name);
+ cache.callView = view.findViewById(R.id.call_view);
+ cache.callButton = (ImageView) view.findViewById(R.id.call_button);
+ if (cache.callButton != null) {
+ cache.callButton.setOnClickListener(ContactsListActivity.this);
+ }
cache.labelView = (TextView) view.findViewById(R.id.label);
- cache.numberView = (TextView) view.findViewById(R.id.number);
+ cache.dataView = (TextView) view.findViewById(R.id.data);
cache.presenceView = (ImageView) view.findViewById(R.id.presence);
- cache.photoView = (ImageView) view.findViewById(R.id.photo);
+ cache.photoView = (QuickContactBadge) view.findViewById(R.id.photo);
+ cache.nonQuickContactPhotoView = (ImageView) view.findViewById(R.id.noQuickContactPhoto);
view.setTag(cache);
return view;
@@ -1781,67 +2416,130 @@
@Override
public void bindView(View view, Context context, Cursor cursor) {
final ContactListItemCache cache = (ContactListItemCache) view.getTag();
-
- // Set the name
- cursor.copyStringToBuffer(NAME_COLUMN_INDEX, cache.nameBuffer);
+
+ TextView dataView = cache.dataView;
+ TextView labelView = cache.labelView;
+ int typeColumnIndex;
+ int dataColumnIndex;
+ int labelColumnIndex;
+ int defaultType;
+ int nameColumnIndex;
+ boolean displayAdditionalData = mDisplayAdditionalData;
+ switch(mMode) {
+ case MODE_PICK_PHONE:
+ case MODE_LEGACY_PICK_PHONE: {
+ nameColumnIndex = PHONE_DISPLAY_NAME_COLUMN_INDEX;
+ 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;
+ dataColumnIndex = POSTAL_ADDRESS_COLUMN_INDEX;
+ typeColumnIndex = POSTAL_TYPE_COLUMN_INDEX;
+ labelColumnIndex = POSTAL_LABEL_COLUMN_INDEX;
+ defaultType = StructuredPostal.TYPE_HOME;
+ break;
+ }
+ default: {
+ nameColumnIndex = SUMMARY_NAME_COLUMN_INDEX;
+ dataColumnIndex = -1;
+ typeColumnIndex = -1;
+ labelColumnIndex = -1;
+ defaultType = Phone.TYPE_HOME;
+ displayAdditionalData = false;
+ }
+ }
+
+ // Set the name
+ cursor.copyStringToBuffer(nameColumnIndex, cache.nameBuffer);
int size = cache.nameBuffer.sizeCopied;
if (size != 0) {
cache.nameView.setText(cache.nameBuffer.data, 0, size);
} else {
cache.nameView.setText(mUnknownNameText);
}
-
- // Bail out early if using 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 (mQueryMode != QUERY_MODE_NONE) {
- cache.numberView.setVisibility(View.GONE);
- cache.labelView.setVisibility(View.GONE);
- cache.presenceView.setVisibility(View.GONE);
- return;
- }
-
- // Set the phone number
- TextView numberView = cache.numberView;
- TextView labelView = cache.labelView;
- cursor.copyStringToBuffer(NUMBER_COLUMN_INDEX, cache.numberBuffer);
- size = cache.numberBuffer.sizeCopied;
- if (size != 0) {
- numberView.setText(cache.numberBuffer.data, 0, size);
- numberView.setVisibility(View.VISIBLE);
- labelView.setVisibility(View.VISIBLE);
+
+ // Make the call button visible if requested.
+ if (mDisplayCallButton) {
+ int pos = cursor.getPosition();
+ cache.callView.setVisibility(View.VISIBLE);
+ cache.callButton.setTag(pos);
} else {
- numberView.setVisibility(View.GONE);
- labelView.setVisibility(View.GONE);
+ cache.callView.setVisibility(View.GONE);
}
- // Set the label
- if (!cursor.isNull(TYPE_COLUMN_INDEX)) {
- int type = cursor.getInt(TYPE_COLUMN_INDEX);
+ // Set the photo, if requested
+ if (mDisplayPhotos) {
+ boolean useQuickContact = (mMode & MODE_MASK_DISABLE_QUIKCCONTACT) == 0;
- if (type != People.Phones.TYPE_CUSTOM) {
- try {
- labelView.setText(mLocalizedLabels[type - 1]);
- } catch (ArrayIndexOutOfBoundsException e) {
- labelView.setText(mLocalizedLabels[People.Phones.TYPE_HOME - 1]);
- }
- } else {
- cursor.copyStringToBuffer(LABEL_COLUMN_INDEX, cache.labelBuffer);
- // Don't check size, if it's zero just don't show anything
- labelView.setText(cache.labelBuffer.data, 0, cache.labelBuffer.sizeCopied);
+ long photoId = 0;
+ if (!cursor.isNull(SUMMARY_PHOTO_ID_COLUMN_INDEX)) {
+ photoId = cursor.getLong(SUMMARY_PHOTO_ID_COLUMN_INDEX);
}
- } else {
- // There is no label, hide the the view
- labelView.setVisibility(View.GONE);
+
+ ImageView viewToUse;
+ if (useQuickContact) {
+ viewToUse = cache.photoView;
+ // Build soft lookup reference
+ final long contactId = cursor.getLong(SUMMARY_ID_COLUMN_INDEX);
+ final String lookupKey = cursor.getString(SUMMARY_LOOKUP_KEY);
+ cache.photoView.assignContactUri(Contacts.getLookupUri(contactId, lookupKey));
+ cache.photoView.setVisibility(View.VISIBLE);
+ cache.nonQuickContactPhotoView.setVisibility(View.INVISIBLE);
+ } else {
+ viewToUse = cache.nonQuickContactPhotoView;
+ cache.photoView.setVisibility(View.INVISIBLE);
+ cache.nonQuickContactPhotoView.setVisibility(View.VISIBLE);
+ }
+
+
+ final int position = cursor.getPosition();
+ viewToUse.setTag(new PhotoInfo(position, photoId));
+
+ if (photoId == 0) {
+ viewToUse.setImageResource(R.drawable.ic_contact_list_picture);
+ } else {
+
+ Bitmap photo = null;
+
+ // Look for the cached bitmap
+ SoftReference<Bitmap> ref = mBitmapCache.get(photoId);
+ if (ref != null) {
+ photo = ref.get();
+ if (photo == null) {
+ mBitmapCache.remove(photoId);
+ }
+ }
+
+ // Bind the photo, or use the fallback no photo resource
+ if (photo != null) {
+ viewToUse.setImageBitmap(photo);
+ } else {
+ // Cache miss
+ viewToUse.setImageResource(R.drawable.ic_contact_list_picture);
+
+ // Add it to a set of images that are populated asynchronously.
+ mItemsMissingImages.add(viewToUse);
+
+ if (mScrollState != OnScrollListener.SCROLL_STATE_FLING) {
+
+ // Scrolling is idle or slow, go get the image right now.
+ sendFetchImageMessage(viewToUse);
+ }
+ }
+ }
}
- // Set the proper icon (star or presence or nothing)
ImageView presenceView = cache.presenceView;
if ((mMode & MODE_MASK_NO_PRESENCE) == 0) {
+ // Set the proper icon (star or presence or nothing)
int serverStatus;
- if (!cursor.isNull(SERVER_STATUS_COLUMN_INDEX)) {
- serverStatus = cursor.getInt(SERVER_STATUS_COLUMN_INDEX);
+ if (!cursor.isNull(SUMMARY_PRESENCE_STATUS_COLUMN_INDEX)) {
+ serverStatus = cursor.getInt(SUMMARY_PRESENCE_STATUS_COLUMN_INDEX);
presenceView.setImageResource(
Presence.getPresenceIconResourceId(serverStatus));
presenceView.setVisibility(View.VISIBLE);
@@ -1852,49 +2550,81 @@
presenceView.setVisibility(View.GONE);
}
- // Set the photo, if requested
- if (mDisplayPhotos) {
- Bitmap photo = null;
+ if (!displayAdditionalData) {
+ cache.dataView.setVisibility(View.GONE);
+ cache.labelView.setVisibility(View.GONE);
+ return;
+ }
- // Look for the cached bitmap
- int pos = cursor.getPosition();
- SoftReference<Bitmap> ref = mBitmapCache.get(pos);
- if (ref != null) {
- photo = ref.get();
- }
+ // Set the data.
+ cursor.copyStringToBuffer(dataColumnIndex, cache.dataBuffer);
- if (photo == null) {
- // Bitmap cache miss, decode it from the cursor
- if (!cursor.isNull(PHOTO_COLUMN_INDEX)) {
- try {
- byte[] photoData = cursor.getBlob(PHOTO_COLUMN_INDEX);
- photo = BitmapFactory.decodeByteArray(photoData, 0,
- photoData.length);
- mBitmapCache.put(pos, new SoftReference<Bitmap>(photo));
- } catch (OutOfMemoryError e) {
- // Not enough memory for the photo, use the default one instead
- photo = null;
- }
- }
- }
+ size = cache.dataBuffer.sizeCopied;
+ if (size != 0) {
+ dataView.setText(cache.dataBuffer.data, 0, size);
+ dataView.setVisibility(View.VISIBLE);
+ } else {
+ dataView.setVisibility(View.GONE);
+ }
- // Bind the photo, or use the fallback no photo resource
- if (photo != null) {
- cache.photoView.setImageBitmap(photo);
+ // Set the label.
+ if (!cursor.isNull(typeColumnIndex)) {
+ labelView.setVisibility(View.VISIBLE);
+
+ final int type = cursor.getInt(typeColumnIndex);
+ final String label = cursor.getString(labelColumnIndex);
+
+ if (mMode == MODE_LEGACY_PICK_POSTAL || mMode == MODE_PICK_POSTAL) {
+ labelView.setText(StructuredPostal.getTypeLabel(context.getResources(), type,
+ label));
} else {
- cache.photoView.setImageResource(R.drawable.ic_contact_list_picture);
+ labelView.setText(Phone.getTypeLabel(context.getResources(), type, label));
+ }
+ } else {
+ // There is no label, hide the the view
+ labelView.setVisibility(View.GONE);
+ }
+ }
+
+ private void bindSectionHeader(View view, int position, boolean displaySectionHeaders) {
+ final ContactListItemCache cache = (ContactListItemCache) view.getTag();
+ if (!displaySectionHeaders) {
+ cache.header.setVisibility(View.GONE);
+ cache.divider.setVisibility(View.VISIBLE);
+ } else {
+ final int section = getSectionForPosition(position);
+ if (getPositionForSection(section) == position) {
+ String title = mIndexer.getSections()[section].toString().trim();
+ if (!TextUtils.isEmpty(title)) {
+ cache.headerText.setText(title);
+ cache.header.setVisibility(View.VISIBLE);
+ } else {
+ cache.header.setVisibility(View.GONE);
+ }
+ } else {
+ cache.header.setVisibility(View.GONE);
+ }
+
+ // move the divider for the last item in a section
+ if (getPositionForSection(section + 1) - 1 == position) {
+ cache.divider.setVisibility(View.GONE);
+ } else {
+ cache.divider.setVisibility(View.VISIBLE);
}
}
}
@Override
public void changeCursor(Cursor cursor) {
+
// Get the split between starred and frequent items, if the mode is strequent
mFrequentSeparatorPos = ListView.INVALID_POSITION;
- if (cursor != null && cursor.getCount() > 0 && mMode == MODE_STREQUENT) {
+ 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(STARRED_COLUMN_INDEX);
+ int starred = cursor.getInt(SUMMARY_STARRED_COLUMN_INDEX);
if (starred == 0) {
if (i > 0) {
// Only add the separator when there are starred items present
@@ -1906,16 +2636,10 @@
}
super.changeCursor(cursor);
-
// Update the indexer for the fast scroll widget
updateIndexer(cursor);
-
- // Clear the photo bitmap cache, if there is one
- if (mBitmapCache != null) {
- mBitmapCache.clear();
- }
}
-
+
private void updateIndexer(Cursor cursor) {
if (mIndexer == null) {
mIndexer = getNewIndexer(cursor);
@@ -1934,8 +2658,16 @@
}
}
}
+
+ int sectionCount = mIndexer.getSections().length;
+ if (mSectionPositions == null || mSectionPositions.length != sectionCount) {
+ mSectionPositions = new int[sectionCount];
+ }
+ for (int i = 0; i < sectionCount; i++) {
+ mSectionPositions[i] = ListView.INVALID_POSITION;
+ }
}
-
+
/**
* Run the query on a helper thread. Beware that this code does not run
* on the main UI thread!
@@ -1944,18 +2676,22 @@
public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
return doFilter(constraint.toString());
}
-
+
public Object [] getSections() {
- if (mMode == MODE_STREQUENT) {
+ if (mMode == MODE_STARRED) {
return new String[] { " " };
} else {
return mIndexer.getSections();
- }
+ }
}
public int getPositionForSection(int sectionIndex) {
- if (mMode == MODE_STREQUENT) {
- return 0;
+ if (mMode == MODE_STARRED) {
+ return -1;
+ }
+
+ if (sectionIndex < 0 || sectionIndex >= mSectionPositions.length) {
+ return -1;
}
if (mIndexer == null) {
@@ -1967,37 +2703,113 @@
mIndexer = getNewIndexer(cursor);
}
- return mIndexer.getPositionForSection(sectionIndex);
+ int position = mSectionPositions[sectionIndex];
+ if (position == ListView.INVALID_POSITION) {
+ position = mSectionPositions[sectionIndex] =
+ mIndexer.getPositionForSection(sectionIndex);
+ }
+
+ return position;
}
public int getSectionForPosition(int position) {
- // Note: JapaneseContactListIndexer depends on the fact
- // this method always returns 0. If you change this,
- // please care it too.
- return 0;
+ // The current implementations of SectionIndexers (specifically the Japanese indexer)
+ // only work in one direction: given a section they can calculate the position.
+ // Here we are using that existing functionality to do the reverse mapping. We are
+ // performing binary search in the mSectionPositions array, which itself is populated
+ // lazily using the "forward" mapping supported by the indexer.
+
+ int start = 0;
+ int end = mSectionPositions.length;
+ while (start != end) {
+
+ // We are making the binary search slightly asymmetrical, because the
+ // user is more likely to be scrolling the list from the top down.
+ int pivot = start + (end - start) / 4;
+
+ int value = getPositionForSection(pivot);
+ if (value <= position) {
+ start = pivot + 1;
+ } else {
+ end = pivot;
+ }
+ }
+
+ // The variable "start" cannot be 0, as long as the indexer is implemented properly
+ // and actually maps position = 0 to section = 0
+ return start - 1;
}
@Override
public boolean areAllItemsEnabled() {
- return mMode != MODE_STREQUENT;
+ return mMode != MODE_STARRED
+ && (mMode & MODE_MASK_SHOW_NUMBER_OF_CONTACTS) == 0
+ && mSuggestionsCursorCount == 0;
}
@Override
public boolean isEnabled(int position) {
+ if ((mMode & MODE_MASK_SHOW_NUMBER_OF_CONTACTS) != 0) {
+ if (position == 0) {
+ return false;
+ }
+ position--;
+ }
+
+ if (mSuggestionsCursorCount > 0) {
+ return position != 0 && position != mSuggestionsCursorCount + 1;
+ }
return position != mFrequentSeparatorPos;
}
@Override
public int getCount() {
- if (mFrequentSeparatorPos != ListView.INVALID_POSITION) {
- return super.getCount() + 1;
+ if (!mDataValid) {
+ return 0;
+ }
+ int superCount = super.getCount();
+ if ((mMode & MODE_MASK_SHOW_NUMBER_OF_CONTACTS) != 0 && 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 (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 super.getCount();
+ 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 (mFrequentSeparatorPos == ListView.INVALID_POSITION) {
+ if ((mMode & MODE_MASK_SHOW_NUMBER_OF_CONTACTS) != 0) {
+ pos--;
+ }
+ 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) {
@@ -2007,17 +2819,89 @@
// After the separator, remove 1 from the pos to get the real underlying pos
return pos - 1;
}
-
}
-
+
@Override
public Object getItem(int pos) {
- return super.getItem(getRealPosition(pos));
+ if (mSuggestionsCursorCount != 0 && pos <= mSuggestionsCursorCount) {
+ mSuggestionsCursor.moveToPosition(getRealPosition(pos));
+ return mSuggestionsCursor;
+ } else {
+ return super.getItem(getRealPosition(pos));
+ }
}
-
+
@Override
public long getItemId(int pos) {
- return super.getItemId(getRealPosition(pos));
+ if (mSuggestionsCursorCount != 0 && pos < mSuggestionsCursorCount + 2) {
+ if (mSuggestionsCursor.moveToPosition(pos - 1)) {
+ return mSuggestionsCursor.getLong(mRowIDColumn);
+ } else {
+ return 0;
+ }
+ }
+ return super.getItemId(getRealPosition(pos));
+ }
+
+ public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
+ int totalItemCount) {
+ // no op
+ }
+
+ public void onScrollStateChanged(AbsListView view, int scrollState) {
+ mScrollState = scrollState;
+ if (scrollState == OnScrollListener.SCROLL_STATE_FLING) {
+ // If we are in a fling, stop loading images.
+ clearImageFetching();
+ } else if (mDisplayPhotos) {
+ processMissingImageItems(view);
+ }
+ }
+
+ private void processMissingImageItems(AbsListView view) {
+ for (ImageView iv : mItemsMissingImages) {
+ sendFetchImageMessage(iv);
+ }
+ }
+
+ private void sendFetchImageMessage(ImageView view) {
+ final PhotoInfo info = (PhotoInfo) view.getTag();
+ if (info == null) {
+ return;
+ }
+ final long photoId = info.photoId;
+ if (photoId == 0) {
+ return;
+ }
+ mImageFetcher = new ImageDbFetcher(photoId, view);
+ synchronized (ContactsListActivity.this) {
+ // can't sync on sImageFetchThreadPool.
+ if (sImageFetchThreadPool == null) {
+ // Don't use more than 3 threads at a time to update. The thread pool will be
+ // shared by all contact items.
+ sImageFetchThreadPool = Executors.newFixedThreadPool(3);
+ }
+ sImageFetchThreadPool.execute(mImageFetcher);
+ }
+ }
+
+
+ /**
+ * Stop the image fetching for ALL contacts, if one is in progress we'll
+ * not query the database.
+ *
+ * TODO: move this method to ContactsListActivity, it does not apply to the current
+ * contact.
+ */
+ public void clearImageFetching() {
+ synchronized (ContactsListActivity.this) {
+ if (sImageFetchThreadPool != null) {
+ sImageFetchThreadPool.shutdownNow();
+ sImageFetchThreadPool = null;
+ }
+ }
+
+ mHandler.clearImageFecthing();
}
}
}
diff --git a/src/com/android/contacts/ContactsLiveFolders.java b/src/com/android/contacts/ContactsLiveFolders.java
index d437e80..64be0e2 100644
--- a/src/com/android/contacts/ContactsLiveFolders.java
+++ b/src/com/android/contacts/ContactsLiveFolders.java
@@ -16,13 +16,13 @@
package com.android.contacts;
-import android.content.Intent;
-import android.content.Context;
-import android.net.Uri;
import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
import android.os.Bundle;
+import android.provider.ContactsContract.Contacts;
import android.provider.LiveFolders;
-import android.provider.Contacts;
public class ContactsLiveFolders {
public static class StarredContacts extends Activity {
@@ -100,8 +100,8 @@
final Intent intent = new Intent();
intent.setData(uri);
- intent.putExtra(LiveFolders.EXTRA_LIVE_FOLDER_BASE_INTENT, new Intent(Intent.ACTION_VIEW,
- Contacts.People.CONTENT_URI));
+ intent.putExtra(LiveFolders.EXTRA_LIVE_FOLDER_BASE_INTENT,
+ new Intent(Intent.ACTION_VIEW, Contacts.CONTENT_URI));
intent.putExtra(LiveFolders.EXTRA_LIVE_FOLDER_NAME, name);
intent.putExtra(LiveFolders.EXTRA_LIVE_FOLDER_ICON,
Intent.ShortcutIconResource.fromContext(context, icon));
diff --git a/src/com/android/contacts/ContactsUtils.java b/src/com/android/contacts/ContactsUtils.java
new file mode 100644
index 0000000..1e3b8ad
--- /dev/null
+++ b/src/com/android/contacts/ContactsUtils.java
@@ -0,0 +1,427 @@
+/*
+ * 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.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.RawContacts;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.Im;
+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.Im.ProviderNames;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.contacts.model.ContactsSource;
+import com.android.contacts.util.Constants;
+
+import java.util.ArrayList;
+
+public class ContactsUtils {
+
+ private static final String TAG = "ContactsUtils";
+ /**
+ * Build the display title for the {@link Data#CONTENT_URI} entry in the
+ * provided cursor, assuming the given mimeType.
+ */
+ public static final CharSequence getDisplayLabel(Context context,
+ String mimeType, Cursor cursor) {
+ // Try finding the type and label for this mimetype
+ int colType;
+ int colLabel;
+
+ if (Phone.CONTENT_ITEM_TYPE.equals(mimeType)
+ || Constants.MIME_SMS_ADDRESS.equals(mimeType)) {
+ // Reset to phone mimetype so we generate a label for SMS case
+ mimeType = Phone.CONTENT_ITEM_TYPE;
+ colType = cursor.getColumnIndex(Phone.TYPE);
+ colLabel = cursor.getColumnIndex(Phone.LABEL);
+ } else if (Email.CONTENT_ITEM_TYPE.equals(mimeType)) {
+ colType = cursor.getColumnIndex(Email.TYPE);
+ colLabel = cursor.getColumnIndex(Email.LABEL);
+ } else if (StructuredPostal.CONTENT_ITEM_TYPE.equals(mimeType)) {
+ colType = cursor.getColumnIndex(StructuredPostal.TYPE);
+ colLabel = cursor.getColumnIndex(StructuredPostal.LABEL);
+ } else if (Organization.CONTENT_ITEM_TYPE.equals(mimeType)) {
+ colType = cursor.getColumnIndex(Organization.TYPE);
+ colLabel = cursor.getColumnIndex(Organization.LABEL);
+ } else {
+ return null;
+ }
+
+ final int type = cursor.getInt(colType);
+ final CharSequence label = cursor.getString(colLabel);
+
+ return getDisplayLabel(context, mimeType, type, label);
+ }
+
+ public static final CharSequence getDisplayLabel(Context context, String mimetype, int type,
+ CharSequence label) {
+ CharSequence display = "";
+ final int customType;
+ final int defaultType;
+ final int arrayResId;
+
+ if (Phone.CONTENT_ITEM_TYPE.equals(mimetype)) {
+ defaultType = Phone.TYPE_HOME;
+ customType = Phone.TYPE_CUSTOM;
+ arrayResId = com.android.internal.R.array.phoneTypes;
+ } else if (Email.CONTENT_ITEM_TYPE.equals(mimetype)) {
+ defaultType = Email.TYPE_HOME;
+ customType = Email.TYPE_CUSTOM;
+ arrayResId = com.android.internal.R.array.emailAddressTypes;
+ } else if (StructuredPostal.CONTENT_ITEM_TYPE.equals(mimetype)) {
+ defaultType = StructuredPostal.TYPE_HOME;
+ customType = StructuredPostal.TYPE_CUSTOM;
+ arrayResId = com.android.internal.R.array.postalAddressTypes;
+ } else if (Organization.CONTENT_ITEM_TYPE.equals(mimetype)) {
+ defaultType = Organization.TYPE_WORK;
+ customType = Organization.TYPE_CUSTOM;
+ arrayResId = com.android.internal.R.array.organizationTypes;
+ } else {
+ // Can't return display label for given mimetype.
+ return display;
+ }
+
+ if (type != customType) {
+ CharSequence[] labels = context.getResources().getTextArray(arrayResId);
+ try {
+ display = labels[type - 1];
+ } catch (ArrayIndexOutOfBoundsException e) {
+ display = labels[defaultType - 1];
+ }
+ } else {
+ if (!TextUtils.isEmpty(label)) {
+ display = label;
+ }
+ }
+ return display;
+ }
+
+ /**
+ * Opens an InputStream for the person's photo and returns the photo as a Bitmap.
+ * If the person's photo isn't present returns null.
+ *
+ * @param aggCursor the Cursor pointing to the data record containing the photo.
+ * @param bitmapColumnIndex the column index where the photo Uri is stored.
+ * @param options the decoding options, can be set to null
+ * @return the photo Bitmap
+ */
+ public static Bitmap loadContactPhoto(Cursor cursor, int bitmapColumnIndex,
+ BitmapFactory.Options options) {
+ if (cursor == null) {
+ return null;
+ }
+
+ byte[] data = cursor.getBlob(bitmapColumnIndex);
+ return BitmapFactory.decodeByteArray(data, 0, data.length, options);
+ }
+
+ /**
+ * Loads a placeholder photo.
+ *
+ * @param placeholderImageResource the resource to use for the placeholder image
+ * @param context the Context
+ * @param options the decoding options, can be set to null
+ * @return the placeholder Bitmap.
+ */
+ public static Bitmap loadPlaceholderPhoto(int placeholderImageResource, Context context,
+ BitmapFactory.Options options) {
+ if (placeholderImageResource == 0) {
+ return null;
+ }
+ return BitmapFactory.decodeResource(context.getResources(),
+ placeholderImageResource, options);
+ }
+
+ public static Bitmap loadContactPhoto(Context context, long photoId,
+ BitmapFactory.Options options) {
+ Cursor photoCursor = null;
+ Bitmap photoBm = null;
+
+ try {
+ photoCursor = context.getContentResolver().query(
+ ContentUris.withAppendedId(Data.CONTENT_URI, photoId),
+ new String[] { Photo.PHOTO },
+ null, null, null);
+
+ if (photoCursor.moveToFirst() && !photoCursor.isNull(0)) {
+ byte[] photoData = photoCursor.getBlob(0);
+ photoBm = BitmapFactory.decodeByteArray(photoData, 0,
+ photoData.length, options);
+ }
+ } finally {
+ if (photoCursor != null) {
+ photoCursor.close();
+ }
+ }
+
+ return photoBm;
+ }
+
+ /**
+ * This looks up the provider name defined in
+ * {@link android.provider.Im.ProviderNames} from the predefined IM protocol id.
+ * This is used for interacting with the IM application.
+ *
+ * @param protocol the protocol ID
+ * @return the provider name the IM app uses for the given protocol, or null if no
+ * provider is defined for the given protocol
+ * @hide
+ */
+ public static String lookupProviderNameFromId(int protocol) {
+ switch (protocol) {
+ case Im.PROTOCOL_GOOGLE_TALK:
+ return ProviderNames.GTALK;
+ case Im.PROTOCOL_AIM:
+ return ProviderNames.AIM;
+ case Im.PROTOCOL_MSN:
+ return ProviderNames.MSN;
+ case Im.PROTOCOL_YAHOO:
+ return ProviderNames.YAHOO;
+ case Im.PROTOCOL_ICQ:
+ return ProviderNames.ICQ;
+ case Im.PROTOCOL_JABBER:
+ return ProviderNames.JABBER;
+ case Im.PROTOCOL_SKYPE:
+ return ProviderNames.SKYPE;
+ case Im.PROTOCOL_QQ:
+ return ProviderNames.QQ;
+ }
+ return null;
+ }
+
+ /**
+ * Build {@link Intent} to launch an action for the given {@link Im} or
+ * {@link Email} row. Returns null when missing protocol or data.
+ */
+ public static Intent buildImIntent(ContentValues values) {
+ final boolean isEmail = Email.CONTENT_ITEM_TYPE.equals(values.getAsString(Data.MIMETYPE));
+
+ if (!isEmail && !isProtocolValid(values)) {
+ 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);
+ } else {
+ return null;
+ }
+ }
+
+ private static boolean isProtocolValid(ContentValues values) {
+ String protocolString = values.getAsString(Im.PROTOCOL);
+ if (protocolString == null) {
+ return false;
+ }
+ try {
+ Integer.valueOf(protocolString);
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ return true;
+ }
+
+ 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", 96);
+ intent.putExtra("outputY", 96);
+ intent.putExtra("return-data", true);
+ return intent;
+ }
+
+ public static long queryForContactId(ContentResolver cr, long rawContactId) {
+ Cursor contactIdCursor = null;
+ long contactId = -1;
+ try {
+ contactIdCursor = cr.query(RawContacts.CONTENT_URI,
+ new String[] {RawContacts.CONTACT_ID},
+ RawContacts._ID + "=" + rawContactId, null, null);
+ if (contactIdCursor != null && contactIdCursor.moveToFirst()) {
+ contactId = contactIdCursor.getLong(0);
+ }
+ } finally {
+ if (contactIdCursor != null) {
+ contactIdCursor.close();
+ }
+ }
+ return contactId;
+ }
+
+ public static String querySuperPrimaryPhone(ContentResolver cr, long contactId) {
+ Cursor c = null;
+ String phone = null;
+ try {
+ Uri baseUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
+ Uri dataUri = Uri.withAppendedPath(baseUri, Contacts.Data.CONTENT_DIRECTORY);
+
+ c = cr.query(dataUri,
+ new String[] {Phone.NUMBER},
+ Data.MIMETYPE + "=" + Phone.MIMETYPE +
+ " AND " + Data.IS_SUPER_PRIMARY + "=1",
+ null, null);
+ if (c != null && c.moveToFirst()) {
+ // Just return the first one.
+ phone = c.getString(0);
+ }
+ } finally {
+ if (c != null) {
+ c.close();
+ }
+ }
+ return phone;
+ }
+
+ public static long queryForRawContactId(ContentResolver cr, long contactId) {
+ Cursor rawContactIdCursor = null;
+ long rawContactId = -1;
+ try {
+ rawContactIdCursor = cr.query(RawContacts.CONTENT_URI,
+ new String[] {RawContacts._ID},
+ RawContacts.CONTACT_ID + "=" + contactId, null, null);
+ if (rawContactIdCursor != null && rawContactIdCursor.moveToFirst()) {
+ // Just return the first one.
+ rawContactId = rawContactIdCursor.getLong(0);
+ }
+ } finally {
+ if (rawContactIdCursor != null) {
+ rawContactIdCursor.close();
+ }
+ }
+ return rawContactId;
+ }
+
+ public static ArrayList<Long> queryForAllRawContactIds(ContentResolver cr, long contactId) {
+ Cursor rawContactIdCursor = null;
+ ArrayList<Long> rawContactIds = new ArrayList<Long>();
+ try {
+ rawContactIdCursor = cr.query(RawContacts.CONTENT_URI,
+ new String[] {RawContacts._ID},
+ RawContacts.CONTACT_ID + "=" + contactId, null, null);
+ if (rawContactIdCursor != null) {
+ while (rawContactIdCursor.moveToNext()) {
+ rawContactIds.add(rawContactIdCursor.getLong(0));
+ }
+ }
+ } finally {
+ if (rawContactIdCursor != null) {
+ rawContactIdCursor.close();
+ }
+ }
+ return rawContactIds;
+ }
+
+
+ /**
+ * Utility for creating a standard tab indicator view.
+ *
+ * @param parent The parent ViewGroup to attach the new view to.
+ * @param label The label to display in the tab indicator. If null, not label will be displayed.
+ * @param icon The icon to display. If null, no icon will be displayed.
+ * @return The tab indicator View.
+ */
+ public static View createTabIndicatorView(ViewGroup parent, CharSequence label, Drawable icon) {
+ final LayoutInflater inflater = (LayoutInflater)parent.getContext().getSystemService(
+ Context.LAYOUT_INFLATER_SERVICE);
+ final View tabIndicator = inflater.inflate(R.layout.tab_indicator, parent, false);
+ tabIndicator.getBackground().setDither(true);
+
+ final TextView tv = (TextView) tabIndicator.findViewById(R.id.tab_title);
+ tv.setText(label);
+
+ final ImageView iconView = (ImageView) tabIndicator.findViewById(R.id.tab_icon);
+ iconView.setImageDrawable(icon);
+
+ return tabIndicator;
+ }
+
+ /**
+ * Utility for creating a standard tab indicator view.
+ *
+ * @param context The label to display in the tab indicator. If null, not label will be displayed.
+ * @param parent The parent ViewGroup to attach the new view to.
+ * @param source The {@link ContactsSource} to build the tab view from.
+ * @return The tab indicator View.
+ */
+ public static View createTabIndicatorView(ViewGroup parent, ContactsSource source) {
+ Drawable icon = null;
+ if (source != null) {
+ icon = source.getDisplayIcon(parent.getContext());
+ }
+ return createTabIndicatorView(parent, null, icon);
+ }
+
+ /**
+ * Kick off an intent to initiate a call.
+ */
+ public static void initiateCall(Context context, CharSequence phoneNumber) {
+ Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
+ Uri.fromParts("tel", phoneNumber.toString(), null));
+ context.startActivity(intent);
+ }
+
+ /**
+ * Kick off an intent to initiate an Sms/Mms message.
+ */
+ public static void initiateSms(Context context, CharSequence phoneNumber) {
+ Intent intent = new Intent(Intent.ACTION_SENDTO,
+ Uri.fromParts("sms", phoneNumber.toString(), null));
+ context.startActivity(intent);
+ }
+
+ /**
+ * Test if the given {@link CharSequence} contains any graphic characters,
+ * first checking {@link TextUtils#isEmpty(CharSequence)} to handle null.
+ */
+ public static boolean isGraphic(CharSequence str) {
+ return !TextUtils.isEmpty(str) && TextUtils.isGraphic(str);
+ }
+}
diff --git a/src/com/android/contacts/DialtactsActivity.java b/src/com/android/contacts/DialtactsActivity.java
index 73d702b..208fbf4 100644
--- a/src/com/android/contacts/DialtactsActivity.java
+++ b/src/com/android/contacts/DialtactsActivity.java
@@ -16,29 +16,28 @@
package com.android.contacts;
+import com.android.internal.telephony.ITelephony;
+
import android.app.Activity;
import android.app.TabActivity;
import android.content.Intent;
import android.content.SharedPreferences;
-import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.ServiceManager;
-import android.provider.CallLog;
-import android.provider.Contacts;
import android.provider.CallLog.Calls;
-import android.provider.Contacts.Intents.UI;
+import android.provider.ContactsContract.Intents.UI;
import android.util.Log;
-import android.view.KeyEvent;
import android.view.Window;
import android.widget.TabHost;
-import com.android.internal.telephony.ITelephony;
/**
- * The dialer activity that has one tab with the virtual 12key dialer,
- * and another tab with recent calls in it. This is the container and the tabs
- * are embedded using intents.
+ * The dialer activity that has one tab with the virtual 12key
+ * dialer, a tab with recent calls in it, a tab with the contacts and
+ * a tab with the favorite. This is the container and the tabs are
+ * embedded using intents.
+ * The dialer tab's title is 'phone', a more common name (see strings.xml).
*/
public class DialtactsActivity extends TabActivity implements TabHost.OnTabChangeListener {
private static final String TAG = "Dailtacts";
@@ -49,7 +48,7 @@
private static final int TAB_INDEX_CALL_LOG = 1;
private static final int TAB_INDEX_CONTACTS = 2;
private static final int TAB_INDEX_FAVORITES = 3;
-
+
static final String EXTRA_IGNORE_STATE = "ignore-state";
/** Name of the dialtacts shared preferences */
@@ -59,7 +58,7 @@
static final boolean PREF_FAVORITES_AS_CONTACTS_DEFAULT = false;
private TabHost mTabHost;
- private String mFilterText;
+ private String mFilterText;
private Uri mDialUri;
@Override
@@ -68,7 +67,7 @@
final Intent intent = getIntent();
fixIntent(intent);
-
+
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.dialer_activity);
@@ -83,7 +82,7 @@
setCurrentTab(intent);
- if (intent.getAction().equals(Contacts.Intents.UI.FILTER_CONTACTS_ACTION)
+ if (intent.getAction().equals(UI.FILTER_CONTACTS_ACTION)
&& icicle == null) {
setupFilterText(intent);
}
@@ -92,7 +91,7 @@
@Override
protected void onPause() {
super.onPause();
-
+
int currentTabIndex = mTabHost.getCurrentTab();
if (currentTabIndex == TAB_INDEX_CONTACTS || currentTabIndex == TAB_INDEX_FAVORITES) {
SharedPreferences.Editor editor = getSharedPreferences(PREFS_DIALTACTS, MODE_PRIVATE)
@@ -101,7 +100,7 @@
editor.commit();
}
}
-
+
private void fixIntent(Intent intent) {
// This should be cleaned up: the call key used to send an Intent
// that just said to go to the recent calls list. It now sends this
@@ -112,7 +111,7 @@
setIntent(intent);
}
}
-
+
private void setupCallLogTab() {
// Force the class since overriding tab entries doesn't work
Intent intent = new Intent("com.android.phone.action.RECENT_CALLS");
@@ -156,10 +155,10 @@
/**
* Returns true if the intent is due to hitting the green send key while in a call.
- *
+ *
* @param intent the intent that launched this activity
* @param recentCallsRequest true if the intent is requesting to view recent calls
- * @return true if the intent is due to hitting the green send key while in a call
+ * @return true if the intent is due to hitting the green send key while in a call
*/
private boolean isSendKeyWhileInCall(final Intent intent, final boolean recentCallsRequest) {
// If there is a call in progress go to the call screen
@@ -181,7 +180,7 @@
/**
* Sets the current tab based on the intent's request type
- *
+ *
* @param recentCallsRequest true is the recent calls tab is desired, false otherwise
*/
private void setCurrentTab(Intent intent) {
@@ -191,7 +190,7 @@
finish();
return;
}
-
+
// Dismiss menu provided by any children activities
Activity activity = getLocalActivityManager().
getActivity(mTabHost.getCurrentTabTag());
@@ -235,7 +234,7 @@
fixIntent(newIntent);
setCurrentTab(newIntent);
final String action = newIntent.getAction();
- if (action.equals(Contacts.Intents.UI.FILTER_CONTACTS_ACTION)) {
+ if (action.equals(UI.FILTER_CONTACTS_ACTION)) {
setupFilterText(newIntent);
} else if (isDialIntent(newIntent)) {
setupDialUri(newIntent);
@@ -256,13 +255,13 @@
}
return false;
}
-
+
/**
* Retrieves the filter text stored in {@link #setupFilterText(Intent)}.
* This text originally came from a FILTER_CONTACTS_ACTION intent received
* by this activity. The stored text will then be cleared after after this
* method returns.
- *
+ *
* @return The stored filter text
*/
public String getAndClearFilterText() {
@@ -274,7 +273,7 @@
/**
* Stores the filter text associated with a FILTER_CONTACTS_ACTION intent.
* This is so child activities can check if they are supposed to display a filter.
- *
+ *
* @param intent The intent received in {@link #onNewIntent(Intent)}
*/
private void setupFilterText(Intent intent) {
@@ -282,7 +281,7 @@
if ((intent.getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) != 0) {
return;
}
- String filter = intent.getStringExtra(Contacts.Intents.UI.FILTER_TEXT_EXTRA_KEY);
+ String filter = intent.getStringExtra(UI.FILTER_TEXT_EXTRA_KEY);
if (filter != null && filter.length() > 0) {
mFilterText = filter;
}
@@ -292,7 +291,7 @@
* Retrieves the uri stored in {@link #setupDialUri(Intent)}. This uri
* originally came from a dial intent received by this activity. The stored
* uri will then be cleared after after this method returns.
- *
+ *
* @return The stored uri
*/
public Uri getAndClearDialUri() {
@@ -304,7 +303,7 @@
/**
* Stores the uri associated with a dial intent. This is so child activities can
* check if they are supposed to display new dial info.
- *
+ *
* @param intent The intent received in {@link #onNewIntent(Intent)}
*/
private void setupDialUri(Intent intent) {
@@ -316,18 +315,16 @@
}
@Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- // Handle BACK
- if (keyCode == KeyEvent.KEYCODE_BACK && isTaskRoot()) {
+ public void onBackPressed() {
+ if (isTaskRoot()) {
// Instead of stopping, simply push this to the back of the stack.
// This is only done when running at the top of the stack;
// otherwise, we have been launched by someone else so need to
// allow the user to go back to the caller.
moveTaskToBack(false);
- return true;
+ } else {
+ super.onBackPressed();
}
-
- return super.onKeyDown(keyCode, event);
}
/** {@inheritDoc} */
diff --git a/src/com/android/contacts/EditContactActivity.java b/src/com/android/contacts/EditContactActivity.java
deleted file mode 100644
index b89573b..0000000
--- a/src/com/android/contacts/EditContactActivity.java
+++ /dev/null
@@ -1,2202 +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 static com.android.contacts.ContactEntryAdapter.CONTACT_CUSTOM_RINGTONE_COLUMN;
-import static com.android.contacts.ContactEntryAdapter.CONTACT_NAME_COLUMN;
-import static com.android.contacts.ContactEntryAdapter.CONTACT_NOTES_COLUMN;
-import static com.android.contacts.ContactEntryAdapter.CONTACT_PROJECTION;
-import static com.android.contacts.ContactEntryAdapter.CONTACT_SEND_TO_VOICEMAIL_COLUMN;
-import static com.android.contacts.ContactEntryAdapter.CONTACT_PHONETIC_NAME_COLUMN;
-import static com.android.contacts.ContactEntryAdapter.METHODS_AUX_DATA_COLUMN;
-import static com.android.contacts.ContactEntryAdapter.METHODS_DATA_COLUMN;
-import static com.android.contacts.ContactEntryAdapter.METHODS_ID_COLUMN;
-import static com.android.contacts.ContactEntryAdapter.METHODS_ISPRIMARY_COLUMN;
-import static com.android.contacts.ContactEntryAdapter.METHODS_KIND_COLUMN;
-import static com.android.contacts.ContactEntryAdapter.METHODS_LABEL_COLUMN;
-import static com.android.contacts.ContactEntryAdapter.METHODS_PROJECTION;
-import static com.android.contacts.ContactEntryAdapter.METHODS_TYPE_COLUMN;
-import static com.android.contacts.ContactEntryAdapter.ORGANIZATIONS_COMPANY_COLUMN;
-import static com.android.contacts.ContactEntryAdapter.ORGANIZATIONS_ID_COLUMN;
-import static com.android.contacts.ContactEntryAdapter.ORGANIZATIONS_ISPRIMARY_COLUMN;
-import static com.android.contacts.ContactEntryAdapter.ORGANIZATIONS_LABEL_COLUMN;
-import static com.android.contacts.ContactEntryAdapter.ORGANIZATIONS_PROJECTION;
-import static com.android.contacts.ContactEntryAdapter.ORGANIZATIONS_TITLE_COLUMN;
-import static com.android.contacts.ContactEntryAdapter.ORGANIZATIONS_TYPE_COLUMN;
-import static com.android.contacts.ContactEntryAdapter.PHONES_ID_COLUMN;
-import static com.android.contacts.ContactEntryAdapter.PHONES_ISPRIMARY_COLUMN;
-import static com.android.contacts.ContactEntryAdapter.PHONES_LABEL_COLUMN;
-import static com.android.contacts.ContactEntryAdapter.PHONES_NUMBER_COLUMN;
-import static com.android.contacts.ContactEntryAdapter.PHONES_PROJECTION;
-import static com.android.contacts.ContactEntryAdapter.PHONES_TYPE_COLUMN;
-
-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.Intent;
-import android.content.SharedPreferences;
-import android.content.res.ColorStateList;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.graphics.Bitmap;
-import android.media.Ringtone;
-import android.media.RingtoneManager;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.preference.PreferenceManager;
-import android.provider.Contacts;
-import android.provider.Contacts.ContactMethods;
-import android.provider.Contacts.Intents.Insert;
-import android.provider.Contacts.Groups;
-import android.provider.Contacts.Organizations;
-import android.provider.Contacts.People;
-import android.provider.Contacts.Phones;
-import android.telephony.PhoneNumberFormattingTextWatcher;
-import android.text.Editable;
-import android.text.TextUtils;
-import android.text.TextWatcher;
-import android.text.method.TextKeyListener;
-import android.text.method.TextKeyListener.Capitalize;
-import android.util.Log;
-import android.util.SparseBooleanArray;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewParent;
-import android.view.inputmethod.EditorInfo;
-import android.widget.Button;
-import android.widget.CheckBox;
-import android.widget.EditText;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import java.io.ByteArrayOutputStream;
-import java.util.ArrayList;
-
-/**
- * Activity for editing or inserting a contact. Note that if the contact data changes in the
- * background while this activity is running, the updates will be overwritten.
- */
-public final class EditContactActivity extends Activity implements View.OnClickListener,
- TextWatcher, View.OnFocusChangeListener {
- private static final String TAG = "EditContactActivity";
-
- private static final int STATE_UNKNOWN = 0;
- /** Editing an existing contact */
- private static final int STATE_EDIT = 1;
- /** The full insert mode */
- private static final int STATE_INSERT = 2;
-
- /** 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 picking a ringtone */
- private static final int RINGTONE_PICKED = 3023;
-
- // These correspond to the string array in resources for picker "other" items
- final static int OTHER_ORGANIZATION = 0;
- final static int OTHER_NOTE = 1;
-
- // Dialog IDs
- final static int DELETE_CONFIRMATION_DIALOG = 2;
-
- // Section IDs
- final static int SECTION_PHONES = 3;
- final static int SECTION_EMAIL = 4;
- final static int SECTION_IM = 5;
- final static int SECTION_POSTAL = 6;
- final static int SECTION_ORG = 7;
- final static int SECTION_NOTE = 8;
-
- // Menu item IDs
- public static final int MENU_ITEM_SAVE = 1;
- public static final int MENU_ITEM_DONT_SAVE = 2;
- public static final int MENU_ITEM_DELETE = 3;
- public static final int MENU_ITEM_PHOTO = 6;
-
- /** Used to represent an invalid type for a contact entry */
- private static final int INVALID_TYPE = -1;
-
- /** The default type for a phone that is added via an intent */
- private static final int DEFAULT_PHONE_TYPE = Phones.TYPE_MOBILE;
-
- /** The default type for an email that is added via an intent */
- private static final int DEFAULT_EMAIL_TYPE = ContactMethods.TYPE_HOME;
-
- /** The default type for a postal address that is added via an intent */
- private static final int DEFAULT_POSTAL_TYPE = ContactMethods.TYPE_HOME;
-
- private int mState; // saved across instances
- private boolean mInsert; // saved across instances
- private Uri mUri; // saved across instances
- /** In insert mode this is the photo */
- private Bitmap mPhoto; // saved across instances
- private boolean mPhotoChanged = false; // saved across instances
-
- private EditText mNameView;
- private ImageView mPhotoImageView;
- private ViewGroup mContentView;
- private LinearLayout mLayout;
- private LayoutInflater mInflater;
- private MenuItem mPhotoMenuItem;
- private boolean mPhotoPresent = false;
- private EditText mPhoneticNameView; // invisible in some locales, but always present
-
- /** Flag marking this contact as changed, meaning we should write changes back. */
- private boolean mContactChanged = false;
-
- // These are accessed by inner classes. They're package scoped to make access more efficient.
- /* package */ ContentResolver mResolver;
- /* package */ ArrayList<EditEntry> mPhoneEntries = new ArrayList<EditEntry>();
- /* package */ ArrayList<EditEntry> mEmailEntries = new ArrayList<EditEntry>();
- /* package */ ArrayList<EditEntry> mImEntries = new ArrayList<EditEntry>();
- /* package */ ArrayList<EditEntry> mPostalEntries = new ArrayList<EditEntry>();
- /* package */ ArrayList<EditEntry> mOrgEntries = new ArrayList<EditEntry>();
- /* package */ ArrayList<EditEntry> mNoteEntries = new ArrayList<EditEntry>();
- /* package */ ArrayList<EditEntry> mOtherEntries = new ArrayList<EditEntry>();
- /* package */ ArrayList<ArrayList<EditEntry>> mSections = new ArrayList<ArrayList<EditEntry>>();
-
- /* package */ static final int MSG_DELETE = 1;
- /* package */ static final int MSG_CHANGE_LABEL = 2;
- /* package */ static final int MSG_ADD_PHONE = 3;
- /* package */ static final int MSG_ADD_EMAIL = 4;
- /* package */ static final int MSG_ADD_POSTAL = 5;
-
- private static final int[] TYPE_PRECEDENCE_PHONES = new int[] {
- Phones.TYPE_MOBILE, Phones.TYPE_HOME, Phones.TYPE_WORK, Phones.TYPE_OTHER
- };
- private static final int[] TYPE_PRECEDENCE_METHODS = new int[] {
- ContactMethods.TYPE_HOME, ContactMethods.TYPE_WORK, ContactMethods.TYPE_OTHER
- };
- private static final int[] TYPE_PRECEDENCE_IM = new int[] {
- ContactMethods.PROTOCOL_GOOGLE_TALK, ContactMethods.PROTOCOL_AIM,
- ContactMethods.PROTOCOL_MSN, ContactMethods.PROTOCOL_YAHOO,
- ContactMethods.PROTOCOL_JABBER
- };
- private static final int[] TYPE_PRECEDENCE_ORG = new int[] {
- Organizations.TYPE_WORK, Organizations.TYPE_OTHER
- };
-
- public void onClick(View v) {
- switch (v.getId()) {
- case R.id.photoImage: {
- doPickPhotoAction();
- break;
- }
-
- case R.id.checkable: {
- CheckBox checkBox = (CheckBox) v.findViewById(R.id.checkbox);
- checkBox.toggle();
-
- EditEntry entry = findEntryForView(v);
- entry.data = checkBox.isChecked() ? "1" : "0";
-
- mContactChanged = true;
- break;
- }
-
- case R.id.entry_ringtone: {
- EditEntry entry = findEntryForView(v);
- doPickRingtone(entry);
- break;
- }
-
- case R.id.separator: {
- // Someone clicked on a section header, so handle add action
- int sectionType = (Integer) v.getTag();
- doAddAction(sectionType);
- break;
- }
-
- case R.id.saveButton:
- doSaveAction();
- break;
-
- case R.id.discardButton:
- doRevertAction();
- break;
-
- case R.id.delete: {
- EditEntry entry = findEntryForView(v);
- if (entry != null) {
- // Clear the text and hide the view so it gets saved properly
- ((TextView) entry.view.findViewById(R.id.data)).setText(null);
- entry.view.setVisibility(View.GONE);
- entry.isDeleted = true;
- }
-
- // Force rebuild of views because section headers might need to change
- buildViews();
- break;
- }
-
- case R.id.label: {
- EditEntry entry = findEntryForView(v);
- if (entry != null) {
- String[] labels = getLabelsForKind(this, entry.kind);
- LabelPickedListener listener = new LabelPickedListener(entry, labels);
- new AlertDialog.Builder(EditContactActivity.this)
- .setItems(labels, listener)
- .setTitle(R.string.selectLabel)
- .show();
- }
- break;
- }
- }
- }
-
- private void setPhotoPresent(boolean present) {
- mPhotoPresent = present;
-
- // Correctly scale the contact photo if present, otherwise just center
- // the photo placeholder icon.
- if (mPhotoPresent) {
- mPhotoImageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
- } else {
- mPhotoImageView.setImageResource(R.drawable.ic_menu_add_picture);
- mPhotoImageView.setScaleType(ImageView.ScaleType.CENTER);
- }
-
- if (mPhotoMenuItem != null) {
- if (present) {
- mPhotoMenuItem.setTitle(R.string.removePicture);
- mPhotoMenuItem.setIcon(android.R.drawable.ic_menu_delete);
- } else {
- mPhotoMenuItem.setTitle(R.string.addPicture);
- mPhotoMenuItem.setIcon(R.drawable.ic_menu_add_picture);
- }
- }
- }
-
- private EditEntry findEntryForView(View v) {
- // Try to find the entry for this view
- EditEntry entry = null;
- do {
- Object tag = v.getTag();
- if (tag != null && tag instanceof EditEntry) {
- entry = (EditEntry) tag;
- break;
- } else {
- ViewParent parent = v.getParent();
- if (parent != null && parent instanceof View) {
- v = (View) parent;
- } else {
- v = null;
- }
- }
- } while (v != null);
- return entry;
- }
-
- private DialogInterface.OnClickListener mDeleteContactDialogListener =
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int button) {
- mResolver.delete(mUri, null, null);
- finish();
- }
- };
-
- private boolean mMobilePhoneAdded = false;
- private boolean mPrimaryEmailAdded = false;
-
- @Override
- protected void onCreate(Bundle icicle) {
- super.onCreate(icicle);
-
- mResolver = getContentResolver();
-
- // Build the list of sections
- setupSections();
-
- // Load the UI
- mInflater = getLayoutInflater();
- mContentView = (ViewGroup)mInflater.inflate(R.layout.edit_contact, null);
- setContentView(mContentView);
-
- mLayout = (LinearLayout) findViewById(R.id.list);
- mNameView = (EditText) findViewById(R.id.name);
- mPhotoImageView = (ImageView) findViewById(R.id.photoImage);
- mPhotoImageView.setOnClickListener(this);
- mPhoneticNameView = (EditText) findViewById(R.id.phonetic_name);
-
- // Setup the bottom buttons
- View view = findViewById(R.id.saveButton);
- view.setOnClickListener(this);
- view = findViewById(R.id.discardButton);
- view.setOnClickListener(this);
-
- // Resolve the intent
- mState = STATE_UNKNOWN;
- Intent intent = getIntent();
- String action = intent.getAction();
- mUri = intent.getData();
- if (mUri != null) {
- if (action.equals(Intent.ACTION_EDIT)) {
- if (icicle == null) {
- // Build the entries & views
- buildEntriesForEdit(getIntent().getExtras());
- buildViews();
- }
- setTitle(R.string.editContact_title_edit);
- mState = STATE_EDIT;
- } else if (action.equals(Intent.ACTION_INSERT)) {
- if (icicle == null) {
- // Build the entries & views
- buildEntriesForInsert(getIntent().getExtras());
- buildViews();
- }
- setTitle(R.string.editContact_title_insert);
- mState = STATE_INSERT;
- mInsert = true;
- }
- }
-
- if (mState == STATE_UNKNOWN) {
- Log.e(TAG, "Cannot resolve intent: " + intent);
- finish();
- return;
- }
-
- if (mState == STATE_EDIT) {
- setTitle(getResources().getText(R.string.editContact_title_edit));
- } else {
- setTitle(getResources().getText(R.string.editContact_title_insert));
- }
- }
-
- private void setupSections() {
- mSections.add(mPhoneEntries);
- mSections.add(mEmailEntries);
- mSections.add(mImEntries);
- mSections.add(mPostalEntries);
- mSections.add(mOrgEntries);
- mSections.add(mNoteEntries);
- mSections.add(mOtherEntries);
- }
-
- @Override
- protected void onSaveInstanceState(Bundle outState) {
-
- // To store current focus between config changes, follow focus down the
- // view tree, keeping track of any parents with EditEntry tags
- View focusedChild = mContentView.getFocusedChild();
- EditEntry focusedEntry = null;
- while (focusedChild != null) {
- Object tag = focusedChild.getTag();
- if (tag instanceof EditEntry) {
- focusedEntry = (EditEntry) tag;
- }
-
- // Keep going deeper until child isn't a group
- if (focusedChild instanceof ViewGroup) {
- View deeperFocus = ((ViewGroup) focusedChild).getFocusedChild();
- if (deeperFocus != null) {
- focusedChild = deeperFocus;
- } else {
- break;
- }
- } else {
- break;
- }
- }
-
- if (focusedChild != null) {
- int requestFocusId = focusedChild.getId();
- int requestCursor = 0;
- if (focusedChild instanceof EditText) {
- requestCursor = ((EditText) focusedChild).getSelectionStart();
- }
-
- // Store focus values in EditEntry if found, otherwise store as
- // generic values
- if (focusedEntry != null) {
- focusedEntry.requestFocusId = requestFocusId;
- focusedEntry.requestCursor = requestCursor;
- } else {
- outState.putInt("requestFocusId", requestFocusId);
- outState.putInt("requestCursor", requestCursor);
- }
- }
-
- outState.putParcelableArrayList("phoneEntries", mPhoneEntries);
- outState.putParcelableArrayList("emailEntries", mEmailEntries);
- outState.putParcelableArrayList("imEntries", mImEntries);
- outState.putParcelableArrayList("postalEntries", mPostalEntries);
- outState.putParcelableArrayList("orgEntries", mOrgEntries);
- outState.putParcelableArrayList("noteEntries", mNoteEntries);
- outState.putParcelableArrayList("otherEntries", mOtherEntries);
- outState.putInt("state", mState);
- outState.putBoolean("insert", mInsert);
- outState.putParcelable("uri", mUri);
- outState.putString("name", mNameView.getText().toString());
- outState.putParcelable("photo", mPhoto);
- outState.putBoolean("photoChanged", mPhotoChanged);
- outState.putString("phoneticName", mPhoneticNameView.getText().toString());
- outState.putBoolean("contactChanged", mContactChanged);
- }
-
- @Override
- protected void onRestoreInstanceState(Bundle inState) {
- mPhoneEntries = inState.getParcelableArrayList("phoneEntries");
- mEmailEntries = inState.getParcelableArrayList("emailEntries");
- mImEntries = inState.getParcelableArrayList("imEntries");
- mPostalEntries = inState.getParcelableArrayList("postalEntries");
- mOrgEntries = inState.getParcelableArrayList("orgEntries");
- mNoteEntries = inState.getParcelableArrayList("noteEntries");
- mOtherEntries = inState.getParcelableArrayList("otherEntries");
- setupSections();
-
- mState = inState.getInt("state");
- mInsert = inState.getBoolean("insert");
- mUri = inState.getParcelable("uri");
- mNameView.setText(inState.getString("name"));
- mPhoto = inState.getParcelable("photo");
- if (mPhoto != null) {
- mPhotoImageView.setImageBitmap(mPhoto);
- setPhotoPresent(true);
- } else {
- mPhotoImageView.setImageResource(R.drawable.ic_contact_picture);
- setPhotoPresent(false);
- }
- mPhotoChanged = inState.getBoolean("photoChanged");
- mPhoneticNameView.setText(inState.getString("phoneticName"));
- mContactChanged = inState.getBoolean("contactChanged");
-
- // Now that everything is restored, build the view
- buildViews();
-
- // Try restoring any generally requested focus
- int requestFocusId = inState.getInt("requestFocusId", View.NO_ID);
- View focusedChild = mContentView.findViewById(requestFocusId);
- if (focusedChild != null) {
- focusedChild.requestFocus();
- if (focusedChild instanceof EditText) {
- int requestCursor = inState.getInt("requestCursor", 0);
- ((EditText) focusedChild).setSelection(requestCursor);
- }
- }
- }
-
- @Override
- protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- if (resultCode != RESULT_OK) {
- return;
- }
-
- switch (requestCode) {
- case PHOTO_PICKED_WITH_DATA: {
- final Bundle extras = data.getExtras();
- if (extras != null) {
- Bitmap photo = extras.getParcelable("data");
- mPhoto = photo;
- mPhotoChanged = true;
- mPhotoImageView.setImageBitmap(photo);
- setPhotoPresent(true);
- }
- break;
- }
-
- case RINGTONE_PICKED: {
- Uri pickedUri = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI);
- handleRingtonePicked(pickedUri);
- mContactChanged = true;
- break;
- }
- }
- }
-
- @Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- switch (keyCode) {
- case KeyEvent.KEYCODE_BACK: {
- doSaveAction();
- return true;
- }
- }
- return super.onKeyDown(keyCode, event);
- }
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- super.onCreateOptionsMenu(menu);
- menu.add(0, MENU_ITEM_SAVE, 0, R.string.menu_done)
- .setIcon(android.R.drawable.ic_menu_save)
- .setAlphabeticShortcut('\n');
- menu.add(0, MENU_ITEM_DONT_SAVE, 0, R.string.menu_doNotSave)
- .setIcon(android.R.drawable.ic_menu_close_clear_cancel)
- .setAlphabeticShortcut('q');
- if (!mInsert) {
- menu.add(0, MENU_ITEM_DELETE, 0, R.string.menu_deleteContact)
- .setIcon(android.R.drawable.ic_menu_delete);
- }
-
- mPhotoMenuItem = menu.add(0, MENU_ITEM_PHOTO, 0, null);
- // Updates the state of the menu item
- setPhotoPresent(mPhotoPresent);
-
- return true;
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case MENU_ITEM_SAVE:
- doSaveAction();
- return true;
-
- case MENU_ITEM_DONT_SAVE:
- doRevertAction();
- return true;
-
- case MENU_ITEM_DELETE:
- // Get confirmation
- showDialog(DELETE_CONFIRMATION_DIALOG);
- return true;
-
- case MENU_ITEM_PHOTO:
- if (!mPhotoPresent) {
- doPickPhotoAction();
- } else {
- doRemovePhotoAction();
- }
- return true;
- }
-
- return false;
- }
-
- /**
- * Try guessing the next-best type of {@link EditEntry} to insert into the
- * given list. We walk down the precedence list until we find a type that
- * doesn't exist yet, or default to the lowest ranking type.
- */
- private int guessNextType(ArrayList<EditEntry> entries, int[] precedenceList) {
- // Keep track of the types we've seen already
- SparseBooleanArray existAlready = new SparseBooleanArray(entries.size());
- for (int i = entries.size() - 1; i >= 0; i--) {
- EditEntry entry = entries.get(i);
- if (!entry.isDeleted) {
- existAlready.put(entry.type, true);
- }
- }
-
- // Pick the first item we haven't seen
- for (int type : precedenceList) {
- if (!existAlready.get(type, false)) {
- return type;
- }
- }
-
- // Otherwise default to last item
- return precedenceList[precedenceList.length - 1];
- }
-
- private void doAddAction(int sectionType) {
- EditEntry entry = null;
- switch (sectionType) {
- case SECTION_PHONES: {
- // Try figuring out which type to insert next
- int nextType = guessNextType(mPhoneEntries, TYPE_PRECEDENCE_PHONES);
- entry = EditEntry.newPhoneEntry(EditContactActivity.this,
- Uri.withAppendedPath(mUri, People.Phones.CONTENT_DIRECTORY),
- nextType);
- mPhoneEntries.add(entry);
- break;
- }
- case SECTION_EMAIL: {
- // Try figuring out which type to insert next
- int nextType = guessNextType(mEmailEntries, TYPE_PRECEDENCE_METHODS);
- entry = EditEntry.newEmailEntry(EditContactActivity.this,
- Uri.withAppendedPath(mUri, People.ContactMethods.CONTENT_DIRECTORY),
- nextType);
- mEmailEntries.add(entry);
- break;
- }
- case SECTION_IM: {
- // Try figuring out which type to insert next
- int nextType = guessNextType(mImEntries, TYPE_PRECEDENCE_IM);
- entry = EditEntry.newImEntry(EditContactActivity.this,
- Uri.withAppendedPath(mUri, People.ContactMethods.CONTENT_DIRECTORY),
- nextType);
- mImEntries.add(entry);
- break;
- }
- case SECTION_POSTAL: {
- int nextType = guessNextType(mPostalEntries, TYPE_PRECEDENCE_METHODS);
- entry = EditEntry.newPostalEntry(EditContactActivity.this,
- Uri.withAppendedPath(mUri, People.ContactMethods.CONTENT_DIRECTORY),
- nextType);
- mPostalEntries.add(entry);
- break;
- }
- case SECTION_ORG: {
- int nextType = guessNextType(mOrgEntries, TYPE_PRECEDENCE_ORG);
- entry = EditEntry.newOrganizationEntry(EditContactActivity.this,
- Uri.withAppendedPath(mUri, Organizations.CONTENT_DIRECTORY),
- nextType);
- mOrgEntries.add(entry);
- break;
- }
- case SECTION_NOTE: {
- entry = EditEntry.newNotesEntry(EditContactActivity.this, null, mUri);
- mNoteEntries.add(entry);
- break;
- }
- }
-
- // Rebuild the views if needed
- if (entry != null) {
- buildViews();
- mContactChanged = true;
-
- View dataView = entry.view.findViewById(R.id.data);
- if (dataView == null) {
- entry.view.requestFocus();
- } else {
- dataView.requestFocus();
- }
- }
- }
-
- private void doRevertAction() {
- finish();
- }
-
- private void doPickPhotoAction() {
- Intent intent = new Intent(Intent.ACTION_GET_CONTENT, null);
- // TODO: get these values from constants somewhere
- intent.setType("image/*");
- intent.putExtra("crop", "true");
- intent.putExtra("aspectX", 1);
- intent.putExtra("aspectY", 1);
- intent.putExtra("outputX", 96);
- intent.putExtra("outputY", 96);
- try {
- intent.putExtra("return-data", true);
- startActivityForResult(intent, PHOTO_PICKED_WITH_DATA);
- } catch (ActivityNotFoundException e) {
- new AlertDialog.Builder(EditContactActivity.this)
- .setTitle(R.string.errorDialogTitle)
- .setMessage(R.string.photoPickerNotFoundText)
- .setPositiveButton(android.R.string.ok, null)
- .show();
- }
- }
-
- private void doRemovePhotoAction() {
- mPhoto = null;
- mPhotoChanged = true;
- setPhotoPresent(false);
- }
-
- private void doPickRingtone(EditEntry entry) {
- Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER);
- // Allow user to pick 'Default'
- intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true);
- // Show only ringtones
- intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_RINGTONE);
- // Don't show 'Silent'
- intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, false);
-
- Uri ringtoneUri;
- if (entry.data != null) {
- ringtoneUri = Uri.parse(entry.data);
- } else {
- // Otherwise pick default ringtone Uri so that something is selected.
- ringtoneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE);
- }
-
- // Put checkmark next to the current ringtone for this contact
- intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, ringtoneUri);
- // Launch!
- startActivityForResult(intent, RINGTONE_PICKED);
- }
-
- private void handleRingtonePicked(Uri pickedUri) {
- EditEntry entry = getOtherEntry(People.CUSTOM_RINGTONE);
- if (entry == null) {
- Log.w(TAG, "Ringtone picked but could not find ringtone entry");
- return;
- }
-
- if (pickedUri == null || RingtoneManager.isDefault(pickedUri)) {
- entry.data = null;
- } else {
- entry.data = pickedUri.toString();
- }
-
- updateRingtoneView(entry);
- }
-
- private void updateRingtoneView(EditEntry entry) {
- String ringtoneName;
- if (entry.data == null) {
- ringtoneName = getString(R.string.default_ringtone);
- } else {
- Uri ringtoneUri = Uri.parse(entry.data);
- Ringtone ringtone = RingtoneManager.getRingtone(this, ringtoneUri);
- if (ringtone == null) {
- Log.w(TAG, "ringtone's URI doesn't resolve to a Ringtone");
- return;
- }
- ringtoneName = ringtone.getTitle(this);
- }
-
- updateDataView(entry, ringtoneName);
- }
-
- private void updateDataView(EditEntry entry, String text) {
- TextView dataView = (TextView) entry.view.findViewById(R.id.data);
- dataView.setText(text);
- }
-
- @Override
- protected Dialog onCreateDialog(int id) {
- switch (id) {
- case DELETE_CONFIRMATION_DIALOG:
- return new AlertDialog.Builder(EditContactActivity.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, mDeleteContactDialogListener)
- .setCancelable(false)
- .create();
- }
- return super.onCreateDialog(id);
- }
-
- static String[] getLabelsForKind(Context context, int kind) {
- final Resources resources = context.getResources();
- switch (kind) {
- case Contacts.KIND_PHONE:
- return resources.getStringArray(android.R.array.phoneTypes);
- case Contacts.KIND_EMAIL:
- return resources.getStringArray(android.R.array.emailAddressTypes);
- case Contacts.KIND_POSTAL:
- return resources.getStringArray(android.R.array.postalAddressTypes);
- case Contacts.KIND_IM:
- return resources.getStringArray(android.R.array.imProtocols);
- case Contacts.KIND_ORGANIZATION:
- return resources.getStringArray(android.R.array.organizationTypes);
- case EditEntry.KIND_CONTACT:
- return resources.getStringArray(R.array.otherLabels);
- }
- return null;
- }
-
- int getTypeFromLabelPosition(CharSequence[] labels, int labelPosition) {
- // In the UI Custom... comes last, but it is uses the constant 0
- // so it is in the same location across the various kinds. Fix up the
- // position to a valid type here.
- if (labelPosition == labels.length - 1) {
- return ContactMethods.TYPE_CUSTOM;
- } else {
- return labelPosition + 1;
- }
- }
-
- private EditEntry getOtherEntry(String column) {
- for (int i = mOtherEntries.size() - 1; i >= 0; i--) {
- EditEntry entry = mOtherEntries.get(i);
- if (isOtherEntry(entry, column)) {
- return entry;
- }
- }
- return null;
- }
-
- private static boolean isOtherEntry(EditEntry entry, String column) {
- return entry != null && entry.column != null && entry.column.equals(column);
- }
-
- private void createCustomPicker(final EditEntry entry, final ArrayList<EditEntry> addTo) {
- final EditText label = new EditText(this);
- label.setKeyListener(TextKeyListener.getInstance(false, Capitalize.WORDS));
- label.requestFocus();
- new AlertDialog.Builder(this)
- .setView(label)
- .setTitle(R.string.customLabelPickerTitle)
- .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- entry.setLabel(EditContactActivity.this, ContactMethods.TYPE_CUSTOM,
- label.getText().toString());
- mContactChanged = true;
-
- if (addTo != null) {
- addTo.add(entry);
- buildViews();
- entry.view.requestFocus(View.FOCUS_DOWN);
- }
- }
- })
- .setNegativeButton(android.R.string.cancel, null)
- .show();
- }
-
- /**
- * Saves or creates the contact based on the mode, and if sucessful finishes the activity.
- */
- private void doSaveAction() {
- // Save or create the contact if needed
- switch (mState) {
- case STATE_EDIT:
- save();
- break;
-
- case STATE_INSERT:
- create();
- break;
-
- default:
- Log.e(TAG, "Unknown state in doSaveOrCreate: " + mState);
- break;
- }
- finish();
- }
-
- /**
- * Save the various fields to the existing contact.
- */
- private void save() {
- ContentValues values = new ContentValues();
- String data;
- int numValues = 0;
-
- // Handle the name and send to voicemail specially
- final String name = mNameView.getText().toString();
- if (name != null && TextUtils.isGraphic(name)) {
- numValues++;
- }
- values.put(People.NAME, name);
- values.put(People.PHONETIC_NAME, mPhoneticNameView.getText().toString());
- mResolver.update(mUri, values, null, null);
-
- if (mPhotoChanged) {
- // Only write the photo if it's changed, since we don't initially load mPhoto
- if (mPhoto != null) {
- ByteArrayOutputStream stream = new ByteArrayOutputStream();
- mPhoto.compress(Bitmap.CompressFormat.JPEG, 75, stream);
- Contacts.People.setPhotoData(mResolver, mUri, stream.toByteArray());
- } else {
- Contacts.People.setPhotoData(mResolver, mUri, null);
- }
- }
-
- int entryCount = ContactEntryAdapter.countEntries(mSections, false);
- for (int i = 0; i < entryCount; i++) {
- EditEntry entry = ContactEntryAdapter.getEntry(mSections, i, false);
- int kind = entry.kind;
- data = entry.getData();
- boolean empty = data == null || !TextUtils.isGraphic(data);
- if (kind == EditEntry.KIND_CONTACT) {
- values.clear();
- if (!empty) {
- values.put(entry.column, data);
- mResolver.update(entry.uri, values, null, null);
- if (!People.CUSTOM_RINGTONE.equals(entry.column) &&
- !People.SEND_TO_VOICEMAIL.equals(entry.column)) {
- numValues++;
- }
- } else {
- values.put(entry.column, (String) null);
- mResolver.update(entry.uri, values, null, null);
- }
- } else {
- if (!empty) {
- values.clear();
- entry.toValues(values);
- if (entry.id != 0) {
- mResolver.update(entry.uri, values, null, null);
- } else {
- mResolver.insert(entry.uri, values);
- }
- if (!People.CUSTOM_RINGTONE.equals(entry.column) &&
- !People.SEND_TO_VOICEMAIL.equals(entry.column)) {
- numValues++;
- }
- } else if (entry.id != 0) {
- mResolver.delete(entry.uri, null, null);
- }
- }
- }
-
- if (numValues == 0) {
- // The contact is completely empty, delete it
- mResolver.delete(mUri, null, null);
- mUri = null;
- setResult(RESULT_CANCELED);
- } else {
- // Add the entry to the my contacts group if it isn't there already
- People.addToMyContactsGroup(mResolver, ContentUris.parseId(mUri));
- setResult(RESULT_OK, new Intent().setData(mUri));
-
- // Only notify user if we actually changed contact
- if (mContactChanged || mPhotoChanged) {
- Toast.makeText(this, R.string.contactSavedToast, Toast.LENGTH_SHORT).show();
- }
- }
- }
-
- /**
- * Takes the entered data and saves it to a new contact.
- */
- private void create() {
- ContentValues values = new ContentValues();
- String data;
- int numValues = 0;
-
- // Create the contact itself
- final String name = mNameView.getText().toString();
- if (name != null && TextUtils.isGraphic(name)) {
- numValues++;
- }
- values.put(People.NAME, name);
- values.put(People.PHONETIC_NAME, mPhoneticNameView.getText().toString());
-
- // Add the contact to the My Contacts group
- Uri contactUri = People.createPersonInMyContactsGroup(mResolver, values);
-
- // Add the contact to the group that is being displayed in the contact list
- SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
- int displayType = prefs.getInt(ContactsListActivity.PREF_DISPLAY_TYPE,
- ContactsListActivity.DISPLAY_TYPE_UNKNOWN);
- if (displayType == ContactsListActivity.DISPLAY_TYPE_USER_GROUP) {
- String displayGroup = prefs.getString(ContactsListActivity.PREF_DISPLAY_INFO,
- null);
- if (!TextUtils.isEmpty(displayGroup)) {
- People.addToGroup(mResolver, ContentUris.parseId(contactUri), displayGroup);
- }
- } else {
- // Check to see if we're not syncing everything and if so if My Contacts is synced.
- // If it isn't then the created contact can end up not in any groups that are
- // currently synced and end up getting removed from the phone, which is really bad.
- boolean syncingEverything = !"0".equals(Contacts.Settings.getSetting(mResolver, null,
- Contacts.Settings.SYNC_EVERYTHING));
- if (!syncingEverything) {
- boolean syncingMyContacts = false;
- Cursor c = mResolver.query(Groups.CONTENT_URI, new String[] { Groups.SHOULD_SYNC },
- Groups.SYSTEM_ID + "=?", new String[] { Groups.GROUP_MY_CONTACTS }, null);
- if (c != null) {
- try {
- if (c.moveToFirst()) {
- syncingMyContacts = !"0".equals(c.getString(0));
- }
- } finally {
- c.close();
- }
- }
-
- if (!syncingMyContacts) {
- // Not syncing My Contacts, so find a group that is being synced and stick
- // the contact in there. We sort the list so at least all contacts
- // will appear in the same group.
- c = mResolver.query(Groups.CONTENT_URI, new String[] { Groups._ID },
- Groups.SHOULD_SYNC + "!=0", null, Groups.DEFAULT_SORT_ORDER);
- if (c != null) {
- try {
- if (c.moveToFirst()) {
- People.addToGroup(mResolver, ContentUris.parseId(contactUri),
- c.getLong(0));
- }
- } finally {
- c.close();
- }
- }
- }
- }
- }
-
- // Handle the photo
- if (mPhoto != null) {
- ByteArrayOutputStream stream = new ByteArrayOutputStream();
- mPhoto.compress(Bitmap.CompressFormat.JPEG, 75, stream);
- Contacts.People.setPhotoData(getContentResolver(), contactUri, stream.toByteArray());
- }
-
- // Create the contact methods
- int entryCount = ContactEntryAdapter.countEntries(mSections, false);
- for (int i = 0; i < entryCount; i++) {
- EditEntry entry = ContactEntryAdapter.getEntry(mSections, i, false);
- if (entry.kind != EditEntry.KIND_CONTACT) {
- values.clear();
- if (entry.toValues(values)) {
- // Only create the entry if there is data
- entry.uri = mResolver.insert(
- Uri.withAppendedPath(contactUri, entry.contentDirectory), values);
- entry.id = ContentUris.parseId(entry.uri);
- if (!People.CUSTOM_RINGTONE.equals(entry.column) &&
- !People.SEND_TO_VOICEMAIL.equals(entry.column)) {
- numValues++;
- }
- }
- } else {
- // Update the contact with any straggling data, like notes
- data = entry.getData();
- values.clear();
- if (data != null && TextUtils.isGraphic(data)) {
- values.put(entry.column, data);
- mResolver.update(contactUri, values, null, null);
- if (!People.CUSTOM_RINGTONE.equals(entry.column) &&
- !People.SEND_TO_VOICEMAIL.equals(entry.column)) {
- numValues++;
- }
- }
- }
- }
-
- if (numValues == 0) {
- mResolver.delete(contactUri, null, null);
- setResult(RESULT_CANCELED);
- } else {
- mUri = contactUri;
- Intent resultIntent = new Intent()
- .setData(mUri)
- .putExtra(Intent.EXTRA_SHORTCUT_NAME, name);
- setResult(RESULT_OK, resultIntent);
- Toast.makeText(this, R.string.contactCreatedToast, Toast.LENGTH_SHORT).show();
- }
- }
-
- /**
- * Build up the entries to display on the screen.
- *
- * @param extras the extras used to start this activity, may be null
- */
- private void buildEntriesForEdit(Bundle extras) {
- Cursor personCursor = mResolver.query(mUri, CONTACT_PROJECTION, null, null, null);
- if (personCursor == null) {
- Log.e(TAG, "invalid contact uri: " + mUri);
- finish();
- return;
- } else if (!personCursor.moveToFirst()) {
- Log.e(TAG, "invalid contact uri: " + mUri);
- finish();
- personCursor.close();
- return;
- }
-
- // Clear out the old entries
- int numSections = mSections.size();
- for (int i = 0; i < numSections; i++) {
- mSections.get(i).clear();
- }
-
- EditEntry entry;
-
- // Name
- mNameView.setText(personCursor.getString(CONTACT_NAME_COLUMN));
- mNameView.addTextChangedListener(this);
-
- // Photo
- mPhoto = People.loadContactPhoto(this, mUri, 0, null);
- if (mPhoto == null) {
- setPhotoPresent(false);
- } else {
- setPhotoPresent(true);
- mPhotoImageView.setImageBitmap(mPhoto);
- }
-
- // Organizations
- Uri organizationsUri = Uri.withAppendedPath(mUri, Organizations.CONTENT_DIRECTORY);
- Cursor organizationsCursor = mResolver.query(organizationsUri, ORGANIZATIONS_PROJECTION,
- null, null, null);
-
- if (organizationsCursor != null) {
- while (organizationsCursor.moveToNext()) {
- int type = organizationsCursor.getInt(ORGANIZATIONS_TYPE_COLUMN);
- String label = organizationsCursor.getString(ORGANIZATIONS_LABEL_COLUMN);
- String company = organizationsCursor.getString(ORGANIZATIONS_COMPANY_COLUMN);
- String title = organizationsCursor.getString(ORGANIZATIONS_TITLE_COLUMN);
- long id = organizationsCursor.getLong(ORGANIZATIONS_ID_COLUMN);
- Uri uri = ContentUris.withAppendedId(Organizations.CONTENT_URI, id);
-
- // Add an organization entry
- entry = EditEntry.newOrganizationEntry(this, label, type, company, title, uri, id);
- entry.isPrimary = organizationsCursor.getLong(ORGANIZATIONS_ISPRIMARY_COLUMN) != 0;
- mOrgEntries.add(entry);
- }
- organizationsCursor.close();
- }
-
- // Notes
- if (!personCursor.isNull(CONTACT_NOTES_COLUMN)) {
- entry = EditEntry.newNotesEntry(this, personCursor.getString(CONTACT_NOTES_COLUMN),
- mUri);
- mNoteEntries.add(entry);
- }
-
- // Ringtone
- entry = EditEntry.newRingtoneEntry(this,
- personCursor.getString(CONTACT_CUSTOM_RINGTONE_COLUMN), mUri);
- mOtherEntries.add(entry);
-
- // Send to voicemail
- entry = EditEntry.newSendToVoicemailEntry(this,
- personCursor.getString(CONTACT_SEND_TO_VOICEMAIL_COLUMN), mUri);
- mOtherEntries.add(entry);
-
- // Phonetic name
- mPhoneticNameView.setText(personCursor.getString(CONTACT_PHONETIC_NAME_COLUMN));
- mPhoneticNameView.addTextChangedListener(this);
-
- personCursor.close();
-
- // Build up the phone entries
- Uri phonesUri = Uri.withAppendedPath(mUri, People.Phones.CONTENT_DIRECTORY);
- Cursor phonesCursor = mResolver.query(phonesUri, PHONES_PROJECTION,
- null, null, null);
-
- if (phonesCursor != null) {
- while (phonesCursor.moveToNext()) {
- int type = phonesCursor.getInt(PHONES_TYPE_COLUMN);
- String label = phonesCursor.getString(PHONES_LABEL_COLUMN);
- String number = phonesCursor.getString(PHONES_NUMBER_COLUMN);
- long id = phonesCursor.getLong(PHONES_ID_COLUMN);
- boolean isPrimary = phonesCursor.getLong(PHONES_ISPRIMARY_COLUMN) != 0;
- Uri uri = ContentUris.withAppendedId(phonesUri, id);
-
- // Add a phone number entry
- entry = EditEntry.newPhoneEntry(this, label, type, number, uri, id);
- entry.isPrimary = isPrimary;
- mPhoneEntries.add(entry);
-
- // Keep track of which primary types have been added
- if (type == Phones.TYPE_MOBILE) {
- mMobilePhoneAdded = true;
- }
- }
-
- phonesCursor.close();
- }
-
- // Build the contact method entries
- Uri methodsUri = Uri.withAppendedPath(mUri, People.ContactMethods.CONTENT_DIRECTORY);
- Cursor methodsCursor = mResolver.query(methodsUri, METHODS_PROJECTION, null, null, null);
-
- if (methodsCursor != null) {
- while (methodsCursor.moveToNext()) {
- int kind = methodsCursor.getInt(METHODS_KIND_COLUMN);
- String label = methodsCursor.getString(METHODS_LABEL_COLUMN);
- String data = methodsCursor.getString(METHODS_DATA_COLUMN);
- String auxData = methodsCursor.getString(METHODS_AUX_DATA_COLUMN);
- int type = methodsCursor.getInt(METHODS_TYPE_COLUMN);
- long id = methodsCursor.getLong(METHODS_ID_COLUMN);
- boolean isPrimary = methodsCursor.getLong(METHODS_ISPRIMARY_COLUMN) != 0;
- Uri uri = ContentUris.withAppendedId(methodsUri, id);
-
- switch (kind) {
- case Contacts.KIND_EMAIL: {
- entry = EditEntry.newEmailEntry(this, label, type, data, uri, id);
- entry.isPrimary = isPrimary;
- mEmailEntries.add(entry);
-
- if (isPrimary) {
- mPrimaryEmailAdded = true;
- }
- break;
- }
-
- case Contacts.KIND_POSTAL: {
- entry = EditEntry.newPostalEntry(this, label, type, data, uri, id);
- entry.isPrimary = isPrimary;
- mPostalEntries.add(entry);
- break;
- }
-
- case Contacts.KIND_IM: {
- Object protocolObj = ContactMethods.decodeImProtocol(auxData);
- if (protocolObj == null) {
- // Invalid IM protocol, log it then ignore.
- Log.e(TAG, "Couldn't decode IM protocol: " + auxData);
- continue;
- } else {
- if (protocolObj instanceof Number) {
- int protocol = ((Number) protocolObj).intValue();
- entry = EditEntry.newImEntry(this,
- getLabelsForKind(this, Contacts.KIND_IM)[protocol], protocol,
- data, uri, id);
- } else {
- entry = EditEntry.newImEntry(this, protocolObj.toString(), -1, data,
- uri, id);
- }
- mImEntries.add(entry);
- }
- break;
- }
- }
- }
-
- methodsCursor.close();
- }
-
- // Add values from the extras, if there are any
- if (extras != null) {
- addFromExtras(extras, phonesUri, methodsUri);
- }
-
- // Add the base types if needed
- if (!mMobilePhoneAdded) {
- entry = EditEntry.newPhoneEntry(this,
- Uri.withAppendedPath(mUri, People.Phones.CONTENT_DIRECTORY),
- DEFAULT_PHONE_TYPE);
- mPhoneEntries.add(entry);
- }
-
- if (!mPrimaryEmailAdded) {
- entry = EditEntry.newEmailEntry(this,
- Uri.withAppendedPath(mUri, People.ContactMethods.CONTENT_DIRECTORY),
- DEFAULT_EMAIL_TYPE);
- entry.isPrimary = true;
- mEmailEntries.add(entry);
- }
-
- mContactChanged = false;
- }
-
- /**
- * Build the list of EditEntries for full mode insertions.
- *
- * @param extras the extras used to start this activity, may be null
- */
- private void buildEntriesForInsert(Bundle extras) {
- // Clear out the old entries
- int numSections = mSections.size();
- for (int i = 0; i < numSections; i++) {
- mSections.get(i).clear();
- }
-
- EditEntry entry;
-
- // Check the intent extras
- if (extras != null) {
- addFromExtras(extras, null, null);
- }
-
- // Photo
- mPhotoImageView.setImageResource(R.drawable.ic_contact_picture);
-
- // Add the base entries if they're not already present
- if (!mMobilePhoneAdded) {
- entry = EditEntry.newPhoneEntry(this, null, Phones.TYPE_MOBILE);
- entry.isPrimary = true;
- mPhoneEntries.add(entry);
- }
-
- if (!mPrimaryEmailAdded) {
- entry = EditEntry.newEmailEntry(this, null, DEFAULT_EMAIL_TYPE);
- entry.isPrimary = true;
- mEmailEntries.add(entry);
- }
-
- // Ringtone
- entry = EditEntry.newRingtoneEntry(this, null, mUri);
- mOtherEntries.add(entry);
-
- // Send to voicemail
- entry = EditEntry.newSendToVoicemailEntry(this, "0", mUri);
- mOtherEntries.add(entry);
- }
-
- private void addFromExtras(Bundle extras, Uri phonesUri, Uri methodsUri) {
- EditEntry entry;
-
- // Read the name from the bundle
- CharSequence name = extras.getCharSequence(Insert.NAME);
- if (name != null && TextUtils.isGraphic(name)) {
- mNameView.setText(name);
- }
-
- // Read the phonetic name from the bundle
- CharSequence phoneticName = extras.getCharSequence(Insert.PHONETIC_NAME);
- if (!TextUtils.isEmpty(phoneticName)) {
- mPhoneticNameView.setText(phoneticName);
- }
-
- // Postal entries from extras
- CharSequence postal = extras.getCharSequence(Insert.POSTAL);
- int postalType = extras.getInt(Insert.POSTAL_TYPE, INVALID_TYPE);
- if (!TextUtils.isEmpty(postal) && postalType == INVALID_TYPE) {
- postalType = DEFAULT_POSTAL_TYPE;
- }
-
- if (postalType != INVALID_TYPE) {
- entry = EditEntry.newPostalEntry(this, null, postalType, postal.toString(),
- methodsUri, 0);
- entry.isPrimary = extras.getBoolean(Insert.POSTAL_ISPRIMARY);
- mPostalEntries.add(entry);
- }
-
- // Email entries from extras
- addEmailFromExtras(extras, methodsUri, Insert.EMAIL, Insert.EMAIL_TYPE,
- Insert.EMAIL_ISPRIMARY);
- addEmailFromExtras(extras, methodsUri, Insert.SECONDARY_EMAIL, Insert.SECONDARY_EMAIL_TYPE,
- null);
- addEmailFromExtras(extras, methodsUri, Insert.TERTIARY_EMAIL, Insert.TERTIARY_EMAIL_TYPE,
- null);
-
- // Phone entries from extras
- addPhoneFromExtras(extras, phonesUri, Insert.PHONE, Insert.PHONE_TYPE,
- Insert.PHONE_ISPRIMARY);
- addPhoneFromExtras(extras, phonesUri, Insert.SECONDARY_PHONE, Insert.SECONDARY_PHONE_TYPE,
- null);
- addPhoneFromExtras(extras, phonesUri, Insert.TERTIARY_PHONE, Insert.TERTIARY_PHONE_TYPE,
- null);
-
- // IM entries from extras
- CharSequence imHandle = extras.getCharSequence(Insert.IM_HANDLE);
- CharSequence imProtocol = extras.getCharSequence(Insert.IM_PROTOCOL);
-
- if (imHandle != null && imProtocol != null) {
- Object protocolObj = ContactMethods.decodeImProtocol(imProtocol.toString());
- if (protocolObj instanceof Number) {
- int protocol = ((Number) protocolObj).intValue();
- entry = EditEntry.newImEntry(this,
- getLabelsForKind(this, Contacts.KIND_IM)[protocol], protocol,
- imHandle.toString(), methodsUri, 0);
- } else {
- entry = EditEntry.newImEntry(this, protocolObj.toString(), -1, imHandle.toString(),
- methodsUri, 0);
- }
- entry.isPrimary = extras.getBoolean(Insert.IM_ISPRIMARY);
- mImEntries.add(entry);
- }
- }
-
- private void addEmailFromExtras(Bundle extras, Uri methodsUri, String emailField,
- String typeField, String primaryField) {
- CharSequence email = extras.getCharSequence(emailField);
-
- // Correctly handle String in typeField as TYPE_CUSTOM
- int emailType = INVALID_TYPE;
- String customLabel = null;
- if(extras.get(typeField) instanceof String) {
- emailType = ContactMethods.TYPE_CUSTOM;
- customLabel = extras.getString(typeField);
- } else {
- emailType = extras.getInt(typeField, INVALID_TYPE);
- }
-
- if (!TextUtils.isEmpty(email) && emailType == INVALID_TYPE) {
- emailType = DEFAULT_EMAIL_TYPE;
- mPrimaryEmailAdded = true;
- }
-
- if (emailType != INVALID_TYPE) {
- EditEntry entry = EditEntry.newEmailEntry(this, customLabel, emailType, email.toString(),
- methodsUri, 0);
- entry.isPrimary = (primaryField == null) ? false : extras.getBoolean(primaryField);
- mEmailEntries.add(entry);
-
- // Keep track of which primary types have been added
- if (entry.isPrimary) {
- mPrimaryEmailAdded = true;
- }
- }
- }
-
- private void addPhoneFromExtras(Bundle extras, Uri phonesUri, String phoneField,
- String typeField, String primaryField) {
- CharSequence phoneNumber = extras.getCharSequence(phoneField);
-
- // Correctly handle String in typeField as TYPE_CUSTOM
- int phoneType = INVALID_TYPE;
- String customLabel = null;
- if(extras.get(typeField) instanceof String) {
- phoneType = Phones.TYPE_CUSTOM;
- customLabel = extras.getString(typeField);
- } else {
- phoneType = extras.getInt(typeField, INVALID_TYPE);
- }
-
- if (!TextUtils.isEmpty(phoneNumber) && phoneType == INVALID_TYPE) {
- phoneType = DEFAULT_PHONE_TYPE;
- }
-
- if (phoneType != INVALID_TYPE) {
- EditEntry entry = EditEntry.newPhoneEntry(this, customLabel, phoneType,
- phoneNumber.toString(), phonesUri, 0);
- entry.isPrimary = (primaryField == null) ? false : extras.getBoolean(primaryField);
- mPhoneEntries.add(entry);
-
- // Keep track of which primary types have been added
- if (phoneType == Phones.TYPE_MOBILE) {
- mMobilePhoneAdded = true;
- }
- }
- }
-
- /**
- * Removes all existing views, builds new ones for all the entries, and adds them.
- */
- private void buildViews() {
- // Remove existing views
- final LinearLayout layout = mLayout;
- layout.removeAllViews();
-
- buildViewsForSection(layout, mPhoneEntries,
- R.string.listSeparatorCallNumber_edit, SECTION_PHONES);
- buildViewsForSection(layout, mEmailEntries,
- R.string.listSeparatorSendEmail_edit, SECTION_EMAIL);
- buildViewsForSection(layout, mImEntries,
- R.string.listSeparatorSendIm_edit, SECTION_IM);
- buildViewsForSection(layout, mPostalEntries,
- R.string.listSeparatorMapAddress_edit, SECTION_POSTAL);
- buildViewsForSection(layout, mOrgEntries,
- R.string.listSeparatorOrganizations, SECTION_ORG);
- buildViewsForSection(layout, mNoteEntries,
- R.string.label_notes, SECTION_NOTE);
-
- buildOtherViews(layout, mOtherEntries);
- }
-
-
- /**
- * Builds the views for a specific section.
- *
- * @param layout the container
- * @param section the section to build the views for
- */
- private void buildViewsForSection(final LinearLayout layout, ArrayList<EditEntry> section,
- int separatorResource, int sectionType) {
-
- View divider = mInflater.inflate(R.layout.edit_divider, layout, false);
- layout.addView(divider);
-
- // Count up undeleted children
- int activeChildren = 0;
- for (int i = section.size() - 1; i >= 0; i--) {
- EditEntry entry = section.get(i);
- if (!entry.isDeleted) {
- activeChildren++;
- }
- }
-
- // Build the correct group header based on undeleted children
- ViewGroup header;
- if (activeChildren == 0) {
- header = (ViewGroup) mInflater.inflate(R.layout.edit_separator_alone, layout, false);
- } else {
- header = (ViewGroup) mInflater.inflate(R.layout.edit_separator, layout, false);
- }
-
- // Because we're emulating a ListView, we need to handle focus changes
- // with some additional logic.
- header.setOnFocusChangeListener(this);
-
- TextView text = (TextView) header.findViewById(R.id.text);
- text.setText(getText(separatorResource));
-
- // Force TextView to always default color if we have children. This makes sure
- // we don't change color when parent is pressed.
- if (activeChildren > 0) {
- ColorStateList stateList = text.getTextColors();
- text.setTextColor(stateList.getDefaultColor());
- }
-
- View addView = header.findViewById(R.id.separator);
- addView.setTag(Integer.valueOf(sectionType));
- addView.setOnClickListener(this);
-
- // Build views for the current section
- for (EditEntry entry : section) {
- entry.activity = this; // this could be null from when the state is restored
- if (!entry.isDeleted) {
- View view = buildViewForEntry(entry);
- header.addView(view);
- }
- }
-
- layout.addView(header);
- }
-
- private void buildOtherViews(final LinearLayout layout, ArrayList<EditEntry> section) {
- // Build views for the current section, putting a divider between each one
- for (EditEntry entry : section) {
- View divider = mInflater.inflate(R.layout.edit_divider, layout, false);
- layout.addView(divider);
-
- entry.activity = this; // this could be null from when the state is restored
- View view = buildViewForEntry(entry);
- view.setOnClickListener(this);
- layout.addView(view);
- }
-
- View divider = mInflater.inflate(R.layout.edit_divider, layout, false);
- layout.addView(divider);
- }
-
- /**
- * Builds a view to display an EditEntry.
- *
- * @param entry the entry to display
- * @return a view that will display the given entry
- */
- /* package */ View buildViewForEntry(final EditEntry entry) {
- // Look for any existing entered text, and save it if found
- if (entry.view != null && entry.syncDataWithView) {
- String enteredText = ((TextView) entry.view.findViewById(R.id.data))
- .getText().toString();
- if (!TextUtils.isEmpty(enteredText)) {
- entry.data = enteredText;
- }
- }
-
- // Build a new view
- final ViewGroup parent = mLayout;
- View view;
-
- // Because we're emulating a ListView, we might need to handle focus changes
- // with some additional logic.
- if (entry.kind == Contacts.KIND_ORGANIZATION) {
- view = mInflater.inflate(R.layout.edit_contact_entry_org, parent, false);
- } else if (isOtherEntry(entry, People.CUSTOM_RINGTONE)) {
- view = mInflater.inflate(R.layout.edit_contact_entry_ringtone, parent, false);
- view.setOnFocusChangeListener(this);
- } else if (isOtherEntry(entry, People.SEND_TO_VOICEMAIL)) {
- view = mInflater.inflate(R.layout.edit_contact_entry_voicemail, parent, false);
- view.setOnFocusChangeListener(this);
- } else if (!entry.isStaticLabel) {
- view = mInflater.inflate(R.layout.edit_contact_entry, parent, false);
- } else {
- view = mInflater.inflate(R.layout.edit_contact_entry_static_label, parent, false);
- }
- entry.view = view;
-
- // Set the entry as the tag so we can find it again later given just the view
- view.setTag(entry);
-
- // Bind the label
- entry.bindLabel(this);
-
- // Bind data
- TextView data = (TextView) view.findViewById(R.id.data);
- TextView data2 = (TextView) view.findViewById(R.id.data2);
-
- if (data instanceof Button) {
- data.setOnClickListener(this);
- }
- if (data.length() == 0) {
- if (entry.syncDataWithView) {
- // If there is already data entered don't overwrite it
- data.setText(entry.data);
- } else {
- fillViewData(entry);
- }
- }
- if (data2 != null && data2.length() == 0) {
- // If there is already data entered don't overwrite it
- data2.setText(entry.data2);
- }
- data.setHint(entry.hint);
- if (data2 != null) data2.setHint(entry.hint2);
- if (entry.lines > 1) {
- data.setLines(entry.lines);
- data.setMaxLines(entry.maxLines);
- if (data2 != null) {
- data2.setLines(entry.lines);
- data2.setMaxLines(entry.maxLines);
- }
- }
- int contentType = entry.contentType;
- if (contentType != EditorInfo.TYPE_NULL) {
- data.setInputType(contentType);
- if (data2 != null) {
- data2.setInputType(contentType);
- }
- if ((contentType&EditorInfo.TYPE_MASK_CLASS)
- == EditorInfo.TYPE_CLASS_PHONE) {
- data.addTextChangedListener(new PhoneNumberFormattingTextWatcher());
- if (data2 != null) {
- data2.addTextChangedListener(new PhoneNumberFormattingTextWatcher());
- }
- }
- }
-
- // Give focus to children as requested, possibly after a configuration change
- View focusChild = view.findViewById(entry.requestFocusId);
- if (focusChild != null) {
- focusChild.requestFocus();
- if (focusChild instanceof EditText) {
- ((EditText) focusChild).setSelection(entry.requestCursor);
- }
- }
-
- // Reset requested focus values
- entry.requestFocusId = View.NO_ID;
- entry.requestCursor = 0;
-
- // Connect listeners up to watch for changed values.
- if (data instanceof EditText) {
- data.addTextChangedListener(this);
- }
- if (data2 instanceof EditText) {
- data2.addTextChangedListener(this);
- }
-
- // Hook up the delete button
- View delete = view.findViewById(R.id.delete);
- if (delete != null) delete.setOnClickListener(this);
-
- return view;
- }
-
- private void fillViewData(final EditEntry entry) {
- if (isOtherEntry(entry, People.CUSTOM_RINGTONE)) {
- updateRingtoneView(entry);
- } else if (isOtherEntry(entry, People.SEND_TO_VOICEMAIL)) {
- CheckBox checkBox = (CheckBox) entry.view.findViewById(R.id.checkbox);
- boolean sendToVoicemail = false;
- if (entry.data != null) {
- sendToVoicemail = (Integer.valueOf(entry.data) == 1);
- }
- checkBox.setChecked(sendToVoicemail);
- }
- }
-
- /**
- * Handles the results from the label change picker.
- */
- private final class LabelPickedListener implements DialogInterface.OnClickListener {
- EditEntry mEntry;
- String[] mLabels;
-
- public LabelPickedListener(EditEntry entry, String[] labels) {
- mEntry = entry;
- mLabels = labels;
- }
-
- public void onClick(DialogInterface dialog, int which) {
- // TODO: Use a managed dialog
- if (mEntry.kind != Contacts.KIND_IM) {
- final int type = getTypeFromLabelPosition(mLabels, which);
- if (type == ContactMethods.TYPE_CUSTOM) {
- createCustomPicker(mEntry, null);
- } else {
- mEntry.setLabel(EditContactActivity.this, type, mLabels[which]);
- mContactChanged = true;
- }
- } else {
- mEntry.setLabel(EditContactActivity.this, which, mLabels[which]);
- mContactChanged = true;
- }
- }
- }
-
- /**
- * A basic structure with the data for a contact entry in the list.
- */
- private static final class EditEntry extends ContactEntryAdapter.Entry implements Parcelable {
- // These aren't stuffed into the parcel
- public EditContactActivity activity;
- public View view;
-
- // These are stuffed into the parcel
- public String hint;
- public String hint2;
- public String column;
- public String contentDirectory;
- public String data2;
- public int contentType;
- public int type;
- /**
- * If 0 or 1, setSingleLine will be called. If negative, setSingleLine
- * will not be called.
- */
- public int lines = 1;
- public boolean isPrimary;
- public boolean isDeleted = false;
- public boolean isStaticLabel = false;
- public boolean syncDataWithView = true;
-
- /**
- * Request focus on the child of this {@link EditEntry} found using
- * {@link View#findViewById(int)}. This value should be reset to
- * {@link View#NO_ID} after each use.
- */
- public int requestFocusId = View.NO_ID;
-
- /**
- * If the {@link #requestFocusId} is an {@link EditText}, this value
- * indicates the requested cursor position placement.
- */
- public int requestCursor = 0;
-
- private EditEntry() {
- // only used by CREATOR
- }
-
- public EditEntry(EditContactActivity activity) {
- this.activity = activity;
- }
-
- public EditEntry(EditContactActivity activity, String label,
- int type, String data, Uri uri, long id) {
- this.activity = activity;
- this.isPrimary = false;
- this.label = label;
- this.type = type;
- this.data = data;
- this.uri = uri;
- this.id = id;
- }
-
- public int describeContents() {
- return 0;
- }
-
- public void writeToParcel(Parcel parcel, int flags) {
- // Make sure to read data from the input field, if anything is entered
- data = getData();
-
- // Write in our own fields.
- parcel.writeString(hint);
- parcel.writeString(hint2);
- parcel.writeString(column);
- parcel.writeString(contentDirectory);
- parcel.writeString(data2);
- parcel.writeInt(contentType);
- parcel.writeInt(type);
- parcel.writeInt(lines);
- parcel.writeInt(isPrimary ? 1 : 0);
- parcel.writeInt(isDeleted ? 1 : 0);
- parcel.writeInt(isStaticLabel ? 1 : 0);
- parcel.writeInt(syncDataWithView ? 1 : 0);
-
- // Write in the fields from Entry
- super.writeToParcel(parcel);
- }
-
- public static final Parcelable.Creator<EditEntry> CREATOR =
- new Parcelable.Creator<EditEntry>() {
- public EditEntry createFromParcel(Parcel in) {
- EditEntry entry = new EditEntry();
-
- // Read out our own fields
- entry.hint = in.readString();
- entry.hint2 = in.readString();
- entry.column = in.readString();
- entry.contentDirectory = in.readString();
- entry.data2 = in.readString();
- entry.contentType = in.readInt();
- entry.type = in.readInt();
- entry.lines = in.readInt();
- entry.isPrimary = in.readInt() == 1;
- entry.isDeleted = in.readInt() == 1;
- entry.isStaticLabel = in.readInt() == 1;
- entry.syncDataWithView = in.readInt() == 1;
-
- // Read out the fields from Entry
- entry.readFromParcel(in);
-
- return entry;
- }
-
- public EditEntry[] newArray(int size) {
- return new EditEntry[size];
- }
- };
-
- public void setLabel(Context context, int typeIn, String labelIn) {
- type = typeIn;
- label = labelIn;
- if (view != null) {
- bindLabel(context);
- }
- }
-
- public void bindLabel(Context context) {
- TextView v = (TextView) view.findViewById(R.id.label);
- if (isStaticLabel) {
- v.setText(label);
- return;
- }
-
- switch (kind) {
- case Contacts.KIND_PHONE: {
- v.setText(Phones.getDisplayLabel(context, type, label));
- break;
- }
-
- case Contacts.KIND_IM: {
- v.setText(getLabelsForKind(activity, kind)[type]);
- break;
- }
-
- case Contacts.KIND_ORGANIZATION: {
- v.setText(Organizations.getDisplayLabel(activity, type, label));
- break;
- }
-
- default: {
- v.setText(Contacts.ContactMethods.getDisplayLabel(context, kind, type, label));
- if (kind == Contacts.KIND_POSTAL) {
- v.setMaxLines(3);
- }
- break;
- }
- }
- v.setOnClickListener(activity);
- }
-
- /**
- * Returns the data for the entry
- * @return the data for the entry
- */
- public String getData() {
- if (view != null && syncDataWithView) {
- CharSequence text = ((TextView) view.findViewById(R.id.data)).getText();
- if (text != null) {
- return text.toString();
- }
- }
-
- if (data != null) {
- return data.toString();
- }
-
- return null;
- }
-
- /**
- * Dumps the entry into a HashMap suitable for passing to the database.
- *
- * @param values the HashMap to fill in.
- * @return true if the value should be saved, false otherwise
- */
- public boolean toValues(ContentValues values) {
- boolean success = false;
- String labelString = null;
- // Save the type and label
- if (view != null) {
- // Read the possibly updated label from the text field
- labelString = ((TextView) view.findViewById(R.id.label)).getText().toString();
- }
- switch (kind) {
- case Contacts.KIND_PHONE:
- if (type != Phones.TYPE_CUSTOM) {
- labelString = null;
- }
- values.put(Phones.LABEL, labelString);
- values.put(Phones.TYPE, type);
- break;
-
- case Contacts.KIND_EMAIL:
- if (type != ContactMethods.TYPE_CUSTOM) {
- labelString = null;
- }
- values.put(ContactMethods.LABEL, labelString);
- values.put(ContactMethods.KIND, kind);
- values.put(ContactMethods.TYPE, type);
- break;
-
- case Contacts.KIND_IM:
- values.put(ContactMethods.KIND, kind);
- values.put(ContactMethods.TYPE, ContactMethods.TYPE_OTHER);
- values.putNull(ContactMethods.LABEL);
- if (type != -1) {
- values.put(ContactMethods.AUX_DATA,
- ContactMethods.encodePredefinedImProtocol(type));
- } else {
- values.put(ContactMethods.AUX_DATA,
- ContactMethods.encodeCustomImProtocol(label.toString()));
- }
- break;
-
- case Contacts.KIND_POSTAL:
- if (type != ContactMethods.TYPE_CUSTOM) {
- labelString = null;
- }
- values.put(ContactMethods.LABEL, labelString);
- values.put(ContactMethods.KIND, kind);
- values.put(ContactMethods.TYPE, type);
- break;
-
- case Contacts.KIND_ORGANIZATION:
- if (type != ContactMethods.TYPE_CUSTOM) {
- labelString = null;
- }
- values.put(ContactMethods.LABEL, labelString);
- values.put(ContactMethods.TYPE, type);
- // Save the title
- if (view != null) {
- // Read the possibly updated data from the text field
- data2 = ((TextView) view.findViewById(R.id.data2)).getText().toString();
- }
- if (!TextUtils.isGraphic(data2)) {
- values.putNull(Organizations.TITLE);
- } else {
- values.put(Organizations.TITLE, data2.toString());
- success = true;
- }
- break;
-
- default:
- Log.w(TAG, "unknown kind " + kind);
- values.put(ContactMethods.LABEL, labelString);
- values.put(ContactMethods.KIND, kind);
- values.put(ContactMethods.TYPE, type);
- break;
- }
-
- // Only set the ISPRIMARY flag if part of the incoming data. This is because the
- // ContentProvider will try finding a new primary when setting to false, meaning
- // it's possible to lose primary altogether as we walk down the list. If this editor
- // implements editing of primaries in the future, this will need to be revisited.
- if (isPrimary) {
- values.put(ContactMethods.ISPRIMARY, 1);
- }
-
- // Save the data
- if (view != null && syncDataWithView) {
- // Read the possibly updated data from the text field
- data = ((TextView) view.findViewById(R.id.data)).getText().toString();
- }
- if (!TextUtils.isGraphic(data)) {
- values.putNull(column);
- return success;
- } else {
- values.put(column, data.toString());
- return true;
- }
- }
-
- /**
- * Create a new empty organization entry
- */
- public static final EditEntry newOrganizationEntry(EditContactActivity activity,
- Uri uri, int type) {
- return newOrganizationEntry(activity, null, type, null, null, uri, 0);
- }
-
- /**
- * Create a new company entry with the given data.
- */
- public static final EditEntry newOrganizationEntry(EditContactActivity activity,
- String label, int type, String company, String title, Uri uri, long id) {
- EditEntry entry = new EditEntry(activity, label, type, company, uri, id);
- entry.hint = activity.getString(R.string.ghostData_company);
- entry.hint2 = activity.getString(R.string.ghostData_title);
- entry.data2 = title;
- entry.column = Organizations.COMPANY;
- entry.contentDirectory = Organizations.CONTENT_DIRECTORY;
- entry.kind = Contacts.KIND_ORGANIZATION;
- entry.contentType = EditorInfo.TYPE_CLASS_TEXT
- | EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
- return entry;
- }
-
- /**
- * Create a new notes entry with the given data.
- */
- public static final EditEntry newNotesEntry(EditContactActivity activity,
- String data, Uri uri) {
- EditEntry entry = new EditEntry(activity);
- entry.label = activity.getString(R.string.label_notes);
- entry.hint = activity.getString(R.string.ghostData_notes);
- entry.data = data;
- entry.uri = uri;
- entry.column = People.NOTES;
- entry.maxLines = 10;
- entry.lines = 2;
- entry.id = 0;
- entry.kind = KIND_CONTACT;
- entry.contentType = EditorInfo.TYPE_CLASS_TEXT
- | EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES
- | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
- entry.isStaticLabel = true;
- return entry;
- }
-
- /**
- * Create a new ringtone entry with the given data.
- */
- public static final EditEntry newRingtoneEntry(EditContactActivity activity,
- String data, Uri uri) {
- EditEntry entry = new EditEntry(activity);
- entry.label = activity.getString(R.string.label_ringtone);
- entry.data = data;
- entry.uri = uri;
- entry.column = People.CUSTOM_RINGTONE;
- entry.kind = KIND_CONTACT;
- entry.isStaticLabel = true;
- entry.syncDataWithView = false;
- entry.lines = -1;
- return entry;
- }
-
- /**
- * Create a new send-to-voicemail entry with the given data.
- */
- public static final EditEntry newSendToVoicemailEntry(EditContactActivity activity,
- String data, Uri uri) {
- EditEntry entry = new EditEntry(activity);
- entry.label = activity.getString(R.string.actionIncomingCall);
- entry.data = data;
- entry.uri = uri;
- entry.column = People.SEND_TO_VOICEMAIL;
- entry.kind = KIND_CONTACT;
- entry.isStaticLabel = true;
- entry.syncDataWithView = false;
- entry.lines = -1;
- return entry;
- }
-
- /**
- * Create a new empty email entry
- */
- public static final EditEntry newPhoneEntry(EditContactActivity activity,
- Uri uri, int type) {
- return newPhoneEntry(activity, null, type, null, uri, 0);
- }
-
- /**
- * Create a new phone entry with the given data.
- */
- public static final EditEntry newPhoneEntry(EditContactActivity activity,
- String label, int type, String data, Uri uri,
- long id) {
- EditEntry entry = new EditEntry(activity, label, type, data, uri, id);
- entry.hint = activity.getString(R.string.ghostData_phone);
- entry.column = People.Phones.NUMBER;
- entry.contentDirectory = People.Phones.CONTENT_DIRECTORY;
- entry.kind = Contacts.KIND_PHONE;
- entry.contentType = EditorInfo.TYPE_CLASS_PHONE;
- return entry;
- }
-
- /**
- * Create a new empty email entry
- */
- public static final EditEntry newEmailEntry(EditContactActivity activity,
- Uri uri, int type) {
- return newEmailEntry(activity, null, type, null, uri, 0);
- }
-
- /**
- * Create a new email entry with the given data.
- */
- public static final EditEntry newEmailEntry(EditContactActivity activity,
- String label, int type, String data, Uri uri,
- long id) {
- EditEntry entry = new EditEntry(activity, label, type, data, uri, id);
- entry.hint = activity.getString(R.string.ghostData_email);
- entry.column = ContactMethods.DATA;
- entry.contentDirectory = People.ContactMethods.CONTENT_DIRECTORY;
- entry.kind = Contacts.KIND_EMAIL;
- entry.contentType = EditorInfo.TYPE_CLASS_TEXT
- | EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS;
- return entry;
- }
-
- /**
- * Create a new empty postal address entry
- */
- public static final EditEntry newPostalEntry(EditContactActivity activity,
- Uri uri, int type) {
- return newPostalEntry(activity, null, type, null, uri, 0);
- }
-
- /**
- * Create a new postal address entry with the given data.
- *
- * @param label label for the item, from the db not the display label
- * @param type the type of postal address
- * @param data the starting data for the entry, may be null
- * @param uri the uri for the entry if it already exists, may be null
- * @param id the id for the entry if it already exists, 0 it it doesn't
- * @return the new EditEntry
- */
- public static final EditEntry newPostalEntry(EditContactActivity activity,
- String label, int type, String data, Uri uri, long id) {
- EditEntry entry = new EditEntry(activity, label, type, data, uri, id);
- entry.hint = activity.getString(R.string.ghostData_postal);
- entry.column = ContactMethods.DATA;
- entry.contentDirectory = People.ContactMethods.CONTENT_DIRECTORY;
- entry.kind = Contacts.KIND_POSTAL;
- entry.contentType = EditorInfo.TYPE_CLASS_TEXT
- | EditorInfo.TYPE_TEXT_VARIATION_POSTAL_ADDRESS
- | EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS
- | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
- entry.maxLines = 4;
- entry.lines = 2;
- return entry;
- }
-
- /**
- * Create a new IM address entry
- */
- public static final EditEntry newImEntry(EditContactActivity activity,
- Uri uri, int type) {
- return newImEntry(activity, null, type, null, uri, 0);
- }
-
- /**
- * Create a new IM address entry with the given data.
- *
- * @param label label for the item, from the db not the display label
- * @param protocol the type used
- * @param data the starting data for the entry, may be null
- * @param uri the uri for the entry if it already exists, may be null
- * @param id the id for the entry if it already exists, 0 it it doesn't
- * @return the new EditEntry
- */
- public static final EditEntry newImEntry(EditContactActivity activity,
- String label, int protocol, String data, Uri uri, long id) {
- EditEntry entry = new EditEntry(activity, label, protocol, data, uri, id);
- entry.hint = activity.getString(R.string.ghostData_im);
- entry.column = ContactMethods.DATA;
- entry.contentDirectory = People.ContactMethods.CONTENT_DIRECTORY;
- entry.kind = Contacts.KIND_IM;
- entry.contentType = EditorInfo.TYPE_CLASS_TEXT
- | EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS;
- return entry;
- }
- }
-
- public void afterTextChanged(Editable s) {
- // Someone edited a text field, so assume this contact is changed
- mContactChanged = true;
- }
-
- public void beforeTextChanged(CharSequence s, int start, int count, int after) {
- // Do nothing; editing handled by afterTextChanged()
- }
-
- public void onTextChanged(CharSequence s, int start, int before, int count) {
- // Do nothing; editing handled by afterTextChanged()
- }
-
- public void onFocusChange(View v, boolean hasFocus) {
- // Because we're emulating a ListView, we need to setSelected() for
- // views as they are focused.
- v.setSelected(hasFocus);
- }
-}
diff --git a/src/com/android/contacts/ExportVCardActivity.java b/src/com/android/contacts/ExportVCardActivity.java
new file mode 100644
index 0000000..baf2371
--- /dev/null
+++ b/src/com/android/contacts/ExportVCardActivity.java
@@ -0,0 +1,454 @@
+/*
+ * 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.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.PowerManager;
+import android.pim.vcard.VCardComposer;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.OutputStream;
+import java.util.HashSet;
+import java.util.Set;
+
+public class ExportVCardActivity extends Activity {
+ private static final String LOG_TAG = "ExportVCardActivity";
+
+ // If true, VCardExporter is able to emits files longer than 8.3 format.
+ private static final boolean ALLOW_LONG_FILE_NAME = false;
+ private String mTargetDirectory;
+ private String mFileNamePrefix;
+ private String mFileNameSuffix;
+ 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
+ private String mTargetFileName;
+
+ // String for storing error reason temporaly.
+ private String mErrorReason;
+
+ private ActualExportThread mActualExportThread;
+
+ 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 ErrorReasonDisplayer implements Runnable {
+ private final int mResId;
+ public ErrorReasonDisplayer(int resId) {
+ mResId = resId;
+ }
+ public ErrorReasonDisplayer(String errorReason) {
+ mResId = R.id.dialog_fail_to_export_with_reason;
+ mErrorReason = errorReason;
+ }
+ public void run() {
+ // Show the Dialog only when the parent Activity is still alive.
+ if (!ExportVCardActivity.this.isFinishing()) {
+ showDialog(mResId);
+ }
+ }
+ }
+
+ private class ExportConfirmationListener implements DialogInterface.OnClickListener {
+ private final String mFileName;
+
+ public ExportConfirmationListener(String fileName) {
+ mFileName = fileName;
+ }
+
+ public void onClick(DialogInterface dialog, int which) {
+ if (which == DialogInterface.BUTTON_POSITIVE) {
+ mActualExportThread = new ActualExportThread(mFileName);
+ mActualExportThread.start();
+ showDialog(R.id.dialog_exporting_vcard);
+ }
+ }
+ }
+
+ 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)) {
+ 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;
+ }
+ }
+
+ @Override
+ protected void onCreate(Bundle bundle) {
+ super.onCreate(bundle);
+
+ mTargetDirectory = getString(R.string.config_export_dir);
+ 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);
+
+ final String additionalExtensions =
+ getString(R.string.config_export_extensions_to_consider);
+ if (!TextUtils.isEmpty(additionalExtensions)) {
+ for (String extension : additionalExtensions.split(",")) {
+ String trimed = extension.trim();
+ if (trimed.length() > 0) {
+ mExtensionsToConsider.add(trimed);
+ }
+ }
+ }
+
+ final Resources resources = getResources();
+ mFileIndexMinimum = resources.getInteger(R.integer.config_export_file_min_index);
+ mFileIndexMaximum = resources.getInteger(R.integer.config_export_file_max_index);
+
+ startExportVCardToSdCard();
+ }
+
+ @Override
+ protected Dialog onCreateDialog(int id) {
+ switch (id) {
+ case R.id.dialog_export_confirmation: {
+ return getExportConfirmationDialog();
+ }
+ case R.string.fail_reason_too_many_vcard: {
+ return new AlertDialog.Builder(this)
+ .setTitle(R.string.exporting_contact_failed_title)
+ .setMessage(getString(R.string.exporting_contact_failed_message,
+ getString(R.string.fail_reason_too_many_vcard)))
+ .setPositiveButton(android.R.string.ok, mCancelListener)
+ .create();
+ }
+ case R.id.dialog_fail_to_export_with_reason: {
+ return getErrorDialogWithReason();
+ }
+ 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)
+ .setPositiveButton(android.R.string.ok, mCancelListener);
+ return builder.create();
+ }
+ case R.id.dialog_exporting_vcard: {
+ return getExportingVCardDialog();
+ }
+ }
+ return super.onCreateDialog(id);
+ }
+
+ private Dialog getExportingVCardDialog() {
+ 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);
+ }
+ return mProgressDialog;
+ }
+
+ @Override
+ protected void onPrepareDialog(int id, Dialog dialog) {
+ 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);
+ }
+ }
+
+ @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();
+ }
+ }
+
+ /**
+ * Tries to start exporting VCard. If there's no SDCard available,
+ * an error dialog is shown.
+ */
+ public void startExportVCardToSdCard() {
+ File targetDirectory = new File(mTargetDirectory);
+
+ if (!(targetDirectory.exists() &&
+ targetDirectory.isDirectory() &&
+ targetDirectory.canRead()) &&
+ !targetDirectory.mkdirs()) {
+ showDialog(R.id.dialog_sdcard_not_found);
+ } else {
+ mTargetFileName = getAppropriateFileName(mTargetDirectory);
+ if (TextUtils.isEmpty(mTargetFileName)) {
+ mTargetFileName = null;
+ // finish() is called via the error dialog. Do not call the method here.
+ return;
+ }
+
+ showDialog(R.id.dialog_export_confirmation);
+ }
+ }
+
+ /**
+ * Tries to get an appropriate filename. Returns null if it fails.
+ */
+ private String getAppropriateFileName(final String destDirectory) {
+ int fileNumberStringLength = 0;
+ {
+ // Calling Math.Log10() is costly.
+ int tmp;
+ for (fileNumberStringLength = 0, tmp = mFileIndexMaximum; tmp > 0;
+ fileNumberStringLength++, tmp /= 10) {
+ }
+ }
+ String bodyFormat = "%s%0" + fileNumberStringLength + "d%s";
+
+ if (!ALLOW_LONG_FILE_NAME) {
+ String possibleBody = String.format(bodyFormat,mFileNamePrefix, 1, mFileNameSuffix);
+ if (possibleBody.length() > 8 || mFileNameExtension.length() > 3) {
+ Log.e(LOG_TAG, "This code does not allow any long file name.");
+ mErrorReason = getString(R.string.fail_reason_too_long_filename,
+ String.format("%s.%s", possibleBody, mFileNameExtension));
+ showDialog(R.id.dialog_fail_to_export_with_reason);
+ // finish() is called via the error dialog. Do not call the method here.
+ return null;
+ }
+ }
+
+ // Note that this logic assumes that the target directory is case insensitive.
+ // As of 2009-07-16, it is true since the external storage is only sdcard, and
+ // it is formated as FAT/VFAT.
+ // TODO: fix this.
+ for (int i = mFileIndexMinimum; i <= mFileIndexMaximum; i++) {
+ boolean numberIsAvailable = true;
+ // SD Association's specification seems to require this feature, though we cannot
+ // have the specification since it is proprietary...
+ String body = null;
+ for (String possibleExtension : mExtensionsToConsider) {
+ body = String.format(bodyFormat, mFileNamePrefix, i, mFileNameSuffix);
+ File file = new File(String.format("%s/%s.%s",
+ destDirectory, body, possibleExtension));
+ if (file.exists()) {
+ numberIsAvailable = false;
+ break;
+ }
+ }
+ if (numberIsAvailable) {
+ return String.format("%s/%s.%s", destDirectory, body, mFileNameExtension);
+ }
+ }
+ showDialog(R.string.fail_reason_too_many_vcard);
+ return null;
+ }
+
+ public Dialog getExportConfirmationDialog() {
+ if (TextUtils.isEmpty(mTargetFileName)) {
+ Log.e(LOG_TAG, "Target file name is empty, which must not be!");
+ // This situation is not acceptable (probably a bug!), but we don't have no reason to
+ // show...
+ mErrorReason = null;
+ return getErrorDialogWithReason();
+ }
+
+ return new AlertDialog.Builder(this)
+ .setTitle(R.string.confirm_export_title)
+ .setMessage(getString(R.string.confirm_export_message, mTargetFileName))
+ .setPositiveButton(android.R.string.ok,
+ new ExportConfirmationListener(mTargetFileName))
+ .setNegativeButton(android.R.string.cancel, mCancelListener)
+ .setOnCancelListener(mCancelListener)
+ .create();
+ }
+
+ public Dialog getErrorDialogWithReason() {
+ if (mErrorReason == null) {
+ Log.e(LOG_TAG, "Error reason must have been set.");
+ mErrorReason = getString(R.string.fail_reason_unknown);
+ }
+ return new AlertDialog.Builder(this)
+ .setTitle(R.string.exporting_contact_failed_title)
+ .setMessage(getString(R.string.exporting_contact_failed_message, mErrorReason))
+ .setPositiveButton(android.R.string.ok, mCancelListener)
+ .setOnCancelListener(mCancelListener)
+ .create();
+ }
+
+ public void cancelExport() {
+ if (mActualExportThread != null) {
+ mActualExportThread.cancel();
+ mActualExportThread = null;
+ }
+ }
+
+ public String getErrorReason() {
+ return mErrorReason;
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/contacts/ImportVCardActivity.java b/src/com/android/contacts/ImportVCardActivity.java
index 07eb821..8fd9c0d 100644
--- a/src/com/android/contacts/ImportVCardActivity.java
+++ b/src/com/android/contacts/ImportVCardActivity.java
@@ -16,30 +16,36 @@
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.Context;
import android.content.DialogInterface;
+import android.content.Intent;
import android.content.DialogInterface.OnCancelListener;
import android.content.DialogInterface.OnClickListener;
import android.os.Bundle;
import android.os.Handler;
import android.os.PowerManager;
-import android.syncml.pim.VBuilder;
-import android.syncml.pim.VBuilderCollection;
-import android.syncml.pim.VParser;
-import android.syncml.pim.vcard.VCardDataBuilder;
-import android.syncml.pim.vcard.VCardEntryCounter;
-import android.syncml.pim.vcard.VCardException;
-import android.syncml.pim.vcard.VCardNestedException;
-import android.syncml.pim.vcard.VCardParser_V21;
-import android.syncml.pim.vcard.VCardParser_V30;
-import android.syncml.pim.vcard.VCardSourceDetector;
-import android.syncml.pim.vcard.VCardVersionException;
+import android.pim.vcard.EntryCommitter;
+import android.pim.vcard.VCardBuilder;
+import android.pim.vcard.VCardBuilderCollection;
+import android.pim.vcard.VCardConfig;
+import android.pim.vcard.VCardDataBuilder;
+import android.pim.vcard.VCardEntryCounter;
+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.text.SpannableStringBuilder;
import android.text.Spanned;
+import android.text.TextUtils;
import android.text.style.RelativeSizeSpan;
import android.util.Log;
@@ -48,10 +54,12 @@
import java.io.IOException;
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;
@@ -87,9 +95,34 @@
private static final String LOG_TAG = "ImportVCardActivity";
private static final boolean DO_PERFORMANCE_PROFILE = false;
- private ProgressDialog mProgressDialog;
private Handler mHandler = new Handler();
- private boolean mLastNameComesBeforeFirstName;
+ private Account mAccount;
+
+ private ProgressDialog mProgressDialogForScanVCard;
+
+ private List<VCardFile> mAllVCardFileList;
+ private VCardScanThread mVCardScanThread;
+ private VCardReadThread mVCardReadThread;
+ private ProgressDialog mProgressDialogForReadVCard;
+
+ private String mErrorMessage;
+
+ 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() {
+ // Show the Dialog only when the parent Activity is still alive.
+ if (!ImportVCardActivity.this.isFinishing()) {
+ showDialog(mResId);
+ }
+ }
+ }
private class CancelListener
implements DialogInterface.OnClickListener, DialogInterface.OnCancelListener {
@@ -104,45 +137,26 @@
private CancelListener mCancelListener = new CancelListener();
- private class ErrorDisplayer implements Runnable {
- private String mErrorMessage;
-
- public ErrorDisplayer(String errorMessage) {
- mErrorMessage = errorMessage;
- }
-
- public void run() {
- String message =
- getString(R.string.reading_vcard_failed_message, mErrorMessage);
- AlertDialog.Builder builder =
- new AlertDialog.Builder(ImportVCardActivity.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);
- builder.show();
- }
- }
-
private class VCardReadThread extends Thread
implements DialogInterface.OnCancelListener {
- private String mCanonicalPath;
- private List<VCardFile> mVCardFileList;
private ContentResolver mResolver;
private VCardParser_V21 mVCardParser;
private boolean mCanceled;
private PowerManager.WakeLock mWakeLock;
+ private String mCanonicalPath;
+
+ private List<VCardFile> mSelectedVCardFileList;
+ private List<String> mErrorFileNameList;
public VCardReadThread(String canonicalPath) {
mCanonicalPath = canonicalPath;
- mVCardFileList = null;
init();
}
- public VCardReadThread(List<VCardFile> vcardFileList) {
+ public VCardReadThread(final List<VCardFile> selectedVCardFileList) {
mCanonicalPath = null;
- mVCardFileList = vcardFileList;
+ mSelectedVCardFileList = selectedVCardFileList;
+ mErrorFileNameList = new ArrayList<String>();
init();
}
@@ -165,35 +179,37 @@
@Override
public void run() {
+ boolean shouldCallFinish = true;
mWakeLock.acquire();
// Some malicious vCard data may make this thread broken
// (e.g. OutOfMemoryError).
// Even in such cases, some should be done.
try {
- if (mCanonicalPath != null) {
- mProgressDialog.setProgressNumberFormat("");
- mProgressDialog.setProgress(0);
+ if (mCanonicalPath != null) { // Read one file
+ mProgressDialogForReadVCard.setProgressNumberFormat("");
+ mProgressDialogForReadVCard.setProgress(0);
// Count the number of VCard entries
- mProgressDialog.setIndeterminate(true);
+ mProgressDialogForReadVCard.setIndeterminate(true);
long start;
if (DO_PERFORMANCE_PROFILE) {
start = System.currentTimeMillis();
}
VCardEntryCounter counter = new VCardEntryCounter();
VCardSourceDetector detector = new VCardSourceDetector();
- VBuilderCollection builderCollection = new VBuilderCollection(
+ VCardBuilderCollection builderCollection = new VCardBuilderCollection(
Arrays.asList(counter, detector));
+
boolean result;
try {
- result = readOneVCard(mCanonicalPath,
- VParser.DEFAULT_CHARSET, builderCollection, null, true);
+ result = readOneVCardFile(mCanonicalPath,
+ 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 = readOneVCard(mCanonicalPath,
- VParser.DEFAULT_CHARSET, counter, detector, false);
+ result = readOneVCardFile(mCanonicalPath,
+ VCardConfig.DEFAULT_CHARSET, counter, detector, false, null);
} catch (VCardNestedException e2) {
result = false;
Log.e(LOG_TAG, "Must not reach here. " + e2);
@@ -205,21 +221,24 @@
time + " ms");
}
if (!result) {
+ shouldCallFinish = false;
return;
}
- mProgressDialog.setProgressNumberFormat(
+ mProgressDialogForReadVCard.setProgressNumberFormat(
getString(R.string.reading_vcard_contacts));
- mProgressDialog.setIndeterminate(false);
- mProgressDialog.setMax(counter.getCount());
+ mProgressDialogForReadVCard.setIndeterminate(false);
+ mProgressDialogForReadVCard.setMax(counter.getCount());
String charset = detector.getEstimatedCharset();
- doActuallyReadOneVCard(charset, true, detector);
- } else {
- mProgressDialog.setProgressNumberFormat(
+ doActuallyReadOneVCard(mCanonicalPath, null, charset, true, detector,
+ mErrorFileNameList);
+ } else { // Read multiple files.
+ mProgressDialogForReadVCard.setProgressNumberFormat(
getString(R.string.reading_vcard_files));
- mProgressDialog.setMax(mVCardFileList.size());
- mProgressDialog.setProgress(0);
- for (VCardFile vcardFile : mVCardFileList) {
+ mProgressDialogForReadVCard.setMax(mSelectedVCardFileList.size());
+ mProgressDialogForReadVCard.setProgress(0);
+
+ for (VCardFile vcardFile : mSelectedVCardFileList) {
if (mCanceled) {
return;
}
@@ -227,66 +246,81 @@
VCardSourceDetector detector = new VCardSourceDetector();
try {
- if (!readOneVCard(canonicalPath, VParser.DEFAULT_CHARSET, detector,
- null, true)) {
+ if (!readOneVCardFile(canonicalPath, 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(charset, false, detector);
- mProgressDialog.incrementProgressBy(1);
+ doActuallyReadOneVCard(canonicalPath, mAccount,
+ charset, false, detector, mErrorFileNameList);
+ mProgressDialogForReadVCard.incrementProgressBy(1);
}
}
} finally {
mWakeLock.release();
- mProgressDialog.dismiss();
- finish();
- }
- }
-
- private void doActuallyReadOneVCard(String charset, boolean doIncrementProgress,
- VCardSourceDetector detector) {
- VCardDataBuilder builder;
- final Context context = ImportVCardActivity.this;
- if (charset != null) {
- builder = new VCardDataBuilder(mResolver,
- mProgressDialog,
- context.getString(R.string.reading_vcard_message),
- mHandler,
- charset,
- charset,
- false,
- mLastNameComesBeforeFirstName);
- } else {
- builder = new VCardDataBuilder(mResolver,
- mProgressDialog,
- context.getString(R.string.reading_vcard_message),
- mHandler,
- null,
- null,
- false,
- mLastNameComesBeforeFirstName);
- charset = VParser.DEFAULT_CHARSET;
- }
- if (doIncrementProgress) {
- builder.setOnProgressRunnable(new Runnable() {
- public void run() {
- mProgressDialog.incrementProgressBy(1);
+ mProgressDialogForReadVCard.dismiss();
+ // finish() is called via mCancelListener, which is used in DialogDisplayer.
+ if (shouldCallFinish && !isFinishing()) {
+ if (mErrorFileNameList == null || mErrorFileNameList.isEmpty()) {
+ finish();
+ } else {
+ StringBuilder builder = new StringBuilder();
+ boolean first = true;
+ for (String fileName : mErrorFileNameList) {
+ if (first) {
+ first = false;
+ } else {
+ builder.append(", ");
+ }
+ builder.append(fileName);
+ }
+
+ mHandler.post(new DialogDisplayer(
+ getString(R.string.fail_reason_failed_to_read_files,
+ builder.toString())));
}
- });
+ }
}
- try {
- readOneVCard(mCanonicalPath, charset, builder, detector, false);
- } catch (VCardNestedException e) {
- Log.e(LOG_TAG, "Must not reach here.");
- }
- builder.showDebugInfo();
}
- private boolean readOneVCard(String canonicalPath, String charset, VBuilder builder,
- VCardSourceDetector detector, boolean throwNestedException)
+ private boolean doActuallyReadOneVCard(String canonicalPath, Account account,
+ String charset, boolean showEntryParseProgress,
+ VCardSourceDetector detector, List<String> errorFileNameList) {
+ final Context context = ImportVCardActivity.this;
+ VCardDataBuilder builder;
+ final String currentLanguage = Locale.getDefault().getLanguage();
+ int vcardType = VCardConfig.getVCardTypeFromString(
+ context.getString(R.string.config_import_vcard_type));
+ if (charset != null) {
+ builder = new VCardDataBuilder(charset, charset, false, vcardType, mAccount);
+ } else {
+ charset = VCardConfig.DEFAULT_CHARSET;
+ builder = new VCardDataBuilder(null, null, false, vcardType, mAccount);
+ }
+ builder.addEntryHandler(new EntryCommitter(mResolver));
+ if (showEntryParseProgress) {
+ builder.addEntryHandler(new ProgressShower(mProgressDialogForReadVCard,
+ context.getString(R.string.reading_vcard_message),
+ ImportVCardActivity.this,
+ mHandler));
+ }
+
+ try {
+ if (!readOneVCardFile(canonicalPath, charset, builder, detector, false, null)) {
+ return false;
+ }
+ } catch (VCardNestedException e) {
+ Log.e(LOG_TAG, "Never reach here.");
+ }
+ return true;
+ }
+
+ private boolean readOneVCardFile(String canonicalPath, String charset,
+ VCardBuilder builder, VCardSourceDetector detector,
+ boolean throwNestedException, List<String> errorFileNameList)
throws VCardNestedException {
FileInputStream is;
try {
@@ -316,63 +350,77 @@
}
}
}
- mVCardParser.showDebugInfo();
} catch (IOException e) {
- Log.e(LOG_TAG, "IOException was emitted: " + e);
+ Log.e(LOG_TAG, "IOException was emitted: " + e.getMessage());
- mProgressDialog.dismiss();
+ mProgressDialogForReadVCard.dismiss();
- mHandler.post(new ErrorDisplayer(
- getString(R.string.fail_reason_io_error) +
- " (" + e.getMessage() + ")"));
- return false;
- } catch (VCardNestedException e) {
- if (throwNestedException) {
- throw e;
+ if (errorFileNameList != null) {
+ errorFileNameList.add(canonicalPath);
} else {
- Log.e(LOG_TAG, "VCardNestedException was emitted: " + e);
- mHandler.post(new ErrorDisplayer(
+ mHandler.post(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(canonicalPath);
+ } else {
+ mHandler.post(new DialogDisplayer(
+ getString(R.string.fail_reason_vcard_not_supported_error) +
+ " (" + e.getMessage() + ")"));
+ }
+ return false;
+ } catch (VCardException e) {
+ if (errorFileNameList != null) {
+ errorFileNameList.add(canonicalPath);
+ } else {
+ mHandler.post(new DialogDisplayer(
getString(R.string.fail_reason_vcard_parse_error) +
" (" + e.getMessage() + ")"));
- return false;
}
- } catch (VCardException e) {
- Log.e(LOG_TAG, "VCardException was emitted: " + e);
-
- mHandler.post(new ErrorDisplayer(
- getString(R.string.fail_reason_vcard_parse_error) +
- " (" + e.getMessage() + ")"));
return false;
}
return true;
}
- public void onCancel(DialogInterface dialog) {
+ 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_ALL = 0;
- public static final int IMPORT_ONE = 1;
-
- private List<VCardFile> mVCardFileList;
+ 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 ImportTypeSelectedListener(List<VCardFile> vcardFileList) {
- mVCardFileList = vcardFileList;
- }
-
public void onClick(DialogInterface dialog, int which) {
if (which == DialogInterface.BUTTON_POSITIVE) {
- if (mCurrentIndex == IMPORT_ALL) {
- importAllVCardFromSDCard(mVCardFileList);
- } else {
- showVCardFileSelectDialog(mVCardFileList);
+ 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();
@@ -381,24 +429,55 @@
}
}
}
-
- private class VCardSelectedListener implements DialogInterface.OnClickListener {
- private List<VCardFile> mVCardFileList;
+
+ private class VCardSelectedListener implements
+ DialogInterface.OnClickListener, DialogInterface.OnMultiChoiceClickListener {
private int mCurrentIndex;
+ private Set<Integer> mSelectedIndexSet;
- public VCardSelectedListener(List<VCardFile> vcardFileList) {
- mVCardFileList = vcardFileList;
+ public VCardSelectedListener(boolean multipleSelect) {
mCurrentIndex = 0;
+ if (multipleSelect) {
+ mSelectedIndexSet = new HashSet<Integer>();
+ }
}
public void onClick(DialogInterface dialog, int which) {
if (which == DialogInterface.BUTTON_POSITIVE) {
- importOneVCardFromSDCard(mVCardFileList.get(mCurrentIndex).getCanonicalPath());
+ 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 {
+ importOneVCardFromSDCard(mAllVCardFileList.get(mCurrentIndex).getCanonicalPath());
+ }
} 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);
}
}
}
@@ -412,9 +491,6 @@
private boolean mGotIOException;
private File mRootDirectory;
- // null when search operation is canceled.
- private List<VCardFile> mVCardFiles;
-
// To avoid recursive link.
private Set<String> mCheckedPaths;
private PowerManager.WakeLock mWakeLock;
@@ -427,7 +503,6 @@
mGotIOException = false;
mRootDirectory = sdcardDirectory;
mCheckedPaths = new HashSet<String>();
- mVCardFiles = new Vector<VCardFile>();
PowerManager powerManager = (PowerManager)ImportVCardActivity.this.getSystemService(
Context.POWER_SERVICE);
mWakeLock = powerManager.newWakeLock(
@@ -437,6 +512,7 @@
@Override
public void run() {
+ mAllVCardFileList = new Vector<VCardFile>();
try {
mWakeLock.acquire();
getVCardFileRecursively(mRootDirectory);
@@ -449,59 +525,24 @@
}
if (mCanceled) {
- mVCardFiles = null;
+ mAllVCardFileList = null;
}
- mProgressDialog.dismiss();
+ mProgressDialogForScanVCard.dismiss();
+ mProgressDialogForScanVCard = null;
if (mGotIOException) {
- mHandler.post(new Runnable() {
- public void run() {
- String message = (getString(R.string.scanning_sdcard_failed_message,
- getString(R.string.fail_reason_io_error)));
-
- AlertDialog.Builder builder =
- new AlertDialog.Builder(ImportVCardActivity.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);
- builder.show();
- }
- });
+ mHandler.post(new DialogDisplayer(R.id.dialog_io_exception));
} else if (mCanceled) {
finish();
} else {
- mHandler.post(new Runnable() {
- public void run() {
- int size = mVCardFiles.size();
- final Context context = ImportVCardActivity.this;
- if (size == 0) {
- String message = (getString(R.string.scanning_sdcard_failed_message,
- getString(R.string.fail_reason_no_vcard_file)));
-
- AlertDialog.Builder builder =
- new AlertDialog.Builder(context)
- .setTitle(R.string.scanning_sdcard_failed_title)
- .setMessage(message)
- .setOnCancelListener(mCancelListener)
- .setPositiveButton(android.R.string.ok, mCancelListener);
- builder.show();
- return;
- } else if (context.getResources().getBoolean(
- R.bool.config_import_all_vcard_from_sdcard_automatically)) {
- importAllVCardFromSDCard(mVCardFiles);
- } else if (size == 1) {
- importOneVCardFromSDCard(mVCardFiles.get(0).getCanonicalPath());
- } else if (context.getResources().getBoolean(
- R.bool.config_allow_users_select_all_vcard_import)) {
- showSelectImportTypeDialog(mVCardFiles);
- } else {
- showVCardFileSelectDialog(mVCardFiles);
- }
- }
- });
+ int size = mAllVCardFileList.size();
+ final Context context = ImportVCardActivity.this;
+ if (size == 0) {
+ mHandler.post(new DialogDisplayer(R.id.dialog_vcard_not_found));
+ } else {
+ startVCardSelectAndImport();
+ }
}
}
@@ -529,7 +570,7 @@
String fileName = file.getName();
VCardFile vcardFile = new VCardFile(
fileName, canonicalPath, file.lastModified());
- mVCardFiles.add(vcardFile);
+ mAllVCardFileList.add(vcardFile);
}
}
}
@@ -545,43 +586,60 @@
}
}
+ 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) {
+ importOneVCardFromSDCard(mAllVCardFileList.get(0).getCanonicalPath());
+ } else if (getResources().getBoolean(R.bool.config_allow_users_select_all_vcard_import)) {
+ mHandler.post(new DialogDisplayer(R.id.dialog_select_import_type));
+ } else {
+ mHandler.post(new DialogDisplayer(R.id.dialog_select_one_vcard));
+ }
+ }
- private void importOneVCardFromSDCard(String canonicalPath) {
- VCardReadThread thread = new VCardReadThread(canonicalPath);
- showReadingVCardDialog(thread);
- thread.start();
+ private void importMultipleVCardFromSDCard(final List<VCardFile> selectedVCardFileList) {
+ mHandler.post(new Runnable() {
+ public void run() {
+ mVCardReadThread = new VCardReadThread(selectedVCardFileList);
+ showDialog(R.id.dialog_reading_vcard);
+ }
+ });
}
- private void importAllVCardFromSDCard(List<VCardFile> vcardFileList) {
- VCardReadThread thread = new VCardReadThread(vcardFileList);
- showReadingVCardDialog(thread);
- thread.start();
+ private void importOneVCardFromSDCard(final String canonicalPath) {
+ mHandler.post(new Runnable() {
+ public void run() {
+ mVCardReadThread = new VCardReadThread(canonicalPath);
+ showDialog(R.id.dialog_reading_vcard);
+ }
+ });
}
- private void showSelectImportTypeDialog(List<VCardFile> vcardFileList) {
+ private Dialog getSelectImportTypeDialog() {
DialogInterface.OnClickListener listener =
- new ImportTypeSelectedListener(vcardFileList);
- AlertDialog.Builder builder =
- new AlertDialog.Builder(ImportVCardActivity.this)
- .setTitle(R.string.select_vcard_title)
- .setPositiveButton(android.R.string.ok, listener)
- .setOnCancelListener(mCancelListener)
- .setNegativeButton(android.R.string.cancel, mCancelListener);
+ 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[2];
- items[ImportTypeSelectedListener.IMPORT_ALL] =
- getString(R.string.import_all_vcard_string);
+ String[] items = new String[ImportTypeSelectedListener.IMPORT_TYPE_SIZE];
items[ImportTypeSelectedListener.IMPORT_ONE] =
getString(R.string.import_one_vcard_string);
- builder.setSingleChoiceItems(items,
- ImportTypeSelectedListener.IMPORT_ALL, listener);
- builder.show();
+ 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 void showVCardFileSelectDialog(List<VCardFile> vcardFileList) {
- int size = vcardFileList.size();
- DialogInterface.OnClickListener listener =
- new VCardSelectedListener(vcardFileList);
+ 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)
@@ -592,7 +650,7 @@
CharSequence[] items = new CharSequence[size];
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
for (int i = 0; i < size; i++) {
- VCardFile vcardFile = vcardFileList.get(i);
+ VCardFile vcardFile = mAllVCardFileList.get(i);
SpannableStringBuilder stringBuilder = new SpannableStringBuilder();
stringBuilder.append(vcardFile.getName());
stringBuilder.append('\n');
@@ -607,31 +665,150 @@
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
items[i] = stringBuilder;
}
- builder.setSingleChoiceItems(items, 0, listener);
- builder.show();
+ if (multipleSelect) {
+ builder.setMultiChoiceItems(items, (boolean[])null, listener);
+ } else {
+ builder.setSingleChoiceItems(items, 0, listener);
+ }
+ return builder.create();
}
- private void showReadingVCardDialog(DialogInterface.OnCancelListener listener) {
- String title = getString(R.string.reading_vcard_title);
- String message = getString(R.string.reading_vcard_message);
- mProgressDialog = new ProgressDialog(this);
- mProgressDialog.setTitle(title);
- mProgressDialog.setMessage(message);
- mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
- mProgressDialog.setOnCancelListener(listener);
- mProgressDialog.show();
+ private Dialog getReadingVCardDialog() {
+ 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;
}
@Override
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
- mLastNameComesBeforeFirstName = getResources().getBoolean(
- com.android.internal.R.bool.config_lastname_comes_before_firstname);
+ 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");
+ }
startImportVCardFromSdCard();
}
+ @Override
+ protected Dialog onCreateDialog(int resId) {
+ switch (resId) {
+ 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: {
+ return getReadingVCardDialog();
+ }
+ 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 onStop() {
+ super.onStop();
+ 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 onStop(), this Activity should finish its work and give the main
+ // screen back to the caller Activity.
+ if (!isFinishing()) {
+ finish();
+ }
+ }
+
+ @Override
+ public void finalize() {
+ if (mVCardReadThread != null) {
+ // Not sure this procedure is really needed, but just in case...
+ Log.w(LOG_TAG, "VCardReadThread exists while this Activity is now being killed!");
+ mVCardReadThread.cancel();
+ mVCardReadThread = null;
+ }
+ }
+
+ /* public methods */
+
/**
* Tries to start importing VCard. If there's no SDCard available,
* an error dialog is shown. If there is, start scanning using another thread
@@ -641,21 +818,11 @@
public void startImportVCardFromSdCard() {
File file = new File("/sdcard");
if (!file.exists() || !file.isDirectory() || !file.canRead()) {
- 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)
- .show();
+ showDialog(R.id.dialog_sdcard_not_found);
} else {
- String title = getString(R.string.searching_vcard_title);
- String message = getString(R.string.searching_vcard_message);
-
- mProgressDialog = ProgressDialog.show(this, title, message, true, false);
- VCardScanThread thread = new VCardScanThread(file);
- mProgressDialog.setOnCancelListener(thread);
- thread.start();
+ File sdcardDirectory = new File("/sdcard");
+ mVCardScanThread = new VCardScanThread(sdcardDirectory);
+ showDialog(R.id.dialog_searching_vcard);
}
}
}
diff --git a/src/com/android/contacts/PhoneDisambigDialog.java b/src/com/android/contacts/PhoneDisambigDialog.java
new file mode 100644
index 0000000..b727c77
--- /dev/null
+++ b/src/com/android/contacts/PhoneDisambigDialog.java
@@ -0,0 +1,180 @@
+/*
+ * 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 java.util.ArrayList;
+import java.util.List;
+
+import com.android.contacts.Collapser.Collapsible;
+
+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.CommonDataKinds.Phone;
+import android.telephony.PhoneNumberUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.ArrayAdapter;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.ListAdapter;
+
+/**
+ * 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);
+
+ 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> {
+
+ public PhonesAdapter(Context context, List<PhoneItem> objects) {
+ super(context, android.R.layout.simple_dropdown_item_1line,
+ android.R.id.text1, objects);
+ }
+ }
+
+ private class PhoneItem implements Collapsible<PhoneItem> {
+
+ String phoneNumber;
+ long id;
+
+ public PhoneItem(String newPhoneNumber, long newId) {
+ phoneNumber = newPhoneNumber;
+ id = newId;
+ }
+
+ 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;
+ }
+
+ 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));
+ phoneList.add(new PhoneItem(phone, id));
+ }
+
+ return phoneList;
+ }
+}
diff --git a/src/com/android/contacts/ProgressShower.java b/src/com/android/contacts/ProgressShower.java
new file mode 100644
index 0000000..c1a2493
--- /dev/null
+++ b/src/com/android/contacts/ProgressShower.java
@@ -0,0 +1,87 @@
+/*
+ * 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.ContactStruct;
+import android.pim.vcard.EntryHandler;
+import android.pim.vcard.VCardConfig;
+import android.util.Log;
+
+public class ProgressShower implements EntryHandler {
+ 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 ContactStruct mContact;
+
+ public ShowProgressRunnable(ContactStruct 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 onParsingStart() {
+ }
+
+ public void onEntryCreated(ContactStruct 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 onParsingEnd() {
+ if (VCardConfig.showPerformanceLog()) {
+ Log.d(LOG_TAG,
+ String.format("Time to progress a dialog: %d ms", mTime));
+ }
+ }
+}
diff --git a/src/com/android/contacts/RecentCallsListActivity.java b/src/com/android/contacts/RecentCallsListActivity.java
index 54e9a05..d892def 100644
--- a/src/com/android/contacts/RecentCallsListActivity.java
+++ b/src/com/android/contacts/RecentCallsListActivity.java
@@ -38,8 +38,9 @@
import android.os.SystemClock;
import android.provider.CallLog;
import android.provider.CallLog.Calls;
-import android.provider.Contacts.People;
-import android.provider.Contacts.Phones;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.PhoneLookup;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.Contacts.Intents.Insert;
import android.telephony.PhoneNumberUtils;
import android.telephony.TelephonyManager;
@@ -100,11 +101,11 @@
/** The projection to use when querying the phones table */
static final String[] PHONES_PROJECTION = new String[] {
- Phones.PERSON_ID,
- Phones.DISPLAY_NAME,
- Phones.TYPE,
- Phones.LABEL,
- Phones.NUMBER
+ PhoneLookup._ID,
+ PhoneLookup.DISPLAY_NAME,
+ PhoneLookup.TYPE,
+ PhoneLookup.LABEL,
+ PhoneLookup.NUMBER
};
static final int PERSON_ID_COLUMN_INDEX = 0;
@@ -321,7 +322,7 @@
} else {
Cursor phonesCursor =
RecentCallsListActivity.this.getContentResolver().query(
- Uri.withAppendedPath(Phones.CONTENT_FILTER_URL,
+ Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI,
Uri.encode(ciq.number)),
PHONES_PROJECTION, null, null, null);
if (phonesCursor != null) {
@@ -452,11 +453,15 @@
// Format the cached call_log phone number
formattedNumber = formatPhoneNumber(number);
}
- // Set the text lines
+ // Set the text lines and call icon.
+ // Assumes the call back feature is on most of the
+ // time. For private and unknown numbers: hide it.
+ views.callView.setVisibility(View.VISIBLE);
+
if (!TextUtils.isEmpty(name)) {
views.line1View.setText(name);
views.labelView.setVisibility(View.VISIBLE);
- CharSequence numberLabel = Phones.getDisplayLabel(context, ntype, label,
+ CharSequence numberLabel = Phone.getDisplayLabel(context, ntype, label,
mLabelArray);
views.numberView.setVisibility(View.VISIBLE);
views.numberView.setText(formattedNumber);
@@ -469,8 +474,10 @@
} else {
if (number.equals(CallerInfo.UNKNOWN_NUMBER)) {
number = getString(R.string.unknown);
+ views.callView.setVisibility(View.INVISIBLE);
} else if (number.equals(CallerInfo.PRIVATE_NUMBER)) {
number = getString(R.string.private_num);
+ views.callView.setVisibility(View.INVISIBLE);
} else if (number.equals(CallerInfo.PAYPHONE_NUMBER)) {
number = getString(R.string.payphone);
} else if (number.equals(mVoiceMailNumber)) {
@@ -655,8 +662,14 @@
* <p>
* Because of the shared {@link #sEditable} builder, <b>this method is not
* thread safe</b>, and should only be called from the GUI thread.
+ * <p>
+ * If the given String object is null or empty, return an empty String.
*/
private String formatPhoneNumber(String number) {
+ if (TextUtils.isEmpty(number)) {
+ return "";
+ }
+
// Cache formatting type if not already present
if (sFormattingType == FORMATTING_TYPE_INVALID) {
sFormattingType = PhoneNumberUtils.getFormatTypeForLocale(Locale.getDefault());
@@ -743,7 +756,7 @@
if (contactInfoPresent) {
menu.add(0, 0, 0, R.string.menu_viewContact)
.setIntent(new Intent(Intent.ACTION_VIEW,
- ContentUris.withAppendedId(People.CONTENT_URI, info.personId)));
+ ContentUris.withAppendedId(Contacts.CONTENT_URI, info.personId)));
}
if (numberUri != null && !isVoicemail) {
@@ -755,7 +768,7 @@
}
if (!contactInfoPresent && numberUri != null && !isVoicemail) {
Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
- intent.setType(People.CONTENT_ITEM_TYPE);
+ intent.setType(Contacts.CONTENT_ITEM_TYPE);
intent.putExtra(Insert.PHONE, number);
menu.add(0, 0, 0, R.string.recentCalls_addToContact)
.setIntent(intent);
@@ -775,7 +788,7 @@
}
case MENU_ITEM_VIEW_CONTACTS: {
- Intent intent = new Intent(Intent.ACTION_VIEW, People.CONTENT_URI);
+ Intent intent = new Intent(Intent.ACTION_VIEW, Contacts.CONTENT_URI);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
return true;
@@ -868,7 +881,7 @@
try {
Cursor phonesCursor =
RecentCallsListActivity.this.getContentResolver().query(
- Uri.withAppendedPath(Phones.CONTENT_FILTER_URL,
+ Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI,
number),
PHONES_PROJECTION, null, null, null);
if (phonesCursor != null) {
diff --git a/src/com/android/contacts/ScrollingTabWidget.java b/src/com/android/contacts/ScrollingTabWidget.java
new file mode 100644
index 0000000..b45abe4
--- /dev/null
+++ b/src/com/android/contacts/ScrollingTabWidget.java
@@ -0,0 +1,418 @@
+/*
+ * 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/ShowOrCreateActivity.java b/src/com/android/contacts/ShowOrCreateActivity.java
deleted file mode 100755
index 0732ffe..0000000
--- a/src/com/android/contacts/ShowOrCreateActivity.java
+++ /dev/null
@@ -1,273 +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.Activity;
-import android.app.AlertDialog;
-import android.content.AsyncQueryHandler;
-import android.content.ComponentName;
-import android.content.ContentUris;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Bundle;
-import android.provider.Contacts;
-import android.provider.Contacts.ContactMethods;
-import android.provider.Contacts.ContactMethodsColumns;
-import android.provider.Contacts.Intents;
-import android.provider.Contacts.People;
-import android.provider.Contacts.Phones;
-import android.util.Log;
-
-import java.lang.ref.WeakReference;
-
-/**
- * Handle several edge cases around showing or possibly creating contacts in
- * connected with a specific E-mail address or phone number. Will search based
- * on incoming {@link Intent#getData()} as described by
- * {@link Intents#SHOW_OR_CREATE_CONTACT}.
- * <ul>
- * <li>If no matching contacts found, will prompt user with dialog to add to a
- * contact, then will use {@link Intent#ACTION_INSERT_OR_EDIT} to let create new
- * contact or edit new data into an existing one.
- * <li>If one matching contact found, directly show {@link Intent#ACTION_VIEW}
- * that specific contact.
- * <li>If more than one matching found, show list of matching contacts using
- * {@link Intent#ACTION_SEARCH}.
- * </ul>
- */
-public final class ShowOrCreateActivity extends Activity {
- static final String TAG = "ShowOrCreateActivity";
- static final boolean LOGD = false;
-
- static final String[] PHONES_PROJECTION = new String[] {
- Phones.PERSON_ID,
- };
-
- static final String[] PEOPLE_PROJECTION = new String[] {
- People._ID,
- };
-
- static final String SCHEME_MAILTO = "mailto";
- static final String SCHEME_TEL = "tel";
-
- static final int PERSON_ID_INDEX = 0;
-
- /**
- * Query clause to filter {@link ContactMethods#CONTENT_URI} to only search
- * {@link Contacts#KIND_EMAIL} or {@link Contacts#KIND_IM}.
- */
- static final String QUERY_KIND_EMAIL_OR_IM = ContactMethodsColumns.KIND +
- " IN (" + Contacts.KIND_EMAIL + "," + Contacts.KIND_IM + ")";
-
- static final int QUERY_TOKEN = 42;
-
- private QueryHandler mQueryHandler;
-
- @Override
- protected void onCreate(Bundle icicle) {
- super.onCreate(icicle);
-
- // Create handler if doesn't exist, otherwise cancel any running
- if (mQueryHandler == null) {
- mQueryHandler = new QueryHandler(this);
- } else {
- mQueryHandler.cancelOperation(QUERY_TOKEN);
- }
-
- final Intent intent = getIntent();
- final Uri data = intent.getData();
-
- // Unpack scheme and target data from intent
- String scheme = null;
- String ssp = null;
- if (data != null) {
- scheme = data.getScheme();
- ssp = data.getSchemeSpecificPart();
- }
-
- // Build set of extras for possible use when creating contact
- Bundle createExtras = new Bundle();
- Bundle originalExtras = intent.getExtras();
- if (originalExtras != null) {
- createExtras.putAll(originalExtras);
- }
- mQueryHandler.setCreateExtras(createExtras);
-
- // Read possible extra with specific title
- String createDescrip = intent.getStringExtra(Intents.EXTRA_CREATE_DESCRIPTION);
- if (createDescrip == null) {
- createDescrip = ssp;
- }
- mQueryHandler.setCreateDescription(createDescrip);
-
- // Allow caller to bypass dialog prompt
- boolean createForce = intent.getBooleanExtra(Intents.EXTRA_FORCE_CREATE, false);
- mQueryHandler.setCreateForce(createForce);
-
- // Handle specific query request
- if (SCHEME_MAILTO.equals(scheme)) {
- createExtras.putString(Intents.Insert.EMAIL, ssp);
- Uri uri = Uri.withAppendedPath(People.WITH_EMAIL_OR_IM_FILTER_URI, Uri.encode(ssp));
- mQueryHandler.startQuery(QUERY_TOKEN, null, uri,
- PEOPLE_PROJECTION, null, null, null);
- } else if (SCHEME_TEL.equals(scheme)) {
- createExtras.putString(Intents.Insert.PHONE, ssp);
- mQueryHandler.startQuery(QUERY_TOKEN, null,
- Uri.withAppendedPath(Phones.CONTENT_FILTER_URL, ssp),
- PHONES_PROJECTION, null, null, null);
-
- } else {
- Log.w(TAG, "Invalid intent:" + getIntent());
- finish();
- }
- }
-
- @Override
- protected void onStop() {
- super.onStop();
- if (mQueryHandler != null) {
- mQueryHandler.cancelOperation(QUERY_TOKEN);
- }
- }
-
- /**
- * Listener for {@link DialogInterface} that launches a given {@link Intent}
- * when clicked. When clicked, this also closes the parent using
- * {@link Activity#finish()}.
- */
- private static class IntentClickListener implements DialogInterface.OnClickListener {
- private Activity mParent;
- private Intent mIntent;
-
- /**
- * @param parent {@link Activity} to use for launching target.
- * @param intent Target {@link Intent} to launch when clicked.
- */
- public IntentClickListener(Activity parent, Intent intent) {
- mParent = parent;
- mIntent = intent;
- }
-
- public void onClick(DialogInterface dialog, int which) {
- if (mIntent != null) {
- mParent.startActivity(mIntent);
- }
- mParent.finish();
- }
- }
-
- /**
- * Handle asynchronous query to find matching contacts. When query finishes,
- * will handle based on number of matching contacts found.
- */
- private static final class QueryHandler extends AsyncQueryHandler {
- private final WeakReference<Activity> mActivity;
- private Bundle mCreateExtras;
- private String mCreateDescrip;
- private boolean mCreateForce;
-
- public QueryHandler(Activity activity) {
- super(activity.getContentResolver());
- mActivity = new WeakReference<Activity>(activity);
- }
-
- public void setCreateExtras(Bundle createExtras) {
- mCreateExtras = createExtras;
- }
-
- public void setCreateDescription(String createDescrip) {
- mCreateDescrip = createDescrip;
- }
-
- public void setCreateForce(boolean createForce) {
- mCreateForce = createForce;
- }
-
- @Override
- protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
- Activity activity = mActivity.get();
- if (activity == null) {
- return;
- }
-
- // Count contacts found by query
- int count = 0;
- long personId = -1;
- if (cursor != null) {
- try {
- count = cursor.getCount();
- if (count == 1 && cursor.moveToFirst()) {
- // Try reading ID if only one contact returned
- personId = cursor.getLong(PERSON_ID_INDEX);
- }
- } finally {
- cursor.close();
- }
- }
-
- if (LOGD) Log.d(TAG, "onQueryComplete count=" + count);
-
- if (count == 1) {
- // If we only found one item, jump right to viewing it
- Intent viewIntent = new Intent(Intent.ACTION_VIEW,
- ContentUris.withAppendedId(People.CONTENT_URI, personId));
- activity.startActivity(viewIntent);
- activity.finish();
-
- } else if (count > 1) {
- // If more than one, show pick list
- Intent listIntent = new Intent(Intent.ACTION_SEARCH);
- listIntent.setComponent(new ComponentName(activity, ContactsListActivity.class));
- listIntent.putExtras(mCreateExtras);
- activity.startActivity(listIntent);
- activity.finish();
-
- } else {
- // No matching contacts found
- if (mCreateForce) {
- // Forced to create new contact
- Intent createIntent = new Intent(Intent.ACTION_INSERT, People.CONTENT_URI);
- createIntent.putExtras(mCreateExtras);
- createIntent.setType(People.CONTENT_TYPE);
-
- activity.startActivity(createIntent);
- activity.finish();
-
- } else {
- // Prompt user to insert or edit contact
- Intent createIntent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
- createIntent.putExtras(mCreateExtras);
- createIntent.setType(People.CONTENT_ITEM_TYPE);
-
- CharSequence message = activity.getResources().getString(
- R.string.add_contact_dlg_message_fmt, mCreateDescrip);
-
- new AlertDialog.Builder(activity)
- .setTitle(R.string.add_contact_dlg_title)
- .setMessage(message)
- .setPositiveButton(android.R.string.ok,
- new IntentClickListener(activity, createIntent))
- .setNegativeButton(android.R.string.cancel,
- new IntentClickListener(activity, null))
- .show();
- }
- }
- }
- }
-}
diff --git a/src/com/android/contacts/SpecialCharSequenceMgr.java b/src/com/android/contacts/SpecialCharSequenceMgr.java
index 999e141..644b66f 100644
--- a/src/com/android/contacts/SpecialCharSequenceMgr.java
+++ b/src/com/android/contacts/SpecialCharSequenceMgr.java
@@ -179,14 +179,14 @@
static boolean handleIMEIDisplay(Context context, String input, boolean useSystemWindow) {
if (input.equals(MMI_IMEI_DISPLAY)) {
- int networkType = ((TelephonyManager)context.getSystemService(
- Context.TELEPHONY_SERVICE)).getNetworkType();
- // check for GSM
- if(networkType == TelephonyManager.NETWORK_TYPE_GPRS ||
- networkType == TelephonyManager.NETWORK_TYPE_EDGE ||
- networkType == TelephonyManager.NETWORK_TYPE_UMTS ) {
+ int phoneType = ((TelephonyManager)context.getSystemService(
+ Context.TELEPHONY_SERVICE)).getPhoneType();
- showIMEIPanel(context, useSystemWindow);
+ if (phoneType == TelephonyManager.PHONE_TYPE_GSM) {
+ showIMEIPanel(context, useSystemWindow);
+ return true;
+ } else if (phoneType == TelephonyManager.PHONE_TYPE_CDMA) {
+ showMEIDPanel(context, useSystemWindow);
return true;
}
}
@@ -207,6 +207,19 @@
alert.getWindow().setType(WindowManager.LayoutParams.TYPE_PRIORITY_PHONE);
}
+ static void showMEIDPanel(Context context, boolean useSystemWindow) {
+ String meidStr = ((TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE))
+ .getDeviceId();
+
+ AlertDialog alert = new AlertDialog.Builder(context)
+ .setTitle(R.string.meid)
+ .setMessage(meidStr)
+ .setPositiveButton(android.R.string.ok, null)
+ .setCancelable(false)
+ .show();
+ alert.getWindow().setType(WindowManager.LayoutParams.TYPE_PRIORITY_PHONE);
+ }
+
/*******
* This code is used to handle SIM Contact queries
*******/
diff --git a/src/com/android/contacts/SplitAggregateView.java b/src/com/android/contacts/SplitAggregateView.java
new file mode 100644
index 0000000..ed78fd9
--- /dev/null
+++ b/src/com/android/contacts/SplitAggregateView.java
@@ -0,0 +1,264 @@
+/*
+ * 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.
+ */
+
+package com.android.contacts;
+
+import com.android.contacts.model.ContactsSource;
+import com.android.contacts.model.Sources;
+import com.google.common.util.text.TextUtil;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.provider.ContactsContract.RawContacts;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.Nickname;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.provider.ContactsContract.Contacts.Data;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * A list view for constituent contacts of an aggregate. Shows the contact name, source icon
+ * and additional data such as a nickname, email address or phone number, whichever
+ * is available.
+ */
+public class SplitAggregateView extends ListView {
+
+ private static final String TAG = "SplitAggregateView";
+
+ private interface SplitQuery {
+ String[] COLUMNS = new String[] {
+ Data.MIMETYPE, RawContacts.ACCOUNT_TYPE, Data.RAW_CONTACT_ID, Data.IS_PRIMARY,
+ StructuredName.DISPLAY_NAME, Nickname.NAME, Email.DATA, Phone.NUMBER
+ };
+
+ int MIMETYPE = 0;
+ int ACCOUNT_TYPE = 1;
+ int RAW_CONTACT_ID = 2;
+ int IS_PRIMARY = 3;
+ int DISPLAY_NAME = 4;
+ int NICKNAME = 5;
+ int EMAIL = 6;
+ int PHONE = 7;
+ }
+
+ private final Uri mAggregateUri;
+ private OnContactSelectedListener mListener;
+ private Sources mSources;
+
+ /**
+ * Listener interface that gets the contact ID of the user-selected contact.
+ */
+ public interface OnContactSelectedListener {
+ void onContactSelected(long rawContactId);
+ }
+
+ /**
+ * Constructor.
+ */
+ public SplitAggregateView(Context context, Uri aggregateUri) {
+ super(context);
+
+ mAggregateUri = aggregateUri;
+
+ mSources = Sources.getInstance(context);
+
+ final List<RawContactInfo> list = loadData();
+
+ setAdapter(new SplitAggregateAdapter(context, list));
+ setOnItemClickListener(new OnItemClickListener() {
+
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ mListener.onContactSelected(list.get(position).rawContactId);
+ }
+ });
+ }
+
+ /**
+ * Sets a contact selection listener.
+ */
+ public void setOnContactSelectedListener(OnContactSelectedListener listener) {
+ mListener = listener;
+ }
+
+ /**
+ * Contact information loaded from the content provider.
+ */
+ private static class RawContactInfo implements Comparable<RawContactInfo> {
+ final long rawContactId;
+ String accountType;
+ String name;
+ String phone;
+ String email;
+ String nickname;
+
+ public RawContactInfo(long rawContactId) {
+ this.rawContactId = rawContactId;
+ }
+
+ public String getAdditionalData() {
+ if (nickname != null) {
+ return nickname;
+ }
+
+ if (email != null) {
+ return email;
+ }
+
+ if (phone != null) {
+ return phone;
+ }
+
+ return "";
+ }
+
+ public int compareTo(RawContactInfo another) {
+ String thisAccount = accountType != null ? accountType : "";
+ String thatAccount = another.accountType != null ? another.accountType : "";
+ return thisAccount.compareTo(thatAccount);
+ }
+ }
+
+ /**
+ * Loads data from the content provider, organizes it into {@link RawContactInfo} objects
+ * and returns a sorted list of {@link RawContactInfo}'s.
+ */
+ private List<RawContactInfo> loadData() {
+ HashMap<Long, RawContactInfo> rawContactInfos = new HashMap<Long, RawContactInfo>();
+ Uri dataUri = Uri.withAppendedPath(mAggregateUri, Data.CONTENT_DIRECTORY);
+ Cursor cursor = getContext().getContentResolver().query(dataUri,
+ SplitQuery.COLUMNS, null, null, null);
+ try {
+ while (cursor.moveToNext()) {
+ long rawContactId = cursor.getLong(SplitQuery.RAW_CONTACT_ID);
+ RawContactInfo info = rawContactInfos.get(rawContactId);
+ if (info == null) {
+ info = new RawContactInfo(rawContactId);
+ rawContactInfos.put(rawContactId, info);
+ info.accountType = cursor.getString(SplitQuery.ACCOUNT_TYPE);
+ }
+
+ String mimetype = cursor.getString(SplitQuery.MIMETYPE);
+ if (StructuredName.CONTENT_ITEM_TYPE.equals(mimetype)) {
+ loadStructuredName(cursor, info);
+ } else if (Phone.CONTENT_ITEM_TYPE.equals(mimetype)) {
+ loadPhoneNumber(cursor, info);
+ } else if (Email.CONTENT_ITEM_TYPE.equals(mimetype)) {
+ loadEmail(cursor, info);
+ } else if (Nickname.CONTENT_ITEM_TYPE.equals(mimetype)) {
+ loadNickname(cursor, info);
+ }
+ }
+ } finally {
+ cursor.close();
+ }
+
+ List<RawContactInfo> list = new ArrayList<RawContactInfo>(rawContactInfos.values());
+ Collections.sort(list);
+ return list;
+ }
+
+ private void loadStructuredName(Cursor cursor, RawContactInfo info) {
+ info.name = cursor.getString(SplitQuery.DISPLAY_NAME);
+ }
+
+ private void loadNickname(Cursor cursor, RawContactInfo info) {
+ if (info.nickname == null || cursor.getInt(SplitQuery.IS_PRIMARY) != 0) {
+ info.nickname = cursor.getString(SplitQuery.NICKNAME);
+ }
+ }
+
+ private void loadEmail(Cursor cursor, RawContactInfo info) {
+ if (info.email == null || cursor.getInt(SplitQuery.IS_PRIMARY) != 0) {
+ info.email = cursor.getString(SplitQuery.EMAIL);
+ }
+ }
+
+ private void loadPhoneNumber(Cursor cursor, RawContactInfo info) {
+ if (info.phone == null || cursor.getInt(SplitQuery.IS_PRIMARY) != 0) {
+ info.phone = cursor.getString(SplitQuery.PHONE);
+ }
+ }
+
+ private static class SplitAggregateItemCache {
+ TextView name;
+ TextView additionalData;
+ ImageView sourceIcon;
+ }
+
+ /**
+ * List adapter for the list of {@link RawContactInfo} objects.
+ */
+ private class SplitAggregateAdapter extends ArrayAdapter<RawContactInfo> {
+
+ private LayoutInflater mInflater;
+
+ public SplitAggregateAdapter(Context context, List<RawContactInfo> sources) {
+ super(context, 0, sources);
+ mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ if (convertView == null) {
+ convertView = mInflater.inflate(R.layout.split_aggregate_list_item, parent, false);
+ }
+
+ SplitAggregateItemCache cache = (SplitAggregateItemCache)convertView.getTag();
+ if (cache == null) {
+ cache = new SplitAggregateItemCache();
+ cache.name = (TextView)convertView.findViewById(R.id.name);
+ cache.additionalData = (TextView)convertView.findViewById(R.id.additionalData);
+ cache.sourceIcon = (ImageView)convertView.findViewById(R.id.sourceIcon);
+ convertView.setTag(cache);
+ }
+
+ final RawContactInfo info = getItem(position);
+ cache.name.setText(info.name);
+ cache.additionalData.setText(info.getAdditionalData());
+
+ Drawable icon = null;
+ ContactsSource source = mSources.getInflatedSource(info.accountType,
+ ContactsSource.LEVEL_SUMMARY);
+ if (source != null) {
+ icon = source.getDisplayIcon(getContext());
+ }
+ if (icon != null) {
+ cache.sourceIcon.setImageDrawable(icon);
+ } else {
+ cache.sourceIcon.setImageResource(R.drawable.unknown_source);
+ }
+ return convertView;
+ }
+ }
+}
diff --git a/src/com/android/contacts/TabStripView.java b/src/com/android/contacts/TabStripView.java
new file mode 100644
index 0000000..9b875d1
--- /dev/null
+++ b/src/com/android/contacts/TabStripView.java
@@ -0,0 +1,123 @@
+/*
+ * 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.graphics.Color;
+import android.graphics.Rect;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewParent;
+import android.widget.HorizontalScrollView;
+import android.widget.LinearLayout;
+
+/** Extension of LinearLayout that takes care of drawing bottom strips over the tab children. */
+public class TabStripView extends LinearLayout {
+
+ private Drawable mBottomLeftStrip;
+ private Drawable mBottomRightStrip;
+ private int mSelectedTabIndex;
+
+ public TabStripView(Context context) {
+ this(context, null);
+ }
+
+ public TabStripView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init();
+ }
+
+ private void init() {
+ mGroupFlags |= FLAG_USE_CHILD_DRAWING_ORDER;
+ mBottomLeftStrip = mContext.getResources().getDrawable(
+ R.drawable.tab_bottom);
+ mBottomRightStrip = mContext.getResources().getDrawable(
+ R.drawable.tab_bottom);
+ }
+
+ public void setSelected(int index, boolean selected) {
+ mSelectedTabIndex = index;
+ getChildAt(index).setSelected(selected);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ ViewParent parent = getParent();
+ if (parent instanceof HorizontalScrollView) {
+ setMinimumWidth(((HorizontalScrollView) getParent()).getMeasuredWidth());
+ }
+
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+
+ @Override
+ protected int getChildDrawingOrder(int childCount, int i) {
+ // Always draw the selected tab last, so that drop shadows are drawn
+ // in the correct z-order.
+ if (i == childCount - 1) {
+ return mSelectedTabIndex;
+ } else if (i >= mSelectedTabIndex) {
+ return i + 1;
+ } else {
+ return i;
+ }
+ }
+
+ @Override
+ public void childDrawableStateChanged(View child) {
+ if (child == getChildAt(mSelectedTabIndex)) {
+ // To make sure that the bottom strip is redrawn
+ invalidate();
+ }
+ super.childDrawableStateChanged(child);
+ }
+
+ @Override
+ public void dispatchDraw(Canvas canvas) {
+ super.dispatchDraw(canvas);
+
+ View selectedChild = getChildAt(mSelectedTabIndex);
+
+ mBottomRightStrip.setState(selectedChild.getDrawableState());
+ mBottomLeftStrip.setState(selectedChild.getDrawableState());
+
+ Rect selBounds = new Rect(); // Bounds of the selected tab indicator
+ selBounds.left = selectedChild.getLeft() - getScrollX();
+ selBounds.right = selectedChild.getRight() - getScrollX();
+ final int myHeight = getHeight();
+ mBottomLeftStrip.setBounds(
+ Math.min(0, selBounds.left
+ - mBottomLeftStrip.getIntrinsicWidth()),
+ myHeight - mBottomLeftStrip.getIntrinsicHeight(),
+ selBounds.left,
+ myHeight);
+ mBottomRightStrip.setBounds(
+ selBounds.right,
+ myHeight - mBottomRightStrip.getIntrinsicHeight(),
+ Math.max(getWidth(),
+ selBounds.right + mBottomRightStrip.getIntrinsicWidth()),
+ myHeight);
+
+ mBottomLeftStrip.draw(canvas);
+ mBottomRightStrip.draw(canvas);
+ }
+
+}
diff --git a/src/com/android/contacts/TwelveKeyDialer.java b/src/com/android/contacts/TwelveKeyDialer.java
index 33f261e..176997a 100644
--- a/src/com/android/contacts/TwelveKeyDialer.java
+++ b/src/com/android/contacts/TwelveKeyDialer.java
@@ -34,6 +34,7 @@
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
+import android.os.Vibrator;
import android.provider.Contacts.Intents.Insert;
import android.provider.Contacts.People;
import android.provider.Contacts.Phones;
@@ -44,6 +45,7 @@
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.text.Editable;
+import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.text.method.DialerKeyListener;
@@ -73,15 +75,16 @@
AdapterView.OnItemClickListener, TextWatcher {
private static final String TAG = "TwelveKeyDialer";
-
- private static final int STOP_TONE = 1;
/** The length of DTMF tones in milliseconds */
private static final int TONE_LENGTH_MS = 150;
-
+
/** The DTMF tone volume relative to other sounds in the stream */
private static final int TONE_RELATIVE_VOLUME = 50;
+ /** Play the vibrate pattern only once. */
+ private static final int VIBRATE_NO_REPEAT = -1;
+
private EditText mDigits;
private View mDelete;
private MenuItem mAddToContactMenuItem;
@@ -89,16 +92,28 @@
private Object mToneGeneratorLock = new Object();
private Drawable mDigitsBackground;
private Drawable mDigitsEmptyBackground;
- private Drawable mDeleteBackground;
- private Drawable mDeleteEmptyBackground;
- private View mDigitsAndBackspace;
private View mDialpad;
+ private View mVoicemailDialAndDeleteRow;
+ private View mVoicemailButton;
+ private View mDialButton;
private ListView mDialpadChooser;
private DialpadChooserAdapter mDialpadChooserAdapter;
+ //Member variables for dialpad options
+ private MenuItem m2SecPauseMenuItem;
+ private MenuItem mWaitMenuItem;
+ private static final int MENU_ADD_CONTACTS = 1;
+ private static final int MENU_2S_PAUSE = 2;
+ private static final int MENU_WAIT = 3;
// determines if we want to playback local DTMF tones.
private boolean mDTMFToneEnabled;
-
+
+ // Vibration (haptic feedback) for dialer key presses.
+ private Vibrator mVibrator;
+ private boolean mVibrateOn;
+ private long[] mVibratePattern;
+
+
/** Identifier for the "Add Call" intent extra. */
static final String ADD_CALL_MODE_KEY = "add_call_mode";
/** Indicates if we are opening this dialer to add a call from the InCallScreen. */
@@ -133,7 +148,7 @@
public void onTextChanged(CharSequence input, int start, int before, int changeCount) {
// Do nothing
- // DTMF Tones do not need to be played here any longer -
+ // DTMF Tones do not need to be played here any longer -
// the DTMF dialer handles that functionality now.
}
@@ -143,17 +158,15 @@
mDigits.getText().clear();
}
- // Set the proper background for the dial input area
- if (mDigits.length() != 0) {
- mDelete.setBackgroundDrawable(mDeleteBackground);
+ final boolean notEmpty = mDigits.length() != 0;
+ if (notEmpty) {
mDigits.setBackgroundDrawable(mDigitsBackground);
- mDigits.setCompoundDrawablesWithIntrinsicBounds(
- getResources().getDrawable(R.drawable.ic_dial_number), null, null, null);
} else {
- mDelete.setBackgroundDrawable(mDeleteEmptyBackground);
+ mDigits.setCursorVisible(false);
mDigits.setBackgroundDrawable(mDigitsEmptyBackground);
- mDigits.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null);
}
+
+ updateDialAndDeleteButtonStateEnabledAttr();
}
@Override
@@ -163,21 +176,17 @@
// Set the content view
setContentView(getContentViewResource());
- // Load up the resources for the text field and delete button
+ // Load up the resources for the text field.
Resources r = getResources();
mDigitsBackground = r.getDrawable(R.drawable.btn_dial_textfield_active);
- //mDigitsBackground.setDither(true);
mDigitsEmptyBackground = r.getDrawable(R.drawable.btn_dial_textfield);
- //mDigitsEmptyBackground.setDither(true);
- mDeleteBackground = r.getDrawable(R.drawable.btn_dial_delete_active);
- //mDeleteBackground.setDither(true);
- mDeleteEmptyBackground = r.getDrawable(R.drawable.btn_dial_delete);
- //mDeleteEmptyBackground.setDither(true);
mDigits = (EditText) findViewById(R.id.digits);
mDigits.setKeyListener(DialerKeyListener.getInstance());
mDigits.setOnClickListener(this);
mDigits.setOnKeyListener(this);
+ mDigits.setInputType(android.text.InputType.TYPE_NULL); // Don't show IME when focused.
+
maybeAddNumberFormatting();
// Check for the presence of the keypad
@@ -186,13 +195,26 @@
setupKeypad();
}
- view = findViewById(R.id.backspace);
+ mVoicemailDialAndDeleteRow = findViewById(R.id.voicemailAndDialAndDelete);
+
+ initVoicemailButton();
+
+ // Check whether we should show the onscreen "Dial" button.
+ mDialButton = mVoicemailDialAndDeleteRow.findViewById(R.id.dialButton);
+
+ if (r.getBoolean(R.bool.config_show_onscreen_dial_button)) {
+ mDialButton.setOnClickListener(this);
+ } else {
+ mDialButton.setVisibility(View.GONE); // It's VISIBLE by default
+ mDialButton = null;
+ }
+
+ view = mVoicemailDialAndDeleteRow.findViewById(R.id.deleteButton);
view.setOnClickListener(this);
view.setOnLongClickListener(this);
mDelete = view;
- mDigitsAndBackspace = (View) findViewById(R.id.digitsAndBackspace);
- mDialpad = (View) findViewById(R.id.dialpad); // This is null in landscape mode
+ mDialpad = findViewById(R.id.dialpad); // This is null in landscape mode
// Set up the "dialpad chooser" UI; see showDialpadChooser().
mDialpadChooser = (ListView) findViewById(R.id.dialpadChooser);
@@ -202,44 +224,23 @@
super.onRestoreInstanceState(icicle);
}
- // If the mToneGenerator creation fails, just continue without it. It is
- // a local audio signal, and is not as important as the dtmf tone itself.
- synchronized (mToneGeneratorLock) {
- if (mToneGenerator == null) {
- try {
- mToneGenerator = new ToneGenerator(AudioManager.STREAM_VOICE_CALL,
- TONE_RELATIVE_VOLUME);
- } catch (RuntimeException e) {
- Log.w(TAG, "Exception caught while creating local tone generator: " + e);
- mToneGenerator = null;
- }
- }
- }
- }
-
- @Override
- protected void onDestroy() {
- super.onDestroy();
- synchronized(mToneGeneratorLock) {
- if (mToneGenerator != null) {
- mToneStopper.removeMessages(STOP_TONE);
- mToneGenerator.release();
- mToneGenerator = null;
- }
- }
+ // TODO: We might eventually need to make mVibrateOn come from a
+ // user preference rather than a per-platform resource, in which
+ // case we would need to update it in onResume() rather than here.
+ initVibrationPattern(r);
}
@Override
protected void onRestoreInstanceState(Bundle icicle) {
// Do nothing, state is restored in onCreate() if needed
}
-
+
protected void maybeAddNumberFormatting() {
mDigits.addTextChangedListener(new PhoneNumberFormattingTextWatcher());
}
-
+
/**
- * Overridden by subclasses to control the resource used by the content view.
+ * Overridden by subclasses to control the resource used by the content view.
*/
protected int getContentViewResource() {
return R.layout.twelve_key_dialer;
@@ -333,7 +334,7 @@
setIntent(newIntent);
resolveIntent();
}
-
+
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
@@ -344,7 +345,7 @@
// will always happen after onRestoreSavedInstanceState().
mDigits.addTextChangedListener(this);
}
-
+
private void setupKeypad() {
// Setup the listeners for the buttons
View view = findViewById(R.id.one);
@@ -371,17 +372,17 @@
@Override
protected void onResume() {
super.onResume();
-
+
// retrieve the DTMF tone play back setting.
mDTMFToneEnabled = Settings.System.getInt(getContentResolver(),
Settings.System.DTMF_TONE_WHEN_DIALING, 1) == 1;
- // if the mToneGenerator creation fails, just continue without it. It is
+ // if the mToneGenerator creation fails, just continue without it. It is
// a local audio signal, and is not as important as the dtmf tone itself.
synchronized(mToneGeneratorLock) {
if (mToneGenerator == null) {
try {
- mToneGenerator = new ToneGenerator(AudioManager.STREAM_VOICE_CALL,
+ mToneGenerator = new ToneGenerator(AudioManager.STREAM_DTMF,
TONE_RELATIVE_VOLUME);
} catch (RuntimeException e) {
Log.w(TAG, "Exception caught while creating local tone generator: " + e);
@@ -389,7 +390,7 @@
}
}
}
-
+
Activity parent = getParent();
// See if we were invoked with a DIAL intent. If we were, fill in the appropriate
// digits in the dialer field.
@@ -425,6 +426,8 @@
// be visible if the phone is idle!
showDialpadChooser(false);
}
+
+ updateDialAndDeleteButtonStateEnabledAttr();
}
@Override
@@ -436,7 +439,7 @@
// have a window token yet in onCreate / onNewIntent
InputMethodManager inputMethodManager = (InputMethodManager)
getSystemService(Context.INPUT_METHOD_SERVICE);
- inputMethodManager.hideSoftInputFromWindow(mDigits.getWindowToken(), 0);
+ inputMethodManager.hideSoftInputFromWindow(mDigits.getWindowToken(), 0);
}
}
@@ -450,7 +453,6 @@
synchronized(mToneGeneratorLock) {
if (mToneGenerator != null) {
- mToneStopper.removeMessages(STOP_TONE);
mToneGenerator.release();
mToneGenerator = null;
}
@@ -459,9 +461,12 @@
@Override
public boolean onCreateOptionsMenu(Menu menu) {
- mAddToContactMenuItem = menu.add(0, 0, 0, R.string.recentCalls_addToContact)
+ mAddToContactMenuItem = menu.add(0, MENU_ADD_CONTACTS, 0, R.string.recentCalls_addToContact)
.setIcon(android.R.drawable.ic_menu_add);
-
+ m2SecPauseMenuItem = menu.add(0, MENU_2S_PAUSE, 0, R.string.add_2sec_pause)
+ .setIcon(R.drawable.ic_menu_2sec_pause);
+ mWaitMenuItem = menu.add(0, MENU_WAIT, 0, R.string.add_wait)
+ .setIcon(R.drawable.ic_menu_wait);
return true;
}
@@ -475,6 +480,8 @@
CharSequence digits = mDigits.getText();
if (digits == null || !TextUtils.isGraphic(digits)) {
mAddToContactMenuItem.setVisible(false);
+ m2SecPauseMenuItem.setVisible(false);
+ mWaitMenuItem.setVisible(false);
} else {
// Put the current digits string into an intent
Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
@@ -482,6 +489,41 @@
intent.setType(People.CONTENT_ITEM_TYPE);
mAddToContactMenuItem.setIntent(intent);
mAddToContactMenuItem.setVisible(true);
+
+ // Check out whether to show Pause & Wait option menu items
+ int selectionStart;
+ int selectionEnd;
+ String strDigits = digits.toString();
+
+ selectionStart = mDigits.getSelectionStart();
+ selectionEnd = mDigits.getSelectionEnd();
+
+ if (selectionStart != -1) {
+ if (selectionStart > selectionEnd) {
+ // swap it as we want start to be less then end
+ int tmp = selectionStart;
+ selectionStart = selectionEnd;
+ selectionEnd = tmp;
+ }
+
+ if (selectionStart != 0) {
+ // Pause can be visible if cursor is not in the begining
+ m2SecPauseMenuItem.setVisible(true);
+
+ // For Wait to be visible set of condition to meet
+ mWaitMenuItem.setVisible(showWait(selectionStart,
+ selectionEnd, strDigits));
+ } else {
+ // cursor in the beginning both pause and wait to be invisible
+ m2SecPauseMenuItem.setVisible(false);
+ mWaitMenuItem.setVisible(false);
+ }
+ } else {
+ // cursor is not selected so assume new digit is added to the end
+ int strLength = strDigits.length();
+ mWaitMenuItem.setVisible(showWait(strLength,
+ strLength, strDigits));
+ }
}
return true;
}
@@ -503,7 +545,7 @@
return true;
}
case KeyEvent.KEYCODE_1: {
- long timeDiff = SystemClock.uptimeMillis() - event.getDownTime();
+ long timeDiff = SystemClock.uptimeMillis() - event.getDownTime();
if (timeDiff >= ViewConfiguration.getLongPressTimeout()) {
// Long press detected, call voice mail
callVoicemail();
@@ -532,8 +574,9 @@
}
return super.onKeyUp(keyCode, event);
}
-
+
private void keyPressed(int keyCode) {
+ vibrate();
KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode);
mDigits.onKeyDown(keyCode, event);
}
@@ -612,14 +655,26 @@
keyPressed(KeyEvent.KEYCODE_STAR);
return;
}
- case R.id.backspace: {
+ case R.id.deleteButton: {
keyPressed(KeyEvent.KEYCODE_DEL);
return;
}
- case R.id.digits: {
+ case R.id.dialButton: {
+ vibrate(); // Vibrate here too, just like we do for the regular keys
placeCall();
return;
}
+ case R.id.voicemailButton: {
+ callVoicemail();
+ vibrate();
+ return;
+ }
+ case R.id.digits: {
+ if (mDigits.length() != 0) {
+ mDigits.setCursorVisible(true);
+ }
+ return;
+ }
}
}
@@ -627,8 +682,12 @@
final Editable digits = mDigits.getText();
int id = view.getId();
switch (id) {
- case R.id.backspace: {
+ case R.id.deleteButton: {
digits.clear();
+ // TODO: The framework forgets to clear the pressed
+ // status of disabled button. Until this is fixed,
+ // clear manually the pressed status. b/2133127
+ mDelete.setPressed(false);
return true;
}
case R.id.one: {
@@ -670,22 +729,6 @@
finish();
}
- Handler mToneStopper = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case STOP_TONE:
- synchronized(mToneGeneratorLock) {
- if (mToneGenerator == null) {
- Log.w(TAG, "mToneStopper: mToneGenerator == null");
- } else {
- mToneGenerator.stopTone();
- }
- }
- break;
- }
- }
- };
/**
* Plays the specified tone for TONE_LENGTH_MS milliseconds.
@@ -719,13 +762,9 @@
Log.w(TAG, "playTone: mToneGenerator == null, tone: "+tone);
return;
}
-
- // Remove pending STOP_TONE messages
- mToneStopper.removeMessages(STOP_TONE);
-
+
// Start the new tone (will stop any playing tone)
- mToneGenerator.startTone(tone);
- mToneStopper.sendEmptyMessageDelayed(STOP_TONE, TONE_LENGTH_MS);
+ mToneGenerator.startTone(tone, TONE_LENGTH_MS);
}
}
@@ -748,8 +787,9 @@
private void showDialpadChooser(boolean enabled) {
if (enabled) {
// Log.i(TAG, "Showing dialpad chooser!");
- mDigitsAndBackspace.setVisibility(View.GONE);
+ mDigits.setVisibility(View.GONE);
if (mDialpad != null) mDialpad.setVisibility(View.GONE);
+ mVoicemailDialAndDeleteRow.setVisibility(View.GONE);
mDialpadChooser.setVisibility(View.VISIBLE);
// Instantiate the DialpadChooserAdapter and hook it up to the
@@ -760,8 +800,9 @@
}
} else {
// Log.i(TAG, "Displaying normal Dialer UI.");
- mDigitsAndBackspace.setVisibility(View.VISIBLE);
+ mDigits.setVisibility(View.VISIBLE);
if (mDialpad != null) mDialpad.setVisibility(View.VISIBLE);
+ mVoicemailDialAndDeleteRow.setVisibility(View.VISIBLE);
mDialpadChooser.setVisibility(View.GONE);
}
}
@@ -941,4 +982,153 @@
}
return phoneInUse;
}
+
+ /**
+ * Triggers haptic feedback (if enabled) for dialer key presses.
+ */
+ private synchronized void vibrate() {
+ if (!mVibrateOn) {
+ return;
+ }
+ if (mVibrator == null) {
+ mVibrator = new Vibrator();
+ }
+ mVibrator.vibrate(mVibratePattern, VIBRATE_NO_REPEAT);
+ }
+
+ /**
+ * Returns true whenever any one of the options from the menu is selected.
+ * Code changes to support dialpad options
+ */
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case MENU_2S_PAUSE:
+ updateDialString(",");
+ return true;
+ case MENU_WAIT:
+ updateDialString(";");
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Updates the dial string (mDigits) after inserting a Pause character (,)
+ * or Wait character (;).
+ */
+ private void updateDialString(String newDigits) {
+ int selectionStart;
+ int selectionEnd;
+
+ // SpannableStringBuilder editable_text = new SpannableStringBuilder(mDigits.getText());
+ int anchor = mDigits.getSelectionStart();
+ int point = mDigits.getSelectionEnd();
+
+ selectionStart = Math.min(anchor, point);
+ selectionEnd = Math.max(anchor, point);
+
+ Editable digits = mDigits.getText();
+ if (selectionStart != -1 ) {
+ if (selectionStart == selectionEnd) {
+ // then there is no selection. So insert the pause at this
+ // position and update the mDigits.
+ digits.replace(selectionStart, selectionStart, newDigits);
+ } else {
+ digits.replace(selectionStart, selectionEnd, newDigits);
+ // Unselect: back to a regular cursor, just pass the character inserted.
+ mDigits.setSelection(selectionStart + 1);
+ }
+ } else {
+ int len = mDigits.length();
+ digits.replace(len, len, newDigits);
+ }
+ }
+
+ /**
+ * Update the enabledness of the "Dial" and "Backspace" buttons if applicable.
+ */
+ private void updateDialAndDeleteButtonStateEnabledAttr() {
+ final boolean notEmpty = mDigits.length() != 0;
+
+ if (mDialButton != null) {
+ mDialButton.setEnabled(notEmpty);
+ }
+ mDelete.setEnabled(notEmpty);
+ }
+
+
+ /**
+ * Check if voicemail is enabled/accessible.
+ */
+ private void initVoicemailButton() {
+ boolean hasVoicemail = false;
+ try {
+ hasVoicemail = TelephonyManager.getDefault().getVoiceMailNumber() != null;
+ } catch (SecurityException se) {
+ // Possibly no READ_PHONE_STATE privilege.
+ }
+
+ mVoicemailButton = mVoicemailDialAndDeleteRow.findViewById(R.id.voicemailButton);
+ if (hasVoicemail) {
+ mVoicemailButton.setOnClickListener(this);
+ } else {
+ mVoicemailButton.setEnabled(false);
+ }
+ }
+
+ /**
+ * Initialize the vibration parameters.
+ * @param r The Resources with the vibration parameters.
+ */
+ private void initVibrationPattern(Resources r) {
+ int[] pattern = null;
+ try {
+ mVibrateOn = r.getBoolean(R.bool.config_enable_dialer_key_vibration);
+ pattern = r.getIntArray(com.android.internal.R.array.config_virtualKeyVibePattern);
+ if (null == pattern) {
+ Log.e(TAG, "Vibrate pattern is null.");
+ mVibrateOn = false;
+ }
+ } catch (Resources.NotFoundException nfe) {
+ Log.e(TAG, "Vibrate control bool or pattern missing.", nfe);
+ mVibrateOn = false;
+ }
+
+ if (!mVibrateOn) {
+ return;
+ }
+
+ // int[] to long[] conversion.
+ mVibratePattern = new long[pattern.length];
+ for (int i = 0; i < pattern.length; i++) {
+ mVibratePattern[i] = pattern[i];
+ }
+ }
+
+ /**
+ * This function return true if Wait menu item can be shown
+ * otherwise returns false. Assumes the passed string is non-empty
+ * and the 0th index check is not required.
+ */
+ private boolean showWait(int start, int end, String digits) {
+ if (start == end) {
+ // visible false in this case
+ if (start > digits.length()) return false;
+
+ // preceding char is ';', so visible should be false
+ if (digits.charAt(start-1) == ';') return false;
+
+ // next char is ';', so visible should be false
+ if ((digits.length() > start) && (digits.charAt(start) == ';')) return false;
+ } else {
+ // visible false in this case
+ if (start > digits.length() || end > digits.length()) return false;
+
+ // In this case we need to just check for ';' preceding to start
+ // or next to end
+ if (digits.charAt(start-1) == ';') return false;
+ }
+ return true;
+ }
}
diff --git a/src/com/android/contacts/TypePrecedence.java b/src/com/android/contacts/TypePrecedence.java
new file mode 100644
index 0000000..62520a0
--- /dev/null
+++ b/src/com/android/contacts/TypePrecedence.java
@@ -0,0 +1,118 @@
+/*
+ * 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.model.EntityModifier;
+import com.android.contacts.util.Constants;
+
+import android.accounts.Account;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.Im;
+import android.provider.ContactsContract.CommonDataKinds.Organization;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+
+/**
+ * This class contains utility functions for determining the precedence of
+ * different types associated with contact data items.
+ *
+ * @deprecated use {@link EntityModifier#getTypePrecedence} instead, since this
+ * list isn't {@link Account} based.
+ */
+@Deprecated
+public final class TypePrecedence {
+
+ /* This utility class has cannot be instantiated.*/
+ private TypePrecedence() {}
+
+ //TODO These may need to be tweaked.
+ private static final int[] TYPE_PRECEDENCE_PHONES = {
+ Phone.TYPE_CUSTOM,
+ Phone.TYPE_MOBILE,
+ Phone.TYPE_HOME,
+ Phone.TYPE_WORK,
+ Phone.TYPE_OTHER,
+ Phone.TYPE_FAX_HOME,
+ Phone.TYPE_FAX_WORK,
+ Phone.TYPE_PAGER};
+
+ private static final int[] TYPE_PRECEDENCE_EMAIL = {
+ Email.TYPE_CUSTOM,
+ Email.TYPE_HOME,
+ Email.TYPE_WORK,
+ Email.TYPE_OTHER};
+
+ private static final int[] TYPE_PRECEDENCE_POSTAL = {
+ StructuredPostal.TYPE_CUSTOM,
+ StructuredPostal.TYPE_HOME,
+ StructuredPostal.TYPE_WORK,
+ StructuredPostal.TYPE_OTHER};
+
+ private static final int[] TYPE_PRECEDENCE_IM = {
+ Im.TYPE_CUSTOM,
+ Im.TYPE_HOME,
+ Im.TYPE_WORK,
+ Im.TYPE_OTHER};
+
+ private static final int[] TYPE_PRECEDENCE_ORG = {
+ Organization.TYPE_CUSTOM,
+ Organization.TYPE_WORK,
+ Organization.TYPE_OTHER};
+
+ /**
+ * Returns the precedence (1 being the highest) of a type in the context of it's mimetype.
+ *
+ * @param mimetype The mimetype of the data with which the type is associated.
+ * @param type The integer type as defined in {@Link ContactsContract#CommonDataKinds}.
+ * @return The integer precedence, where 1 is the highest.
+ */
+ @Deprecated
+ public static int getTypePrecedence(String mimetype, int type) {
+ int[] typePrecedence = getTypePrecedenceList(mimetype);
+ if (typePrecedence == null) {
+ return -1;
+ }
+
+ for (int i = 0; i < typePrecedence.length; i++) {
+ if (typePrecedence[i] == type) {
+ return i;
+ }
+ }
+ return typePrecedence.length;
+ }
+
+ @Deprecated
+ private static int[] getTypePrecedenceList(String mimetype) {
+ if (mimetype.equals(Phone.CONTENT_ITEM_TYPE)) {
+ return TYPE_PRECEDENCE_PHONES;
+ } else if (mimetype.equals(Constants.MIME_SMS_ADDRESS)) {
+ return TYPE_PRECEDENCE_PHONES;
+ } else if (mimetype.equals(Email.CONTENT_ITEM_TYPE)) {
+ return TYPE_PRECEDENCE_EMAIL;
+ } else if (mimetype.equals(StructuredPostal.CONTENT_ITEM_TYPE)) {
+ return TYPE_PRECEDENCE_POSTAL;
+ } else if (mimetype.equals(Im.CONTENT_ITEM_TYPE)) {
+ return TYPE_PRECEDENCE_IM;
+ } else if (mimetype.equals(Organization.CONTENT_ITEM_TYPE)) {
+ return TYPE_PRECEDENCE_ORG;
+ } else {
+ return null;
+ }
+ }
+
+
+}
diff --git a/src/com/android/contacts/VCardExporter.java b/src/com/android/contacts/VCardExporter.java
deleted file mode 100644
index 2b3c4eb..0000000
--- a/src/com/android/contacts/VCardExporter.java
+++ /dev/null
@@ -1,1674 +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.AlertDialog;
-import android.app.ProgressDialog;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteException;
-import android.os.Handler;
-import android.os.PowerManager;
-import android.provider.Contacts;
-import android.provider.Contacts.People;
-import android.syncml.pim.PropertyNode;
-import android.telephony.TelephonyManager;
-import android.text.TextUtils;
-import android.util.CharsetUtils;
-import android.util.Log;
-
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.io.UnsupportedEncodingException;
-import java.io.Writer;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-public class VCardExporter {
- private static final String LOG_TAG = "VCardExporter";
-
- // If true, VCardExporter is able to emits files longer than 8.3 format.
- private static final boolean ALLOW_LONG_FILE_NAME = false;
- private final String mTargetDirectory;
- private final String mFileNamePrefix;
- private final String mFileNameSuffix;
- private final int mFileIndexMinimum;
- private final int mFileIndexMaximum;
- private final String mFileNameExtension;
- private final String mVCardType;
- private final Set<String> mExtensionsToConsider;
-
- private Context mParentContext;
- private Handler mParentHandler;
- private ProgressDialog mProgressDialog;
-
- private class ErrorMessageDisplayRunnable implements Runnable {
- private String mReason;
- public ErrorMessageDisplayRunnable(String reason) {
- mReason = reason;
- }
-
- public void run() {
- new AlertDialog.Builder(mParentContext)
- .setTitle(getString(R.string.exporting_contact_failed_title))
- .setMessage(getString(R.string.exporting_contact_failed_message, mReason))
- .setPositiveButton(android.R.string.ok, null)
- .show();
- }
- }
-
- private class ConfirmListener implements DialogInterface.OnClickListener {
- private String mFileName;
-
- public ConfirmListener(String fileName) {
- mFileName = fileName;
- }
-
- public void onClick(DialogInterface dialog, int which) {
- if (which == DialogInterface.BUTTON_POSITIVE) {
- startExport(mFileName);
- } else if (which == DialogInterface.BUTTON_NEGATIVE) {
- }
- }
- }
-
- private class ActualExportThread extends Thread
- implements DialogInterface.OnCancelListener {
- private PowerManager.WakeLock mWakeLock;
- private String mFileName;
- private boolean mCanceled = false;
-
- public ActualExportThread(String fileName) {
- mFileName = fileName;
- PowerManager powerManager = (PowerManager)mParentContext.getSystemService(
- Context.POWER_SERVICE);
- mWakeLock = powerManager.newWakeLock(
- PowerManager.SCREEN_DIM_WAKE_LOCK |
- PowerManager.ON_AFTER_RELEASE, LOG_TAG);
- }
-
- @Override
- public void run() {
- mWakeLock.acquire();
- VCardExporterImpl exporterImpl = null;
- try {
- OutputStream outputStream = null;
- try {
- outputStream = new FileOutputStream(mFileName);
- } catch (FileNotFoundException e) {
- String reason = getString(R.string.fail_reason_could_not_open_file,
- mFileName, e.getMessage());
- mParentHandler.post(new ErrorMessageDisplayRunnable(reason));
- return;
- }
-
- TelephonyManager telephonyManager =
- (TelephonyManager)mParentContext.getSystemService(
- Context.TELEPHONY_SERVICE);
-
- exporterImpl = new VCardExporterImpl(mParentContext.getContentResolver(),
- outputStream, mVCardType);
-
- if (!exporterImpl.init()) {
- String reason = getString(R.string.fail_reason_could_not_initialize_exporter,
- exporterImpl.getErrorReason());
- mParentHandler.post(new ErrorMessageDisplayRunnable(reason));
- return;
- }
-
- int size = exporterImpl.getCount();
-
- mProgressDialog.setProgressNumberFormat(
- getString(R.string.exporting_contact_list_progress));
- mProgressDialog.setMax(size);
- mProgressDialog.setProgress(0);
-
- while (!exporterImpl.isAfterLast()) {
- if (mCanceled) {
- return;
- }
- if (!exporterImpl.exportOneContactData()) {
- Log.e(LOG_TAG, "Failed to read a contact.");
- String reason = getString(R.string.fail_reason_error_occurred_during_export,
- exporterImpl.getErrorReason());
- mParentHandler.post(new ErrorMessageDisplayRunnable(reason));
- return;
- }
- mProgressDialog.incrementProgressBy(1);
- }
- } finally {
- if (exporterImpl != null) {
- exporterImpl.terminate();
- }
- mWakeLock.release();
- mProgressDialog.dismiss();
- }
- }
-
- @Override
- public void finalize() {
- if (mWakeLock != null && mWakeLock.isHeld()) {
- mWakeLock.release();
- }
- }
-
- public void onCancel(DialogInterface dialog) {
- mCanceled = true;
- }
- }
-
- /**
- * @param parentContext must not be null
- * @param parentHandler must not be null
- */
- public VCardExporter(Context parentContext, Handler parentHandler) {
- mParentContext = parentContext;
- mParentHandler = parentHandler;
- mTargetDirectory = getString(R.string.config_export_dir);
- mFileNamePrefix = getString(R.string.config_export_file_prefix);
- mFileNameSuffix = getString(R.string.config_export_file_suffix);
- mFileNameExtension = getString(R.string.config_export_file_extension);
- mVCardType = getString(R.string.config_export_vcard_type);
-
- mExtensionsToConsider = new HashSet<String>();
- mExtensionsToConsider.add(mFileNameExtension);
-
- final String additionalExtensions =
- getString(R.string.config_export_extensions_to_consider);
- if (!TextUtils.isEmpty(additionalExtensions)) {
- for (String extension : additionalExtensions.split(",")) {
- String trimed = extension.trim();
- if (trimed.length() > 0) {
- mExtensionsToConsider.add(trimed);
- }
- }
- }
-
- Resources resources = parentContext.getResources();
- mFileIndexMinimum = resources.getInteger(R.integer.config_export_file_min_index);
- mFileIndexMaximum = resources.getInteger(R.integer.config_export_file_max_index);
- }
-
- /**
- * Tries to start exporting VCard. If there's no SDCard available,
- * an error dialog is shown.
- */
- public void startExportVCardToSdCard() {
- File targetDirectory = new File(mTargetDirectory);
-
- if (!(targetDirectory.exists() &&
- targetDirectory.isDirectory() &&
- targetDirectory.canRead()) &&
- !targetDirectory.mkdirs()) {
- new AlertDialog.Builder(mParentContext)
- .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)
- .show();
- } else {
- String fileName = getAppropriateFileName(mTargetDirectory);
- if (TextUtils.isEmpty(fileName)) {
- return;
- }
-
- new AlertDialog.Builder(mParentContext)
- .setTitle(R.string.confirm_export_title)
- .setMessage(getString(R.string.confirm_export_message, fileName))
- .setPositiveButton(android.R.string.ok, new ConfirmListener(fileName))
- .setNegativeButton(android.R.string.cancel, null)
- .show();
- }
- }
-
- /**
- * Tries to get an appropriate filename. Returns null if it fails.
- */
- private String getAppropriateFileName(final String destDirectory) {
- int fileNumberStringLength = 0;
- {
- // Calling Math.Log10() is costly.
- int tmp;
- for (fileNumberStringLength = 0, tmp = mFileIndexMaximum; tmp > 0;
- fileNumberStringLength++, tmp /= 10) {
- }
- }
- String bodyFormat = "%s%0" + fileNumberStringLength + "d%s";
-
- if (!ALLOW_LONG_FILE_NAME) {
- String possibleBody = String.format(bodyFormat,mFileNamePrefix, 1, mFileNameSuffix);
- if (possibleBody.length() > 8 || mFileNameExtension.length() > 3) {
- Log.e(LOG_TAG, "This code does not allow any long file name.");
- displayErrorMessage(getString(R.string.fail_reason_too_long_filename,
- String.format("%s.%s", possibleBody, mFileNameExtension)));
- return null;
- }
- }
-
- // Note that this logic assumes that the target directory is case insensitive.
- // As of 2009-07-16, it is true since the external storage is only sdcard, and
- // it is formated as FAT/VFAT.
- // TODO: fix this.
- for (int i = mFileIndexMinimum; i <= mFileIndexMaximum; i++) {
- boolean numberIsAvailable = true;
- // SD Association's specification seems to require this feature, though we cannot
- // have the specification since it is proprietary...
- String body = null;
- for (String possibleExtension : mExtensionsToConsider) {
- body = String.format(bodyFormat, mFileNamePrefix, i, mFileNameSuffix);
- File file = new File(String.format("%s/%s.%s",
- destDirectory, body, possibleExtension));
- if (file.exists()) {
- numberIsAvailable = false;
- break;
- }
- }
- if (numberIsAvailable) {
- return String.format("%s/%s.%s", destDirectory, body, mFileNameExtension);
- }
- }
- displayErrorMessage(getString(R.string.fail_reason_too_many_vcard));
- return null;
- }
-
- private void startExport(String fileName) {
- ActualExportThread thread = new ActualExportThread(fileName);
- displayReadingVCardDialog(thread, fileName);
- thread.start();
- }
-
- private void displayReadingVCardDialog(DialogInterface.OnCancelListener listener,
- String fileName) {
- String title = getString(R.string.exporting_contact_list_title);
- String message = getString(R.string.exporting_contact_list_message, fileName);
- mProgressDialog = new ProgressDialog(mParentContext);
- mProgressDialog.setTitle(title);
- mProgressDialog.setMessage(message);
- mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
- mProgressDialog.setOnCancelListener(listener);
- mProgressDialog.show();
- }
-
- private void displayErrorMessage(String failureReason) {
- new AlertDialog.Builder(mParentContext)
- .setTitle(R.string.exporting_contact_failed_title)
- .setMessage(getString(R.string.exporting_contact_failed_message,
- failureReason))
- .setPositiveButton(android.R.string.ok, null)
- .show();
- }
-
- private String getString(int resId, Object... formatArgs) {
- return mParentContext.getString(resId, formatArgs);
- }
-
- private String getString(int resId) {
- return mParentContext.getString(resId);
- }
-}
-
-// TODO: This class should be splitted into two parts; exporter part and composer part.
-class VCardExporterImpl {
- private static final String LOG_TAG = "VCardExporterImpl";
-
- /* Type of exporting VCard. */
- // General VCard. Use UTF-8 and do not care vendor specific things.
- public static int VCARD_TYPE_GENERIC = 0;
- // VCard format used in DoCoMo. Shift_Jis is used as charset.
- public static int VCARD_TYPE_DOCOMO = 1;
- public static final String VCARD_TYPE_STRING_DOCOMO = "docomo";
-
- private static final String VCARD_PROPERTY_ADR = "ADR";
- private static final String VCARD_PROPERTY_BEGIN = "BEGIN";
- private static final String VCARD_PROPERTY_EMAIL = "EMAIL";
- private static final String VCARD_PROPERTY_END = "END";
- private static final String VCARD_PROPERTY_NAME = "N";
- private static final String VCARD_PROPERTY_NOTE = "NOTE";
- private static final String VCARD_PROPERTY_ORG = "ORG";
- private static final String VCARD_PROPERTY_SOUND = "SOUND";
- private static final String VCARD_PROPERTY_TEL = "TEL";
- private static final String VCARD_PROPERTY_TITLE = "TITLE";
- private static final String VCARD_PROPERTY_PHOTO = "PHOTO";
- private static final String VCARD_PROPERTY_VERSION = "VERSION";
- private static final String VCARD_PROPERTY_BDAY = "BDAY";
- private static final String VCARD_PROPERTY_URL = "URL";
-
- // Properties for DoCoMo vCard.
- private static final String VCARD_PROPERTY_X_CLASS = "X-CLASS";
- private static final String VCARD_PROPERTY_X_REDUCTION = "X-REDUCTION";
- private static final String VCARD_PROPERTY_X_NO = "X-NO";
- private static final String VCARD_PROPERTY_X_DCM_HMN_MODE = "X-DCM-HMN-MODE";
-
- private static final String VCARD_DATA_VCARD = "VCARD";
- private static final String VCARD_DATA_VERSION_V21 = "2.1";
- private static final String VCARD_DATA_PUBLIC = "PUBLIC";
-
- private static final String VCARD_ATTR_SEPARATOR = ";";
- private static final String VCARD_COL_SEPARATOR = "\r\n";
- private static final String VCARD_DATA_SEPARATOR = ":";
- private static final String VCARD_ITEM_SEPARATOR = ";";
- private static final String VCARD_WS = " ";
-
- private static final String VCARD_ATTR_VOICE = "VOICE";
- private static final String VCARD_ATTR_CELL = "CELL";
- private static final String VCARD_ATTR_WORK = "WORK";
- private static final String VCARD_ATTR_HOME = "HOME";
- private static final String VCARD_ATTR_FAX = "FAX";
- private static final String VCARD_ATTR_INTERNET = "INTERNET";
- private static final String VCARD_ATTR_ENCODING_QP = "ENCODING=QUOTED-PRINTABLE";
- private static final String VCARD_ATTR_ENCODING_BASE64_V21 = "ENCODING=BASE64";
- // This is just a reminder: in VCard 3.0, do not use the string "BASE64"
- @SuppressWarnings("unused")
- private static final String VCARD_ATTR_ENCODING_BASE64_V30 = "ENCODING=b";
-
- // DoCoMo specific attribute.Used with "SOUND" property.
- private static final String VCARD_ATTR_X_IRMC_N = "X-IRMC-N";
-
- private static final String SHIFT_JIS = "SHIFT_JIS";
-
- private Cursor mCursor;
- private int mIdColumn;
- private int mNameColumn;
- private int mNotesColumn;
- private int mPhoneticNameColumn;
- private ContentResolver mContentResolver;
-
- private int mVCardType;
- private String mCharsetString;
- private static String mVCardAttributeCharset;
- private OutputStream mOutputStream; // mWriter will close this.
- private Writer mWriter;
- private boolean mTerminateIsCalled;
-
- private String mErrorReason = "No error";
-
- /**
- * @param resolver
- * @param outputStream close() must not be called outside.
- * @param vcardType
- */
- public VCardExporterImpl(ContentResolver resolver, OutputStream outputStream, int vcardType) {
- mContentResolver = resolver;
- mOutputStream = outputStream;
-
- mVCardType = vcardType;
- if (vcardType == VCARD_TYPE_DOCOMO) {
- mCharsetString = CharsetUtils.charsetForVendor(SHIFT_JIS, "docomo").name();
- mVCardAttributeCharset = "CHARSET=" + SHIFT_JIS;
- } else {
- mCharsetString = "UTF-8";
- mVCardAttributeCharset = "CHARSET=UTF-8";
- }
- }
-
- public VCardExporterImpl(ContentResolver resolver, OutputStream outputStream, String vcardType) {
- this(resolver, outputStream,
- (vcardType.equalsIgnoreCase(VCARD_TYPE_STRING_DOCOMO) ?
- VCARD_TYPE_DOCOMO : VCARD_TYPE_GENERIC));
- }
-
- /**
- * @return Returns true when initialization is successful and all the other methods are
- * available. Returns false otherwise.
- */
- public boolean init() {
- try {
- mWriter = new BufferedWriter(
- new OutputStreamWriter(mOutputStream, mCharsetString));
- } catch (UnsupportedEncodingException e1) {
- Log.e(LOG_TAG, "Unsupported charset: " + mCharsetString);
- mErrorReason = "Encoding is not supported (usually this does not happen!): " +
- mCharsetString;
- return false;
- }
-
- final String[] projection = new String[] {
- People._ID,
- People.NAME,
- People.NOTES,
- People.PHONETIC_NAME,
- };
-
- mCursor = mContentResolver.query(People.CONTENT_URI, projection, null, null, null);
- if (mCursor == null || !mCursor.moveToFirst()) {
- if (mCursor != null) {
- try {
- mCursor.close();
- } catch (SQLiteException e) {
- }
- mCursor = null;
- }
- mErrorReason = "Getting database information failed.";
- return false;
- }
-
- mIdColumn = mCursor.getColumnIndex(People._ID);
- mNameColumn = mCursor.getColumnIndex(People.NAME);
- mNotesColumn = mCursor.getColumnIndex(People.NOTES);
- mPhoneticNameColumn = mCursor.getColumnIndex(People.PHONETIC_NAME);
-
- if (mVCardType == VCARD_TYPE_DOCOMO) {
- try {
- mWriter.write(convertContactToVCard(new ContactData()));
- } catch (IOException e) {
- Log.e(LOG_TAG, "IOException occurred during exportOneContactData: " +
- e.getMessage());
- mErrorReason = "IOException occurred: " + e.getMessage();
- }
- }
-
- return true;
- }
-
- @Override
- public void finalize() {
- if (!mTerminateIsCalled) {
- terminate();
- }
- }
-
- public void terminate() {
- if (mWriter != null) {
- try {
- // Flush and sync the data so that a user is able to pull the SDCard just after the
- // export.
- mWriter.flush();
- if (mOutputStream != null && mOutputStream instanceof FileOutputStream) {
- try {
- ((FileOutputStream)mOutputStream).getFD().sync();
- } catch (IOException e) {
- }
- }
- mWriter.close();
- } catch (IOException e) {
- }
- }
- if (mCursor != null) {
- try {
- mCursor.close();
- } catch (SQLiteException e) {
- }
- mCursor = null;
- }
- }
-
- public int getCount() {
- if (mCursor == null) {
- return 0;
- }
- return mCursor.getCount();
- }
-
- public boolean isAfterLast() {
- if (mCursor == null) {
- return false;
- }
- return mCursor.isAfterLast();
- }
-
- public boolean exportOneContactData() {
- if (mCursor == null || mCursor.isAfterLast()) {
- mErrorReason = "Not initialized or database has some problem.";
- return false;
- }
- String name = null;
- try {
- ContactData contactData = new ContactData();
- int personId = mCursor.getInt(mIdColumn);
- name = contactData.mName = mCursor.getString(mNameColumn);
- contactData.mNote = mCursor.getString(mNotesColumn);
- contactData.mPhoneticName = mCursor.getString(mPhoneticNameColumn);
-
- readAllPhones(contactData, personId);
- readAllAddresses(contactData, personId);
- readAllOrgs(contactData, personId);
- readAllPhotos(contactData, personId);
- readAllExtensions(contactData, personId);
-
- mCursor.moveToNext();
-
- String vcardString = convertContactToVCard(contactData);
- try {
- mWriter.write(vcardString);
- } catch (IOException e) {
- Log.e(LOG_TAG, "IOException occurred during exportOneContactData: " +
- e.getMessage());
- mErrorReason = "IOException occurred: " + e.getMessage();
- return false;
- }
- } catch (OutOfMemoryError error) {
- // Maybe some data (e.g. photo) is too big to have in memory. But it should be rare.
- Log.e(LOG_TAG, "OutOfMemoryError occured. Ignore the entry: " + name);
- System.gc();
- }
-
- return true;
- }
-
- /**
- * @return Return the error reason if possible.
- */
- public String getErrorReason() {
- return mErrorReason;
- }
-
- private void readAllPhones(ContactData contact, int personId) {
- final String[] projection = new String[] {
- Contacts.Phones.TYPE,
- Contacts.Phones.LABEL,
- Contacts.Phones.NUMBER,
- Contacts.Phones.LABEL,
- };
- String selection = String.format("%s=%d", Contacts.Phones.PERSON_ID, personId);
- Cursor cursor = null;
- try {
- cursor = mContentResolver.query(Contacts.Phones.CONTENT_URI,
- projection, selection, null, null);
- if ((cursor != null) && (cursor.moveToFirst())) {
- int typeColumn = cursor.getColumnIndex(Contacts.Phones.TYPE);
- int labelColumn = cursor.getColumnIndex(Contacts.Phones.LABEL);
- int numberColumn = cursor.getColumnIndex(Contacts.Phones.NUMBER);
- do {
- TelData telData = new TelData(cursor.getInt(typeColumn),
- cursor.getString(labelColumn), cursor.getString(numberColumn));
- contact.mTel.add(telData);
- } while (cursor.moveToNext());
- }
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- }
-
- private void readAllPhotos(ContactData contact, int personId) {
- final String[] projection = new String[] {
- Contacts.Photos.DATA,
- };
- String selection = String.format("%s=%d", Contacts.Photos.PERSON_ID, personId);
- Cursor cursor = null;
- try {
- cursor = mContentResolver.query(Contacts.Photos.CONTENT_URI,
- projection, selection, null, null);
- if ((cursor != null) && (cursor.moveToFirst())) {
- int dataColumn = cursor.getColumnIndex(Contacts.Photos.DATA);
-
- byte[] data;
- do {
- data = cursor.getBlob(dataColumn);
- // Use some heuristics for guessing the format of the image.
- if (data != null && data.length > 0) {
- if (data.length >= 3 &&
- data[0] == 'G' && data[1] == 'I' && data[2] == 'F') {
- contact.mPhotoType = "GIF";
- } else if (data.length >= 4 &&
- data[0] == (byte)0x89 && data[1] == 'P' && data[2] == 'N' &&
- data[3] == 'G') {
- // Note: vCard 2.1 officially does not support PNG, but we may have it
- // and using X- word like "X-PNG" may not let importers know it is
- // PNG. So we use the String "PNG" as is...
- contact.mPhotoType = "PNG";
- } else if (data.length >= 2 &&
- data[0] == (byte)0xff && data[1] == (byte)0xd8) {
- contact.mPhotoType = "JPEG";
- } else {
- // TODO: vCard specification requires the other formats like TIFF...
- Log.d(LOG_TAG, "Unknown photo type. Ignore.");
- continue;
- }
- }
- String photoData = encodeBase64(data);
- if (photoData.length() > 0) {
- contact.mPhoto = photoData;
- }
- } while (cursor.moveToNext());
- }
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- }
-
- private void readAllAddresses(ContactData contact, int personId) {
- final String[] projection = new String[] {
- Contacts.ContactMethods.TYPE,
- Contacts.ContactMethods.LABEL,
- Contacts.ContactMethods.DATA,
- Contacts.ContactMethods.KIND,
- };
- String selection = String.format("%s=%d AND %s IN (1,2)",
- Contacts.ContactMethods.PERSON_ID, personId, Contacts.ContactMethods.KIND);
- Cursor cursor = null;
- try {
- cursor = mContentResolver.query(Contacts.ContactMethods.CONTENT_URI,
- projection, selection, null, null);
- if ((cursor != null) && (cursor.moveToFirst())) {
- int typeColumn = cursor.getColumnIndex(Contacts.ContactMethods.TYPE);
- int labelColumn = cursor.getColumnIndex(Contacts.ContactMethods.LABEL);
- int dataColumn = cursor.getColumnIndex(Contacts.ContactMethods.DATA);
- int kindColumn = cursor.getColumnIndex(Contacts.ContactMethods.KIND);
- do {
- int kind = cursor.getInt(kindColumn);
-
- switch(kind) {
- case Contacts.KIND_EMAIL:
- EmailData emailData = new EmailData(cursor.getInt(typeColumn),
- cursor.getString(labelColumn), cursor.getString(dataColumn));
- contact.mEmail.add(emailData);
- break;
- case Contacts.KIND_POSTAL:
- AddressData addr = new AddressData(cursor.getInt(typeColumn),
- cursor.getString(labelColumn), cursor.getString(dataColumn));
- contact.mAddr.add(addr);
- break;
- default:
- break;
- }
- } while (cursor.moveToNext());
- }
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- }
-
- private void readAllOrgs(ContactData contactData, int personId) {
- final String[] projection = new String[] {
- Contacts.Organizations.COMPANY,
- Contacts.Organizations.TITLE,
- };
- String selection = String.format("%s=%d", Contacts.ContactMethods.PERSON_ID, personId);
- Cursor cursor = null;
- try {
- cursor = mContentResolver.query(Contacts.Organizations.CONTENT_URI,
- projection, selection, null, null);
- if ((cursor != null) && (cursor.moveToFirst())) {
- int companyColumn = cursor.getColumnIndex(Contacts.Organizations.COMPANY);
- int titleColumn = cursor.getColumnIndex(Contacts.Organizations.TITLE);
- do {
- contactData.mOrg = cursor.getString(companyColumn);
- contactData.mTitle = cursor.getString(titleColumn);
- } while (cursor.moveToNext());
- }
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- }
-
- private void readAllExtensions(ContactData contactData, int personId) {
- final String[] projection = new String[] {
- Contacts.Extensions.NAME,
- Contacts.Extensions.VALUE,
- };
- String selection = String.format("%s=%d", Contacts.Extensions.PERSON_ID, personId);
- Cursor cursor = null;
- try {
- cursor = mContentResolver.query(Contacts.Extensions.CONTENT_URI,
- projection, selection, null, null);
- if ((cursor != null) && (cursor.moveToFirst())) {
- int nameColumn = cursor.getColumnIndex(Contacts.Extensions.NAME);
- int valueColumn = cursor.getColumnIndex(Contacts.Extensions.VALUE);
- do {
- contactData.mExtensions.put(
- cursor.getString(nameColumn),
- cursor.getString(valueColumn));
- } while (cursor.moveToNext());
- }
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- }
-
- private String toHalfWidthString(String orgString) {
- StringBuilder builder = new StringBuilder();
- int length = orgString.length();
- for (int i = 0; i < length; i++) {
- // All Japanese character is able to be expressed by char.
- // Do not need to use String#codepPointAt().
- char ch = orgString.charAt(i);
- CharSequence halfWidthText = JapaneseUtils.tryGetHalfWidthText(ch);
- if (halfWidthText != null) {
- builder.append(halfWidthText);
- } else {
- builder.append(ch);
- }
- }
- return builder.toString();
- }
-
- private String encodeSomeCharacters(String str) {
- char[] strArray = str.toCharArray();
- StringBuilder builder = new StringBuilder();
- for (int i = 0; i < strArray.length; i++) {
- char ch = strArray[i];
- switch (ch) {
- case ';':
- builder.append('\\');
- builder.append(';');
- break;
- case '\r':
- case '\n':
- // ignore
- break;
- case '\\':
- case '<':
- case '>':
- if (mVCardType == VCARD_TYPE_DOCOMO) {
- builder.append('\\');
- builder.append(ch);
- }
- break;
- default:
- builder.append(ch);
- break;
- }
- }
- return builder.toString();
- }
-
- private String convertContactToVCard(ContactData contactData) {
- // Some DoCoMo mobile devices cannot parse a VCard data which does not have empty field.
- final boolean isDoCoMo = (mVCardType == VCARD_TYPE_DOCOMO);
- StringBuilder builder = new StringBuilder();
- appendVCardLine(builder, VCARD_PROPERTY_BEGIN, VCARD_DATA_VCARD);
- appendVCardLine(builder, VCARD_PROPERTY_VERSION, VCARD_DATA_VERSION_V21);
-
- if (!TextUtils.isEmpty(contactData.mName)) {
- builder.append(VCARD_PROPERTY_NAME);
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(mVCardAttributeCharset);
- builder.append(VCARD_DATA_SEPARATOR);
- builder.append(encodeSomeCharacters(contactData.mName));
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(VCARD_COL_SEPARATOR);
- } else if (isDoCoMo) {
- appendVCardLine(builder, VCARD_PROPERTY_NAME, "");
- }
-
- if (!TextUtils.isEmpty(contactData.mPhoneticName)) {
- // Note: There is no appropriate property for expressing phonetic name in VCard 2.1,
- // while there is in VCard 3.0 (SORT-STRING).
- // We choose to use DoCoMo's way since it is supported by Japanese mobile phones.
- builder.append(VCARD_PROPERTY_SOUND);
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(VCARD_ATTR_X_IRMC_N);
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(mVCardAttributeCharset);
- builder.append(VCARD_DATA_SEPARATOR);
- // TODO: Not only DoCoMo but also other Japanese mobile careers requires this.
- String phoneticName =
- (isDoCoMo ? toHalfWidthString(contactData.mPhoneticName) :
- contactData.mPhoneticName);
- builder.append(encodeSomeCharacters(phoneticName));
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(VCARD_COL_SEPARATOR);
- } else if (isDoCoMo) {
- // VCARD_ITEM_SEPARATOR should be inserted for DoCoMo devices.
- builder.append(VCARD_PROPERTY_SOUND);
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(VCARD_ATTR_X_IRMC_N);
- builder.append(VCARD_DATA_SEPARATOR);
- // Empty data.
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(VCARD_COL_SEPARATOR);
- }
-
- if (contactData.mTel.size() > 0) {
- for (TelData telData : contactData.mTel) {
- appendVCardTelephoneLine(builder, telData.mType,
- telData.mLabel, telData.mValue);
- }
- } else if (isDoCoMo) {
- appendVCardTelephoneLine(builder, Contacts.Phones.TYPE_HOME, "", "");
- }
-
- if (contactData.mEmail.size() > 0) {
- for (EmailData emailData : contactData.mEmail) {
- appendVCardEmailLine(builder, emailData.mType,
- emailData.mLabel, emailData.mValue);
- }
- } else if (isDoCoMo) {
- appendVCardEmailLine(builder, Contacts.ContactMethods.TYPE_HOME, "", "");
- }
-
- if (isDoCoMo) {
- appendVCardAddressLinesForDoCoMo(builder, contactData);
- } else {
- appendVCardAddressLinesForGeneric(builder, contactData);
- }
-
- if (!TextUtils.isEmpty(contactData.mOrg)) {
- appendVCardLine(builder, VCARD_PROPERTY_ORG, contactData.mOrg, true, true);
- }
-
- if (!TextUtils.isEmpty(contactData.mTitle)) {
- appendVCardLine(builder, VCARD_PROPERTY_TITLE, contactData.mTitle, true, true);
- }
-
- if (!TextUtils.isEmpty(contactData.mNote)) {
- appendVCardLine(builder, VCARD_PROPERTY_NOTE, contactData.mNote, true, true);
- }
-
- if ((contactData.mPhoto != null) && (contactData.mPhoto.length() > 0)) {
- // Note that contactData.mPhoto is already BASE64-encoded.
- appendVCardPhotoLine(builder, contactData.mPhoto, contactData.mPhotoType);
- }
-
- appendVCardExtension(builder, contactData, VCARD_PROPERTY_BDAY, isDoCoMo);
-
- // XXX: URL may have non-ascii chars. Should we add charset?
- appendVCardExtension(builder, contactData, VCARD_PROPERTY_URL, isDoCoMo);
-
- if (isDoCoMo) {
- if (contactData.mExtensions.containsKey(VCARD_PROPERTY_X_CLASS)) {
- appendVCardExtension(builder, contactData, VCARD_PROPERTY_X_CLASS, true);
- } else {
- appendVCardLine(builder, VCARD_PROPERTY_X_CLASS, VCARD_DATA_PUBLIC);
- }
- appendVCardExtension(builder, contactData, VCARD_PROPERTY_X_REDUCTION, true);
- appendVCardExtension(builder, contactData, VCARD_PROPERTY_X_NO, true);
- appendVCardExtension(builder, contactData, VCARD_PROPERTY_X_DCM_HMN_MODE, true);
- }
-
- appendVCardLine(builder, VCARD_PROPERTY_END, VCARD_DATA_VCARD);
-
- return builder.toString();
- }
-
- private void appendVCardAddressLinesForGeneric(StringBuilder builder, ContactData contactData) {
- for (AddressData addr : contactData.mAddr) {
- appendVCardAddressLine(builder, addr.mType, addr.mLabel, addr.mValue);
- }
- }
-
- private void appendVCardAddressLinesForDoCoMo(StringBuilder builder, ContactData contactData) {
- boolean isAddrSet = false;
- for (AddressData addr : contactData.mAddr) {
- if ((!isAddrSet) && (addr.mType == Contacts.ContactMethods.TYPE_HOME)) {
- appendVCardAddressLine(builder, addr.mType, addr.mLabel,
- addr.mValue);
- isAddrSet = true;
- break;
- }
- }
- if (!isAddrSet) {
- for (AddressData addr : contactData.mAddr) {
- if ((!isAddrSet) && (addr.mType == Contacts.ContactMethods.TYPE_WORK)) {
- appendVCardAddressLine(builder, addr.mType, addr.mLabel,
- addr.mValue);
- isAddrSet = true;
- break;
- }
- }
- }
- if (!isAddrSet) {
- for (AddressData addr : contactData.mAddr) {
- if ((!isAddrSet) && (addr.mType == Contacts.ContactMethods.TYPE_OTHER)) {
- appendVCardAddressLine(builder, addr.mType, addr.mLabel,
- addr.mValue);
- isAddrSet = true;
- break;
- }
- }
- }
- if (!isAddrSet) {
- for (AddressData addr : contactData.mAddr) {
- if ((!isAddrSet) && (addr.mType == Contacts.ContactMethods.TYPE_CUSTOM)) {
- appendVCardAddressLine(builder, addr.mType, addr.mLabel,
- addr.mValue);
- isAddrSet = true;
- break;
- }
- }
- }
- if (!isAddrSet) {
- appendVCardAddressLine(builder, Contacts.ContactMethods.TYPE_HOME, "", "");
- }
- }
-
- private void appendVCardPhotoLine(StringBuilder builder, String encodedData, String type) {
- StringBuilder tmpBuilder = new StringBuilder();
- tmpBuilder.append(VCARD_PROPERTY_PHOTO);
- tmpBuilder.append(VCARD_ATTR_SEPARATOR);
- tmpBuilder.append(VCARD_ATTR_ENCODING_BASE64_V21);
- tmpBuilder.append(VCARD_ATTR_SEPARATOR);
- tmpBuilder.append("TYPE=");
- tmpBuilder.append(type);
- tmpBuilder.append(VCARD_DATA_SEPARATOR);
- tmpBuilder.append(encodedData);
-
- String tmpStr = tmpBuilder.toString();
- tmpBuilder = new StringBuilder();
- int lineCount = 0;
- for (int i = 0; i < tmpStr.length(); i++) {
- tmpBuilder.append(tmpStr.charAt(i));
- lineCount++;
- if (lineCount > 72) {
- tmpBuilder.append(VCARD_COL_SEPARATOR);
- tmpBuilder.append(VCARD_WS);
- lineCount = 0;
- }
- }
- builder.append(tmpBuilder.toString());
- builder.append(VCARD_COL_SEPARATOR);
- builder.append(VCARD_COL_SEPARATOR);
- }
-
- private void appendVCardAddressLine(StringBuilder builder,
- int type, String label, String rawData) {
- builder.append(VCARD_PROPERTY_ADR);
- builder.append(VCARD_ATTR_SEPARATOR);
-
- boolean dataExists = !TextUtils.isEmpty(rawData);
-
- switch(type) {
- case Contacts.ContactMethods.TYPE_HOME:
- builder.append(VCARD_ATTR_HOME);
- if (dataExists) {
- builder.append(VCARD_ATTR_SEPARATOR);
- }
- break;
- case Contacts.ContactMethods.TYPE_WORK:
- builder.append(VCARD_ATTR_WORK);
- if (dataExists) {
- builder.append(VCARD_ATTR_SEPARATOR);
- }
- break;
- case Contacts.ContactMethods.TYPE_CUSTOM:
- // Ignore custom value since
- // - it may violate vCard spec
- // - it may contain non-ASCII characters
- // TODO: fix this.
- //
- // builder.append(label);
- // builder.append(VCARD_DATA_SEPARATOR);
- // break;
- case Contacts.ContactMethods.TYPE_OTHER:
- default:
- // Ignore other methods.
- // TODO: fix this.
- break;
- }
-
- if (dataExists) {
- builder.append(mVCardAttributeCharset);
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(VCARD_ATTR_ENCODING_QP);
- }
- builder.append(VCARD_DATA_SEPARATOR);
- if (dataExists) {
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(encodeQuotedPrintable(rawData));
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(VCARD_ITEM_SEPARATOR);
- }
- builder.append(VCARD_COL_SEPARATOR);
- }
-
- private void appendVCardEmailLine(StringBuilder builder, int type, String label, String data) {
- builder.append(VCARD_PROPERTY_EMAIL);
- builder.append(VCARD_ATTR_SEPARATOR);
-
- switch(type) {
- case Contacts.ContactMethods.TYPE_CUSTOM:
- if (label.equals(Contacts.ContactMethodsColumns.MOBILE_EMAIL_TYPE_NAME)) {
- builder.append(VCARD_ATTR_CELL);
- } else {
- // Ignore custom value.
- builder.append(VCARD_ATTR_INTERNET);
- }
- break;
- case Contacts.ContactMethods.TYPE_HOME:
- builder.append(VCARD_ATTR_HOME);
- break;
- case Contacts.ContactMethods.TYPE_WORK:
- builder.append(VCARD_ATTR_WORK);
- break;
- case Contacts.ContactMethods.TYPE_OTHER:
- default:
- builder.append(VCARD_ATTR_INTERNET);
- break;
- }
-
- builder.append(VCARD_DATA_SEPARATOR);
- builder.append(data);
- builder.append(VCARD_COL_SEPARATOR);
- }
-
- private void appendVCardTelephoneLine(StringBuilder builder,
- int type, String label, String data) {
- builder.append(VCARD_PROPERTY_TEL);
- builder.append(VCARD_ATTR_SEPARATOR);
-
- switch(type) {
- case Contacts.Phones.TYPE_CUSTOM:
- // Ignore custom label.
- builder.append(VCARD_ATTR_VOICE);
- break;
- case Contacts.Phones.TYPE_HOME:
- builder.append(VCARD_ATTR_HOME);
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(VCARD_ATTR_VOICE);
- break;
- case Contacts.Phones.TYPE_MOBILE:
- builder.append(VCARD_ATTR_CELL);
- break;
- case Contacts.Phones.TYPE_WORK:
- builder.append(VCARD_ATTR_WORK);
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(VCARD_ATTR_VOICE);
- break;
- case Contacts.Phones.TYPE_FAX_WORK:
- builder.append(VCARD_ATTR_WORK);
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(VCARD_ATTR_FAX);
- break;
- case Contacts.Phones.TYPE_FAX_HOME:
- builder.append(VCARD_ATTR_HOME);
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(VCARD_ATTR_FAX);
- break;
- case Contacts.Phones.TYPE_PAGER:
- builder.append(VCARD_ATTR_VOICE);
- break;
- case Contacts.Phones.TYPE_OTHER:
- builder.append(VCARD_ATTR_VOICE);
- break;
- default:
- builder.append(VCARD_ATTR_VOICE);
- break;
- }
-
- builder.append(VCARD_DATA_SEPARATOR);
- builder.append(data);
- builder.append(VCARD_COL_SEPARATOR);
- }
-
- private void appendVCardExtension(StringBuilder builder, ContactData contactData,
- String propertyName, boolean mustEmitSomething) {
- if (contactData.mExtensions.containsKey(propertyName)) {
- PropertyNode propertyNode =
- PropertyNode.decode(contactData.mExtensions.get(propertyName));
- appendVCardLine(builder, propertyName, propertyNode.propValue);
- } else if (mustEmitSomething) {
- appendVCardLine(builder, propertyName, "");
- }
- }
-
- private void appendVCardLine(StringBuilder builder, String propertyName, String data) {
- appendVCardLine(builder, propertyName, data, false, false);
- }
-
- private void appendVCardLine(StringBuilder builder, String field, String data,
- boolean needCharset, boolean needQuotedPrintable) {
- builder.append(field);
- if (needCharset) {
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(mVCardAttributeCharset);
- }
-
- if (needQuotedPrintable) {
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(VCARD_ATTR_ENCODING_QP);
- data = encodeQuotedPrintable(data);
- }
-
- builder.append(VCARD_DATA_SEPARATOR);
- builder.append(data);
- builder.append(VCARD_COL_SEPARATOR);
- }
-
- // TODO: Replace with the method in Base64 class.
- private static char PAD = '=';
- private static final char[] ENCODE64 = {
- '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','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','0','1','2','3','4','5','6','7','8','9','+','/'
- };
-
- private String encodeBase64(byte[] data) {
- if (data == null) {
- return "";
- }
-
- char[] charBuffer = new char[(data.length + 2) / 3 * 4];
- int position = 0;
- int _3byte = 0;
- for (int i=0; i<data.length-2; i+=3) {
- _3byte = ((data[i] & 0xFF) << 16) + ((data[i+1] & 0xFF) << 8) + (data[i+2] & 0xFF);
- charBuffer[position++] = ENCODE64[_3byte >> 18];
- charBuffer[position++] = ENCODE64[(_3byte >> 12) & 0x3F];
- charBuffer[position++] = ENCODE64[(_3byte >> 6) & 0x3F];
- charBuffer[position++] = ENCODE64[_3byte & 0x3F];
- }
- switch(data.length % 3) {
- case 1: // [111111][11 0000][0000 00][000000]
- _3byte = ((data[data.length-1] & 0xFF) << 16);
- charBuffer[position++] = ENCODE64[_3byte >> 18];
- charBuffer[position++] = ENCODE64[(_3byte >> 12) & 0x3F];
- charBuffer[position++] = PAD;
- charBuffer[position++] = PAD;
- break;
- case 2: // [111111][11 1111][1111 00][000000]
- _3byte = ((data[data.length-2] & 0xFF) << 16) + ((data[data.length-1] & 0xFF) << 8);
- charBuffer[position++] = ENCODE64[_3byte >> 18];
- charBuffer[position++] = ENCODE64[(_3byte >> 12) & 0x3F];
- charBuffer[position++] = ENCODE64[(_3byte >> 6) & 0x3F];
- charBuffer[position++] = PAD;
- break;
- }
-
- return new String(charBuffer);
- }
-
- private String encodeQuotedPrintable(String str) {
- {
- // Replace "\n" and "\r" with "\r\n".
- StringBuilder tmpBuilder = new StringBuilder();
- int length = str.length();
- for (int i = 0; i < length; i++) {
- char ch = str.charAt(i);
- if (ch == '\r') {
- if (i + 1 < length && str.charAt(i + 1) == '\n') {
- i++;
- }
- tmpBuilder.append("\r\n");
- } else if (ch == '\n') {
- tmpBuilder.append("\r\n");
- } else {
- tmpBuilder.append(str.charAt(i));
- }
- }
- str = tmpBuilder.toString();
- }
-
- StringBuilder builder = new StringBuilder();
- int index = 0;
- int lineCount = 0;
- byte[] strArray = null;
-
- try {
- strArray = str.getBytes(mCharsetString);
- } catch (UnsupportedEncodingException e) {
- Log.e(LOG_TAG, "Charset " + mCharsetString + " cannot be used. " +
- "Try default charset");
- strArray = str.getBytes();
- }
- while (index < strArray.length) {
- builder.append(String.format("=%02X", strArray[index]));
- index += 1;
- lineCount += 3;
-
- if (lineCount >= 67) {
- // Specification requires CRLF must be inserted before the length of the line
- // becomes more than 76.
- // Assuming that the next character is a multi-byte character, it will become
- // 6 bytes.
- // 76 - 6 - 3 = 67
- builder.append("=\r\n");
- lineCount = 0;
- }
- }
-
- return builder.toString();
- }
-
- // TODO: replace this with ContactStruct
- public class ContactData {
- private String mName = "";
- private String mPhoneticName = "";
- private ArrayList<TelData> mTel = new ArrayList<TelData>();
- private ArrayList<EmailData> mEmail = new ArrayList<EmailData>();
- private ArrayList<AddressData> mAddr = new ArrayList<AddressData>();
- private String mOrg = "";
- private String mTitle = "";
- private String mNote = "";
- private String mPhoto = "";
- private String mPhotoType = "JPG"; // Default
- private Map<String, String> mExtensions = new HashMap<String, String>();
-
- public boolean isEmptyName() {
- return TextUtils.isEmpty(mName);
- }
- }
-
- private class AddressData {
- private int mType;
- private String mLabel;
- private String mValue;
-
- public AddressData(int type, String label, String value) {
- mType = type;
- mLabel = label;
- mValue = value;
- }
- }
-
- private class EmailData {
- private int mType;
- private String mLabel;
- private String mValue;
-
- public EmailData(int type, String label, String value) {
- mType = type;
- mLabel = label;
- mValue = value;
- }
- }
-
- private class TelData {
- private int mType;
- private String mLabel;
- private String mValue;
-
- public TelData(int type, String label, String value) {
- mType = type;
- mLabel = label;
- mValue = value;
- }
- }
-}
-
-/**
- * TextUtils especially for Japanese.
- * TODO: make this in android.text in the future
- */
-class JapaneseUtils {
- static private final Map<Character, String> sHalfWidthMap =
- new HashMap<Character, String>();
-
- static {
- // There's no logical mapping rule in Unicode. Sigh.
- sHalfWidthMap.put('\u3001', "\uFF64");
- sHalfWidthMap.put('\u3002', "\uFF61");
- sHalfWidthMap.put('\u300C', "\uFF62");
- sHalfWidthMap.put('\u300D', "\uFF63");
- sHalfWidthMap.put('\u301C', "~");
- sHalfWidthMap.put('\u3041', "\uFF67");
- sHalfWidthMap.put('\u3042', "\uFF71");
- sHalfWidthMap.put('\u3043', "\uFF68");
- sHalfWidthMap.put('\u3044', "\uFF72");
- sHalfWidthMap.put('\u3045', "\uFF69");
- sHalfWidthMap.put('\u3046', "\uFF73");
- sHalfWidthMap.put('\u3047', "\uFF6A");
- sHalfWidthMap.put('\u3048', "\uFF74");
- sHalfWidthMap.put('\u3049', "\uFF6B");
- sHalfWidthMap.put('\u304A', "\uFF75");
- sHalfWidthMap.put('\u304B', "\uFF76");
- sHalfWidthMap.put('\u304C', "\uFF76\uFF9E");
- sHalfWidthMap.put('\u304D', "\uFF77");
- sHalfWidthMap.put('\u304E', "\uFF77\uFF9E");
- sHalfWidthMap.put('\u304F', "\uFF78");
- sHalfWidthMap.put('\u3050', "\uFF78\uFF9E");
- sHalfWidthMap.put('\u3051', "\uFF79");
- sHalfWidthMap.put('\u3052', "\uFF79\uFF9E");
- sHalfWidthMap.put('\u3053', "\uFF7A");
- sHalfWidthMap.put('\u3054', "\uFF7A\uFF9E");
- sHalfWidthMap.put('\u3055', "\uFF7B");
- sHalfWidthMap.put('\u3056', "\uFF7B\uFF9E");
- sHalfWidthMap.put('\u3057', "\uFF7C");
- sHalfWidthMap.put('\u3058', "\uFF7C\uFF9E");
- sHalfWidthMap.put('\u3059', "\uFF7D");
- sHalfWidthMap.put('\u305A', "\uFF7D\uFF9E");
- sHalfWidthMap.put('\u305B', "\uFF7E");
- sHalfWidthMap.put('\u305C', "\uFF7E\uFF9E");
- sHalfWidthMap.put('\u305D', "\uFF7F");
- sHalfWidthMap.put('\u305E', "\uFF7F\uFF9E");
- sHalfWidthMap.put('\u305F', "\uFF80");
- sHalfWidthMap.put('\u3060', "\uFF80\uFF9E");
- sHalfWidthMap.put('\u3061', "\uFF81");
- sHalfWidthMap.put('\u3062', "\uFF81\uFF9E");
- sHalfWidthMap.put('\u3063', "\uFF6F");
- sHalfWidthMap.put('\u3064', "\uFF82");
- sHalfWidthMap.put('\u3065', "\uFF82\uFF9E");
- sHalfWidthMap.put('\u3066', "\uFF83");
- sHalfWidthMap.put('\u3067', "\uFF83\uFF9E");
- sHalfWidthMap.put('\u3068', "\uFF84");
- sHalfWidthMap.put('\u3069', "\uFF84\uFF9E");
- sHalfWidthMap.put('\u306A', "\uFF85");
- sHalfWidthMap.put('\u306B', "\uFF86");
- sHalfWidthMap.put('\u306C', "\uFF87");
- sHalfWidthMap.put('\u306D', "\uFF88");
- sHalfWidthMap.put('\u306E', "\uFF89");
- sHalfWidthMap.put('\u306F', "\uFF8A");
- sHalfWidthMap.put('\u3070', "\uFF8A\uFF9E");
- sHalfWidthMap.put('\u3071', "\uFF8A\uFF9F");
- sHalfWidthMap.put('\u3072', "\uFF8B");
- sHalfWidthMap.put('\u3073', "\uFF8B\uFF9E");
- sHalfWidthMap.put('\u3074', "\uFF8B\uFF9F");
- sHalfWidthMap.put('\u3075', "\uFF8C");
- sHalfWidthMap.put('\u3076', "\uFF8C\uFF9E");
- sHalfWidthMap.put('\u3077', "\uFF8C\uFF9F");
- sHalfWidthMap.put('\u3078', "\uFF8D");
- sHalfWidthMap.put('\u3079', "\uFF8D\uFF9E");
- sHalfWidthMap.put('\u307A', "\uFF8D\uFF9F");
- sHalfWidthMap.put('\u307B', "\uFF8E");
- sHalfWidthMap.put('\u307C', "\uFF8E\uFF9E");
- sHalfWidthMap.put('\u307D', "\uFF8E\uFF9F");
- sHalfWidthMap.put('\u307E', "\uFF8F");
- sHalfWidthMap.put('\u307F', "\uFF90");
- sHalfWidthMap.put('\u3080', "\uFF91");
- sHalfWidthMap.put('\u3081', "\uFF92");
- sHalfWidthMap.put('\u3082', "\uFF93");
- sHalfWidthMap.put('\u3083', "\uFF6C");
- sHalfWidthMap.put('\u3084', "\uFF94");
- sHalfWidthMap.put('\u3085', "\uFF6D");
- sHalfWidthMap.put('\u3086', "\uFF95");
- sHalfWidthMap.put('\u3087', "\uFF6E");
- sHalfWidthMap.put('\u3088', "\uFF96");
- sHalfWidthMap.put('\u3089', "\uFF97");
- sHalfWidthMap.put('\u308A', "\uFF98");
- sHalfWidthMap.put('\u308B', "\uFF99");
- sHalfWidthMap.put('\u308C', "\uFF9A");
- sHalfWidthMap.put('\u308D', "\uFF9B");
- sHalfWidthMap.put('\u308E', "\uFF9C");
- sHalfWidthMap.put('\u308F', "\uFF9C");
- sHalfWidthMap.put('\u3090', "\uFF72");
- sHalfWidthMap.put('\u3091', "\uFF74");
- sHalfWidthMap.put('\u3092', "\uFF66");
- sHalfWidthMap.put('\u3093', "\uFF9D");
- sHalfWidthMap.put('\u309B', "\uFF9E");
- sHalfWidthMap.put('\u309C', "\uFF9F");
- sHalfWidthMap.put('\u30A1', "\uFF67");
- sHalfWidthMap.put('\u30A2', "\uFF71");
- sHalfWidthMap.put('\u30A3', "\uFF68");
- sHalfWidthMap.put('\u30A4', "\uFF72");
- sHalfWidthMap.put('\u30A5', "\uFF69");
- sHalfWidthMap.put('\u30A6', "\uFF73");
- sHalfWidthMap.put('\u30A7', "\uFF6A");
- sHalfWidthMap.put('\u30A8', "\uFF74");
- sHalfWidthMap.put('\u30A9', "\uFF6B");
- sHalfWidthMap.put('\u30AA', "\uFF75");
- sHalfWidthMap.put('\u30AB', "\uFF76");
- sHalfWidthMap.put('\u30AC', "\uFF76\uFF9E");
- sHalfWidthMap.put('\u30AD', "\uFF77");
- sHalfWidthMap.put('\u30AE', "\uFF77\uFF9E");
- sHalfWidthMap.put('\u30AF', "\uFF78");
- sHalfWidthMap.put('\u30B0', "\uFF78\uFF9E");
- sHalfWidthMap.put('\u30B1', "\uFF79");
- sHalfWidthMap.put('\u30B2', "\uFF79\uFF9E");
- sHalfWidthMap.put('\u30B3', "\uFF7A");
- sHalfWidthMap.put('\u30B4', "\uFF7A\uFF9E");
- sHalfWidthMap.put('\u30B5', "\uFF7B");
- sHalfWidthMap.put('\u30B6', "\uFF7B\uFF9E");
- sHalfWidthMap.put('\u30B7', "\uFF7C");
- sHalfWidthMap.put('\u30B8', "\uFF7C\uFF9E");
- sHalfWidthMap.put('\u30B9', "\uFF7D");
- sHalfWidthMap.put('\u30BA', "\uFF7D\uFF9E");
- sHalfWidthMap.put('\u30BB', "\uFF7E");
- sHalfWidthMap.put('\u30BC', "\uFF7E\uFF9E");
- sHalfWidthMap.put('\u30BD', "\uFF7F");
- sHalfWidthMap.put('\u30BE', "\uFF7F\uFF9E");
- sHalfWidthMap.put('\u30BF', "\uFF80");
- sHalfWidthMap.put('\u30C0', "\uFF80\uFF9E");
- sHalfWidthMap.put('\u30C1', "\uFF81");
- sHalfWidthMap.put('\u30C2', "\uFF81\uFF9E");
- sHalfWidthMap.put('\u30C3', "\uFF6F");
- sHalfWidthMap.put('\u30C4', "\uFF82");
- sHalfWidthMap.put('\u30C5', "\uFF82\uFF9E");
- sHalfWidthMap.put('\u30C6', "\uFF83");
- sHalfWidthMap.put('\u30C7', "\uFF83\uFF9E");
- sHalfWidthMap.put('\u30C8', "\uFF84");
- sHalfWidthMap.put('\u30C9', "\uFF84\uFF9E");
- sHalfWidthMap.put('\u30CA', "\uFF85");
- sHalfWidthMap.put('\u30CB', "\uFF86");
- sHalfWidthMap.put('\u30CC', "\uFF87");
- sHalfWidthMap.put('\u30CD', "\uFF88");
- sHalfWidthMap.put('\u30CE', "\uFF89");
- sHalfWidthMap.put('\u30CF', "\uFF8A");
- sHalfWidthMap.put('\u30D0', "\uFF8A\uFF9E");
- sHalfWidthMap.put('\u30D1', "\uFF8A\uFF9F");
- sHalfWidthMap.put('\u30D2', "\uFF8B");
- sHalfWidthMap.put('\u30D3', "\uFF8B\uFF9E");
- sHalfWidthMap.put('\u30D4', "\uFF8B\uFF9F");
- sHalfWidthMap.put('\u30D5', "\uFF8C");
- sHalfWidthMap.put('\u30D6', "\uFF8C\uFF9E");
- sHalfWidthMap.put('\u30D7', "\uFF8C\uFF9F");
- sHalfWidthMap.put('\u30D8', "\uFF8D");
- sHalfWidthMap.put('\u30D9', "\uFF8D\uFF9E");
- sHalfWidthMap.put('\u30DA', "\uFF8D\uFF9F");
- sHalfWidthMap.put('\u30DB', "\uFF8E");
- sHalfWidthMap.put('\u30DC', "\uFF8E\uFF9E");
- sHalfWidthMap.put('\u30DD', "\uFF8E\uFF9F");
- sHalfWidthMap.put('\u30DE', "\uFF8F");
- sHalfWidthMap.put('\u30DF', "\uFF90");
- sHalfWidthMap.put('\u30E0', "\uFF91");
- sHalfWidthMap.put('\u30E1', "\uFF92");
- sHalfWidthMap.put('\u30E2', "\uFF93");
- sHalfWidthMap.put('\u30E3', "\uFF6C");
- sHalfWidthMap.put('\u30E4', "\uFF94");
- sHalfWidthMap.put('\u30E5', "\uFF6D");
- sHalfWidthMap.put('\u30E6', "\uFF95");
- sHalfWidthMap.put('\u30E7', "\uFF6E");
- sHalfWidthMap.put('\u30E8', "\uFF96");
- sHalfWidthMap.put('\u30E9', "\uFF97");
- sHalfWidthMap.put('\u30EA', "\uFF98");
- sHalfWidthMap.put('\u30EB', "\uFF99");
- sHalfWidthMap.put('\u30EC', "\uFF9A");
- sHalfWidthMap.put('\u30ED', "\uFF9B");
- sHalfWidthMap.put('\u30EE', "\uFF9C");
- sHalfWidthMap.put('\u30EF', "\uFF9C");
- sHalfWidthMap.put('\u30F0', "\uFF72");
- sHalfWidthMap.put('\u30F1', "\uFF74");
- sHalfWidthMap.put('\u30F2', "\uFF66");
- sHalfWidthMap.put('\u30F3', "\uFF9D");
- sHalfWidthMap.put('\u30F4', "\uFF73\uFF9E");
- sHalfWidthMap.put('\u30F5', "\uFF76");
- sHalfWidthMap.put('\u30F6', "\uFF79");
- sHalfWidthMap.put('\u30FB', "\uFF65");
- sHalfWidthMap.put('\u30FC', "\uFF70");
- sHalfWidthMap.put('\uFF01', "!");
- sHalfWidthMap.put('\uFF02', "\"");
- sHalfWidthMap.put('\uFF03', "#");
- sHalfWidthMap.put('\uFF04', "$");
- sHalfWidthMap.put('\uFF05', "%");
- sHalfWidthMap.put('\uFF06', "&");
- sHalfWidthMap.put('\uFF07', "'");
- sHalfWidthMap.put('\uFF08', "(");
- sHalfWidthMap.put('\uFF09', ")");
- sHalfWidthMap.put('\uFF0A', "*");
- sHalfWidthMap.put('\uFF0B', "+");
- sHalfWidthMap.put('\uFF0C', ",");
- sHalfWidthMap.put('\uFF0D', "-");
- sHalfWidthMap.put('\uFF0E', ".");
- sHalfWidthMap.put('\uFF0F', "/");
- sHalfWidthMap.put('\uFF10', "0");
- sHalfWidthMap.put('\uFF11', "1");
- sHalfWidthMap.put('\uFF12', "2");
- sHalfWidthMap.put('\uFF13', "3");
- sHalfWidthMap.put('\uFF14', "4");
- sHalfWidthMap.put('\uFF15', "5");
- sHalfWidthMap.put('\uFF16', "6");
- sHalfWidthMap.put('\uFF17', "7");
- sHalfWidthMap.put('\uFF18', "8");
- sHalfWidthMap.put('\uFF19', "9");
- sHalfWidthMap.put('\uFF1A', ":");
- sHalfWidthMap.put('\uFF1B', ";");
- sHalfWidthMap.put('\uFF1C', "<");
- sHalfWidthMap.put('\uFF1D', "=");
- sHalfWidthMap.put('\uFF1E', ">");
- sHalfWidthMap.put('\uFF1F', "?");
- sHalfWidthMap.put('\uFF20', "@");
- sHalfWidthMap.put('\uFF21', "A");
- sHalfWidthMap.put('\uFF22', "B");
- sHalfWidthMap.put('\uFF23', "C");
- sHalfWidthMap.put('\uFF24', "D");
- sHalfWidthMap.put('\uFF25', "E");
- sHalfWidthMap.put('\uFF26', "F");
- sHalfWidthMap.put('\uFF27', "G");
- sHalfWidthMap.put('\uFF28', "H");
- sHalfWidthMap.put('\uFF29', "I");
- sHalfWidthMap.put('\uFF2A', "J");
- sHalfWidthMap.put('\uFF2B', "K");
- sHalfWidthMap.put('\uFF2C', "L");
- sHalfWidthMap.put('\uFF2D', "M");
- sHalfWidthMap.put('\uFF2E', "N");
- sHalfWidthMap.put('\uFF2F', "O");
- sHalfWidthMap.put('\uFF30', "P");
- sHalfWidthMap.put('\uFF31', "Q");
- sHalfWidthMap.put('\uFF32', "R");
- sHalfWidthMap.put('\uFF33', "S");
- sHalfWidthMap.put('\uFF34', "T");
- sHalfWidthMap.put('\uFF35', "U");
- sHalfWidthMap.put('\uFF36', "V");
- sHalfWidthMap.put('\uFF37', "W");
- sHalfWidthMap.put('\uFF38', "X");
- sHalfWidthMap.put('\uFF39', "Y");
- sHalfWidthMap.put('\uFF3A', "Z");
- sHalfWidthMap.put('\uFF3B', "[");
- sHalfWidthMap.put('\uFF3C', "\\");
- sHalfWidthMap.put('\uFF3D', "]");
- sHalfWidthMap.put('\uFF3E', "^");
- sHalfWidthMap.put('\uFF3F', "_");
- sHalfWidthMap.put('\uFF41', "a");
- sHalfWidthMap.put('\uFF42', "b");
- sHalfWidthMap.put('\uFF43', "c");
- sHalfWidthMap.put('\uFF44', "d");
- sHalfWidthMap.put('\uFF45', "e");
- sHalfWidthMap.put('\uFF46', "f");
- sHalfWidthMap.put('\uFF47', "g");
- sHalfWidthMap.put('\uFF48', "h");
- sHalfWidthMap.put('\uFF49', "i");
- sHalfWidthMap.put('\uFF4A', "j");
- sHalfWidthMap.put('\uFF4B', "k");
- sHalfWidthMap.put('\uFF4C', "l");
- sHalfWidthMap.put('\uFF4D', "m");
- sHalfWidthMap.put('\uFF4E', "n");
- sHalfWidthMap.put('\uFF4F', "o");
- sHalfWidthMap.put('\uFF50', "p");
- sHalfWidthMap.put('\uFF51', "q");
- sHalfWidthMap.put('\uFF52', "r");
- sHalfWidthMap.put('\uFF53', "s");
- sHalfWidthMap.put('\uFF54', "t");
- sHalfWidthMap.put('\uFF55', "u");
- sHalfWidthMap.put('\uFF56', "v");
- sHalfWidthMap.put('\uFF57', "w");
- sHalfWidthMap.put('\uFF58', "x");
- sHalfWidthMap.put('\uFF59', "y");
- sHalfWidthMap.put('\uFF5A', "z");
- sHalfWidthMap.put('\uFF5B', "{");
- sHalfWidthMap.put('\uFF5C', "|");
- sHalfWidthMap.put('\uFF5D', "}");
- sHalfWidthMap.put('\uFF5E', "~");
- sHalfWidthMap.put('\uFF61', "\uFF61");
- sHalfWidthMap.put('\uFF62', "\uFF62");
- sHalfWidthMap.put('\uFF63', "\uFF63");
- sHalfWidthMap.put('\uFF64', "\uFF64");
- sHalfWidthMap.put('\uFF65', "\uFF65");
- sHalfWidthMap.put('\uFF66', "\uFF66");
- sHalfWidthMap.put('\uFF67', "\uFF67");
- sHalfWidthMap.put('\uFF68', "\uFF68");
- sHalfWidthMap.put('\uFF69', "\uFF69");
- sHalfWidthMap.put('\uFF6A', "\uFF6A");
- sHalfWidthMap.put('\uFF6B', "\uFF6B");
- sHalfWidthMap.put('\uFF6C', "\uFF6C");
- sHalfWidthMap.put('\uFF6D', "\uFF6D");
- sHalfWidthMap.put('\uFF6E', "\uFF6E");
- sHalfWidthMap.put('\uFF6F', "\uFF6F");
- sHalfWidthMap.put('\uFF70', "\uFF70");
- sHalfWidthMap.put('\uFF71', "\uFF71");
- sHalfWidthMap.put('\uFF72', "\uFF72");
- sHalfWidthMap.put('\uFF73', "\uFF73");
- sHalfWidthMap.put('\uFF74', "\uFF74");
- sHalfWidthMap.put('\uFF75', "\uFF75");
- sHalfWidthMap.put('\uFF76', "\uFF76");
- sHalfWidthMap.put('\uFF77', "\uFF77");
- sHalfWidthMap.put('\uFF78', "\uFF78");
- sHalfWidthMap.put('\uFF79', "\uFF79");
- sHalfWidthMap.put('\uFF7A', "\uFF7A");
- sHalfWidthMap.put('\uFF7B', "\uFF7B");
- sHalfWidthMap.put('\uFF7C', "\uFF7C");
- sHalfWidthMap.put('\uFF7D', "\uFF7D");
- sHalfWidthMap.put('\uFF7E', "\uFF7E");
- sHalfWidthMap.put('\uFF7F', "\uFF7F");
- sHalfWidthMap.put('\uFF80', "\uFF80");
- sHalfWidthMap.put('\uFF81', "\uFF81");
- sHalfWidthMap.put('\uFF82', "\uFF82");
- sHalfWidthMap.put('\uFF83', "\uFF83");
- sHalfWidthMap.put('\uFF84', "\uFF84");
- sHalfWidthMap.put('\uFF85', "\uFF85");
- sHalfWidthMap.put('\uFF86', "\uFF86");
- sHalfWidthMap.put('\uFF87', "\uFF87");
- sHalfWidthMap.put('\uFF88', "\uFF88");
- sHalfWidthMap.put('\uFF89', "\uFF89");
- sHalfWidthMap.put('\uFF8A', "\uFF8A");
- sHalfWidthMap.put('\uFF8B', "\uFF8B");
- sHalfWidthMap.put('\uFF8C', "\uFF8C");
- sHalfWidthMap.put('\uFF8D', "\uFF8D");
- sHalfWidthMap.put('\uFF8E', "\uFF8E");
- sHalfWidthMap.put('\uFF8F', "\uFF8F");
- sHalfWidthMap.put('\uFF90', "\uFF90");
- sHalfWidthMap.put('\uFF91', "\uFF91");
- sHalfWidthMap.put('\uFF92', "\uFF92");
- sHalfWidthMap.put('\uFF93', "\uFF93");
- sHalfWidthMap.put('\uFF94', "\uFF94");
- sHalfWidthMap.put('\uFF95', "\uFF95");
- sHalfWidthMap.put('\uFF96', "\uFF96");
- sHalfWidthMap.put('\uFF97', "\uFF97");
- sHalfWidthMap.put('\uFF98', "\uFF98");
- sHalfWidthMap.put('\uFF99', "\uFF99");
- sHalfWidthMap.put('\uFF9A', "\uFF9A");
- sHalfWidthMap.put('\uFF9B', "\uFF9B");
- sHalfWidthMap.put('\uFF9C', "\uFF9C");
- sHalfWidthMap.put('\uFF9D', "\uFF9D");
- sHalfWidthMap.put('\uFF9E', "\uFF9E");
- sHalfWidthMap.put('\uFF9F', "\uFF9F");
- sHalfWidthMap.put('\uFFE5', "\u005C\u005C");
- }
-
- /**
- * Return half-width version of that character if possible. Return null if not possible
- * @param ch input character
- * @return CharSequence object if the mapping for ch exists. Return null otherwise.
- */
- public static CharSequence tryGetHalfWidthText(char ch) {
- if (sHalfWidthMap.containsKey(ch)) {
- return sHalfWidthMap.get(ch);
- } else {
- return null;
- }
- }
-}
diff --git a/src/com/android/contacts/ViewContactActivity.java b/src/com/android/contacts/ViewContactActivity.java
index 28a82b4..06296cc 100644
--- a/src/com/android/contacts/ViewContactActivity.java
+++ b/src/com/android/contacts/ViewContactActivity.java
@@ -16,132 +16,139 @@
package com.android.contacts;
-import static com.android.contacts.ContactEntryAdapter.CONTACT_CUSTOM_RINGTONE_COLUMN;
-import static com.android.contacts.ContactEntryAdapter.CONTACT_NAME_COLUMN;
-import static com.android.contacts.ContactEntryAdapter.CONTACT_NOTES_COLUMN;
-import static com.android.contacts.ContactEntryAdapter.CONTACT_PHONETIC_NAME_COLUMN;
-import static com.android.contacts.ContactEntryAdapter.CONTACT_PROJECTION;
-import static com.android.contacts.ContactEntryAdapter.CONTACT_SEND_TO_VOICEMAIL_COLUMN;
-import static com.android.contacts.ContactEntryAdapter.CONTACT_STARRED_COLUMN;
-import static com.android.contacts.ContactEntryAdapter.METHODS_AUX_DATA_COLUMN;
-import static com.android.contacts.ContactEntryAdapter.METHODS_DATA_COLUMN;
-import static com.android.contacts.ContactEntryAdapter.METHODS_ID_COLUMN;
-import static com.android.contacts.ContactEntryAdapter.METHODS_KIND_COLUMN;
-import static com.android.contacts.ContactEntryAdapter.METHODS_LABEL_COLUMN;
-import static com.android.contacts.ContactEntryAdapter.METHODS_STATUS_COLUMN;
-import static com.android.contacts.ContactEntryAdapter.METHODS_TYPE_COLUMN;
-import static com.android.contacts.ContactEntryAdapter.METHODS_WITH_PRESENCE_PROJECTION;
-import static com.android.contacts.ContactEntryAdapter.ORGANIZATIONS_COMPANY_COLUMN;
-import static com.android.contacts.ContactEntryAdapter.ORGANIZATIONS_ID_COLUMN;
-import static com.android.contacts.ContactEntryAdapter.ORGANIZATIONS_LABEL_COLUMN;
-import static com.android.contacts.ContactEntryAdapter.ORGANIZATIONS_PROJECTION;
-import static com.android.contacts.ContactEntryAdapter.ORGANIZATIONS_TITLE_COLUMN;
-import static com.android.contacts.ContactEntryAdapter.ORGANIZATIONS_TYPE_COLUMN;
-import static com.android.contacts.ContactEntryAdapter.PHONES_ID_COLUMN;
-import static com.android.contacts.ContactEntryAdapter.PHONES_ISPRIMARY_COLUMN;
-import static com.android.contacts.ContactEntryAdapter.PHONES_LABEL_COLUMN;
-import static com.android.contacts.ContactEntryAdapter.PHONES_NUMBER_COLUMN;
-import static com.android.contacts.ContactEntryAdapter.PHONES_PROJECTION;
-import static com.android.contacts.ContactEntryAdapter.PHONES_TYPE_COLUMN;
-
+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.app.ListActivity;
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.pm.PackageManager;
-import android.content.pm.ResolveInfo;
+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.media.Ringtone;
-import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteException;
import android.os.ServiceManager;
-import android.os.SystemClock;
-import android.provider.Contacts;
-import android.provider.Im;
-import android.provider.Contacts.ContactMethods;
-import android.provider.Contacts.Organizations;
-import android.provider.Contacts.People;
-import android.provider.Contacts.Phones;
-import android.provider.Contacts.Presence;
+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.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.StructuredPostal;
+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.CheckBox;
+import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ListView;
+import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;
import java.util.ArrayList;
-import java.util.List;
+import java.util.HashMap;
/**
* Displays the details of a specific contact.
*/
-public class ViewContactActivity extends ListActivity
- implements View.OnCreateContextMenuListener, View.OnClickListener,
- DialogInterface.OnClickListener {
+public class ViewContactActivity extends Activity
+ implements View.OnCreateContextMenuListener, DialogInterface.OnClickListener,
+ AdapterView.OnItemClickListener, NotifyingAsyncQueryHandler.AsyncQueryListener {
private static final String TAG = "ViewContact";
- private static final String SHOW_BARCODE_INTENT = "com.google.zxing.client.android.ENCODE";
private static final boolean SHOW_SEPARATORS = false;
-
- private static final String[] PHONE_KEYS = {
- Contacts.Intents.Insert.PHONE,
- Contacts.Intents.Insert.SECONDARY_PHONE,
- Contacts.Intents.Insert.TERTIARY_PHONE
- };
-
- private static final String[] EMAIL_KEYS = {
- Contacts.Intents.Insert.EMAIL,
- Contacts.Intents.Insert.SECONDARY_EMAIL,
- Contacts.Intents.Insert.TERTIARY_EMAIL
- };
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;
- public static final int MENU_ITEM_DELETE = 1;
- public static final int MENU_ITEM_MAKE_DEFAULT = 2;
- public static final int MENU_ITEM_SHOW_BARCODE = 3;
+ private static final int REQUEST_JOIN_CONTACT = 1;
+ private static final int REQUEST_EDIT_CONTACT = 2;
- private Uri mUri;
+ 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> 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;
- private boolean mObserverRegistered;
-
+
+ protected ContactHeaderWidget mContactHeaderWidget;
+ private NotifyingAsyncQueryHandler mHandler;
+
+ protected LayoutInflater mInflater;
+
+ protected int mReadOnlySourcesCnt;
+ protected int mWritableSourcesCnt;
+ 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 ArrayList<Entity> mEntities = Lists.newArrayList();
+ private HashMap<Long, DataStatus> mStatuses = Maps.newHashMap();
+
private ContentObserver mObserver = new ContentObserver(new Handler()) {
@Override
public boolean deliverSelfNotifications() {
@@ -150,75 +157,55 @@
@Override
public void onChange(boolean selfChange) {
- if (mCursor != null && !mCursor.isClosed()){
- dataChanged();
+ if (mCursor != null && !mCursor.isClosed()) {
+ startEntityQuery();
}
}
};
public void onClick(DialogInterface dialog, int which) {
- if (mCursor != null) {
- if (mObserverRegistered) {
- mCursor.unregisterContentObserver(mObserver);
- mObserverRegistered = false;
- }
- mCursor.close();
- mCursor = null;
- }
- getContentResolver().delete(mUri, null, null);
+ closeCursor();
+ getContentResolver().delete(mLookupUri, null, null);
finish();
}
- public void onClick(View view) {
- if (!mObserverRegistered) {
- return;
- }
- switch (view.getId()) {
- case R.id.star: {
- int oldStarredState = mCursor.getInt(CONTACT_STARRED_COLUMN);
- ContentValues values = new ContentValues(1);
- values.put(People.STARRED, oldStarredState == 1 ? 0 : 1);
- getContentResolver().update(mUri, values, null, null);
- break;
- }
- }
- }
-
- private TextView mNameView;
- private TextView mPhoneticNameView; // may be null in some locales
- private ImageView mPhotoView;
- private int mNoPhotoResource;
- private CheckBox mStarView;
+ private ListView mListView;
private boolean mShowSmsLinksForAllPhones;
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
- setContentView(R.layout.view_contact);
- getListView().setOnCreateContextMenuListener(this);
+ 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));
- mNameView = (TextView) findViewById(R.id.name);
- mPhoneticNameView = (TextView) findViewById(R.id.phonetic_name);
- mPhotoView = (ImageView) findViewById(R.id.photo);
- mStarView = (CheckBox) findViewById(R.id.star);
- mStarView.setOnClickListener(this);
-
- // Set the photo with a random "no contact" image
- long now = SystemClock.elapsedRealtime();
- int num = (int) now & 0xf;
- if (num < 9) {
- // Leaning in from right, common
- mNoPhotoResource = R.drawable.ic_contact_picture;
- } else if (num < 14) {
- // Leaning in from left uncommon
- mNoPhotoResource = R.drawable.ic_contact_picture_2;
- } else {
- // Coming in from the top, rare
- mNoPhotoResource = R.drawable.ic_contact_picture_3;
}
+ mInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- mUri = getIntent().getData();
+ 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);
+ mListView.setEmptyView((ScrollView) findViewById(android.R.id.empty));
+
mResolver = getContentResolver();
// Build the list of sections. The order they're added to mSections dictates the
@@ -229,45 +216,29 @@
mSections.add(mImEntries);
mSections.add(mPostalEntries);
mSections.add(mOrganizationEntries);
+ mSections.add(mGroupEntries);
mSections.add(mOtherEntries);
//TODO Read this value from a preference
mShowSmsLinksForAllPhones = true;
-
- mCursor = mResolver.query(mUri, CONTACT_PROJECTION, null, null, null);
}
@Override
protected void onResume() {
super.onResume();
- mObserverRegistered = true;
- mCursor.registerContentObserver(mObserver);
- dataChanged();
+ startEntityQuery();
}
@Override
protected void onPause() {
super.onPause();
- if (mCursor != null) {
- if (mObserverRegistered) {
- mObserverRegistered = false;
- mCursor.unregisterContentObserver(mObserver);
- }
- mCursor.deactivate();
- }
+ closeCursor();
}
@Override
protected void onDestroy() {
super.onDestroy();
-
- if (mCursor != null) {
- if (mObserverRegistered) {
- mCursor.unregisterContentObserver(mObserver);
- mObserverRegistered = false;
- }
- mCursor.close();
- }
+ closeCursor();
}
@Override
@@ -282,81 +253,205 @@
.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;
}
- private void dataChanged() {
- mCursor.requery();
- if (mCursor.moveToFirst()) {
- // Set the name
- String name = mCursor.getString(CONTACT_NAME_COLUMN);
- if (TextUtils.isEmpty(name)) {
- mNameView.setText(getText(android.R.string.unknownName));
- } else {
- mNameView.setText(name);
+ // QUERY CODE //
+ /** {@inheritDoc} */
+ public void onQueryEntitiesComplete(int token, Object cookie, EntityIterator iterator) {
+ try {
+ // Read incoming entities and consider binding
+ readEntities(iterator);
+ considerBindData();
+ } finally {
+ if (iterator != null) {
+ iterator.close();
}
+ }
+ }
- if (mPhoneticNameView != null) {
- String phoneticName = mCursor.getString(CONTACT_PHONETIC_NAME_COLUMN);
- mPhoneticNameView.setText(phoneticName);
+ /** {@inheritDoc} */
+ public void onQueryComplete(int token, Object cookie, Cursor cursor) {
+ try {
+ // Read available social rows and consider binding
+ readStatuses(cursor);
+ considerBindData();
+ } finally {
+ if (cursor != null) {
+ cursor.close();
}
+ }
+ }
- // Load the photo
- mPhotoView.setImageBitmap(People.loadContactPhoto(this, mUri, mNoPhotoResource,
- null /* use the default options */));
+ private long getRefreshedContactId() {
+ Uri freshContactUri = Contacts.lookupContact(getContentResolver(), mLookupUri);
+ if (freshContactUri != null) {
+ return ContentUris.parseId(freshContactUri);
+ }
+ return -1;
+ }
- // Set the star
- mStarView.setChecked(mCursor.getInt(CONTACT_STARRED_COLUMN) == 1 ? true : false);
-
- // Build up the contact entries
- buildEntries(mCursor);
- if (mAdapter == null) {
- mAdapter = new ViewAdapter(this, mSections);
- setListAdapter(mAdapter);
- } else {
- mAdapter.setSections(mSections, SHOW_SEPARATORS);
+ /**
+ * Read from the given {@link EntityIterator} to build internal set of
+ * {@link #mEntities} for data display.
+ */
+ private synchronized void readEntities(EntityIterator iterator) {
+ mEntities.clear();
+ try {
+ while (iterator.hasNext()) {
+ mEntities.add(iterator.next());
}
- } else {
+ mHasEntities = true;
+ } catch (RemoteException e) {
+ Log.w(TAG, "Problem reading contact data: " + e.toString());
+ }
+ }
+
+ /**
+ * 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 synchronized void startEntityQuery() {
+ closeCursor();
+
+ Uri uri = null;
+ if (mLookupUri != null) {
+ mLookupUri = Contacts.getLookupUri(getContentResolver(), mLookupUri);
+ if (mLookupUri != null) {
+ uri = Contacts.lookupContact(getContentResolver(), mLookupUri);
+ }
+ }
+
+ if (uri == null) {
+
+ // 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: " + mUri);
+ Log.e(TAG, "invalid contact uri: " + mLookupUri);
finish();
+ return;
+ }
+
+ final Uri dataUri = Uri.withAppendedPath(uri, Contacts.Data.CONTENT_DIRECTORY);
+
+ // Keep stub cursor open on side to watch for change events
+ mCursor = mResolver.query(dataUri,
+ new String[] {Contacts.DISPLAY_NAME}, null, null, null);
+ mCursor.registerContentObserver(mObserver);
+
+ final long contactId = ContentUris.parseId(uri);
+
+ // Clear flags and start queries to data and status
+ mHasEntities = false;
+ mHasStatuses = false;
+
+ mHandler.startQueryEntities(TOKEN_ENTITIES, null, RawContacts.CONTENT_URI,
+ RawContacts.CONTACT_ID + "=" + contactId, null, null);
+ 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);
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
- menu.add(0, 0, 0, R.string.menu_editContact)
- .setIcon(android.R.drawable.ic_menu_edit)
- .setIntent(new Intent(Intent.ACTION_EDIT, mUri))
- .setAlphabeticShortcut('e');
- menu.add(0, MENU_ITEM_DELETE, 0, R.string.menu_deleteContact)
- .setIcon(android.R.drawable.ic_menu_delete);
+ super.onCreateOptionsMenu(menu);
+ final MenuInflater inflater = getMenuInflater();
+ inflater.inflate(R.menu.view, menu);
return true;
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
- // Perform this check each time the menu is about to be shown, because the Barcode Scanner
- // could be installed or uninstalled at any time.
- if (isBarcodeScannerInstalled()) {
- if (menu.findItem(MENU_ITEM_SHOW_BARCODE) == null) {
- menu.add(0, MENU_ITEM_SHOW_BARCODE, 0, R.string.menu_showBarcode)
- .setIcon(R.drawable.ic_menu_show_barcode);
- }
- } else {
- menu.removeItem(MENU_ITEM_SHOW_BARCODE);
- }
- return true;
- }
- private boolean isBarcodeScannerInstalled() {
- final Intent intent = new Intent(SHOW_BARCODE_INTENT);
- ResolveInfo ri = getPackageManager().resolveActivity(intent,
- PackageManager.MATCH_DEFAULT_ONLY);
- return ri != null;
+ // 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);
+
+ return true;
}
@Override
@@ -376,105 +471,212 @@
}
ViewEntry entry = ContactEntryAdapter.getEntry(mSections, info.position, SHOW_SEPARATORS);
- switch (entry.kind) {
- case Contacts.KIND_PHONE: {
- menu.add(0, 0, 0, R.string.menu_call).setIntent(entry.intent);
- menu.add(0, 0, 0, R.string.menu_sendSMS).setIntent(entry.auxIntent);
- if (entry.primaryIcon == -1) {
- menu.add(0, MENU_ITEM_MAKE_DEFAULT, 0, R.string.menu_makeDefaultNumber);
- }
- break;
+ 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);
}
-
- case Contacts.KIND_EMAIL: {
- menu.add(0, 0, 0, R.string.menu_sendEmail).setIntent(entry.intent);
- break;
+ } 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);
}
-
- case Contacts.KIND_POSTAL: {
- menu.add(0, 0, 0, R.string.menu_viewAddress).setIntent(entry.intent);
- break;
- }
+ } 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 MENU_ITEM_DELETE: {
+ 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
- showDialog(DIALOG_CONFIRM_DELETE);
+ 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 MENU_ITEM_SHOW_BARCODE:
- if (mCursor.moveToFirst()) {
- Intent intent = new Intent(SHOW_BARCODE_INTENT);
- intent.putExtra("ENCODE_TYPE", "CONTACT_TYPE");
- Bundle bundle = new Bundle();
- String name = mCursor.getString(CONTACT_NAME_COLUMN);
- if (!TextUtils.isEmpty(name)) {
- // Correctly handle when section headers are hidden
- int sepAdjust = SHOW_SEPARATORS ? 1 : 0;
-
- bundle.putString(Contacts.Intents.Insert.NAME, name);
- // The 0th ViewEntry in each ArrayList below is a separator item
- int entriesToAdd = Math.min(mPhoneEntries.size() - sepAdjust, PHONE_KEYS.length);
- for (int x = 0; x < entriesToAdd; x++) {
- ViewEntry entry = mPhoneEntries.get(x + sepAdjust);
- bundle.putString(PHONE_KEYS[x], entry.data);
- }
- entriesToAdd = Math.min(mEmailEntries.size() - sepAdjust, EMAIL_KEYS.length);
- for (int x = 0; x < entriesToAdd; x++) {
- ViewEntry entry = mEmailEntries.get(x + sepAdjust);
- bundle.putString(EMAIL_KEYS[x], entry.data);
- }
- if (mPostalEntries.size() >= 1 + sepAdjust) {
- ViewEntry entry = mPostalEntries.get(sepAdjust);
- bundle.putString(Contacts.Intents.Insert.POSTAL, entry.data);
- }
- intent.putExtra("ENCODE_DATA", bundle);
- try {
- startActivity(intent);
- } catch (ActivityNotFoundException e) {
- // The check in onPrepareOptionsMenu() should make this impossible, but
- // for safety I'm catching the exception rather than crashing. Ideally
- // I'd call Menu.removeItem() here too, but I don't see a way to get
- // the options menu.
- Log.e(TAG, "Show barcode menu item was clicked but Barcode Scanner " +
- "was not installed.");
- }
- return true;
- }
+ case R.id.menu_join: {
+ showJoinAggregateActivity();
+ return true;
+ }
+ case R.id.menu_options: {
+ showOptionsActivity();
+ return true;
+ }
+ case R.id.menu_share: {
+ // TODO: Keep around actual LOOKUP_KEY, or formalize method of extracting
+ final String lookupKey = 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();
}
- break;
+ return true;
+ }
}
return super.onOptionsItemSelected(item);
}
-
+
@Override
public boolean onContextItemSelected(MenuItem item) {
switch (item.getItemId()) {
case MENU_ITEM_MAKE_DEFAULT: {
- AdapterView.AdapterContextMenuInfo info;
- try {
- info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
- } catch (ClassCastException e) {
- Log.e(TAG, "bad menuInfo", e);
- break;
+ if (makeItemDefault(item)) {
+ return true;
}
-
- ViewEntry entry = ContactEntryAdapter.getEntry(mSections, info.position,
- SHOW_SEPARATORS);
- ContentValues values = new ContentValues(1);
- values.put(People.PRIMARY_PHONE_ID, entry.id);
- getContentResolver().update(mUri, values, null, null);
- dataChanged();
- 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 splitContact(long rawContactId) {
+ setAggregationException(rawContactId, AggregationExceptions.TYPE_KEEP_SEPARATE);
+
+ // The split operation may have removed the original aggregate contact, so we need
+ // to requery everything
+ Toast.makeText(this, R.string.contactsSplitMessage, Toast.LENGTH_LONG).show();
+ startEntityQuery();
+ }
+
+ 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) {
@@ -490,23 +692,35 @@
// Fall through and try to call the contact
}
- int index = getListView().getSelectedItemPosition();
+ int index = mListView.getSelectedItemPosition();
if (index != -1) {
ViewEntry entry = ViewAdapter.getEntry(mSections, index, SHOW_SEPARATORS);
- if (entry.kind == Contacts.KIND_PHONE) {
- Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED, entry.uri);
- startActivity(intent);
+ if (entry.intent.getAction() == Intent.ACTION_CALL_PRIVILEGED) {
+ startActivity(entry.intent);
}
} else if (mNumPhoneNumbers != 0) {
// There isn't anything selected, call the default number
- Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED, mUri);
- startActivity(intent);
+ long freshContactId = getRefreshedContactId();
+ if (freshContactId > 0) {
+ Uri hardContacUri = ContentUris.withAppendedId(
+ Contacts.CONTENT_URI, freshContactId);
+ Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED, hardContacUri);
+ startActivity(intent);
+ }
}
return true;
}
case KeyEvent.KEYCODE_DEL: {
- showDialog(DIALOG_CONFIRM_DELETE);
+ 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;
}
}
@@ -514,8 +728,7 @@
return super.onKeyDown(keyCode, event);
}
- @Override
- protected void onListItemClick(ListView l, View v, int position, long id) {
+ 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;
@@ -542,413 +755,345 @@
}
/**
- * Build separator entries for all of the sections.
- */
- private void buildSeparators() {
- ViewEntry separator;
-
- separator = new ViewEntry();
- separator.kind = ViewEntry.KIND_SEPARATOR;
- separator.data = getString(R.string.listSeparatorCallNumber);
- mPhoneEntries.add(separator);
-
- separator = new ViewEntry();
- separator.kind = ViewEntry.KIND_SEPARATOR;
- separator.data = getString(R.string.listSeparatorSendSmsMms);
- mSmsEntries.add(separator);
-
- separator = new ViewEntry();
- separator.kind = ViewEntry.KIND_SEPARATOR;
- separator.data = getString(R.string.listSeparatorSendEmail);
- mEmailEntries.add(separator);
-
- separator = new ViewEntry();
- separator.kind = ViewEntry.KIND_SEPARATOR;
- separator.data = getString(R.string.listSeparatorSendIm);
- mImEntries.add(separator);
-
- separator = new ViewEntry();
- separator.kind = ViewEntry.KIND_SEPARATOR;
- separator.data = getString(R.string.listSeparatorMapAddress);
- mPostalEntries.add(separator);
-
- separator = new ViewEntry();
- separator.kind = ViewEntry.KIND_SEPARATOR;
- separator.data = getString(R.string.listSeparatorOrganizations);
- mOrganizationEntries.add(separator);
-
- separator = new ViewEntry();
- separator.kind = ViewEntry.KIND_SEPARATOR;
- separator.data = getString(R.string.listSeparatorOtherInformation);
- mOtherEntries.add(separator);
- }
-
- private Uri constructImToUrl(String host, String data) {
- // don't encode the url, because the Activity Manager can't find using the encoded url
- StringBuilder buf = new StringBuilder("imto://");
- buf.append(host);
- buf.append('/');
- buf.append(data);
- return Uri.parse(buf.toString());
- }
-
- /**
* Build up the entries to display on the screen.
- *
+ *
* @param personCursor the URI for the contact being displayed
*/
- private final void buildEntries(Cursor personCursor) {
+ 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();
}
- if (SHOW_SEPARATORS) {
- buildSeparators();
- }
+ mRawContactIds.clear();
+ mReadOnlySourcesCnt = 0;
+ mWritableSourcesCnt = 0;
+ mWritableRawContactIds.clear();
- // Build up the phone entries
- final Uri phonesUri = Uri.withAppendedPath(mUri, People.Phones.CONTENT_DIRECTORY);
- final Cursor phonesCursor = mResolver.query(phonesUri, PHONES_PROJECTION, null, null,
- Phones.ISPRIMARY + " DESC");
+ final Context context = this;
+ final Sources sources = Sources.getInstance(context);
- if (phonesCursor != null) {
- while (phonesCursor.moveToNext()) {
- final int type = phonesCursor.getInt(PHONES_TYPE_COLUMN);
- final String number = phonesCursor.getString(PHONES_NUMBER_COLUMN);
- final String label = phonesCursor.getString(PHONES_LABEL_COLUMN);
- final boolean isPrimary = phonesCursor.getInt(PHONES_ISPRIMARY_COLUMN) == 1;
- final long id = phonesCursor.getLong(PHONES_ID_COLUMN);
- final Uri uri = ContentUris.withAppendedId(phonesUri, id);
+ // 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);
- // Don't crash if the number is bogus
- if (TextUtils.isEmpty(number)) {
- Log.w(TAG, "empty number for phone " + id);
- continue;
+ 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);
}
- mNumPhoneNumbers++;
-
- // Add a phone number entry
- final ViewEntry entry = new ViewEntry();
- final CharSequence displayLabel = Phones.getDisplayLabel(this, type, label);
- entry.label = buildActionString(R.string.actionCall, displayLabel, true);
- entry.data = number;
- entry.id = id;
- entry.uri = uri;
- entry.intent = new Intent(Intent.ACTION_CALL_PRIVILEGED, entry.uri);
- entry.auxIntent = new Intent(Intent.ACTION_SENDTO,
- Uri.fromParts("sms", number, null));
- entry.kind = Contacts.KIND_PHONE;
- if (isPrimary) {
- entry.primaryIcon = R.drawable.ic_default_number;
- }
- entry.actionIcon = android.R.drawable.sym_action_call;
- mPhoneEntries.add(entry);
- if (type == Phones.TYPE_MOBILE || mShowSmsLinksForAllPhones) {
- // Add an SMS entry
- ViewEntry smsEntry = new ViewEntry();
- smsEntry.label = buildActionString(R.string.actionText, displayLabel, true);
- smsEntry.data = number;
- smsEntry.id = id;
- smsEntry.uri = uri;
- smsEntry.intent = entry.auxIntent;
- smsEntry.kind = ViewEntry.KIND_SMS;
- smsEntry.actionIcon = R.drawable.sym_action_sms;
- mSmsEntries.add(smsEntry);
- }
- }
+ for (NamedContentValues subValue : entity.getSubValues()) {
+ final ContentValues entryValues = subValue.values;
+ entryValues.put(Data.RAW_CONTACT_ID, rawContactId);
- phonesCursor.close();
- }
+ final long dataId = entryValues.getAsLong(Data._ID);
+ final String mimeType = entryValues.getAsString(Data.MIMETYPE);
+ if (mimeType == null) continue;
- // Build the contact method entries
- final Uri methodsUri = Uri.withAppendedPath(mUri, People.ContactMethods.CONTENT_DIRECTORY);
- Cursor methodsCursor = mResolver.query(
- Uri.withAppendedPath(mUri, "contact_methods_with_presence"),
- METHODS_WITH_PRESENCE_PROJECTION, null, null, null);
+ final DataKind kind = sources.getKindOrFallback(accountType, mimeType, this,
+ ContactsSource.LEVEL_MIMETYPES);
+ if (kind == null) continue;
- if (methodsCursor != null) {
- String[] protocolStrings = getResources().getStringArray(android.R.array.imProtocols);
+ final ViewEntry entry = ViewEntry.fromValues(context, mimeType, kind,
+ rawContactId, dataId, entryValues);
- while (methodsCursor.moveToNext()) {
- final int kind = methodsCursor.getInt(METHODS_KIND_COLUMN);
- final String label = methodsCursor.getString(METHODS_LABEL_COLUMN);
- final String data = methodsCursor.getString(METHODS_DATA_COLUMN);
- final int type = methodsCursor.getInt(METHODS_TYPE_COLUMN);
- final long id = methodsCursor.getLong(METHODS_ID_COLUMN);
- final Uri uri = ContentUris.withAppendedId(methodsUri, id);
+ final boolean hasData = !TextUtils.isEmpty(entry.data);
+ final boolean isSuperPrimary = entryValues.getAsInteger(
+ Data.IS_SUPER_PRIMARY) != 0;
- // Don't crash if the data is bogus
- if (TextUtils.isEmpty(data)) {
- Log.w(TAG, "empty data for contact method " + id);
- continue;
- }
+ if (Phone.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
+ // Build phone entries
+ mNumPhoneNumbers++;
- ViewEntry entry = new ViewEntry();
- entry.id = id;
- entry.uri = uri;
- entry.kind = kind;
+ 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));
- switch (kind) {
- case Contacts.KIND_EMAIL:
- entry.label = buildActionString(R.string.actionEmail,
- ContactMethods.getDisplayLabel(this, kind, type, label), true);
- entry.data = data;
- entry.intent = new Intent(Intent.ACTION_SENDTO,
- Uri.fromParts("mailto", data, null));
- entry.actionIcon = android.R.drawable.sym_action_email;
- mEmailEntries.add(entry);
- break;
+ entry.isPrimary = isSuperPrimary;
+ mPhoneEntries.add(entry);
- case Contacts.KIND_POSTAL:
- entry.label = buildActionString(R.string.actionMap,
- ContactMethods.getDisplayLabel(this, kind, type, label), true);
- entry.data = data;
- entry.maxLines = 4;
- entry.intent = new Intent(Intent.ACTION_VIEW, uri);
- entry.actionIcon = R.drawable.sym_action_map;
- mPostalEntries.add(entry);
- break;
-
- case Contacts.KIND_IM: {
- Object protocolObj = ContactMethods.decodeImProtocol(
- methodsCursor.getString(METHODS_AUX_DATA_COLUMN));
- String host;
- if (protocolObj instanceof Number) {
- int protocol = ((Number) protocolObj).intValue();
- entry.label = buildActionString(R.string.actionChat,
- protocolStrings[protocol], false);
- host = ContactMethods.lookupProviderNameFromId(protocol).toLowerCase();
- if (protocol == ContactMethods.PROTOCOL_GOOGLE_TALK
- || protocol == ContactMethods.PROTOCOL_MSN) {
- entry.maxLabelLines = 2;
+ if (entry.type == CommonDataKinds.Phone.TYPE_MOBILE
+ || mShowSmsLinksForAllPhones) {
+ // Add an SMS entry
+ if (kind.iconAltRes > 0) {
+ entry.secondaryActionIcon = kind.iconAltRes;
}
- } else {
- String providerName = (String) protocolObj;
- entry.label = buildActionString(R.string.actionChat,
- providerName, false);
- host = providerName.toLowerCase();
+ }
+ } 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();
}
- // Only add the intent if there is a valid host
- if (!TextUtils.isEmpty(host)) {
- entry.intent = new Intent(Intent.ACTION_SENDTO,
- constructImToUrl(host, data));
+ // Apply presence and status details when available
+ final DataStatus status = mStatuses.get(entry.id);
+ if (status != null) {
+ entry.applyStatus(status, false);
}
- entry.data = data;
- if (!methodsCursor.isNull(METHODS_STATUS_COLUMN)) {
- entry.presenceIcon = Presence.getPresenceIconResourceId(
- methodsCursor.getInt(METHODS_STATUS_COLUMN));
- }
- entry.actionIcon = android.R.drawable.sym_action_chat;
mImEntries.add(entry);
- break;
- }
- }
- }
-
- methodsCursor.close();
- }
-
- // Build IM entries for things we have presence info about but not explicit IM entries for
- long personId = ContentUris.parseId(mUri);
- String[] projection = new String[] {
- Presence.IM_HANDLE, // 0
- Presence.IM_PROTOCOL, // 1
- Presence.PRESENCE_STATUS, // 2
- };
- Cursor presenceCursor = mResolver.query(Presence.CONTENT_URI, projection,
- Presence.PERSON_ID + "=" + personId, null, null);
- if (presenceCursor != null) {
- try {
- while (presenceCursor.moveToNext()) {
- // Find the display info for the provider
- String data = presenceCursor.getString(0);
- String label;
- Object protocolObj = ContactMethods.decodeImProtocol(
- presenceCursor.getString(1));
- String host;
- if (protocolObj instanceof Number) {
- int protocol = ((Number) protocolObj).intValue();
- label = getResources().getStringArray(
- android.R.array.imProtocols)[protocol];
- host = ContactMethods.lookupProviderNameFromId(protocol).toLowerCase();
+ } else if ((Organization.CONTENT_ITEM_TYPE.equals(mimeType)
+ || Nickname.CONTENT_ITEM_TYPE.equals(mimeType)) && hasData) {
+ // Build organization and note entries
+ entry.uri = null;
+ mOrganizationEntries.add(entry);
+ } else if (Note.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
+ // Build note entries
+ entry.uri = null;
+ entry.maxLines = 10;
+ mOtherEntries.add(entry);
} else {
- String providerName = (String) protocolObj;
- label = providerName;
- host = providerName.toLowerCase();
- }
+ // Handle showing custom rows
+ entry.intent = new Intent(Intent.ACTION_VIEW, entry.uri);
- if (TextUtils.isEmpty(host)) {
- // A valid provider name is required
- continue;
- }
+ // 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);
+ }
-
- Intent intent = new Intent(Intent.ACTION_SENDTO, constructImToUrl(host, data));
-
- // Check to see if there is already an entry for this IM account
- boolean addEntry = true;
- int numImEntries = mImEntries.size();
- for (int i = 0; i < numImEntries; i++) {
- // Check to see if the intent point to the same thing, if so we won't
- // add this entry to the list since there is already an explict entry
- // for the IM account
- Intent existingIntent = mImEntries.get(i).intent;
- if (intent.filterEquals(existingIntent)) {
- addEntry = false;
- break;
+ if (hasSocial || hasData) {
+ mOtherEntries.add(entry);
}
}
-
- // Add the entry if an existing one wasn't found
- if (addEntry) {
- ViewEntry entry = new ViewEntry();
- entry.kind = Contacts.KIND_IM;
- entry.data = data;
- entry.label = label;
- entry.intent = intent;
- entry.actionIcon = android.R.drawable.sym_action_chat;
- entry.presenceIcon = Presence.getPresenceIconResourceId(
- presenceCursor.getInt(2));
- entry.maxLabelLines = 2;
- mImEntries.add(entry);
- }
- }
- } finally {
- presenceCursor.close();
- }
- }
-
- // Build the organization entries
- final Uri organizationsUri = Uri.withAppendedPath(mUri, Organizations.CONTENT_DIRECTORY);
- Cursor organizationsCursor = mResolver.query(organizationsUri, ORGANIZATIONS_PROJECTION,
- null, null, null);
-
- if (organizationsCursor != null) {
- while (organizationsCursor.moveToNext()) {
- ViewEntry entry = new ViewEntry();
- entry.id = organizationsCursor.getLong(ORGANIZATIONS_ID_COLUMN);
- entry.uri = ContentUris.withAppendedId(organizationsUri, entry.id);
- entry.kind = Contacts.KIND_ORGANIZATION;
- entry.label = organizationsCursor.getString(ORGANIZATIONS_COMPANY_COLUMN);
- entry.data = organizationsCursor.getString(ORGANIZATIONS_TITLE_COLUMN);
- entry.actionIcon = R.drawable.sym_action_organization;
-/*
- entry.label = Organizations.getDisplayLabel(this,
- organizationsCursor.getInt(ORGANIZATIONS_TYPE_COLUMN),
- organizationsCursor.getString(ORGANIZATIONS_LABEL_COLUMN)).toString();
-*/
- mOrganizationEntries.add(entry);
- }
-
- organizationsCursor.close();
- }
-
-
- // Build the other entries
- String note = personCursor.getString(CONTACT_NOTES_COLUMN);
- if (!TextUtils.isEmpty(note)) {
- ViewEntry entry = new ViewEntry();
- entry.label = getString(R.string.label_notes);
- entry.data = note;
- entry.id = 0;
- entry.kind = ViewEntry.KIND_CONTACT;
- entry.uri = null;
- entry.intent = null;
- entry.maxLines = 10;
- entry.actionIcon = R.drawable.sym_note;
- mOtherEntries.add(entry);
- }
-
- // Build the ringtone entry
- String ringtoneStr = personCursor.getString(CONTACT_CUSTOM_RINGTONE_COLUMN);
- if (!TextUtils.isEmpty(ringtoneStr)) {
- // Get the URI
- Uri ringtoneUri = Uri.parse(ringtoneStr);
- if (ringtoneUri != null) {
- Ringtone ringtone = RingtoneManager.getRingtone(this, ringtoneUri);
- if (ringtone != null) {
- ViewEntry entry = new ViewEntry();
- entry.label = getString(R.string.label_ringtone);
- entry.data = ringtone.getTitle(this);
- entry.kind = ViewEntry.KIND_CONTACT;
- entry.uri = ringtoneUri;
- entry.actionIcon = R.drawable.sym_ringtone;
- mOtherEntries.add(entry);
}
}
}
-
- // Build the send directly to voice mail entry
- boolean sendToVoicemail = personCursor.getInt(CONTACT_SEND_TO_VOICEMAIL_COLUMN) == 1;
- if (sendToVoicemail) {
- ViewEntry entry = new ViewEntry();
- entry.label = getString(R.string.actionIncomingCall);
- entry.data = getString(R.string.detailIncomingCallsGoToVoicemail);
- entry.kind = ViewEntry.KIND_CONTACT;
- entry.actionIcon = R.drawable.sym_send_to_voicemail;
- mOtherEntries.add(entry);
- }
}
- String buildActionString(int actionResId, CharSequence type, boolean lowerCase) {
- // If there is no type just display an empty string
- if (type == null) {
- type = "";
+ static String buildActionString(DataKind kind, ContentValues values, boolean lowerCase,
+ Context context) {
+ if (kind.actionHeader == null) {
+ return null;
}
-
- if (lowerCase) {
- return getString(actionResId, type.toString().toLowerCase());
- } else {
- return getString(actionResId, type.toString());
+ 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.
*/
- final static class ViewEntry extends ContactEntryAdapter.Entry {
- public int primaryIcon = -1;
- public Intent intent;
- public Intent auxIntent = null;
- public int presenceIcon = -1;
+ 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 int presenceIcon = -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();
+ presenceIcon = (presence == -1) ? -1 :
+ StatusUpdates.getPresenceIconResourceId(this.presence);
+
+ 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 (Phone.CONTENT_ITEM_TYPE.equals(mimetype)
+ && Phone.CONTENT_ITEM_TYPE.equals(entry.mimetype)) {
+ if (!PhoneNumberUtils.compare(this.context, data, entry.data)) {
+ return false;
+ }
+ } else {
+ if (!equals(data, entry.data)) {
+ return false;
+ }
+ }
+
+ if (!equals(mimetype, entry.mimetype)
+ || !intentCollapsible(intent, entry.intent)
+ || !intentCollapsible(secondaryIntent, entry.secondaryIntent)
+ || actionIcon != entry.actionIcon) {
+ return false;
+ }
+
+ return true;
+ }
+
+ private boolean equals(Object a, Object b) {
+ return a==b || (a != null && a.equals(b));
+ }
+
+ private boolean intentCollapsible(Intent a, Intent b) {
+ if (a == b) {
+ return true;
+ } else if ((a != null && b != null) && equals(a.getAction(), b.getAction())) {
+ return true;
+ }
+ return false;
+ }
}
- private static final class ViewAdapter extends ContactEntryAdapter<ViewEntry> {
- /** Cache of the children views of a row */
- static class ViewCache {
- public TextView label;
- public TextView data;
- public ImageView actionIcon;
- public ImageView presenceIcon;
-
- // Need to keep track of this too
- ViewEntry entry;
- }
-
+ /** 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);
+ ViewEntry entry = getEntry(mSections, position, false);
View v;
- // Handle separators specially
- if (entry.kind == ViewEntry.KIND_SEPARATOR) {
- TextView separator = (TextView) mInflater.inflate(
- R.layout.list_separator, parent, SHOW_SEPARATORS);
- separator.setText(entry.data);
- return separator;
- }
-
ViewCache views;
// Check to see if we can reuse convertView
@@ -963,8 +1108,14 @@
views = new ViewCache();
views.label = (TextView) v.findViewById(android.R.id.text1);
views.data = (TextView) v.findViewById(android.R.id.text2);
- views.actionIcon = (ImageView) v.findViewById(R.id.icon1);
- views.presenceIcon = (ImageView) v.findViewById(R.id.icon2);
+ 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);
}
@@ -995,14 +1146,38 @@
// Set the data
TextView data = views.data;
if (data != null) {
- data.setText(entry.data);
+ 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) {
- action.setImageDrawable(resources.getDrawable(entry.actionIcon));
+ 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
@@ -1011,18 +1186,34 @@
// Set the presence icon
Drawable presenceIcon = null;
- if (entry.primaryIcon != -1) {
- presenceIcon = resources.getDrawable(entry.primaryIcon);
- } else if (entry.presenceIcon != -1) {
+ if (entry.presenceIcon != -1) {
presenceIcon = resources.getDrawable(entry.presenceIcon);
+ } else if (entry.presence != -1) {
+ presenceIcon = resources.getDrawable(
+ StatusUpdates.getPresenceIconResourceId(entry.presence));
+ }
+ ImageView presenceIconView = views.presenceIcon;
+ if (presenceIcon != null) {
+ presenceIconView.setImageDrawable(presenceIcon);
+ presenceIconView.setVisibility(View.VISIBLE);
+ } else {
+ presenceIconView.setVisibility(View.GONE);
}
- ImageView presence = views.presenceIcon;
- if (presenceIcon != null) {
- presence.setImageDrawable(presenceIcon);
- presence.setVisibility(View.VISIBLE);
+ // 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 {
- presence.setVisibility(View.GONE);
+ secondaryActionView.setVisibility(View.GONE);
+ views.secondaryActionDivider.setVisibility(View.GONE);
}
}
@@ -1037,4 +1228,18 @@
}
}
}
+
+ 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;
+ }
}
diff --git a/src/com/android/contacts/model/ContactsSource.java b/src/com/android/contacts/model/ContactsSource.java
new file mode 100644
index 0000000..1198837
--- /dev/null
+++ b/src/com/android/contacts/model/ContactsSource.java
@@ -0,0 +1,316 @@
+/*
+ * 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 com.google.android.collect.Lists;
+import com.google.android.collect.Maps;
+
+import android.accounts.Account;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.graphics.drawable.Drawable;
+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.widget.EditText;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * Internal structure that represents constraints and styles for a specific data
+ * source, such as the various data types they support, including details on how
+ * those types should be rendered and edited.
+ * <p>
+ * In the future this may be inflated from XML defined by a data source.
+ */
+public abstract class ContactsSource {
+ /**
+ * The {@link RawContacts#ACCOUNT_TYPE} these constraints apply to.
+ */
+ public String accountType = null;
+
+ /**
+ * Package that resources should be loaded from, either defined through an
+ * {@link Account} or for matching against {@link Data#RES_PACKAGE}.
+ */
+ public String resPackageName;
+ public String summaryResPackageName;
+
+ public int titleRes;
+ public int iconRes;
+
+ public boolean readOnly;
+
+ /**
+ * Set of {@link DataKind} supported by this source.
+ */
+ private ArrayList<DataKind> mKinds = Lists.newArrayList();
+
+ /**
+ * Lookup map of {@link #mKinds} on {@link DataKind#mimeType}.
+ */
+ private HashMap<String, DataKind> mMimeKinds = Maps.newHashMap();
+
+ public static final int LEVEL_NONE = 0;
+ public static final int LEVEL_SUMMARY = 1;
+ public static final int LEVEL_MIMETYPES = 2;
+ public static final int LEVEL_CONSTRAINTS = 3;
+
+ private int mInflatedLevel = LEVEL_NONE;
+
+ public synchronized boolean isInflated(int inflateLevel) {
+ return mInflatedLevel >= inflateLevel;
+ }
+
+ /** @hide exposed for unit tests */
+ public void setInflatedLevel(int inflateLevel) {
+ mInflatedLevel = inflateLevel;
+ }
+
+ /**
+ * Ensure that this {@link ContactsSource} has been inflated to the
+ * requested level.
+ */
+ public synchronized void ensureInflated(Context context, int inflateLevel) {
+ if (!isInflated(inflateLevel)) {
+ inflate(context, inflateLevel);
+ }
+ }
+
+ /**
+ * Perform the actual inflation to the requested level. Called by
+ * {@link #ensureInflated(Context, int)} when inflation is needed.
+ */
+ protected abstract void inflate(Context context, int inflateLevel);
+
+ /**
+ * Invalidate any cache for this {@link ContactsSource}, removing all
+ * inflated data. Calling {@link #ensureInflated(Context, int)} will
+ * populate again from scratch.
+ */
+ public synchronized void invalidateCache() {
+ this.mKinds.clear();
+ this.mMimeKinds.clear();
+ setInflatedLevel(LEVEL_NONE);
+ }
+
+ public CharSequence getDisplayLabel(Context context) {
+ if (this.titleRes != -1 && this.summaryResPackageName != null) {
+ final PackageManager pm = context.getPackageManager();
+ return pm.getText(this.summaryResPackageName, this.titleRes, null);
+ } else if (this.titleRes != -1) {
+ return context.getText(this.titleRes);
+ } else {
+ return this.accountType;
+ }
+ }
+
+ public Drawable getDisplayIcon(Context context) {
+ if (this.titleRes != -1 && this.summaryResPackageName != null) {
+ final PackageManager pm = context.getPackageManager();
+ return pm.getDrawable(this.summaryResPackageName, this.iconRes, null);
+ } else if (this.titleRes != -1) {
+ return context.getResources().getDrawable(this.iconRes);
+ } else {
+ return null;
+ }
+ }
+
+ abstract public int getHeaderColor(Context context);
+
+ abstract public int getSideBarColor(Context context);
+
+ /**
+ * {@link Comparator} to sort by {@link DataKind#weight}.
+ */
+ private static Comparator<DataKind> sWeightComparator = new Comparator<DataKind>() {
+ public int compare(DataKind object1, DataKind object2) {
+ return object1.weight - object2.weight;
+ }
+ };
+
+ /**
+ * Return list of {@link DataKind} supported, sorted by
+ * {@link DataKind#weight}.
+ */
+ public ArrayList<DataKind> getSortedDataKinds() {
+ // TODO: optimize by marking if already sorted
+ Collections.sort(mKinds, sWeightComparator);
+ return mKinds;
+ }
+
+ /**
+ * Find the {@link DataKind} for a specific MIME-type, if it's handled by
+ * this data source. If you may need a fallback {@link DataKind}, use
+ * {@link Sources#getKindOrFallback(String, String, Context, int)}.
+ */
+ public DataKind getKindForMimetype(String mimeType) {
+ return this.mMimeKinds.get(mimeType);
+ }
+
+ /**
+ * Add given {@link DataKind} to list of those provided by this source.
+ */
+ public DataKind addKind(DataKind kind) {
+ kind.resPackageName = this.resPackageName;
+ this.mKinds.add(kind);
+ this.mMimeKinds.put(kind.mimeType, kind);
+ return kind;
+ }
+
+ /**
+ * Description of a specific data type, usually marked by a unique
+ * {@link Data#MIMETYPE}. Includes details about how to view and edit
+ * {@link Data} rows of this kind, including the possible {@link EditType}
+ * labels and editable {@link EditField}.
+ */
+ public static class DataKind {
+ public String resPackageName;
+ public String mimeType;
+ public int titleRes;
+ public int iconRes;
+ public int iconAltRes;
+ public int weight;
+ public boolean secondary;
+ public boolean editable;
+
+ public StringInflater actionHeader;
+ public StringInflater actionAltHeader;
+ public StringInflater actionBody;
+
+ public boolean actionBodySocial = false;
+
+ public String typeColumn;
+ public int typeOverallMax;
+
+ public List<EditType> typeList;
+ public List<EditField> fieldList;
+
+ public ContentValues defaultValues;
+
+ public DataKind() {
+ }
+
+ public DataKind(String mimeType, int titleRes, int iconRes, int weight, boolean editable) {
+ this.mimeType = mimeType;
+ this.titleRes = titleRes;
+ this.iconRes = iconRes;
+ this.weight = weight;
+ this.editable = editable;
+ this.typeOverallMax = -1;
+ }
+ }
+
+ /**
+ * Description of a specific "type" or "label" of a {@link DataKind} row,
+ * such as {@link Phone#TYPE_WORK}. Includes constraints on total number of
+ * rows a {@link Contacts} may have of this type, and details on how
+ * user-defined labels are stored.
+ */
+ public static class EditType {
+ public int rawValue;
+ public int labelRes;
+// public int actionRes;
+// public int actionAltRes;
+ public boolean secondary;
+ public int specificMax;
+ public String customColumn;
+
+ public EditType(int rawValue, int labelRes) {
+ this.rawValue = rawValue;
+ this.labelRes = labelRes;
+ this.specificMax = -1;
+ }
+
+ public EditType setSecondary(boolean secondary) {
+ this.secondary = secondary;
+ return this;
+ }
+
+ public EditType setSpecificMax(int specificMax) {
+ this.specificMax = specificMax;
+ return this;
+ }
+
+ public EditType setCustomColumn(String customColumn) {
+ this.customColumn = customColumn;
+ return this;
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ if (object instanceof EditType) {
+ final EditType other = (EditType)object;
+ return other.rawValue == rawValue;
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return rawValue;
+ }
+ }
+
+ /**
+ * Description of a user-editable field on a {@link DataKind} row, such as
+ * {@link Phone#NUMBER}. Includes flags to apply to an {@link EditText}, and
+ * the column where this field is stored.
+ */
+ public static class EditField {
+ public String column;
+ public int titleRes;
+ public int inputType;
+ public int minLines;
+ public boolean optional;
+
+ public EditField(String column, int titleRes) {
+ this.column = column;
+ this.titleRes = titleRes;
+ }
+
+ public EditField(String column, int titleRes, int inputType) {
+ this(column, titleRes);
+ this.inputType = inputType;
+ }
+
+ public EditField setOptional(boolean optional) {
+ this.optional = optional;
+ return this;
+ }
+ }
+
+ /**
+ * Generic method of inflating a given {@link Cursor} into a user-readable
+ * {@link CharSequence}. For example, an inflater could combine the multiple
+ * columns of {@link StructuredPostal} together using a string resource
+ * before presenting to the user.
+ */
+ public interface StringInflater {
+ public CharSequence inflateUsing(Context context, Cursor cursor);
+ public CharSequence inflateUsing(Context context, ContentValues values);
+ }
+
+}
diff --git a/src/com/android/contacts/model/Editor.java b/src/com/android/contacts/model/Editor.java
new file mode 100644
index 0000000..b3e8443
--- /dev/null
+++ b/src/com/android/contacts/model/Editor.java
@@ -0,0 +1,65 @@
+/*
+ * 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 com.android.contacts.model.ContactsSource.DataKind;
+import com.android.contacts.model.EntityDelta.ValuesDelta;
+
+import android.provider.ContactsContract.Data;
+
+/**
+ * Generic definition of something that edits a {@link Data} row through an
+ * {@link ValuesDelta} object.
+ */
+public interface Editor {
+ /**
+ * Listener for an {@link Editor}, usually to handle deleted items.
+ */
+ public interface EditorListener {
+ /**
+ * Called when the given {@link Editor} has been deleted.
+ */
+ public void onDeleted(Editor editor);
+
+ /**
+ * Called when the given {@link Editor} has a request, for example it
+ * wants to select a photo.
+ */
+ public void onRequest(int request);
+
+ public static final int REQUEST_PICK_PHOTO = 1;
+ public static final int FIELD_CHANGED = 2;
+ }
+
+ /**
+ * Prepare this editor for the given {@link ValuesDelta}, which
+ * builds any needed views. Any changes performed by the user will be
+ * written back to that same object.
+ */
+ public void setValues(DataKind kind, ValuesDelta values, EntityDelta state, boolean readOnly);
+
+ /**
+ * Add a specific {@link EditorListener} to this {@link Editor}.
+ */
+ public void setEditorListener(EditorListener listener);
+
+ /**
+ * Called internally when the contents of a specific field have changed,
+ * allowing advanced editors to persist data in a specific way.
+ */
+ public void onFieldChanged(String column, String value);
+}
diff --git a/src/com/android/contacts/model/EntityDelta.java b/src/com/android/contacts/model/EntityDelta.java
new file mode 100644
index 0000000..d4e9632
--- /dev/null
+++ b/src/com/android/contacts/model/EntityDelta.java
@@ -0,0 +1,833 @@
+/*
+ * 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 com.google.android.collect.Lists;
+import com.google.android.collect.Maps;
+import com.google.android.collect.Sets;
+
+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.os.Parcel;
+import android.os.Parcelable;
+import android.provider.BaseColumns;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.RawContacts;
+import android.util.Log;
+import android.view.View;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Contains an {@link Entity} and records any modifications separately so the
+ * original {@link Entity} can be swapped out with a newer version and the
+ * changes still cleanly applied.
+ * <p>
+ * One benefit of this approach is that we can build changes entirely on an
+ * empty {@link Entity}, which then becomes an insert {@link RawContacts} case.
+ * <p>
+ * When applying modifications over an {@link Entity}, we try finding the
+ * original {@link Data#_ID} rows where the modifications took place. If those
+ * rows are missing from the new {@link Entity}, we know the original data must
+ * be deleted, but to preserve the user modifications we treat as an insert.
+ */
+public class EntityDelta implements Parcelable {
+ // TODO: optimize by using contentvalues pool, since we allocate so many of them
+
+ private static final String TAG = "EntityDelta";
+ private static final boolean LOGV = true;
+
+ /**
+ * Direct values from {@link Entity#getEntityValues()}.
+ */
+ private ValuesDelta mValues;
+
+ /**
+ * Internal map of children values from {@link Entity#getSubValues()}, which
+ * we store here sorted into {@link Data#MIMETYPE} bins.
+ */
+ private HashMap<String, ArrayList<ValuesDelta>> mEntries = Maps.newHashMap();
+
+ public EntityDelta() {
+ }
+
+ public EntityDelta(ValuesDelta values) {
+ mValues = values;
+ }
+
+ /**
+ * Build an {@link EntityDelta} using the given {@link Entity} as a
+ * starting point; the "before" snapshot.
+ */
+ public static EntityDelta fromBefore(Entity before) {
+ final EntityDelta entity = new EntityDelta();
+ entity.mValues = ValuesDelta.fromBefore(before.getEntityValues());
+ entity.mValues.setIdColumn(RawContacts._ID);
+ for (NamedContentValues namedValues : before.getSubValues()) {
+ entity.addEntry(ValuesDelta.fromBefore(namedValues.values));
+ }
+ return entity;
+ }
+
+ /**
+ * Merge the "after" values from the given {@link EntityDelta} onto the
+ * "before" state represented by this {@link EntityDelta}, discarding any
+ * existing "after" states. This is typically used when re-parenting changes
+ * onto an updated {@link Entity}.
+ */
+ public static EntityDelta mergeAfter(EntityDelta local, EntityDelta remote) {
+ // Bail early if trying to merge delete with missing local
+ final ValuesDelta remoteValues = remote.mValues;
+ if (local == null && (remoteValues.isDelete() || remoteValues.isTransient())) return null;
+
+ // Create local version if none exists yet
+ if (local == null) local = new EntityDelta();
+
+ if (LOGV) {
+ final Long localVersion = (local.mValues == null) ? null : local.mValues
+ .getAsLong(RawContacts.VERSION);
+ final Long remoteVersion = remote.mValues.getAsLong(RawContacts.VERSION);
+ Log.d(TAG, "Re-parenting from original version " + remoteVersion + " to "
+ + localVersion);
+ }
+
+ // Create values if needed, and merge "after" changes
+ local.mValues = ValuesDelta.mergeAfter(local.mValues, remote.mValues);
+
+ // Find matching local entry for each remote values, or create
+ for (ArrayList<ValuesDelta> mimeEntries : remote.mEntries.values()) {
+ for (ValuesDelta remoteEntry : mimeEntries) {
+ final Long childId = remoteEntry.getId();
+
+ // Find or create local match and merge
+ final ValuesDelta localEntry = local.getEntry(childId);
+ final ValuesDelta merged = ValuesDelta.mergeAfter(localEntry, remoteEntry);
+
+ if (localEntry == null && merged != null) {
+ // No local entry before, so insert
+ local.addEntry(merged);
+ }
+ }
+ }
+
+ return local;
+ }
+
+ public ValuesDelta getValues() {
+ return mValues;
+ }
+
+ public boolean isContactInsert() {
+ return mValues.isInsert();
+ }
+
+ /**
+ * Get the {@link ValuesDelta} child marked as {@link Data#IS_PRIMARY},
+ * which may return null when no entry exists.
+ */
+ public ValuesDelta getPrimaryEntry(String mimeType) {
+ final ArrayList<ValuesDelta> mimeEntries = getMimeEntries(mimeType, false);
+ if (mimeEntries == null) return null;
+
+ for (ValuesDelta entry : mimeEntries) {
+ if (entry.isPrimary()) {
+ return entry;
+ }
+ }
+
+ // When no direct primary, return something
+ return mimeEntries.size() > 0 ? mimeEntries.get(0) : null;
+ }
+
+ /**
+ * calls {@link #getSuperPrimaryEntry(String, boolean)} with true
+ * @see #getSuperPrimaryEntry(String, boolean)
+ */
+ public ValuesDelta getSuperPrimaryEntry(String mimeType) {
+ return getSuperPrimaryEntry(mimeType, true);
+ }
+
+ /**
+ * Returns the super-primary entry for the given mime type
+ * @param forceSelection if true, will try to return some value even if a super-primary
+ * doesn't exist (may be a primary, or just a random item
+ * @return
+ */
+ public ValuesDelta getSuperPrimaryEntry(String mimeType, boolean forceSelection) {
+ final ArrayList<ValuesDelta> mimeEntries = getMimeEntries(mimeType, false);
+ if (mimeEntries == null) return null;
+
+ ValuesDelta primary = null;
+ for (ValuesDelta entry : mimeEntries) {
+ if (entry.isSuperPrimary()) {
+ return entry;
+ } else if (entry.isPrimary()) {
+ primary = entry;
+ }
+ }
+
+ if (!forceSelection) {
+ return null;
+ }
+
+ // When no direct super primary, return something
+ if (primary != null) {
+ return primary;
+ }
+ return mimeEntries.size() > 0 ? mimeEntries.get(0) : null;
+ }
+
+ /**
+ * Return the list of child {@link ValuesDelta} from our optimized map,
+ * creating the list if requested.
+ */
+ private ArrayList<ValuesDelta> getMimeEntries(String mimeType, boolean lazyCreate) {
+ ArrayList<ValuesDelta> mimeEntries = mEntries.get(mimeType);
+ if (mimeEntries == null && lazyCreate) {
+ mimeEntries = Lists.newArrayList();
+ mEntries.put(mimeType, mimeEntries);
+ }
+ return mimeEntries;
+ }
+
+ public ArrayList<ValuesDelta> getMimeEntries(String mimeType) {
+ return getMimeEntries(mimeType, false);
+ }
+
+ public int getMimeEntriesCount(String mimeType, boolean onlyVisible) {
+ final ArrayList<ValuesDelta> mimeEntries = getMimeEntries(mimeType);
+ if (mimeEntries == null) return 0;
+
+ int count = 0;
+ for (ValuesDelta child : mimeEntries) {
+ // Skip deleted items when requesting only visible
+ if (onlyVisible && !child.isVisible()) continue;
+ count++;
+ }
+ return count;
+ }
+
+ public boolean hasMimeEntries(String mimeType) {
+ return mEntries.containsKey(mimeType);
+ }
+
+ public ValuesDelta addEntry(ValuesDelta entry) {
+ final String mimeType = entry.getMimetype();
+ getMimeEntries(mimeType, true).add(entry);
+ return entry;
+ }
+
+ /**
+ * Find entry with the given {@link BaseColumns#_ID} value.
+ */
+ public ValuesDelta getEntry(Long childId) {
+ if (childId == null) {
+ // Requesting an "insert" entry, which has no "before"
+ return null;
+ }
+
+ // Search all children for requested entry
+ for (ArrayList<ValuesDelta> mimeEntries : mEntries.values()) {
+ for (ValuesDelta entry : mimeEntries) {
+ if (childId.equals(entry.getId())) {
+ return entry;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Return the total number of {@link ValuesDelta} contained.
+ */
+ public int getEntryCount(boolean onlyVisible) {
+ int count = 0;
+ for (String mimeType : mEntries.keySet()) {
+ count += getMimeEntriesCount(mimeType, onlyVisible);
+ }
+ return count;
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ if (object instanceof EntityDelta) {
+ final EntityDelta other = (EntityDelta)object;
+
+ // Equality failed if parent values different
+ if (!other.mValues.equals(mValues)) return false;
+
+ for (ArrayList<ValuesDelta> mimeEntries : mEntries.values()) {
+ for (ValuesDelta child : mimeEntries) {
+ // Equality failed if any children unmatched
+ if (!other.containsEntry(child)) return false;
+ }
+ }
+
+ // Passed all tests, so equal
+ return true;
+ }
+ return false;
+ }
+
+ private boolean containsEntry(ValuesDelta entry) {
+ for (ArrayList<ValuesDelta> mimeEntries : mEntries.values()) {
+ for (ValuesDelta child : mimeEntries) {
+ // Contained if we find any child that matches
+ if (child.equals(entry)) return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Mark this entire object deleted, including any {@link ValuesDelta}.
+ */
+ public void markDeleted() {
+ this.mValues.markDeleted();
+ for (ArrayList<ValuesDelta> mimeEntries : mEntries.values()) {
+ for (ValuesDelta child : mimeEntries) {
+ child.markDeleted();
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("\n(");
+ builder.append(mValues.toString());
+ builder.append(") = {");
+ for (ArrayList<ValuesDelta> mimeEntries : mEntries.values()) {
+ for (ValuesDelta child : mimeEntries) {
+ builder.append("\n\t");
+ child.toString(builder);
+ }
+ }
+ builder.append("\n}\n");
+ return builder.toString();
+ }
+
+ /**
+ * Consider building the given {@link ContentProviderOperation.Builder} and
+ * appending it to the given list, which only happens if builder is valid.
+ */
+ private void possibleAdd(ArrayList<ContentProviderOperation> diff,
+ ContentProviderOperation.Builder builder) {
+ if (builder != null) {
+ diff.add(builder.build());
+ }
+ }
+
+ /**
+ * Build a list of {@link ContentProviderOperation} that will assert any
+ * "before" state hasn't changed. This is maintained separately so that all
+ * asserts can take place before any updates occur.
+ */
+ public void buildAssert(ArrayList<ContentProviderOperation> buildInto) {
+ final boolean isContactInsert = mValues.isInsert();
+ if (!isContactInsert) {
+ // Assert version is consistent while persisting changes
+ final Long beforeId = mValues.getId();
+ final Long beforeVersion = mValues.getAsLong(RawContacts.VERSION);
+ if (beforeId == null || beforeVersion == null) return;
+
+ final ContentProviderOperation.Builder builder = ContentProviderOperation
+ .newAssertQuery(RawContacts.CONTENT_URI);
+ builder.withSelection(RawContacts._ID + "=" + beforeId, null);
+ builder.withValue(RawContacts.VERSION, beforeVersion);
+ buildInto.add(builder.build());
+ }
+ }
+
+ /**
+ * Build a list of {@link ContentProviderOperation} that will transform the
+ * current "before" {@link Entity} state into the modified state which this
+ * {@link EntityDelta} represents.
+ */
+ public void buildDiff(ArrayList<ContentProviderOperation> buildInto) {
+ final int firstIndex = buildInto.size();
+
+ final boolean isContactInsert = mValues.isInsert();
+ final boolean isContactDelete = mValues.isDelete();
+ final boolean isContactUpdate = !isContactInsert && !isContactDelete;
+
+ final Long beforeId = mValues.getId();
+
+ Builder builder;
+
+ // Build possible operation at Contact level
+ builder = mValues.buildDiff(RawContacts.CONTENT_URI);
+ possibleAdd(buildInto, builder);
+
+ // Build operations for all children
+ for (ArrayList<ValuesDelta> mimeEntries : mEntries.values()) {
+ for (ValuesDelta child : mimeEntries) {
+ // Ignore children if parent was deleted
+ if (isContactDelete) continue;
+
+ builder = child.buildDiff(Data.CONTENT_URI);
+ if (child.isInsert()) {
+ if (isContactInsert) {
+ // Parent is brand new insert, so back-reference _id
+ builder.withValueBackReference(Data.RAW_CONTACT_ID, firstIndex);
+ } else {
+ // Inserting under existing, so fill with known _id
+ builder.withValue(Data.RAW_CONTACT_ID, beforeId);
+ }
+ } else if (isContactInsert && builder != null) {
+ // Child must be insert when Contact insert
+ throw new IllegalArgumentException("When parent insert, child must be also");
+ }
+ possibleAdd(buildInto, builder);
+ }
+ }
+
+ final boolean addedOperations = buildInto.size() > firstIndex;
+ if (addedOperations && isContactUpdate) {
+ // Suspend aggregation while persisting updates
+ builder = buildSetAggregationMode(beforeId, RawContacts.AGGREGATION_MODE_SUSPENDED);
+ buildInto.add(firstIndex, builder.build());
+
+ // Restore aggregation as last operation
+ builder = buildSetAggregationMode(beforeId, RawContacts.AGGREGATION_MODE_DEFAULT);
+ buildInto.add(builder.build());
+ }
+ }
+
+ /**
+ * Build a {@link ContentProviderOperation} that changes
+ * {@link RawContacts#AGGREGATION_MODE} to the given value.
+ */
+ protected Builder buildSetAggregationMode(Long beforeId, int mode) {
+ Builder builder = ContentProviderOperation.newUpdate(RawContacts.CONTENT_URI);
+ builder.withValue(RawContacts.AGGREGATION_MODE, mode);
+ builder.withSelection(RawContacts._ID + "=" + beforeId, null);
+ return builder;
+ }
+
+ /** {@inheritDoc} */
+ public int describeContents() {
+ // Nothing special about this parcel
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ public void writeToParcel(Parcel dest, int flags) {
+ final int size = this.getEntryCount(false);
+ dest.writeInt(size);
+ dest.writeParcelable(mValues, flags);
+ for (ArrayList<ValuesDelta> mimeEntries : mEntries.values()) {
+ for (ValuesDelta child : mimeEntries) {
+ dest.writeParcelable(child, flags);
+ }
+ }
+ }
+
+ public void readFromParcel(Parcel source) {
+ final int size = source.readInt();
+ mValues = source.<ValuesDelta> readParcelable(null);
+ for (int i = 0; i < size; i++) {
+ final ValuesDelta child = source.<ValuesDelta> readParcelable(null);
+ this.addEntry(child);
+ }
+ }
+
+ public static final Parcelable.Creator<EntityDelta> CREATOR = new Parcelable.Creator<EntityDelta>() {
+ public EntityDelta createFromParcel(Parcel in) {
+ final EntityDelta state = new EntityDelta();
+ state.readFromParcel(in);
+ return state;
+ }
+
+ public EntityDelta[] newArray(int size) {
+ return new EntityDelta[size];
+ }
+ };
+
+ /**
+ * Type of {@link ContentValues} that maintains both an original state and a
+ * modified version of that state. This allows us to build insert, update,
+ * or delete operations based on a "before" {@link Entity} snapshot.
+ */
+ public static class ValuesDelta implements Parcelable {
+ protected ContentValues mBefore;
+ protected ContentValues mAfter;
+ protected String mIdColumn = BaseColumns._ID;
+ private boolean mFromTemplate;
+
+ /**
+ * Next value to assign to {@link #mIdColumn} when building an insert
+ * operation through {@link #fromAfter(ContentValues)}. This is used so
+ * we can concretely reference this {@link ValuesDelta} before it has
+ * been persisted.
+ */
+ protected static int sNextInsertId = -1;
+
+ protected ValuesDelta() {
+ }
+
+ /**
+ * Create {@link ValuesDelta}, using the given object as the
+ * "before" state, usually from an {@link Entity}.
+ */
+ public static ValuesDelta fromBefore(ContentValues before) {
+ final ValuesDelta entry = new ValuesDelta();
+ entry.mBefore = before;
+ entry.mAfter = new ContentValues();
+ return entry;
+ }
+
+ /**
+ * Create {@link ValuesDelta}, using the given object as the "after"
+ * state, usually when we are inserting a row instead of updating.
+ */
+ public static ValuesDelta fromAfter(ContentValues after) {
+ final ValuesDelta entry = new ValuesDelta();
+ entry.mBefore = null;
+ entry.mAfter = after;
+
+ // Assign temporary id which is dropped before insert.
+ entry.mAfter.put(entry.mIdColumn, sNextInsertId--);
+ return entry;
+ }
+
+ public ContentValues getAfter() {
+ return mAfter;
+ }
+
+ public String getAsString(String key) {
+ if (mAfter != null && mAfter.containsKey(key)) {
+ return mAfter.getAsString(key);
+ } else if (mBefore != null && mBefore.containsKey(key)) {
+ return mBefore.getAsString(key);
+ } else {
+ return null;
+ }
+ }
+
+ public byte[] getAsByteArray(String key) {
+ if (mAfter != null && mAfter.containsKey(key)) {
+ return mAfter.getAsByteArray(key);
+ } else if (mBefore != null && mBefore.containsKey(key)) {
+ return mBefore.getAsByteArray(key);
+ } else {
+ return null;
+ }
+ }
+
+ public Long getAsLong(String key) {
+ if (mAfter != null && mAfter.containsKey(key)) {
+ return mAfter.getAsLong(key);
+ } else if (mBefore != null && mBefore.containsKey(key)) {
+ return mBefore.getAsLong(key);
+ } else {
+ return null;
+ }
+ }
+
+ public Integer getAsInteger(String key) {
+ return getAsInteger(key, null);
+ }
+
+ public Integer getAsInteger(String key, Integer defaultValue) {
+ if (mAfter != null && mAfter.containsKey(key)) {
+ return mAfter.getAsInteger(key);
+ } else if (mBefore != null && mBefore.containsKey(key)) {
+ return mBefore.getAsInteger(key);
+ } else {
+ return defaultValue;
+ }
+ }
+
+ public String getMimetype() {
+ return getAsString(Data.MIMETYPE);
+ }
+
+ public Long getId() {
+ return getAsLong(mIdColumn);
+ }
+
+ /**
+ * Return a valid integer value suitable for {@link View#setId(int)}.
+ */
+ public int getViewId() {
+ final Long id = this.getId();
+ return (id == null) ? View.NO_ID : id.intValue();
+ }
+
+ public void setIdColumn(String idColumn) {
+ mIdColumn = idColumn;
+ }
+
+ public boolean isPrimary() {
+ final Long isPrimary = getAsLong(Data.IS_PRIMARY);
+ return isPrimary == null ? false : isPrimary != 0;
+ }
+
+ public void setFromTemplate(boolean isFromTemplate) {
+ mFromTemplate = isFromTemplate;
+ }
+
+ public boolean isFromTemplate() {
+ return mFromTemplate;
+ }
+
+ public boolean isSuperPrimary() {
+ final Long isSuperPrimary = getAsLong(Data.IS_SUPER_PRIMARY);
+ return isSuperPrimary == null ? false : isSuperPrimary != 0;
+ }
+
+ public boolean beforeExists() {
+ return (mBefore != null && mBefore.containsKey(mIdColumn));
+ }
+
+ public boolean isVisible() {
+ // When "after" is present, then visible
+ return (mAfter != null);
+ }
+
+ public boolean isDelete() {
+ // When "after" is wiped, action is "delete"
+ return beforeExists() && (mAfter == null);
+ }
+
+ public boolean isTransient() {
+ // When no "before" or "after", is transient
+ return (mBefore == null) && (mAfter == null);
+ }
+
+ public boolean isUpdate() {
+ // When "after" has some changes, action is "update"
+ return beforeExists() && (mAfter != null && mAfter.size() > 0);
+ }
+
+ public boolean isNoop() {
+ // When "after" has no changes, action is no-op
+ return beforeExists() && (mAfter != null && mAfter.size() == 0);
+ }
+
+ public boolean isInsert() {
+ // When no "before" id, and has "after", action is "insert"
+ return !beforeExists() && (mAfter != null);
+ }
+
+ public void markDeleted() {
+ mAfter = null;
+ }
+
+ /**
+ * Ensure that our internal structure is ready for storing updates.
+ */
+ private void ensureUpdate() {
+ if (mAfter == null) {
+ mAfter = new ContentValues();
+ }
+ }
+
+ public void put(String key, String value) {
+ ensureUpdate();
+ mAfter.put(key, value);
+ }
+
+ public void put(String key, byte[] value) {
+ ensureUpdate();
+ mAfter.put(key, value);
+ }
+
+ public void put(String key, int value) {
+ ensureUpdate();
+ mAfter.put(key, value);
+ }
+
+ /**
+ * Return set of all keys defined through this object.
+ */
+ public Set<String> keySet() {
+ final HashSet<String> keys = Sets.newHashSet();
+
+ if (mBefore != null) {
+ for (Map.Entry<String, Object> entry : mBefore.valueSet()) {
+ keys.add(entry.getKey());
+ }
+ }
+
+ if (mAfter != null) {
+ for (Map.Entry<String, Object> entry : mAfter.valueSet()) {
+ keys.add(entry.getKey());
+ }
+ }
+
+ return keys;
+ }
+
+ /**
+ * Return complete set of "before" and "after" values mixed together,
+ * giving full state regardless of edits.
+ */
+ public ContentValues getCompleteValues() {
+ final ContentValues values = new ContentValues();
+ if (mBefore != null) {
+ values.putAll(mBefore);
+ }
+ if (mAfter != null) {
+ values.putAll(mAfter);
+ }
+ values.remove(ContactsContract.CommonDataKinds.GroupMembership.GROUP_SOURCE_ID);
+ return values;
+ }
+
+ /**
+ * Merge the "after" values from the given {@link ValuesDelta},
+ * discarding any existing "after" state. This is typically used when
+ * re-parenting changes onto an updated {@link Entity}.
+ */
+ public static ValuesDelta mergeAfter(ValuesDelta local, ValuesDelta remote) {
+ // Bail early if trying to merge delete with missing local
+ if (local == null && (remote.isDelete() || remote.isTransient())) return null;
+
+ // Create local version if none exists yet
+ if (local == null) local = new ValuesDelta();
+
+ if (!local.beforeExists()) {
+ // Any "before" record is missing, so take all values as "insert"
+ local.mAfter = remote.getCompleteValues();
+ } else {
+ // Existing "update" with only "after" values
+ local.mAfter = remote.mAfter;
+ }
+
+ return local;
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ if (object instanceof ValuesDelta) {
+ // Only exactly equal with both are identical subsets
+ final ValuesDelta other = (ValuesDelta)object;
+ return this.subsetEquals(other) && other.subsetEquals(this);
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder builder = new StringBuilder();
+ toString(builder);
+ return builder.toString();
+ }
+
+ /**
+ * Helper for building string representation, leveraging the given
+ * {@link StringBuilder} to minimize allocations.
+ */
+ public void toString(StringBuilder builder) {
+ builder.append("{ ");
+ for (String key : this.keySet()) {
+ builder.append(key);
+ builder.append("=");
+ builder.append(this.getAsString(key));
+ builder.append(", ");
+ }
+ builder.append("}");
+ }
+
+ /**
+ * Check if the given {@link ValuesDelta} is both a subset of this
+ * object, and any defined keys have equal values.
+ */
+ public boolean subsetEquals(ValuesDelta other) {
+ for (String key : this.keySet()) {
+ final String ourValue = this.getAsString(key);
+ final String theirValue = other.getAsString(key);
+ if (ourValue == null) {
+ // If they have value when we're null, no match
+ if (theirValue != null) return false;
+ } else {
+ // If both values defined and aren't equal, no match
+ if (!ourValue.equals(theirValue)) return false;
+ }
+ }
+ // All values compared and matched
+ return true;
+ }
+
+ /**
+ * Build a {@link ContentProviderOperation} that will transform our
+ * "before" state into our "after" state, using insert, update, or
+ * delete as needed.
+ */
+ public ContentProviderOperation.Builder buildDiff(Uri targetUri) {
+ Builder builder = null;
+ if (isInsert()) {
+ // Changed values are "insert" back-referenced to Contact
+ mAfter.remove(mIdColumn);
+ builder = ContentProviderOperation.newInsert(targetUri);
+ builder.withValues(mAfter);
+ } else if (isDelete()) {
+ // When marked for deletion and "before" exists, then "delete"
+ builder = ContentProviderOperation.newDelete(targetUri);
+ builder.withSelection(mIdColumn + "=" + getId(), null);
+ } else if (isUpdate()) {
+ // When has changes and "before" exists, then "update"
+ builder = ContentProviderOperation.newUpdate(targetUri);
+ builder.withSelection(mIdColumn + "=" + getId(), null);
+ builder.withValues(mAfter);
+ }
+ return builder;
+ }
+
+ /** {@inheritDoc} */
+ public int describeContents() {
+ // Nothing special about this parcel
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeParcelable(mBefore, flags);
+ dest.writeParcelable(mAfter, flags);
+ dest.writeString(mIdColumn);
+ }
+
+ public void readFromParcel(Parcel source) {
+ mBefore = source.<ContentValues> readParcelable(null);
+ mAfter = source.<ContentValues> readParcelable(null);
+ mIdColumn = source.readString();
+ }
+
+ public static final Parcelable.Creator<ValuesDelta> CREATOR = new Parcelable.Creator<ValuesDelta>() {
+ public ValuesDelta createFromParcel(Parcel in) {
+ final ValuesDelta values = new ValuesDelta();
+ values.readFromParcel(in);
+ return values;
+ }
+
+ public ValuesDelta[] newArray(int size) {
+ return new ValuesDelta[size];
+ }
+ };
+ }
+}
diff --git a/src/com/android/contacts/model/EntityDiff.java b/src/com/android/contacts/model/EntityDiff.java
new file mode 100644
index 0000000..ea46567
--- /dev/null
+++ b/src/com/android/contacts/model/EntityDiff.java
@@ -0,0 +1,148 @@
+/*
+ * 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
new file mode 100644
index 0000000..519ff80
--- /dev/null
+++ b/src/com/android/contacts/model/EntityModifier.java
@@ -0,0 +1,545 @@
+/*
+ * 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 com.android.contacts.ContactsUtils;
+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.EntityDelta.ValuesDelta;
+import com.google.android.collect.Lists;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.Intents;
+import android.provider.ContactsContract.RawContacts;
+import android.provider.ContactsContract.CommonDataKinds.BaseTypes;
+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.StructuredName;
+import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+import android.provider.ContactsContract.Intents.Insert;
+import android.provider.ContactsContract;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.SparseIntArray;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Helper methods for modifying an {@link EntityDelta}, such as inserting
+ * new rows, or enforcing {@link ContactsSource}.
+ */
+public class EntityModifier {
+ private static final String TAG = "EntityModifier";
+
+ /**
+ * For the given {@link EntityDelta}, determine if the given
+ * {@link DataKind} could be inserted under specific
+ * {@link ContactsSource}.
+ */
+ public static boolean canInsert(EntityDelta state, DataKind kind) {
+ // Insert possible when have valid types and under overall maximum
+ final int visibleCount = state.getMimeEntriesCount(kind.mimeType, true);
+ final boolean validTypes = hasValidTypes(state, kind);
+ final boolean validOverall = (kind.typeOverallMax == -1)
+ || (visibleCount < kind.typeOverallMax);
+ return (validTypes && validOverall);
+ }
+
+ public static boolean hasValidTypes(EntityDelta state, DataKind kind) {
+ if (EntityModifier.hasEditTypes(kind)) {
+ return (getValidTypes(state, kind).size() > 0);
+ } else {
+ return true;
+ }
+ }
+
+ /**
+ * Ensure that at least one of the given {@link DataKind} exists in the
+ * given {@link EntityDelta} state, and try creating one if none exist.
+ */
+ public static void ensureKindExists(EntityDelta state, ContactsSource source, String mimeType) {
+ final DataKind kind = source.getKindForMimetype(mimeType);
+ final boolean hasChild = state.getMimeEntriesCount(mimeType, true) > 0;
+
+ if (!hasChild && kind != null) {
+ // Create child when none exists and valid kind
+ final ValuesDelta child = insertChild(state, kind);
+ if (kind.mimeType.equals(Photo.CONTENT_ITEM_TYPE)) {
+ child.setFromTemplate(true);
+ }
+ }
+ }
+
+ /**
+ * For the given {@link EntityDelta} and {@link DataKind}, return the
+ * list possible {@link EditType} options available based on
+ * {@link ContactsSource}.
+ */
+ public static ArrayList<EditType> getValidTypes(EntityDelta state, DataKind kind) {
+ return getValidTypes(state, kind, null, true, null);
+ }
+
+ /**
+ * For the given {@link EntityDelta} and {@link DataKind}, return the
+ * list possible {@link EditType} options available based on
+ * {@link ContactsSource}.
+ *
+ * @param forceInclude Always include this {@link EditType} in the returned
+ * list, even when an otherwise-invalid choice. This is useful
+ * when showing a dialog that includes the current type.
+ */
+ public static ArrayList<EditType> getValidTypes(EntityDelta state, DataKind kind,
+ EditType forceInclude) {
+ return getValidTypes(state, kind, forceInclude, true, null);
+ }
+
+ /**
+ * For the given {@link EntityDelta} and {@link DataKind}, return the
+ * list possible {@link EditType} options available based on
+ * {@link ContactsSource}.
+ *
+ * @param forceInclude Always include this {@link EditType} in the returned
+ * list, even when an otherwise-invalid choice. This is useful
+ * when showing a dialog that includes the current type.
+ * @param includeSecondary If true, include any valid types marked as
+ * {@link EditType#secondary}.
+ * @param typeCount When provided, will be used for the frequency count of
+ * each {@link EditType}, otherwise built using
+ * {@link #getTypeFrequencies(EntityDelta, DataKind)}.
+ */
+ private static ArrayList<EditType> getValidTypes(EntityDelta state, DataKind kind,
+ EditType forceInclude, boolean includeSecondary, SparseIntArray typeCount) {
+ final ArrayList<EditType> validTypes = Lists.newArrayList();
+
+ // Bail early if no types provided
+ if (!hasEditTypes(kind)) return validTypes;
+
+ if (typeCount == null) {
+ // Build frequency counts if not provided
+ typeCount = getTypeFrequencies(state, kind);
+ }
+
+ // Build list of valid types
+ final int overallCount = typeCount.get(FREQUENCY_TOTAL);
+ for (EditType type : kind.typeList) {
+ final boolean validOverall = (kind.typeOverallMax == -1 ? true
+ : overallCount < kind.typeOverallMax);
+ final boolean validSpecific = (type.specificMax == -1 ? true : typeCount
+ .get(type.rawValue) < type.specificMax);
+ final boolean validSecondary = (includeSecondary ? true : !type.secondary);
+ final boolean forcedInclude = type.equals(forceInclude);
+ if (forcedInclude || (validOverall && validSpecific && validSecondary)) {
+ // Type is valid when no limit, under limit, or forced include
+ validTypes.add(type);
+ }
+ }
+
+ return validTypes;
+ }
+
+ private static final int FREQUENCY_TOTAL = Integer.MIN_VALUE;
+
+ /**
+ * Count up the frequency that each {@link EditType} appears in the given
+ * {@link EntityDelta}. The returned {@link SparseIntArray} maps from
+ * {@link EditType#rawValue} to counts, with the total overall count stored
+ * as {@link #FREQUENCY_TOTAL}.
+ */
+ private static SparseIntArray getTypeFrequencies(EntityDelta state, DataKind kind) {
+ final SparseIntArray typeCount = new SparseIntArray();
+
+ // Find all entries for this kind, bailing early if none found
+ final List<ValuesDelta> mimeEntries = state.getMimeEntries(kind.mimeType);
+ if (mimeEntries == null) return typeCount;
+
+ int totalCount = 0;
+ for (ValuesDelta entry : mimeEntries) {
+ // Only count visible entries
+ if (!entry.isVisible()) continue;
+ totalCount++;
+
+ final EditType type = getCurrentType(entry, kind);
+ if (type != null) {
+ final int count = typeCount.get(type.rawValue);
+ typeCount.put(type.rawValue, count + 1);
+ }
+ }
+ typeCount.put(FREQUENCY_TOTAL, totalCount);
+ return typeCount;
+ }
+
+ /**
+ * Check if the given {@link DataKind} has multiple types that should be
+ * displayed for users to pick.
+ */
+ public static boolean hasEditTypes(DataKind kind) {
+ return kind.typeList != null && kind.typeList.size() > 0;
+ }
+
+ /**
+ * Find the {@link EditType} that describes the given
+ * {@link ValuesDelta} row, assuming the given {@link DataKind} dictates
+ * the possible types.
+ */
+ public static EditType getCurrentType(ValuesDelta entry, DataKind kind) {
+ final Long rawValue = entry.getAsLong(kind.typeColumn);
+ if (rawValue == null) return null;
+ return getType(kind, rawValue.intValue());
+ }
+
+ /**
+ * Find the {@link EditType} that describes the given {@link ContentValues} row,
+ * assuming the given {@link DataKind} dictates the possible types.
+ */
+ public static EditType getCurrentType(ContentValues entry, DataKind kind) {
+ if (kind.typeColumn == null) return null;
+ final Integer rawValue = entry.getAsInteger(kind.typeColumn);
+ if (rawValue == null) return null;
+ return getType(kind, rawValue);
+ }
+
+ /**
+ * Find the {@link EditType} that describes the given {@link Cursor} row,
+ * assuming the given {@link DataKind} dictates the possible types.
+ */
+ public static EditType getCurrentType(Cursor cursor, DataKind kind) {
+ if (kind.typeColumn == null) return null;
+ final int index = cursor.getColumnIndex(kind.typeColumn);
+ if (index == -1) return null;
+ final int rawValue = cursor.getInt(index);
+ return getType(kind, rawValue);
+ }
+
+ /**
+ * Find the {@link EditType} with the given {@link EditType#rawValue}.
+ */
+ public static EditType getType(DataKind kind, int rawValue) {
+ for (EditType type : kind.typeList) {
+ if (type.rawValue == rawValue) {
+ return type;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Return the precedence for the the given {@link EditType#rawValue}, where
+ * lower numbers are higher precedence.
+ */
+ public static int getTypePrecedence(DataKind kind, int rawValue) {
+ for (int i = 0; i < kind.typeList.size(); i++) {
+ final EditType type = kind.typeList.get(i);
+ if (type.rawValue == rawValue) {
+ return i;
+ }
+ }
+ return Integer.MAX_VALUE;
+ }
+
+ /**
+ * Find the best {@link EditType} for a potential insert. The "best" is the
+ * first primary type that doesn't already exist. When all valid types
+ * exist, we pick the last valid option.
+ */
+ public static EditType getBestValidType(EntityDelta state, DataKind kind,
+ boolean includeSecondary, int exactValue) {
+ // Shortcut when no types
+ if (kind.typeColumn == null) return null;
+
+ // Find type counts and valid primary types, bail if none
+ final SparseIntArray typeCount = getTypeFrequencies(state, kind);
+ final ArrayList<EditType> validTypes = getValidTypes(state, kind, null, includeSecondary,
+ typeCount);
+ if (validTypes.size() == 0) return null;
+
+ // Keep track of the last valid type
+ final EditType lastType = validTypes.get(validTypes.size() - 1);
+
+ // Remove any types that already exist
+ Iterator<EditType> iterator = validTypes.iterator();
+ while (iterator.hasNext()) {
+ final EditType type = iterator.next();
+ final int count = typeCount.get(type.rawValue);
+
+ if (exactValue == type.rawValue) {
+ // Found exact value match
+ return type;
+ }
+
+ if (count > 0) {
+ // Type already appears, so don't consider
+ iterator.remove();
+ }
+ }
+
+ // Use the best remaining, otherwise the last valid
+ if (validTypes.size() > 0) {
+ return validTypes.get(0);
+ } else {
+ return lastType;
+ }
+ }
+
+ /**
+ * Insert a new child of kind {@link DataKind} into the given
+ * {@link EntityDelta}. Tries using the best {@link EditType} found using
+ * {@link #getBestValidType(EntityDelta, DataKind, boolean, int)}.
+ */
+ public static ValuesDelta insertChild(EntityDelta state, DataKind kind) {
+ // First try finding a valid primary
+ EditType bestType = getBestValidType(state, kind, false, Integer.MIN_VALUE);
+ if (bestType == null) {
+ // No valid primary found, so expand search to secondary
+ bestType = getBestValidType(state, kind, true, Integer.MIN_VALUE);
+ }
+ return insertChild(state, kind, bestType);
+ }
+
+ /**
+ * Insert a new child of kind {@link DataKind} into the given
+ * {@link EntityDelta}, marked with the given {@link EditType}.
+ */
+ public static ValuesDelta insertChild(EntityDelta state, DataKind kind, EditType type) {
+ // Bail early if invalid kind
+ if (kind == null) return null;
+ final ContentValues after = new ContentValues();
+
+ // Our parent CONTACT_ID is provided later
+ after.put(Data.MIMETYPE, kind.mimeType);
+
+ // Fill-in with any requested default values
+ if (kind.defaultValues != null) {
+ after.putAll(kind.defaultValues);
+ }
+
+ if (kind.typeColumn != null && type != null) {
+ // Set type, if provided
+ after.put(kind.typeColumn, type.rawValue);
+ }
+
+ final ValuesDelta child = ValuesDelta.fromAfter(after);
+ state.addEntry(child);
+ return child;
+ }
+
+ /**
+ * Processing to trim any empty {@link ValuesDelta} and {@link EntityDelta}
+ * from the given {@link EntitySet}, 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) {
+ for (EntityDelta state : set) {
+ final String accountType = state.getValues().getAsString(RawContacts.ACCOUNT_TYPE);
+ final ContactsSource source = sources.getInflatedSource(accountType,
+ ContactsSource.LEVEL_MIMETYPES);
+ trimEmpty(state, source);
+ }
+ }
+
+ /**
+ * Processing to trim any empty {@link ValuesDelta} rows from the given
+ * {@link EntityDelta}, assuming the given {@link ContactsSource} dictates
+ * the structure for various fields. This method ignores rows not described
+ * by the {@link ContactsSource}.
+ */
+ public static void trimEmpty(EntityDelta state, ContactsSource source) {
+ boolean hasValues = false;
+
+ // Walk through entries for each well-known kind
+ for (DataKind kind : source.getSortedDataKinds()) {
+ final String mimeType = kind.mimeType;
+ final ArrayList<ValuesDelta> entries = state.getMimeEntries(mimeType);
+ if (entries == null) continue;
+
+ for (ValuesDelta entry : entries) {
+ // Skip any values that haven't been touched
+ final boolean touched = entry.isInsert() || entry.isUpdate();
+ if (!touched) {
+ hasValues = true;
+ continue;
+ }
+
+ // Test and remove this row if empty and it isn't a photo from google
+ final boolean isGoogleSource = TextUtils.equals(GoogleSource.ACCOUNT_TYPE,
+ state.getValues().getAsString(RawContacts.ACCOUNT_TYPE));
+ final boolean isPhoto = TextUtils.equals(Photo.CONTENT_ITEM_TYPE, kind.mimeType);
+ final boolean isGooglePhoto = isPhoto && isGoogleSource;
+
+ if (EntityModifier.isEmpty(entry, kind) && !isGooglePhoto) {
+ // TODO: remove this verbose logging
+ Log.w(TAG, "Trimming: " + entry.toString());
+ entry.markDeleted();
+ } else if (!entry.isFromTemplate()) {
+ hasValues = true;
+ }
+ }
+ }
+ if (!hasValues) {
+ // Trim overall entity if no children exist
+ state.markDeleted();
+ }
+ }
+
+ /**
+ * Test if the given {@link ValuesDelta} would be considered "empty" in
+ * terms of {@link DataKind#fieldList}.
+ */
+ public static boolean isEmpty(ValuesDelta values, DataKind kind) {
+ boolean hasValues = false;
+ for (EditField field : kind.fieldList) {
+ // If any field has values, we're not empty
+ final String value = values.getAsString(field.column);
+ if (ContactsUtils.isGraphic(value)) {
+ hasValues = true;
+ }
+ }
+
+ return !hasValues;
+ }
+
+ /**
+ * Parse the given {@link Bundle} into the given {@link EntityDelta} state,
+ * assuming the extras defined through {@link Intents}.
+ */
+ public static void parseExtras(Context context, ContactsSource source, EntityDelta state,
+ Bundle extras) {
+ if (extras == null || extras.size() == 0) {
+ // Bail early if no useful data
+ return;
+ }
+
+ {
+ // StructuredName
+ EntityModifier.ensureKindExists(state, source, StructuredName.CONTENT_ITEM_TYPE);
+ final ValuesDelta child = state.getPrimaryEntry(StructuredName.CONTENT_ITEM_TYPE);
+
+ final String name = extras.getString(Insert.NAME);
+ if (ContactsUtils.isGraphic(name)) {
+ child.put(StructuredName.GIVEN_NAME, name);
+ }
+
+ final String phoneticName = extras.getString(Insert.PHONETIC_NAME);
+ if (ContactsUtils.isGraphic(phoneticName)) {
+ child.put(StructuredName.PHONETIC_GIVEN_NAME, phoneticName);
+ }
+ }
+
+ {
+ // StructuredPostal
+ final DataKind kind = source.getKindForMimetype(StructuredPostal.CONTENT_ITEM_TYPE);
+ parseExtras(state, kind, extras, Insert.POSTAL_TYPE, Insert.POSTAL,
+ StructuredPostal.STREET);
+ }
+
+ {
+ // Phone
+ final DataKind kind = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE);
+ parseExtras(state, kind, extras, Insert.PHONE_TYPE, Insert.PHONE, Phone.NUMBER);
+ parseExtras(state, kind, extras, Insert.SECONDARY_PHONE_TYPE, Insert.SECONDARY_PHONE,
+ Phone.NUMBER);
+ parseExtras(state, kind, extras, Insert.TERTIARY_PHONE_TYPE, Insert.TERTIARY_PHONE,
+ Phone.NUMBER);
+ }
+
+ {
+ // Email
+ final DataKind kind = source.getKindForMimetype(Email.CONTENT_ITEM_TYPE);
+ parseExtras(state, kind, extras, Insert.EMAIL_TYPE, Insert.EMAIL, Email.DATA);
+ parseExtras(state, kind, extras, Insert.SECONDARY_EMAIL_TYPE, Insert.SECONDARY_EMAIL,
+ Email.DATA);
+ parseExtras(state, kind, extras, Insert.TERTIARY_EMAIL_TYPE, Insert.TERTIARY_EMAIL,
+ Email.DATA);
+ }
+
+ {
+ // Im
+ final DataKind kind = source.getKindForMimetype(Im.CONTENT_ITEM_TYPE);
+ fixupLegacyImType(extras);
+ parseExtras(state, kind, extras, Insert.IM_PROTOCOL, Insert.IM_HANDLE, Im.DATA);
+ }
+ }
+
+ /**
+ * Attempt to parse legacy {@link Insert#IM_PROTOCOL} values, replacing them
+ * with updated values.
+ */
+ private static void fixupLegacyImType(Bundle bundle) {
+ final String encodedString = bundle.getString(Insert.IM_PROTOCOL);
+ if (encodedString == null) return;
+
+ try {
+ final Object protocol = android.provider.Contacts.ContactMethods
+ .decodeImProtocol(encodedString);
+ if (protocol instanceof Integer) {
+ bundle.putInt(Insert.IM_PROTOCOL, (Integer)protocol);
+ } else {
+ bundle.putString(Insert.IM_PROTOCOL, (String)protocol);
+ }
+ } catch (IllegalArgumentException e) {
+ // Ignore exception when legacy parser fails
+ }
+ }
+
+ /**
+ * Parse a specific entry from the given {@link Bundle} and insert into the
+ * given {@link EntityDelta}. Silently skips the insert when missing value
+ * or no valid {@link EditType} found.
+ *
+ * @param typeExtra {@link Bundle} key that holds the incoming
+ * {@link EditType#rawValue} value.
+ * @param valueExtra {@link Bundle} key that holds the incoming value.
+ * @param valueColumn Column to write value into {@link ValuesDelta}.
+ */
+ public static void parseExtras(EntityDelta state, DataKind kind, Bundle extras,
+ String typeExtra, String valueExtra, String valueColumn) {
+ final CharSequence value = extras.getCharSequence(valueExtra);
+
+ // Bail early if source doesn't handle this type
+ if (kind == null) return;
+
+ // Bail when can't insert type, or value missing
+ final boolean canInsert = EntityModifier.canInsert(state, kind);
+ final boolean validValue = (value != null && TextUtils.isGraphic(value));
+ if (!validValue || !canInsert) return;
+
+ // Find exact type when requested, otherwise best available type
+ final boolean hasType = extras.containsKey(typeExtra);
+ final int typeValue = extras.getInt(typeExtra, hasType ? BaseTypes.TYPE_CUSTOM
+ : Integer.MIN_VALUE);
+ final EditType editType = EntityModifier.getBestValidType(state, kind, true, typeValue);
+
+ // Create data row and fill with value
+ final ValuesDelta child = EntityModifier.insertChild(state, kind, editType);
+ child.put(valueColumn, value.toString());
+
+ if (editType != null && editType.customColumn != null) {
+ // Write down label when custom type picked
+ final String customType = extras.getString(typeExtra);
+ child.put(editType.customColumn, customType);
+ }
+ }
+}
diff --git a/src/com/android/contacts/model/EntitySet.java b/src/com/android/contacts/model/EntitySet.java
new file mode 100644
index 0000000..be2f70f
--- /dev/null
+++ b/src/com/android/contacts/model/EntitySet.java
@@ -0,0 +1,343 @@
+/*
+ * 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.ContentResolver;
+import android.content.Entity;
+import android.content.EntityIterator;
+import android.content.ContentProviderOperation.Builder;
+import android.graphics.BitmapFactory;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.provider.ContactsContract.AggregationExceptions;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.RawContacts;
+import android.provider.ContactsContract.CommonDataKinds.Photo;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+
+import com.google.android.collect.Lists;
+
+import com.android.contacts.model.EntityDelta.ValuesDelta;
+
+import java.util.ArrayList;
+
+/**
+ * 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.
+ */
+public class EntitySet extends ArrayList<EntityDelta> implements Parcelable {
+ private boolean mSplitRawContacts;
+
+ private EntitySet() {
+ }
+
+ /**
+ * Create an {@link EntitySet} 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();
+ state.add(delta);
+ return state;
+ }
+
+ /**
+ * Create an {@link EntitySet} 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,
+ String[] selectionArgs, String sortOrder) {
+ EntityIterator iterator = null;
+ final EntitySet state = new EntitySet();
+ try {
+ // Perform background query to pull contact details
+ iterator = resolver.queryEntities(RawContacts.CONTENT_URI, selection, selectionArgs,
+ sortOrder);
+ 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);
+ }
+ } catch (RemoteException e) {
+ throw new IllegalStateException("Problem querying contact details", e);
+ } finally {
+ if (iterator != null) {
+ iterator.close();
+ }
+ }
+ return state;
+ }
+
+ /**
+ * 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}.
+ */
+ public static EntitySet mergeAfter(EntitySet local, EntitySet remote) {
+ if (local == null) local = new EntitySet();
+
+ // For each entity in the remote set, try matching over existing
+ for (EntityDelta remoteEntity : remote) {
+ final Long rawContactId = remoteEntity.getValues().getId();
+
+ // Find or create local match and merge
+ final EntityDelta localEntity = local.getByRawContactId(rawContactId);
+ final EntityDelta merged = EntityDelta.mergeAfter(localEntity, remoteEntity);
+
+ if (localEntity == null && merged != null) {
+ // No local entry before, so insert
+ local.add(merged);
+ }
+ }
+
+ return local;
+ }
+
+ /**
+ * Build a list of {@link ContentProviderOperation} that will transform all
+ * the "before" {@link Entity} states into the modified state which all
+ * {@link EntityDelta} objects represent. This method specifically creates
+ * any {@link AggregationExceptions} rules needed to groups edits together.
+ */
+ public ArrayList<ContentProviderOperation> buildDiff() {
+ final ArrayList<ContentProviderOperation> diff = Lists.newArrayList();
+
+ final long rawContactId = this.findRawContactId();
+ int firstInsertRow = -1;
+
+ // First pass enforces versions remain consistent
+ for (EntityDelta delta : this) {
+ delta.buildAssert(diff);
+ }
+
+ final int assertMark = diff.size();
+ int backRefs[] = new int[size()];
+
+ int rawContactIndex = 0;
+
+ // Second pass builds actual operations
+ for (EntityDelta delta : this) {
+ final int firstBatch = diff.size();
+ backRefs[rawContactIndex++] = firstBatch;
+ delta.buildDiff(diff);
+
+ // Only create rules for inserts
+ if (!delta.isContactInsert()) continue;
+
+ // If we are going to split all contacts, there is no point in first combining them
+ if (mSplitRawContacts) continue;
+
+ if (rawContactId != -1) {
+ // Has existing contact, so bind to it strongly
+ final Builder builder = beginKeepTogether();
+ builder.withValue(AggregationExceptions.RAW_CONTACT_ID1, rawContactId);
+ builder.withValueBackReference(AggregationExceptions.RAW_CONTACT_ID2, firstBatch);
+ diff.add(builder.build());
+
+ } else if (firstInsertRow == -1) {
+ // First insert case, so record row
+ firstInsertRow = firstBatch;
+
+ } 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_ID2, firstBatch);
+ diff.add(builder.build());
+ }
+ }
+
+ if (mSplitRawContacts) {
+ buildSplitContactDiff(diff, backRefs);
+ }
+
+ // No real changes if only left with asserts
+ if (diff.size() == assertMark) {
+ diff.clear();
+ }
+
+ return diff;
+ }
+
+ /**
+ * Start building a {@link ContentProviderOperation} that will keep two
+ * {@link RawContacts} together.
+ */
+ protected Builder beginKeepTogether() {
+ final Builder builder = ContentProviderOperation
+ .newUpdate(AggregationExceptions.CONTENT_URI);
+ builder.withValue(AggregationExceptions.TYPE, AggregationExceptions.TYPE_KEEP_TOGETHER);
+ return builder;
+ }
+
+ /**
+ * Builds {@link AggregationExceptions} to split all constituent raw contacts into
+ * separate contacts.
+ */
+ private void buildSplitContactDiff(final ArrayList<ContentProviderOperation> diff,
+ int[] backRefs) {
+ int count = size();
+ for (int i = 0; i < count; i++) {
+ for (int j = 0; j < count; j++) {
+ if (i != j) {
+ buildSplitContactDiff(diff, i, j, backRefs);
+ }
+ }
+ }
+ }
+
+ /**
+ * Construct a {@link AggregationExceptions#TYPE_KEEP_SEPARATE}.
+ */
+ private void buildSplitContactDiff(ArrayList<ContentProviderOperation> diff, int index1,
+ int index2, int[] backRefs) {
+ Builder builder =
+ ContentProviderOperation.newUpdate(AggregationExceptions.CONTENT_URI);
+ builder.withValue(AggregationExceptions.TYPE, AggregationExceptions.TYPE_KEEP_SEPARATE);
+
+ Long rawContactId1 = get(index1).getValues().getAsLong(RawContacts._ID);
+ if (rawContactId1 != null && rawContactId1 >= 0) {
+ builder.withValue(AggregationExceptions.RAW_CONTACT_ID1, rawContactId1);
+ } else {
+ builder.withValueBackReference(AggregationExceptions.RAW_CONTACT_ID1, backRefs[index1]);
+ }
+
+ Long rawContactId2 = get(index2).getValues().getAsLong(RawContacts._ID);
+ if (rawContactId2 != null && rawContactId2 >= 0) {
+ builder.withValue(AggregationExceptions.RAW_CONTACT_ID2, rawContactId2);
+ } else {
+ builder.withValueBackReference(AggregationExceptions.RAW_CONTACT_ID2, backRefs[index2]);
+ }
+ diff.add(builder.build());
+ }
+
+ /**
+ * Search all contained {@link EntityDelta} for the first one with an
+ * existing {@link RawContacts#_ID} value. Usually used when creating
+ * {@link AggregationExceptions} during an update.
+ */
+ public long findRawContactId() {
+ for (EntityDelta delta : this) {
+ final Long rawContactId = delta.getValues().getAsLong(RawContacts._ID);
+ if (rawContactId != null && rawContactId >= 0) {
+ return rawContactId;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Find {@link RawContacts#_ID} of the requested {@link EntityDelta}.
+ */
+ public Long getRawContactId(int index) {
+ if (index >= 0 && index < this.size()) {
+ final EntityDelta delta = this.get(index);
+ final ValuesDelta values = delta.getValues();
+ if (values.isVisible()) {
+ return values.getAsLong(RawContacts._ID);
+ }
+ }
+ return null;
+ }
+
+ public EntityDelta getByRawContactId(Long rawContactId) {
+ final int index = this.indexOfRawContactId(rawContactId);
+ return (index == -1) ? null : this.get(index);
+ }
+
+ /**
+ * Find index of given {@link RawContacts#_ID} when present.
+ */
+ public int indexOfRawContactId(Long rawContactId) {
+ if (rawContactId == null) return -1;
+ final int size = this.size();
+ for (int i = 0; i < size; i++) {
+ final Long currentId = getRawContactId(i);
+ if (rawContactId.equals(currentId)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ public ValuesDelta getSuperPrimaryEntry(final String mimeType) {
+ ValuesDelta primary = null;
+ ValuesDelta randomEntry = null;
+ for (EntityDelta delta : this) {
+ final ArrayList<ValuesDelta> mimeEntries = delta.getMimeEntries(mimeType);
+ if (mimeEntries == null) return null;
+
+ for (ValuesDelta entry : mimeEntries) {
+ if (entry.isSuperPrimary()) {
+ return entry;
+ } else if (primary == null && entry.isPrimary()) {
+ primary = entry;
+ } else if (randomEntry == null) {
+ randomEntry = entry;
+ }
+ }
+ }
+ // When no direct super primary, return something
+ if (primary != null) {
+ return primary;
+ }
+ return randomEntry;
+ }
+
+ public void splitRawContacts() {
+ mSplitRawContacts = true;
+ }
+
+ /** {@inheritDoc} */
+ public int describeContents() {
+ // Nothing special about this parcel
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ public void writeToParcel(Parcel dest, int flags) {
+ final int size = this.size();
+ dest.writeInt(size);
+ for (EntityDelta delta : this) {
+ dest.writeParcelable(delta, flags);
+ }
+ }
+
+ public void readFromParcel(Parcel source) {
+ final int size = source.readInt();
+ for (int i = 0; i < size; i++) {
+ this.add(source.<EntityDelta> readParcelable(null));
+ }
+ }
+
+ public static final Parcelable.Creator<EntitySet> CREATOR = new Parcelable.Creator<EntitySet>() {
+ public EntitySet createFromParcel(Parcel in) {
+ final EntitySet state = new EntitySet();
+ state.readFromParcel(in);
+ return state;
+ }
+
+ public EntitySet[] newArray(int size) {
+ return new EntitySet[size];
+ }
+ };
+}
diff --git a/src/com/android/contacts/model/ExchangeSource.java b/src/com/android/contacts/model/ExchangeSource.java
new file mode 100644
index 0000000..b46824c
--- /dev/null
+++ b/src/com/android/contacts/model/ExchangeSource.java
@@ -0,0 +1,285 @@
+/*
+ * 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 com.android.contacts.R;
+import com.google.android.collect.Lists;
+
+import android.content.ContentValues;
+import android.content.Context;
+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.Photo;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+import android.provider.ContactsContract.CommonDataKinds.Website;
+
+public class ExchangeSource extends FallbackSource {
+
+ public static final String ACCOUNT_TYPE = "com.android.exchange";
+
+ public ExchangeSource(String resPackageName) {
+ this.accountType = ACCOUNT_TYPE;
+ this.resPackageName = null;
+ this.summaryResPackageName = resPackageName;
+ }
+
+ @Override
+ protected void inflate(Context context, int inflateLevel) {
+
+ inflateStructuredName(inflateLevel);
+ inflateNickname(inflateLevel);
+ inflatePhone(inflateLevel);
+ inflateEmail(inflateLevel);
+ inflateStructuredPostal(inflateLevel);
+ inflateIm(inflateLevel);
+ inflateOrganization(inflateLevel);
+ inflatePhoto(inflateLevel);
+ inflateNote(inflateLevel);
+ inflateWebsite(inflateLevel);
+
+ setInflatedLevel(inflateLevel);
+ }
+
+ @Override
+ protected DataKind inflateStructuredName(int inflateLevel) {
+ final DataKind kind = super.inflateStructuredName(ContactsSource.LEVEL_MIMETYPES);
+
+ if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
+ kind.typeOverallMax = 1;
+
+ kind.fieldList = Lists.newArrayList();
+ kind.fieldList.add(new EditField(StructuredName.PREFIX, R.string.name_prefix,
+ FLAGS_PERSON_NAME).setOptional(true));
+ kind.fieldList.add(new EditField(StructuredName.GIVEN_NAME, R.string.name_given,
+ FLAGS_PERSON_NAME));
+ kind.fieldList.add(new EditField(StructuredName.MIDDLE_NAME, R.string.name_middle,
+ FLAGS_PERSON_NAME).setOptional(true));
+ kind.fieldList.add(new EditField(StructuredName.FAMILY_NAME, R.string.name_family,
+ FLAGS_PERSON_NAME));
+ kind.fieldList.add(new EditField(StructuredName.SUFFIX, R.string.name_suffix,
+ FLAGS_PERSON_NAME).setOptional(true));
+ kind.fieldList.add(new EditField(StructuredName.PHONETIC_GIVEN_NAME,
+ R.string.name_phonetic_given, FLAGS_PHONETIC).setOptional(true));
+ kind.fieldList.add(new EditField(StructuredName.PHONETIC_FAMILY_NAME,
+ R.string.name_phonetic_family, FLAGS_PHONETIC).setOptional(true));
+ }
+
+ return kind;
+ }
+
+ @Override
+ protected DataKind inflateNickname(int inflateLevel) {
+ final DataKind kind = super.inflateNickname(ContactsSource.LEVEL_MIMETYPES);
+
+ if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
+ kind.typeOverallMax = 1;
+
+ kind.fieldList = Lists.newArrayList();
+ kind.fieldList.add(new EditField(Nickname.NAME, R.string.nicknameLabelsGroup,
+ FLAGS_PERSON_NAME));
+ }
+
+ return kind;
+ }
+
+ @Override
+ protected DataKind inflatePhone(int inflateLevel) {
+ final DataKind kind = super.inflatePhone(ContactsSource.LEVEL_MIMETYPES);
+
+ if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
+ kind.typeColumn = Phone.TYPE;
+ kind.typeList = Lists.newArrayList();
+ kind.typeList.add(buildPhoneType(Phone.TYPE_HOME).setSpecificMax(2));
+ kind.typeList.add(buildPhoneType(Phone.TYPE_MOBILE).setSpecificMax(1));
+ kind.typeList.add(buildPhoneType(Phone.TYPE_WORK).setSpecificMax(2));
+ kind.typeList.add(buildPhoneType(Phone.TYPE_FAX_WORK).setSecondary(true)
+ .setSpecificMax(1));
+ kind.typeList.add(buildPhoneType(Phone.TYPE_FAX_HOME).setSecondary(true)
+ .setSpecificMax(1));
+ kind.typeList
+ .add(buildPhoneType(Phone.TYPE_PAGER).setSecondary(true).setSpecificMax(1));
+ kind.typeList.add(buildPhoneType(Phone.TYPE_CAR).setSecondary(true).setSpecificMax(1));
+ kind.typeList.add(buildPhoneType(Phone.TYPE_COMPANY_MAIN).setSecondary(true)
+ .setSpecificMax(1));
+ kind.typeList.add(buildPhoneType(Phone.TYPE_MMS).setSecondary(true).setSpecificMax(1));
+ kind.typeList
+ .add(buildPhoneType(Phone.TYPE_RADIO).setSecondary(true).setSpecificMax(1));
+ kind.typeList.add(buildPhoneType(Phone.TYPE_ASSISTANT).setSecondary(true)
+ .setSpecificMax(1).setCustomColumn(Phone.LABEL));
+
+ kind.fieldList = Lists.newArrayList();
+ kind.fieldList.add(new EditField(Phone.NUMBER, R.string.phoneLabelsGroup, FLAGS_PHONE));
+ }
+
+ return kind;
+ }
+
+ @Override
+ protected DataKind inflateEmail(int inflateLevel) {
+ final DataKind kind = super.inflateEmail(ContactsSource.LEVEL_MIMETYPES);
+
+ if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
+ kind.typeOverallMax = 3;
+
+ kind.fieldList = Lists.newArrayList();
+ kind.fieldList.add(new EditField(Email.DATA, R.string.emailLabelsGroup, FLAGS_EMAIL));
+ }
+
+ return kind;
+ }
+
+ @Override
+ protected DataKind inflateStructuredPostal(int inflateLevel) {
+ final DataKind kind = super.inflateStructuredPostal(ContactsSource.LEVEL_MIMETYPES);
+
+ if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
+ kind.typeColumn = StructuredPostal.TYPE;
+ kind.typeList = Lists.newArrayList();
+ kind.typeList.add(buildPostalType(StructuredPostal.TYPE_WORK).setSpecificMax(1));
+ kind.typeList.add(buildPostalType(StructuredPostal.TYPE_HOME).setSpecificMax(1));
+ kind.typeList.add(buildPostalType(StructuredPostal.TYPE_OTHER).setSpecificMax(1));
+
+ kind.fieldList = Lists.newArrayList();
+ kind.fieldList.add(new EditField(StructuredPostal.STREET, R.string.postal_street,
+ FLAGS_POSTAL));
+ 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));
+ }
+
+ return kind;
+ }
+
+ @Override
+ protected DataKind inflateIm(int inflateLevel) {
+ final DataKind kind = super.inflateIm(ContactsSource.LEVEL_MIMETYPES);
+
+ if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
+ kind.typeOverallMax = 3;
+
+ // NOTE: even though a traditional "type" exists, for editing
+ // purposes we're using the protocol to pick labels
+
+ kind.defaultValues = new ContentValues();
+ kind.defaultValues.put(Im.TYPE, Im.TYPE_OTHER);
+
+ kind.typeColumn = Im.PROTOCOL;
+ kind.typeList = Lists.newArrayList();
+ kind.typeList.add(buildImType(Im.PROTOCOL_AIM));
+ kind.typeList.add(buildImType(Im.PROTOCOL_MSN));
+ kind.typeList.add(buildImType(Im.PROTOCOL_YAHOO));
+ kind.typeList.add(buildImType(Im.PROTOCOL_SKYPE));
+ kind.typeList.add(buildImType(Im.PROTOCOL_QQ));
+ kind.typeList.add(buildImType(Im.PROTOCOL_GOOGLE_TALK));
+ kind.typeList.add(buildImType(Im.PROTOCOL_ICQ));
+ kind.typeList.add(buildImType(Im.PROTOCOL_JABBER));
+ kind.typeList.add(buildImType(Im.PROTOCOL_CUSTOM).setSecondary(true).setCustomColumn(
+ Im.CUSTOM_PROTOCOL));
+
+ kind.fieldList = Lists.newArrayList();
+ kind.fieldList.add(new EditField(Im.DATA, R.string.imLabelsGroup, FLAGS_EMAIL));
+ }
+
+ return kind;
+ }
+
+ @Override
+ protected DataKind inflateOrganization(int inflateLevel) {
+ final DataKind kind = super.inflateOrganization(ContactsSource.LEVEL_MIMETYPES);
+
+ if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
+ kind.typeOverallMax = 1;
+
+ kind.typeColumn = Organization.TYPE;
+ kind.typeList = Lists.newArrayList();
+ kind.typeList.add(buildOrgType(Organization.TYPE_WORK).setSpecificMax(1));
+ kind.typeList.add(buildOrgType(Organization.TYPE_OTHER).setSpecificMax(1));
+ kind.typeList.add(buildOrgType(Organization.TYPE_CUSTOM).setSecondary(true)
+ .setSpecificMax(1));
+
+ kind.fieldList = Lists.newArrayList();
+ kind.fieldList.add(new EditField(Organization.COMPANY, R.string.ghostData_company,
+ FLAGS_GENERIC_NAME));
+ kind.fieldList.add(new EditField(Organization.TITLE, R.string.ghostData_title,
+ FLAGS_GENERIC_NAME));
+ }
+
+ return kind;
+ }
+
+ @Override
+ protected DataKind inflatePhoto(int inflateLevel) {
+ final DataKind kind = super.inflatePhoto(ContactsSource.LEVEL_MIMETYPES);
+
+ if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
+ kind.typeOverallMax = 1;
+
+ kind.fieldList = Lists.newArrayList();
+ kind.fieldList.add(new EditField(Photo.PHOTO, -1, -1));
+ }
+
+ return kind;
+ }
+
+ @Override
+ protected DataKind inflateNote(int inflateLevel) {
+ final DataKind kind = super.inflateNote(ContactsSource.LEVEL_MIMETYPES);
+
+ if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
+ kind.typeOverallMax = 1;
+
+ kind.fieldList = Lists.newArrayList();
+ kind.fieldList.add(new EditField(Note.NOTE, R.string.label_notes, FLAGS_NOTE));
+ }
+
+ return kind;
+ }
+
+ @Override
+ protected DataKind inflateWebsite(int inflateLevel) {
+ final DataKind kind = super.inflateWebsite(ContactsSource.LEVEL_MIMETYPES);
+
+ if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
+ kind.typeOverallMax = 1;
+
+ kind.fieldList = Lists.newArrayList();
+ kind.fieldList.add(new EditField(Website.URL, R.string.websiteLabelsGroup, FLAGS_WEBSITE));
+ }
+
+ return kind;
+ }
+
+ @Override
+ public int getHeaderColor(Context context) {
+ return 0xffd5ba96;
+ }
+
+ @Override
+ public int getSideBarColor(Context context) {
+ return 0xffb58e59;
+ }
+}
diff --git a/src/com/android/contacts/model/ExternalSource.java b/src/com/android/contacts/model/ExternalSource.java
new file mode 100644
index 0000000..743eb4e
--- /dev/null
+++ b/src/com/android/contacts/model/ExternalSource.java
@@ -0,0 +1,193 @@
+/*
+ * 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 org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.util.AttributeSet;
+import android.util.Xml;
+
+import java.io.IOException;
+import java.util.List;
+
+/*
+
+<!-- example of what SourceConstraints would look like in XML -->
+<!-- NOTE: may not directly match the current structure version -->
+
+<DataKind
+ mimeType="vnd.android.cursor.item/email"
+ title="@string/title_postal"
+ icon="@drawable/icon_postal"
+ weight="12"
+ editable="true">
+
+ <!-- these are defined using string-builder-ish -->
+ <ActionHeader></ActionHeader>
+ <ActionBody socialSummary="true" /> <!-- can pull together various columns -->
+
+ <!-- ordering handles precedence the "insert/add" case -->
+ <!-- assume uniform type when missing "column", use title in place -->
+ <EditTypes column="data5" overallMax="-1">
+ <EditType rawValue="0" label="@string/type_home" specificMax="-1" />
+ <EditType rawValue="1" label="@string/type_work" specificMax="-1" secondary="true" />
+ <EditType rawValue="4" label="@string/type_custom" customColumn="data6" specificMax="-1" secondary="true" />
+ </EditTypes>
+
+ <!-- when single edit field, simplifies edit case -->
+ <EditField column="data1" title="@string/field_family_name" android:inputType="textCapWords|textPhonetic" />
+ <EditField column="data2" title="@string/field_given_name" android:minLines="2" />
+ <EditField column="data3" title="@string/field_suffix" />
+
+</DataKind>
+
+*/
+
+/**
+ * Internal structure that represents constraints and styles for a specific data
+ * source, such as the various data types they support, including details on how
+ * those types should be rendered and edited.
+ * <p>
+ * In the future this may be inflated from XML defined by a data source.
+ */
+public class ExternalSource extends FallbackSource {
+ private static final String ACTION_SYNC_ADAPTER = "android.content.SyncAdapter";
+ private static final String METADATA_CONTACTS = "android.provider.CONTACTS_STRUCTURE";
+
+ private interface InflateTags {
+ final String CONTACTS_SOURCE = "ContactsSource";
+ final String CONTACTS_DATA_KIND = "ContactsDataKind";
+ }
+
+ public ExternalSource(String resPackageName) {
+ this.resPackageName = resPackageName;
+ this.summaryResPackageName = resPackageName;
+ }
+
+ /**
+ * Ensure that the constraint rules behind this {@link ContactsSource} have
+ * been inflated. Because this may involve parsing meta-data from
+ * {@link PackageManager}, it shouldn't be called from a UI thread.
+ */
+ @Override
+ public void inflate(Context context, int inflateLevel) {
+ // Handle unknown sources by searching their package
+ final PackageManager pm = context.getPackageManager();
+ final Intent syncAdapter = new Intent(ACTION_SYNC_ADAPTER);
+ final List<ResolveInfo> matches = pm.queryIntentServices(syncAdapter,
+ PackageManager.GET_META_DATA);
+ for (ResolveInfo info : matches) {
+ final XmlResourceParser parser = info.serviceInfo.loadXmlMetaData(pm,
+ METADATA_CONTACTS);
+ if (parser == null) continue;
+ inflate(context, parser);
+ }
+
+ // Bring in name and photo from fallback source, which are non-optional
+ inflateStructuredName(inflateLevel);
+ inflatePhoto(inflateLevel);
+
+ setInflatedLevel(inflateLevel);
+ }
+
+ /**
+ * Inflate this {@link ContactsSource} from the given parser. This may only
+ * load details matching the publicly-defined schema.
+ */
+ protected void inflate(Context context, XmlPullParser parser) {
+ final AttributeSet attrs = Xml.asAttributeSet(parser);
+
+ try {
+ 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");
+ }
+
+ if (!InflateTags.CONTACTS_SOURCE.equals(parser.getName())) {
+ throw new IllegalStateException("Top level element must be "
+ + InflateTags.CONTACTS_SOURCE);
+ }
+
+ // Parse all children kinds
+ final int depth = parser.getDepth();
+ while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
+ && type != XmlPullParser.END_DOCUMENT) {
+ if (type == XmlPullParser.END_TAG
+ || !InflateTags.CONTACTS_DATA_KIND.equals(parser.getName())) {
+ continue;
+ }
+
+ final TypedArray a = context.obtainStyledAttributes(attrs,
+ android.R.styleable.ContactsDataKind);
+ final DataKind kind = new DataKind();
+
+ kind.mimeType = a
+ .getString(com.android.internal.R.styleable.ContactsDataKind_mimeType);
+ kind.iconRes = a.getResourceId(
+ com.android.internal.R.styleable.ContactsDataKind_icon, -1);
+
+ final String summaryColumn = a
+ .getString(com.android.internal.R.styleable.ContactsDataKind_summaryColumn);
+ if (summaryColumn != null) {
+ // Inflate a specific column as summary when requested
+ kind.actionHeader = new FallbackSource.SimpleInflater(summaryColumn);
+ }
+
+ final String detailColumn = a
+ .getString(com.android.internal.R.styleable.ContactsDataKind_detailColumn);
+ final boolean detailSocialSummary = a.getBoolean(
+ com.android.internal.R.styleable.ContactsDataKind_detailSocialSummary,
+ false);
+ if (detailSocialSummary) {
+ // Inflate social summary when requested
+ kind.actionBodySocial = true;
+ } else {
+ // Otherwise inflate specific column as summary
+ kind.actionBody = new FallbackSource.SimpleInflater(detailColumn);
+ }
+
+ addKind(kind);
+ }
+ } catch (XmlPullParserException e) {
+ throw new IllegalStateException("Problem reading XML", e);
+ } catch (IOException e) {
+ throw new IllegalStateException("Problem reading XML", e);
+ }
+ }
+
+ @Override
+ public int getHeaderColor(Context context) {
+ return 0xff6d86b4;
+ }
+
+ @Override
+ public int getSideBarColor(Context context) {
+ return 0xff6d86b4;
+ }
+}
diff --git a/src/com/android/contacts/model/FallbackSource.java b/src/com/android/contacts/model/FallbackSource.java
new file mode 100644
index 0000000..ca405eb
--- /dev/null
+++ b/src/com/android/contacts/model/FallbackSource.java
@@ -0,0 +1,634 @@
+/*
+ * 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 com.android.contacts.R;
+import com.google.android.collect.Lists;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.provider.ContactsContract.CommonDataKinds.BaseTypes;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.Event;
+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.Photo;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+import android.provider.ContactsContract.CommonDataKinds.Website;
+import android.text.TextUtils;
+import android.view.inputmethod.EditorInfo;
+
+public class FallbackSource extends ContactsSource {
+ protected static final int FLAGS_PHONE = EditorInfo.TYPE_CLASS_PHONE;
+ protected static final int FLAGS_EMAIL = EditorInfo.TYPE_CLASS_TEXT
+ | EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS;
+ protected static final int FLAGS_PERSON_NAME = EditorInfo.TYPE_CLASS_TEXT
+ | EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS | EditorInfo.TYPE_TEXT_VARIATION_PERSON_NAME;
+ protected static final int FLAGS_PHONETIC = EditorInfo.TYPE_CLASS_TEXT
+ | EditorInfo.TYPE_TEXT_VARIATION_PHONETIC;
+ protected static final int FLAGS_GENERIC_NAME = EditorInfo.TYPE_CLASS_TEXT
+ | EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
+ protected static final int FLAGS_NOTE = EditorInfo.TYPE_CLASS_TEXT
+ | EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
+ protected static final int FLAGS_WEBSITE = EditorInfo.TYPE_CLASS_TEXT
+ | EditorInfo.TYPE_TEXT_VARIATION_URI;
+ protected static final int FLAGS_POSTAL = EditorInfo.TYPE_CLASS_TEXT
+ | EditorInfo.TYPE_TEXT_VARIATION_POSTAL_ADDRESS | EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS
+ | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
+
+ public FallbackSource() {
+ this.accountType = null;
+ this.titleRes = R.string.account_phone;
+ this.iconRes = R.drawable.ic_launcher_contacts;
+ }
+
+ @Override
+ protected void inflate(Context context, int inflateLevel) {
+
+ inflateStructuredName(inflateLevel);
+ inflateNickname(inflateLevel);
+ inflatePhone(inflateLevel);
+ inflateEmail(inflateLevel);
+ inflateStructuredPostal(inflateLevel);
+ inflateIm(inflateLevel);
+ inflateOrganization(inflateLevel);
+ inflatePhoto(inflateLevel);
+ inflateNote(inflateLevel);
+ inflateWebsite(inflateLevel);
+ inflateEvent(inflateLevel);
+
+ setInflatedLevel(inflateLevel);
+
+ }
+
+ protected EditType buildPhoneType(int type) {
+ return new EditType(type, Phone.getTypeLabelResource(type));
+ }
+
+ protected EditType buildEmailType(int type) {
+ return new EditType(type, Email.getTypeLabelResource(type));
+ }
+
+ protected EditType buildPostalType(int type) {
+ return new EditType(type, StructuredPostal.getTypeLabelResource(type));
+ }
+
+ protected EditType buildImType(int type) {
+ return new EditType(type, Im.getProtocolLabelResource(type));
+ }
+
+ protected EditType buildOrgType(int type) {
+ return new EditType(type, Organization.getTypeLabelResource(type));
+ }
+
+ protected DataKind inflateStructuredName(int inflateLevel) {
+ DataKind kind = getKindForMimetype(StructuredName.CONTENT_ITEM_TYPE);
+ if (kind == null) {
+ kind = addKind(new DataKind(StructuredName.CONTENT_ITEM_TYPE,
+ R.string.nameLabelsGroup, -1, -1, true));
+ }
+
+ if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
+ kind.fieldList = Lists.newArrayList();
+ kind.fieldList.add(new EditField(StructuredName.PREFIX, R.string.name_prefix,
+ FLAGS_PERSON_NAME).setOptional(true));
+ kind.fieldList.add(new EditField(StructuredName.GIVEN_NAME, R.string.name_given,
+ FLAGS_PERSON_NAME));
+ kind.fieldList.add(new EditField(StructuredName.MIDDLE_NAME, R.string.name_middle,
+ FLAGS_PERSON_NAME).setOptional(true));
+ kind.fieldList.add(new EditField(StructuredName.FAMILY_NAME, R.string.name_family,
+ FLAGS_PERSON_NAME));
+ kind.fieldList.add(new EditField(StructuredName.SUFFIX, R.string.name_suffix,
+ FLAGS_PERSON_NAME).setOptional(true));
+ kind.fieldList.add(new EditField(StructuredName.PHONETIC_GIVEN_NAME,
+ R.string.name_phonetic_given, FLAGS_PHONETIC).setOptional(true));
+ kind.fieldList.add(new EditField(StructuredName.PHONETIC_MIDDLE_NAME,
+ R.string.name_phonetic_middle, FLAGS_PHONETIC).setOptional(true));
+ kind.fieldList.add(new EditField(StructuredName.PHONETIC_FAMILY_NAME,
+ R.string.name_phonetic_family, FLAGS_PHONETIC).setOptional(true));
+ }
+
+ return kind;
+ }
+
+ protected DataKind inflateNickname(int inflateLevel) {
+ DataKind kind = getKindForMimetype(Nickname.CONTENT_ITEM_TYPE);
+ if (kind == null) {
+ kind = addKind(new DataKind(Nickname.CONTENT_ITEM_TYPE,
+ R.string.nicknameLabelsGroup, -1, 115, true));
+ kind.secondary = true;
+ kind.actionHeader = new SimpleInflater(R.string.nicknameLabelsGroup);
+ kind.actionBody = new SimpleInflater(Nickname.NAME);
+ }
+
+ if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
+ kind.defaultValues = new ContentValues();
+ kind.defaultValues.put(Nickname.TYPE, Nickname.TYPE_DEFAULT);
+
+ kind.fieldList = Lists.newArrayList();
+ kind.fieldList.add(new EditField(Nickname.NAME, R.string.nicknameLabelsGroup,
+ FLAGS_PERSON_NAME));
+ }
+
+ return kind;
+ }
+
+ protected DataKind inflatePhone(int inflateLevel) {
+ DataKind kind = getKindForMimetype(Phone.CONTENT_ITEM_TYPE);
+ if (kind == null) {
+ kind = addKind(new DataKind(Phone.CONTENT_ITEM_TYPE, R.string.phoneLabelsGroup,
+ android.R.drawable.sym_action_call, 10, true));
+ kind.iconAltRes = R.drawable.sym_action_sms;
+ kind.actionHeader = new PhoneActionInflater();
+ kind.actionAltHeader = new PhoneActionAltInflater();
+ kind.actionBody = new SimpleInflater(Phone.NUMBER);
+ }
+
+ if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
+ kind.typeColumn = Phone.TYPE;
+ kind.typeList = Lists.newArrayList();
+ kind.typeList.add(buildPhoneType(Phone.TYPE_HOME));
+ kind.typeList.add(buildPhoneType(Phone.TYPE_MOBILE));
+ kind.typeList.add(buildPhoneType(Phone.TYPE_WORK));
+ kind.typeList.add(buildPhoneType(Phone.TYPE_FAX_WORK).setSecondary(true));
+ kind.typeList.add(buildPhoneType(Phone.TYPE_FAX_HOME).setSecondary(true));
+ kind.typeList.add(buildPhoneType(Phone.TYPE_PAGER).setSecondary(true));
+ kind.typeList.add(buildPhoneType(Phone.TYPE_OTHER));
+ kind.typeList.add(buildPhoneType(Phone.TYPE_CUSTOM).setSecondary(true).setCustomColumn(
+ Phone.LABEL));
+ kind.typeList.add(buildPhoneType(Phone.TYPE_CALLBACK).setSecondary(true));
+ kind.typeList.add(buildPhoneType(Phone.TYPE_CAR).setSecondary(true));
+ kind.typeList.add(buildPhoneType(Phone.TYPE_COMPANY_MAIN).setSecondary(true));
+ kind.typeList.add(buildPhoneType(Phone.TYPE_ISDN).setSecondary(true));
+ kind.typeList.add(buildPhoneType(Phone.TYPE_MAIN).setSecondary(true));
+ kind.typeList.add(buildPhoneType(Phone.TYPE_OTHER_FAX).setSecondary(true));
+ kind.typeList.add(buildPhoneType(Phone.TYPE_RADIO).setSecondary(true));
+ kind.typeList.add(buildPhoneType(Phone.TYPE_TELEX).setSecondary(true));
+ kind.typeList.add(buildPhoneType(Phone.TYPE_TTY_TDD).setSecondary(true));
+ kind.typeList.add(buildPhoneType(Phone.TYPE_WORK_MOBILE).setSecondary(true));
+ kind.typeList.add(buildPhoneType(Phone.TYPE_WORK_PAGER).setSecondary(true));
+ kind.typeList.add(buildPhoneType(Phone.TYPE_ASSISTANT).setSecondary(true)
+ .setCustomColumn(Phone.LABEL));
+ kind.typeList.add(buildPhoneType(Phone.TYPE_MMS).setSecondary(true));
+
+ kind.fieldList = Lists.newArrayList();
+ kind.fieldList.add(new EditField(Phone.NUMBER, R.string.phoneLabelsGroup, FLAGS_PHONE));
+ }
+
+ return kind;
+ }
+
+ protected DataKind inflateEmail(int inflateLevel) {
+ DataKind kind = getKindForMimetype(Email.CONTENT_ITEM_TYPE);
+ if (kind == null) {
+ kind = addKind(new DataKind(Email.CONTENT_ITEM_TYPE,
+ R.string.emailLabelsGroup, android.R.drawable.sym_action_email, 15, true));
+ kind.actionHeader = new EmailActionInflater();
+ kind.actionBody = new SimpleInflater(Email.DATA);
+ }
+
+ if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
+ kind.typeColumn = Email.TYPE;
+ kind.typeList = Lists.newArrayList();
+ kind.typeList.add(buildEmailType(Email.TYPE_HOME));
+ kind.typeList.add(buildEmailType(Email.TYPE_WORK));
+ kind.typeList.add(buildEmailType(Email.TYPE_OTHER));
+ kind.typeList.add(buildEmailType(Email.TYPE_MOBILE));
+ kind.typeList.add(buildEmailType(Email.TYPE_CUSTOM).setSecondary(true).setCustomColumn(
+ Email.LABEL));
+
+ kind.fieldList = Lists.newArrayList();
+ kind.fieldList.add(new EditField(Email.DATA, R.string.emailLabelsGroup, FLAGS_EMAIL));
+ }
+
+ return kind;
+ }
+
+ protected DataKind inflateStructuredPostal(int inflateLevel) {
+ DataKind kind = getKindForMimetype(StructuredPostal.CONTENT_ITEM_TYPE);
+ if (kind == null) {
+ kind = addKind(new DataKind(StructuredPostal.CONTENT_ITEM_TYPE,
+ R.string.postalLabelsGroup, R.drawable.sym_action_map, 25, true));
+ kind.actionHeader = new PostalActionInflater();
+ kind.actionBody = new SimpleInflater(StructuredPostal.FORMATTED_ADDRESS);
+ }
+
+ if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
+ kind.typeColumn = StructuredPostal.TYPE;
+ kind.typeList = Lists.newArrayList();
+ kind.typeList.add(buildPostalType(StructuredPostal.TYPE_HOME));
+ kind.typeList.add(buildPostalType(StructuredPostal.TYPE_WORK));
+ kind.typeList.add(buildPostalType(StructuredPostal.TYPE_OTHER));
+ kind.typeList.add(buildPostalType(StructuredPostal.TYPE_CUSTOM).setSecondary(true)
+ .setCustomColumn(StructuredPostal.LABEL));
+
+ kind.fieldList = Lists.newArrayList();
+ 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));
+ }
+
+ return kind;
+ }
+
+ protected DataKind inflateIm(int inflateLevel) {
+ DataKind kind = getKindForMimetype(Im.CONTENT_ITEM_TYPE);
+ if (kind == null) {
+ kind = addKind(new DataKind(Im.CONTENT_ITEM_TYPE, R.string.imLabelsGroup,
+ android.R.drawable.sym_action_chat, 20, true));
+ kind.actionHeader = new ImActionInflater();
+ kind.actionBody = new SimpleInflater(Im.DATA);
+ }
+
+ if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
+ // NOTE: even though a traditional "type" exists, for editing
+ // purposes we're using the protocol to pick labels
+
+ kind.defaultValues = new ContentValues();
+ kind.defaultValues.put(Im.TYPE, Im.TYPE_OTHER);
+
+ kind.typeColumn = Im.PROTOCOL;
+ kind.typeList = Lists.newArrayList();
+ kind.typeList.add(buildImType(Im.PROTOCOL_AIM));
+ kind.typeList.add(buildImType(Im.PROTOCOL_MSN));
+ kind.typeList.add(buildImType(Im.PROTOCOL_YAHOO));
+ kind.typeList.add(buildImType(Im.PROTOCOL_SKYPE));
+ kind.typeList.add(buildImType(Im.PROTOCOL_QQ));
+ kind.typeList.add(buildImType(Im.PROTOCOL_GOOGLE_TALK));
+ kind.typeList.add(buildImType(Im.PROTOCOL_ICQ));
+ kind.typeList.add(buildImType(Im.PROTOCOL_JABBER));
+ kind.typeList.add(buildImType(Im.PROTOCOL_CUSTOM).setSecondary(true).setCustomColumn(
+ Im.CUSTOM_PROTOCOL));
+
+ kind.fieldList = Lists.newArrayList();
+ kind.fieldList.add(new EditField(Im.DATA, R.string.imLabelsGroup, FLAGS_EMAIL));
+ }
+
+ return kind;
+ }
+
+ protected DataKind inflateOrganization(int inflateLevel) {
+ DataKind kind = getKindForMimetype(Organization.CONTENT_ITEM_TYPE);
+ if (kind == null) {
+ kind = addKind(new DataKind(Organization.CONTENT_ITEM_TYPE,
+ R.string.organizationLabelsGroup, R.drawable.sym_action_organization, 30, true));
+ kind.actionHeader = new SimpleInflater(Organization.COMPANY);
+ kind.actionBody = new SimpleInflater(Organization.TITLE);
+ }
+
+ if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
+ kind.typeColumn = Organization.TYPE;
+ kind.typeList = Lists.newArrayList();
+ kind.typeList.add(buildOrgType(Organization.TYPE_WORK));
+ kind.typeList.add(buildOrgType(Organization.TYPE_OTHER));
+ kind.typeList.add(buildOrgType(Organization.TYPE_CUSTOM).setSecondary(true)
+ .setCustomColumn(Organization.LABEL));
+
+ kind.fieldList = Lists.newArrayList();
+ kind.fieldList.add(new EditField(Organization.COMPANY, R.string.ghostData_company,
+ FLAGS_GENERIC_NAME));
+ kind.fieldList.add(new EditField(Organization.TITLE, R.string.ghostData_title,
+ FLAGS_GENERIC_NAME));
+ }
+
+ return kind;
+ }
+
+ protected DataKind inflatePhoto(int inflateLevel) {
+ DataKind kind = getKindForMimetype(Photo.CONTENT_ITEM_TYPE);
+ if (kind == null) {
+ kind = addKind(new DataKind(Photo.CONTENT_ITEM_TYPE, -1, -1, -1, true));
+ }
+
+ if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
+ kind.fieldList = Lists.newArrayList();
+ kind.fieldList.add(new EditField(Photo.PHOTO, -1, -1));
+ }
+
+ return kind;
+ }
+
+ protected DataKind inflateNote(int inflateLevel) {
+ DataKind kind = getKindForMimetype(Note.CONTENT_ITEM_TYPE);
+ if (kind == null) {
+ kind = addKind(new DataKind(Note.CONTENT_ITEM_TYPE,
+ R.string.label_notes, R.drawable.sym_note, 110, true));
+ kind.secondary = true;
+ kind.actionHeader = new SimpleInflater(R.string.label_notes);
+ kind.actionBody = new SimpleInflater(Note.NOTE);
+ }
+
+ if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
+ kind.fieldList = Lists.newArrayList();
+ kind.fieldList.add(new EditField(Note.NOTE, R.string.label_notes, FLAGS_NOTE));
+ }
+
+ return kind;
+ }
+
+ protected DataKind inflateWebsite(int inflateLevel) {
+ DataKind kind = getKindForMimetype(Website.CONTENT_ITEM_TYPE);
+ if (kind == null) {
+ kind = addKind(new DataKind(Website.CONTENT_ITEM_TYPE,
+ R.string.websiteLabelsGroup, -1, 120, true));
+ kind.secondary = true;
+ kind.actionHeader = new SimpleInflater(R.string.websiteLabelsGroup);
+ kind.actionBody = new SimpleInflater(Website.URL);
+ }
+
+ if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
+ kind.defaultValues = new ContentValues();
+ kind.defaultValues.put(Website.TYPE, Website.TYPE_OTHER);
+
+ kind.fieldList = Lists.newArrayList();
+ kind.fieldList.add(new EditField(Website.URL, R.string.websiteLabelsGroup, FLAGS_WEBSITE));
+ }
+
+ return kind;
+ }
+
+ protected DataKind inflateEvent(int inflateLevel) {
+ DataKind kind = getKindForMimetype(Event.CONTENT_ITEM_TYPE);
+ if (kind == null) {
+ kind = addKind(new DataKind(Event.CONTENT_ITEM_TYPE,
+ R.string.eventLabelsGroup, -1, 150, false));
+ kind.secondary = true;
+ kind.actionHeader = new EventActionInflater();
+ kind.actionBody = new SimpleInflater(Event.START_DATE);
+ }
+
+ return kind;
+ }
+
+ /**
+ * Simple inflater that assumes a string resource has a "%s" that will be
+ * filled from the given column.
+ */
+ public static class SimpleInflater implements StringInflater {
+ private final int mStringRes;
+ private final String mColumnName;
+
+ public SimpleInflater(int stringRes) {
+ this(stringRes, null);
+ }
+
+ public SimpleInflater(String columnName) {
+ this(-1, columnName);
+ }
+
+ public SimpleInflater(int stringRes, String columnName) {
+ mStringRes = stringRes;
+ mColumnName = columnName;
+ }
+
+ public CharSequence inflateUsing(Context context, Cursor cursor) {
+ final int index = mColumnName != null ? cursor.getColumnIndex(mColumnName) : -1;
+ final boolean validString = mStringRes > 0;
+ final boolean validColumn = index != -1;
+
+ final CharSequence stringValue = validString ? context.getText(mStringRes) : null;
+ final CharSequence columnValue = validColumn ? cursor.getString(index) : null;
+
+ if (validString && validColumn) {
+ return String.format(stringValue.toString(), columnValue);
+ } else if (validString) {
+ return stringValue;
+ } else if (validColumn) {
+ return columnValue;
+ } else {
+ return null;
+ }
+ }
+
+ public CharSequence inflateUsing(Context context, ContentValues values) {
+ final boolean validColumn = values.containsKey(mColumnName);
+ final boolean validString = mStringRes > 0;
+
+ final CharSequence stringValue = validString ? context.getText(mStringRes) : null;
+ final CharSequence columnValue = validColumn ? values.getAsString(mColumnName) : null;
+
+ if (validString && validColumn) {
+ return String.format(stringValue.toString(), columnValue);
+ } else if (validString) {
+ return stringValue;
+ } else if (validColumn) {
+ return columnValue;
+ } else {
+ return null;
+ }
+ }
+ }
+
+ public static abstract class CommonInflater implements StringInflater {
+ protected abstract int getTypeLabelResource(Integer type);
+
+ protected boolean isCustom(Integer type) {
+ return type == BaseTypes.TYPE_CUSTOM;
+ }
+
+ protected String getTypeColumn() {
+ return Phone.TYPE;
+ }
+
+ protected String getLabelColumn() {
+ return Phone.LABEL;
+ }
+
+ protected CharSequence getTypeLabel(Resources res, Integer type, CharSequence label) {
+ final int labelRes = getTypeLabelResource(type);
+ if (type == null) {
+ return res.getText(labelRes);
+ } else if (isCustom(type)) {
+ return res.getString(labelRes, label == null ? "" : label);
+ } else {
+ return res.getText(labelRes);
+ }
+ }
+
+ public CharSequence inflateUsing(Context context, Cursor cursor) {
+ final Integer type = cursor.getInt(cursor.getColumnIndex(getTypeColumn()));
+ final String label = cursor.getString(cursor.getColumnIndex(getLabelColumn()));
+ return getTypeLabel(context.getResources(), type, label);
+ }
+
+ public CharSequence inflateUsing(Context context, ContentValues values) {
+ final Integer type = values.getAsInteger(getTypeColumn());
+ final String label = values.getAsString(getLabelColumn());
+ return getTypeLabel(context.getResources(), type, label);
+ }
+ }
+
+ public static class PhoneActionInflater extends CommonInflater {
+ @Override
+ protected boolean isCustom(Integer type) {
+ return type == Phone.TYPE_CUSTOM || type == Phone.TYPE_ASSISTANT;
+ }
+
+ @Override
+ protected int getTypeLabelResource(Integer type) {
+ if (type == null) return R.string.call_other;
+ switch (type) {
+ case Phone.TYPE_HOME: return R.string.call_home;
+ case Phone.TYPE_MOBILE: return R.string.call_mobile;
+ case Phone.TYPE_WORK: return R.string.call_work;
+ case Phone.TYPE_FAX_WORK: return R.string.call_fax_work;
+ case Phone.TYPE_FAX_HOME: return R.string.call_fax_home;
+ case Phone.TYPE_PAGER: return R.string.call_pager;
+ case Phone.TYPE_OTHER: return R.string.call_other;
+ case Phone.TYPE_CALLBACK: return R.string.call_callback;
+ case Phone.TYPE_CAR: return R.string.call_car;
+ case Phone.TYPE_COMPANY_MAIN: return R.string.call_company_main;
+ case Phone.TYPE_ISDN: return R.string.call_isdn;
+ case Phone.TYPE_MAIN: return R.string.call_main;
+ case Phone.TYPE_OTHER_FAX: return R.string.call_other_fax;
+ case Phone.TYPE_RADIO: return R.string.call_radio;
+ case Phone.TYPE_TELEX: return R.string.call_telex;
+ case Phone.TYPE_TTY_TDD: return R.string.call_tty_tdd;
+ case Phone.TYPE_WORK_MOBILE: return R.string.call_work_mobile;
+ case Phone.TYPE_WORK_PAGER: return R.string.call_work_pager;
+ case Phone.TYPE_ASSISTANT: return R.string.call_assistant;
+ case Phone.TYPE_MMS: return R.string.call_mms;
+ default: return R.string.call_custom;
+ }
+ }
+ }
+
+ public static class PhoneActionAltInflater extends CommonInflater {
+ @Override
+ protected boolean isCustom(Integer type) {
+ return (type == Phone.TYPE_CUSTOM || type == Phone.TYPE_ASSISTANT);
+ }
+
+ @Override
+ protected int getTypeLabelResource(Integer type) {
+ if (type == null) return R.string.sms_other;
+ switch (type) {
+ case Phone.TYPE_HOME: return R.string.sms_home;
+ case Phone.TYPE_MOBILE: return R.string.sms_mobile;
+ case Phone.TYPE_WORK: return R.string.sms_work;
+ case Phone.TYPE_FAX_WORK: return R.string.sms_fax_work;
+ case Phone.TYPE_FAX_HOME: return R.string.sms_fax_home;
+ case Phone.TYPE_PAGER: return R.string.sms_pager;
+ case Phone.TYPE_OTHER: return R.string.sms_other;
+ case Phone.TYPE_CALLBACK: return R.string.sms_callback;
+ case Phone.TYPE_CAR: return R.string.sms_car;
+ case Phone.TYPE_COMPANY_MAIN: return R.string.sms_company_main;
+ case Phone.TYPE_ISDN: return R.string.sms_isdn;
+ case Phone.TYPE_MAIN: return R.string.sms_main;
+ case Phone.TYPE_OTHER_FAX: return R.string.sms_other_fax;
+ case Phone.TYPE_RADIO: return R.string.sms_radio;
+ case Phone.TYPE_TELEX: return R.string.sms_telex;
+ case Phone.TYPE_TTY_TDD: return R.string.sms_tty_tdd;
+ case Phone.TYPE_WORK_MOBILE: return R.string.sms_work_mobile;
+ case Phone.TYPE_WORK_PAGER: return R.string.sms_work_pager;
+ case Phone.TYPE_ASSISTANT: return R.string.sms_assistant;
+ case Phone.TYPE_MMS: return R.string.sms_mms;
+ default: return R.string.sms_custom;
+ }
+ }
+ }
+
+ public static class EmailActionInflater extends CommonInflater {
+ @Override
+ protected int getTypeLabelResource(Integer type) {
+ if (type == null) return R.string.email;
+ switch (type) {
+ case Email.TYPE_HOME: return R.string.email_home;
+ case Email.TYPE_WORK: return R.string.email_work;
+ case Email.TYPE_OTHER: return R.string.email_other;
+ case Email.TYPE_MOBILE: return R.string.email_mobile;
+ default: return R.string.email_custom;
+ }
+ }
+ }
+
+ public static class EventActionInflater extends CommonInflater {
+ @Override
+ protected int getTypeLabelResource(Integer type) {
+ return Event.getTypeResource(type);
+ }
+ }
+
+ public static class PostalActionInflater extends CommonInflater {
+ @Override
+ protected int getTypeLabelResource(Integer type) {
+ if (type == null) return R.string.map_other;
+ switch (type) {
+ case StructuredPostal.TYPE_HOME: return R.string.map_home;
+ case StructuredPostal.TYPE_WORK: return R.string.map_work;
+ case StructuredPostal.TYPE_OTHER: return R.string.map_other;
+ default: return R.string.map_custom;
+ }
+ }
+ }
+
+ public static class ImActionInflater extends CommonInflater {
+ @Override
+ protected String getTypeColumn() {
+ return Im.PROTOCOL;
+ }
+
+ @Override
+ protected String getLabelColumn() {
+ return Im.CUSTOM_PROTOCOL;
+ }
+
+ @Override
+ protected int getTypeLabelResource(Integer type) {
+ if (type == null) return R.string.chat;
+ switch (type) {
+ case Im.PROTOCOL_AIM: return R.string.chat_aim;
+ case Im.PROTOCOL_MSN: return R.string.chat_msn;
+ case Im.PROTOCOL_YAHOO: return R.string.chat_yahoo;
+ case Im.PROTOCOL_SKYPE: return R.string.chat_skype;
+ case Im.PROTOCOL_QQ: return R.string.chat_qq;
+ case Im.PROTOCOL_GOOGLE_TALK: return R.string.chat_gtalk;
+ case Im.PROTOCOL_ICQ: return R.string.chat_icq;
+ case Im.PROTOCOL_JABBER: return R.string.chat_jabber;
+ case Im.PROTOCOL_NETMEETING: return R.string.chat;
+ default: return R.string.chat;
+ }
+ }
+ }
+
+ @Override
+ public int getHeaderColor(Context context) {
+ return 0xff7f93bc;
+ }
+
+ @Override
+ public int getSideBarColor(Context context) {
+ return 0xffbdc7b8;
+ }
+}
diff --git a/src/com/android/contacts/model/GoogleSource.java b/src/com/android/contacts/model/GoogleSource.java
new file mode 100644
index 0000000..a4b4cb2
--- /dev/null
+++ b/src/com/android/contacts/model/GoogleSource.java
@@ -0,0 +1,273 @@
+/*
+ * 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 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;
+
+public class GoogleSource extends FallbackSource {
+ public static final String ACCOUNT_TYPE = "com.google";
+ public GoogleSource(String resPackageName) {
+ this.accountType = ACCOUNT_TYPE;
+ this.resPackageName = null;
+ this.summaryResPackageName = resPackageName;
+ }
+
+ @Override
+ protected void inflate(Context context, int inflateLevel) {
+
+ inflateStructuredName(inflateLevel);
+ inflateNickname(inflateLevel);
+ inflatePhone(inflateLevel);
+ inflateEmail(inflateLevel);
+ inflateStructuredPostal(inflateLevel);
+ inflateIm(inflateLevel);
+ inflateOrganization(inflateLevel);
+ inflatePhoto(inflateLevel);
+ inflateNote(inflateLevel);
+ inflateWebsite(inflateLevel);
+ inflateEvent(inflateLevel);
+
+ // TODO: GOOGLE: GROUPMEMBERSHIP
+
+ setInflatedLevel(inflateLevel);
+
+ }
+
+ @Override
+ protected DataKind inflateStructuredName(int inflateLevel) {
+ return super.inflateStructuredName(inflateLevel);
+ }
+
+ @Override
+ protected DataKind inflateNickname(int inflateLevel) {
+ return super.inflateNickname(inflateLevel);
+ }
+
+ @Override
+ protected DataKind inflatePhone(int inflateLevel) {
+ final DataKind kind = super.inflatePhone(ContactsSource.LEVEL_MIMETYPES);
+
+ if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
+ kind.typeColumn = Phone.TYPE;
+ kind.typeList = Lists.newArrayList();
+ kind.typeList.add(buildPhoneType(Phone.TYPE_HOME));
+ kind.typeList.add(buildPhoneType(Phone.TYPE_MOBILE));
+ kind.typeList.add(buildPhoneType(Phone.TYPE_WORK));
+ kind.typeList.add(buildPhoneType(Phone.TYPE_FAX_WORK).setSecondary(true));
+ kind.typeList.add(buildPhoneType(Phone.TYPE_FAX_HOME).setSecondary(true));
+ kind.typeList.add(buildPhoneType(Phone.TYPE_PAGER).setSecondary(true));
+ kind.typeList.add(buildPhoneType(Phone.TYPE_OTHER));
+ kind.typeList.add(buildPhoneType(Phone.TYPE_CUSTOM).setSecondary(true).setCustomColumn(
+ Phone.LABEL));
+
+ kind.fieldList = Lists.newArrayList();
+ kind.fieldList.add(new EditField(Phone.NUMBER, R.string.phoneLabelsGroup, FLAGS_PHONE));
+ }
+
+ return kind;
+ }
+
+ @Override
+ protected DataKind inflateEmail(int inflateLevel) {
+ final DataKind kind = super.inflateEmail(ContactsSource.LEVEL_MIMETYPES);
+
+ if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
+ kind.typeColumn = Email.TYPE;
+ kind.typeList = Lists.newArrayList();
+ kind.typeList.add(buildEmailType(Email.TYPE_HOME));
+ kind.typeList.add(buildEmailType(Email.TYPE_WORK));
+ kind.typeList.add(buildEmailType(Email.TYPE_OTHER));
+ kind.typeList.add(buildEmailType(Email.TYPE_CUSTOM).setSecondary(true).setCustomColumn(
+ Email.LABEL));
+
+ kind.fieldList = Lists.newArrayList();
+ kind.fieldList.add(new EditField(Email.DATA, R.string.emailLabelsGroup, FLAGS_EMAIL));
+ }
+
+ return kind;
+ }
+
+ @Override
+ protected DataKind inflateStructuredPostal(int inflateLevel) {
+ return super.inflateStructuredPostal(inflateLevel);
+ }
+
+ @Override
+ protected DataKind inflateIm(int inflateLevel) {
+ return super.inflateIm(inflateLevel);
+ }
+
+ @Override
+ protected DataKind inflateOrganization(int inflateLevel) {
+ return super.inflateOrganization(inflateLevel);
+ }
+
+ @Override
+ protected DataKind inflatePhoto(int inflateLevel) {
+ return super.inflatePhoto(inflateLevel);
+ }
+
+ @Override
+ protected DataKind inflateNote(int inflateLevel) {
+ return super.inflateNote(inflateLevel);
+ }
+
+ @Override
+ protected DataKind inflateWebsite(int inflateLevel) {
+ return super.inflateWebsite(inflateLevel);
+ }
+
+ // TODO: this should come from resource in the future
+ // Note that frameworks/base/core/java/android/pim/vcard/ContactStruct.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(Groups.TITLE + "=?",
+ new String[] { GOOGLE_MY_CONTACTS_GROUP })
+ .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;
+ }
+
+ @Override
+ public int getSideBarColor(Context context) {
+ return 0xff5bb4b4;
+ }
+}
diff --git a/src/com/android/contacts/model/Sources.java b/src/com/android/contacts/model/Sources.java
new file mode 100644
index 0000000..f664fb1
--- /dev/null
+++ b/src/com/android/contacts/model/Sources.java
@@ -0,0 +1,278 @@
+/*
+ * 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 com.android.contacts.model.ContactsSource.DataKind;
+import com.google.android.collect.Lists;
+import com.google.android.collect.Maps;
+import com.google.android.collect.Sets;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.accounts.AuthenticatorDescription;
+import android.accounts.OnAccountsUpdateListener;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.IContentService;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SyncAdapterType;
+import android.content.pm.PackageManager;
+import android.os.RemoteException;
+import android.provider.ContactsContract;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.lang.ref.SoftReference;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+
+/**
+ * Singleton holder for all parsed {@link ContactsSource} available on the
+ * system, typically filled through {@link PackageManager} queries.
+ */
+public class Sources extends BroadcastReceiver implements OnAccountsUpdateListener {
+ private static final String TAG = "Sources";
+
+ private Context mApplicationContext;
+ private AccountManager mAccountManager;
+
+ private ContactsSource mFallbackSource = null;
+
+ private HashMap<String, ContactsSource> mSources = Maps.newHashMap();
+ private HashSet<String> mKnownPackages = Sets.newHashSet();
+
+ private static SoftReference<Sources> sInstance = null;
+
+ /**
+ * 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.
+ */
+ public static synchronized Sources getInstance(Context context) {
+ Sources sources = sInstance == null ? null : sInstance.get();
+ if (sources == null) {
+ sources = new Sources(context);
+ sInstance = new SoftReference<Sources>(sources);
+ }
+ return sources;
+ }
+
+ /**
+ * Internal constructor that only performs initial parsing.
+ */
+ private Sources(Context context) {
+ mApplicationContext = context.getApplicationContext();
+ mAccountManager = AccountManager.get(mApplicationContext);
+
+ // Create fallback contacts source for on-phone contacts
+ mFallbackSource = new FallbackSource();
+
+ queryAccounts();
+
+ // Request updates when packages or accounts change
+ final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
+ filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+ filter.addDataScheme("package");
+
+ mApplicationContext.registerReceiver(this, filter);
+ mAccountManager.addOnAccountsUpdatedListener(this, null, false);
+ }
+
+ /** @hide exposed for unit tests */
+ public Sources(ContactsSource... sources) {
+ for (ContactsSource source : sources) {
+ addSource(source);
+ }
+ }
+
+ protected void addSource(ContactsSource source) {
+ mSources.put(source.accountType, source);
+ mKnownPackages.add(source.resPackageName);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ final String packageName = intent.getData().getSchemeSpecificPart();
+
+ if (Intent.ACTION_PACKAGE_REMOVED.equals(action)
+ || Intent.ACTION_PACKAGE_ADDED.equals(action)
+ || Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
+ final boolean knownPackage = mKnownPackages.contains(packageName);
+ if (knownPackage) {
+ // Invalidate cache of existing source
+ invalidateCache(packageName);
+ } else {
+ // Unknown source, so reload from scratch
+ queryAccounts();
+ }
+ }
+ }
+
+ protected void invalidateCache(String packageName) {
+ for (ContactsSource source : mSources.values()) {
+ if (TextUtils.equals(packageName, source.resPackageName)) {
+ // Invalidate any cache for the changed package
+ source.invalidateCache();
+ }
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void onAccountsUpdated(Account[] accounts) {
+ // Refresh to catch any changed accounts
+ queryAccounts();
+ }
+
+ /**
+ * Blocking call to load all {@link AuthenticatorDescription} known by the
+ * {@link AccountManager} on the system.
+ */
+ protected synchronized void queryAccounts() {
+ mSources.clear();
+ mKnownPackages.clear();
+
+ final AccountManager am = mAccountManager;
+ final IContentService cs = ContentResolver.getContentService();
+
+ try {
+ final SyncAdapterType[] syncs = cs.getSyncAdapterTypes();
+ final AuthenticatorDescription[] auths = am.getAuthenticatorTypes();
+
+ for (SyncAdapterType sync : syncs) {
+ if (!ContactsContract.AUTHORITY.equals(sync.authority)) {
+ // Skip sync adapters that don't provide contact data.
+ continue;
+ }
+
+ // Look for the formatting details provided by each sync
+ // adapter, using the authenticator to find general resources.
+ final String accountType = sync.accountType;
+ final AuthenticatorDescription auth = findAuthenticator(auths, accountType);
+
+ ContactsSource source;
+ if (GoogleSource.ACCOUNT_TYPE.equals(accountType)) {
+ source = new GoogleSource(auth.packageName);
+ } else if (ExchangeSource.ACCOUNT_TYPE.equals(accountType)) {
+ source = new ExchangeSource(auth.packageName);
+ } else {
+ // TODO: use syncadapter package instead, since it provides resources
+ Log.d(TAG, "Creating external source for type=" + accountType
+ + ", packageName=" + auth.packageName);
+ source = new ExternalSource(auth.packageName);
+ source.readOnly = !sync.supportsUploading();
+ }
+
+ source.accountType = auth.type;
+ source.titleRes = auth.labelId;
+ source.iconRes = auth.iconId;
+
+ addSource(source);
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "Problem loading accounts: " + e.toString());
+ }
+ }
+
+ /**
+ * Find a specific {@link AuthenticatorDescription} in the provided list
+ * that matches the given account type.
+ */
+ protected static AuthenticatorDescription findAuthenticator(AuthenticatorDescription[] auths,
+ String accountType) {
+ for (AuthenticatorDescription auth : auths) {
+ if (accountType.equals(auth.type)) {
+ return auth;
+ }
+ }
+ throw new IllegalStateException("Couldn't find authenticator for specific account type");
+ }
+
+ /**
+ * Return list of all known, writable {@link ContactsSource}. Sources
+ * returned may require inflation before they can be used.
+ */
+ public ArrayList<Account> getAccounts(boolean writableOnly) {
+ final AccountManager am = mAccountManager;
+ final Account[] accounts = am.getAccounts();
+ final ArrayList<Account> matching = Lists.newArrayList();
+
+ for (Account account : accounts) {
+ // Ensure we have details loaded for each account
+ final ContactsSource source = getInflatedSource(account.type,
+ ContactsSource.LEVEL_SUMMARY);
+ final boolean hasContacts = source != null;
+ final boolean matchesWritable = (!writableOnly || (writableOnly && !source.readOnly));
+ if (hasContacts && matchesWritable) {
+ matching.add(account);
+ }
+ }
+ return matching;
+ }
+
+ /**
+ * Find the best {@link DataKind} matching the requested
+ * {@link ContactsSource#accountType} and {@link DataKind#mimeType}. If no
+ * direct match found, we try searching {@link #mFallbackSource}.
+ */
+ public DataKind getKindOrFallback(String accountType, String mimeType, Context context,
+ int inflateLevel) {
+ DataKind kind = null;
+
+ // Try finding source and kind matching request
+ final ContactsSource source = mSources.get(accountType);
+ if (source != null) {
+ source.ensureInflated(context, inflateLevel);
+ kind = source.getKindForMimetype(mimeType);
+ }
+
+ if (kind == null) {
+ // Nothing found, so try fallback as last resort
+ mFallbackSource.ensureInflated(context, inflateLevel);
+ kind = mFallbackSource.getKindForMimetype(mimeType);
+ }
+
+ if (kind == null) {
+ Log.w(TAG, "Unknown type=" + accountType + ", mime=" + mimeType);
+ }
+
+ return kind;
+ }
+
+ /**
+ * Return {@link ContactsSource} for the given account type.
+ */
+ public ContactsSource getInflatedSource(String accountType, int inflateLevel) {
+ // Try finding specific source, otherwise use fallback
+ ContactsSource source = mSources.get(accountType);
+ if (source == null) source = mFallbackSource;
+
+ if (source.isInflated(inflateLevel)) {
+ // Already inflated, so return directly
+ return source;
+ } else {
+ // Not inflated, but requested that we force-inflate
+ source.ensureInflated(mApplicationContext, inflateLevel);
+ return source;
+ }
+ }
+}
diff --git a/src/com/android/contacts/ui/DisplayGroupsActivity.java b/src/com/android/contacts/ui/DisplayGroupsActivity.java
new file mode 100644
index 0000000..ce68dcb
--- /dev/null
+++ b/src/com/android/contacts/ui/DisplayGroupsActivity.java
@@ -0,0 +1,860 @@
+/*
+ * 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.R;
+import com.android.contacts.model.ContactsSource;
+import com.android.contacts.model.GoogleSource;
+import com.android.contacts.model.Sources;
+import com.android.contacts.model.EntityDelta.ValuesDelta;
+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.ExpandableListActivity;
+import android.app.ProgressDialog;
+import android.content.ContentProviderOperation;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.EntityIterator;
+import android.content.Intent;
+import android.content.OperationApplicationException;
+import android.content.SharedPreferences;
+import android.content.ContentProviderOperation.Builder;
+import android.content.SharedPreferences.Editor;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.preference.PreferenceManager;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.Groups;
+import android.provider.ContactsContract.Settings;
+import android.util.Log;
+import android.view.ContextMenu;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.MenuItem.OnMenuItemClickListener;
+import android.widget.AdapterView;
+import android.widget.BaseExpandableListAdapter;
+import android.widget.CheckBox;
+import android.widget.ExpandableListAdapter;
+import android.widget.ExpandableListView;
+import android.widget.TextView;
+import android.widget.ExpandableListView.ExpandableListContextMenuInfo;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+
+/**
+ * Shows a list of all available {@link Groups} available, letting the user
+ * select which ones they want to be visible.
+ */
+public final class DisplayGroupsActivity extends ExpandableListActivity implements
+ AdapterView.OnItemClickListener, View.OnClickListener {
+ private static final String TAG = "DisplayGroupsActivity";
+
+ public interface Prefs {
+ public static final String DISPLAY_ONLY_PHONES = "only_phones";
+ public static final boolean DISPLAY_ONLY_PHONES_DEFAULT = false;
+
+ }
+
+ private ExpandableListView mList;
+ private DisplayAdapter mAdapter;
+
+ private SharedPreferences mPrefs;
+
+ private CheckBox mDisplayPhones;
+
+ private View mHeaderPhones;
+ private View mHeaderSeparator;
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ setContentView(R.layout.act_display_groups);
+
+ mList = getExpandableListView();
+ mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
+
+ final LayoutInflater inflater = getLayoutInflater();
+
+ // Add the "Only contacts with phones" header modifier.
+ mHeaderPhones = inflater.inflate(R.layout.display_header, mList, false);
+ mHeaderPhones.setId(R.id.header_phones);
+ mDisplayPhones = (CheckBox) mHeaderPhones.findViewById(android.R.id.checkbox);
+ mDisplayPhones.setChecked(mPrefs.getBoolean(Prefs.DISPLAY_ONLY_PHONES,
+ Prefs.DISPLAY_ONLY_PHONES_DEFAULT));
+ {
+ final TextView text1 = (TextView)mHeaderPhones.findViewById(android.R.id.text1);
+ final TextView text2 = (TextView)mHeaderPhones.findViewById(android.R.id.text2);
+ text1.setText(R.string.showFilterPhones);
+ text2.setText(R.string.showFilterPhonesDescrip);
+ }
+ mList.addHeaderView(mHeaderPhones, null, true);
+
+ // Add the separator before showing the detailed group list.
+ mHeaderSeparator = inflater.inflate(R.layout.list_separator, mList, false);
+ {
+ final TextView text1 = (TextView)mHeaderSeparator;
+ text1.setText(R.string.headerContactGroups);
+ }
+ mList.addHeaderView(mHeaderSeparator, null, false);
+
+ findViewById(R.id.btn_done).setOnClickListener(this);
+ findViewById(R.id.btn_discard).setOnClickListener(this);
+
+ // Catch clicks on the header views
+ mList.setOnItemClickListener(this);
+ mList.setOnCreateContextMenuListener(this);
+
+ // Start background query to find account details
+ new QueryGroupsTask(this).execute();
+ }
+
+ /**
+ * Background operation to build set of {@link AccountDisplay} for each
+ * {@link Sources#getAccounts(boolean)} that provides groups.
+ */
+ private static class QueryGroupsTask extends
+ WeakAsyncTask<Void, Void, AccountSet, DisplayGroupsActivity> {
+ public QueryGroupsTask(DisplayGroupsActivity target) {
+ super(target);
+ }
+
+ @Override
+ protected AccountSet doInBackground(DisplayGroupsActivity target,
+ Void... params) {
+ final Context context = target;
+ final Sources sources = Sources.getInstance(context);
+ final ContentResolver resolver = context.getContentResolver();
+
+ // Inflate groups entry for each account
+ final AccountSet accounts = new AccountSet();
+ for (Account account : sources.getAccounts(false)) {
+ accounts.add(new AccountDisplay(resolver, account.name, account.type));
+ }
+
+ return accounts;
+ }
+
+ @Override
+ protected void onPostExecute(DisplayGroupsActivity target, AccountSet result) {
+ // Build adapter to show available groups
+ final Context context = target;
+ final DisplayAdapter adapter = new DisplayAdapter(context, result);
+ target.setListAdapter(adapter);
+ }
+ }
+
+ public void setListAdapter(DisplayAdapter adapter) {
+ mAdapter = adapter;
+ mAdapter.setChildDescripWithPhones(mDisplayPhones.isChecked());
+ super.setListAdapter(mAdapter);
+ }
+
+ private static final int DEFAULT_SHOULD_SYNC = 1;
+ private static final int DEFAULT_VISIBLE = 0;
+
+ /**
+ * Entry holding any changes to {@link Groups} or {@link Settings} rows,
+ * such as {@link Groups#SHOULD_SYNC} or {@link Groups#GROUP_VISIBLE}.
+ */
+ protected static class GroupDelta extends ValuesDelta {
+ private boolean mUngrouped = false;
+ private boolean mAccountHasGroups;
+
+ private GroupDelta() {
+ super();
+ }
+
+ /**
+ * Build {@link GroupDelta} from the {@link Settings} row for the given
+ * {@link Settings#ACCOUNT_NAME} and {@link Settings#ACCOUNT_TYPE}.
+ */
+ public static GroupDelta fromSettings(ContentResolver resolver, String accountName,
+ String accountType, boolean accountHasGroups) {
+ final Uri settingsUri = Settings.CONTENT_URI.buildUpon()
+ .appendQueryParameter(Settings.ACCOUNT_NAME, accountName)
+ .appendQueryParameter(Settings.ACCOUNT_TYPE, accountType).build();
+ final Cursor cursor = resolver.query(settingsUri, new String[] {
+ Settings.SHOULD_SYNC, Settings.UNGROUPED_VISIBLE
+ }, null, null, null);
+
+ try {
+ final ContentValues values = new ContentValues();
+ values.put(Settings.ACCOUNT_NAME, accountName);
+ values.put(Settings.ACCOUNT_TYPE, accountType);
+
+ if (cursor != null && cursor.moveToFirst()) {
+ // Read existing values when present
+ values.put(Settings.SHOULD_SYNC, cursor.getInt(0));
+ values.put(Settings.UNGROUPED_VISIBLE, cursor.getInt(1));
+ return fromBefore(values).setUngrouped(accountHasGroups);
+ } else {
+ // Nothing found, so treat as create
+ values.put(Settings.SHOULD_SYNC, DEFAULT_SHOULD_SYNC);
+ values.put(Settings.UNGROUPED_VISIBLE, DEFAULT_VISIBLE);
+ return fromAfter(values).setUngrouped(accountHasGroups);
+ }
+ } finally {
+ if (cursor != null) cursor.close();
+ }
+ }
+
+ public static GroupDelta fromBefore(ContentValues before) {
+ final GroupDelta entry = new GroupDelta();
+ entry.mBefore = before;
+ entry.mAfter = new ContentValues();
+ return entry;
+ }
+
+ public static GroupDelta fromAfter(ContentValues after) {
+ final GroupDelta entry = new GroupDelta();
+ entry.mBefore = null;
+ entry.mAfter = after;
+ return entry;
+ }
+
+ protected GroupDelta setUngrouped(boolean accountHasGroups) {
+ mUngrouped = true;
+ mAccountHasGroups = accountHasGroups;
+ return this;
+ }
+
+ @Override
+ public boolean beforeExists() {
+ return mBefore != null;
+ }
+
+ public boolean getShouldSync() {
+ return getAsInteger(mUngrouped ? Settings.SHOULD_SYNC : Groups.SHOULD_SYNC,
+ DEFAULT_SHOULD_SYNC) != 0;
+ }
+
+ public boolean getVisible() {
+ return getAsInteger(mUngrouped ? Settings.UNGROUPED_VISIBLE : Groups.GROUP_VISIBLE,
+ DEFAULT_VISIBLE) != 0;
+ }
+
+ public void putShouldSync(boolean shouldSync) {
+ put(mUngrouped ? Settings.SHOULD_SYNC : Groups.SHOULD_SYNC, shouldSync ? 1 : 0);
+ }
+
+ public void putVisible(boolean visible) {
+ put(mUngrouped ? Settings.UNGROUPED_VISIBLE : Groups.GROUP_VISIBLE, visible ? 1 : 0);
+ }
+
+ public CharSequence getTitle(Context context) {
+ if (mUngrouped) {
+ if (mAccountHasGroups) {
+ return context.getText(R.string.display_ungrouped);
+ } else {
+ return context.getText(R.string.display_all_contacts);
+ }
+ } else {
+ final Integer titleRes = getAsInteger(Groups.TITLE_RES);
+ if (titleRes != null) {
+ final String packageName = getAsString(Groups.RES_PACKAGE);
+ return context.getPackageManager().getText(packageName, titleRes, null);
+ } else {
+ return getAsString(Groups.TITLE);
+ }
+ }
+ }
+
+ /**
+ * Build a possible {@link ContentProviderOperation} to persist any
+ * changes to the {@link Groups} or {@link Settings} row described by
+ * this {@link GroupDelta}.
+ */
+ public ContentProviderOperation buildDiff() {
+ if (isNoop()) {
+ return null;
+ } else if (isUpdate()) {
+ // When has changes and "before" exists, then "update"
+ final Builder builder = ContentProviderOperation
+ .newUpdate(mUngrouped ? Settings.CONTENT_URI : addCallerIsSyncAdapterParameter(Groups.CONTENT_URI));
+ if (mUngrouped) {
+ builder.withSelection(Settings.ACCOUNT_NAME + "=? AND " + Settings.ACCOUNT_TYPE
+ + "=?", new String[] {
+ this.getAsString(Settings.ACCOUNT_NAME),
+ this.getAsString(Settings.ACCOUNT_TYPE)
+ });
+ } else {
+ builder.withSelection(Groups._ID + "=" + this.getId(), null);
+ }
+ builder.withValues(mAfter);
+ return builder.build();
+ } else if (isInsert() && mUngrouped) {
+ // Only allow inserts for Settings
+ mAfter.remove(mIdColumn);
+ final Builder builder = ContentProviderOperation.newInsert(Settings.CONTENT_URI);
+ builder.withValues(mAfter);
+ return builder.build();
+ } else {
+ throw new IllegalStateException("Unexpected delete or insert");
+ }
+ }
+ }
+
+ private static Uri addCallerIsSyncAdapterParameter(Uri uri) {
+ return uri.buildUpon()
+ .appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true")
+ .build();
+ }
+
+ /**
+ * {@link Comparator} to sort by {@link Groups#_ID}.
+ */
+ private static Comparator<GroupDelta> sIdComparator = new Comparator<GroupDelta>() {
+ public int compare(GroupDelta object1, GroupDelta object2) {
+ return object1.getViewId() - object2.getViewId();
+ }
+ };
+
+ /**
+ * Set of all {@link AccountDisplay} entries, one for each source.
+ */
+ protected static class AccountSet extends ArrayList<AccountDisplay> {
+ public ArrayList<ContentProviderOperation> buildDiff() {
+ final ArrayList<ContentProviderOperation> diff = Lists.newArrayList();
+ for (AccountDisplay account : this) {
+ account.buildDiff(diff);
+ }
+ return diff;
+ }
+ }
+
+ /**
+ * {@link GroupDelta} details for a single {@link Account}, usually shown as
+ * children under a single expandable group.
+ */
+ protected static class AccountDisplay {
+ public String mName;
+ public String mType;
+
+ public GroupDelta mUngrouped;
+ public ArrayList<GroupDelta> mSyncedGroups = Lists.newArrayList();
+ public ArrayList<GroupDelta> mUnsyncedGroups = Lists.newArrayList();
+
+ /**
+ * Build an {@link AccountDisplay} covering all {@link Groups} under the
+ * given {@link Account}.
+ */
+ public AccountDisplay(ContentResolver resolver, String accountName, String accountType) {
+ mName = accountName;
+ mType = accountType;
+
+ boolean hasGroups = false;
+
+ final Uri groupsUri = Groups.CONTENT_URI.buildUpon()
+ .appendQueryParameter(Groups.ACCOUNT_NAME, accountName)
+ .appendQueryParameter(Groups.ACCOUNT_TYPE, accountType).build();
+ EntityIterator iterator = null;
+ try {
+ // Create entries for each known group
+ iterator = resolver.queryEntities(groupsUri, null, null, null);
+ while (iterator.hasNext()) {
+ final ContentValues values = iterator.next().getEntityValues();
+ final GroupDelta group = GroupDelta.fromBefore(values);
+ addGroup(group);
+ hasGroups = true;
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "Problem reading groups: " + e.toString());
+ } finally {
+ if (iterator != null) iterator.close();
+ }
+
+ // Create single entry handling ungrouped status
+ mUngrouped = GroupDelta.fromSettings(resolver, accountName, accountType, hasGroups);
+ addGroup(mUngrouped);
+ }
+
+ /**
+ * Add the given {@link GroupDelta} internally, filing based on its
+ * {@link GroupDelta#getShouldSync()} status.
+ */
+ private void addGroup(GroupDelta group) {
+ if (group.getShouldSync()) {
+ mSyncedGroups.add(group);
+ } else {
+ mUnsyncedGroups.add(group);
+ }
+ }
+
+ /**
+ * Set the {@link GroupDelta#putShouldSync(boolean)} value for all
+ * children {@link GroupDelta} rows.
+ */
+ public void setShouldSync(boolean shouldSync) {
+ final Iterator<GroupDelta> oppositeChildren = shouldSync ?
+ mUnsyncedGroups.iterator() : mSyncedGroups.iterator();
+ while (oppositeChildren.hasNext()) {
+ final GroupDelta child = oppositeChildren.next();
+ setShouldSync(child, shouldSync, false);
+ oppositeChildren.remove();
+ }
+ }
+
+ public void setShouldSync(GroupDelta child, boolean shouldSync) {
+ setShouldSync(child, shouldSync, true);
+ }
+
+ /**
+ * Set {@link GroupDelta#putShouldSync(boolean)}, and file internally
+ * based on updated state.
+ */
+ public void setShouldSync(GroupDelta child, boolean shouldSync, boolean attemptRemove) {
+ child.putShouldSync(shouldSync);
+ if (shouldSync) {
+ if (attemptRemove) {
+ mUnsyncedGroups.remove(child);
+ }
+ mSyncedGroups.add(child);
+ Collections.sort(mSyncedGroups, sIdComparator);
+ } else {
+ if (attemptRemove) {
+ mSyncedGroups.remove(child);
+ }
+ mUnsyncedGroups.add(child);
+ }
+ }
+
+ /**
+ * Build set of {@link ContentProviderOperation} to persist any user
+ * changes to {@link GroupDelta} rows under this {@link Account}.
+ */
+ public void buildDiff(ArrayList<ContentProviderOperation> diff) {
+ for (GroupDelta group : mSyncedGroups) {
+ final ContentProviderOperation oper = group.buildDiff();
+ if (oper != null) diff.add(oper);
+ }
+ for (GroupDelta group : mUnsyncedGroups) {
+ final ContentProviderOperation oper = group.buildDiff();
+ if (oper != null) diff.add(oper);
+ }
+ }
+ }
+
+ /**
+ * {@link ExpandableListAdapter} that shows {@link GroupDelta} settings,
+ * grouped by {@link Account} source. Shows footer row when any groups are
+ * unsynced, as determined through {@link AccountDisplay#mUnsyncedGroups}.
+ */
+ protected static class DisplayAdapter extends BaseExpandableListAdapter {
+ private Context mContext;
+ private LayoutInflater mInflater;
+ private Sources mSources;
+
+ private AccountSet mAccounts;
+
+ private boolean mChildWithPhones = false;
+
+ public DisplayAdapter(Context context, AccountSet accounts) {
+ mContext = context;
+ mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ mSources = Sources.getInstance(context);
+
+ mAccounts = accounts;
+ }
+
+ /**
+ * In group descriptions, show the number of contacts with phone
+ * numbers, in addition to the total contacts.
+ */
+ public void setChildDescripWithPhones(boolean withPhones) {
+ mChildWithPhones = withPhones;
+ }
+
+ /** {@inheritDoc} */
+ public View getChildView(int groupPosition, int childPosition, boolean isLastChild,
+ View convertView, ViewGroup parent) {
+ if (convertView == null) {
+ convertView = mInflater.inflate(R.layout.display_child, parent, false);
+ }
+
+ final TextView text1 = (TextView)convertView.findViewById(android.R.id.text1);
+ final TextView text2 = (TextView)convertView.findViewById(android.R.id.text2);
+ final CheckBox checkbox = (CheckBox)convertView.findViewById(android.R.id.checkbox);
+
+ final AccountDisplay account = mAccounts.get(groupPosition);
+ final GroupDelta child = (GroupDelta)this.getChild(groupPosition, childPosition);
+ if (child != null) {
+ // Handle normal group, with title and checkbox
+ final boolean groupVisible = child.getVisible();
+ checkbox.setVisibility(View.VISIBLE);
+ checkbox.setChecked(groupVisible);
+
+ final CharSequence groupTitle = child.getTitle(mContext);
+ text1.setText(groupTitle);
+
+// final int count = cursor.getInt(GroupsQuery.SUMMARY_COUNT);
+// final int withPhones = cursor.getInt(GroupsQuery.SUMMARY_WITH_PHONES);
+
+// final CharSequence descrip = mContext.getResources().getQuantityString(
+// mChildWithPhones ? R.plurals.groupDescripPhones : R.plurals.groupDescrip,
+// count, count, withPhones);
+
+// text2.setText(descrip);
+ text2.setVisibility(View.GONE);
+ } else {
+ // When unknown child, this is "more" footer view
+ checkbox.setVisibility(View.GONE);
+ text1.setText(R.string.display_more_groups);
+ text2.setVisibility(View.GONE);
+ }
+
+ return convertView;
+ }
+
+ /** {@inheritDoc} */
+ public View getGroupView(int groupPosition, boolean isExpanded, View convertView,
+ ViewGroup parent) {
+ if (convertView == null) {
+ convertView = mInflater.inflate(R.layout.display_group, parent, false);
+ }
+
+ final TextView text1 = (TextView)convertView.findViewById(android.R.id.text1);
+ final TextView text2 = (TextView)convertView.findViewById(android.R.id.text2);
+
+ final AccountDisplay account = (AccountDisplay)this.getGroup(groupPosition);
+
+ final ContactsSource source = mSources.getInflatedSource(account.mType,
+ ContactsSource.LEVEL_SUMMARY);
+
+ text1.setText(account.mName);
+ text2.setText(source.getDisplayLabel(mContext));
+ text2.setVisibility(account.mName == null ? View.GONE : View.VISIBLE);
+
+ return convertView;
+ }
+
+ /** {@inheritDoc} */
+ public Object getChild(int groupPosition, int childPosition) {
+ final AccountDisplay account = mAccounts.get(groupPosition);
+ final boolean validChild = childPosition >= 0
+ && childPosition < account.mSyncedGroups.size();
+ if (validChild) {
+ return account.mSyncedGroups.get(childPosition);
+ } else {
+ return null;
+ }
+ }
+
+ /** {@inheritDoc} */
+ public long getChildId(int groupPosition, int childPosition) {
+ final GroupDelta child = (GroupDelta)getChild(groupPosition, childPosition);
+ if (child != null) {
+ final Long childId = child.getId();
+ return childId != null ? childId : Long.MIN_VALUE;
+ } else {
+ return Long.MIN_VALUE;
+ }
+ }
+
+ /** {@inheritDoc} */
+ public int getChildrenCount(int groupPosition) {
+ // Count is any synced groups, plus possible footer
+ final AccountDisplay account = mAccounts.get(groupPosition);
+ final boolean anyHidden = account.mUnsyncedGroups.size() > 0;
+ return account.mSyncedGroups.size() + (anyHidden ? 1 : 0);
+ }
+
+ /** {@inheritDoc} */
+ public Object getGroup(int groupPosition) {
+ return mAccounts.get(groupPosition);
+ }
+
+ /** {@inheritDoc} */
+ public int getGroupCount() {
+ return mAccounts.size();
+ }
+
+ /** {@inheritDoc} */
+ public long getGroupId(int groupPosition) {
+ return groupPosition;
+ }
+
+ /** {@inheritDoc} */
+ public boolean hasStableIds() {
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ public boolean isChildSelectable(int groupPosition, int childPosition) {
+ return true;
+ }
+ }
+
+ /**
+ * Handle any clicks on header views added to our {@link #mAdapter}, which
+ * are usually the global modifier checkboxes.
+ */
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ switch (view.getId()) {
+ case R.id.header_phones: {
+ mDisplayPhones.toggle();
+ break;
+ }
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void onClick(View view) {
+ switch (view.getId()) {
+ case R.id.btn_done: {
+ this.doSaveAction();
+ break;
+ }
+ case R.id.btn_discard: {
+ this.finish();
+ break;
+ }
+ }
+ }
+
+ /**
+ * Assign a specific value to {@link Prefs#DISPLAY_ONLY_PHONES}, refreshing
+ * the visible list as needed.
+ */
+ protected void setDisplayOnlyPhones(boolean displayOnlyPhones) {
+ mDisplayPhones.setChecked(displayOnlyPhones);
+
+ Editor editor = mPrefs.edit();
+ editor.putBoolean(Prefs.DISPLAY_ONLY_PHONES, displayOnlyPhones);
+ editor.commit();
+
+ mAdapter.setChildDescripWithPhones(displayOnlyPhones);
+ mAdapter.notifyDataSetChanged();
+ }
+
+ /**
+ * Handle any clicks on {@link ExpandableListAdapter} children, which
+ * usually mean toggling its visible state.
+ */
+ @Override
+ public boolean onChildClick(ExpandableListView parent, View view, int groupPosition,
+ int childPosition, long id) {
+ final CheckBox checkbox = (CheckBox)view.findViewById(android.R.id.checkbox);
+
+ final AccountDisplay account = (AccountDisplay)mAdapter.getGroup(groupPosition);
+ final GroupDelta child = (GroupDelta)mAdapter.getChild(groupPosition, childPosition);
+ if (child != null) {
+ checkbox.toggle();
+ child.putVisible(checkbox.isChecked());
+ } else {
+ // Open context menu for bringing back unsynced
+ this.openContextMenu(view);
+ }
+ return true;
+ }
+
+ // TODO: move these definitions to framework constants when we begin
+ // defining this mode through <sync-adapter> tags
+ private static final int SYNC_MODE_UNSUPPORTED = 0;
+ private static final int SYNC_MODE_UNGROUPED = 1;
+ private static final int SYNC_MODE_EVERYTHING = 2;
+
+ protected int getSyncMode(AccountDisplay account) {
+ // TODO: read sync mode through <sync-adapter> definition
+ if (GoogleSource.ACCOUNT_TYPE.equals(account.mType)) {
+ return SYNC_MODE_EVERYTHING;
+ } else {
+ return SYNC_MODE_UNSUPPORTED;
+ }
+ }
+
+ @Override
+ public void onCreateContextMenu(ContextMenu menu, View view,
+ ContextMenu.ContextMenuInfo menuInfo) {
+ super.onCreateContextMenu(menu, view, menuInfo);
+
+ // Bail if not working with expandable long-press, or if not child
+ if (!(menuInfo instanceof ExpandableListContextMenuInfo)) return;
+
+ final ExpandableListContextMenuInfo info = (ExpandableListContextMenuInfo) menuInfo;
+ final int groupPosition = ExpandableListView.getPackedPositionGroup(info.packedPosition);
+ final int childPosition = ExpandableListView.getPackedPositionChild(info.packedPosition);
+
+ // Skip long-press on expandable parents
+ if (childPosition == -1) return;
+
+ final AccountDisplay account = (AccountDisplay)mAdapter.getGroup(groupPosition);
+ final GroupDelta child = (GroupDelta)mAdapter.getChild(groupPosition, childPosition);
+
+ // Ignore when selective syncing unsupported
+ final int syncMode = getSyncMode(account);
+ if (syncMode == SYNC_MODE_UNSUPPORTED) return;
+
+ if (child != null) {
+ showRemoveSync(menu, account, child, syncMode);
+ } else {
+ showAddSync(menu, account, syncMode);
+ }
+ }
+
+ protected void showRemoveSync(ContextMenu menu, final AccountDisplay account,
+ final GroupDelta child, final int syncMode) {
+ final CharSequence title = child.getTitle(this);
+
+ menu.setHeaderTitle(title);
+ menu.add(R.string.menu_sync_remove).setOnMenuItemClickListener(
+ new OnMenuItemClickListener() {
+ public boolean onMenuItemClick(MenuItem item) {
+ handleRemoveSync(account, child, syncMode, title);
+ return true;
+ }
+ });
+ }
+
+ protected void handleRemoveSync(final AccountDisplay account, final GroupDelta child,
+ final int syncMode, CharSequence title) {
+ final boolean shouldSyncUngrouped = account.mUngrouped.getShouldSync();
+ if (syncMode == SYNC_MODE_EVERYTHING && shouldSyncUngrouped
+ && !child.equals(account.mUngrouped)) {
+ // Warn before removing this group when it would cause ungrouped to stop syncing
+ final AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ final CharSequence removeMessage = this.getString(
+ R.string.display_warn_remove_ungrouped, title);
+ builder.setTitle(R.string.menu_sync_remove);
+ builder.setMessage(removeMessage);
+ builder.setNegativeButton(android.R.string.cancel, null);
+ builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ // Mark both this group and ungrouped to stop syncing
+ account.setShouldSync(account.mUngrouped, false);
+ account.setShouldSync(child, false);
+ mAdapter.notifyDataSetChanged();
+ }
+ });
+ builder.show();
+ } else {
+ // Mark this group to not sync
+ account.setShouldSync(child, false);
+ mAdapter.notifyDataSetChanged();
+ }
+ }
+
+ protected void showAddSync(ContextMenu menu, final AccountDisplay account, final int syncMode) {
+ menu.setHeaderTitle(R.string.dialog_sync_add);
+
+ // Create item for each available, unsynced group
+ for (final GroupDelta child : account.mUnsyncedGroups) {
+ if (!child.getShouldSync()) {
+ final CharSequence title = child.getTitle(this);
+ menu.add(title).setOnMenuItemClickListener(new OnMenuItemClickListener() {
+ public boolean onMenuItemClick(MenuItem item) {
+ // Adding specific group for syncing
+ if (child.mUngrouped && syncMode == SYNC_MODE_EVERYTHING) {
+ account.setShouldSync(true);
+ } else {
+ account.setShouldSync(child, true);
+ }
+ mAdapter.notifyDataSetChanged();
+ return true;
+ }
+ });
+ }
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void onBackPressed() {
+ doSaveAction();
+ }
+
+ private void doSaveAction() {
+ if (mAdapter == null) return;
+ setDisplayOnlyPhones(mDisplayPhones.isChecked());
+ new UpdateTask(this).execute(mAdapter.mAccounts);
+ }
+
+ /**
+ * Background task that persists changes to {@link Groups#GROUP_VISIBLE},
+ * showing spinner dialog to user while updating.
+ */
+ public static class UpdateTask extends
+ WeakAsyncTask<AccountSet, Void, Void, Activity> {
+ private WeakReference<ProgressDialog> mProgress;
+
+ public UpdateTask(Activity target) {
+ super(target);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void onPreExecute(Activity target) {
+ final Context context = target;
+
+ mProgress = new WeakReference<ProgressDialog>(ProgressDialog.show(context, null,
+ context.getText(R.string.savingDisplayGroups)));
+
+ // Before starting this task, start an empty service to protect our
+ // process from being reclaimed by the system.
+ context.startService(new Intent(context, EmptyService.class));
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected Void doInBackground(Activity target, AccountSet... params) {
+ final Context context = target;
+ final ContentValues values = new ContentValues();
+ final ContentResolver resolver = context.getContentResolver();
+
+ try {
+ // Build changes and persist in transaction
+ final AccountSet set = params[0];
+ final ArrayList<ContentProviderOperation> diff = set.buildDiff();
+ resolver.applyBatch(ContactsContract.AUTHORITY, diff);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Problem saving display groups", e);
+ } catch (OperationApplicationException e) {
+ Log.e(TAG, "Problem saving display groups", e);
+ }
+
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void onPostExecute(Activity target, Void result) {
+ final Context context = target;
+
+ final ProgressDialog dialog = mProgress.get();
+ if (dialog != null) dialog.dismiss();
+
+ target.finish();
+
+ // Stop the service that was protecting us
+ context.stopService(new Intent(context, EmptyService.class));
+ }
+ }
+}
diff --git a/src/com/android/contacts/ui/EditContactActivity.java b/src/com/android/contacts/ui/EditContactActivity.java
new file mode 100644
index 0000000..3b06a48
--- /dev/null
+++ b/src/com/android/contacts/ui/EditContactActivity.java
@@ -0,0 +1,1136 @@
+/*
+ * 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.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.Editor.EditorListener;
+import com.android.contacts.model.EntityDelta.ValuesDelta;
+import com.android.contacts.ui.widget.ContactEditorView;
+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.net.Uri;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.provider.ContactsContract;
+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.CommonDataKinds.StructuredName;
+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.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+
+/**
+ * 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;
+
+ private static final String KEY_EDIT_STATE = "state";
+ private static final String KEY_RAW_CONTACT_ID_REQUESTING_PHOTO = "photorequester";
+
+ /** 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;
+
+ String mQuerySelection;
+
+ private long mContactIdForJoin;
+ EntitySet mState;
+
+ /** The linear layout holding the ContactEditorViews */
+ LinearLayout mContent;
+
+ private ArrayList<Dialog> mManagedDialogs = Lists.newArrayList();
+
+ @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) {
+ // Read initial state from database
+ new QueryEntitiesTask(this).execute(intent);
+ } else if (Intent.ACTION_INSERT.equals(action) && !hasIncomingState) {
+ // Trigger dialog to pick account type
+ doAddAction();
+ }
+ }
+
+ private static class QueryEntitiesTask extends
+ WeakAsyncTask<Intent, Void, Void, EditContactActivity> {
+ public QueryEntitiesTask(EditContactActivity target) {
+ super(target);
+ }
+
+ @Override
+ protected Void doInBackground(EditContactActivity target, Intent... params) {
+ // Load edit details in background
+ final Context context = target;
+ final Sources sources = Sources.getInstance(context);
+ final Intent intent = params[0];
+
+ final ContentResolver resolver = context.getContentResolver();
+
+ // Handle both legacy and new authorities
+ final Uri data = intent.getData();
+ final String authority = data.getAuthority();
+ final String mimeType = intent.resolveType(resolver);
+
+ String selection = "0";
+ if (ContactsContract.AUTHORITY.equals(authority)) {
+ if (Contacts.CONTENT_ITEM_TYPE.equals(mimeType)) {
+ // Handle selected aggregate
+ final long contactId = ContentUris.parseId(data);
+ selection = 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);
+ selection = RawContacts.CONTACT_ID + "=" + contactId;
+ }
+ } else if (android.provider.Contacts.AUTHORITY.equals(authority)) {
+ final long rawContactId = ContentUris.parseId(data);
+ selection = Data.RAW_CONTACT_ID + "=" + rawContactId;
+ }
+
+ target.mQuerySelection = selection;
+ target.mState = EntitySet.fromQuery(resolver, selection, null, null);
+
+ // Handle any incoming values that should be inserted
+ final Bundle extras = intent.getExtras();
+ final boolean hasExtras = extras != null && extras.size() > 0;
+ final boolean hasState = target.mState.size() > 0;
+ if (hasExtras && hasState) {
+ // Find source defining the first RawContact found
+ final EntityDelta state = target.mState.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);
+ }
+
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(EditContactActivity target, Void result) {
+ // 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);
+ 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);
+ bindEditors();
+
+ super.onRestoreInstanceState(savedInstanceState);
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+
+ for (Dialog dialog : mManagedDialogs) {
+ if (dialog.isShowing()) {
+ dialog.dismiss();
+ }
+ }
+ }
+
+ @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, 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();
+ }
+
+ /**
+ * Check if our internal {@link #mState} is valid, usually checked before
+ * performing user actions.
+ */
+ protected boolean hasValidState() {
+ return 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 (!hasValidState()) 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);
+
+ ContactEditorView editor = (ContactEditorView) inflater.inflate(
+ R.layout.item_contact_editor, mContent, false);
+ editor.getPhotoEditor().setEditorListener(new EditorListener() {
+
+ public void onDeleted(Editor editor) {
+ }
+
+ public void onRequest(int request) {
+ if (!hasValidState()) return;
+
+ if (request == EditorListener.REQUEST_PICK_PHOTO) {
+ doPickPhotoAction(rawContactId);
+ }
+ }
+ });
+
+ mContent.addView(editor);
+ editor.setState(entity, source);
+ }
+
+ // Show editor now that we've loaded state
+ mContent.setVisibility(View.VISIBLE);
+ }
+
+ /** {@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: {
+ ContactEditorView requestingEditor = null;
+ for (int i = 0; i < mContent.getChildCount(); i++) {
+ View childView = mContent.getChildAt(i);
+ if (childView instanceof ContactEditorView) {
+ ContactEditorView editor = (ContactEditorView) 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_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> progress;
+
+ private int mSaveMode;
+ private Uri mContactLookupUri = null;
+
+ public PersistTask(EditContactActivity target, int saveMode) {
+ super(target);
+ mSaveMode = saveMode;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void onPreExecute(EditContactActivity target) {
+ this.progress = 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;
+
+ 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();
+ }
+
+ progress.get().dismiss();
+
+ // 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;
+
+ 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) {
+ Intent intent = new Intent();
+ intent.setData(contactLookupUri);
+ setResult(RESULT_OK, intent);
+ }
+ 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:
+ 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);
+ }
+
+ /**
+ * 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,
+ new String[] {RawContacts._ID},
+ RawContacts.CONTACT_ID + "=" + contactId
+ + " OR " + RawContacts.CONTACT_ID + "=" + mContactIdForJoin, null, null);
+
+ long rawContactIds[];
+ try {
+ rawContactIds = new long[c.getCount()];
+ for (int i = 0; i < rawContactIds.length; i++) {
+ c.moveToNext();
+ rawContactIds[i] = c.getLong(0);
+ }
+ } 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]);
+ }
+ }
+ }
+
+ // 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() {
+ // 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;
+
+ try {
+ // Launch picker to choose photo for selected contact
+ final Intent intent = ContactsUtils.getPhotoPickIntent();
+ startActivityForResult(intent, PHOTO_PICKED_WITH_DATA);
+ mRawContactIdRequestingPhoto = rawContactId;
+ } catch (ActivityNotFoundException e) {
+ Toast.makeText(this, R.string.photoPickerNotFoundText, Toast.LENGTH_LONG).show();
+ }
+ return true;
+ }
+
+ /** {@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, AlertDialog.Builder, EditContactActivity> {
+ public AddContactTask(EditContactActivity target) {
+ super(target);
+ }
+
+ @Override
+ protected AlertDialog.Builder doInBackground(final EditContactActivity target,
+ Void... params) {
+ final Sources sources = Sources.getInstance(target);
+
+ // Wrap our context to inflate list items using correct theme
+ final Context dialogContext = new ContextThemeWrapper(target, android.R.style.Theme_Light);
+ final LayoutInflater dialogInflater = (LayoutInflater)dialogContext
+ .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+
+ final ArrayList<Account> writable = sources.getAccounts(true);
+
+ // No Accounts available. Create a phone-local contact.
+ if (writable.isEmpty()) {
+ selectAccount(null);
+ return null; // Don't show a dialog.
+ }
+
+ // In the common case of a single account being writable, auto-select
+ // it without showing a dialog.
+ if (writable.size() == 1) {
+ selectAccount(writable.get(0));
+ return null; // Don't show a dialog.
+ }
+
+ final ArrayAdapter<Account> accountAdapter = new ArrayAdapter<Account>(target,
+ android.R.layout.simple_list_item_2, writable) {
+ @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(target));
+
+ 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);
+ selectAccount(account);
+
+ // Update the UI.
+ EditContactActivity target = mTarget.get();
+ if (target != null) {
+ target.bindEditors();
+ }
+ }
+ };
+
+ final DialogInterface.OnCancelListener cancelListener = new DialogInterface.OnCancelListener() {
+ public void onCancel(DialogInterface dialog) {
+ // If nothing remains, close activity
+ if (!target.hasValidState()) {
+ target.finish();
+ }
+ }
+ };
+
+ // TODO: when canceled and was single add, finish()
+ final AlertDialog.Builder builder = new AlertDialog.Builder(target);
+ builder.setTitle(R.string.dialog_new_contact_account);
+ builder.setSingleChoiceItems(accountAdapter, 0, clickListener);
+ builder.setOnCancelListener(cancelListener);
+ return builder;
+ }
+
+ /**
+ * Sets up EditContactActivity's mState for the account selected.
+ * Runs from a background thread.
+ *
+ * @param account may be null to signal a device-local contact should
+ * be created.
+ */
+ private void selectAccount(Account account) {
+ EditContactActivity target = mTarget.get();
+ if (target == null) {
+ return;
+ }
+ final Sources sources = Sources.getInstance(target);
+ 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
+ final EntityDelta insert = new EntityDelta(ValuesDelta.fromAfter(values));
+ final ContactsSource source = sources.getInflatedSource(
+ account != null ? account.type : null,
+ ContactsSource.LEVEL_CONSTRAINTS);
+ final Bundle extras = target.getIntent().getExtras();
+ EntityModifier.parseExtras(target, 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, target);
+ }
+
+ // TODO: no synchronization here on target.mState. This
+ // runs in the background thread, but it's accessed from
+ // multiple thread, including the UI thread.
+ if (target.mState == null) {
+ // Create state if none exists yet
+ target.mState = EntitySet.fromSingle(insert);
+ } else {
+ // Add contact onto end of existing state
+ target.mState.add(insert);
+ }
+ }
+
+ @Override
+ protected void onPostExecute(EditContactActivity target, AlertDialog.Builder result) {
+ if (result != null) {
+ // Note: null is returned when no dialog is to be
+ // shown (no multiple accounts to select between)
+ target.showAndManageDialog(result.create());
+ } else {
+ // Account was auto-selected on the background thread,
+ // but we need to update the UI still in the
+ // now-current UI thread.
+ target.bindEditors();
+ }
+ }
+ }
+
+
+
+ private Dialog createDeleteDialog() {
+ final AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle(R.string.deleteConfirmation_title);
+ builder.setIcon(android.R.drawable.ic_dialog_alert);
+ builder.setMessage(R.string.deleteConfirmation);
+ builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ // Mark all raw contacts for deletion
+ for (EntityDelta delta : mState) {
+ delta.markDeleted();
+ }
+
+ // Save the deletes
+ doSaveAction(SAVE_MODE_DEFAULT);
+ finish();
+ }
+ });
+ builder.setNegativeButton(android.R.string.cancel, null);
+ builder.setCancelable(false);
+ return builder.create();
+ }
+
+ /**
+ * Create dialog for selecting primary display name.
+ */
+ private Dialog createNameDialog() {
+ // Build set of all available display names
+ final ArrayList<ValuesDelta> allNames = Lists.newArrayList();
+ for (EntityDelta entity : mState) {
+ final ArrayList<ValuesDelta> displayNames = entity
+ .getMimeEntries(StructuredName.CONTENT_ITEM_TYPE);
+ allNames.addAll(displayNames);
+ }
+
+ // Wrap our context to inflate list items using correct theme
+ final Context dialogContext = new ContextThemeWrapper(this, android.R.style.Theme_Light);
+ final LayoutInflater dialogInflater = this.getLayoutInflater()
+ .cloneInContext(dialogContext);
+
+ final ListAdapter nameAdapter = new ArrayAdapter<ValuesDelta>(this,
+ android.R.layout.simple_list_item_1, allNames) {
+ @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 ValuesDelta structuredName = this.getItem(position);
+ final String displayName = structuredName.getAsString(StructuredName.DISPLAY_NAME);
+
+ ((TextView)convertView).setText(displayName);
+
+ return convertView;
+ }
+ };
+
+ final DialogInterface.OnClickListener clickListener = new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.dismiss();
+
+ // User picked display name, so make super-primary
+ final ValuesDelta structuredName = allNames.get(which);
+ structuredName.put(Data.IS_PRIMARY, 1);
+ structuredName.put(Data.IS_SUPER_PRIMARY, 1);
+ }
+ };
+
+ final AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle(R.string.dialog_primary_name);
+ builder.setSingleChoiceItems(nameAdapter, 0, clickListener);
+ return builder.create();
+ }
+
+ /**
+ * 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 {
+ skipAccountTypeCheck = true;
+ }
+
+ int value;
+ if (!skipAccountTypeCheck) {
+ 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);
+ return (int)(oneId - twoId);
+ }
+}
diff --git a/src/com/android/contacts/ui/QuickContactActivity.java b/src/com/android/contacts/ui/QuickContactActivity.java
new file mode 100644
index 0000000..d17e3be
--- /dev/null
+++ b/src/com/android/contacts/ui/QuickContactActivity.java
@@ -0,0 +1,111 @@
+/*
+ * 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 android.app.Activity;
+import android.content.Intent;
+import android.graphics.Rect;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.ContactsContract.QuickContact;
+import android.util.Log;
+
+/**
+ * Stub translucent activity that just shows {@link QuickContactWindow} floating
+ * above the caller. This temporary hack should eventually be replaced with
+ * direct framework support.
+ */
+public final class QuickContactActivity extends Activity implements
+ QuickContactWindow.OnDismissListener {
+ private static final String TAG = "QuickContactActivity";
+
+ static final boolean LOGV = true;
+ static final boolean FORCE_CREATE = false;
+
+ private QuickContactWindow mQuickContact;
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ if (LOGV) Log.d(TAG, "onCreate");
+
+ this.onNewIntent(getIntent());
+ }
+
+ @Override
+ public void onNewIntent(Intent intent) {
+ super.onNewIntent(intent);
+ if (LOGV) Log.d(TAG, "onNewIntent");
+
+ if (QuickContactWindow.TRACE_LAUNCH) {
+ android.os.Debug.startMethodTracing(QuickContactWindow.TRACE_TAG);
+ }
+
+ if (mQuickContact == null || FORCE_CREATE) {
+ if (LOGV) Log.d(TAG, "Preparing window");
+ mQuickContact = new QuickContactWindow(this, this);
+ }
+
+ // Use our local window token for now
+ final Uri lookupUri = intent.getData();
+ final Bundle extras = intent.getExtras();
+
+ // Read requested parameters for displaying
+ final Rect target = (Rect)extras.getParcelable(QuickContact.EXTRA_TARGET_RECT);
+ final int mode = extras.getInt(QuickContact.EXTRA_MODE, QuickContact.MODE_MEDIUM);
+ final String[] excludeMimes = extras.getStringArray(QuickContact.EXTRA_EXCLUDE_MIMES);
+
+ mQuickContact.show(lookupUri, target, mode, excludeMimes);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void onBackPressed() {
+ if (LOGV) Log.w(TAG, "Unexpected back captured by stub activity");
+ finish();
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ if (LOGV) Log.d(TAG, "onPause");
+
+ // Dismiss any dialog when pausing
+ mQuickContact.dismiss();
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ if (LOGV) Log.d(TAG, "onDestroy");
+ }
+
+ /** {@inheritDoc} */
+ public void onDismiss(QuickContactWindow dialog) {
+ if (LOGV) Log.d(TAG, "onDismiss");
+
+ if (isTaskRoot() && !FORCE_CREATE) {
+ // Instead of stopping, simply push this to the back of the stack.
+ // This is only done when running at the top of the stack;
+ // otherwise, we have been launched by someone else so need to
+ // allow the user to go back to the caller.
+ moveTaskToBack(false);
+ } else {
+ finish();
+ }
+ }
+}
diff --git a/src/com/android/contacts/ui/QuickContactWindow.java b/src/com/android/contacts/ui/QuickContactWindow.java
new file mode 100644
index 0000000..d970b43
--- /dev/null
+++ b/src/com/android/contacts/ui/QuickContactWindow.java
@@ -0,0 +1,1582 @@
+/*
+ * 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.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.ui.widget.CheckableImageView;
+import com.android.contacts.util.Constants;
+import com.android.contacts.util.DataStatus;
+import com.android.contacts.util.NotifyingAsyncQueryHandler;
+import com.android.internal.policy.PolicyManager;
+import com.google.android.collect.Sets;
+
+import android.app.Activity;
+import android.content.ActivityNotFoundException;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.EntityIterator;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+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.text.TextUtils;
+import android.text.format.DateUtils;
+import android.util.Log;
+import android.view.ContextThemeWrapper;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewStub;
+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;
+import android.view.animation.Interpolator;
+import android.widget.AbsListView;
+import android.widget.AdapterView;
+import android.widget.BaseAdapter;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.HorizontalScrollView;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import java.lang.ref.SoftReference;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Window that shows QuickContact dialog for a specific {@link Contacts#_ID}.
+ */
+public class QuickContactWindow implements Window.Callback,
+ NotifyingAsyncQueryHandler.AsyncQueryListener, View.OnClickListener,
+ AbsListView.OnItemClickListener, CompoundButton.OnCheckedChangeListener, KeyEvent.Callback,
+ OnGlobalLayoutListener {
+ private static final String TAG = "QuickContactWindow";
+
+ /**
+ * Interface used to allow the person showing a {@link QuickContactWindow} to
+ * know when the window has been dismissed.
+ */
+ public interface OnDismissListener {
+ public void onDismiss(QuickContactWindow dialog);
+ }
+
+ private final Context mContext;
+ private final LayoutInflater mInflater;
+ private final WindowManager mWindowManager;
+ private Window mWindow;
+ private View mDecor;
+ private final Rect mRect = new Rect();
+
+ private boolean mDismissed = false;
+ private boolean mQuerying = false;
+ private boolean mShowing = false;
+
+ private NotifyingAsyncQueryHandler mHandler;
+ private OnDismissListener mDismissListener;
+ private ResolveCache mResolveCache;
+
+ private Uri mLookupUri;
+ private Rect mAnchor;
+
+ private int mShadowHoriz;
+ private int mShadowVert;
+ private int mShadowTouch;
+
+ private int mScreenWidth;
+ private int mScreenHeight;
+ private int mRequestedY;
+
+ private boolean mHasValidSocial = false;
+ private boolean mHasData = false;
+ private boolean mMakePrimary = false;
+
+ private ImageView mArrowUp;
+ private ImageView mArrowDown;
+
+ private int mMode;
+ private View mHeader;
+ private HorizontalScrollView mTrackScroll;
+ private ViewGroup mTrack;
+ private Animation mTrackAnim;
+
+ private View mFooter;
+ private View mFooterDisambig;
+ private ListView mResolveList;
+ private CheckableImageView mLastAction;
+ private CheckBox mSetPrimaryCheckBox;
+
+ private int mWindowRecycled = 0;
+ private int mActionRecycled = 0;
+
+ /**
+ * Set of {@link Action} that are associated with the aggregate currently
+ * displayed by this dialog, represented as a map from {@link String}
+ * MIME-type to {@link ActionList}.
+ */
+ private ActionMap mActions = new ActionMap();
+
+ /**
+ * Pool of unused {@link CheckableImageView} that have previously been
+ * inflated, and are ready to be recycled through {@link #obtainView()}.
+ */
+ private LinkedList<View> mActionPool = new LinkedList<View>();
+
+ private String[] mExcludeMimes;
+
+ /**
+ * Specific MIME-types that should be bumped to the front of the dialog.
+ * Other MIME-types not appearing in this list follow in alphabetic order.
+ */
+ private static final String[] ORDERED_MIMETYPES = new String[] {
+ Phone.CONTENT_ITEM_TYPE,
+ Contacts.CONTENT_ITEM_TYPE,
+ Constants.MIME_SMS_ADDRESS,
+ Email.CONTENT_ITEM_TYPE,
+ };
+
+ /**
+ * Specific list {@link ApplicationInfo#packageName} of apps that are
+ * prefered <strong>only</strong> for the purposes of default icons when
+ * multiple {@link ResolveInfo} are found to match. This only happens when
+ * the user has not selected a default app yet, and they will still be
+ * presented with the system disambiguation dialog.
+ */
+ private static final HashSet<String> sPreferResolve = Sets.newHashSet(new String[] {
+ "com.android.email",
+ "com.android.calendar",
+ "com.android.contacts",
+ "com.android.mms",
+ "com.android.phone",
+ });
+
+ private static final int TOKEN_DATA = 1;
+
+ static final boolean LOGD = false;
+
+ static final boolean TRACE_LAUNCH = false;
+ static final String TRACE_TAG = "quickcontact";
+
+ /**
+ * Prepare a dialog to show in the given {@link Context}.
+ */
+ public QuickContactWindow(Context context) {
+ mContext = new ContextThemeWrapper(context, R.style.QuickContact);
+ mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
+
+ mWindow = PolicyManager.makeNewWindow(mContext);
+ mWindow.setCallback(this);
+ mWindow.setWindowManager(mWindowManager, null, null);
+
+ mWindow.setContentView(R.layout.quickcontact);
+
+ mArrowUp = (ImageView)mWindow.findViewById(R.id.arrow_up);
+ mArrowDown = (ImageView)mWindow.findViewById(R.id.arrow_down);
+
+ mResolveCache = new ResolveCache(mContext);
+
+ final Resources res = mContext.getResources();
+ mShadowHoriz = res.getDimensionPixelSize(R.dimen.quickcontact_shadow_horiz);
+ mShadowVert = res.getDimensionPixelSize(R.dimen.quickcontact_shadow_vert);
+ mShadowTouch = res.getDimensionPixelSize(R.dimen.quickcontact_shadow_touch);
+
+ mScreenWidth = mWindowManager.getDefaultDisplay().getWidth();
+ mScreenHeight = mWindowManager.getDefaultDisplay().getHeight();
+
+ mTrack = (ViewGroup)mWindow.findViewById(R.id.quickcontact);
+ mTrackScroll = (HorizontalScrollView)mWindow.findViewById(R.id.scroll);
+
+ mFooter = mWindow.findViewById(R.id.footer);
+ mFooterDisambig = mWindow.findViewById(R.id.footer_disambig);
+ mResolveList = (ListView)mWindow.findViewById(android.R.id.list);
+ mSetPrimaryCheckBox = (CheckBox)mWindow.findViewById(android.R.id.checkbox);
+
+ mSetPrimaryCheckBox.setOnCheckedChangeListener(this);
+
+ // Prepare track entrance animation
+ mTrackAnim = AnimationUtils.loadAnimation(mContext, R.anim.quickcontact);
+ mTrackAnim.setInterpolator(new Interpolator() {
+ public float getInterpolation(float t) {
+ // Pushes past the target area, then snaps back into place.
+ // Equation for graphing: 1.2-((x*1.6)-1.1)^2
+ final float inner = (t * 1.55f) - 1.1f;
+ return 1.2f - inner * inner;
+ }
+ });
+
+ mHandler = new NotifyingAsyncQueryHandler(mContext, this);
+ }
+
+ /**
+ * Prepare a dialog to show in the given {@link Context}, and notify the
+ * given {@link OnDismissListener} each time this dialog is dismissed.
+ */
+ public QuickContactWindow(Context context, OnDismissListener dismissListener) {
+ this(context);
+ mDismissListener = dismissListener;
+ }
+
+ private View getHeaderView(int mode) {
+ View header = null;
+ switch (mode) {
+ case QuickContact.MODE_SMALL:
+ header = mWindow.findViewById(R.id.header_small);
+ break;
+ case QuickContact.MODE_MEDIUM:
+ header = mWindow.findViewById(R.id.header_medium);
+ break;
+ case QuickContact.MODE_LARGE:
+ header = mWindow.findViewById(R.id.header_large);
+ break;
+ }
+
+ if (header instanceof ViewStub) {
+ // Inflate actual header if we picked a stub
+ final ViewStub stub = (ViewStub)header;
+ header = stub.inflate();
+ } else {
+ header.setVisibility(View.VISIBLE);
+ }
+
+ return header;
+ }
+
+ /**
+ * Start showing a dialog for the given {@link Contacts#_ID} pointing
+ * towards the given location.
+ */
+ public synchronized void show(Uri lookupUri, Rect anchor, int mode, String[] excludeMimes) {
+ if (mQuerying || mShowing) {
+ Log.w(TAG, "dismissing before showing");
+ dismissInternal();
+ }
+
+ if (TRACE_LAUNCH && !android.os.Debug.isMethodTracingActive()) {
+ android.os.Debug.startMethodTracing(TRACE_TAG);
+ }
+
+ // Prepare header view for requested mode
+ mLookupUri = lookupUri;
+ mAnchor = new Rect(anchor);
+ mMode = mode;
+ mExcludeMimes = excludeMimes;
+
+ mHeader = getHeaderView(mode);
+
+ setHeaderText(R.id.name, R.string.quickcontact_missing_name);
+
+ setHeaderText(R.id.status, null);
+ setHeaderText(R.id.timestamp, null);
+
+ setHeaderImage(R.id.presence, null);
+
+ resetTrack();
+
+ mHasValidSocial = false;
+ mDismissed = false;
+ mQuerying = true;
+
+ // Start background query for data, but only select photo rows when they
+ // directly match the super-primary PHOTO_ID.
+ final Uri dataUri = getDataUri(lookupUri);
+ mHandler.cancelOperation(TOKEN_DATA);
+
+ // Only request photo data when required by mode
+ if (mMode == QuickContact.MODE_LARGE) {
+ // Select photos, but only super-primary
+ mHandler.startQuery(TOKEN_DATA, lookupUri, dataUri, DataQuery.PROJECTION, Data.MIMETYPE
+ + "!=? OR (" + Data.MIMETYPE + "=? AND " + Data._ID + "=" + Contacts.PHOTO_ID
+ + ")", new String[] { Photo.CONTENT_ITEM_TYPE, Photo.CONTENT_ITEM_TYPE }, null);
+ } else {
+ // Exclude all photos from cursor
+ mHandler.startQuery(TOKEN_DATA, lookupUri, dataUri, DataQuery.PROJECTION, Data.MIMETYPE
+ + "!=?", new String[] { Photo.CONTENT_ITEM_TYPE }, null);
+ }
+ }
+
+ /**
+ * Build a {@link Uri} into the {@link Data} table for the requested
+ * {@link Contacts#CONTENT_LOOKUP_URI} style {@link Uri}.
+ */
+ private Uri getDataUri(Uri lookupUri) {
+ // TODO: Formalize method of extracting LOOKUP_KEY
+ final List<String> path = lookupUri.getPathSegments();
+ final boolean validLookup = path.size() >= 3 && "lookup".equals(path.get(1));
+ if (!validLookup) {
+ // We only accept valid lookup-style Uris
+ throw new IllegalArgumentException("Expecting lookup-style Uri");
+ } else if (path.size() == 3) {
+ // No direct _ID provided, so force a lookup
+ lookupUri = Contacts.lookupContact(mContext.getContentResolver(), lookupUri);
+ }
+
+ final long contactId = ContentUris.parseId(lookupUri);
+ return Uri.withAppendedPath(ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId),
+ Contacts.Data.CONTENT_DIRECTORY);
+ }
+
+ /**
+ * Show the correct call-out arrow based on a {@link R.id} reference.
+ */
+ private void showArrow(int whichArrow, int requestedX) {
+ final View showArrow = (whichArrow == R.id.arrow_up) ? mArrowUp : mArrowDown;
+ final View hideArrow = (whichArrow == R.id.arrow_up) ? mArrowDown : mArrowUp;
+
+ final int arrowWidth = mArrowUp.getMeasuredWidth();
+
+ showArrow.setVisibility(View.VISIBLE);
+ ViewGroup.MarginLayoutParams param = (ViewGroup.MarginLayoutParams)showArrow.getLayoutParams();
+ param.leftMargin = requestedX - arrowWidth / 2;
+
+ hideArrow.setVisibility(View.INVISIBLE);
+ }
+
+ /**
+ * Actual internal method to show this dialog. Called only by
+ * {@link #considerShowing()} when all data requirements have been met.
+ */
+ private void showInternal() {
+ mDecor = mWindow.getDecorView();
+ mDecor.getViewTreeObserver().addOnGlobalLayoutListener(this);
+ WindowManager.LayoutParams l = mWindow.getAttributes();
+
+ l.width = mScreenWidth + mShadowHoriz + mShadowHoriz;
+ l.height = WindowManager.LayoutParams.WRAP_CONTENT;
+
+ // Force layout measuring pass so we have baseline numbers
+ mDecor.measure(l.width, l.height);
+ final int blockHeight = mDecor.getMeasuredHeight();
+
+ l.gravity = Gravity.TOP | Gravity.LEFT;
+ l.x = -mShadowHoriz;
+
+ if (mAnchor.top > blockHeight) {
+ // Show downwards callout when enough room, aligning bottom block
+ // edge with top of anchor area, and adjusting to inset arrow.
+ showArrow(R.id.arrow_down, mAnchor.centerX());
+ l.y = mAnchor.top - blockHeight + mShadowVert;
+ l.windowAnimations = R.style.QuickContactAboveAnimation;
+
+ } else {
+ // Otherwise show upwards callout, aligning block top with bottom of
+ // anchor area, and adjusting to inset arrow.
+ showArrow(R.id.arrow_up, mAnchor.centerX());
+ l.y = mAnchor.bottom - mShadowVert;
+ l.windowAnimations = R.style.QuickContactBelowAnimation;
+
+ }
+
+ l.flags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
+
+ mRequestedY = l.y;
+ mWindowManager.addView(mDecor, l);
+ mShowing = true;
+ mQuerying = false;
+ mDismissed = false;
+
+ mTrack.startAnimation(mTrackAnim);
+
+ if (TRACE_LAUNCH) {
+ android.os.Debug.stopMethodTracing();
+ Log.d(TAG, "Window recycled " + mWindowRecycled + " times, chiclets "
+ + mActionRecycled + " times");
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void onGlobalLayout() {
+ layoutInScreen();
+ }
+
+ /**
+ * Adjust vertical {@link WindowManager.LayoutParams} to fit window as best
+ * as possible, shifting up to display content as needed.
+ */
+ private void layoutInScreen() {
+ if (!mShowing) return;
+
+ final WindowManager.LayoutParams l = mWindow.getAttributes();
+ final int originalY = l.y;
+
+ final int blockHeight = mDecor.getHeight();
+
+ l.y = mRequestedY;
+ if (mRequestedY + blockHeight > mScreenHeight) {
+ // Shift up from bottom when overflowing
+ l.y = mScreenHeight - blockHeight;
+ }
+
+ if (originalY != l.y) {
+ // Only update when value is changed
+ mWindow.setAttributes(l);
+ }
+ }
+
+ /**
+ * Dismiss this dialog if showing.
+ */
+ public synchronized void dismiss() {
+ // Notify any listeners that we've been dismissed
+ if (mDismissListener != null) {
+ mDismissListener.onDismiss(this);
+ }
+
+ dismissInternal();
+ }
+
+ private void dismissInternal() {
+ // Remove any attached window decor for recycling
+ boolean hadDecor = mDecor != null;
+ if (hadDecor) {
+ mWindowManager.removeView(mDecor);
+ mWindowRecycled++;
+ mDecor.getViewTreeObserver().removeGlobalOnLayoutListener(this);
+ mDecor = null;
+ mWindow.closeAllPanels();
+ }
+ mShowing = false;
+ mDismissed = true;
+
+ // Cancel any pending queries
+ mHandler.cancelOperation(TOKEN_DATA);
+ mQuerying = false;
+
+ // Completely hide header and reset track
+ mHeader.setVisibility(View.GONE);
+ resetTrack();
+ }
+
+ /**
+ * Reset track to initial state, recycling any chiclets.
+ */
+ private void resetTrack() {
+ // Release reference to last chiclet
+ mLastAction = null;
+
+ // Clear track actions and scroll to hard left
+ mResolveCache.clear();
+ mActions.clear();
+
+ // Recycle any chiclets in use
+ while (mTrack.getChildCount() > 2) {
+ this.releaseView(mTrack.getChildAt(1));
+ mTrack.removeViewAt(1);
+ }
+
+ mTrackScroll.fullScroll(View.FOCUS_LEFT);
+ mWasDownArrow = false;
+
+ // Clear any primary requests
+ mMakePrimary = false;
+ mSetPrimaryCheckBox.setChecked(false);
+
+ setResolveVisible(false, null);
+ }
+
+ /**
+ * Consider showing this window, which will only call through to
+ * {@link #showInternal()} when all data items are present.
+ */
+ private void considerShowing() {
+ if (mHasData && !mShowing && !mDismissed) {
+ if (mMode == QuickContact.MODE_MEDIUM && !mHasValidSocial) {
+ // Missing valid social, swap medium for small header
+ mHeader.setVisibility(View.GONE);
+ mHeader = getHeaderView(QuickContact.MODE_SMALL);
+ }
+
+ // All queries have returned, pull curtain
+ showInternal();
+ }
+ }
+
+ /** {@inheritDoc} */
+ public synchronized void onQueryComplete(int token, Object cookie, Cursor cursor) {
+ // Bail early when query is stale
+ if (cookie != mLookupUri) return;
+
+ if (cursor == null) {
+ // Problem while running query, so bail without showing
+ Log.w(TAG, "Missing cursor for token=" + token);
+ this.dismiss();
+ return;
+ }
+
+ handleData(cursor);
+ mHasData = true;
+
+ if (!cursor.isClosed()) {
+ cursor.close();
+ }
+
+ considerShowing();
+ }
+
+ /** Assign this string to the view, if found in {@link #mHeader}. */
+ private void setHeaderText(int id, int resId) {
+ setHeaderText(id, mContext.getResources().getText(resId));
+ }
+
+ /** Assign this string to the view, if found in {@link #mHeader}. */
+ private void setHeaderText(int id, CharSequence value) {
+ final View view = mHeader.findViewById(id);
+ if (view instanceof TextView) {
+ ((TextView)view).setText(value);
+ view.setVisibility(TextUtils.isEmpty(value) ? View.GONE : View.VISIBLE);
+ }
+ }
+
+ /** Assign this image to the view, if found in {@link #mHeader}. */
+ private void setHeaderImage(int id, int resId) {
+ setHeaderImage(id, mContext.getResources().getDrawable(resId));
+ }
+
+ /** Assign this image to the view, if found in {@link #mHeader}. */
+ private void setHeaderImage(int id, Drawable drawable) {
+ final View view = mHeader.findViewById(id);
+ if (view instanceof ImageView) {
+ ((ImageView)view).setImageDrawable(drawable);
+ view.setVisibility(drawable == null ? View.GONE : View.VISIBLE);
+ }
+ }
+
+ /**
+ * Find the presence icon for showing in summary header.
+ */
+ private Drawable getPresenceIcon(int status) {
+ int resId = -1;
+ switch (status) {
+ case StatusUpdates.AVAILABLE:
+ resId = android.R.drawable.presence_online;
+ break;
+ case StatusUpdates.IDLE:
+ case StatusUpdates.AWAY:
+ resId = android.R.drawable.presence_away;
+ break;
+ case StatusUpdates.DO_NOT_DISTURB:
+ resId = android.R.drawable.presence_busy;
+ break;
+ }
+ if (resId != -1) {
+ return mContext.getResources().getDrawable(resId);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Find the QuickContact-specific presence icon for showing in chiclets.
+ */
+ private Drawable getTrackPresenceIcon(int status) {
+ int resId = -1;
+ switch (status) {
+ case StatusUpdates.AVAILABLE:
+ resId = R.drawable.quickcontact_slider_presence_active;
+ break;
+ case StatusUpdates.IDLE:
+ case StatusUpdates.AWAY:
+ resId = R.drawable.quickcontact_slider_presence_away;
+ break;
+ case StatusUpdates.DO_NOT_DISTURB:
+ resId = R.drawable.quickcontact_slider_presence_busy;
+ break;
+ case StatusUpdates.INVISIBLE:
+ resId = R.drawable.quickcontact_slider_presence_inactive;
+ break;
+ case StatusUpdates.OFFLINE:
+ default:
+ resId = R.drawable.quickcontact_slider_presence_inactive;
+ }
+ return mContext.getResources().getDrawable(resId);
+ }
+
+ /** Read {@link String} from the given {@link Cursor}. */
+ private static String getAsString(Cursor cursor, String columnName) {
+ final int index = cursor.getColumnIndex(columnName);
+ return cursor.getString(index);
+ }
+
+ /** Read {@link Integer} from the given {@link Cursor}. */
+ private static int getAsInt(Cursor cursor, String columnName) {
+ final int index = cursor.getColumnIndex(columnName);
+ return cursor.getInt(index);
+ }
+
+ /**
+ * Abstract definition of an action that could be performed, along with
+ * string description and icon.
+ */
+ private interface Action {
+ public CharSequence getHeader();
+ public CharSequence getBody();
+
+ public String getMimeType();
+ public Drawable getFallbackIcon();
+
+ /**
+ * Build an {@link Intent} that will perform this action.
+ */
+ public Intent getIntent();
+
+ /**
+ * Checks if the contact data for this action is primary.
+ */
+ public Boolean isPrimary();
+
+ /**
+ * Returns a lookup (@link Uri) for the contact data item.
+ */
+ public Uri getDataUri();
+ }
+
+ /**
+ * Description of a specific {@link Data#_ID} item, with style information
+ * defined by a {@link DataKind}.
+ */
+ private static class DataAction implements Action {
+ private final Context mContext;
+ private final DataKind mKind;
+ private final String mMimeType;
+
+ private CharSequence mHeader;
+ private CharSequence mBody;
+ private Intent mIntent;
+
+ private boolean mAlternate;
+ private Uri mDataUri;
+ private boolean mIsPrimary;
+
+ /**
+ * Create an action from common {@link Data} elements.
+ */
+ public DataAction(Context context, String mimeType, DataKind kind,
+ long dataId, Cursor cursor) {
+ mContext = context;
+ mKind = kind;
+ mMimeType = mimeType;
+
+ // Inflate strings from cursor
+ mAlternate = Constants.MIME_SMS_ADDRESS.equals(mimeType);
+ if (mAlternate && mKind.actionAltHeader != null) {
+ mHeader = mKind.actionAltHeader.inflateUsing(context, cursor);
+ } else if (mKind.actionHeader != null) {
+ mHeader = mKind.actionHeader.inflateUsing(context, cursor);
+ }
+
+ if (getAsInt(cursor, Data.IS_SUPER_PRIMARY) != 0) {
+ mIsPrimary = true;
+ }
+
+ if (mKind.actionBody != null) {
+ mBody = mKind.actionBody.inflateUsing(context, cursor);
+ }
+
+ mDataUri = ContentUris.withAppendedId(Data.CONTENT_URI, dataId);
+
+ // Handle well-known MIME-types with special care
+ if (Phone.CONTENT_ITEM_TYPE.equals(mimeType)) {
+ final String number = getAsString(cursor, Phone.NUMBER);
+ if (!TextUtils.isEmpty(number)) {
+ final Uri callUri = Uri.fromParts(Constants.SCHEME_TEL, number, null);
+ mIntent = new Intent(Intent.ACTION_CALL_PRIVILEGED, callUri);
+ }
+
+ } else if (Constants.MIME_SMS_ADDRESS.equals(mimeType)) {
+ final String number = getAsString(cursor, Phone.NUMBER);
+ if (!TextUtils.isEmpty(number)) {
+ final Uri smsUri = Uri.fromParts(Constants.SCHEME_SMSTO, number, null);
+ mIntent = new Intent(Intent.ACTION_SENDTO, smsUri);
+ }
+
+ } else if (Email.CONTENT_ITEM_TYPE.equals(mimeType)) {
+ final String address = getAsString(cursor, Email.DATA);
+ if (!TextUtils.isEmpty(address)) {
+ final Uri mailUri = Uri.fromParts(Constants.SCHEME_MAILTO, address, null);
+ mIntent = new Intent(Intent.ACTION_SENDTO, mailUri);
+ }
+
+ } else if (Im.CONTENT_ITEM_TYPE.equals(mimeType)) {
+ final boolean isEmail = Email.CONTENT_ITEM_TYPE.equals(
+ getAsString(cursor, Data.MIMETYPE));
+ if (isEmail || isProtocolValid(cursor)) {
+ final int protocol = isEmail ? Im.PROTOCOL_GOOGLE_TALK :
+ getAsInt(cursor, Im.PROTOCOL);
+
+ if (isEmail) {
+ // Use Google Talk string when using Email, and clear data
+ // Uri so we don't try saving Email as primary.
+ mHeader = context.getText(R.string.chat_gtalk);
+ mDataUri = null;
+ }
+
+ String host = getAsString(cursor, Im.CUSTOM_PROTOCOL);
+ String data = getAsString(cursor, 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();
+ mIntent = new Intent(Intent.ACTION_SENDTO, imUri);
+ }
+ }
+ }
+
+ if (mIntent == null) {
+ // Otherwise fall back to default VIEW action
+ mIntent = new Intent(Intent.ACTION_VIEW, mDataUri);
+ }
+
+ // Always launch as new task, since we're like a launcher
+ mIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ }
+
+ private boolean isProtocolValid(Cursor cursor) {
+ final int columnIndex = cursor.getColumnIndex(Im.PROTOCOL);
+ if (cursor.isNull(columnIndex)) {
+ return false;
+ }
+ try {
+ Integer.valueOf(cursor.getString(columnIndex));
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ public CharSequence getHeader() {
+ return mHeader;
+ }
+
+ /** {@inheritDoc} */
+ public CharSequence getBody() {
+ return mBody;
+ }
+
+ /** {@inheritDoc} */
+ public String getMimeType() {
+ return mMimeType;
+ }
+
+ /** {@inheritDoc} */
+ public Uri getDataUri() {
+ return mDataUri;
+ }
+
+ /** {@inheritDoc} */
+ public Boolean isPrimary() {
+ return mIsPrimary;
+ }
+
+ /** {@inheritDoc} */
+ public Drawable getFallbackIcon() {
+ // Bail early if no valid resources
+ final String resPackageName = mKind.resPackageName;
+ if (resPackageName == null) return null;
+
+ final PackageManager pm = mContext.getPackageManager();
+ if (mAlternate && mKind.iconAltRes != -1) {
+ return pm.getDrawable(resPackageName, mKind.iconAltRes, null);
+ } else if (mKind.iconRes != -1) {
+ return pm.getDrawable(resPackageName, mKind.iconRes, null);
+ } else {
+ return null;
+ }
+ }
+
+ /** {@inheritDoc} */
+ public Intent getIntent() {
+ return mIntent;
+ }
+ }
+
+ /**
+ * Specific action that launches the profile card.
+ */
+ private static class ProfileAction implements Action {
+ private final Context mContext;
+ private final Uri mLookupUri;
+
+ public ProfileAction(Context context, Uri lookupUri) {
+ mContext = context;
+ mLookupUri = lookupUri;
+ }
+
+ /** {@inheritDoc} */
+ public CharSequence getHeader() {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ public CharSequence getBody() {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ public String getMimeType() {
+ return Contacts.CONTENT_ITEM_TYPE;
+ }
+
+ /** {@inheritDoc} */
+ public Drawable getFallbackIcon() {
+ return mContext.getResources().getDrawable(R.drawable.ic_contacts_details);
+ }
+
+ /** {@inheritDoc} */
+ public Intent getIntent() {
+ final Intent intent = new Intent(Intent.ACTION_VIEW, mLookupUri);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ return intent;
+ }
+
+ /** {@inheritDoc} */
+ public Boolean isPrimary() {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ public Uri getDataUri() {
+ return null;
+ }
+
+ }
+
+ /**
+ * Internally hold a cache of scaled icons based on {@link PackageManager}
+ * queries, keyed internally on MIME-type.
+ */
+ private static class ResolveCache {
+ private Context mContext;
+ private PackageManager mPackageManager;
+
+ /**
+ * Cached entry holding the best {@link ResolveInfo} for a specific
+ * MIME-type, along with a {@link SoftReference} to its icon.
+ */
+ private static class Entry {
+ public ResolveInfo bestResolve;
+ public SoftReference<Drawable> icon;
+ }
+
+ private HashMap<String, Entry> mCache = new HashMap<String, Entry>();
+
+ public ResolveCache(Context context) {
+ mContext = context;
+ mPackageManager = context.getPackageManager();
+ }
+
+ /**
+ * Get the {@link Entry} best associated with the given {@link Action},
+ * or create and populate a new one if it doesn't exist.
+ */
+ protected Entry getEntry(Action action) {
+ final String mimeType = action.getMimeType();
+ Entry entry = mCache.get(mimeType);
+ if (entry != null) return entry;
+ entry = new Entry();
+
+ final Intent intent = action.getIntent();
+ if (intent != null) {
+ final List<ResolveInfo> matches = mPackageManager.queryIntentActivities(intent,
+ PackageManager.MATCH_DEFAULT_ONLY);
+
+ // Pick first match, otherwise best found
+ ResolveInfo bestResolve = null;
+ final int size = matches.size();
+ if (size == 1) {
+ bestResolve = matches.get(0);
+ } else if (size > 1) {
+ bestResolve = getBestResolve(intent, matches);
+ }
+
+ if (bestResolve != null) {
+ final Drawable icon = bestResolve.loadIcon(mPackageManager);
+
+ entry.bestResolve = bestResolve;
+ entry.icon = new SoftReference<Drawable>(icon);
+ }
+ }
+
+ mCache.put(mimeType, entry);
+ return entry;
+ }
+
+ /**
+ * Best {@link ResolveInfo} when multiple found. Ties are broken by
+ * selecting first from the {QuickContactWindow#sPreferResolve} list of
+ * preferred packages, second by apps that live on the system partition,
+ * otherwise the app from the top of the list. This is
+ * <strong>only</strong> used for selecting a default icon for
+ * displaying in the track, and does not shortcut the system
+ * {@link Intent} disambiguation dialog.
+ */
+ protected ResolveInfo getBestResolve(Intent intent, List<ResolveInfo> matches) {
+ // Try finding preferred activity, otherwise detect disambig
+ final ResolveInfo foundResolve = mPackageManager.resolveActivity(intent,
+ PackageManager.MATCH_DEFAULT_ONLY);
+ final boolean foundDisambig = (foundResolve.match &
+ IntentFilter.MATCH_CATEGORY_MASK) == 0;
+
+ if (!foundDisambig) {
+ // Found concrete match, so return directly
+ return foundResolve;
+ }
+
+ // Accept any package from prefer list, otherwise first system app
+ ResolveInfo firstSystem = null;
+ for (ResolveInfo info : matches) {
+ final boolean isSystem = (info.activityInfo.applicationInfo.flags
+ & ApplicationInfo.FLAG_SYSTEM) != 0;
+ final boolean isPrefer = QuickContactWindow.sPreferResolve
+ .contains(info.activityInfo.applicationInfo.packageName);
+
+
+
+ if (isPrefer) return info;
+ if (isSystem && firstSystem != null) firstSystem = info;
+ }
+
+ // Return first system found, otherwise first from list
+ return firstSystem != null ? firstSystem : matches.get(0);
+ }
+
+ /**
+ * Check {@link PackageManager} to see if any apps offer to handle the
+ * given {@link Action}.
+ */
+ public boolean hasResolve(Action action) {
+ return getEntry(action).bestResolve != null;
+ }
+
+ /**
+ * Find the best description for the given {@link Action}, usually used
+ * for accessibility purposes.
+ */
+ public CharSequence getDescription(Action action) {
+ final CharSequence actionHeader = action.getHeader();
+ final ResolveInfo info = getEntry(action).bestResolve;
+ if (!TextUtils.isEmpty(actionHeader)) {
+ return actionHeader;
+ } else if (info != null) {
+ return info.loadLabel(mPackageManager);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Return the best icon for the given {@link Action}, which is usually
+ * based on the {@link ResolveInfo} found through a
+ * {@link PackageManager} query.
+ */
+ public Drawable getIcon(Action action) {
+ final SoftReference<Drawable> iconRef = getEntry(action).icon;
+ return (iconRef == null) ? null : iconRef.get();
+ }
+
+ public void clear() {
+ mCache.clear();
+ }
+ }
+
+ /**
+ * Provide a strongly-typed {@link LinkedList} that holds a list of
+ * {@link Action} objects.
+ */
+ private class ActionList extends LinkedList<Action> {
+ }
+
+ /**
+ * Provide a simple way of collecting one or more {@link Action} objects
+ * under a MIME-type key.
+ */
+ private class ActionMap extends HashMap<String, ActionList> {
+ private void collect(String mimeType, Action info) {
+ // Create list for this MIME-type when needed
+ ActionList collectList = get(mimeType);
+ if (collectList == null) {
+ collectList = new ActionList();
+ put(mimeType, collectList);
+ }
+ collectList.add(info);
+ }
+ }
+
+ /**
+ * Check if the given MIME-type appears in the list of excluded MIME-types
+ * that the most-recent caller requested.
+ */
+ private boolean isMimeExcluded(String mimeType) {
+ if (mExcludeMimes == null) return false;
+ for (String excludedMime : mExcludeMimes) {
+ if (TextUtils.equals(excludedMime, mimeType)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Handle the result from the {@link #TOKEN_DATA} query.
+ */
+ private void handleData(Cursor cursor) {
+ if (cursor == null) return;
+
+ if (!isMimeExcluded(Contacts.CONTENT_ITEM_TYPE)) {
+ // Add the profile shortcut action
+ final Action action = new ProfileAction(mContext, mLookupUri);
+ mActions.collect(Contacts.CONTENT_ITEM_TYPE, action);
+ }
+
+ final DataStatus status = new DataStatus();
+ final Sources sources = Sources.getInstance(mContext);
+ final ImageView photoView = (ImageView)mHeader.findViewById(R.id.photo);
+
+ Bitmap photoBitmap = null;
+ while (cursor.moveToNext()) {
+ final long dataId = cursor.getLong(DataQuery._ID);
+ final String accountType = cursor.getString(DataQuery.ACCOUNT_TYPE);
+ final String resPackage = cursor.getString(DataQuery.RES_PACKAGE);
+ final String mimeType = cursor.getString(DataQuery.MIMETYPE);
+
+ // Handle any social status updates from this row
+ status.possibleUpdate(cursor);
+
+ // Skip this data item if MIME-type excluded
+ if (isMimeExcluded(mimeType)) continue;
+
+ // Handle photos included as data row
+ if (Photo.CONTENT_ITEM_TYPE.equals(mimeType)) {
+ final int colPhoto = cursor.getColumnIndex(Photo.PHOTO);
+ final byte[] photoBlob = cursor.getBlob(colPhoto);
+ if (photoBlob != null) {
+ photoBitmap = BitmapFactory.decodeByteArray(photoBlob, 0, photoBlob.length);
+ }
+ continue;
+ }
+
+ final DataKind kind = sources.getKindOrFallback(accountType, mimeType, mContext,
+ ContactsSource.LEVEL_MIMETYPES);
+
+ if (kind != null) {
+ // Build an action for this data entry, find a mapping to a UI
+ // element, build its summary from the cursor, and collect it
+ // along with all others of this MIME-type.
+ final Action action = new DataAction(mContext, mimeType, kind, dataId, cursor);
+ considerAdd(action, mimeType);
+ }
+
+ // If phone number, also insert as text message action
+ if (Phone.CONTENT_ITEM_TYPE.equals(mimeType) && kind != null) {
+ final Action action = new DataAction(mContext, Constants.MIME_SMS_ADDRESS,
+ kind, dataId, cursor);
+ considerAdd(action, Constants.MIME_SMS_ADDRESS);
+ }
+
+ // Handle Email rows with presence data as Im entry
+ final boolean hasPresence = !cursor.isNull(DataQuery.PRESENCE);
+ if (hasPresence && Email.CONTENT_ITEM_TYPE.equals(mimeType)) {
+ final DataKind imKind = sources.getKindOrFallback(accountType,
+ Im.CONTENT_ITEM_TYPE, mContext, ContactsSource.LEVEL_MIMETYPES);
+ if (imKind != null) {
+ final Action action = new DataAction(mContext, Im.CONTENT_ITEM_TYPE, imKind,
+ dataId, cursor);
+ considerAdd(action, Im.CONTENT_ITEM_TYPE);
+ }
+ }
+ }
+
+ if (cursor.moveToLast()) {
+ // Read contact information from last data row
+ final String name = cursor.getString(DataQuery.DISPLAY_NAME);
+ final int presence = cursor.getInt(DataQuery.CONTACT_PRESENCE);
+ final Drawable statusIcon = getPresenceIcon(presence);
+
+ setHeaderText(R.id.name, name);
+ setHeaderImage(R.id.presence, statusIcon);
+ }
+
+ if (photoView != null) {
+ // Place photo when discovered in data, otherwise hide
+ photoView.setVisibility(photoBitmap != null ? View.VISIBLE : View.GONE);
+ photoView.setImageBitmap(photoBitmap);
+ }
+
+ mHasValidSocial = status.isValid();
+ if (mHasValidSocial && mMode != QuickContact.MODE_SMALL) {
+ // Update status when valid was found
+ setHeaderText(R.id.status, status.getStatus());
+ setHeaderText(R.id.timestamp, status.getTimestampLabel(mContext));
+ }
+
+ // Turn our list of actions into UI elements, starting with common types
+ final Set<String> containedTypes = mActions.keySet();
+ for (String mimeType : ORDERED_MIMETYPES) {
+ if (containedTypes.contains(mimeType)) {
+ final int index = mTrack.getChildCount() - 1;
+ mTrack.addView(inflateAction(mimeType), index);
+ containedTypes.remove(mimeType);
+ }
+ }
+
+ // Then continue with remaining MIME-types in alphabetical order
+ final String[] remainingTypes = containedTypes.toArray(new String[containedTypes.size()]);
+ Arrays.sort(remainingTypes);
+ for (String mimeType : remainingTypes) {
+ final int index = mTrack.getChildCount() - 1;
+ mTrack.addView(inflateAction(mimeType), index);
+ }
+ }
+
+ /**
+ * Consider adding the given {@link Action}, which will only happen if
+ * {@link PackageManager} finds an application to handle
+ * {@link Action#getIntent()}.
+ */
+ private void considerAdd(Action action, String mimeType) {
+ if (mResolveCache.hasResolve(action)) {
+ mActions.collect(mimeType, action);
+ }
+ }
+
+ /**
+ * Obtain a new {@link CheckableImageView} for a new chiclet, either by
+ * recycling one from {@link #mActionPool}, or by inflating a new one. When
+ * finished, use {@link #releaseView(View)} to return back into the pool for
+ * later recycling.
+ */
+ private synchronized View obtainView() {
+ View view = mActionPool.poll();
+ if (view == null || QuickContactActivity.FORCE_CREATE) {
+ view = mInflater.inflate(R.layout.quickcontact_item, mTrack, false);
+ }
+ return view;
+ }
+
+ /**
+ * Return the given {@link CheckableImageView} into our internal pool for
+ * possible recycling during another pass.
+ */
+ private synchronized void releaseView(View view) {
+ mActionPool.offer(view);
+ mActionRecycled++;
+ }
+
+ /**
+ * Inflate the in-track view for the action of the given MIME-type. Will use
+ * the icon provided by the {@link DataKind}.
+ */
+ private View inflateAction(String mimeType) {
+ final CheckableImageView view = (CheckableImageView)obtainView();
+ boolean isActionSet = false;
+
+ // Add direct intent if single child, otherwise flag for multiple
+ ActionList children = mActions.get(mimeType);
+ Action firstInfo = children.get(0);
+ if (children.size() == 1) {
+ view.setTag(firstInfo);
+ } else {
+ for (Action action : children) {
+ if (action.isPrimary()) {
+ view.setTag(action);
+ isActionSet = true;
+ break;
+ }
+ }
+ if (!isActionSet) {
+ view.setTag(children);
+ }
+ }
+
+ // Set icon and listen for clicks
+ final CharSequence descrip = mResolveCache.getDescription(firstInfo);
+ final Drawable icon = mResolveCache.getIcon(firstInfo);
+ view.setChecked(false);
+ view.setContentDescription(descrip);
+ view.setImageDrawable(icon);
+ view.setOnClickListener(this);
+ return view;
+ }
+
+ /** {@inheritDoc} */
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ // Pass list item clicks along so that Intents are handled uniformly
+ onClick(view);
+ }
+
+ /**
+ * Flag indicating if {@link #mArrowDown} was visible during the last call
+ * to {@link #setResolveVisible(boolean, CheckableImageView)}. Used to
+ * decide during a later call if the arrow should be restored.
+ */
+ private boolean mWasDownArrow = false;
+
+ /**
+ * Helper for showing and hiding {@link #mFooterDisambig}, which will
+ * correctly manage {@link #mArrowDown} as needed.
+ */
+ private void setResolveVisible(boolean visible, CheckableImageView actionView) {
+ // Show or hide the resolve list if needed
+ boolean visibleNow = mFooterDisambig.getVisibility() == View.VISIBLE;
+
+ if (mLastAction != null) mLastAction.setChecked(false);
+ if (actionView != null) actionView.setChecked(true);
+ mLastAction = actionView;
+
+ // Bail early if already in desired state
+ if (visible == visibleNow) return;
+
+ mFooter.setVisibility(visible ? View.GONE : View.VISIBLE);
+ mFooterDisambig.setVisibility(visible ? View.VISIBLE : View.GONE);
+
+ if (visible) {
+ // If showing list, then hide and save state of down arrow
+ mWasDownArrow = mWasDownArrow || (mArrowDown.getVisibility() == View.VISIBLE);
+ mArrowDown.setVisibility(View.INVISIBLE);
+ } else {
+ // If hiding list, restore any down arrow state
+ mArrowDown.setVisibility(mWasDownArrow ? View.VISIBLE : View.INVISIBLE);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void onClick(View view) {
+ final boolean isActionView = (view instanceof CheckableImageView);
+ final CheckableImageView actionView = isActionView ? (CheckableImageView)view : null;
+ final Object tag = view.getTag();
+ if (tag instanceof Action) {
+ // Incoming tag is concrete intent, so try launching
+ final Action action = (Action)tag;
+ final boolean makePrimary = mMakePrimary;
+
+ try {
+ mContext.startActivity(action.getIntent());
+ } catch (ActivityNotFoundException e) {
+ Toast.makeText(mContext, R.string.quickcontact_missing_app, Toast.LENGTH_SHORT)
+ .show();
+ }
+
+ // Hide the resolution list, if present
+ setResolveVisible(false, actionView);
+ this.dismiss();
+
+ if (makePrimary) {
+ ContentValues values = new ContentValues(1);
+ values.put(Data.IS_SUPER_PRIMARY, 1);
+ final Uri dataUri = action.getDataUri();
+ if (dataUri != null) {
+ mContext.getContentResolver().update(dataUri, values, null, null);
+ }
+ }
+ } else if (tag instanceof ActionList) {
+ // Incoming tag is a MIME-type, so show resolution list
+ final ActionList children = (ActionList)tag;
+
+ // Show resolution list and set adapter
+ setResolveVisible(true, actionView);
+
+ mResolveList.setOnItemClickListener(this);
+ mResolveList.setAdapter(new BaseAdapter() {
+ public int getCount() {
+ return children.size();
+ }
+
+ public Object getItem(int position) {
+ return children.get(position);
+ }
+
+ public long getItemId(int position) {
+ return position;
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ if (convertView == null) {
+ convertView = mInflater.inflate(
+ R.layout.quickcontact_resolve_item, parent, false);
+ }
+
+ // Set action title based on summary value
+ final Action action = (Action)getItem(position);
+ final Drawable icon = mResolveCache.getIcon(action);
+
+ TextView text1 = (TextView)convertView.findViewById(android.R.id.text1);
+ TextView text2 = (TextView)convertView.findViewById(android.R.id.text2);
+
+ text1.setText(action.getHeader());
+ text2.setText(action.getBody());
+
+ convertView.setTag(action);
+ return convertView;
+ }
+ });
+
+ // Make sure we resize to make room for ListView
+ mDecor.forceLayout();
+ mDecor.invalidate();
+
+ }
+ }
+
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ mMakePrimary = isChecked;
+ }
+
+ private void onBackPressed() {
+ // Back key will first dismiss any expanded resolve list, otherwise
+ // it will close the entire dialog.
+ if (mFooterDisambig.getVisibility() == View.VISIBLE) {
+ setResolveVisible(false, null);
+ mDecor.forceLayout();
+ mDecor.invalidate();
+ } else {
+ dismiss();
+ }
+ }
+
+ /** {@inheritDoc} */
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ if (mWindow.superDispatchKeyEvent(event)) {
+ return true;
+ }
+ return event.dispatch(this, mDecor != null
+ ? mDecor.getKeyDispatcherState() : null, this);
+ }
+
+ /** {@inheritDoc} */
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_BACK) {
+ event.startTracking();
+ return true;
+ }
+
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_BACK && event.isTracking()
+ && !event.isCanceled()) {
+ onBackPressed();
+ return true;
+ }
+
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ public boolean onKeyLongPress(int keyCode, KeyEvent event) {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ public boolean onKeyMultiple(int keyCode, int count, KeyEvent event) {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ // TODO: make this window accessible
+ return false;
+ }
+
+ /**
+ * Detect if the given {@link MotionEvent} is outside the boundaries of this
+ * window, which usually means we should dismiss.
+ */
+ protected void detectEventOutside(MotionEvent event) {
+ if (event.getAction() == MotionEvent.ACTION_DOWN) {
+ // Only try detecting outside events on down-press
+ mDecor.getHitRect(mRect);
+ mRect.top = mRect.top + mShadowTouch;
+ mRect.bottom = mRect.bottom - mShadowTouch;
+ final int x = (int)event.getX();
+ final int y = (int)event.getY();
+ if (!mRect.contains(x, y)) {
+ event.setAction(MotionEvent.ACTION_OUTSIDE);
+ }
+ }
+ }
+
+ /** {@inheritDoc} */
+ public boolean dispatchTouchEvent(MotionEvent event) {
+ detectEventOutside(event);
+ if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
+ dismiss();
+ return true;
+ } else {
+ return mWindow.superDispatchTouchEvent(event);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public boolean dispatchTrackballEvent(MotionEvent event) {
+ return mWindow.superDispatchTrackballEvent(event);
+ }
+
+ /** {@inheritDoc} */
+ public void onContentChanged() {
+ }
+
+ /** {@inheritDoc} */
+ public boolean onCreatePanelMenu(int featureId, Menu menu) {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ public View onCreatePanelView(int featureId) {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ public boolean onMenuItemSelected(int featureId, MenuItem item) {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ public boolean onMenuOpened(int featureId, Menu menu) {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ public void onPanelClosed(int featureId, Menu menu) {
+ }
+
+ /** {@inheritDoc} */
+ public boolean onPreparePanel(int featureId, View view, Menu menu) {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ public boolean onSearchRequested() {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ public void onWindowAttributesChanged(android.view.WindowManager.LayoutParams attrs) {
+ if (mDecor != null) {
+ mWindowManager.updateViewLayout(mDecor, attrs);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void onWindowFocusChanged(boolean hasFocus) {
+ }
+
+ /** {@inheritDoc} */
+ public void onQueryEntitiesComplete(int token, Object cookie, EntityIterator iterator) {
+ // No actions
+ }
+
+ /** {@inheritDoc} */
+ public void onAttachedToWindow() {
+ // No actions
+ }
+
+ /** {@inheritDoc} */
+ public void onDetachedFromWindow() {
+ // No actions
+ }
+
+ private interface DataQuery {
+ final String[] PROJECTION = new String[] {
+ Data._ID,
+
+ RawContacts.ACCOUNT_TYPE,
+ Contacts.STARRED,
+ Contacts.DISPLAY_NAME,
+ Contacts.CONTACT_PRESENCE,
+
+ Data.STATUS,
+ Data.STATUS_RES_PACKAGE,
+ Data.STATUS_ICON,
+ Data.STATUS_LABEL,
+ Data.STATUS_TIMESTAMP,
+ Data.PRESENCE,
+
+ Data.RES_PACKAGE,
+ Data.MIMETYPE,
+ Data.IS_PRIMARY,
+ Data.IS_SUPER_PRIMARY,
+ Data.RAW_CONTACT_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,
+ };
+
+ final int _ID = 0;
+
+ final int ACCOUNT_TYPE = 1;
+ final int STARRED = 2;
+ final int DISPLAY_NAME = 3;
+ final int CONTACT_PRESENCE = 4;
+
+ final int STATUS = 5;
+ final int STATUS_RES_PACKAGE = 6;
+ final int STATUS_ICON = 7;
+ final int STATUS_LABEL = 8;
+ final int STATUS_TIMESTAMP = 9;
+ final int PRESENCE = 10;
+
+ final int RES_PACKAGE = 11;
+ final int MIMETYPE = 12;
+ final int IS_PRIMARY = 13;
+ final int IS_SUPER_PRIMARY = 14;
+ }
+}
diff --git a/src/com/android/contacts/ui/ShowOrCreateActivity.java b/src/com/android/contacts/ui/ShowOrCreateActivity.java
new file mode 100755
index 0000000..7728b36
--- /dev/null
+++ b/src/com/android/contacts/ui/ShowOrCreateActivity.java
@@ -0,0 +1,254 @@
+/*
+ * 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.R;
+import com.android.contacts.util.Constants;
+import com.android.contacts.util.NotifyingAsyncQueryHandler;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.ComponentName;
+import android.content.ContentUris;
+import android.content.DialogInterface;
+import android.content.EntityIterator;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Intents;
+import android.provider.ContactsContract.PhoneLookup;
+import android.provider.ContactsContract.RawContacts;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.util.Log;
+
+/**
+ * Handle several edge cases around showing or possibly creating contacts in
+ * connected with a specific E-mail address or phone number. Will search based
+ * on incoming {@link Intent#getData()} as described by
+ * {@link Intents#SHOW_OR_CREATE_CONTACT}.
+ * <ul>
+ * <li>If no matching contacts found, will prompt user with dialog to add to a
+ * contact, then will use {@link Intent#ACTION_INSERT_OR_EDIT} to let create new
+ * contact or edit new data into an existing one.
+ * <li>If one matching contact found, directly show {@link Intent#ACTION_VIEW}
+ * that specific contact.
+ * <li>If more than one matching found, show list of matching contacts using
+ * {@link Intent#ACTION_SEARCH}.
+ * </ul>
+ */
+public final class ShowOrCreateActivity extends Activity implements
+ NotifyingAsyncQueryHandler.AsyncQueryListener {
+ static final String TAG = "ShowOrCreateActivity";
+ static final boolean LOGD = false;
+
+ static final String[] PHONES_PROJECTION = new String[] {
+ PhoneLookup._ID,
+ };
+
+ static final String[] CONTACTS_PROJECTION = new String[] {
+ RawContacts.CONTACT_ID,
+ };
+
+ static final int CONTACT_ID_INDEX = 0;
+
+ static final int CREATE_CONTACT_DIALOG = 1;
+
+ static final int QUERY_TOKEN = 42;
+
+ private NotifyingAsyncQueryHandler mQueryHandler;
+
+ private Bundle mCreateExtras;
+ private String mCreateDescrip;
+ private boolean mCreateForce;
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ // Create handler if doesn't exist, otherwise cancel any running
+ if (mQueryHandler == null) {
+ mQueryHandler = new NotifyingAsyncQueryHandler(this, this);
+ } else {
+ mQueryHandler.cancelOperation(QUERY_TOKEN);
+ }
+
+ final Intent intent = getIntent();
+ final Uri data = intent.getData();
+
+ // Unpack scheme and target data from intent
+ String scheme = null;
+ String ssp = null;
+ if (data != null) {
+ scheme = data.getScheme();
+ ssp = data.getSchemeSpecificPart();
+ }
+
+ // Build set of extras for possible use when creating contact
+ mCreateExtras = new Bundle();
+ Bundle originalExtras = intent.getExtras();
+ if (originalExtras != null) {
+ mCreateExtras.putAll(originalExtras);
+ }
+
+ // Read possible extra with specific title
+ mCreateDescrip = intent.getStringExtra(Intents.EXTRA_CREATE_DESCRIPTION);
+ if (mCreateDescrip == null) {
+ mCreateDescrip = ssp;
+ }
+
+ // Allow caller to bypass dialog prompt
+ mCreateForce = intent.getBooleanExtra(Intents.EXTRA_FORCE_CREATE, false);
+
+ // Handle specific query request
+ if (Constants.SCHEME_MAILTO.equals(scheme)) {
+ mCreateExtras.putString(Intents.Insert.EMAIL, ssp);
+
+ Uri uri = Uri.withAppendedPath(Email.CONTENT_FILTER_URI, Uri.encode(ssp));
+ mQueryHandler.startQuery(QUERY_TOKEN, null, uri, CONTACTS_PROJECTION, null, null, null);
+
+ } else if (Constants.SCHEME_TEL.equals(scheme)) {
+ mCreateExtras.putString(Intents.Insert.PHONE, ssp);
+
+ Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, ssp);
+ mQueryHandler.startQuery(QUERY_TOKEN, null, uri, PHONES_PROJECTION, null, null, null);
+
+ } else {
+ Log.w(TAG, "Invalid intent:" + getIntent());
+ finish();
+ }
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ if (mQueryHandler != null) {
+ mQueryHandler.cancelOperation(QUERY_TOKEN);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void onQueryComplete(int token, Object cookie, Cursor cursor) {
+ if (cursor == null) {
+ // Bail when problem running query in background
+ finish();
+ return;
+ }
+
+ // Count contacts found by query
+ int count = 0;
+ long contactId = -1;
+ try {
+ count = cursor.getCount();
+ if (count == 1 && cursor.moveToFirst()) {
+ // Try reading ID if only one contact returned
+ contactId = cursor.getLong(CONTACT_ID_INDEX);
+ }
+ } finally {
+ cursor.close();
+ }
+
+ if (count == 1 && contactId != -1) {
+ // If we only found one item, jump right to viewing it
+ final Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
+ final Intent viewIntent = new Intent(Intent.ACTION_VIEW, contactUri);
+ startActivity(viewIntent);
+ finish();
+
+ } 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.putExtras(mCreateExtras);
+ startActivity(listIntent);
+ finish();
+
+ } else {
+ // No matching contacts found
+ if (mCreateForce) {
+ // Forced to create new contact
+ Intent createIntent = new Intent(Intent.ACTION_INSERT, RawContacts.CONTENT_URI);
+ createIntent.putExtras(mCreateExtras);
+ createIntent.setType(RawContacts.CONTENT_TYPE);
+
+ startActivity(createIntent);
+ finish();
+
+ } else {
+ showDialog(CREATE_CONTACT_DIALOG);
+ }
+ }
+ }
+
+ @Override
+ protected Dialog onCreateDialog(int id) {
+ switch(id) {
+ case CREATE_CONTACT_DIALOG:
+ // Prompt user to insert or edit contact
+ final Intent createIntent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
+ createIntent.putExtras(mCreateExtras);
+ createIntent.setType(RawContacts.CONTENT_ITEM_TYPE);
+
+ final CharSequence message = getResources().getString(
+ R.string.add_contact_dlg_message_fmt, mCreateDescrip);
+
+ return new AlertDialog.Builder(this)
+ .setTitle(R.string.add_contact_dlg_title)
+ .setMessage(message)
+ .setPositiveButton(android.R.string.ok,
+ new IntentClickListener(this, createIntent))
+ .setNegativeButton(android.R.string.cancel,
+ new IntentClickListener(this, null))
+ .create();
+ }
+ return super.onCreateDialog(id);
+ }
+
+ /** {@inheritDoc} */
+ public void onQueryEntitiesComplete(int token, Object cookie, EntityIterator iterator) {
+ // No actions
+ }
+
+ /**
+ * Listener for {@link DialogInterface} that launches a given {@link Intent}
+ * when clicked. When clicked, this also closes the parent using
+ * {@link Activity#finish()}.
+ */
+ private static class IntentClickListener implements DialogInterface.OnClickListener {
+ private Activity mParent;
+ private Intent mIntent;
+
+ /**
+ * @param parent {@link Activity} to use for launching target.
+ * @param intent Target {@link Intent} to launch when clicked.
+ */
+ public IntentClickListener(Activity parent, Intent intent) {
+ mParent = parent;
+ mIntent = intent;
+ }
+
+ public void onClick(DialogInterface dialog, int which) {
+ if (mIntent != null) {
+ mParent.startActivity(mIntent);
+ }
+ mParent.finish();
+ }
+ }
+}
diff --git a/src/com/android/contacts/ui/widget/CheckableImageView.java b/src/com/android/contacts/ui/widget/CheckableImageView.java
new file mode 100644
index 0000000..ff5abc0
--- /dev/null
+++ b/src/com/android/contacts/ui/widget/CheckableImageView.java
@@ -0,0 +1,63 @@
+/*
+ * 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.ui.widget;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.Checkable;
+import android.widget.ImageView;
+
+/**
+ * A special variation of ImageView that can be used as a checkable object.
+ * This is used as the background view of quickcontact chiclet, which is in checked state
+ * when disambig list is shown. Otherwise, it works identically to a ImageView.
+ */
+public class CheckableImageView extends ImageView implements Checkable {
+ private boolean mChecked;
+
+ private static final int[] CHECKED_STATE_SET = {
+ android.R.attr.state_checked
+ };
+
+ public CheckableImageView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ public int[] onCreateDrawableState(int extraSpace) {
+ final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
+ if (isChecked()) {
+ mergeDrawableStates(drawableState, CHECKED_STATE_SET);
+ }
+ return drawableState;
+ }
+
+ public void toggle() {
+ setChecked(!mChecked);
+ }
+
+ public boolean isChecked() {
+ return mChecked;
+ }
+
+ public void setChecked(boolean checked) {
+ if (mChecked != checked) {
+ mChecked = checked;
+ refreshDrawableState();
+ }
+ }
+}
diff --git a/src/com/android/contacts/ui/widget/ContactEditorView.java b/src/com/android/contacts/ui/widget/ContactEditorView.java
new file mode 100644
index 0000000..8bd8e4c
--- /dev/null
+++ b/src/com/android/contacts/ui/widget/ContactEditorView.java
@@ -0,0 +1,281 @@
+/*
+ * 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 com.android.contacts.R;
+import com.android.contacts.model.ContactsSource;
+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.EditType;
+import com.android.contacts.model.Editor.EditorListener;
+import com.android.contacts.model.EntityDelta.ValuesDelta;
+
+import android.content.Context;
+import android.content.Entity;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.RawContacts;
+import android.provider.ContactsContract.CommonDataKinds.Photo;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.View.OnClickListener;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+/**
+ * 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)}.
+ * <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 LinearLayout implements OnClickListener {
+ private LayoutInflater mInflater;
+
+ private TextView mReadOnly;
+ private TextView mReadOnlyName;
+
+ private PhotoEditorView mPhoto;
+ private GenericEditorView mName;
+
+ private boolean mHasPhotoEditor = false;
+
+ private ViewGroup mGeneral;
+ private ViewGroup mSecondary;
+
+ private TextView mSecondaryHeader;
+
+ private Drawable mSecondaryOpen;
+ private Drawable mSecondaryClosed;
+
+ private View mHeaderColorBar;
+ private View mSideBar;
+ private ImageView mHeaderIcon;
+ private TextView mHeaderAccountType;
+ private TextView mHeaderAccountName;
+
+ private long mRawContactId = -1;
+
+ public ContactEditorView(Context context) {
+ super(context);
+ }
+
+ public ContactEditorView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+
+ mInflater = (LayoutInflater)getContext().getSystemService(
+ Context.LAYOUT_INFLATER_SERVICE);
+
+ mPhoto = (PhotoEditorView)findViewById(R.id.edit_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);
+
+ 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);
+ 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);
+ }
+
+ /**
+ * Assign the given {@link Bitmap} to the internal {@link PhotoEditorView}
+ * for the {@link EntityDelta} currently being edited.
+ */
+ public void setPhotoBitmap(Bitmap bitmap) {
+ mPhoto.setPhotoBitmap(bitmap);
+ }
+
+ /**
+ * Return true if the current {@link RawContacts} supports {@link Photo},
+ * which means that {@link PhotoEditorView} is enabled.
+ */
+ public boolean hasPhotoEditor() {
+ return mHasPhotoEditor;
+ }
+
+ /**
+ * Return true if internal {@link PhotoEditorView} has a {@link Photo} set.
+ */
+ public boolean hasSetPhoto() {
+ return mPhoto.hasSetPhoto();
+ }
+
+ public PhotoEditorView getPhotoEditor() {
+ return mPhoto;
+ }
+
+ /** {@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.
+ */
+ private void setSecondaryVisible(boolean makeVisible) {
+ mSecondary.setVisibility(makeVisible ? View.VISIBLE : View.GONE);
+ mSecondaryHeader.setCompoundDrawablesWithIntrinsicBounds(makeVisible ? mSecondaryOpen
+ : mSecondaryClosed, null, null, null);
+ }
+
+ /**
+ * Set the internal state for this view, given a current
+ * {@link EntityDelta} state and the {@link ContactsSource} that
+ * apply to that state.
+ */
+ public void setState(EntityDelta state, ContactsSource source) {
+ // Remove any existing sections
+ mGeneral.removeAllViews();
+ mSecondary.removeAllViews();
+
+ // Bail if invalid state or source
+ if (state == null || source == null) return;
+
+ // Make sure we have StructuredName
+ EntityModifier.ensureKindExists(state, source, StructuredName.CONTENT_ITEM_TYPE);
+
+ // Fill in the header info
+ mHeaderColorBar.setBackgroundColor(source.getHeaderColor(mContext));
+ mSideBar.setBackgroundColor(source.getSideBarColor(mContext));
+ ValuesDelta values = state.getValues();
+ String accountName = values.getAsString(RawContacts.ACCOUNT_NAME);
+ CharSequence accountType = source.getDisplayLabel(mContext);
+ if (TextUtils.isEmpty(accountType)) {
+ accountType = mContext.getString(R.string.account_phone);
+ }
+ if (!TextUtils.isEmpty(accountName)) {
+ mHeaderAccountName.setText(
+ mContext.getString(R.string.from_account_format, accountName));
+ }
+ mHeaderAccountType.setText(mContext.getString(R.string.account_type_format, accountType));
+ mHeaderIcon.setImageDrawable(source.getDisplayIcon(mContext));
+
+ mRawContactId = values.getAsLong(RawContacts._ID);
+
+ // Show photo editor when supported
+ 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(!source.readOnly);
+ mName.setEnabled(!source.readOnly);
+
+ boolean readOnly = source.readOnly;
+ // Show and hide the appropriate views
+ if (readOnly) {
+ mGeneral.setVisibility(View.GONE);
+ mSecondary.setVisibility(View.GONE);
+ mSecondaryHeader.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);
+ mSecondary.setVisibility(View.VISIBLE);
+ mSecondaryHeader.setVisibility(View.VISIBLE);
+ mName.setVisibility(View.VISIBLE);
+ mReadOnly.setVisibility(View.GONE);
+ mReadOnlyName.setVisibility(View.GONE);
+ }
+
+ // Create editor sections for each possible data kind
+ for (DataKind kind : source.getSortedDataKinds()) {
+ // Skip kind of not editable
+ if (!kind.editable) continue;
+
+ final String mimeType = kind.mimeType;
+ if (StructuredName.CONTENT_ITEM_TYPE.equals(mimeType)) {
+ // Handle special case editor for structured name
+ final ValuesDelta primary = state.getPrimaryEntry(mimeType);
+ if (!readOnly) {
+ mName.setValues(kind, primary, state, source.readOnly);
+ } else {
+ String displayName = primary.getAsString(StructuredName.DISPLAY_NAME);
+ mReadOnlyName.setText(displayName);
+ }
+ } 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, source.readOnly);
+ } else if (!readOnly) {
+ // 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, source.readOnly);
+ section.setId(kind.weight);
+ parent.addView(section);
+ }
+ }
+ final int secondaryVisibility = mSecondary.getChildCount() > 0 ? View.VISIBLE : View.GONE;
+ mSecondary.setVisibility(secondaryVisibility);
+ mSecondaryHeader.setVisibility(secondaryVisibility);
+ }
+
+ /**
+ * Sets the {@link EditorListener} on the name field
+ */
+ public void setNameEditorListener(EditorListener listener) {
+ mName.setEditorListener(listener);
+ }
+
+ public long getRawContactId() {
+ return mRawContactId;
+ }
+}
diff --git a/src/com/android/contacts/ui/widget/DontPressWithParentImageView.java b/src/com/android/contacts/ui/widget/DontPressWithParentImageView.java
new file mode 100644
index 0000000..bdb0e0a
--- /dev/null
+++ b/src/com/android/contacts/ui/widget/DontPressWithParentImageView.java
@@ -0,0 +1,42 @@
+/*
+ * 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
new file mode 100644
index 0000000..8b50551
--- /dev/null
+++ b/src/com/android/contacts/ui/widget/GenericEditorView.java
@@ -0,0 +1,360 @@
+/*
+ * 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 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.EntityDelta.ValuesDelta;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Entity;
+import android.text.Editable;
+import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.util.AttributeSet;
+import android.view.ContextThemeWrapper;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.inputmethod.EditorInfo;
+import android.widget.ArrayAdapter;
+import android.widget.EditText;
+import android.widget.ListAdapter;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import java.util.List;
+
+/**
+ * Simple editor that handles labels and any {@link EditField} defined for
+ * 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;
+
+ protected LayoutInflater mInflater;
+
+ protected 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 DataKind mKind;
+ protected ValuesDelta mEntry;
+ protected EntityDelta mState;
+ protected boolean mReadOnly;
+
+ protected boolean mHideOptional = true;
+
+ protected EditType mType;
+ // Used only when a user tries to use custom label.
+ private EditType mPendingType;
+
+ public GenericEditorView(Context context) {
+ super(context);
+ }
+
+ public GenericEditorView(Context context, AttributeSet attrs) {
+ 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);
+ }
+
+ protected EditorListener mListener;
+
+ public void setEditorListener(EditorListener listener) {
+ mListener = listener;
+ }
+
+ public void setDeletable(boolean deletable) {
+ mDelete.setVisibility(deletable ? View.VISIBLE : View.INVISIBLE);
+ }
+
+ @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);
+ }
+ mMore.setEnabled(enabled);
+ }
+
+ /**
+ * Build the current label state based on selected {@link EditType} and
+ * possible custom label string.
+ */
+ private void rebuildLabel() {
+ // Handle undetected types
+ if (mType == null) {
+ mLabel.setText(R.string.unknown);
+ return;
+ }
+
+ if (mType.customColumn != null) {
+ // Use custom label string when present
+ final String customText = mEntry.getAsString(mType.customColumn);
+ if (customText != null) {
+ mLabel.setText(customText);
+ return;
+ }
+ }
+
+ // Otherwise fall back to using default label
+ mLabel.setText(mType.labelRes);
+ }
+
+ /** {@inheritDoc} */
+ public void onFieldChanged(String column, String value) {
+ // Field changes are saved directly
+ mEntry.put(column, value);
+ if (mListener != null) {
+ mListener.onRequest(EditorListener.FIELD_CHANGED);
+ }
+ }
+
+ private void rebuildValues() {
+ setValues(mKind, mEntry, mState, mReadOnly);
+ }
+
+ /**
+ * Prepare this editor using the given {@link DataKind} for defining
+ * structure and {@link ValuesDelta} describing the content to edit.
+ */
+ public void setValues(DataKind kind, ValuesDelta entry, EntityDelta state, boolean readOnly) {
+ mKind = kind;
+ mEntry = entry;
+ mState = state;
+ mReadOnly = readOnly;
+
+ final boolean enabled = !readOnly;
+
+ if (!entry.isVisible()) {
+ // Hide ourselves entirely if deleted
+ setVisibility(View.GONE);
+ return;
+ } else {
+ setVisibility(View.VISIBLE);
+ }
+
+ // Display label selector if multiple types available
+ final boolean hasTypes = EntityModifier.hasEditTypes(kind);
+ mLabel.setVisibility(hasTypes ? View.VISIBLE : View.GONE);
+ mLabel.setEnabled(enabled);
+ if (hasTypes) {
+ mType = EntityModifier.getCurrentType(entry, kind);
+ rebuildLabel();
+ }
+
+ // Build out set of fields
+ mFields.removeAllViews();
+ boolean hidePossible = false;
+ for (EditField field : kind.fieldList) {
+ // Inflate field from definition
+ EditText fieldView = (EditText)mInflater.inflate(RES_FIELD, mFields, false);
+ if (field.titleRes > 0) {
+ fieldView.setHint(field.titleRes);
+ }
+ fieldView.setInputType(field.inputType);
+ fieldView.setMinLines(field.minLines);
+
+ // Read current value from state
+ final String column = field.column;
+ final String value = entry.getAsString(column);
+ fieldView.setText(value);
+
+ // Prepare listener for writing changes
+ fieldView.addTextChangedListener(new TextWatcher() {
+ public void afterTextChanged(Editable s) {
+ // Trigger event for newly changed value
+ onFieldChanged(column, s.toString());
+ }
+
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ }
+
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ }
+ });
+
+ // 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);
+ }
+
+ // When hiding fields, place expandable
+ mMore.setVisibility(hidePossible ? View.VISIBLE : View.GONE);
+ mMore.setEnabled(enabled);
+ }
+
+ /**
+ * Prepare dialog for entering a custom label. The input value is trimmed: white spaces before
+ * and after the input text is removed.
+ * <p>
+ * If the final value is empty, this change request is ignored;
+ * no empty text is allowed in any custom label.
+ */
+ private Dialog createCustomDialog() {
+ final EditText customType = new EditText(mContext);
+ customType.setInputType(INPUT_TYPE_CUSTOM);
+ customType.requestFocus();
+
+ final AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
+ builder.setTitle(R.string.customLabelPickerTitle);
+ builder.setView(customType);
+
+ builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ final String customText = customType.getText().toString().trim();
+ if (ContactsUtils.isGraphic(customText)) {
+ // Now we're sure it's ok to actually change the type value.
+ mType = mPendingType;
+ mPendingType = null;
+ mEntry.put(mKind.typeColumn, mType.rawValue);
+ mEntry.put(mType.customColumn, customText);
+ rebuildLabel();
+ }
+ }
+ });
+
+ builder.setNegativeButton(android.R.string.cancel, null);
+
+ return builder.create();
+ }
+
+ /**
+ * 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 createLabelDialog() {
+ // Build list of valid types, including the current value
+ final List<EditType> validTypes = EntityModifier.getValidTypes(mState, mKind, mType);
+
+ // 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 ListAdapter typeAdapter = new ArrayAdapter<EditType>(mContext, RES_LABEL_ITEM,
+ validTypes) {
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ if (convertView == null) {
+ convertView = dialogInflater.inflate(RES_LABEL_ITEM, parent, false);
+ }
+
+ final EditType type = this.getItem(position);
+ final TextView textView = (TextView)convertView;
+ textView.setText(type.labelRes);
+ return textView;
+ }
+ };
+
+ final DialogInterface.OnClickListener clickListener =
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.dismiss();
+
+ final EditType selected = validTypes.get(which);
+ if (selected.customColumn != null) {
+ // Show custom label dialog if requested by type.
+ //
+ // 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();
+ } 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();
+ }
+ }
+ };
+
+ final AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
+ builder.setTitle(R.string.selectLabel);
+ builder.setSingleChoiceItems(typeAdapter, 0, clickListener);
+ 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();
+
+ // Remove editor from parent view
+ final ViewGroup parent = (ViewGroup)getParent();
+ parent.removeView(this);
+
+ if (mListener != null) {
+ // Notify listener when present
+ mListener.onDeleted(this);
+ }
+ break;
+ }
+ case R.id.edit_more: {
+ mHideOptional = !mHideOptional;
+ rebuildValues();
+ break;
+ }
+ }
+ }
+}
diff --git a/src/com/android/contacts/ui/widget/KindSectionView.java b/src/com/android/contacts/ui/widget/KindSectionView.java
new file mode 100644
index 0000000..b52cfd0
--- /dev/null
+++ b/src/com/android/contacts/ui/widget/KindSectionView.java
@@ -0,0 +1,146 @@
+/*
+ * 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 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.EditorListener;
+import com.android.contacts.model.EntityDelta.ValuesDelta;
+
+import android.content.Context;
+import android.provider.ContactsContract.Data;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.View.OnClickListener;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+/**
+ * Custom view for an entire section of data as segmented by
+ * {@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 {
+ private static final String TAG = "KindSectionView";
+
+ private LayoutInflater mInflater;
+
+ private ViewGroup mEditors;
+ private View mAdd;
+ private TextView mTitle;
+
+ private DataKind mKind;
+ private EntityDelta mState;
+ private boolean mReadOnly;
+
+ public KindSectionView(Context context) {
+ super(context);
+ }
+
+ public KindSectionView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ /** {@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);
+
+ mTitle = (TextView)findViewById(R.id.kind_title);
+ }
+
+ /** {@inheritDoc} */
+ public void onDeleted(Editor editor) {
+ this.updateAddEnabled();
+ this.updateEditorsVisible();
+ }
+
+ /** {@inheritDoc} */
+ public void onRequest(int request) {
+ // Ignore requests
+ }
+
+ public void setState(DataKind kind, EntityDelta state, boolean readOnly) {
+ mKind = kind;
+ mState = state;
+ mReadOnly = readOnly;
+
+ // TODO: handle resources from remote packages
+ mTitle.setText(kind.titleRes);
+
+ this.rebuildFromState();
+ this.updateAddEnabled();
+ this.updateEditorsVisible();
+ }
+
+ /**
+ * Build editors for all current {@link #mState} rows.
+ */
+ public void rebuildFromState() {
+ // Remove any existing editors
+ mEditors.removeAllViews();
+
+ // Build individual editors for each entry
+ if (!mState.hasMimeEntries(mKind.mimeType)) return;
+ for (ValuesDelta entry : mState.getMimeEntries(mKind.mimeType)) {
+ // Skip entries that aren't visible
+ if (!entry.isVisible()) continue;
+
+ final GenericEditorView editor = (GenericEditorView)mInflater.inflate(
+ R.layout.item_generic_editor, mEditors, false);
+ editor.setValues(mKind, entry, mState, mReadOnly);
+ editor.setEditorListener(this);
+ editor.setId(entry.getViewId());
+ mEditors.addView(editor);
+ }
+ }
+
+ protected void updateEditorsVisible() {
+ final boolean hasChildren = mEditors.getChildCount() > 0;
+ mEditors.setVisibility(hasChildren ? View.VISIBLE : View.GONE);
+ }
+
+ protected void updateAddEnabled() {
+ // Set enabled state on the "add" view
+ final boolean canInsert = EntityModifier.canInsert(mState, mKind);
+ final boolean isEnabled = !mReadOnly && canInsert;
+ mAdd.setEnabled(isEnabled);
+ }
+
+ /** {@inheritDoc} */
+ public void onClick(View v) {
+ // Insert a new child and rebuild
+ EntityModifier.insertChild(mState, mKind);
+ this.rebuildFromState();
+ this.updateAddEnabled();
+ this.updateEditorsVisible();
+ }
+}
diff --git a/src/com/android/contacts/ui/widget/PhotoEditorView.java b/src/com/android/contacts/ui/widget/PhotoEditorView.java
new file mode 100644
index 0000000..7dfe3d7
--- /dev/null
+++ b/src/com/android/contacts/ui/widget/PhotoEditorView.java
@@ -0,0 +1,147 @@
+/*
+ * 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 com.android.contacts.R;
+import com.android.contacts.model.EntityDelta;
+import com.android.contacts.model.ContactsSource.DataKind;
+import com.android.contacts.model.EntityDelta.ValuesDelta;
+import com.android.contacts.model.Editor;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.provider.ContactsContract.CommonDataKinds.Photo;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.ImageView;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+/**
+ * Simple editor for {@link Photo}.
+ */
+public class PhotoEditorView extends ImageView implements Editor, OnClickListener {
+ private static final String TAG = "PhotoEditorView";
+
+ private ValuesDelta mEntry;
+ private EditorListener mListener;
+
+ private boolean mHasSetPhoto = false;
+
+ public PhotoEditorView(Context context) {
+ super(context);
+ }
+
+ public PhotoEditorView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ this.setOnClickListener(this);
+ }
+
+ /** {@inheritDoc} */
+ public void onClick(View v) {
+ if (mListener != null) {
+ mListener.onRequest(EditorListener.REQUEST_PICK_PHOTO);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void onFieldChanged(String column, String value) {
+ throw new UnsupportedOperationException("Photos don't support direct field changes");
+ }
+
+ /** {@inheritDoc} */
+ public void setValues(DataKind kind, ValuesDelta values, EntityDelta state, boolean readOnly) {
+ mEntry = values;
+ if (values != null) {
+ // Try decoding photo if actual entry
+ final byte[] photoBytes = values.getAsByteArray(Photo.PHOTO);
+ if (photoBytes != null) {
+ final Bitmap photo = BitmapFactory.decodeByteArray(photoBytes, 0,
+ photoBytes.length);
+
+ setScaleType(ImageView.ScaleType.CENTER_CROP);
+ setImageBitmap(photo);
+ setEnabled(!readOnly);
+ mHasSetPhoto = true;
+ mEntry.setFromTemplate(false);
+ } else {
+ resetDefault();
+ }
+ } else {
+ resetDefault();
+ }
+ }
+
+ /**
+ * Return true if a valid {@link Photo} has been set.
+ */
+ public boolean hasSetPhoto() {
+ return mHasSetPhoto;
+ }
+
+ /**
+ * Assign the given {@link Bitmap} as the new value, updating UI and
+ * readying for persisting through {@link ValuesDelta}.
+ */
+ public void setPhotoBitmap(Bitmap photo) {
+ if (photo == null) {
+ // Clear any existing photo and return
+ mEntry.put(Photo.PHOTO, (byte[])null);
+ resetDefault();
+ return;
+ }
+
+ final int size = photo.getWidth() * photo.getHeight() * 4;
+ final ByteArrayOutputStream out = new ByteArrayOutputStream(size);
+
+ try {
+ photo.compress(Bitmap.CompressFormat.PNG, 100, out);
+ out.flush();
+ out.close();
+
+ mEntry.put(Photo.PHOTO, out.toByteArray());
+ setImageBitmap(photo);
+ mHasSetPhoto = true;
+ mEntry.setFromTemplate(false);
+ } catch (IOException e) {
+ Log.w(TAG, "Unable to serialize photo: " + e.toString());
+ }
+ }
+
+ protected void resetDefault() {
+ // Invalid photo, show default "add photo" place-holder
+ setScaleType(ImageView.ScaleType.CENTER);
+ setImageResource(R.drawable.ic_menu_add_picture);
+ mHasSetPhoto = false;
+ mEntry.setFromTemplate(true);
+ }
+
+ /** {@inheritDoc} */
+ public void setEditorListener(EditorListener listener) {
+ mListener = listener;
+ }
+}
diff --git a/src/com/android/contacts/util/AccountSelectionUtil.java b/src/com/android/contacts/util/AccountSelectionUtil.java
new file mode 100644
index 0000000..cf83581
--- /dev/null
+++ b/src/com/android/contacts/util/AccountSelectionUtil.java
@@ -0,0 +1,170 @@
+/*
+ * 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.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.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 java.util.List;
+
+/**
+ * Utility class for selectiong an Account for importing contact(s)
+ */
+public class AccountSelectionUtil {
+ // TODO: maybe useful for EditContactActivity.java...
+ private static final String LOG_TAG = "AccountSelectionUtil";
+
+ private static class AccountSelectedListener
+ implements DialogInterface.OnClickListener, DialogInterface.OnCancelListener {
+
+ final private Context mContext;
+ final private List<Account> mAccountList;
+ final private int mResId;
+
+ public AccountSelectedListener(Context context, List<Account> accountList, int resId) {
+ if (accountList == null || accountList.size() == 0) {
+ Log.e(LOG_TAG, "The size of Account list is 0.");
+ }
+ mContext = context;
+ mAccountList = accountList;
+ mResId = resId;
+ }
+
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.dismiss();
+ doImport(mContext, mResId, mAccountList.get(which));
+ }
+
+ public void onCancel(DialogInterface dialog) {
+ dialog.dismiss();
+ }
+ }
+
+ public static Dialog getSelectAccountDialog(Context context, int resId) {
+ return getSelectAccountDialog(context, resId, null);
+ }
+
+ public static Dialog getSelectAccountDialog(Context context, int resId,
+ DialogInterface.OnCancelListener onCancelListener) {
+ final Sources sources = Sources.getInstance(context);
+ final List<Account> writableAccountList = sources.getAccounts(true);
+
+ // Assume accountList.size() > 1
+
+ // Wrap our context to inflate list items using correct theme
+ final Context dialogContext = new ContextThemeWrapper(
+ context, android.R.style.Theme_Light);
+ final LayoutInflater dialogInflater = (LayoutInflater)dialogContext
+ .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ final ArrayAdapter<Account> accountAdapter =
+ new ArrayAdapter<Account>(context, android.R.layout.simple_list_item_2,
+ writableAccountList) {
+
+ @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);
+ final Context context = getContext();
+
+ text1.setText(account.name);
+ text2.setText(source.getDisplayLabel(context));
+
+ return convertView;
+ }
+ };
+
+ AccountSelectedListener accountSelectedListener =
+ new AccountSelectedListener(context, writableAccountList, resId);
+ return new AlertDialog.Builder(context)
+ .setTitle(R.string.dialog_new_contact_account)
+ .setSingleChoiceItems(accountAdapter, 0, accountSelectedListener)
+ .setOnCancelListener(accountSelectedListener)
+ .create();
+ }
+
+ public static void doImport(Context context, int resId, Account account) {
+ switch (resId) {
+ case R.string.import_from_sim: {
+ doImportFromSim(context, account);
+ break;
+ }
+ case R.string.import_from_sdcard: {
+ doImportFromSdCard(context, account);
+ break;
+ }
+ }
+ }
+
+ 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) {
+ importIntent.putExtra("account_name", account.name);
+ importIntent.putExtra("account_type", account.type);
+ }
+ importIntent.setClassName("com.android.phone", "com.android.phone.SimContacts");
+ context.startActivity(importIntent);
+ }
+
+ public static void doImportFromSdCard(Context context, Account account) {
+ if (account != null) {
+ GoogleSource.createMyContactsIfNotExist(account, context);
+ }
+
+ Intent importIntent = new Intent(context, ImportVCardActivity.class);
+ if (account != null) {
+ importIntent.putExtra("account_name", account.name);
+ importIntent.putExtra("account_type", account.type);
+ }
+ context.startActivity(importIntent);
+ }
+}
diff --git a/src/com/android/contacts/util/Constants.java b/src/com/android/contacts/util/Constants.java
new file mode 100644
index 0000000..e0178ad
--- /dev/null
+++ b/src/com/android/contacts/util/Constants.java
@@ -0,0 +1,39 @@
+/*
+ * 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.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
+ * distinguishes actions that should initiate a text message.
+ */
+ public static final String MIME_SMS_ADDRESS = "vnd.android.cursor.item/sms-address";
+
+ public static final String SCHEME_TEL = "tel";
+ public static final String SCHEME_SMSTO = "smsto";
+ public static final String SCHEME_MAILTO = "mailto";
+ public static final String SCHEME_IMTO = "imto";
+
+}
diff --git a/src/com/android/contacts/util/DataStatus.java b/src/com/android/contacts/util/DataStatus.java
new file mode 100644
index 0000000..88c6594
--- /dev/null
+++ b/src/com/android/contacts/util/DataStatus.java
@@ -0,0 +1,159 @@
+/*
+ * 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.util;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.graphics.drawable.Drawable;
+import android.provider.ContactsContract.Data;
+import android.text.TextUtils;
+import android.text.format.DateUtils;
+
+/**
+ * Storage for a social status update. Holds a single update, but can use
+ * {@link #possibleUpdate(Cursor)} to consider updating when a better status
+ * exists. Statuses with timestamps, or with newer timestamps win.
+ */
+public class DataStatus {
+ private int mPresence = -1;
+ private String mStatus = null;
+ private long mTimestamp = -1;
+
+ private String mResPackage = null;
+ private int mIconRes = -1;
+ private int mLabelRes = -1;
+
+ public DataStatus() {
+ }
+
+ public DataStatus(Cursor cursor) {
+ // When creating from cursor row, fill normally
+ fromCursor(cursor);
+ }
+
+ /**
+ * Attempt updating this {@link DataStatus} based on values at the
+ * current row of the given {@link Cursor}.
+ */
+ public void possibleUpdate(Cursor cursor) {
+ final boolean hasStatus = !isNull(cursor, Data.STATUS);
+ final boolean hasTimestamp = !isNull(cursor, Data.STATUS_TIMESTAMP);
+
+ // Bail early when not valid status, or when previous status was
+ // found and we can't compare this one.
+ if (!hasStatus) return;
+ if (isValid() && !hasTimestamp) return;
+
+ if (hasTimestamp) {
+ // Compare timestamps and bail if older status
+ final long newTimestamp = getLong(cursor, Data.STATUS_TIMESTAMP, -1);
+ if (newTimestamp < mTimestamp) return;
+
+ mTimestamp = newTimestamp;
+ }
+
+ // Fill in remaining details from cursor
+ fromCursor(cursor);
+ }
+
+ private void fromCursor(Cursor cursor) {
+ mPresence = getInt(cursor, Data.PRESENCE, -1);
+ mStatus = getString(cursor, Data.STATUS);
+ mTimestamp = getLong(cursor, Data.STATUS_TIMESTAMP, -1);
+ mResPackage = getString(cursor, Data.STATUS_RES_PACKAGE);
+ mIconRes = getInt(cursor, Data.STATUS_ICON, -1);
+ mLabelRes = getInt(cursor, Data.STATUS_LABEL, -1);
+ }
+
+ public boolean isValid() {
+ return !TextUtils.isEmpty(mStatus);
+ }
+
+ public int getPresence() {
+ return mPresence;
+ }
+
+ public CharSequence getStatus() {
+ return mStatus;
+ }
+
+ /**
+ * Build any timestamp and label into a single string.
+ */
+ public CharSequence getTimestampLabel(Context context) {
+ final PackageManager pm = context.getPackageManager();
+
+ // Use local package for resources when none requested
+ if (mResPackage == null) mResPackage = context.getPackageName();
+
+ final boolean validTimestamp = mTimestamp > 0;
+ final boolean validLabel = mResPackage != null && mLabelRes != -1;
+
+ final CharSequence timeClause = validTimestamp ? DateUtils.getRelativeTimeSpanString(
+ mTimestamp, System.currentTimeMillis(), DateUtils.MINUTE_IN_MILLIS,
+ DateUtils.FORMAT_ABBREV_RELATIVE) : null;
+ final CharSequence labelClause = validLabel ? pm.getText(mResPackage, mLabelRes,
+ null) : null;
+
+ if (validTimestamp && validLabel) {
+ return context.getString(
+ com.android.internal.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,
+ labelClause);
+ } else if (validTimestamp) {
+ return timeClause;
+ } else {
+ return null;
+ }
+ }
+
+ public Drawable getIcon(Context context) {
+ final PackageManager pm = context.getPackageManager();
+
+ // Use local package for resources when none requested
+ if (mResPackage == null) mResPackage = context.getPackageName();
+
+ final boolean validIcon = mResPackage != null && mIconRes != -1;
+ return validIcon ? pm.getDrawable(mResPackage, mIconRes, null) : null;
+ }
+
+ private static String getString(Cursor cursor, String columnName) {
+ return cursor.getString(cursor.getColumnIndex(columnName));
+ }
+
+ private static int getInt(Cursor cursor, String columnName) {
+ return cursor.getInt(cursor.getColumnIndex(columnName));
+ }
+
+ private static int getInt(Cursor cursor, String columnName, int missingValue) {
+ final int columnIndex = cursor.getColumnIndex(columnName);
+ return cursor.isNull(columnIndex) ? missingValue : cursor.getInt(columnIndex);
+ }
+
+ private static long getLong(Cursor cursor, String columnName, long missingValue) {
+ final int columnIndex = cursor.getColumnIndex(columnName);
+ return cursor.isNull(columnIndex) ? missingValue : cursor.getLong(columnIndex);
+ }
+
+ private static boolean isNull(Cursor cursor, String columnName) {
+ return cursor.isNull(cursor.getColumnIndex(columnName));
+ }
+}
diff --git a/src/com/android/contacts/util/EmptyService.java b/src/com/android/contacts/util/EmptyService.java
new file mode 100644
index 0000000..2e6a159
--- /dev/null
+++ b/src/com/android/contacts/util/EmptyService.java
@@ -0,0 +1,33 @@
+/*
+ * 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.util;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+
+/**
+ * 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 EmptyService extends Service {
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+}
diff --git a/src/com/android/contacts/util/NotifyingAsyncQueryHandler.java b/src/com/android/contacts/util/NotifyingAsyncQueryHandler.java
new file mode 100644
index 0000000..795ac79
--- /dev/null
+++ b/src/com/android/contacts/util/NotifyingAsyncQueryHandler.java
@@ -0,0 +1,81 @@
+/*
+ * 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.util;
+
+import android.content.AsyncQueryHandler;
+import android.content.Context;
+import android.content.EntityIterator;
+import android.database.Cursor;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * Slightly more abstract {@link AsyncQueryHandler} that helps keep a
+ * {@link WeakReference} back to a listener. Will properly close any
+ * {@link Cursor} or {@link EntityIterator} if the listener ceases to exist.
+ * <p>
+ * This pattern can be used to perform background queries without leaking
+ * {@link Context} objects.
+ *
+ * @hide pending API council review
+ */
+public class NotifyingAsyncQueryHandler extends AsyncQueryHandler {
+ private WeakReference<AsyncQueryListener> mListener;
+
+ /**
+ * Interface to listen for completed query operations.
+ */
+ public interface AsyncQueryListener {
+ void onQueryComplete(int token, Object cookie, Cursor cursor);
+ void onQueryEntitiesComplete(int token, Object cookie, EntityIterator iterator);
+ }
+
+ public NotifyingAsyncQueryHandler(Context context, AsyncQueryListener listener) {
+ super(context.getContentResolver());
+ setQueryListener(listener);
+ }
+
+ /**
+ * Assign the given {@link AsyncQueryListener} to receive query events from
+ * asynchronous calls. Will replace any existing listener.
+ */
+ public void setQueryListener(AsyncQueryListener listener) {
+ mListener = new WeakReference<AsyncQueryListener>(listener);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
+ final AsyncQueryListener listener = mListener.get();
+ if (listener != null) {
+ listener.onQueryComplete(token, cookie, cursor);
+ } else if (cursor != null) {
+ cursor.close();
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void onQueryEntitiesComplete(int token, Object cookie, EntityIterator iterator) {
+ final AsyncQueryListener listener = mListener.get();
+ if (listener != null) {
+ listener.onQueryEntitiesComplete(token, cookie, iterator);
+ } else if (iterator != null) {
+ iterator.close();
+ }
+ }
+}
diff --git a/src/com/android/contacts/util/WeakAsyncTask.java b/src/com/android/contacts/util/WeakAsyncTask.java
new file mode 100644
index 0000000..f60cfd7
--- /dev/null
+++ b/src/com/android/contacts/util/WeakAsyncTask.java
@@ -0,0 +1,69 @@
+/*
+ * 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.util;
+
+import android.os.AsyncTask;
+
+import java.lang.ref.WeakReference;
+
+public abstract class WeakAsyncTask<Params, Progress, Result, WeakTarget> extends
+ AsyncTask<Params, Progress, Result> {
+ protected WeakReference<WeakTarget> mTarget;
+
+ public WeakAsyncTask(WeakTarget target) {
+ mTarget = new WeakReference<WeakTarget>(target);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected final void onPreExecute() {
+ final WeakTarget target = mTarget.get();
+ if (target != null) {
+ this.onPreExecute(target);
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected final Result doInBackground(Params... params) {
+ final WeakTarget target = mTarget.get();
+ if (target != null) {
+ return this.doInBackground(target, params);
+ } else {
+ return null;
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected final void onPostExecute(Result result) {
+ final WeakTarget target = mTarget.get();
+ if (target != null) {
+ this.onPostExecute(target, result);
+ }
+ }
+
+ protected void onPreExecute(WeakTarget target) {
+ // No default action
+ }
+
+ protected abstract Result doInBackground(WeakTarget target, Params... params);
+
+ protected void onPostExecute(WeakTarget target, Result result) {
+ // No default action
+ }
+}
diff --git a/tests/Android.mk b/tests/Android.mk
index 47782e3..ef11c5e 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -3,7 +3,7 @@
# We only want this apk build for tests.
LOCAL_MODULE_TAGS := tests
-LOCAL_CERTIFICATE := platform
+LOCAL_CERTIFICATE := shared
LOCAL_JAVA_LIBRARIES := android.test.runner
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index ca28a6a..7f845f9 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -19,11 +19,12 @@
<application>
<uses-library android:name="android.test.runner" />
+ <meta-data android:name="com.android.contacts.iconset" android:resource="@xml/iconset" />
</application>
- <instrumentation android:name="ContactsLaunchPerformance"
+ <instrumentation android:name="android.test.InstrumentationTestRunner"
android:targetPackage="com.android.contacts"
- android:label="Contacts Launch Performance">
+ android:label="Contacts app tests">
</instrumentation>
</manifest>
diff --git a/tests/res/drawable/default_icon.png b/tests/res/drawable/default_icon.png
new file mode 100644
index 0000000..cea0eb3
--- /dev/null
+++ b/tests/res/drawable/default_icon.png
Binary files differ
diff --git a/tests/res/drawable/phone_icon.png b/tests/res/drawable/phone_icon.png
new file mode 100644
index 0000000..4e613ec
--- /dev/null
+++ b/tests/res/drawable/phone_icon.png
Binary files differ
diff --git a/tests/res/xml/iconset.xml b/tests/res/xml/iconset.xml
new file mode 100644
index 0000000..d910815
--- /dev/null
+++ b/tests/res/xml/iconset.xml
@@ -0,0 +1,8 @@
+<icon-set
+ xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <icon-default android:icon="@drawable/default_icon" />
+ <icon android:mimeType="vnd.android.cursor.item/phone"
+ android:icon="@drawable/phone_icon" />
+
+</icon-set>
\ No newline at end of file
diff --git a/tests/src/com/android/contacts/ContactsLaunchPerformance.java b/tests/src/com/android/contacts/ContactsLaunchPerformance.java
index 85dba56..bd60e70 100644
--- a/tests/src/com/android/contacts/ContactsLaunchPerformance.java
+++ b/tests/src/com/android/contacts/ContactsLaunchPerformance.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.contacts.tests;
+package com.android.contacts;
import android.app.Activity;
import android.test.LaunchPerformanceBase;
diff --git a/tests/src/com/android/contacts/ContactsUtilsTests.java b/tests/src/com/android/contacts/ContactsUtilsTests.java
new file mode 100644
index 0000000..01a1ef4
--- /dev/null
+++ b/tests/src/com/android/contacts/ContactsUtilsTests.java
@@ -0,0 +1,101 @@
+/*
+ * 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.ContentValues;
+import android.content.Intent;
+import android.net.Uri;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.Im;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+
+/**
+ * Tests for {@link ContactsUtils}.
+ */
+@LargeTest
+public class ContactsUtilsTests extends AndroidTestCase {
+ private static final String TEST_ADDRESS = "user@example.org";
+ private static final String TEST_PROTOCOL = "prot%col";
+
+ public void testImIntent() throws Exception {
+ // Normal IM is appended as path
+ 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);
+ assertEquals(Intent.ACTION_SENDTO, intent.getAction());
+
+ final Uri data = intent.getData();
+ assertEquals("imto", data.getScheme());
+ assertEquals("gtalk", data.getAuthority());
+ assertEquals(TEST_ADDRESS, data.getPathSegments().get(0));
+ }
+
+ public void testImIntentCustom() throws Exception {
+ // Custom IM types have encoded authority
+ 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_CUSTOM);
+ values.put(Im.CUSTOM_PROTOCOL, TEST_PROTOCOL);
+ values.put(Im.DATA, TEST_ADDRESS);
+
+ final Intent intent = ContactsUtils.buildImIntent(values);
+ 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));
+ }
+
+ public void testImEmailIntent() throws Exception {
+ // Email addresses are treated as Google Talk entries
+ 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);
+
+ final Intent intent = ContactsUtils.buildImIntent(values);
+ assertEquals(Intent.ACTION_SENDTO, intent.getAction());
+
+ final Uri data = intent.getData();
+ assertEquals("imto", data.getScheme());
+ assertEquals("gtalk", data.getAuthority());
+ assertEquals(TEST_ADDRESS, data.getPathSegments().get(0));
+ }
+
+ public void testIsGraphicNull() throws Exception {
+ assertFalse(ContactsUtils.isGraphic(null));
+ }
+
+ public void testIsGraphicEmpty() throws Exception {
+ assertFalse(ContactsUtils.isGraphic(""));
+ }
+
+ public void testIsGraphicSpaces() throws Exception {
+ assertFalse(ContactsUtils.isGraphic(" "));
+ }
+
+ public void testIsGraphicPunctuation() throws Exception {
+ assertTrue(ContactsUtils.isGraphic("."));
+ }
+}
diff --git a/tests/src/com/android/contacts/EntityDeltaTests.java b/tests/src/com/android/contacts/EntityDeltaTests.java
new file mode 100644
index 0000000..70a506b
--- /dev/null
+++ b/tests/src/com/android/contacts/EntityDeltaTests.java
@@ -0,0 +1,411 @@
+/*
+ * 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.model.EntityDelta;
+import com.android.contacts.model.EntityDelta.ValuesDelta;
+import com.google.android.collect.Lists;
+
+import static android.content.ContentProviderOperation.TYPE_INSERT;
+import static android.content.ContentProviderOperation.TYPE_UPDATE;
+import static android.content.ContentProviderOperation.TYPE_DELETE;
+import static android.content.ContentProviderOperation.TYPE_ASSERT;
+
+import android.content.ContentProviderOperation;
+import android.content.ContentValues;
+import android.content.Entity;
+import android.content.ContentProviderOperation.Builder;
+import android.os.Parcel;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.RawContacts;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+
+import java.util.ArrayList;
+
+/**
+ * Tests for {@link EntityDelta} and {@link ValuesDelta}. These tests
+ * focus on passing changes across {@link Parcel}, and verifying that they
+ * correctly build expected "diff" operations.
+ */
+@LargeTest
+public class EntityDeltaTests extends AndroidTestCase {
+ public static final String TAG = "EntityDeltaTests";
+
+ public static final long TEST_CONTACT_ID = 12;
+ public static final long TEST_PHONE_ID = 24;
+
+ public static final String TEST_PHONE_NUMBER_1 = "218-555-1111";
+ public static final String TEST_PHONE_NUMBER_2 = "218-555-2222";
+
+ public static final String TEST_ACCOUNT_NAME = "TEST";
+
+ public EntityDeltaTests() {
+ super();
+ }
+
+ @Override
+ public void setUp() {
+ mContext = getContext();
+ }
+
+ public static Entity getEntity(long contactId, long phoneId) {
+ // Build an existing contact read from database
+ final ContentValues contact = new ContentValues();
+ contact.put(RawContacts.VERSION, 43);
+ contact.put(RawContacts._ID, contactId);
+
+ final ContentValues phone = new ContentValues();
+ phone.put(Data._ID, phoneId);
+ phone.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
+ phone.put(Phone.NUMBER, TEST_PHONE_NUMBER_1);
+ phone.put(Phone.TYPE, Phone.TYPE_HOME);
+
+ final Entity before = new Entity(contact);
+ before.addSubValue(Data.CONTENT_URI, phone);
+ return before;
+ }
+
+ /**
+ * Test that {@link EntityDelta#mergeAfter(EntityDelta)} correctly passes
+ * any changes through the {@link Parcel} object. This enforces that
+ * {@link EntityDelta} should be identical when serialized against the same
+ * "before" {@link Entity}.
+ */
+ public void testParcelChangesNone() {
+ final Entity before = getEntity(TEST_CONTACT_ID, TEST_PHONE_ID);
+ final EntityDelta source = EntityDelta.fromBefore(before);
+ final EntityDelta dest = EntityDelta.fromBefore(before);
+
+ // Merge modified values and assert they match
+ final EntityDelta merged = EntityDelta.mergeAfter(dest, source);
+ assertEquals("Unexpected change when merging", source, merged);
+ }
+
+ public void testParcelChangesInsert() {
+ final Entity before = getEntity(TEST_CONTACT_ID, TEST_PHONE_ID);
+ final EntityDelta source = EntityDelta.fromBefore(before);
+ final EntityDelta dest = EntityDelta.fromBefore(before);
+
+ // Add a new row and pass across parcel, should be same
+ final ContentValues phone = new ContentValues();
+ phone.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
+ phone.put(Phone.NUMBER, TEST_PHONE_NUMBER_2);
+ phone.put(Phone.TYPE, Phone.TYPE_WORK);
+ source.addEntry(ValuesDelta.fromAfter(phone));
+
+ // Merge modified values and assert they match
+ final EntityDelta merged = EntityDelta.mergeAfter(dest, source);
+ assertEquals("Unexpected change when merging", source, merged);
+ }
+
+ public void testParcelChangesUpdate() {
+ // Update existing row and pass across parcel, should be same
+ final Entity before = getEntity(TEST_CONTACT_ID, TEST_PHONE_ID);
+ final EntityDelta source = EntityDelta.fromBefore(before);
+ final EntityDelta dest = EntityDelta.fromBefore(before);
+
+ final ValuesDelta child = source.getEntry(TEST_PHONE_ID);
+ child.put(Phone.NUMBER, TEST_PHONE_NUMBER_2);
+
+ // Merge modified values and assert they match
+ final EntityDelta merged = EntityDelta.mergeAfter(dest, source);
+ assertEquals("Unexpected change when merging", source, merged);
+ }
+
+ public void testParcelChangesDelete() {
+ // Delete a row and pass across parcel, should be same
+ final Entity before = getEntity(TEST_CONTACT_ID, TEST_PHONE_ID);
+ final EntityDelta source = EntityDelta.fromBefore(before);
+ final EntityDelta dest = EntityDelta.fromBefore(before);
+
+ final ValuesDelta child = source.getEntry(TEST_PHONE_ID);
+ child.markDeleted();
+
+ // Merge modified values and assert they match
+ final EntityDelta merged = EntityDelta.mergeAfter(dest, source);
+ assertEquals("Unexpected change when merging", source, merged);
+ }
+
+ /**
+ * Test that {@link ValuesDelta#buildDiff(android.net.Uri)} is correctly
+ * built for insert, update, and delete cases. Note this only tests behavior
+ * for individual {@link Data} rows.
+ */
+ public void testValuesDiffNone() {
+ final ContentValues before = new ContentValues();
+ before.put(Data._ID, TEST_PHONE_ID);
+ before.put(Phone.NUMBER, TEST_PHONE_NUMBER_1);
+
+ final ValuesDelta values = ValuesDelta.fromBefore(before);
+
+ // None action shouldn't produce a builder
+ final Builder builder = values.buildDiff(Data.CONTENT_URI);
+ assertNull("None action produced a builder", builder);
+ }
+
+ public void testValuesDiffInsert() {
+ final ContentValues after = new ContentValues();
+ after.put(Phone.NUMBER, TEST_PHONE_NUMBER_2);
+
+ final ValuesDelta values = ValuesDelta.fromAfter(after);
+
+ // Should produce an insert action
+ final Builder builder = values.buildDiff(Data.CONTENT_URI);
+ final int type = builder.build().getType();
+ assertEquals("Didn't produce insert action", TYPE_INSERT, type);
+ }
+
+ public void testValuesDiffUpdate() {
+ final ContentValues before = new ContentValues();
+ before.put(Data._ID, TEST_PHONE_ID);
+ before.put(Phone.NUMBER, TEST_PHONE_NUMBER_1);
+
+ final ValuesDelta values = ValuesDelta.fromBefore(before);
+ values.put(Phone.NUMBER, TEST_PHONE_NUMBER_2);
+
+ // Should produce an update action
+ final Builder builder = values.buildDiff(Data.CONTENT_URI);
+ final int type = builder.build().getType();
+ assertEquals("Didn't produce update action", TYPE_UPDATE, type);
+ }
+
+ public void testValuesDiffDelete() {
+ final ContentValues before = new ContentValues();
+ before.put(Data._ID, TEST_PHONE_ID);
+ before.put(Phone.NUMBER, TEST_PHONE_NUMBER_1);
+
+ final ValuesDelta values = ValuesDelta.fromBefore(before);
+ values.markDeleted();
+
+ // Should produce a delete action
+ final Builder builder = values.buildDiff(Data.CONTENT_URI);
+ final int type = builder.build().getType();
+ assertEquals("Didn't produce delete action", TYPE_DELETE, type);
+ }
+
+ /**
+ * Test that {@link EntityDelta#buildDiff(ArrayList)} is correctly built for
+ * insert, update, and delete cases. This only tests a subset of possible
+ * {@link Data} row changes.
+ */
+ public void testEntityDiffNone() {
+ final Entity before = getEntity(TEST_CONTACT_ID, TEST_PHONE_ID);
+ final EntityDelta source = EntityDelta.fromBefore(before);
+
+ // Assert that writing unchanged produces few operations
+ final ArrayList<ContentProviderOperation> diff = Lists.newArrayList();
+ source.buildDiff(diff);
+
+ assertTrue("Created changes when none needed", (diff.size() == 0));
+ }
+
+ public void testEntityDiffNoneInsert() {
+ final Entity before = getEntity(TEST_CONTACT_ID, TEST_PHONE_ID);
+ final EntityDelta source = EntityDelta.fromBefore(before);
+
+ // Insert a new phone number
+ final ContentValues phone = new ContentValues();
+ phone.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
+ phone.put(Phone.NUMBER, TEST_PHONE_NUMBER_2);
+ phone.put(Phone.TYPE, Phone.TYPE_WORK);
+ source.addEntry(ValuesDelta.fromAfter(phone));
+
+ // Assert two operations: insert Data row and enforce version
+ final ArrayList<ContentProviderOperation> diff = Lists.newArrayList();
+ source.buildAssert(diff);
+ source.buildDiff(diff);
+ assertEquals("Unexpected operations", 4, diff.size());
+ {
+ final ContentProviderOperation oper = diff.get(0);
+ assertEquals("Expected version enforcement", TYPE_ASSERT, oper.getType());
+ }
+ {
+ final ContentProviderOperation oper = diff.get(1);
+ assertEquals("Expected aggregation mode change", TYPE_UPDATE, oper.getType());
+ assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri());
+ }
+ {
+ final ContentProviderOperation oper = diff.get(2);
+ assertEquals("Incorrect type", TYPE_INSERT, oper.getType());
+ assertEquals("Incorrect target", Data.CONTENT_URI, oper.getUri());
+ }
+ {
+ final ContentProviderOperation oper = diff.get(3);
+ assertEquals("Expected aggregation mode change", TYPE_UPDATE, oper.getType());
+ assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri());
+ }
+ }
+
+ public void testEntityDiffUpdateInsert() {
+ final Entity before = getEntity(TEST_CONTACT_ID, TEST_PHONE_ID);
+ final EntityDelta source = EntityDelta.fromBefore(before);
+
+ // Update parent contact values
+ source.getValues().put(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_DISABLED);
+
+ // Insert a new phone number
+ final ContentValues phone = new ContentValues();
+ phone.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
+ phone.put(Phone.NUMBER, TEST_PHONE_NUMBER_2);
+ phone.put(Phone.TYPE, Phone.TYPE_WORK);
+ source.addEntry(ValuesDelta.fromAfter(phone));
+
+ // Assert three operations: update Contact, insert Data row, enforce version
+ final ArrayList<ContentProviderOperation> diff = Lists.newArrayList();
+ source.buildAssert(diff);
+ source.buildDiff(diff);
+ assertEquals("Unexpected operations", 5, diff.size());
+ {
+ final ContentProviderOperation oper = diff.get(0);
+ assertEquals("Expected version enforcement", TYPE_ASSERT, oper.getType());
+ }
+ {
+ final ContentProviderOperation oper = diff.get(1);
+ assertEquals("Expected aggregation mode change", TYPE_UPDATE, oper.getType());
+ assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri());
+ }
+ {
+ final ContentProviderOperation oper = diff.get(2);
+ assertEquals("Incorrect type", TYPE_UPDATE, oper.getType());
+ assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri());
+ }
+ {
+ final ContentProviderOperation oper = diff.get(3);
+ assertEquals("Incorrect type", TYPE_INSERT, oper.getType());
+ assertEquals("Incorrect target", Data.CONTENT_URI, oper.getUri());
+ }
+ {
+ final ContentProviderOperation oper = diff.get(4);
+ assertEquals("Expected aggregation mode change", TYPE_UPDATE, oper.getType());
+ assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri());
+ }
+ }
+
+ public void testEntityDiffNoneUpdate() {
+ final Entity before = getEntity(TEST_CONTACT_ID, TEST_PHONE_ID);
+ final EntityDelta source = EntityDelta.fromBefore(before);
+
+ // Update existing phone number
+ final ValuesDelta child = source.getEntry(TEST_PHONE_ID);
+ child.put(Phone.NUMBER, TEST_PHONE_NUMBER_2);
+
+ // Assert that version is enforced
+ final ArrayList<ContentProviderOperation> diff = Lists.newArrayList();
+ source.buildAssert(diff);
+ source.buildDiff(diff);
+ assertEquals("Unexpected operations", 4, diff.size());
+ {
+ final ContentProviderOperation oper = diff.get(0);
+ assertEquals("Expected version enforcement", TYPE_ASSERT, oper.getType());
+ }
+ {
+ final ContentProviderOperation oper = diff.get(1);
+ assertEquals("Expected aggregation mode change", TYPE_UPDATE, oper.getType());
+ assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri());
+ }
+ {
+ final ContentProviderOperation oper = diff.get(2);
+ assertEquals("Incorrect type", TYPE_UPDATE, oper.getType());
+ assertEquals("Incorrect target", Data.CONTENT_URI, oper.getUri());
+ }
+ {
+ final ContentProviderOperation oper = diff.get(3);
+ assertEquals("Expected aggregation mode change", TYPE_UPDATE, oper.getType());
+ assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri());
+ }
+ }
+
+ public void testEntityDiffDelete() {
+ final Entity before = getEntity(TEST_CONTACT_ID, TEST_PHONE_ID);
+ final EntityDelta source = EntityDelta.fromBefore(before);
+
+ // Delete entire entity
+ source.getValues().markDeleted();
+
+ // Assert two operations: delete Contact and enforce version
+ final ArrayList<ContentProviderOperation> diff = Lists.newArrayList();
+ source.buildAssert(diff);
+ source.buildDiff(diff);
+ assertEquals("Unexpected operations", 2, diff.size());
+ {
+ final ContentProviderOperation oper = diff.get(0);
+ assertEquals("Expected version enforcement", TYPE_ASSERT, oper.getType());
+ }
+ {
+ final ContentProviderOperation oper = diff.get(1);
+ assertEquals("Incorrect type", TYPE_DELETE, oper.getType());
+ assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri());
+ }
+ }
+
+ public void testEntityDiffInsert() {
+ // Insert a RawContact
+ final ContentValues after = new ContentValues();
+ after.put(RawContacts.ACCOUNT_NAME, TEST_ACCOUNT_NAME);
+ after.put(RawContacts.SEND_TO_VOICEMAIL, 1);
+
+ final ValuesDelta values = ValuesDelta.fromAfter(after);
+ final EntityDelta source = new EntityDelta(values);
+
+ // Assert two operations: delete Contact and enforce version
+ final ArrayList<ContentProviderOperation> diff = Lists.newArrayList();
+ source.buildAssert(diff);
+ source.buildDiff(diff);
+ assertEquals("Unexpected operations", 1, diff.size());
+ {
+ final ContentProviderOperation oper = diff.get(0);
+ assertEquals("Incorrect type", TYPE_INSERT, oper.getType());
+ assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri());
+ }
+ }
+
+ public void testEntityDiffInsertInsert() {
+ // Insert a RawContact
+ final ContentValues after = new ContentValues();
+ after.put(RawContacts.ACCOUNT_NAME, TEST_ACCOUNT_NAME);
+ after.put(RawContacts.SEND_TO_VOICEMAIL, 1);
+
+ final ValuesDelta values = ValuesDelta.fromAfter(after);
+ final EntityDelta source = new EntityDelta(values);
+
+ // Insert a new phone number
+ final ContentValues phone = new ContentValues();
+ phone.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
+ phone.put(Phone.NUMBER, TEST_PHONE_NUMBER_2);
+ phone.put(Phone.TYPE, Phone.TYPE_WORK);
+ source.addEntry(ValuesDelta.fromAfter(phone));
+
+ // Assert two operations: delete Contact and enforce version
+ final ArrayList<ContentProviderOperation> diff = Lists.newArrayList();
+ source.buildAssert(diff);
+ source.buildDiff(diff);
+ assertEquals("Unexpected operations", 2, diff.size());
+ {
+ final ContentProviderOperation oper = diff.get(0);
+ assertEquals("Incorrect type", TYPE_INSERT, oper.getType());
+ assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri());
+ }
+ {
+ final ContentProviderOperation oper = diff.get(1);
+ assertEquals("Incorrect type", TYPE_INSERT, oper.getType());
+ assertEquals("Incorrect target", Data.CONTENT_URI, oper.getUri());
+
+ }
+ }
+}
diff --git a/tests/src/com/android/contacts/EntityModifierTests.java b/tests/src/com/android/contacts/EntityModifierTests.java
new file mode 100644
index 0000000..489bf04
--- /dev/null
+++ b/tests/src/com/android/contacts/EntityModifierTests.java
@@ -0,0 +1,665 @@
+/*
+ * 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 static android.content.ContentProviderOperation.TYPE_DELETE;
+import static android.content.ContentProviderOperation.TYPE_INSERT;
+import static android.content.ContentProviderOperation.TYPE_UPDATE;
+
+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.Sources;
+import com.android.contacts.model.ContactsSource.DataKind;
+import com.android.contacts.model.ContactsSource.EditType;
+import com.android.contacts.model.EntityDelta.ValuesDelta;
+import com.google.android.collect.Lists;
+
+import android.content.ContentProviderOperation;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Entity;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.RawContacts;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tests for {@link EntityModifier} to verify that {@link ContactsSource}
+ * constraints are being enforced correctly.
+ */
+@LargeTest
+public class EntityModifierTests extends AndroidTestCase {
+ public static final String TAG = "EntityModifierTests";
+
+ public static final long VER_FIRST = 100;
+
+ private static final long TEST_ID = 4;
+ private static final String TEST_PHONE = "218-555-1212";
+
+ private static final String TEST_ACCOUNT_NAME = "unittest@example.com";
+ private static final String TEST_ACCOUNT_TYPE = "com.example.unittest";
+
+ public EntityModifierTests() {
+ super();
+ }
+
+ @Override
+ public void setUp() {
+ mContext = getContext();
+ }
+
+ public static class MockContactsSource extends ContactsSource {
+ @Override
+ protected void inflate(Context context, int inflateLevel) {
+ this.accountType = TEST_ACCOUNT_TYPE;
+ this.setInflatedLevel(ContactsSource.LEVEL_CONSTRAINTS);
+
+ // Phone allows maximum 2 home, 1 work, and unlimited other, with
+ // constraint of 5 numbers maximum.
+ DataKind kind = new DataKind(Phone.CONTENT_ITEM_TYPE, -1, -1, 10, true);
+
+ kind.typeOverallMax = 5;
+ kind.typeColumn = Phone.TYPE;
+ kind.typeList = Lists.newArrayList();
+ kind.typeList.add(new EditType(Phone.TYPE_HOME, -1).setSpecificMax(2));
+ kind.typeList.add(new EditType(Phone.TYPE_WORK, -1).setSpecificMax(1));
+ kind.typeList.add(new EditType(Phone.TYPE_FAX_WORK, -1).setSecondary(true));
+ kind.typeList.add(new EditType(Phone.TYPE_OTHER, -1));
+
+ kind.fieldList = Lists.newArrayList();
+ kind.fieldList.add(new EditField(Phone.NUMBER, -1, -1));
+ kind.fieldList.add(new EditField(Phone.LABEL, -1, -1));
+
+ addKind(kind);
+
+ // Email is unlimited
+ kind = new DataKind(Email.CONTENT_ITEM_TYPE, -1, -1, 10, true);
+
+ kind.typeOverallMax = -1;
+
+ kind.fieldList = Lists.newArrayList();
+ kind.fieldList.add(new EditField(Email.DATA, -1, -1));
+
+ addKind(kind);
+ }
+
+ @Override
+ public int getHeaderColor(Context context) {
+ return 0;
+ }
+
+ @Override
+ public int getSideBarColor(Context context) {
+ return 0xffffff;
+ }
+ }
+
+ /**
+ * Build a {@link ContactsSource} that has various odd constraints for
+ * testing purposes.
+ */
+ protected ContactsSource getSource() {
+ final ContactsSource source = new MockContactsSource();
+ source.ensureInflated(getContext(), ContactsSource.LEVEL_CONSTRAINTS);
+ return source;
+ }
+
+ /**
+ * Build {@link Sources} instance.
+ */
+ protected Sources getSources(ContactsSource... sources) {
+ return new Sources(sources);
+ }
+
+ /**
+ * Build an {@link Entity} with the requested set of phone numbers.
+ */
+ protected EntityDelta getEntity(Long existingId, ContentValues... entries) {
+ final ContentValues contact = new ContentValues();
+ if (existingId != null) {
+ contact.put(RawContacts._ID, existingId);
+ }
+ contact.put(RawContacts.ACCOUNT_NAME, TEST_ACCOUNT_NAME);
+ contact.put(RawContacts.ACCOUNT_TYPE, TEST_ACCOUNT_TYPE);
+
+ final Entity before = new Entity(contact);
+ for (ContentValues values : entries) {
+ before.addSubValue(Data.CONTENT_URI, values);
+ }
+ return EntityDelta.fromBefore(before);
+ }
+
+ /**
+ * Assert this {@link List} contains the given {@link Object}.
+ */
+ protected void assertContains(List<?> list, Object object) {
+ assertTrue("Missing expected value", list.contains(object));
+ }
+
+ /**
+ * Assert this {@link List} does not contain the given {@link Object}.
+ */
+ protected void assertNotContains(List<?> list, Object object) {
+ assertFalse("Contained unexpected value", list.contains(object));
+ }
+
+ /**
+ * Insert various rows to test
+ * {@link EntityModifier#getValidTypes(EntityDelta, DataKind, EditType)}
+ */
+ public void testValidTypes() {
+ // Build a source and pull specific types
+ final ContactsSource source = getSource();
+ final DataKind kindPhone = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE);
+ final EditType typeHome = EntityModifier.getType(kindPhone, Phone.TYPE_HOME);
+ final EditType typeWork = EntityModifier.getType(kindPhone, Phone.TYPE_WORK);
+ final EditType typeOther = EntityModifier.getType(kindPhone, Phone.TYPE_OTHER);
+
+ List<EditType> validTypes;
+
+ // Add first home, first work
+ final EntityDelta state = getEntity(TEST_ID);
+ EntityModifier.insertChild(state, kindPhone, typeHome);
+ EntityModifier.insertChild(state, kindPhone, typeWork);
+
+ // Expecting home, other
+ validTypes = EntityModifier.getValidTypes(state, kindPhone, null);
+ assertContains(validTypes, typeHome);
+ assertNotContains(validTypes, typeWork);
+ assertContains(validTypes, typeOther);
+
+ // Add second home
+ EntityModifier.insertChild(state, kindPhone, typeHome);
+
+ // Expecting other
+ validTypes = EntityModifier.getValidTypes(state, kindPhone, null);
+ assertNotContains(validTypes, typeHome);
+ assertNotContains(validTypes, typeWork);
+ assertContains(validTypes, typeOther);
+
+ // Add third and fourth home (invalid, but possible)
+ EntityModifier.insertChild(state, kindPhone, typeHome);
+ EntityModifier.insertChild(state, kindPhone, typeHome);
+
+ // Expecting none
+ validTypes = EntityModifier.getValidTypes(state, kindPhone, null);
+ assertNotContains(validTypes, typeHome);
+ assertNotContains(validTypes, typeWork);
+ assertNotContains(validTypes, typeOther);
+ }
+
+ /**
+ * Test {@link EntityModifier#canInsert(EntityDelta, DataKind)} by
+ * inserting various rows.
+ */
+ public void testCanInsert() {
+ // Build a source and pull specific types
+ final ContactsSource source = getSource();
+ final DataKind kindPhone = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE);
+ final EditType typeHome = EntityModifier.getType(kindPhone, Phone.TYPE_HOME);
+ final EditType typeWork = EntityModifier.getType(kindPhone, Phone.TYPE_WORK);
+ final EditType typeOther = EntityModifier.getType(kindPhone, Phone.TYPE_OTHER);
+
+ // Add first home, first work
+ final EntityDelta state = getEntity(TEST_ID);
+ EntityModifier.insertChild(state, kindPhone, typeHome);
+ EntityModifier.insertChild(state, kindPhone, typeWork);
+ assertTrue("Unable to insert", EntityModifier.canInsert(state, kindPhone));
+
+ // Add two other, which puts us just under "5" overall limit
+ EntityModifier.insertChild(state, kindPhone, typeOther);
+ EntityModifier.insertChild(state, kindPhone, typeOther);
+ assertTrue("Unable to insert", EntityModifier.canInsert(state, kindPhone));
+
+ // Add second home, which should push to snug limit
+ EntityModifier.insertChild(state, kindPhone, typeHome);
+ assertFalse("Able to insert", EntityModifier.canInsert(state, kindPhone));
+ }
+
+ /**
+ * Test
+ * {@link EntityModifier#getBestValidType(EntityDelta, DataKind, boolean, int)}
+ * by asserting expected best options in various states.
+ */
+ public void testBestValidType() {
+ // Build a source and pull specific types
+ final ContactsSource source = getSource();
+ final DataKind kindPhone = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE);
+ final EditType typeHome = EntityModifier.getType(kindPhone, Phone.TYPE_HOME);
+ final EditType typeWork = EntityModifier.getType(kindPhone, Phone.TYPE_WORK);
+ final EditType typeFaxWork = EntityModifier.getType(kindPhone, Phone.TYPE_FAX_WORK);
+ final EditType typeOther = EntityModifier.getType(kindPhone, Phone.TYPE_OTHER);
+
+ EditType suggested;
+
+ // Default suggestion should be home
+ final EntityDelta state = getEntity(TEST_ID);
+ suggested = EntityModifier.getBestValidType(state, kindPhone, false, Integer.MIN_VALUE);
+ assertEquals("Unexpected suggestion", typeHome, suggested);
+
+ // Add first home, should now suggest work
+ EntityModifier.insertChild(state, kindPhone, typeHome);
+ suggested = EntityModifier.getBestValidType(state, kindPhone, false, Integer.MIN_VALUE);
+ assertEquals("Unexpected suggestion", typeWork, suggested);
+
+ // Add work fax, should still suggest work
+ EntityModifier.insertChild(state, kindPhone, typeFaxWork);
+ suggested = EntityModifier.getBestValidType(state, kindPhone, false, Integer.MIN_VALUE);
+ assertEquals("Unexpected suggestion", typeWork, suggested);
+
+ // Add other, should still suggest work
+ EntityModifier.insertChild(state, kindPhone, typeOther);
+ suggested = EntityModifier.getBestValidType(state, kindPhone, false, Integer.MIN_VALUE);
+ assertEquals("Unexpected suggestion", typeWork, suggested);
+
+ // Add work, now should suggest other
+ EntityModifier.insertChild(state, kindPhone, typeWork);
+ suggested = EntityModifier.getBestValidType(state, kindPhone, false, Integer.MIN_VALUE);
+ assertEquals("Unexpected suggestion", typeOther, suggested);
+ }
+
+ public void testIsEmptyEmpty() {
+ final ContactsSource source = getSource();
+ final DataKind kindPhone = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE);
+
+ // Test entirely empty row
+ final ContentValues after = new ContentValues();
+ final ValuesDelta values = ValuesDelta.fromAfter(after);
+
+ assertTrue("Expected empty", EntityModifier.isEmpty(values, kindPhone));
+ }
+
+ public void testIsEmptyDirectFields() {
+ final ContactsSource source = getSource();
+ final DataKind kindPhone = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE);
+ final EditType typeHome = EntityModifier.getType(kindPhone, Phone.TYPE_HOME);
+
+ // Test row that has type values, but core fields are empty
+ final EntityDelta state = getEntity(TEST_ID);
+ final ValuesDelta values = EntityModifier.insertChild(state, kindPhone, typeHome);
+
+ assertTrue("Expected empty", EntityModifier.isEmpty(values, kindPhone));
+
+ // Insert some data to trigger non-empty state
+ values.put(Phone.NUMBER, TEST_PHONE);
+
+ assertFalse("Expected non-empty", EntityModifier.isEmpty(values, kindPhone));
+ }
+
+ public void testTrimEmptySingle() {
+ final ContactsSource source = getSource();
+ final DataKind kindPhone = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE);
+ final EditType typeHome = EntityModifier.getType(kindPhone, Phone.TYPE_HOME);
+
+ // Test row that has type values, but core fields are empty
+ final EntityDelta state = getEntity(TEST_ID);
+ final ValuesDelta values = EntityModifier.insertChild(state, kindPhone, typeHome);
+
+ // Build diff, expecting insert for data row and update enforcement
+ final ArrayList<ContentProviderOperation> diff = Lists.newArrayList();
+ state.buildDiff(diff);
+ assertEquals("Unexpected operations", 3, diff.size());
+ {
+ final ContentProviderOperation oper = diff.get(0);
+ assertEquals("Expected aggregation mode change", TYPE_UPDATE, oper.getType());
+ assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri());
+ }
+ {
+ final ContentProviderOperation oper = diff.get(1);
+ assertEquals("Incorrect type", TYPE_INSERT, oper.getType());
+ assertEquals("Incorrect target", Data.CONTENT_URI, oper.getUri());
+ }
+ {
+ final ContentProviderOperation oper = diff.get(2);
+ assertEquals("Expected aggregation mode change", TYPE_UPDATE, oper.getType());
+ assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri());
+ }
+
+ // Trim empty rows and try again, expecting delete of overall contact
+ EntityModifier.trimEmpty(state, source);
+ diff.clear();
+ state.buildDiff(diff);
+ assertEquals("Unexpected operations", 1, diff.size());
+ {
+ final ContentProviderOperation oper = diff.get(0);
+ assertEquals("Incorrect type", TYPE_DELETE, oper.getType());
+ assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri());
+ }
+ }
+
+ public void testTrimEmptySpaces() {
+ final ContactsSource source = getSource();
+ final DataKind kindPhone = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE);
+ 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 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());
+
+ // 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));
+ }
+
+ public void testTrimLeaveValid() {
+ final ContactsSource source = getSource();
+ final DataKind kindPhone = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE);
+ 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 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());
+
+ // 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());
+ }
+
+ public void testTrimEmptyUntouched() {
+ final ContactsSource source = getSource();
+ final DataKind kindPhone = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE);
+ final EditType typeHome = EntityModifier.getType(kindPhone, Phone.TYPE_HOME);
+
+ // Build "before" that has empty row
+ final EntityDelta state = getEntity(TEST_ID);
+ final ContentValues before = new ContentValues();
+ before.put(Data._ID, TEST_ID);
+ before.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
+ state.addEntry(ValuesDelta.fromBefore(before));
+
+ // Build diff, expecting no changes
+ final ArrayList<ContentProviderOperation> diff = Lists.newArrayList();
+ state.buildDiff(diff);
+ assertEquals("Unexpected operations", 0, diff.size());
+
+ // Try trimming existing empty, which we shouldn't touch
+ EntityModifier.trimEmpty(state, source);
+ diff.clear();
+ state.buildDiff(diff);
+ assertEquals("Unexpected operations", 0, diff.size());
+ }
+
+ public void testTrimEmptyAfterUpdate() {
+ final ContactsSource source = getSource();
+ final DataKind kindPhone = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE);
+ final EditType typeHome = EntityModifier.getType(kindPhone, Phone.TYPE_HOME);
+
+ // Build "before" that has row with some phone number
+ final ContentValues before = new ContentValues();
+ before.put(Data._ID, TEST_ID);
+ before.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
+ before.put(kindPhone.typeColumn, typeHome.rawValue);
+ before.put(Phone.NUMBER, TEST_PHONE);
+ final EntityDelta state = getEntity(TEST_ID, before);
+
+ // Build diff, expecting no changes
+ final ArrayList<ContentProviderOperation> diff = Lists.newArrayList();
+ state.buildDiff(diff);
+ assertEquals("Unexpected operations", 0, diff.size());
+
+ // Now update row by changing number to empty string, expecting single update
+ final ValuesDelta child = state.getEntry(TEST_ID);
+ child.put(Phone.NUMBER, "");
+ diff.clear();
+ state.buildDiff(diff);
+ assertEquals("Unexpected operations", 3, diff.size());
+ {
+ final ContentProviderOperation oper = diff.get(0);
+ assertEquals("Expected aggregation mode change", TYPE_UPDATE, oper.getType());
+ assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri());
+ }
+ {
+ final ContentProviderOperation oper = diff.get(1);
+ assertEquals("Incorrect type", TYPE_UPDATE, oper.getType());
+ assertEquals("Incorrect target", Data.CONTENT_URI, oper.getUri());
+ }
+ {
+ final ContentProviderOperation oper = diff.get(2);
+ assertEquals("Expected aggregation mode change", TYPE_UPDATE, oper.getType());
+ assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri());
+ }
+
+ // Now run trim, which should turn that update into delete
+ EntityModifier.trimEmpty(state, source);
+ diff.clear();
+ state.buildDiff(diff);
+ assertEquals("Unexpected operations", 1, diff.size());
+ {
+ final ContentProviderOperation oper = diff.get(0);
+ assertEquals("Incorrect type", TYPE_DELETE, oper.getType());
+ assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri());
+ }
+ }
+
+ public void testTrimInsertEmpty() {
+ final ContactsSource source = getSource();
+ final Sources sources = getSources(source);
+ final DataKind kindPhone = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE);
+ final EditType typeHome = EntityModifier.getType(kindPhone, Phone.TYPE_HOME);
+
+ // Try creating a contact without any child entries
+ final EntityDelta state = getEntity(null);
+ final EntitySet set = EntitySet.fromSingle(state);
+
+ // Build diff, expecting single insert
+ final ArrayList<ContentProviderOperation> diff = Lists.newArrayList();
+ state.buildDiff(diff);
+ assertEquals("Unexpected operations", 1, diff.size());
+ {
+ final ContentProviderOperation oper = diff.get(0);
+ assertEquals("Incorrect type", TYPE_INSERT, oper.getType());
+ assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri());
+ }
+
+ // Trim empty rows and try again, expecting no insert
+ EntityModifier.trimEmpty(set, sources);
+ diff.clear();
+ state.buildDiff(diff);
+ assertEquals("Unexpected operations", 0, diff.size());
+ }
+
+ public void testTrimInsertInsert() {
+ final ContactsSource source = getSource();
+ final Sources sources = getSources(source);
+ final DataKind kindPhone = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE);
+ final EditType typeHome = EntityModifier.getType(kindPhone, Phone.TYPE_HOME);
+
+ // 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);
+
+ // Build diff, expecting two insert operations
+ final ArrayList<ContentProviderOperation> diff = Lists.newArrayList();
+ state.buildDiff(diff);
+ assertEquals("Unexpected operations", 2, diff.size());
+ {
+ final ContentProviderOperation oper = diff.get(0);
+ assertEquals("Incorrect type", TYPE_INSERT, oper.getType());
+ assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri());
+ }
+ {
+ final ContentProviderOperation oper = diff.get(1);
+ assertEquals("Incorrect type", TYPE_INSERT, oper.getType());
+ assertEquals("Incorrect target", Data.CONTENT_URI, oper.getUri());
+ }
+
+ // Trim empty rows and try again, expecting silence
+ EntityModifier.trimEmpty(set, sources);
+ diff.clear();
+ state.buildDiff(diff);
+ assertEquals("Unexpected operations", 0, diff.size());
+ }
+
+ public void testTrimUpdateRemain() {
+ final ContactsSource source = getSource();
+ final Sources sources = getSources(source);
+ final DataKind kindPhone = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE);
+ final EditType typeHome = EntityModifier.getType(kindPhone, Phone.TYPE_HOME);
+
+ // Build "before" with two phone numbers
+ final ContentValues first = new ContentValues();
+ first.put(Data._ID, TEST_ID);
+ first.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
+ first.put(kindPhone.typeColumn, typeHome.rawValue);
+ first.put(Phone.NUMBER, TEST_PHONE);
+
+ final ContentValues second = new ContentValues();
+ second.put(Data._ID, TEST_ID);
+ second.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
+ second.put(kindPhone.typeColumn, typeHome.rawValue);
+ second.put(Phone.NUMBER, TEST_PHONE);
+
+ final EntityDelta state = getEntity(TEST_ID, first, second);
+ final EntitySet set = EntitySet.fromSingle(state);
+
+ // Build diff, expecting no changes
+ final ArrayList<ContentProviderOperation> diff = Lists.newArrayList();
+ state.buildDiff(diff);
+ assertEquals("Unexpected operations", 0, diff.size());
+
+ // Now update row by changing number to empty string, expecting single update
+ final ValuesDelta child = state.getEntry(TEST_ID);
+ child.put(Phone.NUMBER, "");
+ diff.clear();
+ state.buildDiff(diff);
+ assertEquals("Unexpected operations", 3, diff.size());
+ {
+ final ContentProviderOperation oper = diff.get(0);
+ assertEquals("Expected aggregation mode change", TYPE_UPDATE, oper.getType());
+ assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri());
+ }
+ {
+ final ContentProviderOperation oper = diff.get(1);
+ assertEquals("Incorrect type", TYPE_UPDATE, oper.getType());
+ assertEquals("Incorrect target", Data.CONTENT_URI, oper.getUri());
+ }
+ {
+ final ContentProviderOperation oper = diff.get(2);
+ assertEquals("Expected aggregation mode change", TYPE_UPDATE, oper.getType());
+ assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri());
+ }
+
+ // Now run trim, which should turn that update into delete
+ EntityModifier.trimEmpty(set, sources);
+ diff.clear();
+ state.buildDiff(diff);
+ assertEquals("Unexpected operations", 3, diff.size());
+ {
+ final ContentProviderOperation oper = diff.get(0);
+ assertEquals("Expected aggregation mode change", TYPE_UPDATE, oper.getType());
+ assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri());
+ }
+ {
+ final ContentProviderOperation oper = diff.get(1);
+ assertEquals("Incorrect type", TYPE_DELETE, oper.getType());
+ assertEquals("Incorrect target", Data.CONTENT_URI, oper.getUri());
+ }
+ {
+ final ContentProviderOperation oper = diff.get(2);
+ assertEquals("Expected aggregation mode change", TYPE_UPDATE, oper.getType());
+ assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri());
+ }
+ }
+
+ public void testTrimUpdateUpdate() {
+ final ContactsSource source = getSource();
+ final Sources sources = getSources(source);
+ final DataKind kindPhone = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE);
+ final EditType typeHome = EntityModifier.getType(kindPhone, Phone.TYPE_HOME);
+
+ // Build "before" with two phone numbers
+ final ContentValues first = new ContentValues();
+ first.put(Data._ID, TEST_ID);
+ first.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
+ first.put(kindPhone.typeColumn, typeHome.rawValue);
+ first.put(Phone.NUMBER, TEST_PHONE);
+
+ final EntityDelta state = getEntity(TEST_ID, first);
+ final EntitySet set = EntitySet.fromSingle(state);
+
+ // Build diff, expecting no changes
+ final ArrayList<ContentProviderOperation> diff = Lists.newArrayList();
+ state.buildDiff(diff);
+ assertEquals("Unexpected operations", 0, diff.size());
+
+ // Now update row by changing number to empty string, expecting single update
+ final ValuesDelta child = state.getEntry(TEST_ID);
+ child.put(Phone.NUMBER, "");
+ diff.clear();
+ state.buildDiff(diff);
+ assertEquals("Unexpected operations", 3, diff.size());
+ {
+ final ContentProviderOperation oper = diff.get(0);
+ assertEquals("Expected aggregation mode change", TYPE_UPDATE, oper.getType());
+ assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri());
+ }
+ {
+ final ContentProviderOperation oper = diff.get(1);
+ assertEquals("Incorrect type", TYPE_UPDATE, oper.getType());
+ assertEquals("Incorrect target", Data.CONTENT_URI, oper.getUri());
+ }
+ {
+ final ContentProviderOperation oper = diff.get(2);
+ assertEquals("Expected aggregation mode change", TYPE_UPDATE, oper.getType());
+ assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri());
+ }
+
+ // Now run trim, which should turn into deleting the whole contact
+ EntityModifier.trimEmpty(set, sources);
+ diff.clear();
+ state.buildDiff(diff);
+ assertEquals("Unexpected operations", 1, diff.size());
+ {
+ final ContentProviderOperation oper = diff.get(0);
+ assertEquals("Incorrect type", TYPE_DELETE, oper.getType());
+ assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri());
+ }
+ }
+}
diff --git a/tests/src/com/android/contacts/EntitySetTests.java b/tests/src/com/android/contacts/EntitySetTests.java
new file mode 100644
index 0000000..26f4184
--- /dev/null
+++ b/tests/src/com/android/contacts/EntitySetTests.java
@@ -0,0 +1,589 @@
+/*
+ * 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 static android.content.ContentProviderOperation.TYPE_ASSERT;
+import static android.content.ContentProviderOperation.TYPE_DELETE;
+import static android.content.ContentProviderOperation.TYPE_INSERT;
+import static android.content.ContentProviderOperation.TYPE_UPDATE;
+
+import com.android.contacts.EntityModifierTests.MockContactsSource;
+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.EntityDelta.ValuesDelta;
+import com.google.android.collect.Lists;
+
+import android.content.ContentProviderOperation;
+import android.content.ContentValues;
+import android.content.Entity;
+import android.net.Uri;
+import android.provider.BaseColumns;
+import android.provider.ContactsContract.AggregationExceptions;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.RawContacts;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+
+/**
+ * Tests for {@link EntitySet} 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";
+
+ private static final long CONTACT_FIRST = 1;
+ private static final long CONTACT_SECOND = 2;
+
+ public static final long CONTACT_BOB = 10;
+ public static final long CONTACT_MARY = 11;
+
+ public static final long PHONE_RED = 20;
+ public static final long PHONE_GREEN = 21;
+ public static final long PHONE_BLUE = 22;
+
+ public static final long EMAIL_YELLOW = 25;
+
+ public static final long VER_FIRST = 100;
+ public static final long VER_SECOND = 200;
+
+ public static final String TEST_PHONE = "555-1212";
+ public static final String TEST_ACCOUNT = "org.example.test";
+
+ public EntitySetTests() {
+ super();
+ }
+
+ @Override
+ public void setUp() {
+ mContext = getContext();
+ }
+
+ /**
+ * Build a {@link ContactsSource} that has various odd constraints for
+ * testing purposes.
+ */
+ protected ContactsSource getSource() {
+ final ContactsSource source = new MockContactsSource();
+ source.ensureInflated(getContext(), ContactsSource.LEVEL_CONSTRAINTS);
+ return source;
+ }
+
+ static ContentValues getValues(ContentProviderOperation operation)
+ throws NoSuchFieldException, IllegalAccessException {
+ final Field field = ContentProviderOperation.class.getDeclaredField("mValues");
+ field.setAccessible(true);
+ return (ContentValues) field.get(operation);
+ }
+
+ static EntityDelta getUpdate(long rawContactId) {
+ final Entity before = EntityDeltaTests.getEntity(rawContactId,
+ EntityDeltaTests.TEST_PHONE_ID);
+ return EntityDelta.fromBefore(before);
+ }
+
+ static EntityDelta getInsert() {
+ final ContentValues after = new ContentValues();
+ after.put(RawContacts.ACCOUNT_NAME, EntityDeltaTests.TEST_ACCOUNT_NAME);
+ after.put(RawContacts.SEND_TO_VOICEMAIL, 1);
+
+ final ValuesDelta values = ValuesDelta.fromAfter(after);
+ return new EntityDelta(values);
+ }
+
+ static EntitySet buildSet(EntityDelta... deltas) {
+ final EntitySet set = EntitySet.fromSingle(deltas[0]);
+ for (int i = 1; i < deltas.length; i++) {
+ set.add(deltas[i]);
+ }
+ return set;
+ }
+
+ static EntityDelta buildBeforeEntity(long rawContactId, long version,
+ ContentValues... entries) {
+ // Build an existing contact read from database
+ final ContentValues contact = new ContentValues();
+ contact.put(RawContacts.VERSION, version);
+ contact.put(RawContacts._ID, rawContactId);
+ final Entity before = new Entity(contact);
+ for (ContentValues entry : entries) {
+ before.addSubValue(Data.CONTENT_URI, entry);
+ }
+ return EntityDelta.fromBefore(before);
+ }
+
+ static EntityDelta buildAfterEntity(ContentValues... entries) {
+ // Build an existing contact read from database
+ final ContentValues contact = new ContentValues();
+ contact.put(RawContacts.ACCOUNT_TYPE, TEST_ACCOUNT);
+ final EntityDelta after = new EntityDelta(ValuesDelta.fromAfter(contact));
+ for (ContentValues entry : entries) {
+ after.addEntry(ValuesDelta.fromAfter(entry));
+ }
+ return after;
+ }
+
+ static ContentValues buildPhone(long phoneId) {
+ return buildPhone(phoneId, Long.toString(phoneId));
+ }
+
+ static ContentValues buildPhone(long phoneId, String value) {
+ final ContentValues values = new ContentValues();
+ values.put(Data._ID, phoneId);
+ values.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
+ values.put(Phone.NUMBER, value);
+ values.put(Phone.TYPE, Phone.TYPE_HOME);
+ return values;
+ }
+
+ static ContentValues buildEmail(long emailId) {
+ final ContentValues values = new ContentValues();
+ values.put(Data._ID, emailId);
+ values.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE);
+ values.put(Email.DATA, Long.toString(emailId));
+ values.put(Email.TYPE, Email.TYPE_HOME);
+ return values;
+ }
+
+ static void insertPhone(EntitySet 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) {
+ final EntityDelta match = set.getByRawContactId(rawContactId);
+ return match.getEntry(dataId);
+ }
+
+ static void assertDiffPattern(EntityDelta delta, ContentProviderOperation... pattern) {
+ final ArrayList<ContentProviderOperation> diff = Lists.newArrayList();
+ delta.buildAssert(diff);
+ delta.buildDiff(diff);
+ assertDiffPattern(diff, pattern);
+ }
+
+ static void assertDiffPattern(EntitySet set, ContentProviderOperation... pattern) {
+ assertDiffPattern(set.buildDiff(), pattern);
+ }
+
+ static void assertDiffPattern(ArrayList<ContentProviderOperation> diff,
+ ContentProviderOperation... pattern) {
+ assertEquals("Unexpected operations", pattern.length, diff.size());
+ for (int i = 0; i < pattern.length; i++) {
+ final ContentProviderOperation expected = pattern[i];
+ final ContentProviderOperation found = diff.get(i);
+
+ assertEquals("Unexpected uri", expected.getUri(), found.getUri());
+
+ final String expectedType = getStringForType(expected.getType());
+ final String foundType = getStringForType(found.getType());
+ assertEquals("Unexpected type", expectedType, foundType);
+
+ if (expected.getType() == TYPE_DELETE) continue;
+
+ try {
+ final ContentValues expectedValues = getValues(expected);
+ final ContentValues foundValues = getValues(found);
+
+ expectedValues.remove(BaseColumns._ID);
+ foundValues.remove(BaseColumns._ID);
+
+ assertEquals("Unexpected values", expectedValues, foundValues);
+ } catch (NoSuchFieldException e) {
+ fail(e.toString());
+ } catch (IllegalAccessException e) {
+ fail(e.toString());
+ }
+ }
+ }
+
+ static String getStringForType(int type) {
+ switch (type) {
+ case TYPE_ASSERT: return "TYPE_ASSERT";
+ case TYPE_INSERT: return "TYPE_INSERT";
+ case TYPE_UPDATE: return "TYPE_UPDATE";
+ case TYPE_DELETE: return "TYPE_DELETE";
+ default: return Integer.toString(type);
+ }
+ }
+
+ static ContentProviderOperation buildAssertVersion(long version) {
+ final ContentValues values = new ContentValues();
+ values.put(RawContacts.VERSION, version);
+ return buildOper(RawContacts.CONTENT_URI, TYPE_ASSERT, values);
+ }
+
+ static ContentProviderOperation buildAggregationModeUpdate(int mode) {
+ final ContentValues values = new ContentValues();
+ values.put(RawContacts.AGGREGATION_MODE, mode);
+ return buildOper(RawContacts.CONTENT_URI, TYPE_UPDATE, values);
+ }
+
+ static ContentProviderOperation buildUpdateAggregationSuspended() {
+ return buildAggregationModeUpdate(RawContacts.AGGREGATION_MODE_SUSPENDED);
+ }
+
+ static ContentProviderOperation buildUpdateAggregationDefault() {
+ return buildAggregationModeUpdate(RawContacts.AGGREGATION_MODE_DEFAULT);
+ }
+
+ static ContentProviderOperation buildUpdateAggregationKeepTogether(long rawContactId) {
+ final ContentValues values = new ContentValues();
+ values.put(AggregationExceptions.RAW_CONTACT_ID1, rawContactId);
+ values.put(AggregationExceptions.TYPE, AggregationExceptions.TYPE_KEEP_TOGETHER);
+ return buildOper(AggregationExceptions.CONTENT_URI, TYPE_UPDATE, values);
+ }
+
+ static ContentValues buildDataInsert(ValuesDelta values, long rawContactId) {
+ final ContentValues insertValues = values.getCompleteValues();
+ insertValues.put(Data.RAW_CONTACT_ID, rawContactId);
+ return insertValues;
+ }
+
+ static ContentProviderOperation buildDelete(Uri uri) {
+ return buildOper(uri, TYPE_DELETE, (ContentValues)null);
+ }
+
+ static ContentProviderOperation buildOper(Uri uri, int type, ValuesDelta values) {
+ return buildOper(uri, type, values.getCompleteValues());
+ }
+
+ static ContentProviderOperation buildOper(Uri uri, int type, ContentValues values) {
+ switch (type) {
+ case TYPE_ASSERT:
+ return ContentProviderOperation.newAssertQuery(uri).withValues(values).build();
+ case TYPE_INSERT:
+ return ContentProviderOperation.newInsert(uri).withValues(values).build();
+ case TYPE_UPDATE:
+ return ContentProviderOperation.newUpdate(uri).withValues(values).build();
+ case TYPE_DELETE:
+ return ContentProviderOperation.newDelete(uri).build();
+ }
+ return null;
+ }
+
+ static Long getVersion(EntitySet set, Long rawContactId) {
+ return set.getByRawContactId(rawContactId).getValues().getAsLong(RawContacts.VERSION);
+ }
+
+ /**
+ * Count number of {@link AggregationExceptions} updates contained in the
+ * given list of {@link ContentProviderOperation}.
+ */
+ static int countExceptionUpdates(ArrayList<ContentProviderOperation> diff) {
+ int updateCount = 0;
+ for (ContentProviderOperation oper : diff) {
+ if (AggregationExceptions.CONTENT_URI.equals(oper.getUri())
+ && oper.getType() == ContentProviderOperation.TYPE_UPDATE) {
+ updateCount++;
+ }
+ }
+ return updateCount;
+ }
+
+ public void testInsert() {
+ final EntityDelta insert = getInsert();
+ final EntitySet set = buildSet(insert);
+
+ // Inserting single shouldn't create rules
+ final ArrayList<ContentProviderOperation> diff = set.buildDiff();
+ final int exceptionCount = countExceptionUpdates(diff);
+ assertEquals("Unexpected exception updates", 0, exceptionCount);
+ }
+
+ public void testUpdateUpdate() {
+ final EntityDelta updateFirst = getUpdate(CONTACT_FIRST);
+ final EntityDelta updateSecond = getUpdate(CONTACT_SECOND);
+ final EntitySet set = buildSet(updateFirst, updateSecond);
+
+ // Updating two existing shouldn't create rules
+ final ArrayList<ContentProviderOperation> diff = set.buildDiff();
+ final int exceptionCount = countExceptionUpdates(diff);
+ assertEquals("Unexpected exception updates", 0, exceptionCount);
+ }
+
+ public void testUpdateInsert() {
+ final EntityDelta update = getUpdate(CONTACT_FIRST);
+ final EntityDelta insert = getInsert();
+ final EntitySet set = buildSet(update, insert);
+
+ // New insert should only create one rule
+ final ArrayList<ContentProviderOperation> diff = set.buildDiff();
+ final int exceptionCount = countExceptionUpdates(diff);
+ assertEquals("Unexpected exception updates", 1, exceptionCount);
+ }
+
+ public void testInsertUpdateInsert() {
+ final EntityDelta insertFirst = getInsert();
+ final EntityDelta update = getUpdate(CONTACT_FIRST);
+ final EntityDelta insertSecond = getInsert();
+ final EntitySet set = buildSet(insertFirst, update, insertSecond);
+
+ // Two inserts should create two rules to bind against single existing
+ final ArrayList<ContentProviderOperation> diff = set.buildDiff();
+ final int exceptionCount = countExceptionUpdates(diff);
+ assertEquals("Unexpected exception updates", 2, exceptionCount);
+ }
+
+ public void testInsertInsertInsert() {
+ final EntityDelta insertFirst = getInsert();
+ final EntityDelta insertSecond = getInsert();
+ final EntityDelta insertThird = getInsert();
+ final EntitySet set = buildSet(insertFirst, insertSecond, insertThird);
+
+ // Three new inserts should create only two binding rules
+ final ArrayList<ContentProviderOperation> diff = set.buildDiff();
+ final int exceptionCount = countExceptionUpdates(diff);
+ assertEquals("Unexpected exception updates", 2, exceptionCount);
+ }
+
+ public void testMergeDataRemoteInsert() {
+ final EntitySet first = buildSet(buildBeforeEntity(CONTACT_BOB, VER_FIRST,
+ buildPhone(PHONE_RED)));
+ final EntitySet 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);
+ assertEquals("Unexpected change when merging", second, merged);
+ }
+
+ public void testMergeDataLocalUpdateRemoteInsert() {
+ final EntitySet first = buildSet(buildBeforeEntity(CONTACT_BOB, VER_FIRST,
+ buildPhone(PHONE_RED)));
+ final EntitySet second = buildSet(buildBeforeEntity(CONTACT_BOB, VER_SECOND,
+ buildPhone(PHONE_RED), buildPhone(PHONE_GREEN)));
+
+ // Change the local number to trigger update
+ final ValuesDelta phone = getPhone(first, CONTACT_BOB, PHONE_RED);
+ phone.put(Phone.NUMBER, TEST_PHONE);
+
+ assertDiffPattern(first,
+ buildAssertVersion(VER_FIRST),
+ buildUpdateAggregationSuspended(),
+ buildOper(Data.CONTENT_URI, TYPE_UPDATE, phone.getAfter()),
+ buildUpdateAggregationDefault());
+
+ // Merge in the second version, verify diff matches
+ final EntitySet merged = EntitySet.mergeAfter(second, first);
+ assertDiffPattern(merged,
+ buildAssertVersion(VER_SECOND),
+ buildUpdateAggregationSuspended(),
+ buildOper(Data.CONTENT_URI, TYPE_UPDATE, phone.getAfter()),
+ buildUpdateAggregationDefault());
+ }
+
+ public void testMergeDataLocalUpdateRemoteDelete() {
+ final EntitySet first = buildSet(buildBeforeEntity(CONTACT_BOB, VER_FIRST,
+ buildPhone(PHONE_RED)));
+ final EntitySet second = buildSet(buildBeforeEntity(CONTACT_BOB, VER_SECOND,
+ buildPhone(PHONE_GREEN)));
+
+ // Change the local number to trigger update
+ final ValuesDelta phone = getPhone(first, CONTACT_BOB, PHONE_RED);
+ phone.put(Phone.NUMBER, TEST_PHONE);
+
+ assertDiffPattern(first,
+ buildAssertVersion(VER_FIRST),
+ buildUpdateAggregationSuspended(),
+ buildOper(Data.CONTENT_URI, TYPE_UPDATE, phone.getAfter()),
+ buildUpdateAggregationDefault());
+
+ // 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);
+ assertDiffPattern(merged,
+ buildAssertVersion(VER_SECOND),
+ buildUpdateAggregationSuspended(),
+ buildOper(Data.CONTENT_URI, TYPE_INSERT, buildDataInsert(phone, CONTACT_BOB)),
+ buildUpdateAggregationDefault());
+ }
+
+ public void testMergeDataLocalDeleteRemoteUpdate() {
+ final EntitySet first = buildSet(buildBeforeEntity(CONTACT_BOB, VER_FIRST,
+ buildPhone(PHONE_RED)));
+ final EntitySet second = buildSet(buildBeforeEntity(CONTACT_BOB, VER_SECOND,
+ buildPhone(PHONE_RED, TEST_PHONE)));
+
+ // Delete phone locally
+ final ValuesDelta phone = getPhone(first, CONTACT_BOB, PHONE_RED);
+ phone.markDeleted();
+
+ assertDiffPattern(first,
+ buildAssertVersion(VER_FIRST),
+ buildUpdateAggregationSuspended(),
+ buildDelete(Data.CONTENT_URI),
+ buildUpdateAggregationDefault());
+
+ // Merge in the second version, verify that our delete remains
+ final EntitySet merged = EntitySet.mergeAfter(second, first);
+ assertDiffPattern(merged,
+ buildAssertVersion(VER_SECOND),
+ buildUpdateAggregationSuspended(),
+ buildDelete(Data.CONTENT_URI),
+ buildUpdateAggregationDefault());
+ }
+
+ public void testMergeDataLocalInsertRemoteInsert() {
+ final EntitySet first = buildSet(buildBeforeEntity(CONTACT_BOB, VER_FIRST,
+ buildPhone(PHONE_RED)));
+ final EntitySet second = buildSet(buildBeforeEntity(CONTACT_BOB, VER_SECOND,
+ buildPhone(PHONE_RED), buildPhone(PHONE_GREEN)));
+
+ // Insert new phone locally
+ final ValuesDelta bluePhone = ValuesDelta.fromAfter(buildPhone(PHONE_BLUE));
+ first.getByRawContactId(CONTACT_BOB).addEntry(bluePhone);
+ assertDiffPattern(first,
+ buildAssertVersion(VER_FIRST),
+ buildUpdateAggregationSuspended(),
+ buildOper(Data.CONTENT_URI, TYPE_INSERT, buildDataInsert(bluePhone, CONTACT_BOB)),
+ buildUpdateAggregationDefault());
+
+ // Merge in the second version, verify that our insert remains
+ final EntitySet merged = EntitySet.mergeAfter(second, first);
+ assertDiffPattern(merged,
+ buildAssertVersion(VER_SECOND),
+ buildUpdateAggregationSuspended(),
+ buildOper(Data.CONTENT_URI, TYPE_INSERT, buildDataInsert(bluePhone, CONTACT_BOB)),
+ buildUpdateAggregationDefault());
+ }
+
+ public void testMergeRawContactLocalInsertRemoteInsert() {
+ final EntitySet first = buildSet(buildBeforeEntity(CONTACT_BOB, VER_FIRST,
+ buildPhone(PHONE_RED)));
+ final EntitySet second = buildSet(buildBeforeEntity(CONTACT_BOB, VER_SECOND,
+ buildPhone(PHONE_RED)), buildBeforeEntity(CONTACT_MARY, VER_SECOND,
+ buildPhone(PHONE_RED)));
+
+ // Add new contact locally, should remain insert
+ final ContentValues joePhoneInsert = buildPhone(PHONE_BLUE);
+ final EntityDelta joeContact = buildAfterEntity(joePhoneInsert);
+ final ContentValues joeContactInsert = joeContact.getValues().getCompleteValues();
+ first.add(joeContact);
+ assertDiffPattern(first,
+ buildAssertVersion(VER_FIRST),
+ buildOper(RawContacts.CONTENT_URI, TYPE_INSERT, joeContactInsert),
+ buildOper(Data.CONTENT_URI, TYPE_INSERT, joePhoneInsert),
+ buildUpdateAggregationKeepTogether(CONTACT_BOB));
+
+ // Merge in the second version, verify that our insert remains
+ final EntitySet merged = EntitySet.mergeAfter(second, first);
+ assertDiffPattern(merged,
+ buildAssertVersion(VER_SECOND),
+ buildAssertVersion(VER_SECOND),
+ buildOper(RawContacts.CONTENT_URI, TYPE_INSERT, joeContactInsert),
+ buildOper(Data.CONTENT_URI, TYPE_INSERT, joePhoneInsert),
+ buildUpdateAggregationKeepTogether(CONTACT_BOB));
+ }
+
+ public void testMergeRawContactLocalDeleteRemoteDelete() {
+ final EntitySet first = buildSet(
+ buildBeforeEntity(CONTACT_BOB, VER_FIRST, buildPhone(PHONE_RED)),
+ buildBeforeEntity(CONTACT_MARY, VER_FIRST, buildPhone(PHONE_RED)));
+ final EntitySet second = buildSet(
+ buildBeforeEntity(CONTACT_BOB, VER_SECOND, buildPhone(PHONE_RED)));
+
+ // Remove contact locally
+ first.getByRawContactId(CONTACT_MARY).markDeleted();
+ assertDiffPattern(first,
+ buildAssertVersion(VER_FIRST),
+ buildAssertVersion(VER_FIRST),
+ buildDelete(RawContacts.CONTENT_URI));
+
+ // Merge in the second version, verify that our delete isn't needed
+ final EntitySet merged = EntitySet.mergeAfter(second, first);
+ assertDiffPattern(merged);
+ }
+
+ public void testMergeRawContactLocalUpdateRemoteDelete() {
+ final EntitySet first = buildSet(
+ buildBeforeEntity(CONTACT_BOB, VER_FIRST, buildPhone(PHONE_RED)),
+ buildBeforeEntity(CONTACT_MARY, VER_FIRST, buildPhone(PHONE_RED)));
+ final EntitySet second = buildSet(
+ buildBeforeEntity(CONTACT_BOB, VER_SECOND, buildPhone(PHONE_RED)));
+
+ // Perform local update
+ final ValuesDelta phone = getPhone(first, CONTACT_MARY, PHONE_RED);
+ phone.put(Phone.NUMBER, TEST_PHONE);
+ assertDiffPattern(first,
+ buildAssertVersion(VER_FIRST),
+ buildAssertVersion(VER_FIRST),
+ buildUpdateAggregationSuspended(),
+ buildOper(Data.CONTENT_URI, TYPE_UPDATE, phone.getAfter()),
+ buildUpdateAggregationDefault());
+
+ final ContentValues phoneInsert = phone.getCompleteValues();
+ final ContentValues contactInsert = first.getByRawContactId(CONTACT_MARY).getValues()
+ .getCompleteValues();
+
+ // Merge and verify that update turned into insert
+ final EntitySet merged = EntitySet.mergeAfter(second, first);
+ assertDiffPattern(merged,
+ buildAssertVersion(VER_SECOND),
+ buildOper(RawContacts.CONTENT_URI, TYPE_INSERT, contactInsert),
+ buildOper(Data.CONTENT_URI, TYPE_INSERT, phoneInsert),
+ buildUpdateAggregationKeepTogether(CONTACT_BOB));
+ }
+
+ public void testMergeUsesNewVersion() {
+ final EntitySet first = buildSet(buildBeforeEntity(CONTACT_BOB, VER_FIRST,
+ buildPhone(PHONE_RED)));
+ final EntitySet 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);
+ assertEquals((Long)VER_SECOND, getVersion(merged, CONTACT_BOB));
+ }
+
+ public void testMergeAfterEnsureAndTrim() {
+ final EntitySet first = buildSet(buildBeforeEntity(CONTACT_BOB, VER_FIRST,
+ buildEmail(EMAIL_YELLOW)));
+ final EntitySet second = buildSet(buildBeforeEntity(CONTACT_BOB, VER_SECOND,
+ buildEmail(EMAIL_YELLOW)));
+
+ // Ensure we have at least one phone
+ final ContactsSource source = getSource();
+ final EntityDelta bobContact = first.getByRawContactId(CONTACT_BOB);
+ EntityModifier.ensureKindExists(bobContact, source, Phone.CONTENT_ITEM_TYPE);
+ final ValuesDelta bobPhone = bobContact.getSuperPrimaryEntry(Phone.CONTENT_ITEM_TYPE, true);
+
+ // Make sure the update would insert a row
+ assertDiffPattern(first,
+ buildAssertVersion(VER_FIRST),
+ buildUpdateAggregationSuspended(),
+ buildOper(Data.CONTENT_URI, TYPE_INSERT, buildDataInsert(bobPhone, CONTACT_BOB)),
+ buildUpdateAggregationDefault());
+
+ // Trim values and ensure that we don't insert things
+ EntityModifier.trimEmpty(bobContact, source);
+ assertDiffPattern(first);
+
+ // Now re-parent the change, which should remain no-op
+ final EntitySet merged = EntitySet.mergeAfter(second, first);
+ assertDiffPattern(merged);
+ }
+}
diff --git a/tests/src/com/android/contacts/RecentCallsListActivityTests.java b/tests/src/com/android/contacts/RecentCallsListActivityTests.java
new file mode 100644
index 0000000..2cdd8d7
--- /dev/null
+++ b/tests/src/com/android/contacts/RecentCallsListActivityTests.java
@@ -0,0 +1,353 @@
+/*
+ * 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.res.Resources;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.provider.CallLog.Calls;
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.util.Log;
+import android.view.View;
+import android.widget.FrameLayout;
+import com.android.contacts.RecentCallsListActivity;
+import com.android.internal.telephony.CallerInfo;
+import java.util.Date;
+import java.util.Formatter;
+import java.util.HashMap;
+import java.util.Random;
+
+/**
+ * Tests for the contact call list activity.
+ *
+ * Running all tests:
+ *
+ * runtest contacts
+ * or
+ * adb shell am instrument \
+ * -w com.android.contacts.tests/android.test.InstrumentationTestRunner
+ */
+
+public class RecentCallsListActivityTests
+ extends ActivityInstrumentationTestCase2<RecentCallsListActivity> {
+ static private final String TAG = "RecentCallsListActivityTests";
+ static private final String[] CALL_LOG_PROJECTION = new String[] {
+ Calls._ID,
+ Calls.NUMBER,
+ Calls.DATE,
+ Calls.DURATION,
+ Calls.TYPE,
+ Calls.CACHED_NAME,
+ Calls.CACHED_NUMBER_TYPE,
+ Calls.CACHED_NUMBER_LABEL
+ };
+ static private final int RAND_DURATION = -1;
+ static private final long NOW = -1L;
+
+ // We get the call list activity and assign is a frame to build
+ // its list. mAdapter is an inner class of
+ // RecentCallsListActivity to build the rows (view) in the call
+ // list. We reuse it with our own in-mem DB.
+ private RecentCallsListActivity mActivity;
+ private FrameLayout mParentView;
+ private RecentCallsListActivity.RecentCallsAdapter mAdapter;
+ private String mVoicemail;
+
+ // In memory array to hold the rows corresponding to the 'calls' table.
+ private MatrixCursor mCursor;
+ private int mIndex; // Of the next row.
+
+ private Random mRnd;
+
+ // References to the icons bitmaps used to build the list are stored in a
+ // map mIcons. The keys to retrieve the icons are:
+ // Calls.INCOMING_TYPE, Calls.OUTGOING_TYPE and Calls.MISSED_TYPE.
+ private HashMap<Integer, Bitmap> mCallTypeIcons;
+
+ // An item in the call list. All the methods performing checks use it.
+ private RecentCallsListActivity.RecentCallsListItemViews mItem;
+ // The list of views representing the data in the DB. View are in
+ // reverse order compare to the DB.
+ private View[] mList;
+
+ public RecentCallsListActivityTests() {
+ super("com.android.contacts", RecentCallsListActivity.class);
+ mIndex = 1;
+ mRnd = new Random();
+ }
+
+ @Override
+ public void setUp() {
+ mActivity = (RecentCallsListActivity) getActivity();
+ mVoicemail = mActivity.mVoiceMailNumber;
+ mAdapter = mActivity.mAdapter;
+ mParentView = new FrameLayout(mActivity);
+ mCursor = new MatrixCursor(CALL_LOG_PROJECTION);
+ buildIconMap();
+ }
+
+ /**
+ * Checks that the call icon is not visible for private and
+ * unknown numbers.
+ * Use 2 passes, one where new views are created and one where
+ * half of the total views are updated and the other half created.
+ */
+ @MediumTest
+ public void testCallViewIsNotVisibleForPrivateAndUnknownNumbers() {
+ final int SIZE = 100;
+ mList = new View[SIZE];
+
+ // Insert the first batch of entries.
+ mCursor.moveToFirst();
+ insertRandomEntries(SIZE / 2);
+ int startOfSecondBatch = mCursor.getPosition();
+
+ buildViewListFromDb();
+ checkCallStatus();
+
+ // Append the rest of the entries. We keep the first set of
+ // views around so they get updated and not built from
+ // scratch, this exposes some bugs that are not there when the
+ // call log is launched for the 1st time but show up when the
+ // call log gets updated afterwards.
+ mCursor.move(startOfSecondBatch);
+ insertRandomEntries(SIZE / 2);
+
+ buildViewListFromDb();
+ checkCallStatus();
+ }
+
+ //
+ // HELPERS to check conditions on the DB/views
+ //
+ /**
+ * Check the date of the current list item.
+ * @param date That should be present in the call log list
+ * item. Only NOW is supported.
+ */
+ private void checkDate(long date) {
+ if (NOW == date) {
+ assertEquals("0 mins ago", mItem.dateView.getText());
+ }
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Checks the right icon is used to represent the call type
+ * (missed, incoming, outgoing.) in the current item.
+ */
+ private void checkCallType(int type) {
+ Bitmap icon = ((BitmapDrawable) mItem.iconView.getDrawable()).getBitmap();
+ assertEquals(mCallTypeIcons.get(type), icon);
+ }
+
+ /**
+ * Go over all the views in the list and check that the Call
+ * icon's visibility matches the nature of the number.
+ */
+ private void checkCallStatus() {
+ for (int i = 0; i < mList.length; i++) {
+ if (null == mList[i]) {
+ break;
+ }
+ mItem = (RecentCallsListActivity.RecentCallsListItemViews) mList[i].getTag();
+
+ // callView tag is the phone number.
+ String number = (String) mItem.callView.getTag();
+
+ if (CallerInfo.PRIVATE_NUMBER.equals(number) ||
+ CallerInfo.UNKNOWN_NUMBER.equals(number)) {
+ assertFalse(View.VISIBLE == mItem.callView.getVisibility());
+ } else {
+ assertEquals(View.VISIBLE, mItem.callView.getVisibility());
+ }
+ }
+ }
+
+
+ //
+ // HELPERS to setup the tests.
+ //
+
+ /**
+ * Get the Bitmap from the icons in the contacts package.
+ */
+ private Bitmap getBitmap(String resName) {
+ Resources r = mActivity.getResources();
+ int resid = r.getIdentifier(resName, "drawable", "com.android.contacts");
+ BitmapDrawable d = (BitmapDrawable) r.getDrawable(resid);
+ assertNotNull(d);
+ return d.getBitmap();
+ }
+
+ /**
+ * Fetch all the icons we need in tests from the contacts app and store them in a map.
+ */
+ private void buildIconMap() {
+ mCallTypeIcons = new HashMap<Integer, Bitmap>(3);
+
+ mCallTypeIcons.put(Calls.INCOMING_TYPE, getBitmap("ic_call_log_list_incoming_call"));
+ mCallTypeIcons.put(Calls.MISSED_TYPE, getBitmap("ic_call_log_list_missed_call"));
+ mCallTypeIcons.put(Calls.OUTGOING_TYPE, getBitmap("ic_call_log_list_outgoing_call"));
+ }
+
+ //
+ // HELPERS to build/update the call entries (views) from the DB.
+ //
+
+ /**
+ * Read the DB and foreach call either update the existing view if
+ * one exists already otherwise create one.
+ * The list is build from a DESC view of the DB (last inserted entry is first).
+ */
+ private void buildViewListFromDb() {
+ int i = 0;
+ mCursor.moveToLast();
+ while(!mCursor.isBeforeFirst()) {
+ if (null == mList[i]) {
+ mList[i] = mAdapter.newView(mActivity, mCursor, mParentView);
+ }
+ mAdapter.bindView(mList[i], mActivity, mCursor);
+ mCursor.moveToPrevious();
+ i++;
+ }
+ }
+
+ //
+ // HELPERS to insert numbers in the call log DB.
+ //
+
+ /**
+ * Insert a certain number of random numbers in the DB. Makes sure
+ * there is at least one private and one unknown number in the DB.
+ * @param num Of entries to be inserted.
+ */
+ private void insertRandomEntries(int num) {
+ if (num < 10) {
+ throw new IllegalArgumentException("num should be >= 10");
+ }
+ boolean privateOrUnknownOrVm[];
+ privateOrUnknownOrVm = insertRandomRange(0, num - 2);
+
+ if (privateOrUnknownOrVm[0] && privateOrUnknownOrVm[1]) {
+ insertRandomRange(num - 2, num);
+ } else {
+ insertPrivate(NOW, RAND_DURATION);
+ insertUnknown(NOW, RAND_DURATION);
+ }
+ }
+
+ /**
+ * Insert a new call entry in the test DB.
+ * @param number The phone number. For unknown and private numbers,
+ * use CallerInfo.UNKNOWN_NUMBER or CallerInfo.PRIVATE_NUMBER.
+ * @param date In millisec since epoch. Use NOW to use the current time.
+ * @param duration In seconds of the call. Use RAND_DURATION to pick a random one.
+ * @param type Eigher Call.OUTGOING_TYPE or Call.INCOMING_TYPE or Call.MISSED_TYPE.
+ */
+ private void insert(String number, long date, int duration, int type) {
+ MatrixCursor.RowBuilder row = mCursor.newRow();
+ row.add(mIndex);
+ mIndex ++;
+ row.add(number);
+ if (NOW == date) {
+ row.add(new Date().getTime());
+ }
+ if (duration < 0) {
+ duration = mRnd.nextInt(10 * 60); // 0 - 10 minutes random.
+ }
+ row.add(duration); // duration
+ if (mVoicemail.equals(number)) {
+ assertEquals(Calls.OUTGOING_TYPE, type);
+ }
+ row.add(type); // type
+ row.add(""); // cached name
+ row.add(0); // cached number type
+ row.add(""); // cached number label
+ }
+
+ /**
+ * Insert a new private call entry in the test DB.
+ * @param date In millisec since epoch. Use NOW to use the current time.
+ * @param duration In seconds of the call. Use RAND_DURATION to pick a random one.
+ */
+ private void insertPrivate(long date, int duration) {
+ insert(CallerInfo.PRIVATE_NUMBER, date, duration, Calls.INCOMING_TYPE);
+ }
+
+ /**
+ * Insert a new unknown call entry in the test DB.
+ * @param date In millisec since epoch. Use NOW to use the current time.
+ * @param duration In seconds of the call. Use RAND_DURATION to pick a random one.
+ */
+ private void insertUnknown(long date, int duration) {
+ insert(CallerInfo.UNKNOWN_NUMBER, date, duration, Calls.INCOMING_TYPE);
+ }
+
+ /**
+ * Insert a new voicemail call entry in the test DB.
+ * @param date In millisec since epoch. Use NOW to use the current time.
+ * @param duration In seconds of the call. Use RAND_DURATION to pick a random one.
+ */
+ private void insertVoicemail(long date, int duration) {
+ insert(mVoicemail, date, duration, Calls.OUTGOING_TYPE);
+ }
+
+ /**
+ * Insert a range [start, end) of random numbers in the DB. For
+ * each row, there is a 1/10 probability that the number will be
+ * marked as PRIVATE or UNKNOWN or VOICEMAIL. For regular numbers, a number is
+ * inserted, its last 4 digits will be the number of the iteration
+ * in the range.
+ * @param start Of the range.
+ * @param end Of the range (excluded).
+ * @return An array with 2 booleans [0 = private number, 1 =
+ * unknown number, 2 = voicemail] to indicate if at least one
+ * private or unknown or voicemail number has been inserted. Since
+ * the numbers are random some tests may want to enforce the
+ * insertion of such numbers.
+ */
+ // TODO: Should insert numbers with contact entries too.
+ private boolean[] insertRandomRange(int start, int end) {
+ boolean[] privateOrUnknownOrVm = new boolean[] {false, false, false};
+
+ for (int i = start; i < end; i++ ) {
+ int type = mRnd.nextInt(10);
+
+ if (0 == type) {
+ insertPrivate(NOW, RAND_DURATION);
+ privateOrUnknownOrVm[0] = true;
+ } else if (1 == type) {
+ insertUnknown(NOW, RAND_DURATION);
+ privateOrUnknownOrVm[1] = true;
+ } else if (2 == type) {
+ insertVoicemail(NOW, RAND_DURATION);
+ privateOrUnknownOrVm[2] = true;
+ } else {
+ int inout = mRnd.nextBoolean() ? Calls.OUTGOING_TYPE : Calls.INCOMING_TYPE;
+ String number = new Formatter().format("1800123%04d", i).toString();
+ insert(number, NOW, RAND_DURATION, inout);
+ }
+ }
+ return privateOrUnknownOrVm;
+ }
+}