Merge "Added new assets for contact editor."
diff --git a/res/layout-sw580dp-w1000dp/contact_detail_fragment.xml b/res/layout-sw580dp-w1000dp/contact_detail_fragment.xml
index b8328ef..a15d98f2 100644
--- a/res/layout-sw580dp-w1000dp/contact_detail_fragment.xml
+++ b/res/layout-sw580dp-w1000dp/contact_detail_fragment.xml
@@ -46,6 +46,7 @@
<ListView android:id="@android:id/list"
android:layout_width="0dip"
android:layout_height="match_parent"
+ android:fadingEdge="none"
android:layout_weight="1"
android:divider="@null"/>
diff --git a/res/layout-sw580dp-w1000dp/contact_detail_list_item.xml b/res/layout-sw580dp-w1000dp/contact_detail_list_item.xml
index b57e85c..0d94622 100644
--- a/res/layout-sw580dp-w1000dp/contact_detail_list_item.xml
+++ b/res/layout-sw580dp-w1000dp/contact_detail_list_item.xml
@@ -63,16 +63,17 @@
<ImageView
android:id="@+id/presence_icon"
- android:layout_width="32dip"
- android:layout_height="@dimen/detail_min_line_item_height"
- android:layout_marginLeft="5dip"
- android:gravity="center"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginRight="4dip"
+ android:layout_gravity="center_vertical"
android:scaleType="centerInside" />
<TextView
android:id="@+id/kind"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
android:visibility="gone" />
<TextView
@@ -80,6 +81,7 @@
style="@style/ContactDetailItemType"
android:layout_width="wrap_content"
android:layout_height="match_parent"
+ android:layout_gravity="center_vertical"
android:paddingRight="16dip" />
<View
diff --git a/res/layout-sw580dp-w1000dp/contact_detail_updates_fragment.xml b/res/layout-sw580dp-w1000dp/contact_detail_updates_fragment.xml
index 0e89d24..71c2267 100644
--- a/res/layout-sw580dp-w1000dp/contact_detail_updates_fragment.xml
+++ b/res/layout-sw580dp-w1000dp/contact_detail_updates_fragment.xml
@@ -19,4 +19,5 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/background_social_updates"
+ android:fadingEdge="none"
android:divider="@null"/>
diff --git a/res/layout-sw580dp/contact_detail_fragment.xml b/res/layout-sw580dp/contact_detail_fragment.xml
index f05dc56..9b997e1 100644
--- a/res/layout-sw580dp/contact_detail_fragment.xml
+++ b/res/layout-sw580dp/contact_detail_fragment.xml
@@ -33,6 +33,7 @@
android:layout_weight="1"
android:layout_width="match_parent"
android:layout_height="0dip"
+ android:fadingEdge="none"
android:cacheColorHint="#00000000"
android:divider="@null"
/>
diff --git a/res/layout-sw580dp/contact_detail_updates_fragment.xml b/res/layout-sw580dp/contact_detail_updates_fragment.xml
index d73275e..6002151 100644
--- a/res/layout-sw580dp/contact_detail_updates_fragment.xml
+++ b/res/layout-sw580dp/contact_detail_updates_fragment.xml
@@ -25,6 +25,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/background_social_updates"
+ android:fadingEdge="none"
android:divider="@null"/>
<View
diff --git a/res/layout-sw580dp/contact_editor_fragment.xml b/res/layout-sw580dp/contact_editor_fragment.xml
index 988be2a..572de4f 100644
--- a/res/layout-sw580dp/contact_editor_fragment.xml
+++ b/res/layout-sw580dp/contact_editor_fragment.xml
@@ -26,6 +26,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
+ android:fadingEdge="none"
ex:layout_wideParentWidth="800dip"
ex:layout_wideMarginLeft="128dip"
ex:layout_wideMarginRight="128dip"
diff --git a/res/layout-w470dp/contact_detail_fragment.xml b/res/layout-w470dp/contact_detail_fragment.xml
index 4f26cc5..b523ff8 100644
--- a/res/layout-w470dp/contact_detail_fragment.xml
+++ b/res/layout-w470dp/contact_detail_fragment.xml
@@ -49,6 +49,7 @@
<ListView android:id="@android:id/list"
android:layout_width="0dip"
android:layout_height="match_parent"
+ android:fadingEdge="none"
android:layout_weight="1"
android:divider="@null"/>
@@ -57,6 +58,7 @@
<ScrollView android:id="@android:id/empty"
android:layout_width="match_parent"
android:layout_height="0px"
+ android:fadingEdge="none"
android:visibility="gone">
<TextView android:id="@+id/emptyText"
android:layout_width="match_parent"
diff --git a/res/layout-w470dp/contact_detail_updates_fragment.xml b/res/layout-w470dp/contact_detail_updates_fragment.xml
index 6081cfd..44207fc 100644
--- a/res/layout-w470dp/contact_detail_updates_fragment.xml
+++ b/res/layout-w470dp/contact_detail_updates_fragment.xml
@@ -24,6 +24,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/background_social_updates"
+ android:fadingEdge="none"
android:divider="@null"/>
<View
diff --git a/res/layout/call_log_fragment.xml b/res/layout/call_log_fragment.xml
index ff7dd4b..7cfcef3 100644
--- a/res/layout/call_log_fragment.xml
+++ b/res/layout/call_log_fragment.xml
@@ -37,6 +37,7 @@
<ListView android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:fadingEdge="none"
android:scrollbarStyle="outsideOverlay"
/>
<TextView android:id="@android:id/empty"
diff --git a/res/layout/contact_detail_fragment.xml b/res/layout/contact_detail_fragment.xml
index 581dc03..a7b509d 100644
--- a/res/layout/contact_detail_fragment.xml
+++ b/res/layout/contact_detail_fragment.xml
@@ -24,6 +24,7 @@
android:layout_width="match_parent"
android:layout_height="0px"
android:layout_weight="1"
+ android:fadingEdge="none"
android:background="@color/background_primary"
android:divider="@null"/>
@@ -31,6 +32,7 @@
android:layout_width="match_parent"
android:layout_height="0px"
android:layout_weight="1"
+ android:fadingEdge="none"
android:visibility="gone">
<TextView android:id="@+id/emptyText"
diff --git a/res/layout/contact_detail_list_item.xml b/res/layout/contact_detail_list_item.xml
index e292f39..4d6ed5f 100644
--- a/res/layout/contact_detail_list_item.xml
+++ b/res/layout/contact_detail_list_item.xml
@@ -64,6 +64,15 @@
android:layout_height="wrap_content"
android:orientation="horizontal">
+ <ImageView
+ android:id="@+id/presence_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginRight="4dip"
+ android:layout_gravity="center_vertical"
+ android:gravity="center"
+ android:scaleType="centerInside" />
+
<TextView
android:id="@+id/type"
style="@style/ContactDetailItemType"
@@ -90,14 +99,6 @@
android:visibility="gone" />
</LinearLayout>
-
- <ImageView
- android:id="@+id/presence_icon"
- android:layout_width="32dip"
- android:layout_height="wrap_content"
- android:layout_marginLeft="5dip"
- android:gravity="center"
- android:scaleType="centerInside" />
</com.android.contacts.detail.PrimaryActionViewContainer>
<View
diff --git a/res/layout/contact_editor_fragment.xml b/res/layout/contact_editor_fragment.xml
index f3989e1..913a5e0 100644
--- a/res/layout/contact_editor_fragment.xml
+++ b/res/layout/contact_editor_fragment.xml
@@ -24,6 +24,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
+ android:fadingEdge="none"
>
<LinearLayout android:id="@+id/editors"
diff --git a/res/layout/contact_tile_list.xml b/res/layout/contact_tile_list.xml
index 2047b13..b1469f3 100644
--- a/res/layout/contact_tile_list.xml
+++ b/res/layout/contact_tile_list.xml
@@ -24,6 +24,7 @@
android:id="@+id/contact_tile_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:fadingEdge="none"
android:divider="@null" />
<TextView
@@ -32,6 +33,7 @@
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:layout_marginTop="@dimen/empty_message_top_margin"
- android:textAppearance="?android:attr/textAppearanceMedium"/>
+ android:textColor="?android:attr/textColorSecondary"
+ android:textAppearance="?android:attr/textAppearanceLarge"/>
</FrameLayout>
diff --git a/res/layout/contact_tile_starred.xml b/res/layout/contact_tile_starred.xml
index 757abea..3f8d91d 100644
--- a/res/layout/contact_tile_starred.xml
+++ b/res/layout/contact_tile_starred.xml
@@ -60,6 +60,7 @@
android:textColor="@color/people_contact_tile_status_color"
android:singleLine="true"
android:drawablePadding="4dip"
+ android:paddingBottom="4dip"
android:fadingEdge="horizontal"
android:fadingEdgeLength="3dip"
android:ellipsize="marquee" />
diff --git a/res/layout/contacts_list_content.xml b/res/layout/contacts_list_content.xml
index cc0ebf4..4223f54 100644
--- a/res/layout/contacts_list_content.xml
+++ b/res/layout/contacts_list_content.xml
@@ -64,6 +64,7 @@
android:layout_marginLeft="?attr/contact_browser_list_padding_left"
android:layout_marginRight="?attr/contact_browser_list_padding_right"
android:fastScrollEnabled="true"
+ android:fadingEdge="none"
android:layout_weight="1" />
<ViewStub
diff --git a/res/layout/contacts_unavailable_fragment.xml b/res/layout/contacts_unavailable_fragment.xml
index 5566589..1abc020 100644
--- a/res/layout/contacts_unavailable_fragment.xml
+++ b/res/layout/contacts_unavailable_fragment.xml
@@ -14,70 +14,81 @@
limitations under the License.
-->
-<com.android.contacts.widget.InterpolatingLayout
+<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:ex="http://schemas.android.com/apk/res/com.android.contacts"
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="@drawable/panel_message">
-
+ android:fillViewport="true">
<LinearLayout
- android:layout_width="0dip"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
android:orientation="vertical"
- ex:layout_narrowParentWidth="600dip"
- ex:layout_narrowWidth="400dip"
- ex:layout_wideParentWidth="880dip"
- ex:layout_wideWidth="600dip">
-
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center_horizontal"
+ android:background="@drawable/panel_message">
<TextView
android:id="@+id/message"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:layout_marginBottom="20dip" />
-
- <Button
- android:id="@+id/create_contact_button"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginBottom="15dip"
- android:text="@string/contacts_unavailable_create_contact" />
-
- <Button
- android:id="@+id/add_account_button"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginBottom="15dip"
- android:text="@string/contacts_unavailable_add_account" />
-
- <Button
- android:id="@+id/import_contacts_button"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginBottom="15dip"
- android:text="@string/contacts_unavailable_import_contacts" />
-
- <Button
- android:id="@+id/import_failure_uninstall_button"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginBottom="15dip"
- android:text="@string/upgrade_out_of_memory_uninstall" />
-
- <Button
- android:id="@+id/import_failure_retry_button"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginBottom="15dip"
- android:text="@string/upgrade_out_of_memory_retry" />
-
- <ProgressBar
- android:id="@+id/progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:layout_marginTop="48dip"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:textColor="?android:attr/textColorSecondary" />
+
+ <TextView
+ android:id="@+id/secondary_message"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textColor="?android:attr/textColorSecondary"
android:layout_gravity="center_horizontal"
- android:layout_marginBottom="15dip" />
+ android:layout_marginBottom="@dimen/no_accounts_message_margin" />
+
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_marginLeft="48dip"
+ android:layout_marginRight="48dip"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent">
+ <Button
+ android:id="@+id/create_contact_button"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="15dip"
+ android:text="@string/contacts_unavailable_create_contact" />
+
+ <Button
+ android:id="@+id/add_account_button"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="15dip"
+ android:text="@string/contacts_unavailable_add_account" />
+
+ <Button
+ android:id="@+id/import_contacts_button"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="15dip"
+ android:text="@string/contacts_unavailable_import_contacts" />
+
+ <Button
+ android:id="@+id/import_failure_uninstall_button"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="15dip"
+ android:text="@string/upgrade_out_of_memory_uninstall" />
+
+ <Button
+ android:id="@+id/import_failure_retry_button"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="15dip"
+ android:text="@string/upgrade_out_of_memory_retry" />
+
+ <ProgressBar
+ android:id="@+id/progress"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:layout_marginBottom="15dip" />
+ </LinearLayout>
</LinearLayout>
-</com.android.contacts.widget.InterpolatingLayout>
+</ScrollView>
\ No newline at end of file
diff --git a/res/layout/dialpad_fragment.xml b/res/layout/dialpad_fragment.xml
index 9c5099f..75d5938 100644
--- a/res/layout/dialpad_fragment.xml
+++ b/res/layout/dialpad_fragment.xml
@@ -25,6 +25,7 @@
<!-- Text field and possibly soft menu button above the keypad where
the digits are displayed. -->
<RelativeLayout
+ android:id="@+id/digits_container"
android:layout_width="match_parent"
android:layout_height="0px"
android:layout_weight="0.200"
diff --git a/res/layout/group_browse_list_fragment.xml b/res/layout/group_browse_list_fragment.xml
index 6e82e8f..c390e67 100644
--- a/res/layout/group_browse_list_fragment.xml
+++ b/res/layout/group_browse_list_fragment.xml
@@ -30,6 +30,7 @@
android:paddingRight="16dip"
android:scrollbarStyle="outsideOverlay"
android:layout_weight="1"
+ android:fadingEdge="none"
android:cacheColorHint="@android:color/transparent"
android:divider="@null" />
@@ -38,32 +39,31 @@
android:layout_marginTop="@dimen/empty_message_top_margin"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:paddingTop="8dip"
android:gravity="center_horizontal"
- android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:textColor="?android:attr/textColorSecondary"
android:text="@string/noGroups" />
<LinearLayout
android:id="@+id/add_accounts"
- android:layout_width="match_parent"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
android:orientation="vertical">
<TextView
- android:layout_width="match_parent"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
- android:layout_marginTop="@dimen/no_accounts_message_margin"
android:layout_marginBottom="@dimen/no_accounts_message_margin"
android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textColor="?android:attr/textColorSecondary"
android:text="@string/noAccounts" />
<Button
android:id="@+id/add_account_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginLeft="@dimen/add_account_button_left_margin"
- android:layout_marginRight="@dimen/add_account_button_right_margin"
android:gravity="center"
android:layout_gravity="center_horizontal"
android:textAppearance="?android:attr/textAppearanceMedium"
diff --git a/res/layout/group_browse_list_item.xml b/res/layout/group_browse_list_item.xml
index 599cc13..6bac5ea 100644
--- a/res/layout/group_browse_list_item.xml
+++ b/res/layout/group_browse_list_item.xml
@@ -69,38 +69,6 @@
android:singleLine="true" />
</LinearLayout>
-
- <TableLayout
- android:id="@+id/icons"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentRight="true"
- android:layout_centerVertical="true">
- <TableRow
- android:layout_marginBottom="1dip">
- <ImageView
- android:id="@+id/icon_1"
- android:layout_width="@dimen/group_list_icon_size"
- android:layout_height="@dimen/group_list_icon_size"
- android:layout_marginRight="1dip" />
- <ImageView
- android:id="@+id/icon_2"
- android:layout_width="@dimen/group_list_icon_size"
- android:layout_height="@dimen/group_list_icon_size" />
- </TableRow>
- <TableRow>
- <ImageView
- android:id="@+id/icon_3"
- android:layout_width="@dimen/group_list_icon_size"
- android:layout_height="@dimen/group_list_icon_size"
- android:layout_marginRight="1dip" />
- <ImageView
- android:id="@+id/icon_4"
- android:layout_width="@dimen/group_list_icon_size"
- android:layout_height="@dimen/group_list_icon_size" />
- </TableRow>
-
- </TableLayout>
</RelativeLayout>
</LinearLayout>
diff --git a/res/menu/dialtacts_options.xml b/res/menu/dialtacts_options.xml
index cc9543a..2c83f6b 100644
--- a/res/menu/dialtacts_options.xml
+++ b/res/menu/dialtacts_options.xml
@@ -29,4 +29,9 @@
android:id="@+id/filter_option"
android:title="@string/menu_contacts_filter"
android:showAsAction="withText" />
+
+ <item
+ android:id="@+id/add_contact"
+ android:title="@string/menu_newContact"
+ android:showAsAction="withText" />
</menu>
diff --git a/res/menu/dialtacts_search_options.xml b/res/menu/dialtacts_search_options.xml
index f2e0c67..0979ebb 100644
--- a/res/menu/dialtacts_search_options.xml
+++ b/res/menu/dialtacts_search_options.xml
@@ -19,4 +19,8 @@
android:id="@+id/filter_option"
android:title="@string/menu_contacts_filter"
android:showAsAction="withText" />
+ <item
+ android:id="@+id/add_contact"
+ android:title="@string/menu_newContact"
+ android:showAsAction="withText" />
</menu>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index a67aa6b..f5fd3bf 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -240,10 +240,8 @@
<dimen name="call_detail_action_bar_height">60dip</dimen>
<!-- Empty message margins -->
- <dimen name="empty_message_top_margin">43dip</dimen>
- <dimen name="no_accounts_message_margin">15dip</dimen>
- <dimen name="add_account_button_left_margin">50dip</dimen>
- <dimen name="add_account_button_right_margin">50dip</dimen>
+ <dimen name="empty_message_top_margin">48dip</dimen>
+ <dimen name="no_accounts_message_margin">20dip</dimen>
<!-- For contact filter setting screens -->
<dimen name="contact_filter_left_margin">16dip</dimen>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index c7a2bc7..11e7e38 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -783,16 +783,16 @@
<string name="searching_vcard_message" product="default">Searching for vCard data on SD card</string>
<!-- Dialog title shown when scanning VCard data failed. [CHAR LIMIT=NONE] -->
- <string name="scanning_sdcard_failed_title" product="nosdcard">Scanning storage failed</string>
+ <string name="scanning_sdcard_failed_title" product="nosdcard">Couldn\'t scan storage</string>
<!-- Dialog title shown when scanning VCard data failed. -->
- <string name="scanning_sdcard_failed_title" product="default">Scanning SD card failed</string>
+ <string name="scanning_sdcard_failed_title" product="default">Couldn\'t scan SD card</string>
<!-- Dialog message shown when searching VCard data failed.
An exact reason for the failure should [CHAR LIMIT=NONE] -->
- <string name="scanning_sdcard_failed_message" product="nosdcard">Scanning storage failed (Reason: \"<xliff:g id="fail_reason">%s</xliff:g>\")</string>
+ <string name="scanning_sdcard_failed_message" product="nosdcard">Couldn\'t scan storage (Reason: \"<xliff:g id="fail_reason">%s</xliff:g>\")</string>
<!-- Dialog message shown when searching VCard data failed.
An exact reason for the failure should -->
- <string name="scanning_sdcard_failed_message" product="default">Scanning SD card failed (Reason: \"<xliff:g id="fail_reason">%s</xliff:g>\")</string>
+ <string name="scanning_sdcard_failed_message" product="default">Couldn\'t scan SD card (Reason: \"<xliff:g id="fail_reason">%s</xliff:g>\")</string>
<!-- The failed reason shown when Contacts app (especially vCard importer/exporter)
emitted some I/O error. Exact reason will be appended by the system. -->
@@ -805,14 +805,14 @@
<!-- The failed reason shown when vCard parser was not able to be parsed by the current vCard
implementation. This might happen even when the input vCard is completely valid, though
we believe it is rather rare in the actual world. -->
- <string name="fail_reason_vcard_parse_error">Failed to parse vCard for unexpected reason</string>
+ <string name="fail_reason_vcard_parse_error">Couldn\'t parse vCard for unexpected reason</string>
<!-- The failed reason shown when vCard importer doesn't support the format.
This may be shown when the vCard is corrupted [CHAR LIMIT=40] -->
<string name="fail_reason_not_supported">The format is not supported.</string>
<!-- Message used when vCard import has failed. [CHAR LIMIT=40] -->
- <string name="vcard_import_failed">Failed to import vCard</string>
+ <string name="vcard_import_failed">Couldn\'t import vCard</string>
<!-- The failure message shown when the system could not find any vCard file.
(with extension ".vcf" in (USB) storage.)
@@ -824,11 +824,11 @@
<string name="import_failure_no_vcard_file" product="default">No vCard file found on the SD card</string>
<!-- Fail reason shown when vCard importer failed to look over meta information stored in vCard file(s). -->
- <string name="fail_reason_failed_to_collect_vcard_meta_info">Failed to collect meta information of given vCard file(s).</string>
+ <string name="fail_reason_failed_to_collect_vcard_meta_info">Couldn\'t collect meta information of given vCard file(s).</string>
<!-- The failed reason shown when the import of some of vCard files failed during multiple vCard
files import. It includes the case where all files were failed to be imported. -->
- <string name="fail_reason_failed_to_read_files">One or more files failed to be imported (%s).</string>
+ <string name="fail_reason_failed_to_read_files">One or more files couldn\'t be imported (%s).</string>
<!-- The failed reason which should not be shown but it may in some buggy condition. -->
<string name="fail_reason_unknown">Unknown error</string>
@@ -857,7 +857,7 @@
<string name="importing_vcard_description">Importing <xliff:g id="name" example="Joe Due">%s</xliff:g></string>
<!-- Dialog title shown when reading vCard data failed [CHAR LIMIT=40] -->
- <string name="reading_vcard_failed_title">Failed to Read vCard data</string>
+ <string name="reading_vcard_failed_title">Couldn\'t read vCard data</string>
<!-- The title shown when reading vCard is canceled (probably by a user)
[CHAR LIMIT=40] -->
@@ -905,10 +905,10 @@
<string name="confirm_export_message">Export your contact list to file \"<xliff:g id="vcard_filename">%s</xliff:g>\"?</string>
<!-- Dialog title shown when exporting Contact data failed -->
- <string name="exporting_contact_failed_title">Failed to export contact data</string>
+ <string name="exporting_contact_failed_title">Couldn\'t export contact data</string>
<!-- Dialog message shown when exporting Contact data failed -->
- <string name="exporting_contact_failed_message">Failed to export contact data.\nReason for failure: \"<xliff:g id="fail_reason">%s</xliff:g>\"</string>
+ <string name="exporting_contact_failed_message">Couldn\'t export contact data.\nReason: \"<xliff:g id="fail_reason">%s</xliff:g>\"</string>
<!-- The failed reason shown when there's no contact which is allowed to be exported.
Note that user may have contacts data but all of them are probably not allowed to be
@@ -951,7 +951,7 @@
<!-- The error reason the vCard composer "may" emit when database is corrupted or
something is going wrong. Usually users should not see this text. -->
- <string name="composer_failed_to_get_database_infomation">Failed to get database information</string>
+ <string name="composer_failed_to_get_database_infomation">Couldn\'t get database information</string>
<!-- This error message shown when the user actually have no contact
(e.g. just after data-wiping), or, data providers of the contact list prohibits their
@@ -991,7 +991,7 @@
<string name="cancel_export_confirmation_message">Are you sure to cancel exporting <xliff:g id="filename" example="export.vcf">%s</xliff:g>?</string>
<!-- Title shown in a Dialog telling users cancel vCard import/export operation is failed. [CHAR LIMIT=40] -->
- <string name="cancel_vcard_import_or_export_failed">Failed to cancel vCard import/export</string>
+ <string name="cancel_vcard_import_or_export_failed">Couldn\'t cancel vCard import/export</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>
@@ -1630,16 +1630,16 @@
<string name="notification_new_voicemail_ticker">New voicemail from <xliff:g id="caller">%1$s</xliff:g></string>
<!-- Message to show when there is an error playing back the voicemail. [CHAR LIMIT=40] -->
- <string name="voicemail_playback_error">failed to play voicemail</string>
+ <string name="voicemail_playback_error">Couldn\'t play voicemail.</string>
<!-- Message to display before we have prepared the media player, i.e. before we know duration. [CHAR LIMIT=40] -->
- <string name="voicemail_buffering">buffering...</string>
+ <string name="voicemail_buffering">Buffering\u2026</string>
<!-- Message to display whilst we are waiting for the content to be fetched. [CHAR LIMIT=40] -->
- <string name="voicemail_fetching_content">fetching voicemail...</string>
+ <string name="voicemail_fetching_content">Fetching voicemail\u2026</string>
<!-- Message to display if we fail to get content within a suitable time period. [CHAR LIMIT=40] -->
- <string name="voicemail_fetching_timout">failed to fetch voicemail</string>
+ <string name="voicemail_fetching_timout">Couldn\'t fetch voicemail.</string>
<!-- The header in the call log used to identify missed calls and voicemail that have not yet been consumed [CHAR LIMIT=10] -->
<string name="call_log_new_header">New</string>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 418d632..03a9134 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -168,6 +168,7 @@
<style name="ContactsActionBarStyle" parent="@android:style/Widget.Holo.Light.ActionBar.Solid.Inverse">
<item name="android:background">@drawable/ab_solid_custom_blue_inverse_holo</item>
<item name="android:backgroundStacked">@drawable/ab_solid_custom_blue_inverse_holo</item>
+ <item name="android:displayOptions"></item>
</style>
<style name="ContactsActionBarTabView" parent="@android:style/Widget.Holo.ActionBar.TabView">
@@ -287,6 +288,7 @@
<style name="DialtactsActionBarStyle" parent="android:Widget.Holo.ActionBar">
<item name="android:backgroundSplit">@drawable/ab_bottom_opaque_dark_holo</item>
<item name="android:backgroundStacked">@drawable/ab_stacked_opaque_dark_holo</item>
+ <item name="android:displayOptions"></item>
</style>
</resources>
diff --git a/src/com/android/contacts/ContactsUtils.java b/src/com/android/contacts/ContactsUtils.java
index 45ce4fe..2f13481 100644
--- a/src/com/android/contacts/ContactsUtils.java
+++ b/src/com/android/contacts/ContactsUtils.java
@@ -16,6 +16,8 @@
package com.android.contacts;
+import com.android.contacts.model.AccountTypeManager;
+import com.android.contacts.model.AccountWithDataSet;
import com.android.i18n.phonenumbers.NumberParseException;
import com.android.i18n.phonenumbers.PhoneNumberUtil;
import com.android.i18n.phonenumbers.PhoneNumberUtil.MatchType;
@@ -29,6 +31,8 @@
import android.telephony.PhoneNumberUtils;
import android.text.TextUtils;
+import java.util.List;
+
public class ContactsUtils {
private static final String TAG = "ContactsUtils";
private static final String WAIT_SYMBOL_AS_STRING = String.valueOf(PhoneNumberUtils.WAIT);
@@ -166,4 +170,12 @@
(CountryDetector) context.getSystemService(Context.COUNTRY_DETECTOR);
return detector.detectCountry().getCountryIso();
}
+
+ public static boolean areAccountsAvailable(Context context) {
+ final List<AccountWithDataSet> accounts =
+ AccountTypeManager.getInstance(context).getAccounts(true /* writeable */);
+ return !accounts.isEmpty();
+ }
+
+
}
diff --git a/src/com/android/contacts/activities/ContactEditorActivity.java b/src/com/android/contacts/activities/ContactEditorActivity.java
index 2936110..c69cc04 100644
--- a/src/com/android/contacts/activities/ContactEditorActivity.java
+++ b/src/com/android/contacts/activities/ContactEditorActivity.java
@@ -151,11 +151,6 @@
public void onSaveFinished(Intent resultIntent) {
if (resultIntent != null) {
startActivity(resultIntent);
- } else {
- // Navigate home
- Intent intent = new Intent(ContactEditorActivity.this, PeopleActivity.class);
- intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
- startActivity(intent);
}
finish();
}
diff --git a/src/com/android/contacts/activities/DialtactsActivity.java b/src/com/android/contacts/activities/DialtactsActivity.java
index 12709c8..048ae5f 100644
--- a/src/com/android/contacts/activities/DialtactsActivity.java
+++ b/src/com/android/contacts/activities/DialtactsActivity.java
@@ -46,6 +46,7 @@
import android.os.RemoteException;
import android.os.ServiceManager;
import android.provider.CallLog.Calls;
+import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Intents.UI;
import android.support.v13.app.FragmentPagerAdapter;
import android.support.v4.view.ViewPager;
@@ -253,6 +254,9 @@
popupMenu.inflate(R.menu.dialtacts_search_options);
final MenuItem filterOptionMenuItem = menu.findItem(R.id.filter_option);
filterOptionMenuItem.setOnMenuItemClickListener(mFilterOptionsMenuItemClickListener);
+ final MenuItem addContactOptionMenuItem = menu.findItem(R.id.add_contact);
+ addContactOptionMenuItem.setIntent(
+ new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI));
popupMenu.show();
}
};
@@ -693,6 +697,7 @@
public boolean onPrepareOptionsMenu(Menu menu) {
final MenuItem searchMenuItem = menu.findItem(R.id.search_on_action_bar);
final MenuItem filterOptionMenuItem = menu.findItem(R.id.filter_option);
+ final MenuItem addContactOptionMenuItem = menu.findItem(R.id.add_contact);
final MenuItem callSettingsMenuItem = menu.findItem(R.id.menu_call_settings);
Tab tab = getActionBar().getSelectedTab();
if (mInSearchUi) {
@@ -701,9 +706,13 @@
filterOptionMenuItem.setVisible(true);
filterOptionMenuItem.setOnMenuItemClickListener(
mFilterOptionsMenuItemClickListener);
+ addContactOptionMenuItem.setVisible(true);
+ addContactOptionMenuItem.setIntent(
+ new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI));
} else {
// Filter option menu should be not be shown as a overflow menu.
filterOptionMenuItem.setVisible(false);
+ addContactOptionMenuItem.setVisible(false);
}
callSettingsMenuItem.setVisible(false);
} else {
@@ -719,6 +728,7 @@
showCallSettingsMenu = true;
}
filterOptionMenuItem.setVisible(false);
+ addContactOptionMenuItem.setVisible(false);
if (showCallSettingsMenu) {
callSettingsMenuItem.setVisible(true);
diff --git a/src/com/android/contacts/activities/PeopleActivity.java b/src/com/android/contacts/activities/PeopleActivity.java
index 68e9cbb..1c69ef9 100644
--- a/src/com/android/contacts/activities/PeopleActivity.java
+++ b/src/com/android/contacts/activities/PeopleActivity.java
@@ -19,6 +19,7 @@
import com.android.contacts.ContactLoader;
import com.android.contacts.ContactSaveService;
import com.android.contacts.ContactsActivity;
+import com.android.contacts.ContactsUtils;
import com.android.contacts.R;
import com.android.contacts.activities.ActionBarAdapter.TabState;
import com.android.contacts.detail.ContactDetailFragment;
@@ -199,9 +200,7 @@
}
private boolean areAccountsAvailable() {
- final List<AccountWithDataSet> accounts =
- AccountTypeManager.getInstance(this).getAccounts(true /* writeable */);
- return !accounts.isEmpty();
+ return ContactsUtils.areAccountsAvailable(this);
}
@@ -681,13 +680,14 @@
switch (tab) {
case FAVORITES:
mContactsUnavailableFragment.setMessageText(
- R.string.listTotalAllContactsZeroStarred);
+ R.string.listTotalAllContactsZeroStarred, -1);
break;
case GROUPS:
- mContactsUnavailableFragment.setMessageText(R.string.noGroups);
+ mContactsUnavailableFragment.setMessageText(R.string.noGroups,
+ areAccountsAvailable() ? -1 : R.string.noAccounts);
break;
case ALL:
- mContactsUnavailableFragment.setMessageText(R.string.noContacts);
+ mContactsUnavailableFragment.setMessageText(R.string.noContacts, -1);
break;
}
}
diff --git a/src/com/android/contacts/calllog/CallLogAdapter.java b/src/com/android/contacts/calllog/CallLogAdapter.java
index 7e934b6..28e2e90 100644
--- a/src/com/android/contacts/calllog/CallLogAdapter.java
+++ b/src/com/android/contacts/calllog/CallLogAdapter.java
@@ -24,6 +24,7 @@
import com.android.contacts.util.ExpirableCache;
import com.google.common.annotations.VisibleForTesting;
+import android.content.ContentValues;
import android.content.Context;
import android.content.res.Resources;
import android.database.Cursor;
@@ -37,6 +38,7 @@
import android.provider.ContactsContract.PhoneLookup;
import android.telephony.PhoneNumberUtils;
import android.text.TextUtils;
+import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -47,7 +49,7 @@
/**
* Adapter class to fill in data for the Call Log.
*/
-public final class CallLogAdapter extends GroupingListAdapter
+public class CallLogAdapter extends GroupingListAdapter
implements Runnable, ViewTreeObserver.OnPreDrawListener, CallLogGroupBuilder.GroupCreator {
/** Interface used to initiate a refresh of the content. */
public interface CallFetcher {
@@ -75,10 +77,13 @@
/**
* List of requests to update contact details.
* <p>
+ * Each request is made of a phone number to look up, and the contact info currently stored in
+ * the call log for this number.
+ * <p>
* The requests are added when displaying the contacts and are processed by a background
* thread.
*/
- private final LinkedList<String> mRequests;
+ private final LinkedList<Pair<String, ContactInfo>> mRequests;
private volatile boolean mDone;
private boolean mLoading = true;
@@ -155,7 +160,7 @@
mCallFetcher = callFetcher;
mContactInfoCache = ExpirableCache.create(CONTACT_INFO_CACHE_SIZE);
- mRequests = new LinkedList<String>();
+ mRequests = new LinkedList<Pair<String,ContactInfo>>();
mPreDrawListener = null;
Resources resources = mContext.getResources();
@@ -230,10 +235,21 @@
mPreDrawListener = null;
}
- private void enqueueRequest(String number, boolean immediate) {
+ /**
+ * Enqueues a request to look up the contact details for the given phone number.
+ * <p>
+ * It also provides the current contact info stored in the call log for this number.
+ * <p>
+ * If the {@code immediate} parameter is true, it will start immediately the thread that looks
+ * up the contact information (if it has not been already started). Otherwise, it will be
+ * started with a delay. See {@link #START_PROCESSING_REQUESTS_DELAY_MILLIS}.
+ */
+ @VisibleForTesting
+ void enqueueRequest(String number, ContactInfo callLogInfo, boolean immediate) {
+ Pair<String, ContactInfo> request = new Pair<String, ContactInfo>(number, callLogInfo);
synchronized (mRequests) {
- if (!mRequests.contains(number)) {
- mRequests.add(number);
+ if (!mRequests.contains(request)) {
+ mRequests.add(request);
mRequests.notifyAll();
}
}
@@ -378,12 +394,15 @@
/**
* Queries the appropriate content provider for the contact associated with the number.
* <p>
+ * Upon completion it also updates the cache in the call log, if it is different from
+ * {@code callLogInfo}.
+ * <p>
* The number might be either a SIP address or a phone number.
* <p>
* It returns true if it updated the content of the cache and we should therefore tell the
* view to update its content.
*/
- private boolean queryContactInfo(String number) {
+ private boolean queryContactInfo(String number, ContactInfo callLogInfo) {
final ContactInfo info;
// Determine the contact info.
@@ -411,6 +430,9 @@
// Store the data in the cache so that the UI thread can use to display it. Store it
// even if it has not changed so that it is marked as not expired.
mContactInfoCache.put(number, info);
+ // Update the call log even if the cache it is up-to-date: it is possible that the cache
+ // contains the value from a different call log entry.
+ updateCallLogContactInfoCache(number, info, callLogInfo);
return updated;
}
@@ -423,9 +445,12 @@
boolean needNotify = false;
while (!mDone) {
String number = null;
+ ContactInfo callLogInfo = null;
synchronized (mRequests) {
if (!mRequests.isEmpty()) {
- number = mRequests.removeFirst();
+ Pair<String, ContactInfo> request = mRequests.removeFirst();
+ number = request.first;
+ callLogInfo = request.second;
} else {
if (needNotify) {
needNotify = false;
@@ -439,7 +464,7 @@
}
}
}
- if (!mDone && number != null && queryContactInfo(number)) {
+ if (!mDone && number != null && queryContactInfo(number, callLogInfo)) {
needNotify = true;
}
}
@@ -571,14 +596,20 @@
info = ContactInfo.EMPTY;
mContactInfoCache.put(number, info);
// Request the contact details immediately since they are currently missing.
- enqueueRequest(number, true);
+ enqueueRequest(number, cachedContactInfo, true);
// Format the phone number in the call log as best as we can.
formattedNumber = formatPhoneNumber(number, null, countryIso);
} else {
if (cachedInfo.isExpired()) {
// The contact info is no longer up to date, we should request it. However, we
// do not need to request them immediately.
- enqueueRequest(number, false);
+ enqueueRequest(number, cachedContactInfo, false);
+ } else if (!callLogInfoMatches(cachedContactInfo, info)) {
+ // The call log information does not match the one we have, look it up again.
+ // We could simply update the call log directly, but that needs to be done in a
+ // background thread, so it is easier to simply request a new lookup, which will, as
+ // a side-effect, update the call log.
+ enqueueRequest(number, cachedContactInfo, false);
}
if (info != ContactInfo.EMPTY) {
@@ -629,6 +660,52 @@
}
}
+ /** Checks whether the contact info from the call log matches the one from the contacts db. */
+ private boolean callLogInfoMatches(ContactInfo callLogInfo, ContactInfo info) {
+ // The call log only contains a subset of the fields in the contacts db.
+ // Only check those.
+ return TextUtils.equals(callLogInfo.name, info.name)
+ && callLogInfo.type == info.type
+ && TextUtils.equals(callLogInfo.label, info.label);
+ }
+
+ /** Stores the updated contact info in the call log if it is different from the current one. */
+ private void updateCallLogContactInfoCache(String number, ContactInfo updatedInfo,
+ ContactInfo callLogInfo) {
+ final ContentValues values = new ContentValues();
+ boolean needsUpdate = false;
+
+ if (callLogInfo != null) {
+ if (!TextUtils.equals(updatedInfo.name, callLogInfo.name)) {
+ values.put(Calls.CACHED_NAME, updatedInfo.name);
+ needsUpdate = true;
+ }
+
+ if (updatedInfo.type != callLogInfo.type) {
+ values.put(Calls.CACHED_NUMBER_TYPE, updatedInfo.type);
+ needsUpdate = true;
+ }
+
+ if (!TextUtils.equals(updatedInfo.label, callLogInfo.label)) {
+ values.put(Calls.CACHED_NUMBER_LABEL, updatedInfo.label);
+ needsUpdate = true;
+ }
+ } else {
+ needsUpdate = true;
+ }
+
+ if (!needsUpdate) {
+ return;
+ }
+
+ StringBuilder where = new StringBuilder();
+ where.append(Calls.NUMBER);
+ where.append(" = ?");
+
+ mContext.getContentResolver().update(Calls.CONTENT_URI_WITH_VOICEMAIL, values,
+ where.toString(), new String[]{ number });
+ }
+
/** Returns the contact information as stored in the call log. */
private ContactInfo getContactInfoFromCallLog(Cursor c) {
ContactInfo info = new ContactInfo();
diff --git a/src/com/android/contacts/detail/ContactDetailDisplayUtils.java b/src/com/android/contacts/detail/ContactDetailDisplayUtils.java
index cbdf148..18a4750 100644
--- a/src/com/android/contacts/detail/ContactDetailDisplayUtils.java
+++ b/src/com/android/contacts/detail/ContactDetailDisplayUtils.java
@@ -20,13 +20,13 @@
import com.android.contacts.ContactLoader.Result;
import com.android.contacts.ContactPhotoManager;
import com.android.contacts.R;
-import com.android.contacts.format.FormatUtils;
import com.android.contacts.preference.ContactsPreferences;
import com.android.contacts.util.ContactBadgeUtil;
import com.android.contacts.util.StreamItemEntry;
import com.android.contacts.util.StreamItemPhotoEntry;
import com.google.common.annotations.VisibleForTesting;
+import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.Entity;
@@ -37,16 +37,15 @@
import android.content.res.Resources.NotFoundException;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
-import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.provider.ContactsContract;
import android.provider.ContactsContract.CommonDataKinds.Organization;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.DisplayNameSources;
+import android.provider.ContactsContract.StreamItems;
import android.text.Html;
import android.text.Html.ImageGetter;
-import android.text.Spanned;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
@@ -71,6 +70,27 @@
private static final int PHOTO_FADE_IN_ANIMATION_DURATION_MILLIS = 100;
+ /**
+ * Tag object used for stream item photos.
+ */
+ public static class StreamPhotoTag {
+ public final StreamItemEntry streamItem;
+ public final StreamItemPhotoEntry streamItemPhoto;
+
+ public StreamPhotoTag(StreamItemEntry streamItem, StreamItemPhotoEntry streamItemPhoto) {
+ this.streamItem = streamItem;
+ this.streamItemPhoto = streamItemPhoto;
+ }
+
+ public Uri getStreamItemPhotoUri() {
+ final Uri.Builder builder = StreamItems.CONTENT_URI.buildUpon();
+ ContentUris.appendId(builder, streamItem.getId());
+ builder.appendPath(StreamItems.StreamItemPhotos.CONTENT_DIRECTORY);
+ ContentUris.appendId(builder, streamItemPhoto.getId());
+ return builder.build();
+ }
+ }
+
private ContactDetailDisplayUtils() {
// Disallow explicit creation of this class.
}
@@ -244,7 +264,8 @@
/** Creates the view that represents a stream item. */
public static View createStreamItemView(LayoutInflater inflater, Context context,
- StreamItemEntry streamItem, LinearLayout parent) {
+ StreamItemEntry streamItem, LinearLayout parent,
+ View.OnClickListener photoClickListener) {
View container = inflater.inflate(R.layout.stream_item_container, parent, false);
ViewGroup contentTable = (ViewGroup) container.findViewById(R.id.stream_item_content);
@@ -261,17 +282,17 @@
View photoContainer = inflater.inflate(R.layout.stream_item_row_two_images,
contentTable, false);
- loadPhoto(contactPhotoManager, firstPhoto, photoContainer,
- R.id.stream_item_first_image);
- loadPhoto(contactPhotoManager, secondPhoto, photoContainer,
- R.id.stream_item_second_image);
+ loadPhoto(contactPhotoManager, streamItem, firstPhoto, photoContainer,
+ R.id.stream_item_first_image, photoClickListener);
+ loadPhoto(contactPhotoManager, streamItem, secondPhoto, photoContainer,
+ R.id.stream_item_second_image, photoClickListener);
contentTable.addView(photoContainer);
} else {
// Put in a single photo with text on the side.
View photoContainer = inflater.inflate(
R.layout.stream_item_row_image_and_text, contentTable, false);
- loadPhoto(contactPhotoManager, firstPhoto, photoContainer,
- R.id.stream_item_first_image);
+ loadPhoto(contactPhotoManager, streamItem, firstPhoto, photoContainer,
+ R.id.stream_item_first_image, photoClickListener);
addStreamItemText(context, streamItem,
photoContainer.findViewById(R.id.stream_item_second_text));
contentTable.addView(photoContainer);
@@ -294,11 +315,22 @@
return container;
}
- /** Loads a photo into an image view. The image view is identifiedc by the given id. */
+ /** Loads a photo into an image view. The image view is identified by the given id. */
private static void loadPhoto(ContactPhotoManager contactPhotoManager,
- final StreamItemPhotoEntry firstPhoto, View photoContainer, int imageViewId) {
- ImageView firstImageView = (ImageView) photoContainer.findViewById(imageViewId);
- contactPhotoManager.loadPhoto(firstImageView, Uri.parse(firstPhoto.getPhotoUri()));
+ final StreamItemEntry streamItem, final StreamItemPhotoEntry streamItemPhoto,
+ View photoContainer, int imageViewId, View.OnClickListener photoClickListener) {
+ ImageView imageView = (ImageView) photoContainer.findViewById(imageViewId);
+ if (photoClickListener != null) {
+ imageView.setOnClickListener(photoClickListener);
+ imageView.setTag(new StreamPhotoTag(streamItem, streamItemPhoto));
+ imageView.setFocusable(true);
+ } else {
+ imageView.setOnClickListener(null);
+ imageView.setTag(null);
+ imageView.setFocusable(false);
+ imageView.setClickable(false); // setOnClickListener makes it clickable, so overwrite it
+ }
+ contactPhotoManager.loadPhoto(imageView, Uri.parse(streamItemPhoto.getPhotoUri()));
}
@VisibleForTesting
diff --git a/src/com/android/contacts/detail/ContactDetailFragment.java b/src/com/android/contacts/detail/ContactDetailFragment.java
index ca99c79..4d50a39 100644
--- a/src/com/android/contacts/detail/ContactDetailFragment.java
+++ b/src/com/android/contacts/detail/ContactDetailFragment.java
@@ -43,6 +43,7 @@
import com.android.contacts.util.PhoneCapabilityTester;
import com.android.contacts.widget.TransitionAnimationView;
import com.android.internal.telephony.ITelephony;
+import com.google.common.annotations.VisibleForTesting;
import android.app.Activity;
import android.app.Fragment;
@@ -122,8 +123,6 @@
private static final String TAG = "ContactDetailFragment";
- private static final int LOADER_DETAILS = 1;
-
private interface ContextMenuIds {
static final int COPY_TEXT = 0;
static final int CLEAR_DEFAULT = 1;
@@ -132,7 +131,6 @@
private static final String KEY_CONTACT_URI = "contactUri";
private static final String KEY_LIST_STATE = "liststate";
- private static final String LOADER_ARG_CONTACT_URI = "contactUri";
private Context mContext;
private View mView;
@@ -605,7 +603,7 @@
final DetailViewEntry imEntry = DetailViewEntry.fromValues(mContext, imMime,
imKind, dataId, entryValues, mContactData.isDirectoryEntry(),
mContactData.getDirectoryId());
- buildImActions(imEntry, entryValues);
+ buildImActions(mContext, imEntry, entryValues);
imEntry.applyStatus(status, false);
mImEntries.add(imEntry);
}
@@ -617,7 +615,7 @@
mPostalEntries.add(entry);
} else if (Im.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
// Build IM entries
- buildImActions(entry, entryValues);
+ buildImActions(mContext, entry, entryValues);
// Apply presence and status details when available
final DataStatus status = mContactData.getStatuses().get(entry.id);
@@ -936,11 +934,11 @@
}
/**
- * Build {@link Intent} to launch an action for the given {@link Im} or
- * {@link Email} row. If the result is non-null, it either contains one or two Intents
- * (e.g. [Text, Videochat] or just [Text])
+ * Writes the Instant Messaging action into the given entry value.
*/
- public static void buildImActions(DetailViewEntry entry, ContentValues values) {
+ @VisibleForTesting
+ public static void buildImActions(Context context, DetailViewEntry entry,
+ ContentValues values) {
final boolean isEmail = Email.CONTENT_ITEM_TYPE.equals(values.getAsString(Data.MIMETYPE));
if (!isEmail && !isProtocolValid(values)) {
@@ -958,6 +956,8 @@
final Integer chatCapabilityObj = values.getAsInteger(Im.CHAT_CAPABILITY);
final int chatCapability = chatCapabilityObj == null ? 0 : chatCapabilityObj;
entry.chatCapability = chatCapability;
+ entry.typeString = Im.getProtocolLabel(context.getResources(), Im.PROTOCOL_GOOGLE_TALK,
+ null).toString();
if ((chatCapability & Im.CAPABILITY_HAS_CAMERA) != 0) {
entry.actionIcon = R.drawable.sym_action_talk_holo_light;
entry.intent =
diff --git a/src/com/android/contacts/detail/ContactDetailUpdatesFragment.java b/src/com/android/contacts/detail/ContactDetailUpdatesFragment.java
index 846a957..99766f2 100644
--- a/src/com/android/contacts/detail/ContactDetailUpdatesFragment.java
+++ b/src/com/android/contacts/detail/ContactDetailUpdatesFragment.java
@@ -19,9 +19,11 @@
import com.android.contacts.ContactLoader;
import com.android.contacts.R;
import com.android.contacts.activities.ContactDetailActivity.FragmentKeyListener;
+import com.android.contacts.detail.ContactDetailDisplayUtils.StreamPhotoTag;
import com.android.contacts.model.AccountType;
import com.android.contacts.model.AccountTypeManager;
import com.android.contacts.util.StreamItemEntry;
+import com.android.contacts.util.StreamItemPhotoEntry;
import android.app.ListFragment;
import android.content.ContentUris;
@@ -67,7 +69,7 @@
* <p>
* It assumes the view has a tag of type {@link StreamItemEntry} associated with it.
*/
- private View.OnClickListener mStreamItemClickListener = new View.OnClickListener() {
+ private final View.OnClickListener mStreamItemClickListener = new View.OnClickListener() {
@Override
public void onClick(View view) {
StreamItemEntry streamItemEntry = (StreamItemEntry) view.getTag();
@@ -75,9 +77,7 @@
// Ignore if this item does not have a stream item associated with it.
return;
}
- final AccountTypeManager manager = AccountTypeManager.getInstance(getActivity());
- final AccountType accountType = manager.getAccountType(
- streamItemEntry.getAccountType(), streamItemEntry.getDataSet());
+ final AccountType accountType = getAccountTypeForStreamItemEntry(streamItemEntry);
final Uri uri = ContentUris.withAppendedId(StreamItems.CONTENT_URI,
streamItemEntry.getId());
@@ -88,6 +88,28 @@
}
};
+ private final View.OnClickListener mStreamItemPhotoItemClickListener
+ = new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ StreamPhotoTag tag = (StreamPhotoTag) view.getTag();
+ if (tag == null) {
+ return;
+ }
+ final AccountType accountType = getAccountTypeForStreamItemEntry(tag.streamItem);
+
+ final Intent intent = new Intent(Intent.ACTION_VIEW, tag.getStreamItemPhotoUri());
+ intent.setClassName(accountType.resPackageName,
+ accountType.getViewStreamItemPhotoActivity());
+ startActivity(intent);
+ }
+ };
+
+ private AccountType getAccountTypeForStreamItemEntry(StreamItemEntry streamItemEntry) {
+ return AccountTypeManager.getInstance(getActivity()).getAccountType(
+ streamItemEntry.getAccountType(), streamItemEntry.getDataSet());
+ }
+
public ContactDetailUpdatesFragment() {
// Explicit constructor for inflation
}
@@ -108,7 +130,8 @@
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
- mStreamItemAdapter = new StreamItemAdapter(getActivity(), mStreamItemClickListener);
+ mStreamItemAdapter = new StreamItemAdapter(getActivity(), mStreamItemClickListener,
+ mStreamItemPhotoItemClickListener);
setListAdapter(mStreamItemAdapter);
getListView().setOnScrollListener(mVerticalScrollListener);
diff --git a/src/com/android/contacts/detail/StreamItemAdapter.java b/src/com/android/contacts/detail/StreamItemAdapter.java
index 5128787..9094c5c 100644
--- a/src/com/android/contacts/detail/StreamItemAdapter.java
+++ b/src/com/android/contacts/detail/StreamItemAdapter.java
@@ -42,14 +42,17 @@
private static final int ITEM_VIEW_TYPE_STREAM_ITEM = 2;
private final Context mContext;
- private final View.OnClickListener mListener;
+ private final View.OnClickListener mItemClickListener;
+ private final View.OnClickListener mPhotoClickListener;
private final LayoutInflater mInflater;
private List<StreamItemEntry> mStreamItems;
- public StreamItemAdapter(Context context, View.OnClickListener listener) {
+ public StreamItemAdapter(Context context, View.OnClickListener itemClickListener,
+ View.OnClickListener photoClickListener) {
mContext = context;
- mListener = listener;
+ mItemClickListener = itemClickListener;
+ mPhotoClickListener = photoClickListener;
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mStreamItems = Lists.newArrayList();
}
@@ -83,20 +86,23 @@
if (position == 1) {
return mInflater.inflate(R.layout.updates_title, null);
}
- StreamItemEntry streamItem = (StreamItemEntry) getItem(position);
- View view = ContactDetailDisplayUtils.createStreamItemView(
- mInflater, mContext, streamItem, null);
+ final StreamItemEntry streamItem = (StreamItemEntry) getItem(position);
final AccountTypeManager manager = AccountTypeManager.getInstance(mContext);
final AccountType accountType =
manager.getAccountType(streamItem.getAccountType(), streamItem.getDataSet());
+ final View view = ContactDetailDisplayUtils.createStreamItemView(
+ mInflater, mContext, streamItem, null,
+ (accountType.getViewStreamItemPhotoActivity() == null) ? null : mPhotoClickListener
+ );
if (accountType.getViewStreamItemActivity() != null) {
view.setTag(streamItem);
view.setFocusable(true);
- view.setOnClickListener(mListener);
+ view.setOnClickListener(mItemClickListener);
} else {
view.setTag(null);
view.setFocusable(false);
view.setOnClickListener(null);
+ view.setClickable(false); // setOnClickListener makes it clickable, so overwrite it
}
return view;
}
diff --git a/src/com/android/contacts/dialpad/DialpadFragment.java b/src/com/android/contacts/dialpad/DialpadFragment.java
index 188c546..eef5a61 100644
--- a/src/com/android/contacts/dialpad/DialpadFragment.java
+++ b/src/com/android/contacts/dialpad/DialpadFragment.java
@@ -96,7 +96,9 @@
public void onSearchButtonPressed();
}
+ private View mDigitsContainer;
private EditText mDigits;
+
private View mDelete;
private ToneGenerator mToneGenerator;
private Object mToneGeneratorLock = new Object();
@@ -172,34 +174,42 @@
};
private final PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
- /**
- * Listen for phone state changes so that we can take down the
- * "dialpad chooser" if the phone becomes idle while the
- * chooser UI is visible.
- */
- @Override
- public void onCallStateChanged(int state, String incomingNumber) {
- // Log.i(TAG, "PhoneStateListener.onCallStateChanged: "
- // + state + ", '" + incomingNumber + "'");
- if ((state == TelephonyManager.CALL_STATE_IDLE) && dialpadChooserVisible()) {
- // Log.i(TAG, "Call ended with dialpad chooser visible! Taking it down...");
- // Note there's a race condition in the UI here: the
- // dialpad chooser could conceivably disappear (on its
- // own) at the exact moment the user was trying to select
- // one of the choices, which would be confusing. (But at
- // least that's better than leaving the dialpad chooser
- // onscreen, but useless...)
- showDialpadChooser(false);
- }
+ /**
+ * Listen for phone state changes so that we can take down the
+ * "dialpad chooser" if the phone becomes idle while the
+ * chooser UI is visible.
+ */
+ @Override
+ public void onCallStateChanged(int state, String incomingNumber) {
+ // Log.i(TAG, "PhoneStateListener.onCallStateChanged: "
+ // + state + ", '" + incomingNumber + "'");
+ if ((state == TelephonyManager.CALL_STATE_IDLE) && dialpadChooserVisible()) {
+ // Log.i(TAG, "Call ended with dialpad chooser visible! Taking it down...");
+ // Note there's a race condition in the UI here: the
+ // dialpad chooser could conceivably disappear (on its
+ // own) at the exact moment the user was trying to select
+ // one of the choices, which would be confusing. (But at
+ // least that's better than leaving the dialpad chooser
+ // onscreen, but useless...)
+ showDialpadChooser(false);
}
- };
+ }
+ };
+
+ private boolean mWasEmptyBeforeTextChange;
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
- // Do nothing
+ mWasEmptyBeforeTextChange = TextUtils.isEmpty(s);
}
public void onTextChanged(CharSequence input, int start, int before, int changeCount) {
- // Do nothing
+ if (mWasEmptyBeforeTextChange != TextUtils.isEmpty(input)) {
+ final Activity activity = getActivity();
+ if (activity != null) {
+ activity.invalidateOptionsMenu();
+ }
+ }
+
// DTMF Tones do not need to be played here any longer -
// the DTMF dialer handles that functionality now.
}
@@ -240,6 +250,7 @@
// Load up the resources for the text field.
Resources r = getResources();
+ mDigitsContainer = fragmentView.findViewById(R.id.digits_container);
mDigits = (EditText) fragmentView.findViewById(R.id.digits);
mDigits.setKeyListener(DialerKeyListener.getInstance());
mDigits.setOnClickListener(this);
@@ -920,7 +931,7 @@
if (enabled) {
// Log.i(TAG, "Showing dialpad chooser!");
- mDigits.setVisibility(View.GONE);
+ mDigitsContainer.setVisibility(View.GONE);
if (mDialpad != null) mDialpad.setVisibility(View.GONE);
mAdditionalButtonsRow.setVisibility(View.GONE);
mDialpadChooser.setVisibility(View.VISIBLE);
@@ -933,7 +944,7 @@
mDialpadChooser.setAdapter(mDialpadChooserAdapter);
} else {
// Log.i(TAG, "Displaying normal Dialer UI.");
- mDigits.setVisibility(View.VISIBLE);
+ mDigitsContainer.setVisibility(View.VISIBLE);
if (mDialpad != null) mDialpad.setVisibility(View.VISIBLE);
mAdditionalButtonsRow.setVisibility(View.VISIBLE);
mDialpadChooser.setVisibility(View.GONE);
diff --git a/src/com/android/contacts/group/GroupBrowseListAdapter.java b/src/com/android/contacts/group/GroupBrowseListAdapter.java
index c8c16e6..63a5d2c 100644
--- a/src/com/android/contacts/group/GroupBrowseListAdapter.java
+++ b/src/com/android/contacts/group/GroupBrowseListAdapter.java
@@ -23,155 +23,26 @@
import com.android.contacts.model.AccountTypeManager;
import com.android.internal.util.Objects;
-import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
-import android.os.AsyncTask;
-import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
-import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.Groups;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
-import android.widget.ImageView;
import android.widget.TextView;
-import java.util.ArrayList;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
/**
* Adapter to populate the list of groups.
*/
public class GroupBrowseListAdapter extends BaseAdapter {
- private static final int MAX_ICONS_PER_GROUP_ROW = 4;
-
- private static final String[] PROJECTION_GROUP_MEMBERSHIP_INFO = new String[] {
- GroupMembership._ID,
- GroupMembership.PHOTO_ID
- };
- private static final int GROUP_MEMBERSHIP_COLUMN_PHOTO_ID = 1;
-
- /**
- * Arguments for asynchronous photo ID loading. See {@link AsyncPhotoIdLoadTask}
- */
- private static class AsyncPhotoIdLoadArg {
- public final View icons;
- public final long groupId;
- public final Map<Long, ArrayList<Long>> groupPhotoIdMap;
- public final ContentResolver contentResolver;
- public final ContactPhotoManager contactPhotoManager;
-
- public AsyncPhotoIdLoadArg(
- View icons, long groupId, Map<Long, ArrayList<Long>> groupPhotoIdMap,
- ContentResolver contentResolver, ContactPhotoManager contactPhotoManager) {
- this.icons = icons;
- this.groupId = groupId;
- this.groupPhotoIdMap = groupPhotoIdMap;
- this.contentResolver = contentResolver;
- this.contactPhotoManager = contactPhotoManager;
- }
- }
-
- /**
- * Loads photo IDs associated with a group ID supplied from {@link AsyncPhotoIdLoadArg#groupId},
- * storing them in {@link GroupBrowseListAdapter#mGroupPhotoIdMap}.
- *
- * This AsyncTask also remembers a View which is associated with the group ID at the moment it
- * is initiated (we use {@link View#setTag(Object) and View#getTag() to associate them}. If the
- * View is still associated with the group ID after the asynchronous photo ID load, this class
- * also asks {@link ContactPhotoManager} to load actual photo contents. Its parent (typically
- * ListView) may reuse Views for different group IDs, so the photo content load often don't
- * occur.
- */
- private static class AsyncPhotoIdLoadTask extends
- AsyncTask<AsyncPhotoIdLoadArg, Void, ArrayList<Long>> {
-
- private View mIcons;
- private long mGroupId;
- private Map<Long, ArrayList<Long>> mGroupPhotoIdMap;
- private ContentResolver mContentResolver;
- private ContactPhotoManager mContactPhotoManager;
-
- @Override
- protected ArrayList<Long> doInBackground(AsyncPhotoIdLoadArg... params) {
- final AsyncPhotoIdLoadArg arg = params[0];
- mIcons = arg.icons;
- mGroupId = arg.groupId;
- mGroupPhotoIdMap = arg.groupPhotoIdMap;
- mContentResolver = arg.contentResolver;
- mContactPhotoManager = arg.contactPhotoManager;
-
- // Multiple requests for one group ID is possible. We just ignore duplicates,
- // assuming query results won't change.
- if (mGroupPhotoIdMap.containsKey(mGroupId)) {
- return null;
- }
-
- final ArrayList<Long> photoIds = new ArrayList<Long>(MAX_ICONS_PER_GROUP_ROW);
- Cursor cursor = null;
- try {
- cursor = mContentResolver.query(Data.CONTENT_URI,
- PROJECTION_GROUP_MEMBERSHIP_INFO,
- GroupMembership.MIMETYPE + "=? AND "
- + GroupMembership.PHOTO_ID + " IS NOT NULL AND "
- + GroupMembership.GROUP_ROW_ID + "=?",
- new String[] { GroupMembership.CONTENT_ITEM_TYPE,
- String.valueOf(mGroupId) }, null);
- if (cursor != null) {
- int count = 0;
- while (cursor.moveToNext() && count < MAX_ICONS_PER_GROUP_ROW) {
- photoIds.add(cursor.getLong(GROUP_MEMBERSHIP_COLUMN_PHOTO_ID));
- count++;
- }
- }
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- return photoIds;
- }
-
- @Override
- protected void onPostExecute(ArrayList<Long> photoIds) {
- if (photoIds == null) {
- return;
- }
-
- mGroupPhotoIdMap.put(mGroupId, photoIds);
-
- final View icons = mIcons;
- // If the original group ID, which was supplied when this AsyncTask was executed, is
- // consistent with the ID inside mArgs, it means the View isn't reused by the
- // other groups, and thus we can assume these Views are available for the group ID.
- final Long currentGroupId = (Long) icons.getTag();
- if (currentGroupId == mGroupId) {
- final ImageView[] children = getIconViewsSordedByFillOrder(icons);
- for (int i = 0; i < children.length; i++) {
- if (i < photoIds.size()) {
- mContactPhotoManager.loadPhoto(children[i], photoIds.get(i));
- } else {
- mContactPhotoManager.removePhoto(children[i]);
- }
- }
- }
- }
- }
-
private final Context mContext;
private final LayoutInflater mLayoutInflater;
private final AccountTypeManager mAccountTypeManager;
- private final Map<Long, ArrayList<Long>> mGroupPhotoIdMap =
- new ConcurrentHashMap<Long, ArrayList<Long>>();
-
- private final ContactPhotoManager mContactPhotoManager;
-
private Cursor mCursor;
private boolean mSelectionVisible;
@@ -181,7 +52,6 @@
mContext = context;
mLayoutInflater = LayoutInflater.from(context);
mAccountTypeManager = AccountTypeManager.getInstance(mContext);
- mContactPhotoManager = ContactPhotoManager.getInstance(mContext);
}
public void setCursor(Cursor cursor) {
@@ -308,35 +178,6 @@
viewCache.groupTitle.setText(entry.getTitle());
viewCache.groupMemberCount.setText(memberCountString);
- final View icons = result.findViewById(R.id.icons);
- final ImageView[] children = getIconViewsSordedByFillOrder(icons);
- final ArrayList<Long> photoIds = mGroupPhotoIdMap.get(entry.getGroupId());
-
- // Let the icon holder remember its associated group ID.
- // Each AsyncTask loading photo IDs will compare this ID with the AsyncTask's argument, and
- // check if the bound View is reused by the other list items or not. If the View is reused,
- // the group ID set here will be overridden by the new owner, thus ID inconsistency happens.
- icons.setTag(entry.getGroupId());
- if (photoIds != null) {
- // Cache is available. Let the photo manager load those IDs.
- for (int i = 0; i < children.length; i++) {
- if (i < photoIds.size()) {
- mContactPhotoManager.loadPhoto(children[i], photoIds.get(i));
- } else {
- mContactPhotoManager.removePhoto(children[i]);
- }
- }
- } else {
- // Cache is not available. Load photo IDs asynchronously.
- for (ImageView child : children) {
- mContactPhotoManager.removePhoto(child);
- }
- new AsyncPhotoIdLoadTask().execute(
- new AsyncPhotoIdLoadArg(icons, entry.getGroupId(),
- mGroupPhotoIdMap, mContext.getContentResolver(),
- mContactPhotoManager));
- }
-
if (mSelectionVisible) {
result.setActivated(isSelectedGroup(groupUri));
}
@@ -355,19 +196,6 @@
}
/**
- * Get ImageView objects inside the given View, sorted by the order photos should be filled.
- */
- private static ImageView[] getIconViewsSordedByFillOrder(View icons) {
- final ImageView[] children = new ImageView[] {
- (ImageView) icons.findViewById(R.id.icon_4),
- (ImageView) icons.findViewById(R.id.icon_2),
- (ImageView) icons.findViewById(R.id.icon_3),
- (ImageView) icons.findViewById(R.id.icon_1)
- };
- return children;
- }
-
- /**
* Cache of the children views of a contact detail entry represented by a
* {@link GroupListItem}
*/
diff --git a/src/com/android/contacts/group/GroupBrowseListFragment.java b/src/com/android/contacts/group/GroupBrowseListFragment.java
index aca638e..49835ef 100644
--- a/src/com/android/contacts/group/GroupBrowseListFragment.java
+++ b/src/com/android/contacts/group/GroupBrowseListFragment.java
@@ -16,6 +16,7 @@
package com.android.contacts.group;
+import com.android.contacts.ContactsUtils;
import com.android.contacts.GroupListLoader;
import com.android.contacts.R;
import com.android.contacts.group.GroupBrowseListAdapter.GroupListItemViewCache;
@@ -47,6 +48,7 @@
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ListView;
+import android.widget.TextView;
/**
* Fragment to display the list of groups.
@@ -81,7 +83,7 @@
private View mRootView;
private AutoScrollListView mListView;
- private View mEmptyView;
+ private TextView mEmptyView;
private View mAddAccountsView;
private View mAddAccountButton;
@@ -100,7 +102,7 @@
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
mRootView = inflater.inflate(R.layout.group_browse_list_fragment, null);
- mEmptyView = mRootView.findViewById(R.id.empty);
+ mEmptyView = (TextView)mRootView.findViewById(R.id.empty);
mAdapter = new GroupBrowseListAdapter(mContext);
mAdapter.setSelectionVisible(mSelectionVisible);
@@ -120,7 +122,7 @@
}
});
- mEmptyView = mRootView.findViewById(R.id.empty);
+ mListView.setEmptyView(mEmptyView);
mAddAccountsView = mRootView.findViewById(R.id.add_accounts);
mAddAccountButton = mRootView.findViewById(R.id.add_account_button);
mAddAccountButton.setOnClickListener(new OnClickListener() {
@@ -133,7 +135,7 @@
startActivity(intent);
}
});
- setAddAccountsVisibility(false);
+ setAddAccountsVisibility(!ContactsUtils.areAccountsAvailable(mContext));
if (savedInstanceState != null) {
String groupUriString = savedInstanceState.getString(EXTRA_KEY_GROUP_URI);
@@ -193,6 +195,7 @@
@Override
public CursorLoader onCreateLoader(int id, Bundle args) {
+ mEmptyView.setText(null);
return new GroupListLoader(mContext);
}
@@ -207,6 +210,8 @@
};
private void bindGroupList() {
+ mEmptyView.setText(R.string.noGroups);
+ setAddAccountsVisibility(!ContactsUtils.areAccountsAvailable(mContext));
if (mGroupListCursor == null) {
return;
}
@@ -219,7 +224,6 @@
// Restore the scroll position.
mListView.onRestoreInstanceState(listState);
}
- mListView.setEmptyView(mEmptyView);
mSelectedGroupUri = mAdapter.getSelectedGroup();
if (mSelectionVisible && mSelectedGroupUri != null) {
diff --git a/src/com/android/contacts/list/ContactTileAdapter.java b/src/com/android/contacts/list/ContactTileAdapter.java
index b7434b1..2f0f24b 100644
--- a/src/com/android/contacts/list/ContactTileAdapter.java
+++ b/src/com/android/contacts/list/ContactTileAdapter.java
@@ -122,7 +122,7 @@
// Converting padding in dips to padding in pixels
mPaddingInPixels = mContext.getResources()
- .getDimensionPixelOffset(R.dimen.contact_tile_divider_padding);
+ .getDimensionPixelSize(R.dimen.contact_tile_divider_padding);
bindColumnIndices();
}
diff --git a/src/com/android/contacts/list/ContactTileView.java b/src/com/android/contacts/list/ContactTileView.java
index bfc4a2e..7355dbf 100644
--- a/src/com/android/contacts/list/ContactTileView.java
+++ b/src/com/android/contacts/list/ContactTileView.java
@@ -16,13 +16,11 @@
package com.android.contacts.list;
import com.android.contacts.ContactPhotoManager;
-import com.android.contacts.ContactStatusUtil;
import com.android.contacts.R;
import com.android.contacts.list.ContactTileAdapter.ContactEntry;
import android.content.Context;
import android.net.Uri;
-import android.provider.ContactsContract.StatusUpdates;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
@@ -91,6 +89,7 @@
* fields in {@link ContactEntry}
*/
public void loadFromContact(ContactEntry entry) {
+
if (entry != null) {
mName.setText(entry.name);
mLookupUri = entry.lookupKey;
diff --git a/src/com/android/contacts/list/ContactsUnavailableFragment.java b/src/com/android/contacts/list/ContactsUnavailableFragment.java
index 3bab3fd..74a578f 100644
--- a/src/com/android/contacts/list/ContactsUnavailableFragment.java
+++ b/src/com/android/contacts/list/ContactsUnavailableFragment.java
@@ -39,6 +39,7 @@
private View mView;
private TextView mMessageView;
+ private TextView mSecondaryMessageView;
private Button mCreateContactButton;
private Button mAddAccountButton;
private Button mImportContactsButton;
@@ -46,6 +47,7 @@
private Button mRetryUpgradeButton;
private ProgressBar mProgress;
private int mNoContactsMsgResId = -1;
+ private int mNSecNoContactsMsgResId = -1;
private OnContactsUnavailableActionListener mListener;
@@ -54,6 +56,7 @@
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
mView = inflater.inflate(R.layout.contacts_unavailable_fragment, null);
mMessageView = (TextView) mView.findViewById(R.id.message);
+ mSecondaryMessageView = (TextView) mView.findViewById(R.id.secondary_message);
mCreateContactButton = (Button) mView.findViewById(R.id.create_contact_button);
mCreateContactButton.setOnClickListener(this);
mAddAccountButton = (Button) mView.findViewById(R.id.add_account_button);
@@ -82,14 +85,7 @@
int providerStatus = mProviderStatusLoader.getProviderStatus();
switch (providerStatus) {
case ProviderStatus.STATUS_NO_ACCOUNTS_NO_CONTACTS:
- if (mNoContactsMsgResId != -1) {
- mMessageView.setText(mNoContactsMsgResId);
- mMessageView.setGravity(Gravity.CENTER_HORIZONTAL);
- mMessageView.setVisibility(View.VISIBLE);
- } else {
- mMessageView.setGravity(Gravity.LEFT);
- mMessageView.setVisibility(View.GONE);
- }
+ setMessageText(mNoContactsMsgResId, mNSecNoContactsMsgResId);
mCreateContactButton.setVisibility(View.VISIBLE);
mAddAccountButton.setVisibility(View.VISIBLE);
mImportContactsButton.setVisibility(View.VISIBLE);
@@ -162,18 +158,31 @@
}
}
/**
- * Set the message to be shown if data is available for the selected tab
+ * Set the message to be shown if no data is available for the selected tab
*
- * @param resId - String resource ID of the message
+ * @param resId - String resource ID of the message , -1 means view will not be visible
*/
- public void setMessageText(int resId) {
+ public void setMessageText(int resId, int secResId) {
mNoContactsMsgResId = resId;
+ mNSecNoContactsMsgResId = secResId;
if (mMessageView != null &&
mProviderStatusLoader.getProviderStatus() ==
ProviderStatus.STATUS_NO_ACCOUNTS_NO_CONTACTS) {
- mMessageView.setText(mNoContactsMsgResId);
- mMessageView.setGravity(Gravity.CENTER_HORIZONTAL);
- mMessageView.setVisibility(View.VISIBLE);
+ if (resId != -1) {
+ mMessageView.setText(mNoContactsMsgResId);
+ mMessageView.setGravity(Gravity.CENTER_HORIZONTAL);
+ mMessageView.setVisibility(View.VISIBLE);
+ if (secResId != -1) {
+ mSecondaryMessageView.setText(mNSecNoContactsMsgResId);
+ mSecondaryMessageView.setGravity(Gravity.CENTER_HORIZONTAL);
+ mSecondaryMessageView.setVisibility(View.VISIBLE);
+ } else {
+ mSecondaryMessageView.setVisibility(View.INVISIBLE);
+ }
+ } else {
+ mSecondaryMessageView.setVisibility(View.GONE);
+ mMessageView.setVisibility(View.GONE);
+ }
}
}
}
diff --git a/src/com/android/contacts/socialwidget/SocialWidgetProvider.java b/src/com/android/contacts/socialwidget/SocialWidgetProvider.java
index 682f700..b29a9cd 100644
--- a/src/com/android/contacts/socialwidget/SocialWidgetProvider.java
+++ b/src/com/android/contacts/socialwidget/SocialWidgetProvider.java
@@ -36,6 +36,7 @@
import android.net.Uri;
import android.provider.ContactsContract.QuickContact;
import android.provider.ContactsContract.StreamItems;
+import android.text.Html;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.text.style.AbsoluteSizeSpan;
@@ -204,7 +205,7 @@
} else {
// TODO: Rotate between all the stream items?
StreamItemEntry streamItem = streamItems.get(0);
- CharSequence status = streamItem.getText();
+ CharSequence status = Html.fromHtml(streamItem.getText());
if (status.length() <= SHORT_SNIPPET_LENGTH) {
sb.append("\n");
} else {
diff --git a/tests/src/com/android/contacts/calllog/CallLogAdapterTest.java b/tests/src/com/android/contacts/calllog/CallLogAdapterTest.java
new file mode 100644
index 0000000..28db896
--- /dev/null
+++ b/tests/src/com/android/contacts/calllog/CallLogAdapterTest.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in 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.calllog;
+
+import com.google.common.collect.Lists;
+
+import android.content.Context;
+import android.database.MatrixCursor;
+import android.test.AndroidTestCase;
+import android.view.View;
+
+import java.util.List;
+
+/**
+ * Unit tests for {@link CallLogAdapter}.
+ */
+public class CallLogAdapterTest extends AndroidTestCase {
+ private static final String TEST_NUMBER = "12345678";
+ private static final String TEST_NAME = "name";
+ private static final String TEST_NUMBER_LABEL = "label";
+ private static final int TEST_NUMBER_TYPE = 1;
+ private static final String TEST_COUNTRY_ISO = "US";
+ private static final String TEST_VOICEMAIL_NUMBER = "111";
+
+ /** The object under test. */
+ private TestCallLogAdapter mAdapter;
+
+ private MatrixCursor mCursor;
+ private View mView;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ // Use a call fetcher that does not do anything.
+ CallLogAdapter.CallFetcher fakeCallFetcher = new CallLogAdapter.CallFetcher() {
+ @Override
+ public void startCallsQuery() {}
+ };
+
+ mAdapter = new TestCallLogAdapter(getContext(), fakeCallFetcher, TEST_COUNTRY_ISO,
+ TEST_VOICEMAIL_NUMBER);
+ // The cursor used in the tests to store the entries to display.
+ mCursor = new MatrixCursor(CallLogQuery.EXTENDED_PROJECTION);
+ mCursor.moveToFirst();
+ // The views into which to store the data.
+ mView = new View(getContext());
+ mView.setTag(CallLogListItemViews.createForTest(getContext()));
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ mAdapter = null;
+ mCursor = null;
+ mView = null;
+ super.tearDown();
+ }
+
+ public void testBindView_NoCallLogCacheNorMemoryCache_EnqueueRequest() {
+ mCursor.addRow(createCallLogEntry());
+
+ // Bind the views of a single row.
+ mAdapter.bindStandAloneView(mView, getContext(), mCursor);
+
+ // There is one request for contact details.
+ assertEquals(1, mAdapter.requests.size());
+
+ TestCallLogAdapter.Request request = mAdapter.requests.get(0);
+ // It is for the number we need to show.
+ assertEquals(TEST_NUMBER, request.number);
+ // Since there is nothing in the cache, it is an immediate request.
+ assertTrue("should be immediate", request.immediate);
+ }
+
+ public void testBindView_CallLogCacheButNoMemoryCache_EnqueueRequest() {
+ mCursor.addRow(createCallLogEntryWithCachedValues());
+
+ // Bind the views of a single row.
+ mAdapter.bindStandAloneView(mView, getContext(), mCursor);
+
+ // There is one request for contact details.
+ assertEquals(1, mAdapter.requests.size());
+
+ TestCallLogAdapter.Request request = mAdapter.requests.get(0);
+ // The values passed to the request, match the ones in the call log cache.
+ assertEquals(TEST_NAME, request.callLogInfo.name);
+ assertEquals(1, request.callLogInfo.type);
+ assertEquals(TEST_NUMBER_LABEL, request.callLogInfo.label);
+ }
+
+
+ public void testBindView_NoCallLogButMemoryCache_EnqueueRequest() {
+ mCursor.addRow(createCallLogEntry());
+ mAdapter.injectContactInfoForTest(TEST_NUMBER, createContactInfo());
+
+ // Bind the views of a single row.
+ mAdapter.bindStandAloneView(mView, getContext(), mCursor);
+
+ // There is one request for contact details.
+ assertEquals(1, mAdapter.requests.size());
+
+ TestCallLogAdapter.Request request = mAdapter.requests.get(0);
+ // Since there is something in the cache, it is not an immediate request.
+ assertFalse("should not be immediate", request.immediate);
+ }
+
+ public void testBindView_BothCallLogAndMemoryCache_NoEnqueueRequest() {
+ mCursor.addRow(createCallLogEntryWithCachedValues());
+ mAdapter.injectContactInfoForTest(TEST_NUMBER, createContactInfo());
+
+ // Bind the views of a single row.
+ mAdapter.bindStandAloneView(mView, getContext(), mCursor);
+
+ // Cache and call log are up-to-date: no need to request update.
+ assertEquals(0, mAdapter.requests.size());
+ }
+
+ public void testBindView_MismatchBetwenCallLogAndMemoryCache_EnqueueRequest() {
+ mCursor.addRow(createCallLogEntryWithCachedValues());
+
+ // Contact info contains a different name.
+ ContactInfo info = createContactInfo();
+ info.name = "new name";
+ mAdapter.injectContactInfoForTest(TEST_NUMBER, info);
+
+ // Bind the views of a single row.
+ mAdapter.bindStandAloneView(mView, getContext(), mCursor);
+
+ // There is one request for contact details.
+ assertEquals(1, mAdapter.requests.size());
+
+ TestCallLogAdapter.Request request = mAdapter.requests.get(0);
+ // Since there is something in the cache, it is not an immediate request.
+ assertFalse("should not be immediate", request.immediate);
+ }
+
+ /** Returns a contact info with default values. */
+ private ContactInfo createContactInfo() {
+ ContactInfo info = new ContactInfo();
+ info.number = TEST_NUMBER;
+ info.name = TEST_NAME;
+ info.type = TEST_NUMBER_TYPE;
+ info.label = TEST_NUMBER_LABEL;
+ return info;
+ }
+
+ /** Returns a call log entry without cached values. */
+ private Object[] createCallLogEntry() {
+ Object[] values = CallLogQueryTestUtils.createTestExtendedValues();
+ values[CallLogQuery.NUMBER] = TEST_NUMBER;
+ return values;
+ }
+
+ /** Returns a call log entry with a cached values. */
+ private Object[] createCallLogEntryWithCachedValues() {
+ Object[] values = createCallLogEntry();
+ values[CallLogQuery.CACHED_NAME] = TEST_NAME;
+ values[CallLogQuery.CACHED_NUMBER_TYPE] = TEST_NUMBER_TYPE;
+ values[CallLogQuery.CACHED_NUMBER_LABEL] = TEST_NUMBER_LABEL;
+ return values;
+ }
+
+ /**
+ * Subclass of {@link CallLogAdapter} used in tests to intercept certain calls.
+ */
+ // TODO: This would be better done by splitting the contact lookup into a collaborator class
+ // instead.
+ private static final class TestCallLogAdapter extends CallLogAdapter {
+ public static class Request {
+ public final String number;
+ public final ContactInfo callLogInfo;
+ public final boolean immediate;
+
+ public Request(String number, ContactInfo callLogInfo, boolean immediate) {
+ this.number = number;
+ this.callLogInfo = callLogInfo;
+ this.immediate = immediate;
+ }
+ }
+
+ public final List<Request> requests = Lists.newArrayList();
+
+ public TestCallLogAdapter(Context context, CallFetcher callFetcher,
+ String currentCountryIso, String voicemailNumber) {
+ super(context, callFetcher, currentCountryIso, voicemailNumber);
+ }
+
+ @Override
+ void enqueueRequest(String number, ContactInfo callLogInfo, boolean immediate) {
+ requests.add(new Request(number, callLogInfo, immediate));
+ }
+ }
+}
diff --git a/tests/src/com/android/contacts/calllog/CallLogQueryTestUtils.java b/tests/src/com/android/contacts/calllog/CallLogQueryTestUtils.java
index 0e1952a..c273612 100644
--- a/tests/src/com/android/contacts/calllog/CallLogQueryTestUtils.java
+++ b/tests/src/com/android/contacts/calllog/CallLogQueryTestUtils.java
@@ -17,6 +17,9 @@
package com.android.contacts.calllog;
import static junit.framework.Assert.assertEquals;
+
+import android.provider.CallLog.Calls;
+
import junit.framework.Assert;
/**
@@ -24,13 +27,18 @@
*/
public class CallLogQueryTestUtils {
public static Object[] createTestValues() {
- Object[] values = new Object[]{ -1L, "", 0L, 0L, 0, "", "", "", null, 0, null };
+ Object[] values = new Object[]{
+ -1L, "", 0L, 0L, Calls.INCOMING_TYPE, "", "", "", null, 0, null,
+ };
assertEquals(CallLogQuery._PROJECTION.length, values.length);
return values;
}
public static Object[] createTestExtendedValues() {
- Object[] values = new Object[]{ -1L, "", 0L, 0L, 0, "", "", "", null, 0, null, 0 };
+ Object[] values = new Object[]{
+ -1L, "", 0L, 0L, Calls.INCOMING_TYPE, "", "", "", null, 0, null,
+ CallLogQuery.SECTION_OLD_ITEM
+ };
Assert.assertEquals(CallLogQuery.EXTENDED_PROJECTION.length, values.length);
return values;
}
diff --git a/tests/src/com/android/contacts/detail/ContactDetailFragmentTests.java b/tests/src/com/android/contacts/detail/ContactDetailFragmentTests.java
index cfab94a..02faa24 100644
--- a/tests/src/com/android/contacts/detail/ContactDetailFragmentTests.java
+++ b/tests/src/com/android/contacts/detail/ContactDetailFragmentTests.java
@@ -43,7 +43,7 @@
values.put(Im.DATA, TEST_ADDRESS);
DetailViewEntry entry = new ContactDetailFragment.DetailViewEntry();
- ContactDetailFragment.buildImActions(entry, values);
+ ContactDetailFragment.buildImActions(mContext, entry, values);
assertEquals(Intent.ACTION_SENDTO, entry.intent.getAction());
assertEquals("xmpp:" + TEST_ADDRESS + "?message", entry.intent.getData().toString());
@@ -60,7 +60,7 @@
values.put(Im.CHAT_CAPABILITY, Im.CAPABILITY_HAS_VOICE | Im.CAPABILITY_HAS_VIDEO);
DetailViewEntry entry = new ContactDetailFragment.DetailViewEntry();
- ContactDetailFragment.buildImActions(entry, values);
+ ContactDetailFragment.buildImActions(mContext, entry, values);
assertEquals(Intent.ACTION_SENDTO, entry.intent.getAction());
assertEquals("xmpp:" + TEST_ADDRESS + "?message", entry.intent.getData().toString());
@@ -79,7 +79,7 @@
Im.CAPABILITY_HAS_VOICE);
DetailViewEntry entry = new ContactDetailFragment.DetailViewEntry();
- ContactDetailFragment.buildImActions(entry, values);
+ ContactDetailFragment.buildImActions(mContext, entry, values);
assertEquals(Intent.ACTION_SENDTO, entry.intent.getAction());
assertEquals("xmpp:" + TEST_ADDRESS + "?message", entry.intent.getData().toString());
@@ -98,7 +98,7 @@
values.put(Im.DATA, TEST_ADDRESS);
DetailViewEntry entry = new ContactDetailFragment.DetailViewEntry();
- ContactDetailFragment.buildImActions(entry, values);
+ ContactDetailFragment.buildImActions(mContext, entry, values);
assertEquals(Intent.ACTION_SENDTO, entry.intent.getAction());
final Uri data = entry.intent.getData();
@@ -121,7 +121,7 @@
Im.CAPABILITY_HAS_VOICE);
DetailViewEntry entry = new ContactDetailFragment.DetailViewEntry();
- ContactDetailFragment.buildImActions(entry, values);
+ ContactDetailFragment.buildImActions(mContext, entry, values);
assertEquals(Intent.ACTION_SENDTO, entry.intent.getAction());
assertEquals("xmpp:" + TEST_ADDRESS + "?message", entry.intent.getData().toString());
diff --git a/tests/src/com/android/contacts/detail/StreamItemAdapterTest.java b/tests/src/com/android/contacts/detail/StreamItemAdapterTest.java
index d862d6e..1fee9b6 100644
--- a/tests/src/com/android/contacts/detail/StreamItemAdapterTest.java
+++ b/tests/src/com/android/contacts/detail/StreamItemAdapterTest.java
@@ -28,19 +28,22 @@
// TODO: We should have tests for action, but that requires a mock sync-adapter that specifies
// an action or doesn't
+// TODO Add test for photo click
+
/**
* Unit tests for {@link StreamItemAdapter}.
*/
public class StreamItemAdapterTest extends AndroidTestCase {
private StreamItemAdapter mAdapter;
private FakeOnClickListener mListener;
+ private FakeOnClickListener mPhotoListener;
private View mView;
@Override
protected void setUp() throws Exception {
super.setUp();
mListener = new FakeOnClickListener();
- mAdapter = new StreamItemAdapter(getContext(), mListener);
+ mAdapter = new StreamItemAdapter(getContext(), mListener, mPhotoListener);
}
@Override