Merge "HTML decode social updates"
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index d18515e..95edf0f 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -599,17 +599,23 @@
android:configChanges="orientation|screenSize|keyboardHidden"
android:theme="@style/BackgroundOnly">
<intent-filter>
- <action android:name="android.nfc.action.NDEF_DISCOVERED" />
- <data android:mimeType="text/x-vcard" />
- <data android:mimeType="text/x-vCard" />
- <data android:mimeType="text/vcard" />
- <category android:name="android.intent.category.DEFAULT" />
- </intent-filter>
- <intent-filter>
<action android:name="android.intent.action.VIEW" />
<data android:mimeType="text/directory" />
<data android:mimeType="text/vcard" />
<data android:mimeType="text/x-vcard" />
+ <data android:mimeType="text/x-vCard" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name=".vcard.NfcImportVCardActivity"
+ android:configChanges="orientation|screenSize|keyboardHidden"
+ android:theme="@style/BackgroundOnly">
+ <intent-filter>
+ <action android:name="android.nfc.action.NDEF_DISCOVERED" />
+ <data android:mimeType="text/vcard" />
+ <data android:mimeType="text/x-vcard" />
+ <data android:mimeType="text/x-vCard" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
diff --git a/res/drawable-hdpi/ic_share_holo_dark.png b/res/drawable-hdpi/ic_share_holo_dark.png
deleted file mode 100644
index 686da4c..0000000
--- a/res/drawable-hdpi/ic_share_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/list_activated_holo.9.png b/res/drawable-hdpi/list_activated_holo.9.png
index 7b89319..046b24a 100644
--- a/res/drawable-hdpi/list_activated_holo.9.png
+++ b/res/drawable-hdpi/list_activated_holo.9.png
Binary files differ
diff --git a/res/drawable-hdpi/list_background_holo.9.png b/res/drawable-hdpi/list_background_holo.9.png
index e382b2d..cddf9be 100644
--- a/res/drawable-hdpi/list_background_holo.9.png
+++ b/res/drawable-hdpi/list_background_holo.9.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_share_holo_dark.png b/res/drawable-mdpi/ic_share_holo_dark.png
deleted file mode 100644
index 5c33eac..0000000
--- a/res/drawable-mdpi/ic_share_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/list_activated_holo.9.png b/res/drawable-mdpi/list_activated_holo.9.png
index 4d98919..1ff3373 100644
--- a/res/drawable-mdpi/list_activated_holo.9.png
+++ b/res/drawable-mdpi/list_activated_holo.9.png
Binary files differ
diff --git a/res/drawable-mdpi/list_background_holo.9.png b/res/drawable-mdpi/list_background_holo.9.png
index ca2f750..7d5d66d 100644
--- a/res/drawable-mdpi/list_background_holo.9.png
+++ b/res/drawable-mdpi/list_background_holo.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_share_holo_dark.png b/res/drawable-xhdpi/ic_share_holo_dark.png
deleted file mode 100644
index b3e2f80..0000000
--- a/res/drawable-xhdpi/ic_share_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/list_activated_holo.9.png b/res/drawable-xhdpi/list_activated_holo.9.png
index 4e13add..2eb7c7e 100644
--- a/res/drawable-xhdpi/list_activated_holo.9.png
+++ b/res/drawable-xhdpi/list_activated_holo.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/list_background_holo.9.png b/res/drawable-xhdpi/list_background_holo.9.png
index adba6a8..b652725 100644
--- a/res/drawable-xhdpi/list_background_holo.9.png
+++ b/res/drawable-xhdpi/list_background_holo.9.png
Binary files differ
diff --git a/res/drawable/list_item_activated_background.xml b/res/drawable/list_item_activated_background.xml
index 334e8ec..6ea21d3 100644
--- a/res/drawable/list_item_activated_background.xml
+++ b/res/drawable/list_item_activated_background.xml
@@ -17,5 +17,5 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android"
android:exitFadeDuration="@android:integer/config_mediumAnimTime">
<item android:state_activated="true" android:drawable="@drawable/list_activated_holo" />
- <item android:drawable="@android:color/transparent" />
-</selector>
+ <item android:drawable="@drawable/list_background_holo" />
+</selector>
\ No newline at end of file
diff --git a/res/layout-sw580dp/aggregation_suggestions_item.xml b/res/layout-sw580dp/aggregation_suggestions_item.xml
deleted file mode 100644
index 5ea8347..0000000
--- a/res/layout-sw580dp/aggregation_suggestions_item.xml
+++ /dev/null
@@ -1,61 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
- * Copyright 2010, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
--->
-
-<view xmlns:android="http://schemas.android.com/apk/res/android"
- class="com.android.contacts.editor.AggregationSuggestionView"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:paddingLeft="5dip"
- android:paddingRight="15dip"
- android:background="?android:attr/selectableItemBackground"
- android:focusable="true"
->
- <ImageView
- android:id="@+id/aggregation_suggestion_photo"
- android:layout_width="@dimen/aggregation_suggestion_icon_size"
- android:layout_height="@dimen/aggregation_suggestion_icon_size"
- android:layout_alignParentLeft="true"
- android:layout_centerInParent="true"
- android:layout_marginTop="4dip"
- android:scaleType="fitCenter"
- />
-
- <TextView
- android:id="@+id/aggregation_suggestion_name"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_toRightOf="@id/aggregation_suggestion_photo"
- android:layout_marginLeft="10dip"
- android:layout_marginTop="4dip"
- android:textAppearance="?android:attr/textAppearanceLarge"
- android:textColor="?android:attr/textColorSecondary"
- />
-
- <TextView
- android:id="@+id/aggregation_suggestion_data"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_toRightOf="@id/aggregation_suggestion_photo"
- android:layout_below="@id/aggregation_suggestion_name"
- android:layout_marginLeft="10dip"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:textColor="?android:attr/textColorSecondary"
- />
-</view>
diff --git a/res/layout-w470dp/contact_detail_fragment.xml b/res/layout-w470dp/contact_detail_fragment.xml
index 56c9f20..d63236d 100644
--- a/res/layout-w470dp/contact_detail_fragment.xml
+++ b/res/layout-w470dp/contact_detail_fragment.xml
@@ -77,7 +77,6 @@
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:background="@android:color/black"
- android:alpha=".50"
android:visibility="gone"/>
<View
diff --git a/res/layout-w470dp/contact_detail_updates_fragment.xml b/res/layout-w470dp/contact_detail_updates_fragment.xml
index 92ddd8c..dd7cfbd 100644
--- a/res/layout-w470dp/contact_detail_updates_fragment.xml
+++ b/res/layout-w470dp/contact_detail_updates_fragment.xml
@@ -14,21 +14,45 @@
limitations under the License.
-->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:ex="http://schemas.android.com/apk/res/com.android.contacts"
android:id="@+id/contact_detail_updates_fragment"
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical">
+ android:layout_height="match_parent">
- <include
- android:id="@+id/title"
- layout="@layout/contact_detail_kind_title_entry_view" />
-
- <ListView android:id="@android:id/list"
+ <LinearLayout
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:background="@color/background_social_updates"
- android:divider="@null"/>
+ android:layout_height="match_parent"
+ android:orientation="vertical">
-</LinearLayout>
+ <include
+ android:id="@+id/title"
+ layout="@layout/contact_detail_kind_title_entry_view" />
+
+ <ListView android:id="@android:id/list"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="@color/background_social_updates"
+ android:divider="@null"/>
+
+ </LinearLayout>
+
+ <View
+ android:id="@+id/alpha_overlay"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentTop="true"
+ android:background="@android:color/black"
+ android:visibility="gone"/>
+
+ <View
+ android:id="@+id/touch_intercept_overlay"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentTop="true"
+ android:background="?android:attr/selectableItemBackground"
+ android:visibility="gone"/>
+
+</FrameLayout>
diff --git a/res/layout/account_selector_list_item.xml b/res/layout/account_selector_list_item.xml
index 82b73da..a700866 100644
--- a/res/layout/account_selector_list_item.xml
+++ b/res/layout/account_selector_list_item.xml
@@ -22,7 +22,7 @@
android:layout_width="@dimen/detail_network_icon_size"
android:layout_height="@dimen/detail_network_icon_size"
android:layout_margin="8dip"
- android:layout_gravity="center_vertical"/>
+ android:layout_gravity="center_vertical" />
<LinearLayout
android:layout_width="0dip"
diff --git a/res/layout/aggregation_suggestions_item.xml b/res/layout/aggregation_suggestions_item.xml
index 9ed1bf3..188a26e 100644
--- a/res/layout/aggregation_suggestions_item.xml
+++ b/res/layout/aggregation_suggestions_item.xml
@@ -21,42 +21,34 @@
class="com.android.contacts.editor.AggregationSuggestionView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:paddingLeft="5dip"
- android:paddingRight="15dip"
+ android:minHeight="48dip"
+ android:paddingLeft="8dip"
android:background="?android:attr/selectableItemBackground"
- android:focusable="true"
->
+ android:orientation="horizontal">
+
+ <LinearLayout
+ android:layout_width="0px"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:orientation="vertical"
+ android:layout_gravity="center_vertical">
+ <TextView
+ android:id="@+id/aggregation_suggestion_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+ <TextView
+ android:id="@+id/aggregation_suggestion_data"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textColor="?android:attr/textColorSecondary" />
+ </LinearLayout>
<ImageView
android:id="@+id/aggregation_suggestion_photo"
- android:layout_width="@dimen/aggregation_suggestion_icon_size"
- android:layout_height="@dimen/aggregation_suggestion_icon_size"
- android:layout_alignParentLeft="true"
- android:layout_centerInParent="true"
- android:layout_marginTop="4dip"
+ android:layout_width="48dip"
+ android:layout_height="48dip"
android:scaleType="fitCenter"
- />
-
- <TextView
- android:id="@+id/aggregation_suggestion_name"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_toRightOf="@id/aggregation_suggestion_photo"
- android:layout_marginLeft="10dip"
- android:layout_marginTop="4dip"
- android:textAppearance="?android:attr/textAppearanceSmall"
- android:textColor="?android:attr/textColorSecondary"
- />
-
- <TextView
- android:id="@+id/aggregation_suggestion_data"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_toRightOf="@id/aggregation_suggestion_photo"
- android:layout_below="@id/aggregation_suggestion_name"
- android:layout_marginLeft="10dip"
- android:textAppearance="?android:attr/textAppearanceSmall"
- android:textColor="?android:attr/textColorSecondary"
- />
+ android:layout_gravity="center_vertical" />
</view>
diff --git a/res/layout/call_detail.xml b/res/layout/call_detail.xml
index ece2f64..987a787 100644
--- a/res/layout/call_detail.xml
+++ b/res/layout/call_detail.xml
@@ -89,11 +89,12 @@
android:layout_width="wrap_content"
android:layout_height="0dip"
android:layout_alignLeft="@id/photo_text_bar"
- android:layout_alignTop="@id/photo_text_bar"
android:layout_toLeftOf="@id/main_action"
+ android:layout_alignTop="@id/photo_text_bar"
android:layout_alignBottom="@id/photo_text_bar"
android:layout_marginRight="@dimen/call_log_inner_margin"
android:layout_marginLeft="@dimen/call_detail_contact_name_margin"
+ android:gravity="center_vertical"
android:textColor="?attr/call_log_primary_text_color"
android:textSize="18sp"
android:singleLine="true"
@@ -113,14 +114,16 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/voicemail_container"
+ android:layout_marginTop="@dimen/call_log_icon_margin"
android:background="?attr/call_log_primary_background_color"
/>
<ListView
android:id="@+id/history"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/call_log_icon_margin"
android:layout_alignParentLeft="true"
android:layout_below="@android:id/list"
- android:background="?attr/call_log_secondary_background_color"
+ android:background="@android:color/black"
/>
</RelativeLayout>
diff --git a/res/layout/call_detail_history_item.xml b/res/layout/call_detail_history_item.xml
index b225369..674b034 100644
--- a/res/layout/call_detail_history_item.xml
+++ b/res/layout/call_detail_history_item.xml
@@ -14,45 +14,46 @@
limitations under the License.
-->
-<RelativeLayout
+<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:minHeight="?android:attr/listPreferredItemHeight"
- android:background="?attr/call_log_secondary_background_color"
- android:padding="@dimen/call_log_indent_margin"
+ android:minHeight="@dimen/call_log_list_item_height"
+ android:paddingTop="@dimen/call_log_icon_margin"
+ android:paddingLeft="@dimen/call_log_indent_margin"
+ android:paddingRight="@dimen/call_log_outer_margin"
+ android:orientation="vertical"
>
- <view
- class="com.android.contacts.calllog.CallTypeIconsView"
- android:id="@+id/call_type_icon"
+ <LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_alignParentTop="true"
- android:layout_alignParentLeft="true"
- android:layout_marginRight="@dimen/call_log_icon_margin"
- />
- <TextView
- android:id="@+id/call_type_text"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentTop="true"
- android:layout_toRightOf="@id/call_type_icon"
- android:textColor="?attr/call_log_secondary_text_color"
- />
+ android:orientation="horizontal"
+ >
+ <view
+ class="com.android.contacts.calllog.CallTypeIconsView"
+ android:id="@+id/call_type_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ />
+ <TextView
+ android:id="@+id/call_type_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="@dimen/call_log_icon_margin"
+ android:textColor="@color/secondary_text_color"
+ />
+ </LinearLayout>
<TextView
android:id="@+id/date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_below="@id/call_type_text"
- android:layout_alignLeft="@id/call_type_text"
- android:textColor="?attr/call_log_secondary_text_color"
+ android:textColor="@color/secondary_text_color"
/>
<TextView
android:id="@+id/duration"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_below="@id/date"
- android:layout_alignLeft="@id/date"
- android:textColor="?attr/call_log_secondary_text_color"
+ android:textColor="@color/secondary_text_color"
/>
-</RelativeLayout>
+</LinearLayout>
diff --git a/res/layout/call_detail_list_item.xml b/res/layout/call_detail_list_item.xml
index 8b90e25..e2bf83c 100644
--- a/res/layout/call_detail_list_item.xml
+++ b/res/layout/call_detail_list_item.xml
@@ -16,22 +16,22 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
+ android:layout_height="@dimen/call_log_list_item_height"
android:orientation="horizontal"
- android:paddingLeft="9dip"
- android:paddingRight="5dip"
+ android:layout_marginTop="@dimen/call_log_icon_margin"
android:gravity="center_vertical"
+ android:background="@drawable/dialpad_background"
>
- <LinearLayout
+ <LinearLayout android:id="@+id/main_action"
android:layout_width="0dip"
- android:layout_height="wrap_content"
+ android:layout_height="match_parent"
android:layout_weight="1"
- android:layout_marginRight="5dip"
- android:paddingTop="5dip"
- android:paddingBottom="7dip"
+ android:paddingLeft="@dimen/call_log_indent_margin"
android:orientation="vertical"
android:gravity="center_vertical"
+ android:focusable="true"
+ android:background="@drawable/btn_dial"
>
<TextView android:id="@android:id/text1"
@@ -53,7 +53,7 @@
android:textAppearance="?android:attr/textAppearanceSmall"
android:textStyle="bold"
/>
-
+
<TextView android:id="@+id/number"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -63,12 +63,22 @@
</LinearLayout>
- <ImageView android:id="@+id/icon"
- android:layout_width="32dip"
+ <View android:id="@+id/divider"
+ android:layout_width="1px"
android:layout_height="32dip"
- android:layout_marginLeft="5dip"
+ android:background="@drawable/ic_divider_dashed_holo_dark"
+ android:layout_gravity="center_vertical"
+ />
+
+ <ImageView android:id="@+id/icon"
+ android:layout_width="@color/call_log_voicemail_highlight_color"
+ android:layout_height="match_parent"
+ android:paddingLeft="@dimen/call_log_inner_margin"
+ android:paddingRight="@dimen/call_log_outer_margin"
android:gravity="center"
android:scaleType="centerInside"
+ android:focusable="true"
+ android:background="@drawable/btn_dial"
/>
</LinearLayout>
diff --git a/res/layout/call_log_list_item.xml b/res/layout/call_log_list_item.xml
index 5447f65..8bd6a19 100644
--- a/res/layout/call_log_list_item.xml
+++ b/res/layout/call_log_list_item.xml
@@ -19,7 +19,6 @@
class="com.android.contacts.calllog.CallLogListItemView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:padding="@dimen/call_log_inner_margin"
>
<!--
This layout may represent either a call log item or one of the
@@ -45,22 +44,25 @@
android:layout_height="@dimen/call_log_list_contact_photo_size"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
- android:layout_marginLeft="@dimen/call_log_inner_margin"
+ android:layout_marginLeft="@dimen/call_log_outer_margin"
+ android:layout_marginTop="@dimen/call_log_inner_margin"
+ android:layout_marginBottom="@dimen/call_log_inner_margin"
/>
<LinearLayout
android:id="@+id/divider"
android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ android:layout_height="match_parent"
android:layout_alignParentRight="true"
- android:layout_centerInParent="true"
+ android:layout_centerVertical="true"
+ android:layout_alignTop="@+id/primary_action_view"
+ android:layout_alignBottom="@+id/primary_action_view"
android:orientation="horizontal"
>
<ImageView
android:id="@+id/unheard_icon"
android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginLeft="@dimen/call_log_inner_margin"
- android:layout_gravity="center_vertical"
+ android:layout_height="match_parent"
+ android:paddingRight="@dimen/call_log_inner_margin"
android:scaleType="center"
android:src="@drawable/ic_unheard_voicemail_holo_dark"
android:visibility="gone"
@@ -69,27 +71,32 @@
<View
android:layout_width="1px"
android:layout_height="@dimen/call_log_call_action_size"
- android:layout_gravity="center_vertical"
- android:layout_marginLeft="@dimen/call_log_inner_margin"
android:background="@drawable/ic_divider_dashed_holo_dark"
+ android:layout_gravity="center_vertical"
/>
- <ImageView
+ <ImageButton
android:id="@+id/secondary_action_icon"
android:layout_width="@dimen/call_log_call_action_width"
- android:layout_height="@dimen/call_log_call_action_height"
- android:layout_gravity="center_vertical"
+ android:layout_height="match_parent"
+ android:paddingLeft="@dimen/call_log_inner_margin"
+ android:paddingTop="@dimen/call_log_inner_margin"
+ android:paddingBottom="@dimen/call_log_inner_margin"
+ android:paddingRight="@dimen/call_log_outer_margin"
android:scaleType="center"
android:background="@drawable/list_selector"
/>
</LinearLayout>
<LinearLayout
+ android:id="@+id/primary_action_view"
android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ android:layout_height="match_parent"
android:layout_toRightOf="@id/quick_contact_photo"
android:layout_toLeftOf="@id/divider"
android:layout_centerVertical="true"
- android:layout_marginLeft="@dimen/call_log_inner_margin"
+ android:padding="@dimen/call_log_inner_margin"
android:orientation="vertical"
+ android:background="@drawable/list_selector"
+ android:focusable="true"
>
<TextView
android:id="@+id/name"
@@ -139,8 +146,12 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/call_log_inner_margin"
+ android:layout_marginRight="@dimen/call_log_inner_margin"
android:textSize="14sp"
android:textStyle="bold"
android:textColor="?attr/call_log_header_color"
+ android:padding="@dimen/call_log_inner_margin"
+ android:focusable="true"
+ android:drawableBottom="@android:color/holo_blue_light"
/>
</view>
diff --git a/res/layout/edit_field_list.xml b/res/layout/edit_field_list.xml
index ba715c7..d46828b 100644
--- a/res/layout/edit_field_list.xml
+++ b/res/layout/edit_field_list.xml
@@ -23,4 +23,4 @@
android:layout_weight="1"
android:layout_height="wrap_content"
android:paddingLeft="@dimen/editor_field_left_padding"
- android:orientation="vertical" />
\ No newline at end of file
+ android:orientation="vertical" />
diff --git a/res/layout/edit_field_list_with_anchor_view.xml b/res/layout/edit_field_list_with_anchor_view.xml
new file mode 100644
index 0000000..fa69a5c
--- /dev/null
+++ b/res/layout/edit_field_list_with_anchor_view.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!-- Layout that behaves similarly to edit_field_list.xml,
+ but also has an anchor view for ListPopupWindow -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:paddingLeft="@dimen/editor_field_left_padding"
+ android:orientation="vertical">
+ <LinearLayout
+ android:id="@+id/editors"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical" />
+ <View
+ android:id="@+id/anchor_view"
+ android:layout_width="match_parent"
+ android:layout_height="0px" />
+</LinearLayout>
diff --git a/res/layout/group_browse_list_account_header.xml b/res/layout/group_browse_list_account_header.xml
index 7c07497..f739ea2 100644
--- a/res/layout/group_browse_list_account_header.xml
+++ b/res/layout/group_browse_list_account_header.xml
@@ -18,7 +18,10 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:padding="@dimen/group_list_header_padding"
+ android:background="@drawable/list_background_holo"
+ android:paddingLeft="@dimen/group_list_header_padding"
+ android:paddingRight="@dimen/group_list_header_padding"
+ android:paddingTop="@dimen/group_list_header_padding"
android:orientation="vertical">
<LinearLayout
diff --git a/res/layout/group_browse_list_item.xml b/res/layout/group_browse_list_item.xml
index 6f5fcef..326b413 100644
--- a/res/layout/group_browse_list_item.xml
+++ b/res/layout/group_browse_list_item.xml
@@ -19,16 +19,12 @@
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:minHeight="@dimen/detail_min_line_item_height"
- android:paddingRight="20dip"
- android:paddingBottom="10dip"
- style="@style/GroupBrowseListItem">
+ android:minHeight="@dimen/detail_min_line_item_height">
<ImageView
android:id="@+id/divider"
android:layout_width="match_parent"
android:layout_height="1dip"
- android:layout_marginBottom="10dip"
android:paddingLeft="10dip"
android:paddingRight="10dip"
android:scaleType="fitXY"
@@ -37,11 +33,17 @@
<include
android:id="@+id/group_list_header"
layout="@layout/group_browse_list_account_header"
+ android:paddingRight="20dip"
+ android:paddingBottom="10dip"
android:visibility="gone" />
<RelativeLayout
android:layout_width="match_parent"
- android:layout_height="wrap_content">
+ android:layout_height="wrap_content"
+ android:paddingTop="10dip"
+ android:paddingRight="20dip"
+ android:paddingBottom="10dip"
+ style="@style/GroupBrowseListItem">
<LinearLayout
android:layout_width="match_parent"
diff --git a/res/layout/raw_contact_editor_view.xml b/res/layout/raw_contact_editor_view.xml
index b98f4fb..af95e04 100644
--- a/res/layout/raw_contact_editor_view.xml
+++ b/res/layout/raw_contact_editor_view.xml
@@ -66,13 +66,6 @@
</LinearLayout>
- <ViewStub android:id="@+id/aggregation_suggestion_stub"
- android:inflatedId="@+id/aggregation_suggestion"
- android:layout="@layout/aggregation_suggestions"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:visibility="visible"/>
-
<LinearLayout
android:id="@+id/sect_fields"
android:layout_width="match_parent"
diff --git a/res/layout/structured_name_editor_view.xml b/res/layout/structured_name_editor_view.xml
index 196b079..3c5d5c9 100644
--- a/res/layout/structured_name_editor_view.xml
+++ b/res/layout/structured_name_editor_view.xml
@@ -34,8 +34,7 @@
android:clickable="true">
<include
- android:id="@+id/editors"
- layout="@layout/edit_field_list" />
+ layout="@layout/edit_field_list_with_anchor_view" />
<include
android:id="@+id/expansion_view_container"
diff --git a/res/layout/text_fields_editor_view.xml b/res/layout/text_fields_editor_view.xml
index e63b7da..e187a9e 100644
--- a/res/layout/text_fields_editor_view.xml
+++ b/res/layout/text_fields_editor_view.xml
@@ -31,8 +31,7 @@
android:clickable="true">
<include
- android:id="@+id/editors"
- layout="@layout/edit_field_list" />
+ layout="@layout/edit_field_list_with_anchor_view" />
<include
android:id="@+id/expansion_view_container"
diff --git a/res/menu/call_details_options.xml b/res/menu/call_details_options.xml
index 2ce9826..ed0111b 100644
--- a/res/menu/call_details_options.xml
+++ b/res/menu/call_details_options.xml
@@ -22,13 +22,6 @@
android:onClick="onMenuTrashVoicemail"
/>
<item
- android:id="@+id/menu_share_voicemail"
- android:icon="@drawable/ic_share_holo_dark"
- android:showAsAction="ifRoom"
- android:title="@string/recentCalls_shareVoicemail"
- android:onClick="onMenuShareVoicemail"
- />
- <item
android:id="@+id/menu_remove_from_call_log"
android:icon="@android:drawable/ic_menu_close_clear_cancel"
android:title="@string/recentCalls_removeFromRecentList"
diff --git a/res/values-sw580dp-w720dp/styles.xml b/res/values-sw580dp-w720dp/styles.xml
index 05ac532..ff50d45 100644
--- a/res/values-sw580dp-w720dp/styles.xml
+++ b/res/values-sw580dp-w720dp/styles.xml
@@ -21,7 +21,6 @@
<item name="android:textColorSecondary">@color/secondary_text_color</item>
<item name="list_item_height">66dip</item>
<item name="activated_background">@drawable/list_item_activated_background</item>
- <item name="android:windowContentOverlay">@null</item>
<item name="section_header_background">@drawable/list_title_holo</item>
<item name="list_item_divider">?android:attr/listDivider</item>
<item name="list_item_padding_top">0dip</item>
diff --git a/res/values-sw580dp/dimens.xml b/res/values-sw580dp/dimens.xml
index f7c0f05..91842d6 100644
--- a/res/values-sw580dp/dimens.xml
+++ b/res/values-sw580dp/dimens.xml
@@ -14,7 +14,6 @@
limitations under the License.
-->
<resources>
- <dimen name="aggregation_suggestion_icon_size">64dip</dimen>
<dimen name="editor_padding_top">32dip</dimen>
<dimen name="editor_type_label_width">122dip</dimen>
<dimen name="editor_field_spinner_text_size">15sp</dimen>
diff --git a/res/values-sw580dp/styles.xml b/res/values-sw580dp/styles.xml
index 871d566..a386e57 100644
--- a/res/values-sw580dp/styles.xml
+++ b/res/values-sw580dp/styles.xml
@@ -21,7 +21,6 @@
<item name="android:textColorSecondary">@color/secondary_text_color</item>
<item name="list_item_height">66dip</item>
<item name="activated_background">@drawable/list_item_activated_background</item>
- <item name="android:windowContentOverlay">@null</item>
<item name="section_header_background">@drawable/list_title_holo</item>
<item name="list_item_divider">?android:attr/listDivider</item>
<item name="list_item_padding_top">0dip</item>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 5bc8ea0..1edc590 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -21,8 +21,6 @@
<dimen name="contact_shortcut_frame_width">50dip</dimen>
<dimen name="contact_shortcut_frame_height">56dip</dimen>
- <dimen name="aggregation_suggestion_icon_size">40dip</dimen>
-
<dimen name="account_selector_popup_width">400dip</dimen>
<dimen name="photo_action_popup_width">400dip</dimen>
@@ -228,6 +226,7 @@
<dimen name="call_log_inner_margin">8dip</dimen>
<dimen name="call_log_outer_margin">16dip</dimen>
<dimen name="call_log_indent_margin">24dip</dimen>
+ <dimen name="call_log_list_item_height">56dip</dimen>
<dimen name="call_log_list_contact_photo_size">64dip</dimen>
<dimen name="call_detail_contact_background_height">174dip</dimen>
<dimen name="call_detail_contact_name_margin">24dip</dimen>
@@ -248,4 +247,5 @@
<!-- Height for directory headers in contact lists -->
<dimen name="directory_header_height">24dip</dimen>
+
</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index cf7310c..2e8983f 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -129,13 +129,13 @@
<string name="menu_splitAggregate">Separate</string>
<!-- Menu item that edits the currently selected group [CHAR LIMIT=30] -->
- <string name="menu_editGroup">Edit group</string>
+ <string name="menu_editGroup">Edit</string>
<!-- Menu item that renames the currently selected group [CHAR LIMIT=30] -->
<string name="menu_renameGroup">Rename group</string>
<!-- Menu item that deletes the currently selected group [CHAR LIMIT=30] -->
- <string name="menu_deleteGroup">Delete group</string>
+ <string name="menu_deleteGroup">Delete</string>
<!-- Menu item (in the action bar) that creates a new contacts [CHAR LIMIT=12] -->
<string name="menu_new_contact_action_bar">New</string>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index a137361..763a600 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -76,7 +76,6 @@
<style name="ContactDetailActivityTheme" parent="@android:style/Theme.Holo.Light.DarkActionBar">
<item name="android:actionBarStyle">@style/ContactsActionBarStyle</item>
- <item name="android:windowContentOverlay">@null</item>
<item name="android:textColorPrimary">@color/primary_text_color</item>
<item name="android:textColorSecondary">@color/secondary_text_color</item>
</style>
diff --git a/src/com/android/contacts/CallDetailActivity.java b/src/com/android/contacts/CallDetailActivity.java
index 43e6fc7..5a2e9ea 100644
--- a/src/com/android/contacts/CallDetailActivity.java
+++ b/src/com/android/contacts/CallDetailActivity.java
@@ -41,6 +41,7 @@
import android.provider.CallLog;
import android.provider.CallLog.Calls;
import android.provider.Contacts.Intents.Insert;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.PhoneLookup;
import android.provider.VoicemailContract.Voicemails;
@@ -54,7 +55,6 @@
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.ImageButton;
import android.widget.ImageView;
@@ -71,8 +71,7 @@
* This activity can be either started with the URI of a single call log entry, or with the
* {@link #EXTRA_CALL_LOG_IDS} extra to specify a group of call log entries.
*/
-public class CallDetailActivity extends ListActivity implements
- AdapterView.OnItemClickListener {
+public class CallDetailActivity extends ListActivity {
private static final String TAG = "CallDetail";
/** A long array extra containing ids of call log entries to display. */
@@ -116,6 +115,7 @@
CallLog.Calls.NUMBER,
CallLog.Calls.TYPE,
CallLog.Calls.COUNTRY_ISO,
+ CallLog.Calls.GEOCODED_LOCATION,
};
static final int DATE_COLUMN_INDEX = 0;
@@ -123,6 +123,7 @@
static final int NUMBER_COLUMN_INDEX = 2;
static final int CALL_TYPE_COLUMN_INDEX = 3;
static final int COUNTRY_ISO_COLUMN_INDEX = 4;
+ static final int GEOCODED_LOCATION_COLUMN_INDEX = 5;
static final String[] PHONES_PROJECTION = new String[] {
PhoneLookup._ID,
@@ -167,7 +168,6 @@
mContactBackgroundView = (ImageView) findViewById(R.id.contact_background);
mDefaultCountryIso = ContactsUtils.getCurrentCountryIso(this);
mContactPhotoManager = ContactPhotoManager.getInstance(this);
- getListView().setOnItemClickListener(this);
configureActionBar();
optionallyHandleVoicemail();
}
@@ -272,120 +272,152 @@
* @param callUris URIs into {@link CallLog.Calls} of the calls to be displayed
*/
private void updateData(final Uri... callUris) {
- // TODO: All phone calls correspond to the same person, so we can make a single lookup.
- final int numCalls = callUris.length;
- final PhoneCallDetails[] details = new PhoneCallDetails[numCalls];
- try {
- for (int index = 0; index < numCalls; ++index) {
- details[index] = getPhoneCallDetailsForUri(callUris[index]);
- }
- } catch (IllegalArgumentException e) {
- // Something went wrong reading in our primary data, so we're going to
- // bail out and show error to users.
- Log.w(TAG, "invalid URI starting call details", e);
- Toast.makeText(this, R.string.toast_call_detail_error,
- Toast.LENGTH_SHORT).show();
- finish();
- return;
- }
+ mBackgroundTaskService.submit(new BackgroundTask() {
+ private PhoneCallDetails[] details;
- // We know that all calls are from the same number and the same contact, so pick the first.
- mNumber = details[0].number.toString();
- final long personId = details[0].personId;
- final Uri photoUri = details[0].photoUri;
-
- // Set the details header, based on the first phone call.
- mPhoneCallDetailsHelper.setPhoneCallName(mHeaderTextView, details[0]);
-
- // Cache the details about the phone number.
- final Uri numberCallUri = mPhoneNumberHelper.getCallUri(mNumber);
- final boolean canPlaceCallsTo = mPhoneNumberHelper.canPlaceCallsTo(mNumber);
- final boolean isVoicemailNumber = mPhoneNumberHelper.isVoicemailNumber(mNumber);
- final boolean isSipNumber = mPhoneNumberHelper.isSipNumber(mNumber);
-
- // Let user view contact details if they exist, otherwise add option to create new contact
- // from this number.
- final Intent mainActionIntent;
- final int mainActionIcon;
-
- if (details[0].personId != -1) {
- Uri personUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, personId);
- mainActionIntent = new Intent(Intent.ACTION_VIEW, personUri);
- mainActionIcon = R.drawable.ic_contacts_holo_dark;
- } else if (isVoicemailNumber) {
- mainActionIntent = null;
- mainActionIcon = 0;
- } else if (isSipNumber) {
- // TODO: This item is currently disabled for SIP addresses, because
- // the Insert.PHONE extra only works correctly for PSTN numbers.
- //
- // To fix this for SIP addresses, we need to:
- // - define ContactsContract.Intents.Insert.SIP_ADDRESS, and use it here if
- // the current number is a SIP address
- // - update the contacts UI code to handle Insert.SIP_ADDRESS by
- // updating the SipAddress field
- // and then we can remove the "!isSipNumber" check above.
- mainActionIntent = null;
- mainActionIcon = 0;
- } else if (canPlaceCallsTo) {
- mainActionIntent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
- mainActionIntent.setType(Contacts.CONTENT_ITEM_TYPE);
- mainActionIntent.putExtra(Insert.PHONE, mNumber);
- mainActionIcon = R.drawable.ic_add_contact_holo_dark;
- } else {
- // If we cannot call the number, when we probably cannot add it as a contact either.
- // This is usually the case of private, unknown, or payphone numbers.
- mainActionIntent = null;
- mainActionIcon = 0;
- }
-
- if (mainActionIntent == null) {
- mMainActionView.setVisibility(View.INVISIBLE);
- mMainActionPushLayerView.setVisibility(View.GONE);
- } else {
- mMainActionView.setVisibility(View.VISIBLE);
- mMainActionView.setImageResource(mainActionIcon);
- mMainActionPushLayerView.setVisibility(View.VISIBLE);
- mMainActionPushLayerView.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- startActivity(mainActionIntent);
+ @Override
+ public void doInBackground() {
+ // TODO: All phone calls correspond to the same person, so we can make a single
+ // lookup.
+ final int numCalls = callUris.length;
+ details = new PhoneCallDetails[numCalls];
+ try {
+ for (int index = 0; index < numCalls; ++index) {
+ details[index] = getPhoneCallDetailsForUri(callUris[index]);
+ }
+ } catch (IllegalArgumentException e) {
+ // Something went wrong reading in our primary data.
+ Log.w(TAG, "invalid URI starting call details", e);
+ details = null;
}
- });
- }
+ }
- // Build list of various available actions.
- final List<ViewEntry> actions = new ArrayList<ViewEntry>();
+ @Override
+ public void onPostExecute() {
+ if (details == null) {
+ // Somewhere went wrong: we're going to bail out and show error to users.
+ Toast.makeText(CallDetailActivity.this, R.string.toast_call_detail_error,
+ Toast.LENGTH_SHORT).show();
+ finish();
+ return;
+ }
- // This action allows to call the number that places the call.
- if (canPlaceCallsTo) {
- actions.add(new ViewEntry(android.R.drawable.sym_action_call,
- getString(R.string.menu_callNumber, mNumber),
- new Intent(Intent.ACTION_CALL_PRIVILEGED, numberCallUri)));
- }
+ // We know that all calls are from the same number and the same contact, so pick the
+ // first.
+ PhoneCallDetails firstDetails = details[0];
+ mNumber = firstDetails.number.toString();
+ final long personId = firstDetails.personId;
+ final Uri photoUri = firstDetails.photoUri;
- // This action allows to send an SMS to the number that placed the call.
- if (mPhoneNumberHelper.canSendSmsTo(mNumber)) {
- 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));
- }
+ // Set the details header, based on the first phone call.
+ mPhoneCallDetailsHelper.setPhoneCallName(mHeaderTextView, firstDetails);
- mHasEditNumberBeforeCall = canPlaceCallsTo && !isSipNumber && !isVoicemailNumber;
+ // Cache the details about the phone number.
+ final Uri numberCallUri = mPhoneNumberHelper.getCallUri(mNumber);
+ final boolean canPlaceCallsTo = mPhoneNumberHelper.canPlaceCallsTo(mNumber);
+ final boolean isVoicemailNumber = mPhoneNumberHelper.isVoicemailNumber(mNumber);
+ final boolean isSipNumber = mPhoneNumberHelper.isSipNumber(mNumber);
- if (actions.size() != 0) {
- // Set the actions for this phone number.
- setListAdapter(new ViewAdapter(this, actions));
- getListView().setVisibility(View.VISIBLE);
- } else {
- getListView().setVisibility(View.GONE);
- }
+ // Let user view contact details if they exist, otherwise add option to create new
+ // contact from this number.
+ final Intent mainActionIntent;
+ final int mainActionIcon;
- ListView historyList = (ListView) findViewById(R.id.history);
- historyList.setAdapter(
- new CallDetailHistoryAdapter(this, mInflater, mCallTypeHelper, details));
- loadContactPhotos(photoUri);
+ if (firstDetails.personId != -1) {
+ Uri personUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, personId);
+ mainActionIntent = new Intent(Intent.ACTION_VIEW, personUri);
+ mainActionIcon = R.drawable.ic_contacts_holo_dark;
+ } else if (isVoicemailNumber) {
+ mainActionIntent = null;
+ mainActionIcon = 0;
+ } else if (isSipNumber) {
+ // TODO: This item is currently disabled for SIP addresses, because
+ // the Insert.PHONE extra only works correctly for PSTN numbers.
+ //
+ // To fix this for SIP addresses, we need to:
+ // - define ContactsContract.Intents.Insert.SIP_ADDRESS, and use it here if
+ // the current number is a SIP address
+ // - update the contacts UI code to handle Insert.SIP_ADDRESS by
+ // updating the SipAddress field
+ // and then we can remove the "!isSipNumber" check above.
+ mainActionIntent = null;
+ mainActionIcon = 0;
+ } else if (canPlaceCallsTo) {
+ mainActionIntent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
+ mainActionIntent.setType(Contacts.CONTENT_ITEM_TYPE);
+ mainActionIntent.putExtra(Insert.PHONE, mNumber);
+ mainActionIcon = R.drawable.ic_add_contact_holo_dark;
+ } else {
+ // If we cannot call the number, when we probably cannot add it as a contact either.
+ // This is usually the case of private, unknown, or payphone numbers.
+ mainActionIntent = null;
+ mainActionIcon = 0;
+ }
+
+ if (mainActionIntent == null) {
+ mMainActionView.setVisibility(View.INVISIBLE);
+ mMainActionPushLayerView.setVisibility(View.GONE);
+ } else {
+ mMainActionView.setVisibility(View.VISIBLE);
+ mMainActionView.setImageResource(mainActionIcon);
+ mMainActionPushLayerView.setVisibility(View.VISIBLE);
+ mMainActionPushLayerView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ startActivity(mainActionIntent);
+ }
+ });
+ }
+
+ // Build list of various available actions.
+ final List<ViewEntry> actions = new ArrayList<ViewEntry>();
+
+ // This action allows to call the number that places the call.
+ if (canPlaceCallsTo) {
+ final CharSequence displayNumber =
+ mPhoneNumberHelper.getDisplayNumber(
+ firstDetails.number, firstDetails.formattedNumber);
+
+ ViewEntry entry = new ViewEntry(
+ getString(R.string.menu_callNumber, displayNumber),
+ new Intent(Intent.ACTION_CALL_PRIVILEGED, numberCallUri));
+
+ // Only show a label if the number is shown and it is not a SIP address.
+ if (!TextUtils.isEmpty(firstDetails.number)
+ && !PhoneNumberUtils.isUriNumber(firstDetails.number.toString())) {
+ entry.label = Phone.getTypeLabel(mResources, firstDetails.numberType,
+ firstDetails.numberLabel);
+ }
+
+ // The secondary action allows to send an SMS to the number that placed the
+ // call.
+ if (mPhoneNumberHelper.canSendSmsTo(mNumber)) {
+ entry.setSecondaryAction(R.drawable.ic_text_holo_dark,
+ new Intent(Intent.ACTION_SENDTO,
+ Uri.fromParts("sms", mNumber, null)));
+ }
+
+ actions.add(entry);
+ }
+
+ mHasEditNumberBeforeCall = canPlaceCallsTo && !isSipNumber && !isVoicemailNumber;
+
+ if (actions.size() != 0) {
+ // Set the actions for this phone number.
+ setListAdapter(new ViewAdapter(CallDetailActivity.this, actions));
+ getListView().setVisibility(View.VISIBLE);
+ getListView().setItemsCanFocus(true);
+ } else {
+ getListView().setVisibility(View.GONE);
+ }
+
+ ListView historyList = (ListView) findViewById(R.id.history);
+ historyList.setAdapter(
+ new CallDetailHistoryAdapter(CallDetailActivity.this, mInflater,
+ mCallTypeHelper, details));
+ loadContactPhotos(photoUri);
+ }
+ });
}
/** Return the phone call details for a given call log URI. */
@@ -403,6 +435,8 @@
long duration = callCursor.getLong(DURATION_COLUMN_INDEX);
int callType = callCursor.getInt(CALL_TYPE_COLUMN_INDEX);
String countryIso = callCursor.getString(COUNTRY_ISO_COLUMN_INDEX);
+ final String geocode = callCursor.getString(GEOCODED_LOCATION_COLUMN_INDEX);
+
if (TextUtils.isEmpty(countryIso)) {
countryIso = mDefaultCountryIso;
}
@@ -448,7 +482,7 @@
numberText = candidateNumberText;
}
}
- return new PhoneCallDetails(number, numberText, countryIso,
+ return new PhoneCallDetails(number, numberText, countryIso, geocode,
new int[]{ callType }, date, duration,
nameText, numberType, numberLabel, personId, photoUri);
} finally {
@@ -470,36 +504,50 @@
}
static final class ViewEntry {
- public final int icon;
public final String text;
- public final Intent intent;
- public final View.OnClickListener action;
+ public final Intent primaryIntent;
- public String label = null;
+ public CharSequence label = null;
public String number = null;
+ /** Icon for the secondary action. */
+ public int secondaryIcon = 0;
+ /** Intent for the secondary action. If not null, an icon must be defined. */
+ public Intent secondaryIntent = null;
- public ViewEntry(int icon, String text, Intent intent) {
- this.icon = icon;
+ public ViewEntry(String text, Intent intent) {
this.text = text;
- this.intent = intent;
- this.action = null;
+ this.primaryIntent = intent;
}
- public ViewEntry(int icon, String text, View.OnClickListener listener) {
- this.icon = icon;
- this.text = text;
- this.intent = null;
- this.action = listener;
+ public void setSecondaryAction(int icon, Intent intent) {
+ secondaryIcon = icon;
+ secondaryIntent = intent;
}
}
- static final class ViewAdapter extends BaseAdapter {
-
+ private static final class ViewAdapter extends BaseAdapter {
+ private final Context mContext;
private final List<ViewEntry> mActions;
-
private final LayoutInflater mInflater;
+ private final View.OnClickListener mPrimaryActionListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ ViewEntry entry = (ViewEntry) view.getTag();
+ mContext.startActivity(entry.primaryIntent);
+ }
+ };
+
+ private final View.OnClickListener mSecondaryActionListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ ViewEntry entry = (ViewEntry) view.getTag();
+ mContext.startActivity(entry.secondaryIntent);
+ }
+ };
+
public ViewAdapter(Context context, List<ViewEntry> actions) {
+ mContext = context;
mActions = actions;
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
@@ -528,12 +576,25 @@
// Fill action with icon and text.
ViewEntry entry = mActions.get(position);
- convertView.setTag(entry);
ImageView icon = (ImageView) convertView.findViewById(R.id.icon);
+ View divider = convertView.findViewById(R.id.divider);
TextView text = (TextView) convertView.findViewById(android.R.id.text1);
- icon.setImageResource(entry.icon);
+ View mainAction = convertView.findViewById(R.id.main_action);
+ mainAction.setOnClickListener(mPrimaryActionListener);
+ mainAction.setTag(entry);
+
+ if (entry.secondaryIntent != null) {
+ icon.setOnClickListener(mSecondaryActionListener);
+ icon.setImageResource(entry.secondaryIcon);
+ icon.setVisibility(View.VISIBLE);
+ icon.setTag(entry);
+ divider.setVisibility(View.VISIBLE);
+ } else {
+ icon.setVisibility(View.GONE);
+ divider.setVisibility(View.GONE);
+ }
text.setText(entry.text);
View line2 = convertView.findViewById(R.id.line2);
@@ -561,19 +622,6 @@
}
@Override
- public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- // Handle passing action off to correct handler.
- if (view.getTag() instanceof ViewEntry) {
- ViewEntry entry = (ViewEntry) view.getTag();
- if (entry.intent != null) {
- startActivity(entry.intent);
- } else if (entry.action != null) {
- entry.action.onClick(view);
- }
- }
- }
-
- @Override
public void startSearch(String initialQuery, boolean selectInitialQuery, Bundle appSearchData,
boolean globalSearch) {
if (globalSearch) {
@@ -640,7 +688,6 @@
menu.findItem(R.id.menu_remove_from_call_log).setVisible(!hasVoicemail());
menu.findItem(R.id.menu_edit_number_before_call).setVisible(mHasEditNumberBeforeCall);
menu.findItem(R.id.menu_trash).setVisible(hasVoicemail());
- menu.findItem(R.id.menu_share_voicemail).setVisible(hasVoicemail());
return super.onPrepareOptionsMenu(menu);
}
@@ -682,10 +729,6 @@
startActivity(new Intent(Intent.ACTION_DIAL, mPhoneNumberHelper.getCallUri(mNumber)));
}
- public void onMenuShareVoicemail(MenuItem menuItem) {
- Log.w(TAG, "onMenuShareVoicemail not yet implemented");
- }
-
public void onMenuTrashVoicemail(MenuItem menuItem) {
final Uri voicemailUri = getVoicemailUri();
mBackgroundTaskService.submit(new BackgroundTask() {
diff --git a/src/com/android/contacts/PhoneCallDetails.java b/src/com/android/contacts/PhoneCallDetails.java
index 347a303..5718091 100644
--- a/src/com/android/contacts/PhoneCallDetails.java
+++ b/src/com/android/contacts/PhoneCallDetails.java
@@ -30,6 +30,8 @@
public final CharSequence formattedNumber;
/** The country corresponding with the phone number. */
public final String countryIso;
+ /** The geocoded location for the phone number. */
+ public final String geocode;
/**
* The type of calls, as defined in the call log table, e.g., {@link Calls#INCOMING_TYPE}.
* <p>
@@ -56,18 +58,20 @@
/** Create the details for a call with a number not associated with a contact. */
public PhoneCallDetails(CharSequence number, CharSequence formattedNumber,
- String countryIso, int[] callTypes, long date, long duration) {
- this(number, formattedNumber, countryIso, callTypes, date, duration, "", 0, "", -1L, null);
+ String countryIso, String geocode, int[] callTypes, long date, long duration) {
+ this(number, formattedNumber, countryIso, geocode, callTypes, date, duration, "", 0, "",
+ -1L, null);
}
/** Create the details for a call with a number associated with a contact. */
public PhoneCallDetails(CharSequence number, CharSequence formattedNumber,
- String countryIso, int[] callTypes, long date, long duration,
+ String countryIso, String geocode, int[] callTypes, long date, long duration,
CharSequence name, int numberType, CharSequence numberLabel, long personId,
Uri photoUri) {
this.number = number;
this.formattedNumber = formattedNumber;
this.countryIso = countryIso;
+ this.geocode = geocode;
this.callTypes = callTypes;
this.date = date;
this.duration = duration;
diff --git a/src/com/android/contacts/PhoneCallDetailsHelper.java b/src/com/android/contacts/PhoneCallDetailsHelper.java
index 3101aee..e970fcc 100644
--- a/src/com/android/contacts/PhoneCallDetailsHelper.java
+++ b/src/com/android/contacts/PhoneCallDetailsHelper.java
@@ -107,12 +107,10 @@
mPhoneNumberHelper.getDisplayNumber(details.number, details.formattedNumber);
if (TextUtils.isEmpty(details.name)) {
nameText = displayNumber;
- String geocode = mPhoneNumberHelper.getGeocodeForNumber(
- details.number.toString(), details.countryIso);
- if (TextUtils.isEmpty(geocode)) {
+ if (TextUtils.isEmpty(details.geocode)) {
numberText = mResources.getString(R.string.call_log_empty_gecode);
} else {
- numberText = geocode;
+ numberText = details.geocode;
}
} else {
nameText = details.name;
diff --git a/src/com/android/contacts/activities/ContactDetailActivity.java b/src/com/android/contacts/activities/ContactDetailActivity.java
index 3cd07ca..c863b23 100644
--- a/src/com/android/contacts/activities/ContactDetailActivity.java
+++ b/src/com/android/contacts/activities/ContactDetailActivity.java
@@ -152,7 +152,6 @@
}
mDetailFragment.setListener(mFragmentListener);
- TabCarouselScrollManager.bind(mTabCarousel, mDetailFragment, mUpdatesFragment);
mDetailFragment.setData(mLookupUri, mContactData);
mUpdatesFragment.setData(mLookupUri, mContactData);
@@ -348,6 +347,7 @@
mTabCarousel = (ContactDetailTabCarousel) findViewById(R.id.tab_carousel);
if (mTabCarousel != null) {
mTabCarousel.setListener(mTabCarouselListener);
+ TabCarouselScrollManager.bind(mTabCarousel, mDetailFragment, mUpdatesFragment);
}
mViewPager = (ViewPager) findViewById(R.id.pager);
diff --git a/src/com/android/contacts/calllog/CallDetailHistoryAdapter.java b/src/com/android/contacts/calllog/CallDetailHistoryAdapter.java
index 3e3ba36..3de5730 100644
--- a/src/com/android/contacts/calllog/CallDetailHistoryAdapter.java
+++ b/src/com/android/contacts/calllog/CallDetailHistoryAdapter.java
@@ -26,7 +26,6 @@
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
-import android.widget.FrameLayout;
import android.widget.TextView;
/**
diff --git a/src/com/android/contacts/calllog/CallLogFragment.java b/src/com/android/contacts/calllog/CallLogFragment.java
index bac3987..0713e0e 100644
--- a/src/com/android/contacts/calllog/CallLogFragment.java
+++ b/src/com/android/contacts/calllog/CallLogFragment.java
@@ -17,7 +17,6 @@
package com.android.contacts.calllog;
import com.android.common.widget.GroupingListAdapter;
-import com.android.contacts.CallDetailActivity;
import com.android.contacts.ContactPhotoManager;
import com.android.contacts.ContactsUtils;
import com.android.contacts.PhoneCallDetails;
@@ -33,8 +32,8 @@
import com.android.internal.telephony.ITelephony;
import com.google.common.annotations.VisibleForTesting;
+import android.app.KeyguardManager;
import android.app.ListFragment;
-import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
@@ -89,6 +88,7 @@
Calls.TYPE,
Calls.COUNTRY_ISO,
Calls.VOICEMAIL_URI,
+ Calls.GEOCODED_LOCATION,
};
public static final int ID = 0;
@@ -98,6 +98,7 @@
public static final int CALL_TYPE = 4;
public static final int COUNTRY_ISO = 5;
public static final int VOICEMAIL_URI = 6;
+ public static final int GEOCODED_LOCATION = 7;
/**
* The name of the synthetic "section" column.
@@ -107,7 +108,7 @@
*/
public static final String SECTION_NAME = "section";
/** The index of the "section" column in the projection. */
- public static final int SECTION = 7;
+ public static final int SECTION = 8;
/** The value of the "section" column for the header of the new section. */
public static final int SECTION_NEW_HEADER = 0;
/** The value of the "section" column for the items of the new section. */
@@ -171,6 +172,7 @@
private View mStatusMessageView;
private TextView mStatusMessageText;
private TextView mStatusMessageAction;
+ private KeyguardManager mKeyguardManager;
public static final class ContactInfo {
public long personId = -1;
@@ -273,7 +275,18 @@
/** Can be set to true by tests to disable processing of requests. */
private volatile boolean mRequestProcessingDisabled = false;
- private final View.OnClickListener mCallPlayOnClickListener = new View.OnClickListener() {
+ /** Listener for the primary action in the list, opens the call details. */
+ private final View.OnClickListener mPrimaryActionListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ IntentProvider intentProvider = (IntentProvider) view.getTag();
+ if (intentProvider != null) {
+ mContext.startActivity(intentProvider.getIntent(mContext));
+ }
+ }
+ };
+ /** Listener for the secondary action in the list, either call or play. */
+ private final View.OnClickListener mSecondaryActionListener = new View.OnClickListener() {
@Override
public void onClick(View view) {
IntentProvider intentProvider = (IntentProvider) view.getTag();
@@ -661,7 +674,8 @@
private void findAndCacheViews(View view) {
// Get the views to bind to.
CallLogListItemViews views = CallLogListItemViews.fromView(view);
- views.secondaryActionView.setOnClickListener(mCallPlayOnClickListener);
+ views.primaryActionView.setOnClickListener(mPrimaryActionListener);
+ views.secondaryActionView.setOnClickListener(mSecondaryActionListener);
view.setTag(views);
}
@@ -699,6 +713,9 @@
final String formattedNumber;
final String countryIso = c.getString(CallLogQuery.COUNTRY_ISO);
+ views.primaryActionView.setTag(
+ IntentProvider.getCallDetailIntentProvider(
+ this, c.getPosition(), c.getLong(CallLogQuery.ID), count));
// Store away the voicemail information so we can play it directly.
if (callType == Calls.VOICEMAIL_TYPE) {
String voicemailUri = c.getString(CallLogQuery.VOICEMAIL_URI);
@@ -754,12 +771,13 @@
final Uri thumbnailUri = info.thumbnailUri;
final String lookupKey = info.lookupKey;
final int[] callTypes = getCallTypes(c, count);
+ final String geocode = c.getString(CallLogQuery.GEOCODED_LOCATION);
final PhoneCallDetails details;
if (TextUtils.isEmpty(name)) {
- details = new PhoneCallDetails(number, formattedNumber, countryIso,
+ details = new PhoneCallDetails(number, formattedNumber, countryIso, geocode,
callTypes, date, duration);
} else {
- details = new PhoneCallDetails(number, formattedNumber, countryIso,
+ details = new PhoneCallDetails(number, formattedNumber, countryIso, geocode,
callTypes, date, duration, name, ntype, label, personId, thumbnailUri);
}
@@ -855,6 +873,8 @@
mVoiceMailNumber = ((TelephonyManager) getActivity().getSystemService(
Context.TELEPHONY_SERVICE)).getVoiceMailNumber();
mCallLogQueryHandler = new CallLogQueryHandler(getActivity().getContentResolver(), this);
+ mKeyguardManager =
+ (KeyguardManager) getActivity().getSystemService(Context.KEYGUARD_SERVICE);
setHasOptionsMenu(true);
}
@@ -904,6 +924,7 @@
mAdapter = new CallLogAdapter(getActivity(), mCallLogQueryHandler, currentCountryIso,
getVoiceMailNumber());
setListAdapter(mAdapter);
+ getListView().setItemsCanFocus(true);
}
@Override
@@ -958,11 +979,6 @@
public void onStop() {
super.onStop();
resetNewCallsFlag();
- // Clear notifications only when window gains focus. This activity won't
- // immediately receive focus if the keyguard screen is above it.
- if (getActivity().hasWindowFocus()) {
- removeMissedCallNotifications();
- }
}
@Override
@@ -1098,38 +1114,6 @@
}
}
- @Override
- public void onListItemClick(ListView l, View v, int position, long id) {
- Cursor cursor = (Cursor) mAdapter.getItem(position);
- if (CallLogQuery.isSectionHeader(cursor)) {
- // Do nothing when a header is clicked.
- return;
- }
- Intent intent = new Intent(getActivity(), CallDetailActivity.class);
- if (mAdapter.isGroupHeader(position)) {
- // We want to restore the position in the cursor at the end.
- int currentPosition = cursor.getPosition();
- int groupSize = mAdapter.getGroupSize(position);
- long[] ids = new long[groupSize];
- // Copy the ids of the rows in the group.
- for (int index = 0; index < groupSize; ++index) {
- ids[index] = cursor.getLong(CallLogQuery.ID);
- cursor.moveToNext();
- }
- intent.putExtra(CallDetailActivity.EXTRA_CALL_LOG_IDS, ids);
- cursor.moveToPosition(currentPosition);
- } else {
- // If there is a single item, use the direct URI for it.
- intent.setData(ContentUris.withAppendedId(Calls.CONTENT_URI_WITH_VOICEMAIL, id));
- String voicemailUri = cursor.getString(CallLogQuery.VOICEMAIL_URI);
- if (voicemailUri != null) {
- intent.putExtra(CallDetailActivity.EXTRA_VOICEMAIL_URI, Uri.parse(voicemailUri));
- }
- intent.putExtra(CallDetailActivity.EXTRA_VOICEMAIL_START_PLAYBACK, false);
- }
- startActivity(intent);
- }
-
@VisibleForTesting
public CallLogAdapter getAdapter() {
return mAdapter;
@@ -1156,9 +1140,9 @@
startCallsQuery();
startVoicemailStatusQuery();
mAdapter.mPreDrawListener = null; // Let it restart the thread after next draw
- // Clear notifications only when window gains focus. This activity won't
- // immediately receive focus if the keyguard screen is above it.
- if (getActivity().hasWindowFocus()) {
+ // We don't want to remove notification when keyguard is on because the user has likely not
+ // seen the new call yet.
+ if (!mKeyguardManager.inKeyguardRestrictedInputMode()) {
removeMissedCallNotifications();
}
}
diff --git a/src/com/android/contacts/calllog/CallLogListItemViews.java b/src/com/android/contacts/calllog/CallLogListItemViews.java
index 040d0ad..fbf91cc 100644
--- a/src/com/android/contacts/calllog/CallLogListItemViews.java
+++ b/src/com/android/contacts/calllog/CallLogListItemViews.java
@@ -31,6 +31,8 @@
public final class CallLogListItemViews {
/** The quick contact badge for the contact. */
public final QuickContactBadge quickContactView;
+ /** The primary action view of the entry. */
+ public final View primaryActionView;
/** The secondary action button on the entry. */
public final ImageView secondaryActionView;
/** The icon used for unheard voicemail. */
@@ -44,11 +46,12 @@
/** The text of the header in a stand-alone row, or null for other types of rows. */
public final TextView listHeaderTextView;
- private CallLogListItemViews(QuickContactBadge quickContactView,
+ private CallLogListItemViews(QuickContactBadge quickContactView, View primaryActionView,
ImageView secondaryActionView, View unheardView, View dividerView,
PhoneCallDetailsViews phoneCallDetailsViews, View listItemView,
TextView listHeaderTextView) {
this.quickContactView = quickContactView;
+ this.primaryActionView = primaryActionView;
this.secondaryActionView = secondaryActionView;
this.unheardView = unheardView;
this.dividerView = dividerView;
@@ -60,6 +63,7 @@
public static CallLogListItemViews fromView(View view) {
return new CallLogListItemViews(
(QuickContactBadge) view.findViewById(R.id.quick_contact_photo),
+ view.findViewById(R.id.primary_action_view),
(ImageView) view.findViewById(R.id.secondary_action_icon),
view.findViewById(R.id.unheard_icon),
view.findViewById(R.id.divider),
@@ -71,6 +75,7 @@
public static CallLogListItemViews createForTest(Context context) {
return new CallLogListItemViews(
new QuickContactBadge(context),
+ new View(context),
new ImageView(context),
new View(context),
new View(context),
diff --git a/src/com/android/contacts/calllog/CallLogQueryHandler.java b/src/com/android/contacts/calllog/CallLogQueryHandler.java
index 394599f..68ac63a 100644
--- a/src/com/android/contacts/calllog/CallLogQueryHandler.java
+++ b/src/com/android/contacts/calllog/CallLogQueryHandler.java
@@ -103,7 +103,7 @@
new MatrixCursor(CallLogFragment.CallLogQuery.EXTENDED_PROJECTION);
// The values in this row correspond to default values for _PROJECTION from CallLogQuery
// plus the section value.
- matrixCursor.addRow(new Object[]{ -1L, "", 0L, 0L, 0, "", "", section });
+ matrixCursor.addRow(new Object[]{ -1L, "", 0L, 0L, 0, "", "", "", section });
return matrixCursor;
}
diff --git a/src/com/android/contacts/calllog/IntentProvider.java b/src/com/android/contacts/calllog/IntentProvider.java
index 035d90a..8173238 100644
--- a/src/com/android/contacts/calllog/IntentProvider.java
+++ b/src/com/android/contacts/calllog/IntentProvider.java
@@ -17,10 +17,13 @@
package com.android.contacts.calllog;
import com.android.contacts.CallDetailActivity;
+import com.android.contacts.calllog.CallLogFragment.CallLogAdapter;
+import com.android.contacts.calllog.CallLogFragment.CallLogQuery;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
+import android.database.Cursor;
import android.net.Uri;
import android.provider.CallLog.Calls;
import android.telephony.PhoneNumberUtils;
@@ -68,4 +71,41 @@
}
};
}
+
+ public static IntentProvider getCallDetailIntentProvider(
+ final CallLogAdapter adapter, final int position, final long id, final int groupSize) {
+ return new IntentProvider() {
+ @Override
+ public Intent getIntent(Context context) {
+ Cursor cursor = adapter.getCursor();
+ cursor.moveToPosition(position);
+ if (CallLogQuery.isSectionHeader(cursor)) {
+ // Do nothing when a header is clicked.
+ return null;
+ }
+ Intent intent = new Intent(context, CallDetailActivity.class);
+ if (adapter.isGroupHeader(position)) {
+ // We want to restore the position in the cursor at the end.
+ long[] ids = new long[groupSize];
+ // Copy the ids of the rows in the group.
+ for (int index = 0; index < groupSize; ++index) {
+ ids[index] = cursor.getLong(CallLogQuery.ID);
+ cursor.moveToNext();
+ }
+ intent.putExtra(CallDetailActivity.EXTRA_CALL_LOG_IDS, ids);
+ } else {
+ // If there is a single item, use the direct URI for it.
+ intent.setData(ContentUris.withAppendedId(
+ Calls.CONTENT_URI_WITH_VOICEMAIL, id));
+ String voicemailUri = cursor.getString(CallLogQuery.VOICEMAIL_URI);
+ if (voicemailUri != null) {
+ intent.putExtra(CallDetailActivity.EXTRA_VOICEMAIL_URI,
+ Uri.parse(voicemailUri));
+ }
+ intent.putExtra(CallDetailActivity.EXTRA_VOICEMAIL_START_PLAYBACK, false);
+ }
+ return intent;
+ }
+ };
+ }
}
diff --git a/src/com/android/contacts/calllog/PhoneNumberHelper.java b/src/com/android/contacts/calllog/PhoneNumberHelper.java
index bf493f5..4d96f4f 100644
--- a/src/com/android/contacts/calllog/PhoneNumberHelper.java
+++ b/src/com/android/contacts/calllog/PhoneNumberHelper.java
@@ -18,10 +18,6 @@
import com.android.contacts.R;
import com.android.internal.telephony.CallerInfo;
-import com.google.i18n.phonenumbers.NumberParseException;
-import com.google.i18n.phonenumbers.PhoneNumberUtil;
-import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
-import com.google.i18n.phonenumbers.geocoding.PhoneNumberOfflineGeocoder;
import android.content.res.Resources;
import android.net.Uri;
@@ -34,14 +30,10 @@
public class PhoneNumberHelper {
private final Resources mResources;
private final String mVoicemailNumber;
- private final PhoneNumberUtil mPhoneNumberUtil;
- private final PhoneNumberOfflineGeocoder mPhoneNumberOfflineGeocoder;
public PhoneNumberHelper(Resources resources, String voicemailNumber) {
mResources = resources;
mVoicemailNumber = voicemailNumber;
- mPhoneNumberUtil = PhoneNumberUtil.getInstance();
- mPhoneNumberOfflineGeocoder = PhoneNumberOfflineGeocoder.getInstance();
}
/** Returns true if it is possible to place a call to the given number. */
@@ -106,35 +98,4 @@
public boolean isSipNumber(CharSequence number) {
return PhoneNumberUtils.isUriNumber(number.toString());
}
-
- /**
- * Returns a structured phone number from the given text representation, or null if the number
- * cannot be parsed.
- */
- private PhoneNumber parsePhoneNumber(String number, String countryIso) {
- try {
- return mPhoneNumberUtil.parse(number, countryIso);
- } catch (NumberParseException e) {
- return null;
- }
- }
-
- /** Returns the geocode associated with a phone number or the empty string if not available. */
- public String getGeocodeForNumber(String number, String countryIso) {
- if (!canGeocode(number)) {
- return "";
- }
- PhoneNumber structuredPhoneNumber = parsePhoneNumber(number, countryIso);
- if (structuredPhoneNumber != null) {
- return mPhoneNumberOfflineGeocoder.getDescriptionForNumber(
- structuredPhoneNumber, mResources.getConfiguration().locale);
- } else {
- return "";
- }
- }
-
- /** Returns true if it is possible to compute a geocode for the given number. */
- private boolean canGeocode(CharSequence number) {
- return canPlaceCallsTo(number) && !isVoicemailNumber(number);
- }
}
diff --git a/src/com/android/contacts/detail/ContactDetailDisplayUtils.java b/src/com/android/contacts/detail/ContactDetailDisplayUtils.java
index df62696..f74b56a 100644
--- a/src/com/android/contacts/detail/ContactDetailDisplayUtils.java
+++ b/src/com/android/contacts/detail/ContactDetailDisplayUtils.java
@@ -31,17 +31,24 @@
import android.content.Context;
import android.content.Entity;
import android.content.Entity.NamedContentValues;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.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.text.Html;
+import android.text.Html.ImageGetter;
import android.text.Spanned;
import android.text.TextUtils;
+import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -60,6 +67,8 @@
* {@link ContactLoader.Result} data object to appropriate {@link View}s.
*/
public class ContactDetailDisplayUtils {
+ private static final String TAG = "ContactDetailDisplayUtils";
+
private static final int PHOTO_FADE_IN_ANIMATION_DURATION_MILLIS = 100;
private ContactDetailDisplayUtils() {
@@ -296,10 +305,11 @@
TextView attributionView = (TextView) rootView.findViewById(
R.id.stream_item_attribution);
TextView commentsView = (TextView) rootView.findViewById(R.id.stream_item_comments);
- htmlView.setText(Html.fromHtml(streamItem.getText()));
+ ImageGetter imageGetter = new DefaultImageGetter(context.getPackageManager());
+ htmlView.setText(Html.fromHtml(streamItem.getText(), imageGetter, null));
attributionView.setText(ContactBadgeUtil.getSocialDate(streamItem, context));
if (streamItem.getComments() != null) {
- commentsView.setText(Html.fromHtml(streamItem.getComments()));
+ commentsView.setText(Html.fromHtml(streamItem.getComments(), imageGetter, null));
commentsView.setVisibility(View.VISIBLE);
} else {
commentsView.setVisibility(View.GONE);
@@ -365,4 +375,79 @@
}
}
+ /** Fetcher for images from resources to be included in HTML text. */
+ private static class DefaultImageGetter implements Html.ImageGetter {
+ /** The scheme used to load resources. */
+ private static final String RES_SCHEME = "res";
+
+ private final PackageManager mPackageManager;
+
+ public DefaultImageGetter(PackageManager packageManager) {
+ mPackageManager = packageManager;
+ }
+
+ @Override
+ public Drawable getDrawable(String source) {
+ // Returning null means that a default image will be used.
+ Uri uri;
+ try {
+ uri = Uri.parse(source);
+ } catch (Throwable e) {
+ Log.d(TAG, "Could not parse image source: " + source);
+ return null;
+ }
+ if (!RES_SCHEME.equals(uri.getScheme())) {
+ Log.d(TAG, "Image source does not correspond to a resource: " + source);
+ return null;
+ }
+ // The URI authority represents the package name.
+ String packageName = uri.getAuthority();
+
+ Resources resources = getResourcesForResourceName(packageName);
+ if (resources == null) {
+ Log.d(TAG, "Could not parse image source: " + source);
+ return null;
+ }
+
+ List<String> pathSegments = uri.getPathSegments();
+ if (pathSegments.size() != 1) {
+ Log.d(TAG, "Could not parse image source: " + source);
+ return null;
+ }
+
+ final String name = pathSegments.get(0);
+ final int resId = resources.getIdentifier(name, "drawable", packageName);
+
+ if (resId == 0) {
+ // Use the default image icon in this case.
+ Log.d(TAG, "Cannot resolve resource identifier: " + source);
+ return null;
+ }
+
+ try {
+ return getResourceDrawable(resources, resId);
+ } catch (NotFoundException e) {
+ Log.d(TAG, "Resource not found: " + source, e);
+ return null;
+ }
+ }
+
+ /** Returns the drawable associated with the given id. */
+ private Drawable getResourceDrawable(Resources resources, int resId)
+ throws NotFoundException {
+ Drawable drawable = resources.getDrawable(resId);
+ drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
+ return drawable;
+ }
+
+ /** Returns the {@link Resources} of the package of the given resource name. */
+ private Resources getResourcesForResourceName(String packageName) {
+ try {
+ return mPackageManager.getResourcesForApplication(packageName);
+ } catch (NameNotFoundException e) {
+ Log.d(TAG, "Could not find package: " + packageName);
+ return null;
+ }
+ }
+ }
}
diff --git a/src/com/android/contacts/editor/AggregationSuggestionView.java b/src/com/android/contacts/editor/AggregationSuggestionView.java
index 07e67e8..df90cff 100644
--- a/src/com/android/contacts/editor/AggregationSuggestionView.java
+++ b/src/com/android/contacts/editor/AggregationSuggestionView.java
@@ -29,7 +29,7 @@
import android.provider.ContactsContract.Contacts;
import android.util.AttributeSet;
import android.widget.ImageView;
-import android.widget.RelativeLayout;
+import android.widget.LinearLayout;
import android.widget.TextView;
import java.util.ArrayList;
@@ -38,7 +38,7 @@
/**
* A view that contains a name, picture and other data for a contact aggregation suggestion.
*/
-public class AggregationSuggestionView extends RelativeLayout {
+public class AggregationSuggestionView extends LinearLayout {
public interface Listener {
@@ -63,17 +63,14 @@
public AggregationSuggestionView(Context context) {
super(context);
- setClickable(true);
}
public AggregationSuggestionView(Context context, AttributeSet attrs) {
super(context, attrs);
- setClickable(true);
}
public AggregationSuggestionView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
- setClickable(true);
}
public void setNewContact(boolean flag) {
@@ -135,8 +132,7 @@
mListener = listener;
}
- @Override
- public boolean performClick() {
+ public boolean handleItemClickEvent() {
if (mListener != null && isEnabled()) {
if (canEditSuggestedContact()) {
mListener.onEditAction(Contacts.getLookupUri(mContactId, mLookupKey));
diff --git a/src/com/android/contacts/editor/ContactEditorFragment.java b/src/com/android/contacts/editor/ContactEditorFragment.java
index 412efab..afc3ab1 100644
--- a/src/com/android/contacts/editor/ContactEditorFragment.java
+++ b/src/com/android/contacts/editor/ContactEditorFragment.java
@@ -83,6 +83,8 @@
import android.view.ViewGroup.MarginLayoutParams;
import android.view.ViewStub;
import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.BaseAdapter;
import android.widget.LinearLayout;
import android.widget.ListPopupWindow;
import android.widget.Toast;
@@ -207,17 +209,6 @@
private Cursor mGroupMetaData;
- /**
- * A delay in milliseconds used for bringing aggregation suggestions to
- * the visible part of the screen. The reason this has to be done after
- * a delay is a race condition with the soft keyboard. The keyboard
- * may expand to display its own autocomplete suggestions, which will
- * reduce the visible area of the screen. We will yield to the keyboard
- * hoping that the delay is sufficient. If not - part of the
- * suggestion will be hidden, which is not fatal.
- */
- private static final int AGGREGATION_SUGGESTION_SCROLL_DELAY = 200;
-
private File mCurrentPhotoFile;
// Height/width (in pixels) to request for the photo - queried from the provider.
@@ -245,6 +236,62 @@
private long mAggregationSuggestionsRawContactId;
private View mAggregationSuggestionView;
+ private ListPopupWindow mAggregationSuggestionPopup;
+
+ private static final class AggregationSuggestionAdapter extends BaseAdapter {
+ private final Activity mActivity;
+ private final boolean mSetNewContact;
+ private final AggregationSuggestionView.Listener mListener;
+ private final List<Suggestion> mSuggestions;
+
+ public AggregationSuggestionAdapter(Activity activity, boolean setNewContact,
+ AggregationSuggestionView.Listener listener, List<Suggestion> suggestions) {
+ mActivity = activity;
+ mSetNewContact = setNewContact;
+ mListener = listener;
+ mSuggestions = suggestions;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ Suggestion suggestion = (Suggestion) getItem(position);
+ LayoutInflater inflater = mActivity.getLayoutInflater();
+ AggregationSuggestionView suggestionView =
+ (AggregationSuggestionView) inflater.inflate(
+ R.layout.aggregation_suggestions_item, null);
+ suggestionView.setNewContact(mSetNewContact);
+ suggestionView.setListener(mListener);
+ suggestionView.bindSuggestion(suggestion);
+ return suggestionView;
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return mSuggestions.get(position);
+ }
+
+ @Override
+ public int getCount() {
+ return mSuggestions.size();
+ }
+ }
+
+ private OnItemClickListener mAggregationSuggestionItemClickListener =
+ new OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ final AggregationSuggestionView suggestionView = (AggregationSuggestionView) view;
+ suggestionView.handleItemClickEvent();
+ mAggregationSuggestionPopup.dismiss();
+ mAggregationSuggestionPopup = null;
+ }
+ };
+
private boolean mAutoAddToDefaultGroup;
private boolean mEnabled = true;
@@ -1276,76 +1323,28 @@
return;
}
- RawContactEditorView rawContactView =
+ if (mAggregationSuggestionPopup != null && mAggregationSuggestionPopup.isShowing()) {
+ mAggregationSuggestionPopup.dismiss();
+ }
+
+ if (mAggregationSuggestionEngine.getSuggestedContactCount() == 0) {
+ return;
+ }
+
+ final RawContactEditorView rawContactView =
(RawContactEditorView)getRawContactEditorView(mAggregationSuggestionsRawContactId);
- if (rawContactView == null) {
- return;
- }
-
- ViewStub stub = (ViewStub)rawContactView.findViewById(R.id.aggregation_suggestion_stub);
- if (stub != null) {
- stub.inflate();
- }
-
- // Only request the view on screen when it is first displayed
- boolean requestOnScreen = mAggregationSuggestionView == null;
- mAggregationSuggestionView = rawContactView.findViewById(R.id.aggregation_suggestion);
-
- int count = mAggregationSuggestionEngine.getSuggestedContactCount();
- if (count == 0) {
- mAggregationSuggestionView.setVisibility(View.GONE);
- return;
- }
-
- List<Suggestion> suggestions = mAggregationSuggestionEngine.getSuggestions();
-
- LinearLayout itemList = (LinearLayout) mAggregationSuggestionView.findViewById(
- R.id.aggregation_suggestions);
- itemList.removeAllViews();
-
- LayoutInflater inflater = getActivity().getLayoutInflater();
-
- for (Suggestion suggestion : suggestions) {
- AggregationSuggestionView suggestionView =
- (AggregationSuggestionView) inflater.inflate(
- R.layout.aggregation_suggestions_item, null);
- suggestionView.setLayoutParams(
- new LinearLayout.LayoutParams(
- LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
- suggestionView.setNewContact(mState.size() == 1 && mState.get(0).isContactInsert());
- suggestionView.setListener(this);
- suggestionView.bindSuggestion(suggestion);
- itemList.addView(suggestionView);
- }
-
- adjustAggregationSuggestionViewLayout(rawContactView);
- setAggregationSuggestionViewEnabled(mEnabled);
- mAggregationSuggestionView.setVisibility(View.VISIBLE);
-
- if (requestOnScreen) {
- mContent.postDelayed(new Runnable() {
-
- @Override
- public void run() {
- requestAggregationSuggestionOnScreen(mAggregationSuggestionView);
- }
- }, AGGREGATION_SUGGESTION_SCROLL_DELAY);
- }
- }
-
- /**
- * Adjusts the layout of the aggregation suggestion view so that it is placed directly
- * underneath and have the same width as the last text editor of the contact name editor.
- */
- private void adjustAggregationSuggestionViewLayout(RawContactEditorView rawContactView) {
- TextFieldsEditorView nameEditor = rawContactView.getNameEditor();
- Rect rect = new Rect();
- nameEditor.acquireEditorBounds(rect);
- MarginLayoutParams layoutParams =
- (MarginLayoutParams) mAggregationSuggestionView.getLayoutParams();
- layoutParams.leftMargin = rect.left;
- layoutParams.width = rect.width();
- mAggregationSuggestionView.setLayoutParams(layoutParams);
+ final View anchorView = rawContactView.findViewById(R.id.anchor_view);
+ mAggregationSuggestionPopup = new ListPopupWindow(mContext, null);
+ mAggregationSuggestionPopup.setAnchorView(anchorView);
+ mAggregationSuggestionPopup.setWidth(anchorView.getWidth());
+ mAggregationSuggestionPopup.setInputMethodMode(ListPopupWindow.INPUT_METHOD_NOT_NEEDED);
+ mAggregationSuggestionPopup.setModal(true);
+ mAggregationSuggestionPopup.setAdapter(
+ new AggregationSuggestionAdapter(getActivity(),
+ mState.size() == 1 && mState.get(0).isContactInsert(),
+ this, mAggregationSuggestionEngine.getSuggestions()));
+ mAggregationSuggestionPopup.setOnItemClickListener(mAggregationSuggestionItemClickListener);
+ mAggregationSuggestionPopup.show();
}
@Override
@@ -1452,20 +1451,6 @@
}
}
- /**
- * Scrolls the editor if necessary to reveal the aggregation suggestion that is
- * shown below the name editor. Makes sure that the currently focused field
- * remains visible.
- */
- private void requestAggregationSuggestionOnScreen(final View view) {
- Rect rect = getRelativeBounds(mContent, view);
- View focused = mContent.findFocus();
- if (focused != null) {
- rect.union(getRelativeBounds(mContent, focused));
- }
- mContent.requestRectangleOnScreen(rect);
- }
-
public void setAggregationSuggestionViewEnabled(boolean enabled) {
if (mAggregationSuggestionView == null) {
return;
diff --git a/src/com/android/contacts/model/AccountTypeManager.java b/src/com/android/contacts/model/AccountTypeManager.java
index eccf44b..b517c2c 100644
--- a/src/com/android/contacts/model/AccountTypeManager.java
+++ b/src/com/android/contacts/model/AccountTypeManager.java
@@ -69,6 +69,7 @@
* the available authenticators. This method can safely be called from the UI thread.
*/
public static AccountTypeManager getInstance(Context context) {
+ context = context.getApplicationContext();
AccountTypeManager service =
(AccountTypeManager) context.getSystemService(ACCOUNT_TYPE_SERVICE);
if (service == null) {
diff --git a/src/com/android/contacts/vcard/CancelActivity.java b/src/com/android/contacts/vcard/CancelActivity.java
index b1428bb..c890607 100644
--- a/src/com/android/contacts/vcard/CancelActivity.java
+++ b/src/com/android/contacts/vcard/CancelActivity.java
@@ -72,7 +72,6 @@
private int mJobId;
private String mDisplayName;
private int mType;
- private Messenger mMessenger;
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -121,29 +120,21 @@
}
@Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- mMessenger = new Messenger(service);
+ public void onServiceConnected(ComponentName name, IBinder binder) {
+ VCardService service = ((VCardService.MyBinder) binder).getService();
- boolean callFinish = false;
try {
final CancelRequest request = new CancelRequest(mJobId, mDisplayName);
- mMessenger.send(Message.obtain(null, VCardService.MSG_CANCEL_REQUEST, request));
- callFinish = true;
- } catch (RemoteException e) {
- Log.e(LOG_TAG, "RemoteException is thrown when trying to send request");
- showDialog(R.id.dialog_cancel_failed);
- // finish() should be called from the Dialog
+ service.handleCancelRequest(request, null);
} finally {
unbindService(this);
}
- if (callFinish) {
- finish();
- }
+ finish();
}
@Override
public void onServiceDisconnected(ComponentName name) {
- mMessenger = null;
+ // do nothing
}
}
diff --git a/src/com/android/contacts/vcard/ExportProcessor.java b/src/com/android/contacts/vcard/ExportProcessor.java
index 6dc2c34..b52839b 100644
--- a/src/com/android/contacts/vcard/ExportProcessor.java
+++ b/src/com/android/contacts/vcard/ExportProcessor.java
@@ -235,9 +235,11 @@
final String tickerText =
mService.getString(R.string.exporting_contact_list_title);
final Notification notification =
- VCardService.constructProgressNotification(mService, VCardService.TYPE_EXPORT,
- description, tickerText, mJobId, displayName, totalCount, currentCount);
- mNotificationManager.notify(VCardService.DEFAULT_NOTIFICATION_TAG, mJobId, notification);
+ NotificationImportExportListener.constructProgressNotification(mService,
+ VCardService.TYPE_EXPORT, description, tickerText, mJobId, displayName,
+ totalCount, currentCount);
+ mNotificationManager.notify(NotificationImportExportListener.DEFAULT_NOTIFICATION_TAG,
+ mJobId, notification);
}
private void doCancelNotification() {
@@ -245,16 +247,19 @@
final String description = mService.getString(R.string.exporting_vcard_canceled_title,
mExportRequest.destUri.getLastPathSegment());
final Notification notification =
- VCardService.constructCancelNotification(mService, description);
- mNotificationManager.notify(VCardService.DEFAULT_NOTIFICATION_TAG, mJobId, notification);
+ NotificationImportExportListener.constructCancelNotification(mService, description);
+ mNotificationManager.notify(NotificationImportExportListener.DEFAULT_NOTIFICATION_TAG,
+ mJobId, notification);
}
private void doFinishNotification(final String title, final String description) {
if (DEBUG) Log.d(LOG_TAG, "send finish notification: " + title + ", " + description);
final Intent intent = new Intent(mService, PeopleActivity.class);
final Notification notification =
- VCardService.constructFinishNotification(mService, title, description, intent);
- mNotificationManager.notify(VCardService.DEFAULT_NOTIFICATION_TAG, mJobId, notification);
+ NotificationImportExportListener.constructFinishNotification(mService, title,
+ description, intent);
+ mNotificationManager.notify(NotificationImportExportListener.DEFAULT_NOTIFICATION_TAG,
+ mJobId, notification);
}
@Override
diff --git a/src/com/android/contacts/vcard/ExportVCardActivity.java b/src/com/android/contacts/vcard/ExportVCardActivity.java
index 8bd9899..a0fd4d8 100644
--- a/src/com/android/contacts/vcard/ExportVCardActivity.java
+++ b/src/com/android/contacts/vcard/ExportVCardActivity.java
@@ -110,7 +110,7 @@
*/
private volatile boolean mProcessOngoing = true;
- private Messenger mOutgoingMessenger;
+ private VCardService mService;
private final Messenger mIncomingMessenger = new Messenger(new IncomingHandler());
// Used temporarily when asking users to confirm the file name
@@ -138,10 +138,8 @@
}
final ExportRequest request = new ExportRequest(mDestinationUri);
// The connection object will call finish().
- if (trySend(Message.obtain(null, VCardService.MSG_EXPORT_REQUEST, request))) {
- Log.i(LOG_TAG, "Successfully sent export request. Finish itself");
- unbindAndFinish();
- }
+ mService.handleExportRequest(request, new NotificationImportExportListener(
+ ExportVCardActivity.this));
}
}
}
@@ -160,14 +158,16 @@
return;
}
- if (startService(new Intent(this, VCardService.class)) == null) {
+ Intent intent = new Intent(this, VCardService.class);
+
+ if (startService(intent) == null) {
Log.e(LOG_TAG, "Failed to start vCard service");
mErrorReason = getString(R.string.fail_reason_unknown);
showDialog(R.id.dialog_fail_to_export_with_reason);
return;
}
- if (!bindService(new Intent(this, VCardService.class), this, Context.BIND_AUTO_CREATE)) {
+ if (!bindService(intent, this, Context.BIND_AUTO_CREATE)) {
Log.e(LOG_TAG, "Failed to connect to vCard service.");
mErrorReason = getString(R.string.fail_reason_unknown);
showDialog(R.id.dialog_fail_to_export_with_reason);
@@ -176,14 +176,11 @@
}
@Override
- public synchronized void onServiceConnected(ComponentName name, IBinder service) {
+ public synchronized void onServiceConnected(ComponentName name, IBinder binder) {
if (DEBUG) Log.d(LOG_TAG, "connected to service, requesting a destination file name");
mConnected = true;
- mOutgoingMessenger = new Messenger(service);
- final Message message =
- Message.obtain(null, VCardService.MSG_REQUEST_AVAILABLE_EXPORT_DESTINATION);
- message.replyTo = mIncomingMessenger;
- trySend(message);
+ mService = ((VCardService.MyBinder) binder).getService();
+ mService.handleRequestAvailableExportDestination(mIncomingMessenger);
// Wait until MSG_SET_AVAILABLE_EXPORT_DESTINATION message is available.
}
@@ -191,7 +188,7 @@
@Override
public synchronized void onServiceDisconnected(ComponentName name) {
if (DEBUG) Log.d(LOG_TAG, "onServiceDisconnected()");
- mOutgoingMessenger = null;
+ mService = null;
mConnected = false;
if (mProcessOngoing) {
// Unexpected disconnect event.
@@ -267,19 +264,6 @@
}
}
- private boolean trySend(Message message) {
- try {
- mOutgoingMessenger.send(message);
- return true;
- } catch (RemoteException e) {
- Log.e(LOG_TAG, "RemoteException is thrown when trying to send request");
- unbindService(this);
- mErrorReason = getString(R.string.fail_reason_unknown);
- showDialog(R.id.dialog_fail_to_export_with_reason);
- return false;
- }
- }
-
@Override
public void onClick(DialogInterface dialog, int which) {
if (DEBUG) Log.d(LOG_TAG, "ExportVCardActivity#onClick() is called");
diff --git a/src/com/android/contacts/vcard/ImportProcessor.java b/src/com/android/contacts/vcard/ImportProcessor.java
index 3092087..e8a9193 100644
--- a/src/com/android/contacts/vcard/ImportProcessor.java
+++ b/src/com/android/contacts/vcard/ImportProcessor.java
@@ -15,9 +15,10 @@
*/
package com.android.contacts.vcard;
-import com.android.contacts.R;
+import com.android.vcard.VCardEntry;
import com.android.vcard.VCardEntryCommitter;
import com.android.vcard.VCardEntryConstructor;
+import com.android.vcard.VCardEntryHandler;
import com.android.vcard.VCardInterpreter;
import com.android.vcard.VCardParser;
import com.android.vcard.VCardParser_V21;
@@ -28,14 +29,8 @@
import com.android.vcard.exception.VCardVersionException;
import android.accounts.Account;
-import android.app.Notification;
-import android.app.NotificationManager;
import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.Context;
-import android.content.Intent;
import android.net.Uri;
-import android.provider.ContactsContract.RawContacts;
import android.util.Log;
import java.io.ByteArrayInputStream;
@@ -48,17 +43,15 @@
* Class for processing one import request from a user. Dropped after importing requested Uri(s).
* {@link VCardService} will create another object when there is another import request.
*/
-public class ImportProcessor extends ProcessorBase {
+public class ImportProcessor extends ProcessorBase implements VCardEntryHandler {
private static final String LOG_TAG = "VCardImport";
private static final boolean DEBUG = VCardService.DEBUG;
private final VCardService mService;
private final ContentResolver mResolver;
- private final NotificationManager mNotificationManager;
private final ImportRequest mImportRequest;
private final int mJobId;
-
- private final ImportProgressNotifier mNotifier;
+ private final VCardImportExportListener mListener;
// TODO: remove and show appropriate message instead.
private final List<Uri> mFailedUris = new ArrayList<Uri>();
@@ -68,17 +61,35 @@
private volatile boolean mCanceled;
private volatile boolean mDone;
- public ImportProcessor(final VCardService service, final ImportRequest request,
- final int jobId) {
+ private int mCurrentCount = 0;
+ private int mTotalCount = 0;
+
+ public ImportProcessor(final VCardService service, final VCardImportExportListener listener,
+ final ImportRequest request, final int jobId) {
mService = service;
mResolver = mService.getContentResolver();
- mNotificationManager = (NotificationManager)
- mService.getSystemService(Context.NOTIFICATION_SERVICE);
+ mListener = listener;
mImportRequest = request;
mJobId = jobId;
- mNotifier = new ImportProgressNotifier(service, mNotificationManager, jobId,
- request.displayName);
+ }
+
+ @Override
+ public void onStart() {
+ // do nothing
+ }
+
+ @Override
+ public void onEnd() {
+ // do nothing
+ }
+
+ @Override
+ public void onEntryCreated(VCardEntry entry) {
+ mCurrentCount++;
+ if (mListener != null) {
+ mListener.onImportParsed(mImportRequest, mJobId, entry, mCurrentCount, mTotalCount);
+ }
}
@Override
@@ -92,8 +103,8 @@
try {
runInternal();
- if (isCancelled()) {
- doCancelNotification();
+ if (isCancelled() && mListener != null) {
+ mListener.onImportCanceled(mImportRequest, mJobId);
}
} catch (OutOfMemoryError e) {
Log.e(LOG_TAG, "OutOfMemoryError thrown during import", e);
@@ -136,15 +147,13 @@
final int estimatedVCardType = request.estimatedVCardType;
final String estimatedCharset = request.estimatedCharset;
final int entryCount = request.entryCount;
- mNotifier.addTotalCount(entryCount);
+ mTotalCount += entryCount;
final VCardEntryConstructor constructor =
new VCardEntryConstructor(estimatedVCardType, account, estimatedCharset);
final VCardEntryCommitter committer = new VCardEntryCommitter(mResolver);
constructor.addEntryHandler(committer);
- if (!request.showImmediately) {
- constructor.addEntryHandler(mNotifier);
- }
+ constructor.addEntryHandler(this);
InputStream is = null;
boolean successful = false;
@@ -184,15 +193,17 @@
} else {
Log.i(LOG_TAG, "Successfully finished importing one vCard file: " + uri);
List<Uri> uris = committer.getCreatedUris();
- if (uris != null && uris.size() > 0) {
- // TODO: construct intent showing a list of imported contact list.
- doFinishNotification(uris.get(0));
- } else {
- // Not critical, but suspicious.
- Log.w(LOG_TAG,
- "Created Uris is null or 0 length " +
- "though the creation itself is successful.");
- doFinishNotification(null);
+ if (mListener != null) {
+ if (uris != null && uris.size() > 0) {
+ // TODO: construct intent showing a list of imported contact list.
+ mListener.onImportFinished(mImportRequest, mJobId, uris.get(0));
+ } else {
+ // Not critical, but suspicious.
+ Log.w(LOG_TAG,
+ "Created Uris is null or 0 length " +
+ "though the creation itself is successful.");
+ mListener.onImportFinished(mImportRequest, mJobId, null);
+ }
}
}
} else {
@@ -201,39 +212,6 @@
}
}
- private void doCancelNotification() {
- final String description = mService.getString(R.string.importing_vcard_canceled_title,
- mImportRequest.displayName);
- final Notification notification =
- VCardService.constructCancelNotification(mService, description);
- mNotificationManager.notify(VCardService.DEFAULT_NOTIFICATION_TAG, mJobId, notification);
- }
-
- private void doFinishNotification(final Uri createdUri) {
- final String description = mService.getString(R.string.importing_vcard_finished_title,
- mImportRequest.displayName);
- final Intent intent;
- if (createdUri != null) {
- final long rawContactId = ContentUris.parseId(createdUri);
- final Uri contactUri = RawContacts.getContactLookupUri(
- mResolver, ContentUris.withAppendedId(
- RawContacts.CONTENT_URI, rawContactId));
- intent = new Intent(Intent.ACTION_VIEW, contactUri);
- } else {
- intent = null;
- }
- if (mImportRequest.showImmediately && (intent != null)) {
- mNotificationManager.cancel(VCardService.DEFAULT_NOTIFICATION_TAG, mJobId);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- mService.startActivity(intent);
- } else {
- final Notification notification = VCardService.constructFinishNotification(mService,
- description, null, intent);
- mNotificationManager.notify(VCardService.DEFAULT_NOTIFICATION_TAG, mJobId,
- notification);
- }
- }
-
private boolean readOneVCard(InputStream is, int vcardType, String charset,
final VCardInterpreter interpreter,
final int[] possibleVCardVersions) {
diff --git a/src/com/android/contacts/vcard/ImportProgressNotifier.java b/src/com/android/contacts/vcard/ImportProgressNotifier.java
deleted file mode 100644
index 698487d..0000000
--- a/src/com/android/contacts/vcard/ImportProgressNotifier.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.contacts.vcard;
-
-import com.android.contacts.R;
-import com.android.vcard.VCardEntry;
-import com.android.vcard.VCardEntryHandler;
-
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.content.Context;
-
-/**
- * {@link VCardEntryHandler} implementation letting the system update the current status of
- * vCard import.
- */
-public class ImportProgressNotifier implements VCardEntryHandler {
- private static final String LOG_TAG = "VCardImport";
-
- private final Context mContext;
- private final NotificationManager mNotificationManager;
- private final int mJobId;
- private final String mDisplayName;
-
- private int mCurrentCount;
- private int mTotalCount;
-
- public ImportProgressNotifier(
- Context context, NotificationManager notificationManager,
- int jobId, String displayName) {
- mContext = context;
- mNotificationManager = notificationManager;
- mJobId = jobId;
- mDisplayName = displayName;
- }
-
- public void onStart() {
- }
-
- public void onEntryCreated(VCardEntry contactStruct) {
- mCurrentCount++; // 1 origin.
- if (contactStruct.isIgnorable()) {
- return;
- }
-
- final String totalCountString;
- synchronized (this) {
- totalCountString = String.valueOf(mTotalCount);
- }
- final String tickerText =
- mContext.getString(R.string.progress_notifier_message,
- String.valueOf(mCurrentCount),
- totalCountString,
- contactStruct.getDisplayName());
- final String description = mContext.getString(R.string.importing_vcard_description,
- contactStruct.getDisplayName());
-
- final Notification notification = VCardService.constructProgressNotification(
- mContext.getApplicationContext(), VCardService.TYPE_IMPORT, description, tickerText,
- mJobId, mDisplayName, mTotalCount, mCurrentCount);
- mNotificationManager.notify(VCardService.DEFAULT_NOTIFICATION_TAG, mJobId, notification);
- }
-
- public synchronized void addTotalCount(int additionalCount) {
- mTotalCount += additionalCount;
- }
-
- public void onEnd() {
- }
-}
\ No newline at end of file
diff --git a/src/com/android/contacts/vcard/ImportRequest.java b/src/com/android/contacts/vcard/ImportRequest.java
index 84fbb0e..68b5424 100644
--- a/src/com/android/contacts/vcard/ImportRequest.java
+++ b/src/com/android/contacts/vcard/ImportRequest.java
@@ -56,12 +56,6 @@
public final String displayName;
/**
- * Whether to show the imported vcard immediately after the import is done.
- * If set to false, just a notification will be shown.
- */
- public final boolean showImmediately;
-
- /**
* Can be {@link VCardSourceDetector#PARSE_TYPE_UNKNOWN}.
*/
public final int estimatedVCardType;
@@ -103,7 +97,7 @@
public ImportRequest(Account account,
byte[] data, Uri uri, String displayName, int estimatedType, String estimatedCharset,
- int vcardVersion, int entryCount, boolean showImmediately) {
+ int vcardVersion, int entryCount) {
this.account = account;
this.data = data;
this.uri = uri;
@@ -112,6 +106,5 @@
this.estimatedCharset = estimatedCharset;
this.vcardVersion = vcardVersion;
this.entryCount = entryCount;
- this.showImmediately = showImmediately;
}
}
diff --git a/src/com/android/contacts/vcard/ImportVCardActivity.java b/src/com/android/contacts/vcard/ImportVCardActivity.java
index 28b8540..2baa9cb 100644
--- a/src/com/android/contacts/vcard/ImportVCardActivity.java
+++ b/src/com/android/contacts/vcard/ImportVCardActivity.java
@@ -128,6 +128,7 @@
private VCardCacheThread mVCardCacheThread;
private ImportRequestConnection mConnection;
+ /* package */ VCardImportExportListener mListener;
private String mErrorMessage;
@@ -188,23 +189,16 @@
private CancelListener mCancelListener = new CancelListener();
private class ImportRequestConnection implements ServiceConnection {
- private Messenger mMessenger;
+ private VCardService mService;
public void sendImportRequest(final List<ImportRequest> requests) {
Log.i(LOG_TAG, "Send an import request");
- try {
- mMessenger.send(Message.obtain(null,
- VCardService.MSG_IMPORT_REQUEST,
- requests));
- } catch (RemoteException e) {
- Log.e(LOG_TAG, "RemoteException is thrown when trying to send request");
- runOnUiThread(new DialogDisplayer(getString(R.string.fail_reason_unknown)));
- }
+ mService.handleImportRequest(requests, mListener);
}
@Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- mMessenger = new Messenger(service);
+ public void onServiceConnected(ComponentName name, IBinder binder) {
+ mService = ((VCardService.MyBinder) binder).getService();
Log.i(LOG_TAG,
String.format("Connected to VCardService. Kick a vCard cache thread (uri: %s)",
Arrays.toString(mVCardCacheThread.getSourceUris())));
@@ -233,7 +227,6 @@
private final Uri[] mSourceUris; // Given from a caller.
private final byte[] mSource;
private final String mDisplayName;
- private final boolean mShowImmediately;
public VCardCacheThread(final Uri[] sourceUris) {
mSourceUris = sourceUris;
@@ -245,24 +238,6 @@
PowerManager.SCREEN_DIM_WAKE_LOCK |
PowerManager.ON_AFTER_RELEASE, LOG_TAG);
mDisplayName = null;
- // Showing immediately could make sense here if we restrict
- // it to cases where we import a single vcard. For now disable
- // this feature though.
- mShowImmediately = false;
- }
-
- public VCardCacheThread(final byte[] data, String displayName,
- final boolean showImmediately) {
- mSource = data;
- mSourceUris = null;
- final Context context = ImportVCardActivity.this;
- final PowerManager powerManager =
- (PowerManager)context.getSystemService(Context.POWER_SERVICE);
- mWakeLock = powerManager.newWakeLock(
- PowerManager.SCREEN_DIM_WAKE_LOCK |
- PowerManager.ON_AFTER_RELEASE, LOG_TAG);
- mDisplayName = displayName;
- mShowImmediately = showImmediately;
}
@Override
@@ -301,8 +276,7 @@
ArrayList<ImportRequest> requests = new ArrayList<ImportRequest>();
if (mSource != null) {
try {
- requests.add(constructImportRequest(mSource, null, mDisplayName,
- mShowImmediately));
+ requests.add(constructImportRequest(mSource, null, mDisplayName));
} catch (VCardException e) {
Log.e(LOG_TAG, "Maybe the file is in wrong format", e);
showFailureNotification(R.string.fail_reason_not_supported);
@@ -336,7 +310,7 @@
final ImportRequest request;
try {
request = constructImportRequest(null, localDataUri,
- sourceUri.getLastPathSegment(), mShowImmediately);
+ sourceUri.getLastPathSegment());
} catch (VCardException e) {
Log.e(LOG_TAG, "Maybe the file is in wrong format", e);
showFailureNotification(R.string.fail_reason_not_supported);
@@ -436,8 +410,7 @@
* {@link ImportRequest#displayName}.
*/
private ImportRequest constructImportRequest(final byte[] data,
- final Uri localDataUri, final String displayName,
- final boolean showImmediately)
+ final Uri localDataUri, final String displayName)
throws IOException, VCardException {
final ContentResolver resolver = ImportVCardActivity.this.getContentResolver();
VCardEntryCounter counter = null;
@@ -499,8 +472,7 @@
data, localDataUri, displayName,
detector.getEstimatedType(),
detector.getEstimatedCharset(),
- vcardVersion, counter.getCount(),
- showImmediately);
+ vcardVersion, counter.getCount());
}
public Uri[] getSourceUris() {
@@ -760,19 +732,10 @@
private void importVCard(final Uri[] uris) {
runOnUiThread(new Runnable() {
+ @Override
public void run() {
mVCardCacheThread = new VCardCacheThread(uris);
- showDialog(R.id.dialog_cache_vcard);
- }
- });
- }
-
- private void importVCard(final byte[] data, final String displayName,
- final boolean showImmediately) {
- runOnUiThread(new Runnable() {
- public void run() {
- mVCardCacheThread = new VCardCacheThread(data, displayName,
- showImmediately);
+ mListener = new NotificationImportExportListener(ImportVCardActivity.this);
showDialog(R.id.dialog_cache_vcard);
}
});
@@ -888,32 +851,14 @@
private void startImport() {
Intent intent = getIntent();
- if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(intent.getAction())) {
- // Handle inbound NDEF
- NdefMessage msg = (NdefMessage) intent.getParcelableArrayExtra(
- NfcAdapter.EXTRA_NDEF_MESSAGES)[0];
- NdefRecord record = msg.getRecords()[0];
- String type = new String(record.getType(), Charset.forName("UTF8"));
- if (record.getTnf() != NdefRecord.TNF_MIME_MEDIA ||
- (!"text/x-vcard".equalsIgnoreCase(type) && !"text/vcard".equals(type))) {
- Log.d(LOG_TAG, "Not a vcard");
- showFailureNotification(R.string.fail_reason_not_supported);
- finish();
- return;
- }
- // For NFC imports, we always show the contact once import is
- // complete.
- importVCard(record.getPayload(), getString(R.string.nfc_vcard_file_name), true);
+ // Handle inbound files
+ Uri uri = intent.getData();
+ if (uri != null) {
+ Log.i(LOG_TAG, "Starting vCard import using Uri " + uri);
+ importVCard(uri);
} else {
- // Handle inbound files
- Uri uri = intent.getData();
- if (uri != null) {
- Log.i(LOG_TAG, "Starting vCard import using Uri " + uri);
- importVCard(uri);
- } else {
- Log.i(LOG_TAG, "Start vCard without Uri. The user will select vCard manually.");
- doScanExternalStorageAndImportVCard();
- }
+ Log.i(LOG_TAG, "Start vCard without Uri. The user will select vCard manually.");
+ doScanExternalStorageAndImportVCard();
}
}
@@ -975,13 +920,7 @@
mProgressDialogForCachingVCard.setMessage(message);
mProgressDialogForCachingVCard.setProgressStyle(ProgressDialog.STYLE_SPINNER);
mProgressDialogForCachingVCard.setOnCancelListener(mVCardCacheThread);
- mConnection = new ImportRequestConnection();
-
- Log.i(LOG_TAG, "Bind to VCardService.");
- // We don't want the service finishes itself just after this connection.
- startService(new Intent(this, VCardService.class));
- bindService(new Intent(this, VCardService.class),
- mConnection, Context.BIND_AUTO_CREATE);
+ startVCardService();
}
return mProgressDialogForCachingVCard;
}
@@ -1015,6 +954,17 @@
return super.onCreateDialog(resId, bundle);
}
+ /* package */ void startVCardService() {
+ mConnection = new ImportRequestConnection();
+
+ Log.i(LOG_TAG, "Bind to VCardService.");
+ // We don't want the service finishes itself just after this connection.
+ Intent intent = new Intent(this, VCardService.class);
+ startService(intent);
+ bindService(new Intent(this, VCardService.class),
+ mConnection, Context.BIND_AUTO_CREATE);
+ }
+
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
@@ -1052,11 +1002,11 @@
final NotificationManager notificationManager =
(NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
final Notification notification =
- VCardService.constructImportFailureNotification(
+ NotificationImportExportListener.constructImportFailureNotification(
ImportVCardActivity.this,
getString(reasonId));
- notificationManager.notify(VCardService.FAILURE_NOTIFICATION_TAG, FAILURE_NOTIFICATION_ID,
- notification);
+ notificationManager.notify(NotificationImportExportListener.FAILURE_NOTIFICATION_TAG,
+ FAILURE_NOTIFICATION_ID, notification);
mHandler.post(new Runnable() {
@Override
public void run() {
diff --git a/src/com/android/contacts/vcard/NfcImportVCardActivity.java b/src/com/android/contacts/vcard/NfcImportVCardActivity.java
new file mode 100644
index 0000000..d182cfd
--- /dev/null
+++ b/src/com/android/contacts/vcard/NfcImportVCardActivity.java
@@ -0,0 +1,272 @@
+/*
+ * 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.vcard;
+
+import com.android.contacts.R;
+import com.android.contacts.model.AccountTypeManager;
+import com.android.contacts.model.AccountWithDataSet;
+import com.android.vcard.VCardEntry;
+import com.android.vcard.VCardEntryCounter;
+import com.android.vcard.VCardParser;
+import com.android.vcard.VCardParser_V21;
+import com.android.vcard.VCardParser_V30;
+import com.android.vcard.VCardSourceDetector;
+import com.android.vcard.exception.VCardException;
+import com.android.vcard.exception.VCardNestedException;
+import com.android.vcard.exception.VCardVersionException;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.net.Uri;
+import android.nfc.NdefMessage;
+import android.nfc.NdefRecord;
+import android.nfc.NfcAdapter;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.provider.ContactsContract.RawContacts;
+import android.util.Log;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.List;
+
+public class NfcImportVCardActivity extends Activity implements ServiceConnection,
+ VCardImportExportListener {
+ private static final String TAG = "NfcImportVCardActivity";
+
+ private static final int SELECT_ACCOUNT = 1;
+
+ private NdefRecord mRecord;
+ private AccountWithDataSet mAccount;
+
+ /* package */ class ImportTask extends AsyncTask<VCardService, Void, ImportRequest> {
+ @Override
+ public ImportRequest doInBackground(VCardService... services) {
+ ImportRequest request = createImportRequest();
+ if (request == null) {
+ return null;
+ }
+
+ ArrayList<ImportRequest> requests = new ArrayList<ImportRequest>();
+ requests.add(request);
+ services[0].handleImportRequest(requests, NfcImportVCardActivity.this);
+ return request;
+ }
+
+ @Override
+ public void onCancelled() {
+ unbindService(NfcImportVCardActivity.this);
+ }
+
+ @Override
+ public void onPostExecute(ImportRequest request) {
+ unbindService(NfcImportVCardActivity.this);
+ }
+ }
+
+ /* package */ ImportRequest createImportRequest() {
+ VCardParser parser;
+ VCardEntryCounter counter = null;
+ VCardSourceDetector detector = null;
+ int vcardVersion = ImportVCardActivity.VCARD_VERSION_V21;
+ try {
+ ByteArrayInputStream is = new ByteArrayInputStream(mRecord.getPayload());
+ is.mark(0);
+ parser = new VCardParser_V21();
+ try {
+ counter = new VCardEntryCounter();
+ detector = new VCardSourceDetector();
+ parser.addInterpreter(counter);
+ parser.addInterpreter(detector);
+ parser.parse(is);
+ } catch (VCardVersionException e1) {
+ is.reset();
+ vcardVersion = ImportVCardActivity.VCARD_VERSION_V30;
+ parser = new VCardParser_V30();
+ try {
+ counter = new VCardEntryCounter();
+ detector = new VCardSourceDetector();
+ parser.addInterpreter(counter);
+ parser.addInterpreter(detector);
+ parser.parse(is);
+ } catch (VCardVersionException e2) {
+ return null;
+ }
+ } finally {
+ try {
+ if (is != null) is.close();
+ } catch (IOException e) {
+ }
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Failed reading vcard data", e);
+ return null;
+ } catch (VCardNestedException e) {
+ Log.w(TAG, "Nested Exception is found (it may be false-positive).");
+ // Go through without throwing the Exception, as we may be able to detect the
+ // version before it
+ } catch (VCardException e) {
+ Log.e(TAG, "Error parsing vcard", e);
+ return null;
+ }
+
+ return new ImportRequest(mAccount, mRecord.getPayload(), null,
+ getString(R.string.nfc_vcard_file_name), detector.getEstimatedType(),
+ detector.getEstimatedCharset(), vcardVersion, counter.getCount());
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder binder) {
+ VCardService service = ((VCardService.MyBinder) binder).getService();
+ new ImportTask().execute(service);
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ // Do nothing
+ }
+
+ @Override
+ protected void onCreate(Bundle bundle) {
+ super.onCreate(bundle);
+
+ Intent intent = getIntent();
+ if (!NfcAdapter.ACTION_NDEF_DISCOVERED.equals(intent.getAction())) {
+ Log.w(TAG, "Unknowon intent " + intent);
+ finish();
+ }
+
+ NdefMessage msg = (NdefMessage) intent.getParcelableArrayExtra(
+ NfcAdapter.EXTRA_NDEF_MESSAGES)[0];
+ NdefRecord records[] = msg.getRecords();
+ if (records == null || records.length == 0) {
+ Log.w(TAG, "No records " + intent);
+ finish();
+ }
+
+ NdefRecord record = records[0];
+ String type = new String(record.getType(), Charset.forName("UTF8"));
+ if (record.getTnf() != NdefRecord.TNF_MIME_MEDIA ||
+ (!"text/x-vcard".equalsIgnoreCase(type) && !"text/vcard".equals(type))) {
+ Log.w(TAG, "Not a vcard");
+ //setStatus(getString(R.string.fail_reason_not_supported));
+ return;
+ }
+ mRecord = record;
+
+ final AccountTypeManager accountTypes = AccountTypeManager.getInstance(this);
+ final List<AccountWithDataSet> accountList = accountTypes.getAccounts(true);
+ if (accountList.size() == 0) {
+ mAccount = null;
+ } else if (accountList.size() == 1) {
+ mAccount = accountList.get(0);
+ } else {
+ startActivityForResult(new Intent(this, SelectAccountActivity.class), SELECT_ACCOUNT);
+ return;
+ }
+
+ startImport();
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent intent) {
+ if (requestCode == SELECT_ACCOUNT) {
+ if (resultCode == RESULT_OK) {
+ mAccount = new AccountWithDataSet(
+ intent.getStringExtra(SelectAccountActivity.ACCOUNT_NAME),
+ intent.getStringExtra(SelectAccountActivity.ACCOUNT_TYPE),
+ intent.getStringExtra(SelectAccountActivity.DATA_SET));
+ startImport();
+ } else {
+ finish();
+ }
+ }
+ }
+
+ private void startImport() {
+ // We don't want the service finishes itself just after this connection.
+ Intent intent = new Intent(this, VCardService.class);
+ startService(intent);
+ bindService(intent, this, Context.BIND_AUTO_CREATE);
+ }
+
+ @Override
+ public void onImportProcessed(ImportRequest request, int jobId, int sequence) {
+ // do nothing
+ }
+
+ @Override
+ public void onImportParsed(ImportRequest request, int jobId, VCardEntry entry, int currentCount,
+ int totalCount) {
+ // do nothing
+ }
+
+ @Override
+ public void onImportFinished(ImportRequest request, int jobId, Uri uri) {
+ if (isFinishing()) {
+ Log.i(TAG, "Late import -- ignoring");
+ return;
+ }
+
+ if (uri != null) {
+ Uri contactUri = RawContacts.getContactLookupUri(getContentResolver(), uri);
+ Intent intent = new Intent(Intent.ACTION_VIEW, contactUri);
+ startActivity(intent);
+ finish();
+ }
+ }
+
+ @Override
+ public void onImportFailed(ImportRequest request) {
+ if (isFinishing()) {
+ Log.i(TAG, "Late import failure -- ignoring");
+ return;
+ }
+ // TODO: report failure
+ }
+
+ @Override
+ public void onImportCanceled(ImportRequest request, int jobId) {
+ // do nothing
+ }
+
+ @Override
+ public void onExportProcessed(ExportRequest request, int jobId) {
+ // do nothing
+ }
+
+ @Override
+ public void onExportFailed(ExportRequest request) {
+ // do nothing
+ }
+
+ @Override
+ public void onCancelRequest(CancelRequest request, int type) {
+ // do nothing
+ }
+
+ @Override
+ public void onComplete() {
+ // do nothing
+ }
+}
diff --git a/src/com/android/contacts/vcard/NotificationImportExportListener.java b/src/com/android/contacts/vcard/NotificationImportExportListener.java
new file mode 100644
index 0000000..fb83cec
--- /dev/null
+++ b/src/com/android/contacts/vcard/NotificationImportExportListener.java
@@ -0,0 +1,302 @@
+/*
+ * 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.vcard;
+
+import com.android.contacts.R;
+import com.android.vcard.VCardEntry;
+
+import android.app.Activity;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Message;
+import android.provider.ContactsContract.RawContacts;
+import android.widget.RemoteViews;
+import android.widget.Toast;
+
+public class NotificationImportExportListener implements VCardImportExportListener,
+ Handler.Callback {
+ /** The tag used by vCard-related notifications. */
+ /* package */ static final String DEFAULT_NOTIFICATION_TAG = "VCardServiceProgress";
+ /**
+ * The tag used by vCard-related failure notifications.
+ * <p>
+ * Use a different tag from {@link #DEFAULT_NOTIFICATION_TAG} so that failures do not get
+ * replaced by other notifications and vice-versa.
+ */
+ /* package */ static final String FAILURE_NOTIFICATION_TAG = "VCardServiceFailure";
+
+ private final NotificationManager mNotificationManager;
+ private final Activity mContext;
+ private final Handler mHandler;
+
+ public NotificationImportExportListener(Activity activity) {
+ mContext = activity;
+ mNotificationManager = (NotificationManager) activity.getSystemService(
+ Context.NOTIFICATION_SERVICE);
+ mHandler = new Handler(this);
+ }
+
+ @Override
+ public boolean handleMessage(Message msg) {
+ String text = (String) msg.obj;
+ Toast.makeText(mContext, text, Toast.LENGTH_LONG).show();
+ return true;
+ }
+
+ @Override
+ public void onImportProcessed(ImportRequest request, int jobId, int sequence) {
+ // Show a notification about the status
+ final String displayName;
+ final String message;
+ if (request.displayName != null) {
+ displayName = request.displayName;
+ message = mContext.getString(R.string.vcard_import_will_start_message, displayName);
+ } else {
+ displayName = mContext.getString(R.string.vcard_unknown_filename);
+ message = mContext.getString(
+ R.string.vcard_import_will_start_message_with_default_name);
+ }
+
+ // We just want to show notification for the first vCard.
+ if (sequence == 0) {
+ // TODO: Ideally we should detect the current status of import/export and
+ // show "started" when we can import right now and show "will start" when
+ // we cannot.
+ mHandler.obtainMessage(0, message).sendToTarget();
+ }
+
+ final Notification notification = constructProgressNotification(mContext,
+ VCardService.TYPE_IMPORT, message, message, jobId, displayName, -1, 0);
+ mNotificationManager.notify(DEFAULT_NOTIFICATION_TAG, jobId, notification);
+ }
+
+ @Override
+ public void onImportParsed(ImportRequest request, int jobId, VCardEntry entry, int currentCount,
+ int totalCount) {
+ if (entry.isIgnorable()) {
+ return;
+ }
+
+ final String totalCountString = String.valueOf(totalCount);
+ final String tickerText =
+ mContext.getString(R.string.progress_notifier_message,
+ String.valueOf(currentCount),
+ totalCountString,
+ entry.getDisplayName());
+ final String description = mContext.getString(R.string.importing_vcard_description,
+ entry.getDisplayName());
+
+ final Notification notification = constructProgressNotification(
+ mContext.getApplicationContext(), VCardService.TYPE_IMPORT, description, tickerText,
+ jobId, request.displayName, totalCount, currentCount);
+ mNotificationManager.notify(DEFAULT_NOTIFICATION_TAG, jobId, notification);
+ }
+
+ @Override
+ public void onImportFinished(ImportRequest request, int jobId, Uri createdUri) {
+ final String description = mContext.getString(R.string.importing_vcard_finished_title,
+ request.displayName);
+ final Intent intent;
+ if (createdUri != null) {
+ final long rawContactId = ContentUris.parseId(createdUri);
+ final Uri contactUri = RawContacts.getContactLookupUri(
+ mContext.getContentResolver(), ContentUris.withAppendedId(
+ RawContacts.CONTENT_URI, rawContactId));
+ intent = new Intent(Intent.ACTION_VIEW, contactUri);
+ } else {
+ intent = null;
+ }
+ final Notification notification =
+ NotificationImportExportListener.constructFinishNotification(mContext,
+ description, null, intent);
+ mNotificationManager.notify(NotificationImportExportListener.DEFAULT_NOTIFICATION_TAG,
+ jobId, notification);
+ }
+
+ @Override
+ public void onImportFailed(ImportRequest request) {
+ // TODO: a little unkind to show Toast in this case, which is shown just a moment.
+ // Ideally we should show some persistent something users can notice more easily.
+ mHandler.obtainMessage(0,
+ mContext.getString(R.string.vcard_import_request_rejected_message)).sendToTarget();
+ }
+
+ @Override
+ public void onImportCanceled(ImportRequest request, int jobId) {
+ final String description = mContext.getString(R.string.importing_vcard_canceled_title,
+ request.displayName);
+ final Notification notification =
+ NotificationImportExportListener.constructCancelNotification(mContext, description);
+ mNotificationManager.notify(NotificationImportExportListener.DEFAULT_NOTIFICATION_TAG,
+ jobId, notification);
+ }
+
+ @Override
+ public void onExportProcessed(ExportRequest request, int jobId) {
+ final String displayName = request.destUri.getLastPathSegment();
+ final String message = mContext.getString(R.string.vcard_export_will_start_message,
+ displayName);
+
+ mHandler.obtainMessage(0, message).sendToTarget();
+ final Notification notification =
+ NotificationImportExportListener.constructProgressNotification(mContext,
+ VCardService.TYPE_EXPORT, message, message, jobId, displayName, -1, 0);
+ mNotificationManager.notify(DEFAULT_NOTIFICATION_TAG, jobId, notification);
+ }
+
+ @Override
+ public void onExportFailed(ExportRequest request) {
+ mHandler.obtainMessage(0,
+ mContext.getString(R.string.vcard_export_request_rejected_message)).sendToTarget();
+ }
+
+ @Override
+ public void onCancelRequest(CancelRequest request, int type) {
+ final String description = type == VCardService.TYPE_IMPORT ?
+ mContext.getString(R.string.importing_vcard_canceled_title, request.displayName) :
+ mContext.getString(R.string.exporting_vcard_canceled_title, request.displayName);
+ final Notification notification = constructCancelNotification(mContext, description);
+ mNotificationManager.notify(DEFAULT_NOTIFICATION_TAG, request.jobId, notification);
+ }
+
+ /**
+ * Constructs a {@link Notification} showing the current status of import/export.
+ * Users can cancel the process with the Notification.
+ *
+ * @param context
+ * @param type import/export
+ * @param description Content of the Notification.
+ * @param tickerText
+ * @param jobId
+ * @param displayName Name to be shown to the Notification (e.g. "finished importing XXXX").
+ * Typycally a file name.
+ * @param totalCount The number of vCard entries to be imported. Used to show progress bar.
+ * -1 lets the system show the progress bar with "indeterminate" state.
+ * @param currentCount The index of current vCard. Used to show progress bar.
+ */
+ /* package */ static Notification constructProgressNotification(
+ Context context, int type, String description, String tickerText,
+ int jobId, String displayName, int totalCount, int currentCount) {
+ final RemoteViews remoteViews =
+ new RemoteViews(context.getPackageName(),
+ R.layout.status_bar_ongoing_event_progress_bar);
+ remoteViews.setTextViewText(R.id.status_description, description);
+ remoteViews.setProgressBar(R.id.status_progress_bar, totalCount, currentCount,
+ totalCount == -1);
+ final String percentage;
+ if (totalCount > 0) {
+ percentage = context.getString(R.string.percentage,
+ String.valueOf(currentCount * 100/totalCount));
+ } else {
+ percentage = "";
+ }
+ remoteViews.setTextViewText(R.id.status_progress_text, percentage);
+ final int icon = (type == VCardService.TYPE_IMPORT ? android.R.drawable.stat_sys_download :
+ android.R.drawable.stat_sys_upload);
+ remoteViews.setImageViewResource(R.id.status_icon, icon);
+
+ final Notification notification = new Notification();
+ notification.icon = icon;
+ notification.tickerText = tickerText;
+ notification.contentView = remoteViews;
+ notification.flags |= Notification.FLAG_ONGOING_EVENT;
+
+ // Note: We cannot use extra values here (like setIntExtra()), as PendingIntent doesn't
+ // preserve them across multiple Notifications. PendingIntent preserves the first extras
+ // (when flag is not set), or update them when PendingIntent#getActivity() is called
+ // (See PendingIntent#FLAG_UPDATE_CURRENT). In either case, we cannot preserve extras as we
+ // expect (for each vCard import/export request).
+ //
+ // We use query parameter in Uri instead.
+ // Scheme and Authority is arbitorary, assuming CancelActivity never refers them.
+ final Intent intent = new Intent(context, CancelActivity.class);
+ final Uri uri = (new Uri.Builder())
+ .scheme("invalidscheme")
+ .authority("invalidauthority")
+ .appendQueryParameter(CancelActivity.JOB_ID, String.valueOf(jobId))
+ .appendQueryParameter(CancelActivity.DISPLAY_NAME, displayName)
+ .appendQueryParameter(CancelActivity.TYPE, String.valueOf(type)).build();
+ intent.setData(uri);
+
+ notification.contentIntent = PendingIntent.getActivity(context, 0, intent, 0);
+ return notification;
+ }
+
+ /**
+ * Constructs a Notification telling users the process is canceled.
+ *
+ * @param context
+ * @param description Content of the Notification
+ */
+ /* package */ static Notification constructCancelNotification(
+ Context context, String description) {
+ return new Notification.Builder(context)
+ .setAutoCancel(true)
+ .setSmallIcon(android.R.drawable.stat_notify_error)
+ .setContentTitle(description)
+ .setContentText(description)
+ .setContentIntent(PendingIntent.getActivity(context, 0, new Intent(), 0))
+ .getNotification();
+ }
+
+ /**
+ * Constructs a Notification telling users the process is finished.
+ *
+ * @param context
+ * @param description Content of the Notification
+ * @param intent Intent to be launched when the Notification is clicked. Can be null.
+ */
+ /* package */ static Notification constructFinishNotification(
+ Context context, String title, String description, Intent intent) {
+ return new Notification.Builder(context)
+ .setAutoCancel(true)
+ .setSmallIcon(android.R.drawable.stat_sys_download_done)
+ .setContentTitle(title)
+ .setContentText(description)
+ .setContentIntent(PendingIntent.getActivity(context, 0,
+ (intent != null ? intent : new Intent()), 0))
+ .getNotification();
+ }
+
+ /**
+ * Constructs a Notification telling the vCard import has failed.
+ *
+ * @param context
+ * @param reason The reason why the import has failed. Shown in description field.
+ */
+ /* package */ static Notification constructImportFailureNotification(
+ Context context, String reason) {
+ return new Notification.Builder(context)
+ .setAutoCancel(true)
+ .setSmallIcon(android.R.drawable.stat_notify_error)
+ .setContentTitle(context.getString(R.string.vcard_import_failed))
+ .setContentText(reason)
+ .setContentIntent(PendingIntent.getActivity(context, 0, new Intent(), 0))
+ .getNotification();
+ }
+
+ @Override
+ public void onComplete() {
+ mContext.finish();
+ }
+}
diff --git a/src/com/android/contacts/vcard/VCardImportExportListener.java b/src/com/android/contacts/vcard/VCardImportExportListener.java
new file mode 100644
index 0000000..d3ef198
--- /dev/null
+++ b/src/com/android/contacts/vcard/VCardImportExportListener.java
@@ -0,0 +1,36 @@
+/*
+ * 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.vcard;
+
+import com.android.vcard.VCardEntry;
+
+import android.net.Uri;
+
+interface VCardImportExportListener {
+ void onImportProcessed(ImportRequest request, int jobId, int sequence);
+ void onImportParsed(ImportRequest request, int jobId, VCardEntry entry, int currentCount,
+ int totalCount);
+ void onImportFinished(ImportRequest request, int jobId, Uri uri);
+ void onImportFailed(ImportRequest request);
+ void onImportCanceled(ImportRequest request, int jobId);
+
+ void onExportProcessed(ExportRequest request, int jobId);
+ void onExportFailed(ExportRequest request);
+
+ void onCancelRequest(CancelRequest request, int type);
+ void onComplete();
+}
diff --git a/src/com/android/contacts/vcard/VCardService.java b/src/com/android/contacts/vcard/VCardService.java
index 0358e22..71b0214 100644
--- a/src/com/android/contacts/vcard/VCardService.java
+++ b/src/com/android/contacts/vcard/VCardService.java
@@ -17,16 +17,13 @@
import com.android.contacts.R;
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
import android.app.Service;
-import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.media.MediaScannerConnection;
import android.media.MediaScannerConnection.MediaScannerConnectionClient;
import android.net.Uri;
+import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
@@ -34,8 +31,6 @@
import android.os.RemoteException;
import android.text.TextUtils;
import android.util.Log;
-import android.widget.RemoteViews;
-import android.widget.Toast;
import java.io.File;
import java.util.ArrayList;
@@ -60,16 +55,6 @@
public class VCardService extends Service {
private final static String LOG_TAG = "VCardService";
- /** The tag used by vCard-related notifications. */
- /* package */ static final String DEFAULT_NOTIFICATION_TAG = "VCardServiceProgress";
- /**
- * The tag used by vCard-related failure notifications.
- * <p>
- * Use a different tag from {@link #DEFAULT_NOTIFICATION_TAG} so that failures do not get
- * replaced by other notifications and vice-versa.
- */
- /* package */ static final String FAILURE_NOTIFICATION_TAG = "VCardServiceFailure";
-
/* package */ final static boolean DEBUG = false;
/* package */ static final int MSG_IMPORT_REQUEST = 1;
@@ -79,7 +64,7 @@
/* package */ static final int MSG_SET_AVAILABLE_EXPORT_DESTINATION = 5;
/**
- * Specifies the type of operation. Used when constructing a {@link Notification}, canceling
+ * Specifies the type of operation. Used when constructing a notification, canceling
* some operation, etc.
*/
/* package */ static final int TYPE_IMPORT = 1;
@@ -87,34 +72,6 @@
/* package */ static final String CACHE_FILE_PREFIX = "import_tmp_";
- private final Messenger mMessenger = new Messenger(new Handler() {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_IMPORT_REQUEST: {
- handleImportRequest((List<ImportRequest>)msg.obj);
- break;
- }
- case MSG_EXPORT_REQUEST: {
- handleExportRequest((ExportRequest)msg.obj);
- break;
- }
- case MSG_CANCEL_REQUEST: {
- handleCancelRequest((CancelRequest)msg.obj);
- break;
- }
- case MSG_REQUEST_AVAILABLE_EXPORT_DESTINATION: {
- handleRequestAvailableExportDestination(msg);
- break;
- }
- // TODO: add cancel capability for export..
- default: {
- Log.w(LOG_TAG, "Received unknown request, ignoring it.");
- super.hasMessages(msg.what);
- }
- }
- }
- });
private class CustomMediaScannerConnectionClient implements MediaScannerConnectionClient {
final MediaScannerConnection mConnection;
@@ -143,8 +100,6 @@
}
}
- private NotificationManager mNotificationManager;
-
// Should be single thread, as we don't want to simultaneously handle import and export
// requests.
private final ExecutorService mExecutorService = Executors.newSingleThreadExecutor();
@@ -173,16 +128,23 @@
private String mFileNameExtension;
private Set<String> mExtensionsToConsider;
private String mErrorReason;
+ private MyBinder mBinder;
// File names currently reserved by some export job.
private final Set<String> mReservedDestination = new HashSet<String>();
/* ** end of vCard exporter params ** */
- @Override
+ public class MyBinder extends Binder {
+ public VCardService getService() {
+ return VCardService.this;
+ }
+ }
+
+ @Override
public void onCreate() {
super.onCreate();
+ mBinder = new MyBinder();
if (DEBUG) Log.d(LOG_TAG, "vCard Service is being created.");
- mNotificationManager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
initExporterParams();
}
@@ -218,7 +180,7 @@
@Override
public IBinder onBind(Intent intent) {
- return mMessenger.getBinder();
+ return mBinder;
}
@Override
@@ -229,7 +191,8 @@
super.onDestroy();
}
- private synchronized void handleImportRequest(List<ImportRequest> requests) {
+ public synchronized void handleImportRequest(List<ImportRequest> requests,
+ VCardImportExportListener listener) {
if (DEBUG) {
final ArrayList<String> uris = new ArrayList<String>();
final ArrayList<String> displayNames = new ArrayList<String>();
@@ -245,71 +208,44 @@
for (int i = 0; i < size; i++) {
ImportRequest request = requests.get(i);
- if (tryExecute(new ImportProcessor(this, request, mCurrentJobId))) {
- if (!request.showImmediately) {
- // Show a notification about the status
- final String displayName;
- final String message;
- if (request.displayName != null) {
- displayName = request.displayName;
- message = getString(R.string.vcard_import_will_start_message, displayName);
- } else {
- displayName = getString(R.string.vcard_unknown_filename);
- message = getString(
- R.string.vcard_import_will_start_message_with_default_name);
- }
-
- // We just want to show notification for the first vCard.
- if (i == 0) {
- // TODO: Ideally we should detect the current status of import/export and
- // show "started" when we can import right now and show "will start" when
- // we cannot.
- Toast.makeText(this, message, Toast.LENGTH_LONG).show();
- }
-
- final Notification notification = constructProgressNotification(this,
- TYPE_IMPORT, message, message, mCurrentJobId, displayName, -1, 0);
- mNotificationManager.notify(VCardService.DEFAULT_NOTIFICATION_TAG,
- mCurrentJobId, notification);
+ if (tryExecute(new ImportProcessor(this, listener, request, mCurrentJobId))) {
+ if (listener != null) {
+ listener.onImportProcessed(request, mCurrentJobId, i);
}
mCurrentJobId++;
} else {
- // TODO: a little unkind to show Toast in this case, which is shown just a moment.
- // Ideally we should show some persistent something users can notice more easily.
- Toast.makeText(this, getString(R.string.vcard_import_request_rejected_message),
- Toast.LENGTH_LONG).show();
+ if (listener != null) {
+ listener.onImportFailed(request);
+ }
// A rejection means executor doesn't run any more. Exit.
break;
}
}
}
- private synchronized void handleExportRequest(ExportRequest request) {
+ public synchronized void handleExportRequest(ExportRequest request,
+ VCardImportExportListener listener) {
if (tryExecute(new ExportProcessor(this, request, mCurrentJobId))) {
- final String displayName = request.destUri.getLastPathSegment();
- final String message = getString(R.string.vcard_export_will_start_message,
- displayName);
-
final String path = request.destUri.getEncodedPath();
if (DEBUG) Log.d(LOG_TAG, "Reserve the path " + path);
if (!mReservedDestination.add(path)) {
Log.w(LOG_TAG,
String.format("The path %s is already reserved. Reject export request",
path));
- Toast.makeText(this, getString(R.string.vcard_export_request_rejected_message),
- Toast.LENGTH_LONG).show();
+ if (listener != null) {
+ listener.onExportFailed(request);
+ }
return;
}
- Toast.makeText(this, message, Toast.LENGTH_LONG).show();
- final Notification notification =
- constructProgressNotification(this, TYPE_EXPORT, message, message,
- mCurrentJobId, displayName, -1, 0);
- mNotificationManager.notify(VCardService.DEFAULT_NOTIFICATION_TAG, mCurrentJobId, notification);
+ if (listener != null) {
+ listener.onExportProcessed(request, mCurrentJobId);
+ }
mCurrentJobId++;
} else {
- Toast.makeText(this, getString(R.string.vcard_export_request_rejected_message),
- Toast.LENGTH_LONG).show();
+ if (listener != null) {
+ listener.onExportFailed(request);
+ }
}
}
@@ -332,19 +268,19 @@
}
}
- private synchronized void handleCancelRequest(CancelRequest request) {
+ public synchronized void handleCancelRequest(CancelRequest request,
+ VCardImportExportListener listener) {
final int jobId = request.jobId;
if (DEBUG) Log.d(LOG_TAG, String.format("Received cancel request. (id: %d)", jobId));
final ProcessorBase processor = mRunningJobMap.remove(jobId);
if (processor != null) {
processor.cancel(true);
- final String description = processor.getType() == TYPE_IMPORT ?
- getString(R.string.importing_vcard_canceled_title, request.displayName) :
- getString(R.string.exporting_vcard_canceled_title, request.displayName);
- final Notification notification = constructCancelNotification(this, description);
- mNotificationManager.notify(VCardService.DEFAULT_NOTIFICATION_TAG, jobId, notification);
- if (processor.getType() == TYPE_EXPORT) {
+ final int type = processor.getType();
+ if (listener != null) {
+ listener.onCancelRequest(request, type);
+ }
+ if (type == TYPE_EXPORT) {
final String path =
((ExportProcessor)processor).getRequest().destUri.getEncodedPath();
Log.i(LOG_TAG,
@@ -359,9 +295,8 @@
stopServiceIfAppropriate();
}
- private synchronized void handleRequestAvailableExportDestination(Message msg) {
+ public synchronized void handleRequestAvailableExportDestination(final Messenger messenger) {
if (DEBUG) Log.d(LOG_TAG, "Received available export destination request.");
- final Messenger messenger = msg.replyTo;
final String path = getAppropriateDestination(mTargetDirectory);
final Message message;
if (path != null) {
@@ -494,122 +429,6 @@
}
/**
- * Constructs a {@link Notification} showing the current status of import/export.
- * Users can cancel the process with the Notification.
- *
- * @param context
- * @param type import/export
- * @param description Content of the Notification.
- * @param tickerText
- * @param jobId
- * @param displayName Name to be shown to the Notification (e.g. "finished importing XXXX").
- * Typycally a file name.
- * @param totalCount The number of vCard entries to be imported. Used to show progress bar.
- * -1 lets the system show the progress bar with "indeterminate" state.
- * @param currentCount The index of current vCard. Used to show progress bar.
- */
- /* package */ static Notification constructProgressNotification(
- Context context, int type, String description, String tickerText,
- int jobId, String displayName, int totalCount, int currentCount) {
- final RemoteViews remoteViews =
- new RemoteViews(context.getPackageName(),
- R.layout.status_bar_ongoing_event_progress_bar);
- remoteViews.setTextViewText(R.id.status_description, description);
- remoteViews.setProgressBar(R.id.status_progress_bar, totalCount, currentCount,
- totalCount == -1);
- final String percentage;
- if (totalCount > 0) {
- percentage = context.getString(R.string.percentage,
- String.valueOf(currentCount * 100/totalCount));
- } else {
- percentage = "";
- }
- remoteViews.setTextViewText(R.id.status_progress_text, percentage);
- final int icon = (type == TYPE_IMPORT ? android.R.drawable.stat_sys_download :
- android.R.drawable.stat_sys_upload);
- remoteViews.setImageViewResource(R.id.status_icon, icon);
-
- final Notification notification = new Notification();
- notification.icon = icon;
- notification.tickerText = tickerText;
- notification.contentView = remoteViews;
- notification.flags |= Notification.FLAG_ONGOING_EVENT;
-
- // Note: We cannot use extra values here (like setIntExtra()), as PendingIntent doesn't
- // preserve them across multiple Notifications. PendingIntent preserves the first extras
- // (when flag is not set), or update them when PendingIntent#getActivity() is called
- // (See PendingIntent#FLAG_UPDATE_CURRENT). In either case, we cannot preserve extras as we
- // expect (for each vCard import/export request).
- //
- // We use query parameter in Uri instead.
- // Scheme and Authority is arbitorary, assuming CancelActivity never refers them.
- final Intent intent = new Intent(context, CancelActivity.class);
- final Uri uri = (new Uri.Builder())
- .scheme("invalidscheme")
- .authority("invalidauthority")
- .appendQueryParameter(CancelActivity.JOB_ID, String.valueOf(jobId))
- .appendQueryParameter(CancelActivity.DISPLAY_NAME, displayName)
- .appendQueryParameter(CancelActivity.TYPE, String.valueOf(type)).build();
- intent.setData(uri);
-
- notification.contentIntent = PendingIntent.getActivity(context, 0, intent, 0);
- return notification;
- }
-
- /**
- * Constructs a Notification telling users the process is canceled.
- *
- * @param context
- * @param description Content of the Notification
- */
- /* package */ static Notification constructCancelNotification(
- Context context, String description) {
- return new Notification.Builder(context)
- .setAutoCancel(true)
- .setSmallIcon(android.R.drawable.stat_notify_error)
- .setContentTitle(description)
- .setContentText(description)
- .setContentIntent(PendingIntent.getActivity(context, 0, new Intent(), 0))
- .getNotification();
- }
-
- /**
- * Constructs a Notification telling users the process is finished.
- *
- * @param context
- * @param description Content of the Notification
- * @param intent Intent to be launched when the Notification is clicked. Can be null.
- */
- /* package */ static Notification constructFinishNotification(
- Context context, String title, String description, Intent intent) {
- return new Notification.Builder(context)
- .setAutoCancel(true)
- .setSmallIcon(android.R.drawable.stat_sys_download_done)
- .setContentTitle(title)
- .setContentText(description)
- .setContentIntent(PendingIntent.getActivity(context, 0,
- (intent != null ? intent : new Intent()), 0))
- .getNotification();
- }
-
- /**
- * Constructs a Notification telling the vCard import has failed.
- *
- * @param context
- * @param reason The reason why the import has failed. Shown in description field.
- */
- /* package */ static Notification constructImportFailureNotification(
- Context context, String reason) {
- return new Notification.Builder(context)
- .setAutoCancel(true)
- .setSmallIcon(android.R.drawable.stat_notify_error)
- .setContentTitle(context.getString(R.string.vcard_import_failed))
- .setContentText(reason)
- .setContentIntent(PendingIntent.getActivity(context, 0, new Intent(), 0))
- .getNotification();
- }
-
- /**
* Returns an appropriate file name for vCard export. Returns null when impossible.
*
* @return destination path for a vCard file to be exported. null on error and mErrorReason
diff --git a/tests/src/com/android/contacts/PhoneCallDetailsHelperTest.java b/tests/src/com/android/contacts/PhoneCallDetailsHelperTest.java
index 487a13f..40a33b6 100644
--- a/tests/src/com/android/contacts/PhoneCallDetailsHelperTest.java
+++ b/tests/src/com/android/contacts/PhoneCallDetailsHelperTest.java
@@ -50,6 +50,8 @@
private static final String TEST_FORMATTED_NUMBER = "1-412-255-5555";
/** The country ISO name used in the tests. */
private static final String TEST_COUNTRY_ISO = "US";
+ /** The geocoded location used in the tests. */
+ private static final String TEST_GEOCODE = "United States";
/** The object under test. */
private PhoneCallDetailsHelper mHelper;
@@ -268,7 +270,7 @@
/** Sets the phone call details with default values and the given number. */
private void setPhoneCallDetailsWithNumber(String number, String formattedNumber) {
mHelper.setPhoneCallDetails(mViews,
- new PhoneCallDetails(number, formattedNumber, TEST_COUNTRY_ISO,
+ new PhoneCallDetails(number, formattedNumber, TEST_COUNTRY_ISO, TEST_GEOCODE,
new int[]{ Calls.VOICEMAIL_TYPE }, TEST_DATE, TEST_DURATION),
true);
}
@@ -277,7 +279,7 @@
private void setPhoneCallDetailsWithDate(long date) {
mHelper.setPhoneCallDetails(mViews,
new PhoneCallDetails(TEST_NUMBER, TEST_FORMATTED_NUMBER, TEST_COUNTRY_ISO,
- new int[]{ Calls.INCOMING_TYPE }, date, TEST_DURATION),
+ TEST_GEOCODE, new int[]{ Calls.INCOMING_TYPE }, date, TEST_DURATION),
false);
}
@@ -285,20 +287,20 @@
private void setPhoneCallDetailsWithCallTypeIcons(int... callTypes) {
mHelper.setPhoneCallDetails(mViews,
new PhoneCallDetails(TEST_NUMBER, TEST_FORMATTED_NUMBER, TEST_COUNTRY_ISO,
- callTypes, TEST_DATE, TEST_DURATION),
+ TEST_GEOCODE, callTypes, TEST_DATE, TEST_DURATION),
false);
}
private void setPhoneCallNameWithNumberOnly() {
mHelper.setPhoneCallName(mNameView,
new PhoneCallDetails(TEST_NUMBER, TEST_FORMATTED_NUMBER, TEST_COUNTRY_ISO,
- new int[]{ Calls.INCOMING_TYPE }, TEST_DATE, TEST_DURATION));
+ TEST_GEOCODE, new int[]{ Calls.INCOMING_TYPE }, TEST_DATE, TEST_DURATION));
}
private void setPhoneCallName(String name) {
mHelper.setPhoneCallName(mNameView,
new PhoneCallDetails(TEST_NUMBER, TEST_FORMATTED_NUMBER, TEST_COUNTRY_ISO,
- new int[]{ Calls.INCOMING_TYPE }, TEST_DATE, TEST_DURATION,
+ TEST_GEOCODE, new int[]{ Calls.INCOMING_TYPE }, TEST_DATE, TEST_DURATION,
name, 0, "", 1, null));
}
}
diff --git a/tests/src/com/android/contacts/calllog/CallLogListItemHelperTest.java b/tests/src/com/android/contacts/calllog/CallLogListItemHelperTest.java
index 74f834a..2c29608 100644
--- a/tests/src/com/android/contacts/calllog/CallLogListItemHelperTest.java
+++ b/tests/src/com/android/contacts/calllog/CallLogListItemHelperTest.java
@@ -42,6 +42,8 @@
private static final String TEST_VOICEMAIL_NUMBER = "123";
/** The country ISO name used in the tests. */
private static final String TEST_COUNTRY_ISO = "US";
+ /** The geocoded location used in the tests. */
+ private static final String TEST_GEOCODE = "United States";
/** The object under test. */
private CallLogListItemHelper mHelper;
@@ -132,7 +134,7 @@
private void setPhoneCallDetailsWithNumberAndType(String number, String formattedNumber,
int callType) {
mHelper.setPhoneCallDetails(mViews,
- new PhoneCallDetails(number, formattedNumber, TEST_COUNTRY_ISO,
+ new PhoneCallDetails(number, formattedNumber, TEST_COUNTRY_ISO, TEST_GEOCODE,
new int[]{ callType }, TEST_DATE, TEST_DURATION),
false);
}
@@ -141,7 +143,7 @@
private void setPhoneCallDetailsWithTypes(int... types) {
mHelper.setPhoneCallDetails(mViews,
new PhoneCallDetails(TEST_NUMBER, TEST_FORMATTED_NUMBER, TEST_COUNTRY_ISO,
- types, TEST_DATE, TEST_DURATION),
+ TEST_GEOCODE, types, TEST_DATE, TEST_DURATION),
false);
}
@@ -149,7 +151,7 @@
private void setUnreadPhoneCallDetailsWithTypes(int... types) {
mHelper.setPhoneCallDetails(mViews,
new PhoneCallDetails(TEST_NUMBER, TEST_FORMATTED_NUMBER, TEST_COUNTRY_ISO,
- types, TEST_DATE, TEST_DURATION),
+ TEST_GEOCODE, types, TEST_DATE, TEST_DURATION),
true);
}
}