Merge "Throttle view notifications"
diff --git a/res/layout-w470dp/contact_detail_fragment.xml b/res/layout-w470dp/contact_detail_fragment.xml
index d63236d..982abbb 100644
--- a/res/layout-w470dp/contact_detail_fragment.xml
+++ b/res/layout-w470dp/contact_detail_fragment.xml
@@ -76,7 +76,6 @@
android:layout_height="match_parent"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
- android:background="@android:color/black"
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 60d728d..ccb7123 100644
--- a/res/layout-w470dp/contact_detail_updates_fragment.xml
+++ b/res/layout-w470dp/contact_detail_updates_fragment.xml
@@ -32,7 +32,6 @@
android:layout_height="match_parent"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
- android:background="@android:color/black"
android:visibility="gone"/>
<View
diff --git a/res/layout/carousel_about_tab.xml b/res/layout/carousel_about_tab.xml
index ad763be..c7c4394 100644
--- a/res/layout/carousel_about_tab.xml
+++ b/res/layout/carousel_about_tab.xml
@@ -19,8 +19,7 @@
class="com.android.contacts.detail.CarouselTab"
android:layout_width="0dip"
android:layout_height="match_parent"
- android:layout_weight="1"
- android:background="@color/detail_tab_background">
+ android:layout_weight="1">
<ImageView android:id="@+id/photo"
android:scaleType="centerCrop"
@@ -45,8 +44,6 @@
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_marginBottom="@dimen/detail_tab_carousel_tab_label_height"
- android:background="@android:color/black"
- android:alpha="0"
android:visibility="gone"/>
<TextView
diff --git a/res/layout/carousel_updates_tab.xml b/res/layout/carousel_updates_tab.xml
index b41829f..9637023 100644
--- a/res/layout/carousel_updates_tab.xml
+++ b/res/layout/carousel_updates_tab.xml
@@ -33,7 +33,6 @@
android:layout_alignParentLeft="true"
android:visibility="gone" />
-
<!-- Transparent view to overlay on the update photo
(to allow white text to appear over a white photo). -->
<View
@@ -65,8 +64,6 @@
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_marginBottom="@dimen/detail_tab_carousel_tab_label_height"
- android:background="@android:color/black"
- android:alpha="0"
android:visibility="gone"/>
<TextView
diff --git a/res/layout/contact_tile_frequent.xml b/res/layout/contact_tile_frequent.xml
index 14cb04e..2e79ba6 100644
--- a/res/layout/contact_tile_frequent.xml
+++ b/res/layout/contact_tile_frequent.xml
@@ -17,7 +17,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
class="com.android.contacts.list.ContactTileView"
android:focusable="true"
- android:background="@drawable/list_selector"
+ android:background="@null"
android:paddingRight="16dip"
android:paddingLeft="16dip" >
@@ -32,32 +32,46 @@
android:scaleType="centerCrop"
android:layout_alignParentRight="true" />
- <TextView
- android:id="@+id/contact_tile_name"
+ <LinearLayout
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:singleLine="true"
- android:ellipsize="end"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:layout_toLeftOf="@id/contact_tile_quick" />
+ android:layout_height="64dip"
+ android:orientation="vertical"
+ android:layout_alignParentBottom="true"
+ android:gravity="center_vertical"
+ android:paddingRight="80dip"
+ android:paddingLeft="8dip">
- <ImageView
- android:id="@+id/contact_tile_presence"
- android:layout_width="16dip"
- android:layout_height="16dip"
- android:layout_below="@id/contact_tile_name"
- android:layout_alignParentLeft="true" />
+ <TextView
+ android:id="@+id/contact_tile_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textColor="@color/primary_text_color"
+ android:textSize="18sp"
+ android:singleLine="true"
+ android:fadingEdge="horizontal"
+ android:fadingEdgeLength="3dip"
+ android:ellipsize="marquee" />
- <TextView
- android:id="@+id/contact_tile_status"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textSize="14sp"
- android:ellipsize="end"
- android:singleLine="true"
- android:textColor="#cccccc"
- android:layout_toRightOf="@id/contact_tile_presence"
- android:layout_below="@id/contact_tile_name" />
+ <TextView
+ android:id="@+id/contact_tile_status"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textColor="?android:attr/textColorSecondary"
+ android:singleLine="true"
+ android:drawablePadding="4dip"
+ android:fadingEdge="horizontal"
+ android:fadingEdgeLength="3dip"
+ android:ellipsize="marquee" />
+
+ </LinearLayout>
+
+ <View
+ android:id="@+id/contact_tile_horizontal_divider"
+ android:layout_width="match_parent"
+ android:layout_height="1px"
+ android:background="?android:attr/listDivider"
+ android:layout_below="@id/contact_tile_quick" />
</RelativeLayout>
diff --git a/res/layout/contact_tile_frequent_phone.xml b/res/layout/contact_tile_frequent_phone.xml
index 50c1fc2..563dea0 100644
--- a/res/layout/contact_tile_frequent_phone.xml
+++ b/res/layout/contact_tile_frequent_phone.xml
@@ -17,7 +17,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
class="com.android.contacts.list.ContactTileView"
android:focusable="true"
- android:background="@drawable/list_selector"
+ android:background="@null"
android:paddingRight="16dip"
android:paddingLeft="16dip" >
@@ -36,22 +36,24 @@
android:id="@+id/contact_tile_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:singleLine="true"
- android:ellipsize="end"
android:layout_marginLeft="8dip"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_marginTop="8dip"
- android:layout_toRightOf="@id/contact_tile_quick" />
+ android:layout_toRightOf="@id/contact_tile_quick"
+ android:singleLine="true"
+ android:fadingEdge="horizontal"
+ android:fadingEdgeLength="3dip"
+ android:ellipsize="marquee" />
<TextView
android:id="@+id/contact_tile_phone_number"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14sp"
- android:ellipsize="end"
- android:singleLine="true"
android:textColor="@color/dialtacts_secondary_text_color"
android:layout_marginLeft="8dip"
+ android:singleLine="true"
+ android:maxLength="18"
android:layout_toRightOf="@id/contact_tile_quick"
android:layout_below="@id/contact_tile_name" />
@@ -65,23 +67,16 @@
android:textAllCaps="true"
android:textColor="@color/dialtacts_secondary_text_color"
android:layout_marginLeft="12dip"
+ android:maxLength="8"
android:layout_alignParentRight="true"
android:layout_alignTop="@id/contact_tile_phone_number" />
<View
android:id="@+id/contact_tile_horizontal_divider"
android:layout_width="match_parent"
- android:layout_height="1dip"
+ android:layout_height="1px"
android:background="?android:attr/listDivider"
- android:layout_alignParentBottom="true" />
-
- <View
- android:id="@+id/contact_tile_horizontal_picture_divider"
- android:layout_width="64dip"
- android:layout_height="1dip"
- android:background="@android:color/black"
- android:layout_alignParentBottom="true"
- android:layout_alignParentLeft="true" />
+ android:layout_below="@id/contact_tile_quick" />
</RelativeLayout>
diff --git a/res/layout/contact_tile_starred.xml b/res/layout/contact_tile_starred.xml
index 3b74946..5fdfe65 100644
--- a/res/layout/contact_tile_starred.xml
+++ b/res/layout/contact_tile_starred.xml
@@ -15,10 +15,13 @@
-->
<view
xmlns:android="http://schemas.android.com/apk/res/android"
- class="com.android.contacts.list.ContactTileStarredView"
- style="@style/ContactTileStarred" >
+ android:background="@null"
+ android:paddingBottom="1dip"
+ android:paddingRight="1dip"
+ class="com.android.contacts.list.ContactTileStarredView" >
<RelativeLayout
+ android:id="@+id/contact_tile_layout"
android:layout_width="match_parent"
android:layout_height="match_parent" >
@@ -28,40 +31,42 @@
android:layout_height="match_parent"
android:scaleType="centerCrop" />
- <View
- android:id="@+id/contact_tile_background"
+ <LinearLayout
android:layout_width="match_parent"
android:layout_height="@dimen/contact_tile_shadowbox_height"
+ android:orientation="vertical"
+ android:background="@color/contact_tile_shadow_box_color"
android:layout_alignParentBottom="true"
- style="@style/ContactTileStarredShadowBox" />
+ android:gravity="center_vertical"
+ android:paddingRight="8dip"
+ android:paddingLeft="8dip">
- <ImageView
- android:id="@+id/contact_tile_presence"
- android:layout_width="16dip"
- android:layout_height="16dip"
- android:layout_alignParentBottom="true"
- android:layout_marginLeft="8dip"
- android:layout_marginBottom="10dip" />
+ <TextView
+ android:id="@+id/contact_tile_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textColor="@android:color/white"
+ android:textSize="16sp"
+ android:singleLine="true"
+ android:fadingEdge="horizontal"
+ android:fadingEdgeLength="3dip"
+ android:ellipsize="marquee" />
- <TextView
- android:id="@+id/contact_tile_name"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_alignLeft="@id/contact_tile_presence"
- android:layout_above="@id/contact_tile_presence"
- android:textColor="@android:color/white"
- stlye="@style/ContactTileStarredName" />
+ <TextView
+ android:id="@+id/contact_tile_status"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textColor="@color/people_contact_tile_status_color"
+ android:singleLine="true"
+ android:drawablePadding="4dip"
+ android:fadingEdge="horizontal"
+ android:fadingEdgeLength="3dip"
+ android:ellipsize="marquee" />
- <TextView
- android:id="@+id/contact_tile_status"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_toRightOf="@id/contact_tile_presence"
- android:layout_below="@id/contact_tile_name"
- android:layout_marginLeft="4dip"
- style="@style/ContactTileStatusText" />
+ </LinearLayout>
- <ImageButton
+ <View
android:id="@+id/contact_tile_push_state"
android:layout_width="match_parent"
android:layout_height="match_parent"
diff --git a/res/layout/contact_tile_starred_quick_contact.xml b/res/layout/contact_tile_starred_quick_contact.xml
index 0fb580e..9bb4abe 100644
--- a/res/layout/contact_tile_starred_quick_contact.xml
+++ b/res/layout/contact_tile_starred_quick_contact.xml
@@ -15,51 +15,61 @@
-->
<view
xmlns:android="http://schemas.android.com/apk/res/android"
- class="com.android.contacts.list.ContactTileStarredView"
- style="@style/ContactTileStarred" >
+ android:paddingBottom="1dip"
+ android:paddingRight="1dip"
+ android:background="@null"
+ class="com.android.contacts.list.ContactTileStarredView" >
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent" >
- <QuickContactBadge
- android:id="@+id/contact_tile_quick"
+ <ImageView
+ android:id="@+id/contact_tile_image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop" />
- <View
- android:id="@+id/contact_tile_background"
+ <LinearLayout
android:layout_width="match_parent"
android:layout_height="@dimen/contact_tile_shadowbox_height"
+ android:orientation="vertical"
+ android:background="@color/contact_tile_shadow_box_color"
android:layout_alignParentBottom="true"
- style="@style/ContactTileStarredShadowBox" />
+ android:gravity="center_vertical"
+ android:paddingRight="8dip"
+ android:paddingLeft="8dip">
- <ImageView
- android:id="@+id/contact_tile_presence"
- android:layout_width="16dip"
- android:layout_height="16dip"
- android:layout_alignParentBottom="true"
- android:layout_marginLeft="8dip"
- android:layout_marginBottom="10dip" />
+ <TextView
+ android:id="@+id/contact_tile_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textColor="@android:color/white"
+ android:textSize="16sp"
+ android:singleLine="true"
+ android:fadingEdge="horizontal"
+ android:fadingEdgeLength="3dip"
+ android:ellipsize="marquee" />
- <TextView
- android:id="@+id/contact_tile_name"
+ <TextView
+ android:id="@+id/contact_tile_status"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textColor="@color/people_contact_tile_status_color"
+ android:singleLine="true"
+ android:drawablePadding="4dip"
+ android:fadingEdge="horizontal"
+ android:fadingEdgeLength="3dip"
+ android:ellipsize="marquee" />
+
+ </LinearLayout>
+
+ <QuickContactBadge
+ android:id="@+id/contact_tile_quick"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_alignLeft="@id/contact_tile_presence"
- android:layout_above="@id/contact_tile_presence"
- android:textColor="@android:color/white"
- stlye="@style/ContactTileStarredName" />
-
- <TextView
- android:id="@+id/contact_tile_status"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_toRightOf="@id/contact_tile_presence"
- android:layout_below="@id/contact_tile_name"
- android:layout_marginLeft="4dip"
- style="@style/ContactTileStatusText" />
+ android:layout_height="match_parent"
+ android:background="@null" />
</RelativeLayout>
diff --git a/res/layout/contact_tile_starred_secondary_target.xml b/res/layout/contact_tile_starred_secondary_target.xml
index 3558166..e72575e 100644
--- a/res/layout/contact_tile_starred_secondary_target.xml
+++ b/res/layout/contact_tile_starred_secondary_target.xml
@@ -15,8 +15,10 @@
-->
<view
xmlns:android="http://schemas.android.com/apk/res/android"
- class="com.android.contacts.list.ContactTileSecondaryTargetView"
- style="@style/ContactTileStarred" >
+ android:background="@null"
+ android:paddingBottom="1dip"
+ android:paddingRight="1dip"
+ class="com.android.contacts.list.ContactTileSecondaryTargetView" >
<RelativeLayout
android:layout_width="match_parent"
@@ -28,49 +30,24 @@
android:layout_height="match_parent"
android:scaleType="centerCrop" />
- <View
- android:id="@+id/contact_tile_background"
+ <TextView
+ android:id="@+id/contact_tile_name"
android:layout_width="match_parent"
android:layout_height="@dimen/contact_tile_shadowbox_height"
+ android:background="@color/contact_tile_shadow_box_color"
+ android:gravity="center_vertical"
+ android:textColor="@android:color/white"
+ android:singleLine="true"
+ android:textSize="16sp"
+ android:fadingEdge="horizontal"
+ android:fadingEdgeLength="3dip"
+ android:ellipsize="marquee"
android:layout_alignParentBottom="true"
- android:layout_alignParentLeft="true"
- style="@style/ContactTileStarredShadowBox" />
-
- <FrameLayout
- android:id="@+id/contact_tile_info_layout"
- android:layout_height="@dimen/contact_tile_shadowbox_height"
- android:layout_width="wrap_content"
- android:orientation="horizontal"
- android:layout_alignParentBottom="true"
- android:layout_alignParentLeft="true"
- android:layout_marginLeft="8dip"
- android:layout_marginRight="45dip">
-
- <TextView
- android:id="@+id/contact_tile_name"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
- android:textColor="@android:color/white"
- android:singleLine="true"
- android:textSize="16sp"
- android:fadingEdge="horizontal"
- android:fadingEdgeLength="3dip"
- android:ellipsize="marquee" />
-
- </FrameLayout>
+ android:paddingLeft="8dip"
+ android:paddingRight="47dip"
+ android:drawableRight="@drawable/ic_divider_dashed_holo_dark" />
<View
- android:id="@+id/secondary_vertical_divider"
- android:layout_width="1dip"
- android:layout_height="32dip"
- android:layout_alignParentBottom="true"
- android:layout_alignParentRight="true"
- android:layout_marginRight="44dip"
- android:layout_marginBottom="8dip"
- android:background="@drawable/ic_divider_dashed_holo_dark" />
-
- <ImageButton
android:id="@+id/contact_tile_push_state"
android:layout_width="match_parent"
android:layout_height="match_parent"
@@ -82,25 +59,11 @@
android:background="?android:attr/selectableItemBackground"
android:layout_height="@dimen/contact_tile_shadowbox_height"
android:layout_width="48dip"
- android:layout_alignParentBottom="true"
- android:layout_alignParentRight="true"
android:paddingRight="8dip"
- android:paddingLeft="8dip" />
-
- <View
- android:id="@+id/contact_tile_vertical_divider"
- android:layout_height="match_parent"
- android:layout_width="2dip"
- android:background="@color/phone_contact_tile_divider_color"
+ android:paddingLeft="8dip"
+ android:layout_alignParentBottom="true"
android:layout_alignParentRight="true" />
- <View
- android:id="@+id/contact_tile_horizontal_divider"
- android:layout_width="match_parent"
- android:layout_height="2dip"
- android:background="@color/phone_contact_tile_divider_color"
- android:layout_alignParentBottom="true" />
-
</RelativeLayout>
</view>
diff --git a/res/layout/list_separator.xml b/res/layout/list_separator.xml
index 7d57ef7..39de1da 100644
--- a/res/layout/list_separator.xml
+++ b/res/layout/list_separator.xml
@@ -23,22 +23,22 @@
android:paddingLeft="16dip"
android:paddingRight="16dip">
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:paddingTop="8dip"
- android:paddingLeft="8dip"
- android:textStyle="bold"
- android:textAllCaps="true"
- android:singleLine="true"
- android:textAppearance="?android:attr/textAppearanceSmall"
- android:textColor="@color/people_app_theme_color"
- android:gravity="left|center_vertical"
- android:id="@+id/header_text" />
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingTop="8dip"
+ android:paddingLeft="8dip"
+ android:textStyle="bold"
+ android:textAllCaps="true"
+ android:singleLine="true"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textColor="@color/people_app_theme_color"
+ android:gravity="left|center_vertical"
+ android:id="@+id/header_text" />
- <View
- android:layout_width="match_parent"
- android:layout_height="1dip"
- android:background="@color/people_app_theme_color" />
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="1dip"
+ android:background="@color/people_app_theme_color" />
</LinearLayout>
\ No newline at end of file
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 1e47948..64d7dfb 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -95,7 +95,10 @@
<!-- Color of the text for buttons in the action bar -->
<color name="action_bar_button_text_color">#FFFFFF</color>
- <!-- Color of the vertical and horizontal dividers for starred contacts in the Phone app -->
- <color name="phone_contact_tile_divider_color">#D8000000</color>
+ <!-- Color of the status message for starred contacts in the People app -->
+ <color name="people_contact_tile_status_color">#CCCCCC</color>
+
+ <!-- Color of the semi-transparent shadow box on contact tiles -->
+ <color name="contact_tile_shadow_box_color">#7F000000</color>
</resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 81b962f..2f74c32 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -255,4 +255,7 @@
<!-- Height for directory headers in contact lists -->
<dimen name="directory_header_height">24dip</dimen>
+ <!-- Vertical and horizontal padding in between contact tiles -->
+ <dimen name="contact_tile_divider_padding">2dip</dimen>
+
</resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 84c3d15..677ac10 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -270,25 +270,14 @@
<item name="android:soundEffectsEnabled">false</item>
</style>
- <style name="ContactTileStarred">
- <item name="android:focusable">true</item>
+ <style name="FavoritesFragmentStyle">
+ <item name="android:layout_width">0dip</item>
+ <item name="android:layout_weight">1</item>
</style>
- <style name="ContactTileStarredName">
- <item name="android:singleLine">true</item>
- <item name="android:ellipsize">end</item>
- <item name="android:textSize">16sp</item>
- </style>
-
- <style name="ContactTileStatusText">
- <item name="android:singleLine">true</item>
- <item name="android:ellipsize">end</item>
- <item name="android:textAppearance">?android:attr/textAppearanceSmall</item>
- <item name="android:textColor">#777777</item>
- </style>
-
- <style name="ContactTileStarredShadowBox">
- <item name="android:background">#7F000000</item>
+ <style name="FrequentFragmentStyle">
+ <item name="android:layout_width">0dip</item>
+ <item name="android:layout_weight">1</item>
</style>
<style name="DialtactsActionBarStyle" parent="android:Widget.Holo.ActionBar">
diff --git a/src/com/android/contacts/ContactLoader.java b/src/com/android/contacts/ContactLoader.java
index 4b4d50d..dbfe411 100644
--- a/src/com/android/contacts/ContactLoader.java
+++ b/src/com/android/contacts/ContactLoader.java
@@ -1124,7 +1124,7 @@
mNotifiedRawContactIds.add(rawContactId);
final String type = entityValues.getAsString(RawContacts.ACCOUNT_TYPE);
final String dataSet = entityValues.getAsString(RawContacts.DATA_SET);
- final AccountType accountType = AccountTypeManager.getInstance(context ).getAccountType(
+ final AccountType accountType = AccountTypeManager.getInstance(context).getAccountType(
type, dataSet);
final String serviceName = accountType.getViewContactNotifyServiceClassName();
final String resPackageName = accountType.resPackageName;
diff --git a/src/com/android/contacts/ContactSaveService.java b/src/com/android/contacts/ContactSaveService.java
index 3bb330f..78c4b18 100644
--- a/src/com/android/contacts/ContactSaveService.java
+++ b/src/com/android/contacts/ContactSaveService.java
@@ -53,8 +53,8 @@
import java.util.ArrayList;
import java.util.HashSet;
-import java.util.LinkedList;
import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
/**
* A service responsible for saving changes to the content provider.
@@ -133,7 +133,8 @@
public void onServiceCompleted(Intent callbackIntent);
}
- private static final LinkedList<Listener> sListeners = new LinkedList<Listener>();
+ private static final CopyOnWriteArrayList<Listener> sListeners =
+ new CopyOnWriteArrayList<Listener>();
private Handler mMainHandler;
@@ -148,15 +149,11 @@
throw new ClassCastException("Only activities can be registered to"
+ " receive callback from " + ContactSaveService.class.getName());
}
- synchronized (sListeners) {
- sListeners.addFirst(listener);
- }
+ sListeners.add(0, listener);
}
public static void unregisterListener(Listener listener) {
- synchronized (sListeners) {
- sListeners.remove(listener);
- }
+ sListeners.remove(listener);
}
@Override
@@ -975,13 +972,11 @@
// TODO: this assumes that if there are multiple instances of the same
// activity registered, the last one registered is the one waiting for
// the callback. Validity of this assumption needs to be verified.
- synchronized (sListeners) {
- for (Listener listener : sListeners) {
- if (callbackIntent.getComponent().equals(
- ((Activity) listener).getIntent().getComponent())) {
- listener.onServiceCompleted(callbackIntent);
- return;
- }
+ for (Listener listener : sListeners) {
+ if (callbackIntent.getComponent().equals(
+ ((Activity) listener).getIntent().getComponent())) {
+ listener.onServiceCompleted(callbackIntent);
+ return;
}
}
}
diff --git a/src/com/android/contacts/GroupListLoader.java b/src/com/android/contacts/GroupListLoader.java
index b567ba8..2589c9b 100644
--- a/src/com/android/contacts/GroupListLoader.java
+++ b/src/com/android/contacts/GroupListLoader.java
@@ -34,8 +34,6 @@
Groups.DATA_SET,
Groups._ID,
Groups.TITLE,
- Groups.ACTION,
- Groups.ACTION_URI,
Groups.SUMMARY_COUNT,
};
@@ -44,9 +42,7 @@
public final static int DATA_SET = 2;
public final static int GROUP_ID = 3;
public final static int TITLE = 4;
- public final static int ACTION = 5;
- public final static int ACTION_URI = 6;
- public final static int MEMBER_COUNT = 7;
+ public final static int MEMBER_COUNT = 5;
private static final Uri GROUP_LIST_URI = Groups.CONTENT_SUMMARY_URI;
diff --git a/src/com/android/contacts/GroupMetaDataLoader.java b/src/com/android/contacts/GroupMetaDataLoader.java
index 2f3468b..8344041 100644
--- a/src/com/android/contacts/GroupMetaDataLoader.java
+++ b/src/com/android/contacts/GroupMetaDataLoader.java
@@ -36,8 +36,6 @@
Groups.FAVORITES,
Groups.GROUP_IS_READ_ONLY,
Groups.DELETED,
- Groups.ACTION,
- Groups.ACTION_URI,
};
public final static int ACCOUNT_NAME = 0;
@@ -49,8 +47,6 @@
public final static int FAVORITES = 6;
public final static int IS_READ_ONLY = 7;
public final static int DELETED = 8;
- public final static int ACTION = 9;
- public final static int ACTION_URI = 10;
public GroupMetaDataLoader(Context context, Uri groupUri) {
super(context, ensureIsGroupUri(groupUri), COLUMNS, Groups.ACCOUNT_TYPE + " NOT NULL AND "
diff --git a/src/com/android/contacts/PhoneCallDetailsHelper.java b/src/com/android/contacts/PhoneCallDetailsHelper.java
index e970fcc..e79bdce 100644
--- a/src/com/android/contacts/PhoneCallDetailsHelper.java
+++ b/src/com/android/contacts/PhoneCallDetailsHelper.java
@@ -107,7 +107,8 @@
mPhoneNumberHelper.getDisplayNumber(details.number, details.formattedNumber);
if (TextUtils.isEmpty(details.name)) {
nameText = displayNumber;
- if (TextUtils.isEmpty(details.geocode)) {
+ if (TextUtils.isEmpty(details.geocode)
+ || mPhoneNumberHelper.isVoicemailNumber(details.number)) {
numberText = mResources.getString(R.string.call_log_empty_gecode);
} else {
numberText = details.geocode;
diff --git a/src/com/android/contacts/activities/DialtactsActivity.java b/src/com/android/contacts/activities/DialtactsActivity.java
index 13ce317..cfca831 100644
--- a/src/com/android/contacts/activities/DialtactsActivity.java
+++ b/src/com/android/contacts/activities/DialtactsActivity.java
@@ -552,6 +552,7 @@
Log.e(TAG, "DialpadFragment isn't ready yet when the tab is already selected.");
}
}
+ invalidateOptionsMenu();
}
/** Returns true if the given intent contains a phone number to populate the dialer with */
@@ -753,7 +754,7 @@
private void hideInputMethod(View view) {
InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
- if (imm != null) {
+ if (imm != null && view != null) {
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
}
diff --git a/src/com/android/contacts/activities/GroupDetailActivity.java b/src/com/android/contacts/activities/GroupDetailActivity.java
index b66d8b8..47e02a7 100644
--- a/src/com/android/contacts/activities/GroupDetailActivity.java
+++ b/src/com/android/contacts/activities/GroupDetailActivity.java
@@ -20,11 +20,15 @@
import com.android.contacts.R;
import com.android.contacts.group.GroupDetailDisplayUtils;
import com.android.contacts.group.GroupDetailFragment;
+import com.android.contacts.model.AccountType;
+import com.android.contacts.model.AccountTypeManager;
import android.app.ActionBar;
+import android.content.ContentUris;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
+import android.provider.ContactsContract.Groups;
import android.text.TextUtils;
import android.view.Menu;
import android.view.MenuInflater;
@@ -40,8 +44,8 @@
private String mAccountTypeString;
private String mDataSet;
- private String mGroupSourceAction;
- private String mGroupSourceUri;
+
+ private GroupDetailFragment mFragment;
@Override
public void onCreate(Bundle savedState) {
@@ -55,12 +59,12 @@
mShowGroupSourceInActionBar = getResources().getBoolean(
R.bool.config_show_group_action_in_action_bar);
- GroupDetailFragment fragment = (GroupDetailFragment) getFragmentManager().findFragmentById(
+ mFragment = (GroupDetailFragment) getFragmentManager().findFragmentById(
R.id.group_detail_fragment);
- fragment.setListener(mFragmentListener);
- fragment.setShowGroupSourceInActionBar(mShowGroupSourceInActionBar);
- fragment.loadGroup(getIntent().getData());
- fragment.closeActivityAfterDelete(true);
+ mFragment.setListener(mFragmentListener);
+ mFragment.setShowGroupSourceInActionBar(mShowGroupSourceInActionBar);
+ mFragment.loadGroup(getIntent().getData());
+ mFragment.closeActivityAfterDelete(true);
// We want the UP affordance but no app icon.
ActionBar actionBar = getActionBar();
@@ -85,12 +89,9 @@
}
@Override
- public void onGroupSourceUpdated(String accountTypeString, String dataSet,
- String groupSourceAction, String groupSourceActionUri) {
+ public void onAccountTypeUpdated(String accountTypeString, String dataSet) {
mAccountTypeString = accountTypeString;
mDataSet = dataSet;
- mGroupSourceAction = groupSourceAction;
- mGroupSourceUri = groupSourceActionUri;
invalidateOptionsMenu();
}
@@ -128,8 +129,11 @@
if (groupSourceMenuItem == null) {
return false;
}
- if (TextUtils.isEmpty(mAccountTypeString) || TextUtils.isEmpty(mGroupSourceAction) ||
- TextUtils.isEmpty(mGroupSourceUri)) {
+ final AccountTypeManager manager = AccountTypeManager.getInstance(this);
+ final AccountType accountType =
+ manager.getAccountType(mAccountTypeString, mDataSet);
+ if (TextUtils.isEmpty(mAccountTypeString)
+ || TextUtils.isEmpty(accountType.getViewGroupActivity())) {
groupSourceMenuItem.setVisible(false);
return false;
}
@@ -139,7 +143,11 @@
groupSourceView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
- startActivity(new Intent(mGroupSourceAction, Uri.parse(mGroupSourceUri)));
+ final Uri uri = ContentUris.withAppendedId(Groups.CONTENT_URI,
+ mFragment.getGroupId());
+ final Intent intent = new Intent(Intent.ACTION_VIEW, uri);
+ intent.setClassName(accountType.resPackageName, accountType.getViewGroupActivity());
+ startActivity(intent);
}
});
groupSourceMenuItem.setActionView(groupSourceView);
diff --git a/src/com/android/contacts/activities/PeopleActivity.java b/src/com/android/contacts/activities/PeopleActivity.java
index cab8afd..1da1c97 100644
--- a/src/com/android/contacts/activities/PeopleActivity.java
+++ b/src/com/android/contacts/activities/PeopleActivity.java
@@ -23,7 +23,6 @@
import com.android.contacts.activities.ActionBarAdapter.TabState;
import com.android.contacts.detail.ContactDetailFragment;
import com.android.contacts.detail.ContactDetailLayoutController;
-import com.android.contacts.detail.ContactDetailTabCarousel;
import com.android.contacts.detail.ContactDetailUpdatesFragment;
import com.android.contacts.detail.ContactLoaderFragment;
import com.android.contacts.detail.ContactLoaderFragment.ContactLoaderFragmentListener;
@@ -39,6 +38,7 @@
import com.android.contacts.list.ContactListFilter;
import com.android.contacts.list.ContactListFilterController;
import com.android.contacts.list.ContactTileAdapter.DisplayType;
+import com.android.contacts.list.ContactTileListFragment;
import com.android.contacts.list.ContactsIntentResolver;
import com.android.contacts.list.ContactsRequest;
import com.android.contacts.list.ContactsUnavailableFragment;
@@ -48,7 +48,6 @@
import com.android.contacts.list.OnContactsUnavailableActionListener;
import com.android.contacts.list.ProviderStatusLoader;
import com.android.contacts.list.ProviderStatusLoader.ProviderStatusListener;
-import com.android.contacts.list.ContactTileListFragment;
import com.android.contacts.model.AccountTypeManager;
import com.android.contacts.model.AccountWithDataSet;
import com.android.contacts.preference.ContactsPreferenceActivity;
@@ -1160,8 +1159,7 @@
}
@Override
- public void onGroupSourceUpdated(String accountTypeString, String dataSet,
- String groupSourceAction, String groupSourceUri) {
+ public void onAccountTypeUpdated(String accountTypeString, String dataSet) {
// Nothing needs to be done here because the group source will be displayed in the
// detail fragment
}
diff --git a/src/com/android/contacts/calllog/CallLogAdapter.java b/src/com/android/contacts/calllog/CallLogAdapter.java
new file mode 100644
index 0000000..7e934b6
--- /dev/null
+++ b/src/com/android/contacts/calllog/CallLogAdapter.java
@@ -0,0 +1,755 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.contacts.calllog;
+
+import com.android.common.widget.GroupingListAdapter;
+import com.android.contacts.ContactPhotoManager;
+import com.android.contacts.PhoneCallDetails;
+import com.android.contacts.PhoneCallDetailsHelper;
+import com.android.contacts.R;
+import com.android.contacts.util.ExpirableCache;
+import com.google.common.annotations.VisibleForTesting;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Message;
+import android.provider.CallLog.Calls;
+import android.provider.ContactsContract.CommonDataKinds.SipAddress;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.PhoneLookup;
+import android.telephony.PhoneNumberUtils;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+
+import java.util.LinkedList;
+
+/**
+ * Adapter class to fill in data for the Call Log.
+ */
+public final class CallLogAdapter extends GroupingListAdapter
+ implements Runnable, ViewTreeObserver.OnPreDrawListener, CallLogGroupBuilder.GroupCreator {
+ /** Interface used to initiate a refresh of the content. */
+ public interface CallFetcher {
+ public void startCallsQuery();
+ }
+
+ /** The time in millis to delay starting the thread processing requests. */
+ private static final int START_PROCESSING_REQUESTS_DELAY_MILLIS = 1000;
+
+ /** The size of the cache of contact info. */
+ private static final int CONTACT_INFO_CACHE_SIZE = 100;
+
+ private final Context mContext;
+ private final String mCurrentCountryIso;
+ private final CallFetcher mCallFetcher;
+
+ /**
+ * A cache of the contact details for the phone numbers in the call log.
+ * <p>
+ * The content of the cache is expired (but not purged) whenever the application comes to
+ * the foreground.
+ */
+ private ExpirableCache<String, ContactInfo> mContactInfoCache;
+
+ /**
+ * List of requests to update contact details.
+ * <p>
+ * The requests are added when displaying the contacts and are processed by a background
+ * thread.
+ */
+ private final LinkedList<String> mRequests;
+
+ private volatile boolean mDone;
+ private boolean mLoading = true;
+ private ViewTreeObserver.OnPreDrawListener mPreDrawListener;
+ private static final int REDRAW = 1;
+ private static final int START_THREAD = 2;
+ private boolean mFirst;
+ private Thread mCallerIdThread;
+
+ /** Instance of helper class for managing views. */
+ private final CallLogListItemHelper mCallLogViewsHelper;
+
+ /** Helper to set up contact photos. */
+ private final ContactPhotoManager mContactPhotoManager;
+ /** Helper to parse and process phone numbers. */
+ private PhoneNumberHelper mPhoneNumberHelper;
+ /** Helper to group call log entries. */
+ private final CallLogGroupBuilder mCallLogGroupBuilder;
+
+ /** Can be set to true by tests to disable processing of requests. */
+ private volatile boolean mRequestProcessingDisabled = false;
+
+ /** 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();
+ if (intentProvider != null) {
+ mContext.startActivity(intentProvider.getIntent(mContext));
+ }
+ }
+ };
+
+ @Override
+ public boolean onPreDraw() {
+ if (mFirst) {
+ mHandler.sendEmptyMessageDelayed(START_THREAD,
+ START_PROCESSING_REQUESTS_DELAY_MILLIS);
+ mFirst = false;
+ }
+ return true;
+ }
+
+ private Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case REDRAW:
+ notifyDataSetChanged();
+ break;
+ case START_THREAD:
+ startRequestProcessing();
+ break;
+ }
+ }
+ };
+
+ public CallLogAdapter(Context context, CallFetcher callFetcher,
+ String currentCountryIso, String voicemailNumber) {
+ super(context);
+
+ mContext = context;
+ mCurrentCountryIso = currentCountryIso;
+ mCallFetcher = callFetcher;
+
+ mContactInfoCache = ExpirableCache.create(CONTACT_INFO_CACHE_SIZE);
+ mRequests = new LinkedList<String>();
+ mPreDrawListener = null;
+
+ Resources resources = mContext.getResources();
+ CallTypeHelper callTypeHelper = new CallTypeHelper(resources);
+
+ mContactPhotoManager = ContactPhotoManager.getInstance(mContext);
+ mPhoneNumberHelper = new PhoneNumberHelper(resources, voicemailNumber);
+ PhoneCallDetailsHelper phoneCallDetailsHelper = new PhoneCallDetailsHelper(
+ resources, callTypeHelper, mPhoneNumberHelper);
+ mCallLogViewsHelper =
+ new CallLogListItemHelper(
+ phoneCallDetailsHelper, mPhoneNumberHelper, resources);
+ mCallLogGroupBuilder = new CallLogGroupBuilder(this);
+ }
+
+ /**
+ * Requery on background thread when {@link Cursor} changes.
+ */
+ @Override
+ protected void onContentChanged() {
+ // When the content changes, always fetch all the calls, in case a new missed call came
+ // in and we were filtering over voicemail only, so that we see the missed call.
+ mCallFetcher.startCallsQuery();
+ }
+
+ void setLoading(boolean loading) {
+ mLoading = loading;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ if (mLoading) {
+ // We don't want the empty state to show when loading.
+ return false;
+ } else {
+ return super.isEmpty();
+ }
+ }
+
+ public ContactInfo getContactInfo(String number) {
+ return mContactInfoCache.getPossiblyExpired(number);
+ }
+
+ public void startRequestProcessing() {
+ if (mRequestProcessingDisabled) {
+ return;
+ }
+
+ mDone = false;
+ mCallerIdThread = new Thread(this, "CallLogContactLookup");
+ mCallerIdThread.setPriority(Thread.MIN_PRIORITY);
+ mCallerIdThread.start();
+ }
+
+ /**
+ * Stops the background thread that processes updates and cancels any pending requests to
+ * start it.
+ * <p>
+ * Should be called from the main thread to prevent a race condition between the request to
+ * start the thread being processed and stopping the thread.
+ */
+ public void stopRequestProcessing() {
+ // Remove any pending requests to start the processing thread.
+ mHandler.removeMessages(START_THREAD);
+ mDone = true;
+ if (mCallerIdThread != null) mCallerIdThread.interrupt();
+ }
+
+ public void invalidateCache() {
+ mContactInfoCache.expireAll();
+ // Let it restart the thread after next draw
+ mPreDrawListener = null;
+ }
+
+ private void enqueueRequest(String number, boolean immediate) {
+ synchronized (mRequests) {
+ if (!mRequests.contains(number)) {
+ mRequests.add(number);
+ mRequests.notifyAll();
+ }
+ }
+ if (mFirst && immediate) {
+ startRequestProcessing();
+ mFirst = false;
+ }
+ }
+
+ /**
+ * Determines the contact information for the given SIP address.
+ * <p>
+ * It returns the contact info if found.
+ * <p>
+ * If no contact corresponds to the given SIP address, returns {@link ContactInfo#EMPTY}.
+ * <p>
+ * If the lookup fails for some other reason, it returns null.
+ */
+ private ContactInfo queryContactInfoForSipAddress(String sipAddress) {
+ final ContactInfo info;
+
+ // TODO: This code is duplicated from the
+ // CallerInfoAsyncQuery class. To avoid that, could the
+ // code here just use CallerInfoAsyncQuery, rather than
+ // manually running ContentResolver.query() itself?
+
+ // We look up SIP addresses directly in the Data table:
+ Uri contactRef = Data.CONTENT_URI;
+
+ // Note Data.DATA1 and SipAddress.SIP_ADDRESS are equivalent.
+ //
+ // Also note we use "upper(data1)" in the WHERE clause, and
+ // uppercase the incoming SIP address, in order to do a
+ // case-insensitive match.
+ //
+ // TODO: May also need to normalize by adding "sip:" as a
+ // prefix, if we start storing SIP addresses that way in the
+ // database.
+ String selection = "upper(" + Data.DATA1 + ")=?"
+ + " AND "
+ + Data.MIMETYPE + "='" + SipAddress.CONTENT_ITEM_TYPE + "'";
+ String[] selectionArgs = new String[] { sipAddress.toUpperCase() };
+
+ Cursor dataTableCursor =
+ mContext.getContentResolver().query(
+ contactRef,
+ null, // projection
+ selection, // selection
+ selectionArgs, // selectionArgs
+ null); // sortOrder
+
+ if (dataTableCursor != null) {
+ if (dataTableCursor.moveToFirst()) {
+ info = new ContactInfo();
+
+ // TODO: we could slightly speed this up using an
+ // explicit projection (and thus not have to do
+ // those getColumnIndex() calls) but the benefit is
+ // very minimal.
+
+ // Note the Data.CONTACT_ID column here is
+ // equivalent to the PERSON_ID_COLUMN_INDEX column
+ // we use with "phonesCursor" below.
+ info.personId = dataTableCursor.getLong(
+ dataTableCursor.getColumnIndex(Data.CONTACT_ID));
+ info.name = dataTableCursor.getString(
+ dataTableCursor.getColumnIndex(Data.DISPLAY_NAME));
+ // "type" and "label" are currently unused for SIP addresses
+ info.type = SipAddress.TYPE_OTHER;
+ info.label = null;
+
+ // And "number" is the SIP address.
+ // Note Data.DATA1 and SipAddress.SIP_ADDRESS are equivalent.
+ info.number = dataTableCursor.getString(
+ dataTableCursor.getColumnIndex(Data.DATA1));
+ info.normalizedNumber = null; // meaningless for SIP addresses
+ final String thumbnailUriString = dataTableCursor.getString(
+ dataTableCursor.getColumnIndex(Data.PHOTO_THUMBNAIL_URI));
+ info.thumbnailUri = thumbnailUriString == null
+ ? null
+ : Uri.parse(thumbnailUriString);
+ info.lookupKey = dataTableCursor.getString(
+ dataTableCursor.getColumnIndex(Data.LOOKUP_KEY));
+ } else {
+ info = ContactInfo.EMPTY;
+ }
+ dataTableCursor.close();
+ } else {
+ // Failed to fetch the data, ignore this request.
+ info = null;
+ }
+ return info;
+ }
+
+ /**
+ * Determines the contact information for the given phone number.
+ * <p>
+ * It returns the contact info if found.
+ * <p>
+ * If no contact corresponds to the given phone number, returns {@link ContactInfo#EMPTY}.
+ * <p>
+ * If the lookup fails for some other reason, it returns null.
+ */
+ private ContactInfo queryContactInfoForPhoneNumber(String number) {
+ final ContactInfo info;
+
+ // "number" is a regular phone number, so use the
+ // PhoneLookup table:
+ Cursor phonesCursor =
+ mContext.getContentResolver().query(
+ Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI,
+ Uri.encode(number)),
+ PhoneQuery._PROJECTION, null, null, null);
+ if (phonesCursor != null) {
+ if (phonesCursor.moveToFirst()) {
+ info = new ContactInfo();
+ info.personId = phonesCursor.getLong(PhoneQuery.PERSON_ID);
+ info.name = phonesCursor.getString(PhoneQuery.NAME);
+ info.type = phonesCursor.getInt(PhoneQuery.PHONE_TYPE);
+ info.label = phonesCursor.getString(PhoneQuery.LABEL);
+ info.number = phonesCursor
+ .getString(PhoneQuery.MATCHED_NUMBER);
+ info.normalizedNumber = phonesCursor
+ .getString(PhoneQuery.NORMALIZED_NUMBER);
+ final String thumbnailUriString = phonesCursor.getString(
+ PhoneQuery.THUMBNAIL_URI);
+ info.thumbnailUri = thumbnailUriString == null
+ ? null
+ : Uri.parse(thumbnailUriString);
+ info.lookupKey = phonesCursor.getString(PhoneQuery.LOOKUP_KEY);
+ } else {
+ info = ContactInfo.EMPTY;
+ }
+ phonesCursor.close();
+ } else {
+ // Failed to fetch the data, ignore this request.
+ info = null;
+ }
+ return info;
+ }
+
+ /**
+ * Queries the appropriate content provider for the contact associated with the number.
+ * <p>
+ * The number might be either a SIP address or a phone number.
+ * <p>
+ * It returns true if it updated the content of the cache and we should therefore tell the
+ * view to update its content.
+ */
+ private boolean queryContactInfo(String number) {
+ final ContactInfo info;
+
+ // Determine the contact info.
+ if (PhoneNumberUtils.isUriNumber(number)) {
+ // This "number" is really a SIP address.
+ info = queryContactInfoForSipAddress(number);
+ } else {
+ info = queryContactInfoForPhoneNumber(number);
+ }
+
+ if (info == null) {
+ // The lookup failed, just return without requesting to update the view.
+ return false;
+ }
+
+ // Check the existing entry in the cache: only if it has changed we should update the
+ // view.
+ ContactInfo existingInfo = mContactInfoCache.getPossiblyExpired(number);
+ boolean updated = !info.equals(existingInfo);
+ if (updated) {
+ // The formattedNumber is computed by the UI thread when needed. Since we updated
+ // the details of the contact, set this value to null for now.
+ info.formattedNumber = null;
+ }
+ // Store the data in the cache so that the UI thread can use to display it. Store it
+ // even if it has not changed so that it is marked as not expired.
+ mContactInfoCache.put(number, info);
+ return updated;
+ }
+
+ /*
+ * Handles requests for contact name and number type
+ * @see java.lang.Runnable#run()
+ */
+ @Override
+ public void run() {
+ boolean needNotify = false;
+ while (!mDone) {
+ String number = null;
+ synchronized (mRequests) {
+ if (!mRequests.isEmpty()) {
+ number = mRequests.removeFirst();
+ } else {
+ if (needNotify) {
+ needNotify = false;
+ mHandler.sendEmptyMessage(REDRAW);
+ }
+ try {
+ mRequests.wait(1000);
+ } catch (InterruptedException ie) {
+ // Ignore and continue processing requests
+ Thread.currentThread().interrupt();
+ }
+ }
+ }
+ if (!mDone && number != null && queryContactInfo(number)) {
+ needNotify = true;
+ }
+ }
+ }
+
+ @Override
+ protected void addGroups(Cursor cursor) {
+ mCallLogGroupBuilder.addGroups(cursor);
+ }
+
+ @VisibleForTesting
+ @Override
+ public View newStandAloneView(Context context, ViewGroup parent) {
+ LayoutInflater inflater =
+ (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ View view = inflater.inflate(R.layout.call_log_list_item, parent, false);
+ findAndCacheViews(view);
+ return view;
+ }
+
+ @VisibleForTesting
+ @Override
+ public void bindStandAloneView(View view, Context context, Cursor cursor) {
+ bindView(view, cursor, 1);
+ }
+
+ @VisibleForTesting
+ @Override
+ public View newChildView(Context context, ViewGroup parent) {
+ LayoutInflater inflater =
+ (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ View view = inflater.inflate(R.layout.call_log_list_item, parent, false);
+ findAndCacheViews(view);
+ return view;
+ }
+
+ @VisibleForTesting
+ @Override
+ public void bindChildView(View view, Context context, Cursor cursor) {
+ bindView(view, cursor, 1);
+ }
+
+ @VisibleForTesting
+ @Override
+ public View newGroupView(Context context, ViewGroup parent) {
+ LayoutInflater inflater =
+ (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ View view = inflater.inflate(R.layout.call_log_list_item, parent, false);
+ findAndCacheViews(view);
+ return view;
+ }
+
+ @VisibleForTesting
+ @Override
+ public void bindGroupView(View view, Context context, Cursor cursor, int groupSize,
+ boolean expanded) {
+ bindView(view, cursor, groupSize);
+ }
+
+ private void findAndCacheViews(View view) {
+ // Get the views to bind to.
+ CallLogListItemViews views = CallLogListItemViews.fromView(view);
+ views.primaryActionView.setOnClickListener(mPrimaryActionListener);
+ views.secondaryActionView.setOnClickListener(mSecondaryActionListener);
+ view.setTag(views);
+ }
+
+ /**
+ * Binds the views in the entry to the data in the call log.
+ *
+ * @param view the view corresponding to this entry
+ * @param c the cursor pointing to the entry in the call log
+ * @param count the number of entries in the current item, greater than 1 if it is a group
+ */
+ private void bindView(View view, Cursor c, int count) {
+ final CallLogListItemViews views = (CallLogListItemViews) view.getTag();
+ final int section = c.getInt(CallLogQuery.SECTION);
+
+ // This might be a header: check the value of the section column in the cursor.
+ if (section == CallLogQuery.SECTION_NEW_HEADER
+ || section == CallLogQuery.SECTION_OLD_HEADER) {
+ views.listItemView.setVisibility(View.GONE);
+ views.listHeaderView.setVisibility(View.VISIBLE);
+ views.listHeaderTextView.setText(
+ section == CallLogQuery.SECTION_NEW_HEADER
+ ? R.string.call_log_new_header
+ : R.string.call_log_old_header);
+ // Nothing else to set up for a header.
+ return;
+ }
+ // Default case: an item in the call log.
+ views.listItemView.setVisibility(View.VISIBLE);
+ views.listHeaderView.setVisibility(View.GONE);
+
+ final String number = c.getString(CallLogQuery.NUMBER);
+ final long date = c.getLong(CallLogQuery.DATE);
+ final long duration = c.getLong(CallLogQuery.DURATION);
+ final int callType = c.getInt(CallLogQuery.CALL_TYPE);
+ final String formattedNumber;
+ final String countryIso = c.getString(CallLogQuery.COUNTRY_ISO);
+
+ final ContactInfo cachedContactInfo = getContactInfoFromCallLog(c);
+
+ 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);
+ final long rowId = c.getLong(CallLogQuery.ID);
+ views.secondaryActionView.setTag(
+ IntentProvider.getPlayVoicemailIntentProvider(rowId, voicemailUri));
+ } else if (!TextUtils.isEmpty(number)) {
+ // Store away the number so we can call it directly if you click on the call icon.
+ views.secondaryActionView.setTag(
+ IntentProvider.getReturnCallIntentProvider(number));
+ } else {
+ // No action enabled.
+ views.secondaryActionView.setTag(null);
+ }
+
+ // Lookup contacts with this number
+ ExpirableCache.CachedValue<ContactInfo> cachedInfo =
+ mContactInfoCache.getCachedValue(number);
+ ContactInfo info = cachedInfo == null ? null : cachedInfo.getValue();
+ if (cachedInfo == null) {
+ // Mark it as empty and queue up a request to find the name.
+ // The db request should happen on a non-UI thread.
+ info = ContactInfo.EMPTY;
+ mContactInfoCache.put(number, info);
+ // Request the contact details immediately since they are currently missing.
+ enqueueRequest(number, true);
+ // Format the phone number in the call log as best as we can.
+ formattedNumber = formatPhoneNumber(number, null, countryIso);
+ } else {
+ if (cachedInfo.isExpired()) {
+ // The contact info is no longer up to date, we should request it. However, we
+ // do not need to request them immediately.
+ enqueueRequest(number, false);
+ }
+
+ if (info != ContactInfo.EMPTY) {
+ // Format and cache phone number for found contact.
+ if (info.formattedNumber == null) {
+ info.formattedNumber =
+ formatPhoneNumber(info.number, info.normalizedNumber, countryIso);
+ }
+ formattedNumber = info.formattedNumber;
+ } else {
+ // Format the phone number in the call log as best as we can.
+ formattedNumber = formatPhoneNumber(number, null, countryIso);
+ }
+ }
+
+ if (info == null || info == ContactInfo.EMPTY) {
+ info = cachedContactInfo;
+ }
+
+ final long personId = info.personId;
+ final String name = info.name;
+ final int ntype = info.type;
+ final String label = info.label;
+ 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, geocode,
+ callTypes, date, duration);
+ } else {
+ details = new PhoneCallDetails(number, formattedNumber, countryIso, geocode,
+ callTypes, date, duration, name, ntype, label, personId, thumbnailUri);
+ }
+
+ final boolean isNew = CallLogQuery.isNewSection(c);
+ // New items also use the highlighted version of the text.
+ final boolean isHighlighted = isNew;
+ mCallLogViewsHelper.setPhoneCallDetails(views, details, isHighlighted);
+ setPhoto(views, thumbnailUri, personId, lookupKey);
+
+ // Listen for the first draw
+ if (mPreDrawListener == null) {
+ mFirst = true;
+ mPreDrawListener = this;
+ view.getViewTreeObserver().addOnPreDrawListener(this);
+ }
+ }
+
+ /** Returns the contact information as stored in the call log. */
+ private ContactInfo getContactInfoFromCallLog(Cursor c) {
+ ContactInfo info = new ContactInfo();
+ info.personId = -1;
+ info.name = c.getString(CallLogQuery.CACHED_NAME);
+ info.type = c.getInt(CallLogQuery.CACHED_NUMBER_TYPE);
+ info.label = c.getString(CallLogQuery.CACHED_NUMBER_LABEL);
+ // TODO: This should be added to the call log cached values.
+ info.number = c.getString(CallLogQuery.NUMBER);
+ info.formattedNumber = info.number;
+ info.normalizedNumber = info.number;
+ info.thumbnailUri = null;
+ info.lookupKey = null;
+ return info;
+ }
+
+ /**
+ * Returns the call types for the given number of items in the cursor.
+ * <p>
+ * It uses the next {@code count} rows in the cursor to extract the types.
+ * <p>
+ * It position in the cursor is unchanged by this function.
+ */
+ private int[] getCallTypes(Cursor cursor, int count) {
+ int position = cursor.getPosition();
+ int[] callTypes = new int[count];
+ for (int index = 0; index < count; ++index) {
+ callTypes[index] = cursor.getInt(CallLogQuery.CALL_TYPE);
+ cursor.moveToNext();
+ }
+ cursor.moveToPosition(position);
+ return callTypes;
+ }
+
+ private void setPhoto(CallLogListItemViews views, Uri thumbnailUri, long contactId,
+ String lookupKey) {
+ views.quickContactView.assignContactUri(contactId == -1 ? null :
+ Contacts.getLookupUri(contactId, lookupKey));
+ mContactPhotoManager.loadPhoto(views.quickContactView, thumbnailUri);
+ }
+
+ /**
+ * Sets whether processing of requests for contact details should be enabled.
+ * <p>
+ * This method should be called in tests to disable such processing of requests when not
+ * needed.
+ */
+ public void disableRequestProcessingForTest() {
+ mRequestProcessingDisabled = true;
+ }
+
+ public void injectContactInfoForTest(String number, ContactInfo contactInfo) {
+ mContactInfoCache.put(number, contactInfo);
+ }
+
+ @Override
+ public void addGroup(int cursorPosition, int size, boolean expanded) {
+ super.addGroup(cursorPosition, size, expanded);
+ }
+
+ /**
+ * Format the given phone number
+ *
+ * @param number the number to be formatted.
+ * @param normalizedNumber the normalized number of the given number.
+ * @param countryIso the ISO 3166-1 two letters country code, the country's
+ * convention will be used to format the number if the normalized
+ * phone is null.
+ *
+ * @return the formatted number, or the given number if it was formatted.
+ */
+ private String formatPhoneNumber(String number, String normalizedNumber,
+ String countryIso) {
+ if (TextUtils.isEmpty(number)) {
+ return "";
+ }
+ // If "number" is really a SIP address, don't try to do any formatting at all.
+ if (PhoneNumberUtils.isUriNumber(number)) {
+ return number;
+ }
+ if (TextUtils.isEmpty(countryIso)) {
+ countryIso = mCurrentCountryIso;
+ }
+ return PhoneNumberUtils.formatNumber(number, normalizedNumber, countryIso);
+ }
+
+ /*
+ * Get the number from the Contacts, if available, since sometimes
+ * the number provided by caller id may not be formatted properly
+ * depending on the carrier (roaming) in use at the time of the
+ * incoming call.
+ * Logic : If the caller-id number starts with a "+", use it
+ * Else if the number in the contacts starts with a "+", use that one
+ * Else if the number in the contacts is longer, use that one
+ */
+ public String getBetterNumberFromContacts(String number) {
+ String matchingNumber = null;
+ // Look in the cache first. If it's not found then query the Phones db
+ ContactInfo ci = mContactInfoCache.getPossiblyExpired(number);
+ if (ci != null && ci != ContactInfo.EMPTY) {
+ matchingNumber = ci.number;
+ } else {
+ try {
+ Cursor phonesCursor = mContext.getContentResolver().query(
+ Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, number),
+ PhoneQuery._PROJECTION, null, null, null);
+ if (phonesCursor != null) {
+ if (phonesCursor.moveToFirst()) {
+ matchingNumber = phonesCursor.getString(PhoneQuery.MATCHED_NUMBER);
+ }
+ phonesCursor.close();
+ }
+ } catch (Exception e) {
+ // Use the number from the call log
+ }
+ }
+ if (!TextUtils.isEmpty(matchingNumber) &&
+ (matchingNumber.startsWith("+")
+ || matchingNumber.length() > number.length())) {
+ number = matchingNumber;
+ }
+ return number;
+ }
+}
diff --git a/src/com/android/contacts/calllog/CallLogFragment.java b/src/com/android/contacts/calllog/CallLogFragment.java
index 2d0ddbd..a40379d 100644
--- a/src/com/android/contacts/calllog/CallLogFragment.java
+++ b/src/com/android/contacts/calllog/CallLogFragment.java
@@ -16,40 +16,27 @@
package com.android.contacts.calllog;
-import com.android.common.widget.GroupingListAdapter;
-import com.android.contacts.ContactPhotoManager;
import com.android.contacts.ContactsUtils;
-import com.android.contacts.PhoneCallDetails;
-import com.android.contacts.PhoneCallDetailsHelper;
import com.android.contacts.R;
import com.android.contacts.activities.DialtactsActivity;
import com.android.contacts.activities.DialtactsActivity.ViewPagerVisibilityListener;
import com.android.contacts.test.NeededForTesting;
-import com.android.contacts.util.ExpirableCache;
import com.android.contacts.voicemail.VoicemailStatusHelper;
import com.android.contacts.voicemail.VoicemailStatusHelper.StatusMessage;
import com.android.contacts.voicemail.VoicemailStatusHelperImpl;
import com.android.internal.telephony.CallerInfo;
import com.android.internal.telephony.ITelephony;
-import com.google.common.annotations.VisibleForTesting;
import android.app.KeyguardManager;
import android.app.ListFragment;
import android.content.Context;
import android.content.Intent;
-import android.content.res.Resources;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.provider.CallLog.Calls;
-import android.provider.ContactsContract.CommonDataKinds.SipAddress;
-import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.Data;
-import android.provider.ContactsContract.PhoneLookup;
import android.telephony.PhoneNumberUtils;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
@@ -60,108 +47,18 @@
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
import android.widget.ListView;
import android.widget.TextView;
-import java.util.LinkedList;
import java.util.List;
/**
* Displays a list of call log entries.
*/
public class CallLogFragment extends ListFragment implements ViewPagerVisibilityListener,
- CallLogQueryHandler.Listener {
+ CallLogQueryHandler.Listener, CallLogAdapter.CallFetcher {
private static final String TAG = "CallLogFragment";
- /** The size of the cache of contact info. */
- private static final int CONTACT_INFO_CACHE_SIZE = 100;
-
- /** The query for the call log table. */
- public static final class CallLogQuery {
- // If you alter this, you must also alter the method that inserts a fake row to the headers
- // in the CallLogQueryHandler class called createHeaderCursorFor().
- public static final String[] _PROJECTION = new String[] {
- Calls._ID,
- Calls.NUMBER,
- Calls.DATE,
- Calls.DURATION,
- Calls.TYPE,
- Calls.COUNTRY_ISO,
- Calls.VOICEMAIL_URI,
- Calls.GEOCODED_LOCATION,
- };
-
- public static final int ID = 0;
- public static final int NUMBER = 1;
- public static final int DATE = 2;
- public static final int DURATION = 3;
- 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.
- * <p>
- * This column identifies whether a row is a header or an actual item, and whether it is
- * part of the new or old calls.
- */
- public static final String SECTION_NAME = "section";
- /** The index of the "section" column in the projection. */
- 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. */
- public static final int SECTION_NEW_ITEM = 1;
- /** The value of the "section" column for the header of the old section. */
- public static final int SECTION_OLD_HEADER = 2;
- /** The value of the "section" column for the items of the old section. */
- public static final int SECTION_OLD_ITEM = 3;
-
- /** The call log projection including the section name. */
- public static final String[] EXTENDED_PROJECTION;
- static {
- EXTENDED_PROJECTION = new String[_PROJECTION.length + 1];
- System.arraycopy(_PROJECTION, 0, EXTENDED_PROJECTION, 0, _PROJECTION.length);
- EXTENDED_PROJECTION[_PROJECTION.length] = SECTION_NAME;
- }
-
- public static boolean isSectionHeader(Cursor cursor) {
- int section = cursor.getInt(CallLogQuery.SECTION);
- return section == CallLogQuery.SECTION_NEW_HEADER
- || section == CallLogQuery.SECTION_OLD_HEADER;
- }
-
- public static boolean isNewSection(Cursor cursor) {
- int section = cursor.getInt(CallLogQuery.SECTION);
- return section == CallLogQuery.SECTION_NEW_ITEM
- || section == CallLogQuery.SECTION_NEW_HEADER;
- }
- }
-
- /** The query to use for the phones table */
- private static final class PhoneQuery {
- public static final String[] _PROJECTION = new String[] {
- PhoneLookup._ID,
- PhoneLookup.DISPLAY_NAME,
- PhoneLookup.TYPE,
- PhoneLookup.LABEL,
- PhoneLookup.NUMBER,
- PhoneLookup.NORMALIZED_NUMBER,
- PhoneLookup.PHOTO_THUMBNAIL_URI,
- PhoneLookup.LOOKUP_KEY};
-
- public static final int PERSON_ID = 0;
- public static final int NAME = 1;
- public static final int PHONE_TYPE = 2;
- public static final int LABEL = 3;
- public static final int MATCHED_NUMBER = 4;
- public static final int NORMALIZED_NUMBER = 5;
- public static final int THUMBNAIL_URI = 6;
- public static final int LOOKUP_KEY = 7;
- }
-
private CallLogAdapter mAdapter;
private CallLogQueryHandler mCallLogQueryHandler;
private String mVoiceMailNumber;
@@ -177,702 +74,6 @@
private TextView mStatusMessageAction;
private KeyguardManager mKeyguardManager;
- public static final class ContactInfo {
- public long personId = -1;
- public String name;
- public int type;
- public String label;
- public String number;
- public String formattedNumber;
- public String normalizedNumber;
- public Uri thumbnailUri;
- public String lookupKey;
-
- public static ContactInfo EMPTY = new ContactInfo();
-
- @Override
- public int hashCode() {
- // Uses only name and personId to determine hashcode.
- // This should be sufficient to have a reasonable distribution of hash codes.
- // Moreover, there should be no two people with the same personId.
- final int prime = 31;
- int result = 1;
- result = prime * result + (int) (personId ^ (personId >>> 32));
- result = prime * result + ((name == null) ? 0 : name.hashCode());
- return result;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) return true;
- if (obj == null) return false;
- if (getClass() != obj.getClass()) return false;
- ContactInfo other = (ContactInfo) obj;
- if (personId != other.personId) return false;
- if (!TextUtils.equals(name, other.name)) return false;
- if (type != other.type) return false;
- if (!TextUtils.equals(label, other.label)) return false;
- if (!TextUtils.equals(number, other.number)) return false;
- // Ignore formatted number.
- if (!TextUtils.equals(normalizedNumber, other.normalizedNumber)) return false;
- if (!uriEquals(thumbnailUri, other.thumbnailUri)) return false;
- if (!TextUtils.equals(lookupKey, other.lookupKey)) return false;
- return true;
- }
-
- private static boolean uriEquals(Uri thumbnailUri1, Uri thumbnailUri2) {
- if (thumbnailUri1 == thumbnailUri2) return true;
- if (thumbnailUri1 == null) return false;
- return thumbnailUri1.equals(thumbnailUri2);
- }
- }
-
- public interface GroupCreator {
- public void addGroup(int cursorPosition, int size, boolean expanded);
- }
-
- public interface CallFetcher {
- public void fetchAllCalls();
- }
-
- /** Adapter class to fill in data for the Call Log */
- public static final class CallLogAdapter extends GroupingListAdapter
- implements Runnable, ViewTreeObserver.OnPreDrawListener, GroupCreator {
- /** The time in millis to delay starting the thread processing requests. */
- private static final int START_PROCESSING_REQUESTS_DELAY_MILLIS = 1000;
-
- private final Context mContext;
- private final String mCurrentCountryIso;
- private final CallFetcher mCallFetcher;
-
- /**
- * A cache of the contact details for the phone numbers in the call log.
- * <p>
- * The content of the cache is expired (but not purged) whenever the application comes to
- * the foreground.
- */
- private ExpirableCache<String, ContactInfo> mContactInfoCache;
-
- /**
- * List of requests to update contact details.
- * <p>
- * The requests are added when displaying the contacts and are processed by a background
- * thread.
- */
- private final LinkedList<String> mRequests;
-
- private volatile boolean mDone;
- private boolean mLoading = true;
- private ViewTreeObserver.OnPreDrawListener mPreDrawListener;
- private static final int REDRAW = 1;
- private static final int START_THREAD = 2;
- private boolean mFirst;
- private Thread mCallerIdThread;
-
- /** Instance of helper class for managing views. */
- private final CallLogListItemHelper mCallLogViewsHelper;
-
- /** Helper to set up contact photos. */
- private final ContactPhotoManager mContactPhotoManager;
- /** Helper to parse and process phone numbers. */
- private PhoneNumberHelper mPhoneNumberHelper;
- /** Helper to group call log entries. */
- private final CallLogGroupBuilder mCallLogGroupBuilder;
-
- /** Can be set to true by tests to disable processing of requests. */
- private volatile boolean mRequestProcessingDisabled = false;
-
- /** 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();
- if (intentProvider != null) {
- mContext.startActivity(intentProvider.getIntent(mContext));
- }
- }
- };
-
- @Override
- public boolean onPreDraw() {
- if (mFirst) {
- mHandler.sendEmptyMessageDelayed(START_THREAD,
- START_PROCESSING_REQUESTS_DELAY_MILLIS);
- mFirst = false;
- }
- return true;
- }
-
- private Handler mHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case REDRAW:
- notifyDataSetChanged();
- break;
- case START_THREAD:
- startRequestProcessing();
- break;
- }
- }
- };
-
- public CallLogAdapter(Context context, CallFetcher callFetcher,
- String currentCountryIso, String voicemailNumber) {
- super(context);
-
- mContext = context;
- mCurrentCountryIso = currentCountryIso;
- mCallFetcher = callFetcher;
-
- mContactInfoCache = ExpirableCache.create(CONTACT_INFO_CACHE_SIZE);
- mRequests = new LinkedList<String>();
- mPreDrawListener = null;
-
- Resources resources = mContext.getResources();
- CallTypeHelper callTypeHelper = new CallTypeHelper(resources);
-
- mContactPhotoManager = ContactPhotoManager.getInstance(mContext);
- mPhoneNumberHelper = new PhoneNumberHelper(resources, voicemailNumber);
- PhoneCallDetailsHelper phoneCallDetailsHelper = new PhoneCallDetailsHelper(
- resources, callTypeHelper, mPhoneNumberHelper);
- mCallLogViewsHelper =
- new CallLogListItemHelper(
- phoneCallDetailsHelper, mPhoneNumberHelper, resources);
- mCallLogGroupBuilder = new CallLogGroupBuilder(this);
- }
-
- /**
- * Requery on background thread when {@link Cursor} changes.
- */
- @Override
- protected void onContentChanged() {
- // When the content changes, always fetch all the calls, in case a new missed call came
- // in and we were filtering over voicemail only, so that we see the missed call.
- mCallFetcher.fetchAllCalls();
- }
-
- void setLoading(boolean loading) {
- mLoading = loading;
- }
-
- @Override
- public boolean isEmpty() {
- if (mLoading) {
- // We don't want the empty state to show when loading.
- return false;
- } else {
- return super.isEmpty();
- }
- }
-
- public ContactInfo getContactInfo(String number) {
- return mContactInfoCache.getPossiblyExpired(number);
- }
-
- public void startRequestProcessing() {
- if (mRequestProcessingDisabled) {
- return;
- }
-
- mDone = false;
- mCallerIdThread = new Thread(this, "CallLogContactLookup");
- mCallerIdThread.setPriority(Thread.MIN_PRIORITY);
- mCallerIdThread.start();
- }
-
- /**
- * Stops the background thread that processes updates and cancels any pending requests to
- * start it.
- * <p>
- * Should be called from the main thread to prevent a race condition between the request to
- * start the thread being processed and stopping the thread.
- */
- public void stopRequestProcessing() {
- // Remove any pending requests to start the processing thread.
- mHandler.removeMessages(START_THREAD);
- mDone = true;
- if (mCallerIdThread != null) mCallerIdThread.interrupt();
- }
-
- public void invalidateCache() {
- mContactInfoCache.expireAll();
- }
-
- private void enqueueRequest(String number, boolean immediate) {
- synchronized (mRequests) {
- if (!mRequests.contains(number)) {
- mRequests.add(number);
- mRequests.notifyAll();
- }
- }
- if (mFirst && immediate) {
- startRequestProcessing();
- mFirst = false;
- }
- }
-
- /**
- * Determines the contact information for the given SIP address.
- * <p>
- * It returns the contact info if found.
- * <p>
- * If no contact corresponds to the given SIP address, returns {@link ContactInfo#EMPTY}.
- * <p>
- * If the lookup fails for some other reason, it returns null.
- */
- private ContactInfo queryContactInfoForSipAddress(String sipAddress) {
- final ContactInfo info;
-
- // TODO: This code is duplicated from the
- // CallerInfoAsyncQuery class. To avoid that, could the
- // code here just use CallerInfoAsyncQuery, rather than
- // manually running ContentResolver.query() itself?
-
- // We look up SIP addresses directly in the Data table:
- Uri contactRef = Data.CONTENT_URI;
-
- // Note Data.DATA1 and SipAddress.SIP_ADDRESS are equivalent.
- //
- // Also note we use "upper(data1)" in the WHERE clause, and
- // uppercase the incoming SIP address, in order to do a
- // case-insensitive match.
- //
- // TODO: May also need to normalize by adding "sip:" as a
- // prefix, if we start storing SIP addresses that way in the
- // database.
- String selection = "upper(" + Data.DATA1 + ")=?"
- + " AND "
- + Data.MIMETYPE + "='" + SipAddress.CONTENT_ITEM_TYPE + "'";
- String[] selectionArgs = new String[] { sipAddress.toUpperCase() };
-
- Cursor dataTableCursor =
- mContext.getContentResolver().query(
- contactRef,
- null, // projection
- selection, // selection
- selectionArgs, // selectionArgs
- null); // sortOrder
-
- if (dataTableCursor != null) {
- if (dataTableCursor.moveToFirst()) {
- info = new ContactInfo();
-
- // TODO: we could slightly speed this up using an
- // explicit projection (and thus not have to do
- // those getColumnIndex() calls) but the benefit is
- // very minimal.
-
- // Note the Data.CONTACT_ID column here is
- // equivalent to the PERSON_ID_COLUMN_INDEX column
- // we use with "phonesCursor" below.
- info.personId = dataTableCursor.getLong(
- dataTableCursor.getColumnIndex(Data.CONTACT_ID));
- info.name = dataTableCursor.getString(
- dataTableCursor.getColumnIndex(Data.DISPLAY_NAME));
- // "type" and "label" are currently unused for SIP addresses
- info.type = SipAddress.TYPE_OTHER;
- info.label = null;
-
- // And "number" is the SIP address.
- // Note Data.DATA1 and SipAddress.SIP_ADDRESS are equivalent.
- info.number = dataTableCursor.getString(
- dataTableCursor.getColumnIndex(Data.DATA1));
- info.normalizedNumber = null; // meaningless for SIP addresses
- final String thumbnailUriString = dataTableCursor.getString(
- dataTableCursor.getColumnIndex(Data.PHOTO_THUMBNAIL_URI));
- info.thumbnailUri = thumbnailUriString == null
- ? null
- : Uri.parse(thumbnailUriString);
- info.lookupKey = dataTableCursor.getString(
- dataTableCursor.getColumnIndex(Data.LOOKUP_KEY));
- } else {
- info = ContactInfo.EMPTY;
- }
- dataTableCursor.close();
- } else {
- // Failed to fetch the data, ignore this request.
- info = null;
- }
- return info;
- }
-
- /**
- * Determines the contact information for the given phone number.
- * <p>
- * It returns the contact info if found.
- * <p>
- * If no contact corresponds to the given phone number, returns {@link ContactInfo#EMPTY}.
- * <p>
- * If the lookup fails for some other reason, it returns null.
- */
- private ContactInfo queryContactInfoForPhoneNumber(String number) {
- final ContactInfo info;
-
- // "number" is a regular phone number, so use the
- // PhoneLookup table:
- Cursor phonesCursor =
- mContext.getContentResolver().query(
- Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI,
- Uri.encode(number)),
- PhoneQuery._PROJECTION, null, null, null);
- if (phonesCursor != null) {
- if (phonesCursor.moveToFirst()) {
- info = new ContactInfo();
- info.personId = phonesCursor.getLong(PhoneQuery.PERSON_ID);
- info.name = phonesCursor.getString(PhoneQuery.NAME);
- info.type = phonesCursor.getInt(PhoneQuery.PHONE_TYPE);
- info.label = phonesCursor.getString(PhoneQuery.LABEL);
- info.number = phonesCursor
- .getString(PhoneQuery.MATCHED_NUMBER);
- info.normalizedNumber = phonesCursor
- .getString(PhoneQuery.NORMALIZED_NUMBER);
- final String thumbnailUriString = phonesCursor.getString(
- PhoneQuery.THUMBNAIL_URI);
- info.thumbnailUri = thumbnailUriString == null
- ? null
- : Uri.parse(thumbnailUriString);
- info.lookupKey = phonesCursor.getString(PhoneQuery.LOOKUP_KEY);
- } else {
- info = ContactInfo.EMPTY;
- }
- phonesCursor.close();
- } else {
- // Failed to fetch the data, ignore this request.
- info = null;
- }
- return info;
- }
-
- /**
- * Queries the appropriate content provider for the contact associated with the number.
- * <p>
- * The number might be either a SIP address or a phone number.
- * <p>
- * It returns true if it updated the content of the cache and we should therefore tell the
- * view to update its content.
- */
- private boolean queryContactInfo(String number) {
- final ContactInfo info;
-
- // Determine the contact info.
- if (PhoneNumberUtils.isUriNumber(number)) {
- // This "number" is really a SIP address.
- info = queryContactInfoForSipAddress(number);
- } else {
- info = queryContactInfoForPhoneNumber(number);
- }
-
- if (info == null) {
- // The lookup failed, just return without requesting to update the view.
- return false;
- }
-
- // Check the existing entry in the cache: only if it has changed we should update the
- // view.
- ContactInfo existingInfo = mContactInfoCache.getPossiblyExpired(number);
- boolean updated = !info.equals(existingInfo);
- if (updated) {
- // The formattedNumber is computed by the UI thread when needed. Since we updated
- // the details of the contact, set this value to null for now.
- info.formattedNumber = null;
- }
- // Store the data in the cache so that the UI thread can use to display it. Store it
- // even if it has not changed so that it is marked as not expired.
- mContactInfoCache.put(number, info);
- return updated;
- }
-
- /*
- * Handles requests for contact name and number type
- * @see java.lang.Runnable#run()
- */
- @Override
- public void run() {
- boolean needNotify = false;
- while (!mDone) {
- String number = null;
- synchronized (mRequests) {
- if (!mRequests.isEmpty()) {
- number = mRequests.removeFirst();
- } else {
- if (needNotify) {
- needNotify = false;
- mHandler.sendEmptyMessage(REDRAW);
- }
- try {
- mRequests.wait(1000);
- } catch (InterruptedException ie) {
- // Ignore and continue processing requests
- Thread.currentThread().interrupt();
- }
- }
- }
- if (!mDone && number != null && queryContactInfo(number)) {
- needNotify = true;
- }
- }
- }
-
- @Override
- protected void addGroups(Cursor cursor) {
- mCallLogGroupBuilder.addGroups(cursor);
- }
-
- @VisibleForTesting
- @Override
- public View newStandAloneView(Context context, ViewGroup parent) {
- LayoutInflater inflater =
- (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- View view = inflater.inflate(R.layout.call_log_list_item, parent, false);
- findAndCacheViews(view);
- return view;
- }
-
- @VisibleForTesting
- @Override
- public void bindStandAloneView(View view, Context context, Cursor cursor) {
- bindView(view, cursor, 1);
- }
-
- @VisibleForTesting
- @Override
- public View newChildView(Context context, ViewGroup parent) {
- LayoutInflater inflater =
- (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- View view = inflater.inflate(R.layout.call_log_list_item, parent, false);
- findAndCacheViews(view);
- return view;
- }
-
- @VisibleForTesting
- @Override
- public void bindChildView(View view, Context context, Cursor cursor) {
- bindView(view, cursor, 1);
- }
-
- @VisibleForTesting
- @Override
- public View newGroupView(Context context, ViewGroup parent) {
- LayoutInflater inflater =
- (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- View view = inflater.inflate(R.layout.call_log_list_item, parent, false);
- findAndCacheViews(view);
- return view;
- }
-
- @VisibleForTesting
- @Override
- public void bindGroupView(View view, Context context, Cursor cursor, int groupSize,
- boolean expanded) {
- bindView(view, cursor, groupSize);
- }
-
- private void findAndCacheViews(View view) {
- // Get the views to bind to.
- CallLogListItemViews views = CallLogListItemViews.fromView(view);
- views.primaryActionView.setOnClickListener(mPrimaryActionListener);
- views.secondaryActionView.setOnClickListener(mSecondaryActionListener);
- view.setTag(views);
- }
-
- /**
- * Binds the views in the entry to the data in the call log.
- *
- * @param view the view corresponding to this entry
- * @param c the cursor pointing to the entry in the call log
- * @param count the number of entries in the current item, greater than 1 if it is a group
- */
- private void bindView(View view, Cursor c, int count) {
- final CallLogListItemViews views = (CallLogListItemViews) view.getTag();
- final int section = c.getInt(CallLogQuery.SECTION);
-
- // This might be a header: check the value of the section column in the cursor.
- if (section == CallLogQuery.SECTION_NEW_HEADER
- || section == CallLogQuery.SECTION_OLD_HEADER) {
- views.listItemView.setVisibility(View.GONE);
- views.listHeaderView.setVisibility(View.VISIBLE);
- views.listHeaderTextView.setText(
- section == CallLogQuery.SECTION_NEW_HEADER
- ? R.string.call_log_new_header
- : R.string.call_log_old_header);
- // Nothing else to set up for a header.
- return;
- }
- // Default case: an item in the call log.
- views.listItemView.setVisibility(View.VISIBLE);
- views.listHeaderView.setVisibility(View.GONE);
-
- final String number = c.getString(CallLogQuery.NUMBER);
- final long date = c.getLong(CallLogQuery.DATE);
- final long duration = c.getLong(CallLogQuery.DURATION);
- final int callType = c.getInt(CallLogQuery.CALL_TYPE);
- 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);
- final long rowId = c.getLong(CallLogQuery.ID);
- views.secondaryActionView.setTag(
- IntentProvider.getPlayVoicemailIntentProvider(rowId, voicemailUri));
- } else if (!TextUtils.isEmpty(number)) {
- // Store away the number so we can call it directly if you click on the call icon.
- views.secondaryActionView.setTag(
- IntentProvider.getReturnCallIntentProvider(number));
- } else {
- // No action enabled.
- views.secondaryActionView.setTag(null);
- }
-
- // Lookup contacts with this number
- ExpirableCache.CachedValue<ContactInfo> cachedInfo =
- mContactInfoCache.getCachedValue(number);
- ContactInfo info = cachedInfo == null ? null : cachedInfo.getValue();
- if (cachedInfo == null) {
- // Mark it as empty and queue up a request to find the name.
- // The db request should happen on a non-UI thread.
- info = ContactInfo.EMPTY;
- mContactInfoCache.put(number, info);
- // Request the contact details immediately since they are currently missing.
- enqueueRequest(number, true);
- // Format the phone number in the call log as best as we can.
- formattedNumber = formatPhoneNumber(number, null, countryIso);
- } else {
- if (cachedInfo.isExpired()) {
- // The contact info is no longer up to date, we should request it. However, we
- // do not need to request them immediately.
- enqueueRequest(number, false);
- }
-
- if (info != ContactInfo.EMPTY) {
- // Format and cache phone number for found contact.
- if (info.formattedNumber == null) {
- info.formattedNumber =
- formatPhoneNumber(info.number, info.normalizedNumber, countryIso);
- }
- formattedNumber = info.formattedNumber;
- } else {
- // Format the phone number in the call log as best as we can.
- formattedNumber = formatPhoneNumber(number, null, countryIso);
- }
- }
-
- final long personId = info.personId;
- final String name = info.name;
- final int ntype = info.type;
- final String label = info.label;
- 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, geocode,
- callTypes, date, duration);
- } else {
- details = new PhoneCallDetails(number, formattedNumber, countryIso, geocode,
- callTypes, date, duration, name, ntype, label, personId, thumbnailUri);
- }
-
- final boolean isNew = CallLogQuery.isNewSection(c);
- // New items also use the highlighted version of the text.
- final boolean isHighlighted = isNew;
- mCallLogViewsHelper.setPhoneCallDetails(views, details, isHighlighted);
- setPhoto(views, thumbnailUri, personId, lookupKey);
-
- // Listen for the first draw
- if (mPreDrawListener == null) {
- mFirst = true;
- mPreDrawListener = this;
- view.getViewTreeObserver().addOnPreDrawListener(this);
- }
- }
-
- /**
- * Returns the call types for the given number of items in the cursor.
- * <p>
- * It uses the next {@code count} rows in the cursor to extract the types.
- * <p>
- * It position in the cursor is unchanged by this function.
- */
- private int[] getCallTypes(Cursor cursor, int count) {
- int position = cursor.getPosition();
- int[] callTypes = new int[count];
- for (int index = 0; index < count; ++index) {
- callTypes[index] = cursor.getInt(CallLogQuery.CALL_TYPE);
- cursor.moveToNext();
- }
- cursor.moveToPosition(position);
- return callTypes;
- }
-
- private void setPhoto(CallLogListItemViews views, Uri thumbnailUri, long contactId,
- String lookupKey) {
- views.quickContactView.assignContactUri(contactId == -1 ? null :
- Contacts.getLookupUri(contactId, lookupKey));
- mContactPhotoManager.loadPhoto(views.quickContactView, thumbnailUri);
- }
-
- /**
- * Sets whether processing of requests for contact details should be enabled.
- * <p>
- * This method should be called in tests to disable such processing of requests when not
- * needed.
- */
- public void disableRequestProcessingForTest() {
- mRequestProcessingDisabled = true;
- }
-
- public void injectContactInfoForTest(String number, ContactInfo contactInfo) {
- mContactInfoCache.put(number, contactInfo);
- }
-
- @Override
- public void addGroup(int cursorPosition, int size, boolean expanded) {
- super.addGroup(cursorPosition, size, expanded);
- }
-
- /**
- * Format the given phone number
- *
- * @param number the number to be formatted.
- * @param normalizedNumber the normalized number of the given number.
- * @param countryIso the ISO 3166-1 two letters country code, the country's
- * convention will be used to format the number if the normalized
- * phone is null.
- *
- * @return the formatted number, or the given number if it was formatted.
- */
- private String formatPhoneNumber(String number, String normalizedNumber,
- String countryIso) {
- if (TextUtils.isEmpty(number)) {
- return "";
- }
- // If "number" is really a SIP address, don't try to do any formatting at all.
- if (PhoneNumberUtils.isUriNumber(number)) {
- return number;
- }
- if (TextUtils.isEmpty(countryIso)) {
- countryIso = mCurrentCountryIso;
- }
- return PhoneNumberUtils.formatNumber(number, normalizedNumber, countryIso);
- }
- }
-
@Override
public void onCreate(Bundle state) {
super.onCreate(state);
@@ -928,13 +129,7 @@
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
String currentCountryIso = ContactsUtils.getCurrentCountryIso(getActivity());
- mAdapter = new CallLogAdapter(getActivity(),
- new CallFetcher() {
- @Override
- public void fetchAllCalls() {
- startCallsQuery();
- }
- }, currentCountryIso, mVoiceMailNumber);
+ mAdapter = new CallLogAdapter(getActivity(), this, currentCountryIso, mVoiceMailNumber);
setListAdapter(mAdapter);
getListView().setItemsCanFocus(true);
}
@@ -1000,7 +195,8 @@
mAdapter.changeCursor(null);
}
- private void startCallsQuery() {
+ @Override
+ public void startCallsQuery() {
mAdapter.setLoading(true);
mCallLogQueryHandler.fetchAllCalls();
if (mShowingVoicemailOnly) {
@@ -1052,45 +248,6 @@
return false;
}
}
-
- /*
- * Get the number from the Contacts, if available, since sometimes
- * the number provided by caller id may not be formatted properly
- * depending on the carrier (roaming) in use at the time of the
- * incoming call.
- * Logic : If the caller-id number starts with a "+", use it
- * Else if the number in the contacts starts with a "+", use that one
- * Else if the number in the contacts is longer, use that one
- */
- private String getBetterNumberFromContacts(String number) {
- String matchingNumber = null;
- // Look in the cache first. If it's not found then query the Phones db
- ContactInfo ci = mAdapter.mContactInfoCache.getPossiblyExpired(number);
- if (ci != null && ci != ContactInfo.EMPTY) {
- matchingNumber = ci.number;
- } else {
- try {
- Cursor phonesCursor = getActivity().getContentResolver().query(
- Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, number),
- PhoneQuery._PROJECTION, null, null, null);
- if (phonesCursor != null) {
- if (phonesCursor.moveToFirst()) {
- matchingNumber = phonesCursor.getString(PhoneQuery.MATCHED_NUMBER);
- }
- phonesCursor.close();
- }
- } catch (Exception e) {
- // Use the number from the call log
- }
- }
- if (!TextUtils.isEmpty(matchingNumber) &&
- (matchingNumber.startsWith("+")
- || matchingNumber.length() > number.length())) {
- number = matchingNumber;
- }
- return number;
- }
-
public void callSelectedEntry() {
int position = getListView().getSelectedItemPosition();
if (position < 0) {
@@ -1122,7 +279,7 @@
(callType == Calls.INCOMING_TYPE
|| callType == Calls.MISSED_TYPE)) {
// If the caller-id matches a contact with a better qualified number, use it
- number = getBetterNumberFromContacts(number);
+ number = mAdapter.getBetterNumberFromContacts(number);
}
intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
Uri.fromParts("tel", number, null));
@@ -1162,7 +319,6 @@
mAdapter.invalidateCache();
startCallsQuery();
startVoicemailStatusQuery();
- mAdapter.mPreDrawListener = null; // Let it restart the thread after next draw
updateOnEntry();
}
diff --git a/src/com/android/contacts/calllog/CallLogGroupBuilder.java b/src/com/android/contacts/calllog/CallLogGroupBuilder.java
index f5aef3f..a7f8fa3 100644
--- a/src/com/android/contacts/calllog/CallLogGroupBuilder.java
+++ b/src/com/android/contacts/calllog/CallLogGroupBuilder.java
@@ -17,7 +17,6 @@
package com.android.contacts.calllog;
import com.android.common.widget.GroupingListAdapter;
-import com.android.contacts.calllog.CallLogFragment.CallLogQuery;
import android.database.CharArrayBuffer;
import android.database.Cursor;
@@ -30,15 +29,19 @@
* This class is meant to be used in conjunction with {@link GroupingListAdapter}.
*/
public class CallLogGroupBuilder {
+ public interface GroupCreator {
+ public void addGroup(int cursorPosition, int size, boolean expanded);
+ }
+
/** Reusable char array buffer. */
private CharArrayBuffer mBuffer1 = new CharArrayBuffer(128);
/** Reusable char array buffer. */
private CharArrayBuffer mBuffer2 = new CharArrayBuffer(128);
/** The object on which the groups are created. */
- private final CallLogFragment.GroupCreator mGroupCreator;
+ private final GroupCreator mGroupCreator;
- public CallLogGroupBuilder(CallLogFragment.GroupCreator groupCreator) {
+ public CallLogGroupBuilder(GroupCreator groupCreator) {
mGroupCreator = groupCreator;
}
@@ -74,7 +77,7 @@
final boolean sameNumber = equalPhoneNumbers(firstNumber, currentNumber);
final boolean shouldGroup;
- if (CallLogFragment.CallLogQuery.isSectionHeader(cursor)) {
+ if (CallLogQuery.isSectionHeader(cursor)) {
// Cannot group headers.
shouldGroup = false;
} else if (!sameNumber) {
@@ -120,7 +123,7 @@
* <p>
* The group is always unexpanded.
*
- * @see CallLogFragment.CallLogAdapter#addGroup(int, int, boolean)
+ * @see CallLogAdapter#addGroup(int, int, boolean)
*/
private void addGroup(int cursorPosition, int size) {
mGroupCreator.addGroup(cursorPosition, size, false);
diff --git a/src/com/android/contacts/calllog/CallLogNotificationsService.java b/src/com/android/contacts/calllog/CallLogNotificationsService.java
index 6dd24aa..be540ad 100644
--- a/src/com/android/contacts/calllog/CallLogNotificationsService.java
+++ b/src/com/android/contacts/calllog/CallLogNotificationsService.java
@@ -26,8 +26,8 @@
* <p>
* It handles the following actions:
* <ul>
- * <li>{@link #ACTION_MARK_NEW_CALLS_AS_OLD}: marks all the new items in the call log as old; this
- * is called when a notification is dismissed.</li>
+ * <li>{@link #ACTION_MARK_NEW_VOICEMAILS_AS_OLD}: marks all the new voicemails in the call log as
+ * old; this is called when a notification is dismissed.</li>
* <li>{@link #ACTION_UPDATE_NOTIFICATIONS}: updates the content of the new items notification; it
* may include an optional extra {@link #EXTRA_NEW_VOICEMAIL_URI}, containing the URI of the new
* voicemail that has triggered this update (if any).</li>
@@ -36,11 +36,9 @@
public class CallLogNotificationsService extends IntentService {
private static final String TAG = "CallLogNotificationsService";
- /**
- * Action to mark all the new calls as old.
- */
- public static final String ACTION_MARK_NEW_CALLS_AS_OLD =
- "com.android.contacts.calllog.MARK_NEW_CALLS_AS_OLD";
+ /** Action to mark all the new voicemails as old. */
+ public static final String ACTION_MARK_NEW_VOICEMAILS_AS_OLD =
+ "com.android.contacts.calllog.ACTION_MARK_NEW_VOICEMAILS_AS_OLD";
/**
* Action to update the notifications.
@@ -72,8 +70,8 @@
@Override
protected void onHandleIntent(Intent intent) {
- if (ACTION_MARK_NEW_CALLS_AS_OLD.equals(intent.getAction())) {
- mCallLogQueryHandler.markNewCallsAsOld();
+ if (ACTION_MARK_NEW_VOICEMAILS_AS_OLD.equals(intent.getAction())) {
+ mCallLogQueryHandler.markNewVoicemailsAsOld();
} else if (ACTION_UPDATE_NOTIFICATIONS.equals(intent.getAction())) {
Uri voicemailUri = (Uri) intent.getParcelableExtra(EXTRA_NEW_VOICEMAIL_URI);
DefaultVoicemailNotifier.getInstance(this).updateNotification(voicemailUri);
diff --git a/src/com/android/contacts/calllog/CallLogQuery.java b/src/com/android/contacts/calllog/CallLogQuery.java
new file mode 100644
index 0000000..f596032
--- /dev/null
+++ b/src/com/android/contacts/calllog/CallLogQuery.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.contacts.calllog;
+
+import android.database.Cursor;
+import android.provider.CallLog.Calls;
+
+/**
+ * The query for the call log table.
+ */
+public final class CallLogQuery {
+ // If you alter this, you must also alter the method that inserts a fake row to the headers
+ // in the CallLogQueryHandler class called createHeaderCursorFor().
+ public static final String[] _PROJECTION = new String[] {
+ Calls._ID,
+ Calls.NUMBER,
+ Calls.DATE,
+ Calls.DURATION,
+ Calls.TYPE,
+ Calls.COUNTRY_ISO,
+ Calls.VOICEMAIL_URI,
+ Calls.GEOCODED_LOCATION,
+ Calls.CACHED_NAME,
+ Calls.CACHED_NUMBER_TYPE,
+ Calls.CACHED_NUMBER_LABEL,
+ };
+
+ public static final int ID = 0;
+ public static final int NUMBER = 1;
+ public static final int DATE = 2;
+ public static final int DURATION = 3;
+ 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;
+ public static final int CACHED_NAME = 8;
+ public static final int CACHED_NUMBER_TYPE = 9;
+ public static final int CACHED_NUMBER_LABEL = 10;
+
+ /**
+ * The name of the synthetic "section" column.
+ * <p>
+ * This column identifies whether a row is a header or an actual item, and whether it is
+ * part of the new or old calls.
+ */
+ public static final String SECTION_NAME = "section";
+ /** The index of the "section" column in the projection. */
+ public static final int SECTION = 11;
+ /** 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. */
+ public static final int SECTION_NEW_ITEM = 1;
+ /** The value of the "section" column for the header of the old section. */
+ public static final int SECTION_OLD_HEADER = 2;
+ /** The value of the "section" column for the items of the old section. */
+ public static final int SECTION_OLD_ITEM = 3;
+
+ /** The call log projection including the section name. */
+ public static final String[] EXTENDED_PROJECTION;
+ static {
+ EXTENDED_PROJECTION = new String[_PROJECTION.length + 1];
+ System.arraycopy(_PROJECTION, 0, EXTENDED_PROJECTION, 0, _PROJECTION.length);
+ EXTENDED_PROJECTION[_PROJECTION.length] = SECTION_NAME;
+ }
+
+ public static boolean isSectionHeader(Cursor cursor) {
+ int section = cursor.getInt(CallLogQuery.SECTION);
+ return section == CallLogQuery.SECTION_NEW_HEADER
+ || section == CallLogQuery.SECTION_OLD_HEADER;
+ }
+
+ public static boolean isNewSection(Cursor cursor) {
+ int section = cursor.getInt(CallLogQuery.SECTION);
+ return section == CallLogQuery.SECTION_NEW_ITEM
+ || section == CallLogQuery.SECTION_NEW_HEADER;
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/contacts/calllog/CallLogQueryHandler.java b/src/com/android/contacts/calllog/CallLogQueryHandler.java
index b4e4248..25beba5 100644
--- a/src/com/android/contacts/calllog/CallLogQueryHandler.java
+++ b/src/com/android/contacts/calllog/CallLogQueryHandler.java
@@ -17,7 +17,6 @@
package com.android.contacts.calllog;
import com.android.common.io.MoreCloseables;
-import com.android.contacts.calllog.CallLogFragment.CallLogQuery;
import com.android.contacts.voicemail.VoicemailStatusHelperImpl;
import android.content.AsyncQueryHandler;
@@ -51,12 +50,13 @@
private static final int QUERY_OLD_CALLS_TOKEN = 54;
/** The token for the query to mark all missed calls as old after seeing the call log. */
private static final int UPDATE_MARK_AS_OLD_TOKEN = 55;
+ /** The token for the query to mark all new voicemails as old. */
+ private static final int UPDATE_MARK_VOICEMAILS_AS_OLD_TOKEN = 56;
/** The token for the query to mark all missed calls as read after seeing the call log. */
- private static final int UPDATE_MARK_MISSED_CALL_AS_READ_TOKEN = 56;
+ private static final int UPDATE_MARK_MISSED_CALL_AS_READ_TOKEN = 57;
/** The token for the query to fetch voicemail status messages. */
- private static final int QUERY_VOICEMAIL_STATUS_TOKEN = 57;
-
+ private static final int QUERY_VOICEMAIL_STATUS_TOKEN = 58;
private final WeakReference<Listener> mListener;
@@ -103,10 +103,10 @@
/** Creates a cursor that contains a single row and maps the section to the given value. */
private Cursor createHeaderCursorFor(int section) {
MatrixCursor matrixCursor =
- new MatrixCursor(CallLogFragment.CallLogQuery.EXTENDED_PROJECTION);
+ new MatrixCursor(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, "", "", "", null, 0, null, section });
return matrixCursor;
}
@@ -192,6 +192,22 @@
values, where.toString(), null);
}
+ /** Updates all new voicemails to mark them as old. */
+ public void markNewVoicemailsAsOld() {
+ // Mark all "new" voicemails as not new anymore.
+ StringBuilder where = new StringBuilder();
+ where.append(Calls.NEW);
+ where.append(" = 1 AND ");
+ where.append(Calls.TYPE);
+ where.append(" = ?");
+
+ ContentValues values = new ContentValues(1);
+ values.put(Calls.NEW, "0");
+
+ startUpdate(UPDATE_MARK_VOICEMAILS_AS_OLD_TOKEN, null, Calls.CONTENT_URI_WITH_VOICEMAIL,
+ values, where.toString(), new String[]{ Integer.toString(Calls.VOICEMAIL_TYPE) });
+ }
+
/** Updates all missed calls to mark them as read. */
public void markMissedCallsAsRead() {
// Mark all "new" calls as not new anymore.
diff --git a/src/com/android/contacts/calllog/ContactInfo.java b/src/com/android/contacts/calllog/ContactInfo.java
new file mode 100644
index 0000000..1f106e4
--- /dev/null
+++ b/src/com/android/contacts/calllog/ContactInfo.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.contacts.calllog;
+
+import android.net.Uri;
+import android.text.TextUtils;
+
+/**
+ * Information for a contact as needed by the Call Log.
+ */
+public final class ContactInfo {
+ public long personId = -1;
+ public String name;
+ public int type;
+ public String label;
+ public String number;
+ public String formattedNumber;
+ public String normalizedNumber;
+ public Uri thumbnailUri;
+ public String lookupKey;
+
+ public static ContactInfo EMPTY = new ContactInfo();
+
+ @Override
+ public int hashCode() {
+ // Uses only name and personId to determine hashcode.
+ // This should be sufficient to have a reasonable distribution of hash codes.
+ // Moreover, there should be no two people with the same personId.
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + (int) (personId ^ (personId >>> 32));
+ result = prime * result + ((name == null) ? 0 : name.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ if (obj == null) return false;
+ if (getClass() != obj.getClass()) return false;
+ ContactInfo other = (ContactInfo) obj;
+ if (personId != other.personId) return false;
+ if (!TextUtils.equals(name, other.name)) return false;
+ if (type != other.type) return false;
+ if (!TextUtils.equals(label, other.label)) return false;
+ if (!TextUtils.equals(number, other.number)) return false;
+ // Ignore formatted number.
+ if (!TextUtils.equals(normalizedNumber, other.normalizedNumber)) return false;
+ if (!uriEquals(thumbnailUri, other.thumbnailUri)) return false;
+ if (!TextUtils.equals(lookupKey, other.lookupKey)) return false;
+ return true;
+ }
+
+ private static boolean uriEquals(Uri thumbnailUri1, Uri thumbnailUri2) {
+ if (thumbnailUri1 == thumbnailUri2) return true;
+ if (thumbnailUri1 == null) return false;
+ return thumbnailUri1.equals(thumbnailUri2);
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/contacts/calllog/DefaultVoicemailNotifier.java b/src/com/android/contacts/calllog/DefaultVoicemailNotifier.java
index 1cf67c1..f795a9c 100644
--- a/src/com/android/contacts/calllog/DefaultVoicemailNotifier.java
+++ b/src/com/android/contacts/calllog/DefaultVoicemailNotifier.java
@@ -153,7 +153,7 @@
.setContentTitle(title)
.setContentText(callers)
.setDefaults(callToNotify != null ? Notification.DEFAULT_ALL : 0)
- .setDeleteIntent(createMarkNewCallsAsOld())
+ .setDeleteIntent(createMarkNewVoicemailsAsOldIntent())
.setAutoCancel(true)
.getNotification();
@@ -182,10 +182,10 @@
mNotificationManager.notify(NOTIFICATION_TAG, NOTIFICATION_ID, notification);
}
- /** Creates a pending intent that marks all new calls as old. */
- private PendingIntent createMarkNewCallsAsOld() {
+ /** Creates a pending intent that marks all new voicemails as old. */
+ private PendingIntent createMarkNewVoicemailsAsOldIntent() {
Intent intent = new Intent(mContext, CallLogNotificationsService.class);
- intent.setAction(CallLogNotificationsService.ACTION_MARK_NEW_CALLS_AS_OLD);
+ intent.setAction(CallLogNotificationsService.ACTION_MARK_NEW_VOICEMAILS_AS_OLD);
return PendingIntent.getService(mContext, 0, intent, 0);
}
diff --git a/src/com/android/contacts/calllog/IntentProvider.java b/src/com/android/contacts/calllog/IntentProvider.java
index 9ce3d3d..bfee5ec 100644
--- a/src/com/android/contacts/calllog/IntentProvider.java
+++ b/src/com/android/contacts/calllog/IntentProvider.java
@@ -17,8 +17,6 @@
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;
diff --git a/src/com/android/contacts/calllog/PhoneQuery.java b/src/com/android/contacts/calllog/PhoneQuery.java
new file mode 100644
index 0000000..52faa8b
--- /dev/null
+++ b/src/com/android/contacts/calllog/PhoneQuery.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.contacts.calllog;
+
+import android.provider.ContactsContract.PhoneLookup;
+
+/**
+ * The query to look up the {@link ContactInfo} for a given number in the Call Log.
+ */
+final class PhoneQuery {
+ public static final String[] _PROJECTION = new String[] {
+ PhoneLookup._ID,
+ PhoneLookup.DISPLAY_NAME,
+ PhoneLookup.TYPE,
+ PhoneLookup.LABEL,
+ PhoneLookup.NUMBER,
+ PhoneLookup.NORMALIZED_NUMBER,
+ PhoneLookup.PHOTO_THUMBNAIL_URI,
+ PhoneLookup.LOOKUP_KEY};
+
+ public static final int PERSON_ID = 0;
+ public static final int NAME = 1;
+ public static final int PHONE_TYPE = 2;
+ public static final int LABEL = 3;
+ public static final int MATCHED_NUMBER = 4;
+ public static final int NORMALIZED_NUMBER = 5;
+ public static final int THUMBNAIL_URI = 6;
+ public static final int LOOKUP_KEY = 7;
+}
\ No newline at end of file
diff --git a/src/com/android/contacts/detail/CarouselTab.java b/src/com/android/contacts/detail/CarouselTab.java
index 9b8efd5..26397ff 100644
--- a/src/com/android/contacts/detail/CarouselTab.java
+++ b/src/com/android/contacts/detail/CarouselTab.java
@@ -94,8 +94,6 @@
@Override
public void setAlphaLayerValue(float alpha) {
- if (mAlphaLayer != null) {
- mAlphaLayer.setAlpha(alpha);
- }
+ ContactDetailDisplayUtils.setAlphaOnViewBackground(mAlphaLayer, alpha);
}
}
diff --git a/src/com/android/contacts/detail/ContactDetailDisplayUtils.java b/src/com/android/contacts/detail/ContactDetailDisplayUtils.java
index 131f052..cbdf148 100644
--- a/src/com/android/contacts/detail/ContactDetailDisplayUtils.java
+++ b/src/com/android/contacts/detail/ContactDetailDisplayUtils.java
@@ -452,4 +452,15 @@
}
}
}
+
+ /**
+ * Sets an alpha value on the view.
+ */
+ public static void setAlphaOnViewBackground(View view, float alpha) {
+ if (view != null) {
+ // Convert alpha layer to a black background HEX color with an alpha value for better
+ // performance (i.e. use setBackgroundColor() instead of setAlpha())
+ view.setBackgroundColor((int) (alpha * 255) << 24);
+ }
+ }
}
diff --git a/src/com/android/contacts/detail/ContactDetailFragment.java b/src/com/android/contacts/detail/ContactDetailFragment.java
index 88ae7da..bbf9d5b 100644
--- a/src/com/android/contacts/detail/ContactDetailFragment.java
+++ b/src/com/android/contacts/detail/ContactDetailFragment.java
@@ -315,9 +315,7 @@
@Override
public void setAlphaLayerValue(float alpha) {
- if (mAlphaLayer != null) {
- mAlphaLayer.setAlpha(alpha);
- }
+ ContactDetailDisplayUtils.setAlphaOnViewBackground(mAlphaLayer, alpha);
}
@Override
diff --git a/src/com/android/contacts/detail/ContactDetailFragmentCarousel.java b/src/com/android/contacts/detail/ContactDetailFragmentCarousel.java
index 561d44e..7efcc51 100644
--- a/src/com/android/contacts/detail/ContactDetailFragmentCarousel.java
+++ b/src/com/android/contacts/detail/ContactDetailFragmentCarousel.java
@@ -98,27 +98,16 @@
mAllowedHorizontalScrollLength = (2 * fragmentWidth) - screenWidth;
mLowerThreshold = (screenWidth - fragmentWidth) / 2;
mUpperThreshold = mAllowedHorizontalScrollLength - mLowerThreshold;
-
- // Snap to the current page now that the allowed horizontal scroll length has been
- // computed.
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- if (isAttachedToWindow() && mAboutFragment != null &&
- mUpdatesFragment != null) {
- snapToEdge();
- }
- }
- });
}
}
public void setCurrentPage(int pageIndex) {
if (mCurrentPage != pageIndex) {
mCurrentPage = pageIndex;
- if (isAttachedToWindow() && mAboutFragment != null && mUpdatesFragment != null) {
- snapToEdge();
- }
+
+ // This method could have been called before the view has been measured, so snap to edge
+ // only after the view is ready.
+ postRunnableToSnapToEdge();
}
}
@@ -131,7 +120,26 @@
mUpdatesFragment.enableAlphaLayer();
mUpdatesFragment.setAlphaLayerValue(mCurrentPage == UPDATES_PAGE ? 0 : MAX_ALPHA);
- snapToEdge();
+ // This method could have been called before the view has been measured, so snap to edge
+ // only after the view is ready.
+ postRunnableToSnapToEdge();
+ }
+
+ /**
+ * Snap to the currently selected page only once all the view setup and measurement has
+ * completed (i.e. we need to know the allowed horizontal scroll width in order to
+ * snap to the correct page).
+ */
+ private void postRunnableToSnapToEdge() {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (isAttachedToWindow() && mAboutFragment != null &&
+ mUpdatesFragment != null) {
+ snapToEdge();
+ }
+ }
+ });
}
public int getCurrentPage() {
diff --git a/src/com/android/contacts/detail/ContactDetailTabCarousel.java b/src/com/android/contacts/detail/ContactDetailTabCarousel.java
index cc4f0ff..5daca05 100644
--- a/src/com/android/contacts/detail/ContactDetailTabCarousel.java
+++ b/src/com/android/contacts/detail/ContactDetailTabCarousel.java
@@ -162,7 +162,7 @@
mAboutTab.setAlphaLayerValue(mLastScrollPosition * MAX_ALPHA /
mAllowedHorizontalScrollLength);
mUpdatesTab.setAlphaLayerValue(MAX_ALPHA - mLastScrollPosition * MAX_ALPHA /
- mAllowedVerticalScrollLength);
+ mAllowedHorizontalScrollLength);
}
@Override
diff --git a/src/com/android/contacts/detail/ContactDetailUpdatesFragment.java b/src/com/android/contacts/detail/ContactDetailUpdatesFragment.java
index 02bd314..ba86f9f 100644
--- a/src/com/android/contacts/detail/ContactDetailUpdatesFragment.java
+++ b/src/com/android/contacts/detail/ContactDetailUpdatesFragment.java
@@ -19,12 +19,16 @@
import com.android.contacts.ContactLoader;
import com.android.contacts.R;
import com.android.contacts.activities.ContactDetailActivity.FragmentKeyListener;
+import com.android.contacts.model.AccountType;
+import com.android.contacts.model.AccountTypeManager;
import com.android.contacts.util.StreamItemEntry;
import android.app.ListFragment;
+import android.content.ContentUris;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
+import android.provider.ContactsContract.StreamItems;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@@ -69,27 +73,16 @@
// Ignore if this item does not have a stream item associated with it.
return;
}
- String actionUri = streamItemEntry.getActionUri();
- if (actionUri == null) {
- // Ignore if this item does not have a URI.
- return;
- }
- // Parse the URI.
- Uri uri;
- try {
- uri = Uri.parse(actionUri);
- } catch (Throwable throwable) {
- // This may fail if the URI is invalid: instead of failing, just ignore it.
- Log.e(TAG, "invalid URI for stream item #" + streamItemEntry.getId() + ": "
- + actionUri);
- return;
- }
- String action = streamItemEntry.getAction();
- if (action == null) {
- // Ignore if this item does not have an action.
- return;
- }
- startActivity(new Intent(action, uri));
+ final AccountTypeManager manager = AccountTypeManager.getInstance(getActivity());
+ final AccountType accountType = manager.getAccountType(
+ streamItemEntry.getAccountType(), streamItemEntry.getDataSet());
+
+ final Uri uri = ContentUris.withAppendedId(StreamItems.CONTENT_URI,
+ streamItemEntry.getId());
+ final Intent intent = new Intent(Intent.ACTION_VIEW, uri);
+ intent.setClassName(accountType.resPackageName,
+ accountType.getViewStreamItemActivity());
+ startActivity(intent);
}
};
@@ -142,9 +135,7 @@
@Override
public void setAlphaLayerValue(float alpha) {
- if (mAlphaLayer != null) {
- mAlphaLayer.setAlpha(alpha);
- }
+ ContactDetailDisplayUtils.setAlphaOnViewBackground(mAlphaLayer, alpha);
}
@Override
diff --git a/src/com/android/contacts/detail/StreamItemAdapter.java b/src/com/android/contacts/detail/StreamItemAdapter.java
index 09aa20b..5128787 100644
--- a/src/com/android/contacts/detail/StreamItemAdapter.java
+++ b/src/com/android/contacts/detail/StreamItemAdapter.java
@@ -17,6 +17,8 @@
package com.android.contacts.detail;
import com.android.contacts.R;
+import com.android.contacts.model.AccountType;
+import com.android.contacts.model.AccountTypeManager;
import com.android.contacts.util.StreamItemEntry;
import com.google.android.collect.Lists;
@@ -84,7 +86,10 @@
StreamItemEntry streamItem = (StreamItemEntry) getItem(position);
View view = ContactDetailDisplayUtils.createStreamItemView(
mInflater, mContext, streamItem, null);
- if (streamItem.getAction() != null && streamItem.getActionUri() != null) {
+ final AccountTypeManager manager = AccountTypeManager.getInstance(mContext);
+ final AccountType accountType =
+ manager.getAccountType(streamItem.getAccountType(), streamItem.getDataSet());
+ if (accountType.getViewStreamItemActivity() != null) {
view.setTag(streamItem);
view.setFocusable(true);
view.setOnClickListener(mListener);
diff --git a/src/com/android/contacts/editor/ContactEditorFragment.java b/src/com/android/contacts/editor/ContactEditorFragment.java
index 2725d1f..e1b4e9f 100644
--- a/src/com/android/contacts/editor/ContactEditorFragment.java
+++ b/src/com/android/contacts/editor/ContactEditorFragment.java
@@ -693,7 +693,7 @@
@Override
public void onRequest(int request) {
- if (request == EditorListener.FIELD_CHANGED) {
+ if (request == EditorListener.FIELD_CHANGED && !isEditingUserProfile()) {
acquireAggregationSuggestions(rawContactEditor);
}
}
@@ -803,12 +803,11 @@
// TODO: Find a better way to handle shortcuts, i.e. onKeyDown()?
menu.findItem(R.id.menu_done).setVisible(false);
- boolean editingUserProfile = mIsUserProfile || mNewLocalProfile;
// Split only if more than one raw profile and not a user profile
menu.findItem(R.id.menu_split).setVisible(mState != null && mState.size() > 1 &&
- !editingUserProfile);
+ !isEditingUserProfile());
// Cannot join a user profile
- menu.findItem(R.id.menu_join).setVisible(!editingUserProfile);
+ menu.findItem(R.id.menu_join).setVisible(!isEditingUserProfile());
int size = menu.size();
@@ -974,7 +973,7 @@
setEnabled(false);
Intent intent = ContactSaveService.createSaveContactIntent(getActivity(), mState,
- SAVE_MODE_EXTRA_KEY, saveMode, mNewLocalProfile || mIsUserProfile,
+ SAVE_MODE_EXTRA_KEY, saveMode, isEditingUserProfile(),
getActivity().getClass(), ContactEditorActivity.ACTION_SAVE_COMPLETED);
getActivity().startService(intent);
return true;
@@ -1150,6 +1149,10 @@
return false;
}
+ private boolean isEditingUserProfile() {
+ return mNewLocalProfile || mIsUserProfile;
+ }
+
public static interface Listener {
/**
* Contact was not found, so somehow close this fragment. This is raised after a contact
diff --git a/src/com/android/contacts/format/FormatUtils.java b/src/com/android/contacts/format/FormatUtils.java
index 771656b..cb4dc2d 100644
--- a/src/com/android/contacts/format/FormatUtils.java
+++ b/src/com/android/contacts/format/FormatUtils.java
@@ -15,6 +15,7 @@
*/
package com.android.contacts.format;
+import com.android.contacts.test.NeededForTesting;
import com.google.common.annotations.VisibleForTesting;
import android.database.CharArrayBuffer;
@@ -45,6 +46,7 @@
* Finds the earliest point in string1 at which the first part of string2 matches. For example,
* overlapPoint("abcd", "cdef") == 2.
*/
+ @NeededForTesting // App itself doesn't use this right now, but we don't want to remove it.
public static int overlapPoint(String string1, String string2) {
if (string1 == null || string2 == null) {
return -1;
diff --git a/src/com/android/contacts/group/GroupDetailFragment.java b/src/com/android/contacts/group/GroupDetailFragment.java
index 1205c31..1f47510 100644
--- a/src/com/android/contacts/group/GroupDetailFragment.java
+++ b/src/com/android/contacts/group/GroupDetailFragment.java
@@ -30,6 +30,7 @@
import android.app.Fragment;
import android.app.LoaderManager;
import android.app.LoaderManager.LoaderCallbacks;
+import android.content.ContentUris;
import android.content.Context;
import android.content.CursorLoader;
import android.content.Intent;
@@ -38,6 +39,7 @@
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
+import android.provider.ContactsContract.Groups;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.Menu;
@@ -68,10 +70,9 @@
public void onGroupSizeUpdated(String size);
/**
- * The group source (intent action and action URI) has been determined.
+ * The account type and dataset have been determined.
*/
- public void onGroupSourceUpdated(String accountTypeString, String dataSet,
- String groupSourceAction, String groupSourceUri);
+ public void onAccountTypeUpdated(String accountTypeString, String dataSet);
/**
* User decided to go to Edit-Mode
@@ -277,9 +278,7 @@
final String accountTypeString = cursor.getString(GroupMetaDataLoader.ACCOUNT_TYPE);
final String dataSet = cursor.getString(GroupMetaDataLoader.DATA_SET);
- final String groupSourceAction = cursor.getString(GroupMetaDataLoader.ACTION);
- final String groupSourceUri = cursor.getString(GroupMetaDataLoader.ACTION_URI);
- updateGroupSource(accountTypeString, dataSet, groupSourceAction, groupSourceUri);
+ updateAccountType(accountTypeString, dataSet);
}
}
@@ -322,21 +321,23 @@
* a button in a static header on the page, or as a header that scrolls with the
* {@link ListView}.
*/
- private void updateGroupSource(final String accountTypeString, final String dataSet,
- final String groupSourceAction, final String groupSourceUri) {
+ private void updateAccountType(final String accountTypeString, final String dataSet) {
// If the group action should be shown in the action bar, then pass the data to the
// listener who will take care of setting up the view and click listener. There is nothing
// else to be done by this {@link Fragment}.
if (mShowGroupActionInActionBar) {
- mListener.onGroupSourceUpdated(accountTypeString, dataSet, groupSourceAction,
- groupSourceUri);
+ mListener.onAccountTypeUpdated(accountTypeString, dataSet);
return;
}
+ final AccountTypeManager manager = AccountTypeManager.getInstance(getActivity());
+ final AccountType accountType =
+ manager.getAccountType(accountTypeString, dataSet);
+
// Otherwise, if the {@link Fragment} needs to create and setup the button, then first
// verify that there is a valid action.
- if (!TextUtils.isEmpty(groupSourceAction) && !TextUtils.isEmpty(groupSourceUri)) {
+ if (!TextUtils.isEmpty(accountType.getViewGroupActivity())) {
if (mGroupSourceView == null) {
mGroupSourceView = GroupDetailDisplayUtils.getNewGroupSourceView(mContext);
// Figure out how to add the view to the fragment.
@@ -358,7 +359,11 @@
mGroupSourceView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
- startActivity(new Intent(groupSourceAction, Uri.parse(groupSourceUri)));
+ final Uri uri = ContentUris.withAppendedId(Groups.CONTENT_URI, mGroupId);
+ final Intent intent = new Intent(Intent.ACTION_VIEW, uri);
+ intent.setClassName(accountType.resPackageName,
+ accountType.getViewGroupActivity());
+ startActivity(intent);
}
});
} else if (mGroupSourceView != null) {
@@ -431,4 +436,8 @@
public void closeActivityAfterDelete(boolean closeActivity) {
mCloseActivityAfterDelete = closeActivity;
}
+
+ public long getGroupId() {
+ return mGroupId;
+ }
}
diff --git a/src/com/android/contacts/interactions/PhoneNumberInteraction.java b/src/com/android/contacts/interactions/PhoneNumberInteraction.java
index 9442f5e..0448bd5 100644
--- a/src/com/android/contacts/interactions/PhoneNumberInteraction.java
+++ b/src/com/android/contacts/interactions/PhoneNumberInteraction.java
@@ -89,6 +89,18 @@
long type;
String label;
+ public PhoneItem() {
+ }
+
+ private PhoneItem(Parcel in) {
+ this.id = in.readLong();
+ this.phoneNumber = in.readString();
+ this.accountType = in.readString();
+ this.dataSet = in.readString();
+ this.type = in.readLong();
+ this.label = in.readString();
+ }
+
public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(id);
dest.writeString(phoneNumber);
@@ -129,6 +141,17 @@
public String toString() {
return phoneNumber;
}
+
+ public static final Parcelable.Creator<PhoneItem> CREATOR
+ = new Parcelable.Creator<PhoneItem>() {
+ public PhoneItem createFromParcel(Parcel in) {
+ return new PhoneItem(in);
+ }
+
+ public PhoneItem[] newArray(int size) {
+ return new PhoneItem[size];
+ }
+ };
}
/**
diff --git a/src/com/android/contacts/list/AccountFilterActivity.java b/src/com/android/contacts/list/AccountFilterActivity.java
index 9d872da..fb0cf9e 100644
--- a/src/com/android/contacts/list/AccountFilterActivity.java
+++ b/src/com/android/contacts/list/AccountFilterActivity.java
@@ -83,17 +83,17 @@
accountFilters.add(ContactListFilter.createAccountFilter(account.type, account.name,
account.dataSet, icon, account.name));
}
- int count = accountFilters.size();
+ final int count = accountFilters.size();
if (count >= 1) {
- // If we only have one account, don't show it as "account", instead show it as "all"
mFilters.add(ContactListFilter.createFilterWithType(
ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS));
+ // If we only have one account, don't show it as "account", instead show it as "all"
if (count > 1) {
mFilters.addAll(accountFilters);
- mFilters.add(ContactListFilter.createFilterWithType(
- ContactListFilter.FILTER_TYPE_CUSTOM));
}
+ mFilters.add(ContactListFilter.createFilterWithType(
+ ContactListFilter.FILTER_TYPE_CUSTOM));
}
mListView.setAdapter(new FilterListAdapter(this));
diff --git a/src/com/android/contacts/list/ContactTileAdapter.java b/src/com/android/contacts/list/ContactTileAdapter.java
index cc0a184..37ccded 100644
--- a/src/com/android/contacts/list/ContactTileAdapter.java
+++ b/src/com/android/contacts/list/ContactTileAdapter.java
@@ -73,6 +73,7 @@
private int mPhoneNumberLabelIndex;
private boolean mIsQuickContactEnabled = false;
+ private final int mPaddingInPixels;
/**
* Configures the adapter to filter and display contacts using different view types.
@@ -116,6 +117,10 @@
mColumnCount = (displayType == DisplayType.FREQUENT_ONLY ? 1 : numCols);
mDisplayType = displayType;
+ // Converting padding in dips to padding in pixels
+ mPaddingInPixels = mContext.getResources()
+ .getDimensionPixelOffset(R.dimen.contact_tile_divider_padding);
+
bindColumnIndices();
}
@@ -183,17 +188,32 @@
/**
* Iterates over the {@link Cursor}
* Returns position of the first NON Starred Contact
- * Returns -1 if not {@link DisplayType#STREQUENT} or {@link DisplayType#STREQUENT_PHONE_ONLY}
+ * Returns -1 if {@link DisplayType#STARRED_ONLY} or {@link DisplayType#GROUP_MEMBERS}
+ * Returns 0 if {@link DisplayType#FREQUENT_ONLY}
*/
private int getDividerPosition(Cursor cursor) {
- if (cursor == null || cursor.isClosed() || (mDisplayType != DisplayType.STREQUENT
- && mDisplayType != DisplayType.STREQUENT_PHONE_ONLY)) {
- return -1;
+ if (cursor == null || cursor.isClosed()) {
+ throw new IllegalStateException("Unable to access cursor");
}
- while (cursor.moveToNext()) {
- if (cursor.getInt(mStarredIndex) == 0) {
- return cursor.getPosition();
- }
+
+ switch (mDisplayType) {
+ case STREQUENT:
+ case STREQUENT_PHONE_ONLY:
+ while (cursor.moveToNext()) {
+ if (cursor.getInt(mStarredIndex) == 0) {
+ return cursor.getPosition();
+ }
+ }
+ break;
+ case GROUP_MEMBERS:
+ case STARRED_ONLY:
+ // There is no divider
+ return -1;
+ case FREQUENT_ONLY:
+ // Divider is first
+ return 0;
+ default:
+ throw new IllegalStateException("Unrecognized DisplayType " + mDisplayType);
}
// There are not NON Starred contacts in cursor
@@ -354,6 +374,7 @@
// Creating new row if needed
contactTileRowView = new ContactTileRow(mContext, itemViewType);
}
+
contactTileRowView.configureRow(contactList, position == getCount() - 1);
return contactTileRowView;
}
@@ -390,20 +411,19 @@
}
@Override
public int getViewTypeCount() {
- return (mDisplayType == DisplayType.STREQUENT ||
- mDisplayType == DisplayType.STREQUENT_PHONE_ONLY) ? ViewTypes.COUNT : 1;
+ return ViewTypes.COUNT;
}
- /**
- * Returns view type based on {@link DisplayType}.
- * {@link DisplayType#STARRED_ONLY} and {@link DisplayType#GROUP_MEMBERS}
- * are {@link ViewTypes#STARRED}.
- * {@link DisplayType#FREQUENT_ONLY} is {@link ViewTypes#FREQUENT}.
- * {@link DisplayType#STREQUENT} mixes both {@link ViewTypes}
- * and also adds in {@link ViewTypes#DIVIDER}.
- */
@Override
public int getItemViewType(int position) {
+ /*
+ * Returns view type based on {@link DisplayType}.
+ * {@link DisplayType#STARRED_ONLY} and {@link DisplayType#GROUP_MEMBERS}
+ * are {@link ViewTypes#STARRED}.
+ * {@link DisplayType#FREQUENT_ONLY} is {@link ViewTypes#FREQUENT}.
+ * {@link DisplayType#STREQUENT} mixes both {@link ViewTypes}
+ * and also adds in {@link ViewTypes#DIVIDER}.
+ */
switch (mDisplayType) {
case STREQUENT:
if (position < getRowCount(mDividerPosition)) {
@@ -483,10 +503,21 @@
contactTile.setClickable(entry != null);
contactTile.loadFromContact(entry);
- contactTile.setVerticalDividerVisibility(
- childIndex >= mColumnCount - 1 ? View.GONE : View.VISIBLE);
- contactTile.setHorizontalDividerVisibility(
- isLastRow ? View.GONE : View.VISIBLE);
+ switch (mItemViewType) {
+ case ViewTypes.STARRED_WITH_SECONDARY_ACTION:
+ case ViewTypes.STARRED:
+ // Setting divider visibilities
+ contactTile.setPadding(0, 0,
+ childIndex >= mColumnCount - 1 ? 0 : mPaddingInPixels,
+ isLastRow ? 0 : mPaddingInPixels);
+ break;
+ case ViewTypes.FREQUENT:
+ contactTile.setHorizontalDividerVisibility(
+ isLastRow ? View.GONE : View.VISIBLE);
+ break;
+ default:
+ break;
+ }
}
}
diff --git a/src/com/android/contacts/list/ContactTileView.java b/src/com/android/contacts/list/ContactTileView.java
index ef9c2fb..25edd7f 100644
--- a/src/com/android/contacts/list/ContactTileView.java
+++ b/src/com/android/contacts/list/ContactTileView.java
@@ -27,7 +27,6 @@
import android.util.Log;
import android.view.View;
import android.widget.FrameLayout;
-import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.QuickContactBadge;
import android.widget.TextView;
@@ -40,17 +39,15 @@
private Uri mLookupUri;
private ImageView mPhoto;
- private ImageView mPresence;
private QuickContactBadge mQuickContact;
private TextView mName;
private TextView mStatus;
private TextView mPhoneLabel;
private TextView mPhoneNumber;
private ContactPhotoManager mPhotoManager = null;
- private ImageButton mPushState;
- private Listener mListener;
- private View mVerticalDivider;
+ private View mPushState;
private View mHorizontalDivider;
+ private Listener mListener;
public ContactTileView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -63,12 +60,10 @@
mQuickContact = (QuickContactBadge) findViewById(R.id.contact_tile_quick);
mPhoto = (ImageView) findViewById(R.id.contact_tile_image);
- mPresence = (ImageView) findViewById(R.id.contact_tile_presence);
mStatus = (TextView) findViewById(R.id.contact_tile_status);
mPhoneLabel = (TextView) findViewById(R.id.contact_tile_phone_type);
mPhoneNumber = (TextView) findViewById(R.id.contact_tile_phone_number);
- mPushState = (ImageButton) findViewById(R.id.contact_tile_push_state);
- mVerticalDivider = findViewById(R.id.contact_tile_vertical_divider);
+ mPushState = findViewById(R.id.contact_tile_push_state);
mHorizontalDivider = findViewById(R.id.contact_tile_horizontal_divider);
OnClickListener listener = new OnClickListener() {
@@ -100,23 +95,25 @@
mName.setText(entry.name);
mLookupUri = entry.lookupKey;
- int presenceDrawableResId = (entry.presence == null ? 0 :
- StatusUpdates.getPresenceIconResourceId(entry.presence));
-
- if (mPresence != null) {
- mPresence.setBackgroundResource(presenceDrawableResId);
- }
-
if (mStatus != null) {
String statusText;
if (entry.presence == null) {
- statusText = null;
+ mStatus.setVisibility(View.GONE);
} else {
statusText =
(entry.status != null ? entry.status :
ContactStatusUtil.getStatusString(mContext, entry.presence));
+ mStatus.setText(statusText);
+ int presenceDrawableResId = (entry.presence == null ? 0 :
+ StatusUpdates.getPresenceIconResourceId(entry.presence));
+ if (presenceDrawableResId != 0) {
+ Log.i(TAG, "iconId = " + presenceDrawableResId);
+ mStatus.setCompoundDrawablesWithIntrinsicBounds(
+ getResources().getDrawable(presenceDrawableResId),
+ null, null, null);
+ }
+ mStatus.setVisibility(View.VISIBLE);
}
- mStatus.setText(statusText);
}
if (mPhoneLabel != null) {
@@ -128,21 +125,19 @@
mPhoneNumber.setText(entry.phoneNumber);
}
- if (mQuickContact != null) {
- mQuickContact.assignContactUri(mLookupUri);
- mQuickContact.setImageBitmap(null);
- } else {
- mPhoto.setImageBitmap(null);
- }
-
setVisibility(View.VISIBLE);
if (mPhotoManager != null) {
- if (mQuickContact != null){
- mPhotoManager.loadPhoto(mQuickContact, entry.photoUri);
- } else {
+ if (mPhoto != null) {
mPhotoManager.loadPhoto(mPhoto, entry.photoUri);
- }
+
+ if (mQuickContact != null) {
+ mQuickContact.assignContactUri(mLookupUri);
+ }
+ } else if (mQuickContact != null) {
+ mQuickContact.assignContactUri(mLookupUri);
+ mPhotoManager.loadPhoto(mQuickContact, entry.photoUri);
+ }
} else {
Log.w(TAG, "contactPhotoManager not set");
@@ -152,18 +147,14 @@
}
}
- public void setVerticalDividerVisibility(int visibility) {
- if (mVerticalDivider != null) mVerticalDivider.setVisibility(visibility);
+ public void setListener(Listener listener) {
+ mListener = listener;
}
public void setHorizontalDividerVisibility(int visibility) {
if (mHorizontalDivider != null) mHorizontalDivider.setVisibility(visibility);
}
- public void setListener(Listener listener) {
- mListener = listener;
- }
-
public Uri getLookupUri() {
return mLookupUri;
}
diff --git a/src/com/android/contacts/list/DefaultContactBrowseListFragment.java b/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
index 5134a1a..ca07516 100644
--- a/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
+++ b/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
@@ -140,11 +140,12 @@
if (!isSearchMode() && data != null) {
int count = data.getCount();
if (count != 0) {
+ count -= (mUserProfileExists ? 1: 0);
String format = getResources().getQuantityText(
R.plurals.listTotalAllContacts, count).toString();
// Do not count the user profile in the contacts count
if (mUserProfileExists) {
- getAdapter().setContactsCount(String.format(format, count - 1));
+ getAdapter().setContactsCount(String.format(format, count));
} else {
mCounterHeaderView.setText(String.format(format, count));
}
diff --git a/src/com/android/contacts/list/DefaultContactListAdapter.java b/src/com/android/contacts/list/DefaultContactListAdapter.java
index 89de966..bb49027 100644
--- a/src/com/android/contacts/list/DefaultContactListAdapter.java
+++ b/src/com/android/contacts/list/DefaultContactListAdapter.java
@@ -192,16 +192,16 @@
+ "SELECT DISTINCT " + RawContacts.CONTACT_ID
+ " FROM raw_contacts"
+ " WHERE " + RawContacts.ACCOUNT_TYPE + "=?"
- + " AND " + RawContacts.ACCOUNT_NAME + "=?"
- + " OR " + Contacts.IS_USER_PROFILE + "=1");
+ + " AND " + RawContacts.ACCOUNT_NAME + "=?");
selectionArgs.add(filter.accountType);
selectionArgs.add(filter.accountName);
if (filter.dataSet != null) {
- selection.append(" AND " + RawContacts.DATA_SET + "=?)");
+ selection.append(" AND " + RawContacts.DATA_SET + "=?");
selectionArgs.add(filter.dataSet);
} else {
- selection.append(" AND " + RawContacts.DATA_SET + " IS NULL)");
+ selection.append(" AND " + RawContacts.DATA_SET + " IS NULL");
}
+ selection.append(" OR " + Contacts.IS_USER_PROFILE + "=1)");
break;
}
case ContactListFilter.FILTER_TYPE_GROUP: {
diff --git a/src/com/android/contacts/model/AccountType.java b/src/com/android/contacts/model/AccountType.java
index a78185f..95216e6 100644
--- a/src/com/android/contacts/model/AccountType.java
+++ b/src/com/android/contacts/model/AccountType.java
@@ -123,6 +123,21 @@
return null;
}
+ /** Returns an optional Activity string that can be used to view the group. */
+ public String getViewGroupActivity() {
+ return null;
+ }
+
+ /** Returns an optional Activity string that can be used to view the stream item. */
+ public String getViewStreamItemActivity() {
+ return null;
+ }
+
+ /** Returns an optional Activity string that can be used to view the stream item photo. */
+ public String getViewStreamItemPhotoActivity() {
+ return null;
+ }
+
public CharSequence getDisplayLabel(Context context) {
return getResourceText(context, summaryResPackageName, titleRes, accountType);
}
@@ -130,7 +145,7 @@
/**
* @return resource ID for the "invite contact" action label, or -1 if not defined.
*/
- protected int getInviteContactActionResId(Context conext) {
+ protected int getInviteContactActionResId(Context context) {
return -1;
}
diff --git a/src/com/android/contacts/model/ExternalAccountType.java b/src/com/android/contacts/model/ExternalAccountType.java
index 5dd4d3b..791f0ae 100644
--- a/src/com/android/contacts/model/ExternalAccountType.java
+++ b/src/com/android/contacts/model/ExternalAccountType.java
@@ -58,6 +58,10 @@
private static final String ATTR_INVITE_CONTACT_ACTIVITY = "inviteContactActivity";
private static final String ATTR_INVITE_CONTACT_ACTION_LABEL = "inviteContactActionLabel";
private static final String ATTR_VIEW_CONTACT_NOTIFY_SERVICE = "viewContactNotifyService";
+ private static final String ATTR_VIEW_GROUP_ACTIVITY = "viewGroupActivity";
+ private static final String ATTR_VIEW_STREAM_ITEM_ACTIVITY = "viewStreamItemActivity";
+ private static final String ATTR_VIEW_STREAM_ITEM_PHOTO_ACTIVITY =
+ "viewStreamItemPhotoActivity";
private static final String ATTR_DATA_SET = "dataSet";
private static final String ATTR_EXTENSION_PACKAGE_NAMES = "extensionPackageNames";
@@ -73,6 +77,9 @@
private String mInviteContactActivity;
private String mInviteActionLabelAttribute;
private String mViewContactNotifyService;
+ private String mViewGroupActivity;
+ private String mViewStreamItemActivity;
+ private String mViewStreamItemPhotoActivity;
private List<String> mExtensionPackageNames;
private int mInviteActionLabelResId;
private String mAccountTypeLabelAttribute;
@@ -156,6 +163,21 @@
}
@Override
+ public String getViewGroupActivity() {
+ return mViewGroupActivity;
+ }
+
+ @Override
+ public String getViewStreamItemActivity() {
+ return mViewStreamItemActivity;
+ }
+
+ @Override
+ public String getViewStreamItemPhotoActivity() {
+ return mViewStreamItemPhotoActivity;
+ }
+
+ @Override
public List<String> getExtensionPackageNames() {
return mExtensionPackageNames;
}
@@ -202,6 +224,12 @@
mInviteActionLabelAttribute = value;
} else if (ATTR_VIEW_CONTACT_NOTIFY_SERVICE.equals(attr)) {
mViewContactNotifyService = value;
+ } else if (ATTR_VIEW_GROUP_ACTIVITY.equals(attr)) {
+ mViewGroupActivity = value;
+ } else if (ATTR_VIEW_STREAM_ITEM_ACTIVITY.equals(attr)) {
+ mViewStreamItemActivity = value;
+ } else if (ATTR_VIEW_STREAM_ITEM_PHOTO_ACTIVITY.equals(attr)) {
+ mViewStreamItemPhotoActivity = value;
} else if (ATTR_DATA_SET.equals(attr)) {
dataSet = value;
} else if (ATTR_EXTENSION_PACKAGE_NAMES.equals(attr)) {
diff --git a/src/com/android/contacts/socialwidget/SocialWidgetProvider.java b/src/com/android/contacts/socialwidget/SocialWidgetProvider.java
index 98a843e..682f700 100644
--- a/src/com/android/contacts/socialwidget/SocialWidgetProvider.java
+++ b/src/com/android/contacts/socialwidget/SocialWidgetProvider.java
@@ -18,8 +18,9 @@
import com.android.contacts.ContactLoader;
import com.android.contacts.R;
+import com.android.contacts.model.AccountType;
+import com.android.contacts.model.AccountTypeManager;
import com.android.contacts.util.ContactBadgeUtil;
-import com.android.contacts.util.DataStatus;
import com.android.contacts.util.StreamItemEntry;
import android.app.PendingIntent;
@@ -33,8 +34,8 @@
import android.graphics.BitmapFactory;
import android.graphics.Typeface;
import android.net.Uri;
-import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.QuickContact;
+import android.provider.ContactsContract.StreamItems;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.text.style.AbsoluteSizeSpan;
@@ -44,7 +45,6 @@
import android.view.View;
import android.widget.RemoteViews;
-import java.util.HashMap;
import java.util.List;
public class SocialWidgetProvider extends AppWidgetProvider {
@@ -214,10 +214,15 @@
views.setTextViewText(R.id.name_and_snippet, sb);
views.setViewVisibility(R.id.name, View.GONE);
views.setViewVisibility(R.id.name_and_snippet, View.VISIBLE);
- if (!TextUtils.isEmpty(streamItem.getAction())
- && !TextUtils.isEmpty(streamItem.getActionUri())) {
- final Intent intent = new Intent(streamItem.getAction(),
- Uri.parse(streamItem.getActionUri()));
+ final AccountTypeManager manager = AccountTypeManager.getInstance(context);
+ final AccountType accountType =
+ manager.getAccountType(streamItem.getAccountType(), streamItem.getDataSet());
+ if (accountType.getViewStreamItemActivity() != null) {
+ final Uri uri = ContentUris.withAppendedId(StreamItems.CONTENT_URI,
+ streamItem.getId());
+ final Intent intent = new Intent(Intent.ACTION_VIEW, uri);
+ intent.setClassName(accountType.resPackageName,
+ accountType.getViewStreamItemActivity());
views.setOnClickPendingIntent(R.id.name_and_snippet_container,
PendingIntent.getActivity(context, 0, intent, 0));
}
diff --git a/src/com/android/contacts/util/ContactBadgeUtil.java b/src/com/android/contacts/util/ContactBadgeUtil.java
index 2f37b4c..806264d 100644
--- a/src/com/android/contacts/util/ContactBadgeUtil.java
+++ b/src/com/android/contacts/util/ContactBadgeUtil.java
@@ -16,14 +16,12 @@
package com.android.contacts.util;
-import com.android.contacts.ContactLoader;
import com.android.contacts.R;
import android.content.Context;
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.text.TextUtils;
@@ -57,9 +55,9 @@
String labelDisplayValue = null;
- final Integer statusLabel = streamItem.getLabelRes();
+ final String statusLabelRes = streamItem.getLabelRes();
final String statusResPackage = streamItem.getResPackage();
- if (statusLabel != null) {
+ if (statusLabelRes != null) {
Resources resources;
if (TextUtils.isEmpty(statusResPackage)) {
resources = context.getResources();
@@ -75,11 +73,13 @@
}
if (resources != null) {
- try {
- labelDisplayValue = resources.getString(statusLabel.intValue());
- } catch (NotFoundException e) {
- Log.w(TAG, "Contact status update resource not found: " + statusResPackage + "@"
- + statusLabel.intValue());
+ final int resId = resources.getIdentifier(statusLabelRes, "string",
+ statusResPackage);
+ if (resId == 0) {
+ Log.w(TAG, "Contact status update resource not found: " + statusLabelRes +
+ " in " + statusResPackage);
+ } else {
+ labelDisplayValue = resources.getString(resId);
}
}
}
diff --git a/src/com/android/contacts/util/StreamItemEntry.java b/src/com/android/contacts/util/StreamItemEntry.java
index db15e3a..5959c46 100644
--- a/src/com/android/contacts/util/StreamItemEntry.java
+++ b/src/com/android/contacts/util/StreamItemEntry.java
@@ -37,26 +37,29 @@
private final String mText;
private final String mComments;
private final long mTimestamp;
- private final String mAction;
- private final String mActionUri;
+ private final String mAccountType;
+ private final String mAccountName;
+ private final String mDataSet;
// Package references for label and icon resources.
private final String mResPackage;
- private final int mIconRes;
- private final int mLabelRes;
+ private final String mIconRes;
+ private final String mLabelRes;
// Photos associated with this stream item.
private List<StreamItemPhotoEntry> mPhotos;
@NeededForTesting
- public StreamItemEntry(long id, String text, String comments, long timestamp, String action,
- String actionUri, String resPackage, int iconRes, int labelRes) {
+ public StreamItemEntry(long id, String text, String comments, long timestamp,
+ String accountType, String accountName, String dataSet, String resPackage,
+ String iconRes, String labelRes) {
mId = id;
mText = text;
mComments = comments;
mTimestamp = timestamp;
- mAction = action;
- mActionUri = actionUri;
+ mAccountType = accountType;
+ mAccountName = accountName;
+ mDataSet = dataSet;
mResPackage = resPackage;
mIconRes = iconRes;
mLabelRes = labelRes;
@@ -70,11 +73,12 @@
mText = getString(cursor, StreamItems.TEXT);
mComments = getString(cursor, StreamItems.COMMENTS);
mTimestamp = getLong(cursor, StreamItems.TIMESTAMP);
- mAction = getString(cursor, StreamItems.ACTION);
- mActionUri = getString(cursor, StreamItems.ACTION_URI);
+ mAccountType = getString(cursor, StreamItems.ACCOUNT_TYPE);
+ mAccountName = getString(cursor, StreamItems.ACCOUNT_NAME);
+ mDataSet = getString(cursor, StreamItems.DATA_SET);
mResPackage = getString(cursor, StreamItems.RES_PACKAGE);
- mIconRes = getInt(cursor, StreamItems.RES_ICON, -1);
- mLabelRes = getInt(cursor, StreamItems.RES_LABEL, -1);
+ mIconRes = getString(cursor, StreamItems.RES_ICON);
+ mLabelRes = getString(cursor, StreamItems.RES_LABEL);
mPhotos = new ArrayList<StreamItemPhotoEntry>();
}
@@ -103,23 +107,27 @@
return mTimestamp;
}
- public String getAction() {
- return mAction;
+ public String getAccountType() {
+ return mAccountType;
}
- public String getActionUri() {
- return mActionUri;
+ public String getAccountName() {
+ return mAccountName;
+ }
+
+ public String getDataSet() {
+ return mDataSet;
}
public String getResPackage() {
return mResPackage;
}
- public int getIconRes() {
+ public String getIconRes() {
return mIconRes;
}
- public int getLabelRes() {
+ public String getLabelRes() {
return mLabelRes;
}
@@ -132,11 +140,6 @@
return cursor.getString(cursor.getColumnIndex(columnName));
}
- private static int getInt(Cursor cursor, String columnName, int missingValue) {
- final int columnIndex = cursor.getColumnIndex(columnName);
- return cursor.isNull(columnIndex) ? missingValue : cursor.getInt(columnIndex);
- }
-
private static long getLong(Cursor cursor, String columnName) {
final int columnIndex = cursor.getColumnIndex(columnName);
return cursor.getLong(columnIndex);
diff --git a/src/com/android/contacts/util/StreamItemPhotoEntry.java b/src/com/android/contacts/util/StreamItemPhotoEntry.java
index 6527454..ad69e5c 100644
--- a/src/com/android/contacts/util/StreamItemPhotoEntry.java
+++ b/src/com/android/contacts/util/StreamItemPhotoEntry.java
@@ -33,11 +33,9 @@
private final int mHeight;
private final int mWidth;
private final int mFileSize;
- private final String mAction;
- private final String mActionUri;
public StreamItemPhotoEntry(long id, int sortIndex, long photoFileId, String photoUri,
- int height, int width, int fileSize, String action, String actionUri) {
+ int height, int width, int fileSize) {
mId = id;
mSortIndex = sortIndex;
mPhotoFileId = photoFileId;
@@ -45,8 +43,6 @@
mHeight = height;
mWidth = width;
mFileSize = fileSize;
- mAction = action;
- mActionUri = actionUri;
}
public StreamItemPhotoEntry(Cursor cursor) {
@@ -59,8 +55,6 @@
mHeight = getInt(cursor, PhotoFiles.HEIGHT, -1);
mWidth = getInt(cursor, PhotoFiles.WIDTH, -1);
mFileSize = getInt(cursor, PhotoFiles.FILESIZE, -1);
- mAction = getString(cursor, StreamItemPhotos.ACTION);
- mActionUri = getString(cursor, StreamItemPhotos.ACTION_URI);
}
public long getId() {
@@ -91,14 +85,6 @@
return mFileSize;
}
- public String getAction() {
- return mAction;
- }
-
- public String getActionUri() {
- return mActionUri;
- }
-
@Override
public int compareTo(StreamItemPhotoEntry streamItemPhotoEntry) {
// Sort index is used to compare, falling back to ID if neither entry has a
diff --git a/tests/src/com/android/contacts/ContactLoaderTest.java b/tests/src/com/android/contacts/ContactLoaderTest.java
index a731f24..3560ce1 100644
--- a/tests/src/com/android/contacts/ContactLoaderTest.java
+++ b/tests/src/com/android/contacts/ContactLoaderTest.java
@@ -16,7 +16,12 @@
package com.android.contacts;
+import com.android.contacts.model.AccountType;
+import com.android.contacts.model.AccountWithDataSet;
+import com.android.contacts.model.BaseAccountType;
+import com.android.contacts.test.InjectedServices;
import com.android.contacts.tests.mocks.ContactsMockContext;
+import com.android.contacts.tests.mocks.MockAccountTypeManager;
import com.android.contacts.tests.mocks.MockContentProvider;
import android.content.ContentUris;
@@ -36,18 +41,31 @@
*/
@LargeTest
public class ContactLoaderTest extends LoaderTestCase {
- ContactsMockContext mMockContext;
- MockContentProvider mContactsProvider;
+ private ContactsMockContext mMockContext;
+ private MockContentProvider mContactsProvider;
@Override
protected void setUp() throws Exception {
super.setUp();
mMockContext = new ContactsMockContext(getContext());
mContactsProvider = mMockContext.getContactsProvider();
+
+ InjectedServices services = new InjectedServices();
+ AccountType accountType = new BaseAccountType();
+ accountType.accountType = "mockAccountType";
+
+ AccountWithDataSet account =
+ new AccountWithDataSet("mockAccountName", "mockAccountType", null);
+
+ mMockContext.setMockAccountTypeManager(
+ new MockAccountTypeManager(
+ new AccountType[] { accountType }, new AccountWithDataSet[] { account }));
}
@Override
protected void tearDown() throws Exception {
+ mMockContext = null;
+ mContactsProvider = null;
super.tearDown();
}
diff --git a/tests/src/com/android/contacts/activities/CallLogActivityTests.java b/tests/src/com/android/contacts/activities/CallLogActivityTests.java
index 070941c..5926fd9 100644
--- a/tests/src/com/android/contacts/activities/CallLogActivityTests.java
+++ b/tests/src/com/android/contacts/activities/CallLogActivityTests.java
@@ -18,10 +18,12 @@
import com.android.contacts.CallDetailActivity;
import com.android.contacts.R;
+import com.android.contacts.calllog.CallLogAdapter;
import com.android.contacts.calllog.CallLogFragment;
-import com.android.contacts.calllog.CallLogFragment.CallLogQuery;
-import com.android.contacts.calllog.CallLogFragment.ContactInfo;
import com.android.contacts.calllog.CallLogListItemViews;
+import com.android.contacts.calllog.CallLogQuery;
+import com.android.contacts.calllog.CallLogQueryTestUtils;
+import com.android.contacts.calllog.ContactInfo;
import com.android.contacts.calllog.IntentProvider;
import com.android.internal.telephony.CallerInfo;
@@ -61,17 +63,6 @@
@LargeTest
public class CallLogActivityTests
extends ActivityInstrumentationTestCase2<CallLogActivity> {
- private static final String[] EXTENDED_CALL_LOG_PROJECTION = new String[] {
- Calls._ID,
- Calls.NUMBER,
- Calls.DATE,
- Calls.DURATION,
- Calls.TYPE,
- Calls.COUNTRY_ISO,
- Calls.VOICEMAIL_URI,
- Calls.GEOCODED_LOCATION,
- CallLogFragment.CallLogQuery.SECTION_NAME,
- };
private static final int RAND_DURATION = -1;
private static final long NOW = -1L;
@@ -95,7 +86,7 @@
private CallLogActivity mActivity;
private CallLogFragment mFragment;
private FrameLayout mParentView;
- private CallLogFragment.CallLogAdapter mAdapter;
+ private CallLogAdapter mAdapter;
private String mVoicemail;
// In memory array to hold the rows corresponding to the 'calls' table.
@@ -132,7 +123,7 @@
mAdapter.disableRequestProcessingForTest();
mAdapter.stopRequestProcessing();
mParentView = new FrameLayout(mActivity);
- mCursor = new MatrixCursor(EXTENDED_CALL_LOG_PROJECTION);
+ mCursor = new MatrixCursor(CallLogQuery.EXTENDED_PROJECTION);
buildIconMap();
}
@@ -487,27 +478,19 @@
* @param type Either Call.OUTGOING_TYPE or Call.INCOMING_TYPE or Call.MISSED_TYPE.
*/
private void insert(String number, long date, int duration, int type) {
- MatrixCursor.RowBuilder row = mCursor.newRow();
- row.add(mIndex);
- mIndex ++;
- row.add(number);
- if (NOW == date) {
- row.add(new Date().getTime());
- } else {
- row.add(date);
- }
- if (duration < 0) {
- duration = mRnd.nextInt(10 * 60); // 0 - 10 minutes random.
- }
- row.add(duration); // duration
+ Object[] values = CallLogQueryTestUtils.createTestExtendedValues();
+ values[CallLogQuery.ID] = mIndex;
+ values[CallLogQuery.NUMBER] = number;
+ values[CallLogQuery.DATE] = date == NOW ? new Date().getTime() : date;
+ values[CallLogQuery.DURATION] = duration < 0 ? mRnd.nextInt(10 * 60) : duration;
if (mVoicemail != null && mVoicemail.equals(number)) {
assertEquals(Calls.OUTGOING_TYPE, type);
}
- row.add(type); // type
- row.add(TEST_COUNTRY_ISO); // country ISO
- row.add(null); // voicemail_uri
- row.add(null); // geocoded_location
- row.add(CallLogFragment.CallLogQuery.SECTION_OLD_ITEM); // section
+ values[CallLogQuery.CALL_TYPE] = type;
+ values[CallLogQuery.COUNTRY_ISO] = TEST_COUNTRY_ISO;
+ values[CallLogQuery.SECTION] = CallLogQuery.SECTION_OLD_ITEM;
+ mCursor.addRow(values);
+ ++mIndex;
}
/**
@@ -518,27 +501,19 @@
* @param duration In seconds of the call. Use RAND_DURATION to pick a random one.
*/
private void insertVoicemail(String number, long date, int duration) {
- MatrixCursor.RowBuilder row = mCursor.newRow();
+ Object[] values = CallLogQueryTestUtils.createTestExtendedValues();
+ values[CallLogQuery.ID] = mIndex;
+ values[CallLogQuery.NUMBER] = number;
+ values[CallLogQuery.DATE] = date == NOW ? new Date().getTime() : date;
+ values[CallLogQuery.DURATION] = duration < 0 ? mRnd.nextInt(10 * 60) : duration;
+ values[CallLogQuery.CALL_TYPE] = Calls.VOICEMAIL_TYPE;
+ values[CallLogQuery.COUNTRY_ISO] = TEST_COUNTRY_ISO;
// Must have the same index as the row.
- Uri voicemailUri =
+ values[CallLogQuery.VOICEMAIL_URI] =
ContentUris.withAppendedId(VoicemailContract.Voicemails.CONTENT_URI, mIndex);
- row.add(mIndex);
- mIndex ++;
- row.add(number);
- if (NOW == date) {
- row.add(new Date().getTime());
- } else {
- row.add(date);
- }
- if (duration < 0) {
- duration = mRnd.nextInt(10 * 60); // 0 - 10 minutes random.
- }
- row.add(duration); // duration
- row.add(Calls.VOICEMAIL_TYPE); // type
- row.add(TEST_COUNTRY_ISO); // country ISO
- row.add(voicemailUri); // voicemail_uri
- row.add(null); // geocoded_location
- row.add(CallLogFragment.CallLogQuery.SECTION_OLD_ITEM); // section
+ values[CallLogQuery.SECTION] = CallLogQuery.SECTION_OLD_ITEM;
+ mCursor.addRow(values);
+ ++mIndex;
}
/**
diff --git a/tests/src/com/android/contacts/calllog/CallLogGroupBuilderTest.java b/tests/src/com/android/contacts/calllog/CallLogGroupBuilderTest.java
index 8a7e946..31ad548 100644
--- a/tests/src/com/android/contacts/calllog/CallLogGroupBuilderTest.java
+++ b/tests/src/com/android/contacts/calllog/CallLogGroupBuilderTest.java
@@ -18,8 +18,6 @@
import static com.google.android.collect.Lists.newArrayList;
-import com.android.contacts.calllog.CallLogFragment.CallLogQuery;
-
import android.database.MatrixCursor;
import android.provider.CallLog.Calls;
import android.test.AndroidTestCase;
@@ -171,7 +169,7 @@
/** Creates (or recreates) the cursor used to store the call log content for the tests. */
private void createCursor() {
- mCursor = new MatrixCursor(CallLogFragment.CallLogQuery.EXTENDED_PROJECTION);
+ mCursor = new MatrixCursor(CallLogQuery.EXTENDED_PROJECTION);
}
/** Clears the content of the {@link FakeGroupCreator} used in the tests. */
@@ -223,9 +221,12 @@
throw new IllegalArgumentException("not an item section: " + section);
}
mCursor.moveToNext();
- mCursor.addRow(new Object[]{
- mCursor.getPosition(), number, 0L, 0L, type, "", "", "", section
- });
+ Object[] values = CallLogQueryTestUtils.createTestExtendedValues();
+ values[CallLogQuery.ID] = mCursor.getPosition();
+ values[CallLogQuery.NUMBER] = number;
+ values[CallLogQuery.CALL_TYPE] = type;
+ values[CallLogQuery.SECTION] = section;
+ mCursor.addRow(values);
}
/** Adds the old section header to the call log. */
@@ -245,7 +246,10 @@
throw new IllegalArgumentException("not a header section: " + section);
}
mCursor.moveToNext();
- mCursor.addRow(new Object[]{ mCursor.getPosition(), "", 0L, 0L, 0, "", "", "", section });
+ Object[] values = CallLogQueryTestUtils.createTestExtendedValues();
+ values[CallLogQuery.ID] = mCursor.getPosition();
+ values[CallLogQuery.SECTION] = section;
+ mCursor.addRow(values);
}
/** Asserts that the group matches the given values. */
@@ -272,7 +276,7 @@
}
/** Fake implementation of a GroupCreator which stores the created groups in a member field. */
- private static class FakeGroupCreator implements CallLogFragment.GroupCreator {
+ private static class FakeGroupCreator implements CallLogGroupBuilder.GroupCreator {
/** The list of created groups. */
public final List<GroupSpec> groups = newArrayList();
diff --git a/tests/src/com/android/contacts/calllog/CallLogQueryTestUtils.java b/tests/src/com/android/contacts/calllog/CallLogQueryTestUtils.java
new file mode 100644
index 0000000..0e1952a
--- /dev/null
+++ b/tests/src/com/android/contacts/calllog/CallLogQueryTestUtils.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.contacts.calllog;
+
+import static junit.framework.Assert.assertEquals;
+import junit.framework.Assert;
+
+/**
+ * Helper class to create test values for {@link CallLogQuery}.
+ */
+public class CallLogQueryTestUtils {
+ public static Object[] createTestValues() {
+ Object[] values = new Object[]{ -1L, "", 0L, 0L, 0, "", "", "", null, 0, null };
+ assertEquals(CallLogQuery._PROJECTION.length, values.length);
+ return values;
+ }
+
+ public static Object[] createTestExtendedValues() {
+ Object[] values = new Object[]{ -1L, "", 0L, 0L, 0, "", "", "", null, 0, null, 0 };
+ Assert.assertEquals(CallLogQuery.EXTENDED_PROJECTION.length, values.length);
+ return values;
+ }
+}
diff --git a/tests/src/com/android/contacts/detail/StreamItemAdapterTest.java b/tests/src/com/android/contacts/detail/StreamItemAdapterTest.java
index 99ae834..d862d6e 100644
--- a/tests/src/com/android/contacts/detail/StreamItemAdapterTest.java
+++ b/tests/src/com/android/contacts/detail/StreamItemAdapterTest.java
@@ -20,12 +20,14 @@
import com.android.contacts.util.StreamItemEntryBuilder;
import com.google.common.collect.Lists;
-import android.content.Intent;
import android.test.AndroidTestCase;
import android.view.View;
import java.util.ArrayList;
+// TODO: We should have tests for action, but that requires a mock sync-adapter that specifies
+// an action or doesn't
+
/**
* Unit tests for {@link StreamItemAdapter}.
*/
@@ -51,30 +53,13 @@
public void testGetCount_Empty() {
mAdapter.setStreamItems(createStreamItemList(0));
// There is actually one view: the header.
- assertEquals(1, mAdapter.getCount());
+ assertEquals(2, mAdapter.getCount());
}
public void testGetCount_NonEmpty() {
mAdapter.setStreamItems(createStreamItemList(3));
// There is one extra view: the header.
- assertEquals(4, mAdapter.getCount());
- }
-
- public void testGetView_WithAction() {
- StreamItemEntry streamItem = createStreamItemWithAction();
- mAdapter.setStreamItems(Lists.newArrayList(streamItem));
- mView = mAdapter.getView(1, null, null);
- assertStreamItemViewHasTag(streamItem);
- assertStreamItemViewHasOnClickListener();
- assertStreamItemViewFocusable();
- }
-
- public void testGetView_WithoutAction() {
- mAdapter.setStreamItems(Lists.newArrayList(createStreamItemWithoutAction()));
- mView = mAdapter.getView(1, null, null);
- assertStreamItemViewHasNoTag();
- assertStreamItemViewHasNoOnClickListener();
- assertStreamItemViewNotFocusable();
+ assertEquals(5, mAdapter.getCount());
}
public void testGetView_Header() {
@@ -91,22 +76,6 @@
"text #" + mCreateStreamItemEntryBuilderCounter++);
}
- /** Returns a stream item with an action and action URI set. */
- private StreamItemEntry createStreamItemWithAction() {
- return createStreamItemEntryBuilder()
- .setAction(Intent.ACTION_VIEW)
- .setActionUri("http://www.google.com")
- .build();
- }
-
- /** Returns a stream item without an action and action URI set. */
- private StreamItemEntry createStreamItemWithoutAction() {
- return createStreamItemEntryBuilder()
- .setAction(null)
- .setActionUri(null)
- .build();
- }
-
/** Creates a list containing the given number of {@link StreamItemEntry}s. */
private ArrayList<StreamItemEntry> createStreamItemList(int count) {
ArrayList<StreamItemEntry> list = Lists.newArrayList();
diff --git a/tests/src/com/android/contacts/model/AccountTypeTest.java b/tests/src/com/android/contacts/model/AccountTypeTest.java
index de66694..986d840 100644
--- a/tests/src/com/android/contacts/model/AccountTypeTest.java
+++ b/tests/src/com/android/contacts/model/AccountTypeTest.java
@@ -52,7 +52,7 @@
AccountType.getResourceText(c, packageName, externalResID, DEFAULT));
// Load from the contacts package itself.
- final int internalResId = com.android.contacts.R.string.sharedUserLabel;
+ final int internalResId = com.android.contacts.R.string.launcherDialer;
assertEquals(c.getString(internalResId),
AccountType.getResourceText(c, null, internalResId, DEFAULT));
}
diff --git a/tests/src/com/android/contacts/tests/mocks/ContactsMockContext.java b/tests/src/com/android/contacts/tests/mocks/ContactsMockContext.java
index 93ea4f4..2f959f4 100644
--- a/tests/src/com/android/contacts/tests/mocks/ContactsMockContext.java
+++ b/tests/src/com/android/contacts/tests/mocks/ContactsMockContext.java
@@ -16,6 +16,8 @@
package com.android.contacts.tests.mocks;
+import com.android.contacts.model.AccountTypeManager;
+
import android.content.ContentResolver;
import android.content.Context;
import android.content.ContextWrapper;
@@ -32,13 +34,11 @@
* to mock content providers.
*/
public class ContactsMockContext extends ContextWrapper {
-
- private static final String TAG = "ContactsMockContext";
-
private ContactsMockPackageManager mPackageManager;
private MockContentResolver mContentResolver;
private MockContentProvider mContactsProvider;
private MockContentProvider mSettingsProvider;
+ private MockAccountTypeManager mMockAccountTypeManager;
private Intent mIntentForStartActivity;
public ContactsMockContext(Context base) {
@@ -53,6 +53,10 @@
mContentResolver.addProvider(Settings.AUTHORITY, mSettingsProvider);
}
+ public void setMockAccountTypeManager(MockAccountTypeManager mockAccountTypeManager) {
+ mMockAccountTypeManager = mockAccountTypeManager;
+ }
+
@Override
public ContentResolver getContentResolver() {
return mContentResolver;
@@ -93,4 +97,12 @@
mContactsProvider.verify();
mSettingsProvider.verify();
}
+
+ @Override
+ public Object getSystemService(String name) {
+ if (AccountTypeManager.ACCOUNT_TYPE_SERVICE.equals(name)) {
+ return mMockAccountTypeManager;
+ }
+ return super.getSystemService(name);
+ }
}
diff --git a/tests/src/com/android/contacts/tests/streamitems/StreamItemPopulatorActivity.java b/tests/src/com/android/contacts/tests/streamitems/StreamItemPopulatorActivity.java
index e27c767..d6e95ef 100644
--- a/tests/src/com/android/contacts/tests/streamitems/StreamItemPopulatorActivity.java
+++ b/tests/src/com/android/contacts/tests/streamitems/StreamItemPopulatorActivity.java
@@ -83,15 +83,15 @@
"<i>24567</i> <font color='blue' size='+1'><b>likes</b></font>"
};
- private Integer[] labelIds = new Integer[] {
- R.string.attribution_google_plus,
- R.string.attribution_google_talk,
- R.string.attribution_flicker,
- R.string.attribution_twitter
+ private String[] labelResources = new String[] {
+ "attribution_google_plus",
+ "attribution_google_talk",
+ "attribution_flicker",
+ "attribution_twitter"
};
- public Integer[] iconIds = new Integer[] {
- R.drawable.default_icon,
+ public String[] iconResources = new String[] {
+ "default_icon"
};
// Photos to randomly select from.
@@ -264,10 +264,10 @@
+ (includeAction ? " [a]" : ""));
if (includeAttribution) {
values.put(StreamItems.RES_PACKAGE, "com.android.contacts.tests");
- int sourceIndex = randInt(labelIds.length);
- values.put(StreamItems.RES_LABEL, labelIds[sourceIndex]);
- if (sourceIndex < iconIds.length) {
- values.put(StreamItems.RES_ICON, iconIds[sourceIndex]);
+ int sourceIndex = randInt(labelResources.length);
+ values.put(StreamItems.RES_LABEL, labelResources[sourceIndex]);
+ if (sourceIndex < iconResources.length) {
+ values.put(StreamItems.RES_ICON, iconResources[sourceIndex]);
}
}
if (includeComments) {
@@ -280,10 +280,6 @@
System.currentTimeMillis() - randInt(360000000));
values.put(RawContacts.ACCOUNT_TYPE, accountType);
values.put(RawContacts.ACCOUNT_NAME, accountName);
- if (includeAction) {
- values.put(StreamItems.ACTION, Intent.ACTION_VIEW);
- values.put(StreamItems.ACTION_URI, getGoogleSearchUri(place));
- }
return values;
}
@@ -295,20 +291,9 @@
values.put(StreamItemPhotos.PHOTO, loadPhotoFromResource(imageIndex));
values.put(RawContacts.ACCOUNT_TYPE, accountType);
values.put(RawContacts.ACCOUNT_NAME, accountName);
- if (imageIndex < imageStrings.length) {
- values.put(StreamItemPhotos.ACTION, Intent.ACTION_VIEW);
- String queryTerm = imageStrings[imageIndex];
- values.put(StreamItemPhotos.ACTION_URI, getGoogleSearchUri(queryTerm));
-
- }
return values;
}
- /** Returns the URI of the Google search results page for the given query. */
- private String getGoogleSearchUri(String query) {
- return "http://www.google.com/search?q=" + query.replace(" ", "+");
- }
-
private <T> T pickRandom(T[] from) {
return from[randInt(from.length)];
}
diff --git a/tests/src/com/android/contacts/util/StreamItemEntryBuilder.java b/tests/src/com/android/contacts/util/StreamItemEntryBuilder.java
index 8a17b4a..319ba48 100644
--- a/tests/src/com/android/contacts/util/StreamItemEntryBuilder.java
+++ b/tests/src/com/android/contacts/util/StreamItemEntryBuilder.java
@@ -24,36 +24,42 @@
private String mText;
private String mComment;
private long mTimestamp;
- private String mAction;
- private String mActionUri;
+ private String mAccountType;
+ private String mAccountName;
+ private String mDataSet;
private String mResPackage;
- private int mIconRes;
- private int mLabelRes;
+ private String mIconRes;
+ private String mLabelRes;
public StreamItemEntryBuilder() {}
- public StreamItemEntryBuilder setText(String text) {
- mText = text;
+ public StreamItemEntryBuilder setText(String value) {
+ mText = value;
return this;
}
- public StreamItemEntryBuilder setComment(String comment) {
- mComment = comment;
+ public StreamItemEntryBuilder setComment(String value) {
+ mComment = value;
return this;
}
- public StreamItemEntryBuilder setAction(String action) {
- mAction = action;
+ public StreamItemEntryBuilder setAccountType(String value) {
+ mAccountType = value;
return this;
}
- public StreamItemEntryBuilder setActionUri(String actionUri) {
- mActionUri = actionUri;
+ public StreamItemEntryBuilder setAccountName(String value) {
+ mAccountName = value;
+ return this;
+ }
+
+ public StreamItemEntryBuilder setDataSet(String value) {
+ mDataSet = value;
return this;
}
public StreamItemEntry build() {
- return new StreamItemEntry(mId, mText, mComment, mTimestamp, mAction, mActionUri,
- mResPackage, mIconRes, mLabelRes);
+ return new StreamItemEntry(mId, mText, mComment, mTimestamp, mAccountType, mAccountName,
+ mDataSet, mResPackage, mIconRes, mLabelRes);
}
}
\ No newline at end of file