Merge "One way to make the keyboard appear on starting search."
diff --git a/res/drawable-hdpi/dial_num_0_wht.png b/res/drawable-hdpi/dial_num_0_wht.png
index f2dcf02..2b194c8 100644
--- a/res/drawable-hdpi/dial_num_0_wht.png
+++ b/res/drawable-hdpi/dial_num_0_wht.png
Binary files differ
diff --git a/res/drawable-hdpi/dial_num_1_no_vm_wht.png b/res/drawable-hdpi/dial_num_1_no_vm_wht.png
index 8415b05..beaca97 100644
--- a/res/drawable-hdpi/dial_num_1_no_vm_wht.png
+++ b/res/drawable-hdpi/dial_num_1_no_vm_wht.png
Binary files differ
diff --git a/res/drawable-hdpi/dial_num_2_wht.png b/res/drawable-hdpi/dial_num_2_wht.png
index 852de4e..944d5d8 100644
--- a/res/drawable-hdpi/dial_num_2_wht.png
+++ b/res/drawable-hdpi/dial_num_2_wht.png
Binary files differ
diff --git a/res/drawable-hdpi/dial_num_3_wht.png b/res/drawable-hdpi/dial_num_3_wht.png
index 69fd333..9ed2bb5 100644
--- a/res/drawable-hdpi/dial_num_3_wht.png
+++ b/res/drawable-hdpi/dial_num_3_wht.png
Binary files differ
diff --git a/res/drawable-hdpi/dial_num_4_wht.png b/res/drawable-hdpi/dial_num_4_wht.png
index aab94de..92b1ca6 100644
--- a/res/drawable-hdpi/dial_num_4_wht.png
+++ b/res/drawable-hdpi/dial_num_4_wht.png
Binary files differ
diff --git a/res/drawable-hdpi/dial_num_5_wht.png b/res/drawable-hdpi/dial_num_5_wht.png
index de35f1f..21f1ce6 100644
--- a/res/drawable-hdpi/dial_num_5_wht.png
+++ b/res/drawable-hdpi/dial_num_5_wht.png
Binary files differ
diff --git a/res/drawable-hdpi/dial_num_6_wht.png b/res/drawable-hdpi/dial_num_6_wht.png
index 407ff2e..b794ffb 100644
--- a/res/drawable-hdpi/dial_num_6_wht.png
+++ b/res/drawable-hdpi/dial_num_6_wht.png
Binary files differ
diff --git a/res/drawable-hdpi/dial_num_7_wht.png b/res/drawable-hdpi/dial_num_7_wht.png
index 7a5a38c..c3cc401 100644
--- a/res/drawable-hdpi/dial_num_7_wht.png
+++ b/res/drawable-hdpi/dial_num_7_wht.png
Binary files differ
diff --git a/res/drawable-hdpi/dial_num_8_wht.png b/res/drawable-hdpi/dial_num_8_wht.png
index a29b24d..c199f96 100644
--- a/res/drawable-hdpi/dial_num_8_wht.png
+++ b/res/drawable-hdpi/dial_num_8_wht.png
Binary files differ
diff --git a/res/drawable-hdpi/dial_num_9_wht.png b/res/drawable-hdpi/dial_num_9_wht.png
index a402147..07251e5 100644
--- a/res/drawable-hdpi/dial_num_9_wht.png
+++ b/res/drawable-hdpi/dial_num_9_wht.png
Binary files differ
diff --git a/res/drawable-hdpi/dial_num_pound_wht.png b/res/drawable-hdpi/dial_num_pound_wht.png
index 0de2ab4..0e178dc 100644
--- a/res/drawable-hdpi/dial_num_pound_wht.png
+++ b/res/drawable-hdpi/dial_num_pound_wht.png
Binary files differ
diff --git a/res/drawable-hdpi/dial_num_star_wht.png b/res/drawable-hdpi/dial_num_star_wht.png
index b25eb9b..4271556 100644
--- a/res/drawable-hdpi/dial_num_star_wht.png
+++ b/res/drawable-hdpi/dial_num_star_wht.png
Binary files differ
diff --git a/res/drawable-mdpi/dial_num_0_wht.png b/res/drawable-mdpi/dial_num_0_wht.png
index c3b3f2c..81a2e46 100644
--- a/res/drawable-mdpi/dial_num_0_wht.png
+++ b/res/drawable-mdpi/dial_num_0_wht.png
Binary files differ
diff --git a/res/drawable-mdpi/dial_num_1_no_vm_wht.png b/res/drawable-mdpi/dial_num_1_no_vm_wht.png
index a5bdb41..8871de5 100644
--- a/res/drawable-mdpi/dial_num_1_no_vm_wht.png
+++ b/res/drawable-mdpi/dial_num_1_no_vm_wht.png
Binary files differ
diff --git a/res/drawable-mdpi/dial_num_2_wht.png b/res/drawable-mdpi/dial_num_2_wht.png
index ac99cec..129224f 100644
--- a/res/drawable-mdpi/dial_num_2_wht.png
+++ b/res/drawable-mdpi/dial_num_2_wht.png
Binary files differ
diff --git a/res/drawable-mdpi/dial_num_3_wht.png b/res/drawable-mdpi/dial_num_3_wht.png
index 69170b9..7828567 100644
--- a/res/drawable-mdpi/dial_num_3_wht.png
+++ b/res/drawable-mdpi/dial_num_3_wht.png
Binary files differ
diff --git a/res/drawable-mdpi/dial_num_4_wht.png b/res/drawable-mdpi/dial_num_4_wht.png
index 48a02a5..730a4d8 100644
--- a/res/drawable-mdpi/dial_num_4_wht.png
+++ b/res/drawable-mdpi/dial_num_4_wht.png
Binary files differ
diff --git a/res/drawable-mdpi/dial_num_5_wht.png b/res/drawable-mdpi/dial_num_5_wht.png
index e3c9940..8d5a6b3 100644
--- a/res/drawable-mdpi/dial_num_5_wht.png
+++ b/res/drawable-mdpi/dial_num_5_wht.png
Binary files differ
diff --git a/res/drawable-mdpi/dial_num_6_wht.png b/res/drawable-mdpi/dial_num_6_wht.png
index ab12781..d2bc13d 100644
--- a/res/drawable-mdpi/dial_num_6_wht.png
+++ b/res/drawable-mdpi/dial_num_6_wht.png
Binary files differ
diff --git a/res/drawable-mdpi/dial_num_7_wht.png b/res/drawable-mdpi/dial_num_7_wht.png
index 9e66205..41c29ae 100644
--- a/res/drawable-mdpi/dial_num_7_wht.png
+++ b/res/drawable-mdpi/dial_num_7_wht.png
Binary files differ
diff --git a/res/drawable-mdpi/dial_num_8_wht.png b/res/drawable-mdpi/dial_num_8_wht.png
index 2af30fa..b070426 100644
--- a/res/drawable-mdpi/dial_num_8_wht.png
+++ b/res/drawable-mdpi/dial_num_8_wht.png
Binary files differ
diff --git a/res/drawable-mdpi/dial_num_9_wht.png b/res/drawable-mdpi/dial_num_9_wht.png
index 1c99b61..9392aae 100644
--- a/res/drawable-mdpi/dial_num_9_wht.png
+++ b/res/drawable-mdpi/dial_num_9_wht.png
Binary files differ
diff --git a/res/drawable-mdpi/dial_num_pound_wht.png b/res/drawable-mdpi/dial_num_pound_wht.png
index e17f2bf..c9a1535 100644
--- a/res/drawable-mdpi/dial_num_pound_wht.png
+++ b/res/drawable-mdpi/dial_num_pound_wht.png
Binary files differ
diff --git a/res/drawable-mdpi/dial_num_star_wht.png b/res/drawable-mdpi/dial_num_star_wht.png
index 86113ed..4030646 100644
--- a/res/drawable-mdpi/dial_num_star_wht.png
+++ b/res/drawable-mdpi/dial_num_star_wht.png
Binary files differ
diff --git a/res/drawable-xhdpi/dial_num_0_blk.png b/res/drawable-xhdpi/dial_num_0_blk.png
new file mode 100644
index 0000000..db3d157
--- /dev/null
+++ b/res/drawable-xhdpi/dial_num_0_blk.png
Binary files differ
diff --git a/res/drawable-xhdpi/dial_num_0_wht.png b/res/drawable-xhdpi/dial_num_0_wht.png
new file mode 100644
index 0000000..c5f032f
--- /dev/null
+++ b/res/drawable-xhdpi/dial_num_0_wht.png
Binary files differ
diff --git a/res/drawable-xhdpi/dial_num_1_no_vm_blk.png b/res/drawable-xhdpi/dial_num_1_no_vm_blk.png
new file mode 100644
index 0000000..452eed0
--- /dev/null
+++ b/res/drawable-xhdpi/dial_num_1_no_vm_blk.png
Binary files differ
diff --git a/res/drawable-xhdpi/dial_num_1_no_vm_wht.png b/res/drawable-xhdpi/dial_num_1_no_vm_wht.png
new file mode 100644
index 0000000..37d90be
--- /dev/null
+++ b/res/drawable-xhdpi/dial_num_1_no_vm_wht.png
Binary files differ
diff --git a/res/drawable-xhdpi/dial_num_2_blk.png b/res/drawable-xhdpi/dial_num_2_blk.png
new file mode 100644
index 0000000..2893683
--- /dev/null
+++ b/res/drawable-xhdpi/dial_num_2_blk.png
Binary files differ
diff --git a/res/drawable-xhdpi/dial_num_2_wht.png b/res/drawable-xhdpi/dial_num_2_wht.png
new file mode 100644
index 0000000..3512386
--- /dev/null
+++ b/res/drawable-xhdpi/dial_num_2_wht.png
Binary files differ
diff --git a/res/drawable-xhdpi/dial_num_3_blk.png b/res/drawable-xhdpi/dial_num_3_blk.png
new file mode 100644
index 0000000..7ba26fa
--- /dev/null
+++ b/res/drawable-xhdpi/dial_num_3_blk.png
Binary files differ
diff --git a/res/drawable-xhdpi/dial_num_3_wht.png b/res/drawable-xhdpi/dial_num_3_wht.png
new file mode 100644
index 0000000..c2be01e
--- /dev/null
+++ b/res/drawable-xhdpi/dial_num_3_wht.png
Binary files differ
diff --git a/res/drawable-xhdpi/dial_num_4_blk.png b/res/drawable-xhdpi/dial_num_4_blk.png
new file mode 100644
index 0000000..bff80f8
--- /dev/null
+++ b/res/drawable-xhdpi/dial_num_4_blk.png
Binary files differ
diff --git a/res/drawable-xhdpi/dial_num_4_wht.png b/res/drawable-xhdpi/dial_num_4_wht.png
new file mode 100644
index 0000000..eb9f4e2
--- /dev/null
+++ b/res/drawable-xhdpi/dial_num_4_wht.png
Binary files differ
diff --git a/res/drawable-xhdpi/dial_num_5_blk.png b/res/drawable-xhdpi/dial_num_5_blk.png
new file mode 100644
index 0000000..b2a9e75
--- /dev/null
+++ b/res/drawable-xhdpi/dial_num_5_blk.png
Binary files differ
diff --git a/res/drawable-xhdpi/dial_num_5_wht.png b/res/drawable-xhdpi/dial_num_5_wht.png
new file mode 100644
index 0000000..8da8c82
--- /dev/null
+++ b/res/drawable-xhdpi/dial_num_5_wht.png
Binary files differ
diff --git a/res/drawable-xhdpi/dial_num_6_blk.png b/res/drawable-xhdpi/dial_num_6_blk.png
new file mode 100644
index 0000000..34e88ed
--- /dev/null
+++ b/res/drawable-xhdpi/dial_num_6_blk.png
Binary files differ
diff --git a/res/drawable-xhdpi/dial_num_6_wht.png b/res/drawable-xhdpi/dial_num_6_wht.png
new file mode 100644
index 0000000..c7a89fb
--- /dev/null
+++ b/res/drawable-xhdpi/dial_num_6_wht.png
Binary files differ
diff --git a/res/drawable-xhdpi/dial_num_7_blk.png b/res/drawable-xhdpi/dial_num_7_blk.png
new file mode 100644
index 0000000..c7abc7e
--- /dev/null
+++ b/res/drawable-xhdpi/dial_num_7_blk.png
Binary files differ
diff --git a/res/drawable-xhdpi/dial_num_7_wht.png b/res/drawable-xhdpi/dial_num_7_wht.png
new file mode 100644
index 0000000..e532501
--- /dev/null
+++ b/res/drawable-xhdpi/dial_num_7_wht.png
Binary files differ
diff --git a/res/drawable-xhdpi/dial_num_8_blk.png b/res/drawable-xhdpi/dial_num_8_blk.png
new file mode 100644
index 0000000..156e276
--- /dev/null
+++ b/res/drawable-xhdpi/dial_num_8_blk.png
Binary files differ
diff --git a/res/drawable-xhdpi/dial_num_8_wht.png b/res/drawable-xhdpi/dial_num_8_wht.png
new file mode 100644
index 0000000..bc3873b
--- /dev/null
+++ b/res/drawable-xhdpi/dial_num_8_wht.png
Binary files differ
diff --git a/res/drawable-xhdpi/dial_num_9_blk.png b/res/drawable-xhdpi/dial_num_9_blk.png
new file mode 100644
index 0000000..4d77bd0
--- /dev/null
+++ b/res/drawable-xhdpi/dial_num_9_blk.png
Binary files differ
diff --git a/res/drawable-xhdpi/dial_num_9_wht.png b/res/drawable-xhdpi/dial_num_9_wht.png
new file mode 100644
index 0000000..38c305f
--- /dev/null
+++ b/res/drawable-xhdpi/dial_num_9_wht.png
Binary files differ
diff --git a/res/drawable-xhdpi/dial_num_pound_blk.png b/res/drawable-xhdpi/dial_num_pound_blk.png
new file mode 100644
index 0000000..9946f61
--- /dev/null
+++ b/res/drawable-xhdpi/dial_num_pound_blk.png
Binary files differ
diff --git a/res/drawable-xhdpi/dial_num_pound_wht.png b/res/drawable-xhdpi/dial_num_pound_wht.png
new file mode 100644
index 0000000..114ea7b
--- /dev/null
+++ b/res/drawable-xhdpi/dial_num_pound_wht.png
Binary files differ
diff --git a/res/drawable-xhdpi/dial_num_star_blk.png b/res/drawable-xhdpi/dial_num_star_blk.png
new file mode 100644
index 0000000..2d9baab
--- /dev/null
+++ b/res/drawable-xhdpi/dial_num_star_blk.png
Binary files differ
diff --git a/res/drawable-xhdpi/dial_num_star_wht.png b/res/drawable-xhdpi/dial_num_star_wht.png
new file mode 100644
index 0000000..64c1153
--- /dev/null
+++ b/res/drawable-xhdpi/dial_num_star_wht.png
Binary files differ
diff --git a/res/layout-sw580dp/people_activity.xml b/res/layout-sw580dp/people_activity.xml
index 969bbf9..6d702e7 100644
--- a/res/layout-sw580dp/people_activity.xml
+++ b/res/layout-sw580dp/people_activity.xml
@@ -28,6 +28,7 @@
android:splitMotionEvents="true">
<LinearLayout
+ android:id="@+id/browse_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="vertical"
@@ -36,40 +37,33 @@
ex:layout_narrowWidth="310dip"
ex:layout_wideParentWidth="1280dip"
ex:layout_wideWidth="430dip"
- android:background="@drawable/list_background_holo">
+ android:background="@drawable/list_background_holo"
+ android:visibility="gone">
<View
style="@style/SectionDivider"
android:layout_marginLeft="40dip"
android:layout_marginTop="24dip" />
- <!-- Favorites -->
- <fragment
- android:id="@+id/favorites_fragment"
- class="com.android.contacts.list.StrequentContactListFragment"
- android:layout_height="match_parent"
- android:layout_width="match_parent" />
-
<!-- Contacts -->
<fragment
android:id="@+id/contacts_fragment"
class="com.android.contacts.list.DefaultContactBrowseListFragment"
android:layout_height="0dip"
android:layout_width="match_parent"
- android:layout_weight="1"
- />
+ android:layout_weight="1" />
<!-- Groups -->
<fragment
android:id="@+id/groups_fragment"
class="com.android.contacts.group.GroupBrowseListFragment"
android:layout_height="match_parent"
- android:layout_width="match_parent"
- />
+ android:layout_width="match_parent" />
</LinearLayout>
<view
class="com.android.contacts.widget.TransitionAnimationView"
+ android:id="@+id/details_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
ex:layout_narrowParentWidth="800dip"
@@ -82,19 +76,61 @@
ex:clipMarginBottom="9dip"
ex:enterAnimation="@android:animator/fade_in"
ex:exitAnimation="@android:animator/fade_out"
- ex:animationDuration="200">
+ ex:animationDuration="200"
+ android:visibility="gone">
+
<fragment
android:id="@+id/contact_detail_fragment"
class="com.android.contacts.detail.ContactDetailFragment"
android:layout_width="match_parent"
- android:layout_height="match_parent"
- />
+ android:layout_height="match_parent" />
<fragment
android:id="@+id/group_detail_fragment"
class="com.android.contacts.group.GroupDetailFragment"
android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+ </view>
+
+ <view
+ class="com.android.contacts.widget.TransitionAnimationView"
+ android:id="@+id/favorites_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ ex:layout_narrowParentWidth="800dip"
+ ex:layout_narrowMarginRight="0dip"
+ ex:layout_wideParentWidth="1280dip"
+ ex:layout_wideMarginRight="48dip"
+ ex:clipMarginLeft="0dip"
+ ex:clipMarginTop="3dip"
+ ex:clipMarginRight="3dip"
+ ex:clipMarginBottom="9dip"
+ ex:enterAnimation="@android:animator/fade_in"
+ ex:exitAnimation="@android:animator/fade_out"
+ ex:animationDuration="200"
+ android:paddingTop="32dip"
+ android:paddingRight="32dip">
+
+ <LinearLayout
+ android:layout_width="match_parent"
android:layout_height="match_parent"
- />
+ android:background="@drawable/list_background_holo">
+
+ <!-- Starred -->
+ <fragment
+ android:id="@+id/favorites_fragment"
+ class="com.android.contacts.list.StrequentContactListFragment"
+ android:layout_height="match_parent"
+ android:layout_width="0dip"
+ android:layout_weight="2" />
+
+ <!-- Most Frequent -->
+ <fragment
+ android:id="@+id/frequent_fragment"
+ class="com.android.contacts.list.StrequentContactListFragment"
+ android:layout_height="match_parent"
+ android:layout_width="0dip"
+ android:layout_weight="1" />
+ </LinearLayout>
</view>
</com.android.contacts.widget.InterpolatingLayout>
diff --git a/res/layout/contact_tile_regular.xml b/res/layout/contact_tile_regular.xml
index c931e97..0c02318 100644
--- a/res/layout/contact_tile_regular.xml
+++ b/res/layout/contact_tile_regular.xml
@@ -17,7 +17,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
class="com.android.contacts.list.ContactTileView"
android:focusable="true"
- android:padding="1dip"
+ android:padding="1px"
android:background="@drawable/list_selector" >
<RelativeLayout
@@ -30,15 +30,27 @@
android:layout_height="match_parent"
android:scaleType="centerCrop" />
+ <View
+ android:id="@+id/contact_tile_background"
+ android:layout_width="match_parent"
+ android:layout_height="48dip"
+ android:layout_alignParentBottom="true"
+ android:layout_alignParentLeft="true"
+ android:alpha="0.5"
+ android:background="@android:color/black" />
+
<TextView
android:id="@+id/contact_tile_name"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
+ android:layout_height="48dip"
android:layout_alignParentBottom="true"
- android:background="@color/contact_tile_regular_text"
- android:textColor="@color/contact_tile_regular_text_background"
- android:textSize="17sp"
- android:alpha="0.7" />
+ android:layout_alignParentLeft="true"
+ android:gravity="center_vertical"
+ android:paddingLeft="8dip"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:textColor="@android:color/white"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
</RelativeLayout>
diff --git a/res/layout/contact_tile_single.xml b/res/layout/contact_tile_single.xml
index e1bdccc..7147f3c 100644
--- a/res/layout/contact_tile_single.xml
+++ b/res/layout/contact_tile_single.xml
@@ -31,7 +31,9 @@
android:layout_height="fill_parent"
android:layout_weight="1"
android:textSize="18sp"
- android:textColor="#333333"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:textAppearance="?android:attr/textAppearanceMedium"
android:paddingTop="24dip" />
<ImageView
diff --git a/res/layout/dialpad_chooser_list_item.xml b/res/layout/dialpad_chooser_list_item.xml
index 853ca47..ecd4285 100644
--- a/res/layout/dialpad_chooser_list_item.xml
+++ b/res/layout/dialpad_chooser_list_item.xml
@@ -26,7 +26,7 @@
android:scaleType="center" />
<TextView android:id="@+id/text"
- android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textAppearance="?android:attr/textAppearanceMediumInverse"
android:layout_gravity="center_vertical"
android:layout_width="0dip"
android:layout_weight="1"
diff --git a/res/layout/favorites_star.xml b/res/layout/favorites_star.xml
new file mode 100644
index 0000000..f2afa31
--- /dev/null
+++ b/res/layout/favorites_star.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingLeft="10dip"
+ android:paddingRight="10dip">
+ <CheckBox
+ android:id="@+id/star"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:contentDescription="@string/description_star"
+ android:visibility="invisible"
+ style="?android:attr/starStyle"/>
+</FrameLayout>
\ No newline at end of file
diff --git a/res/menu/star.xml b/res/menu/star.xml
new file mode 100644
index 0000000..904adbf
--- /dev/null
+++ b/res/menu/star.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:id="@+id/menu_star"
+ android:showAsAction="always" />
+</menu>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 60873b1..9d726e2 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -51,8 +51,4 @@
<!-- Color of the background of the tabs on the contact detail page -->
<color name="detail_tab_background">#DBDBDB</color>
-
- <!-- Color of the text foreground and background of Regular Sized ContactTile -->
- <color name="contact_tile_regular_text">#2B1B17</color>
- <color name="contact_tile_regular_text_background">#FFFFFF</color>
</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index c9b2787..8e21762 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -967,6 +967,9 @@
<!-- Title for the call disambiguation dialog -->
<string name="call_disambig_title">Call using</string>
+ <!-- Menu item label for call settings [CHAR LIMIT=30] -->
+ <string name="call_settings">Call settings</string>
+
<!-- Title for the sms disambiguation dialog -->
<string name="sms_disambig_title">Text using</string>
diff --git a/src/com/android/contacts/activities/ContactDetailActivity.java b/src/com/android/contacts/activities/ContactDetailActivity.java
index c62084b..e216d54 100644
--- a/src/com/android/contacts/activities/ContactDetailActivity.java
+++ b/src/com/android/contacts/activities/ContactDetailActivity.java
@@ -22,6 +22,7 @@
import com.android.contacts.ContactsSearchManager;
import com.android.contacts.R;
import com.android.contacts.detail.ContactDetailAboutFragment;
+import com.android.contacts.detail.ContactDetailDisplayUtils;
import com.android.contacts.detail.ContactDetailFragment;
import com.android.contacts.detail.ContactDetailFragmentCarousel;
import com.android.contacts.detail.ContactDetailTabCarousel;
@@ -42,9 +43,15 @@
import android.support.v4.view.ViewPager.OnPageChangeListener;
import android.util.Log;
import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
+import android.widget.CheckBox;
import android.widget.Toast;
import java.util.ArrayList;
@@ -54,6 +61,9 @@
public static final int FRAGMENT_COUNT = 2;
+ private ContactLoader.Result mContactData;
+ private Uri mLookupUri;
+
private ContactDetailAboutFragment mAboutFragment;
private ContactDetailUpdatesFragment mUpdatesFragment;
@@ -129,6 +139,40 @@
}
@Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ super.onCreateOptionsMenu(menu);
+ MenuInflater inflater = getMenuInflater();
+ inflater.inflate(R.menu.star, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ MenuItem starredMenuItem = menu.findItem(R.id.menu_star);
+ ViewGroup starredContainer = (ViewGroup) getLayoutInflater().inflate(
+ R.layout.favorites_star, null, false);
+ final CheckBox starredView = (CheckBox) starredContainer.findViewById(R.id.star);
+ starredView.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // Toggle "starred" state
+ // Make sure there is a contact
+ if (mLookupUri != null) {
+ Intent intent = ContactSaveService.createSetStarredIntent(
+ ContactDetailActivity.this, mLookupUri, starredView.isChecked());
+ ContactDetailActivity.this.startService(intent);
+ }
+ }
+ });
+ // If there is contact data, update the starred state
+ if (mContactData != null) {
+ ContactDetailDisplayUtils.setStarred(mContactData, starredView);
+ }
+ starredMenuItem.setActionView(starredContainer);
+ return true;
+ }
+
+ @Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
FragmentKeyListener mCurrentFragment;
switch (getCurrentPage()) {
@@ -165,6 +209,12 @@
@Override
public void onDetailsLoaded(ContactLoader.Result result) {
+ if (result == null) {
+ return;
+ }
+ mContactData = result;
+ mLookupUri = result.getLookupUri();
+ invalidateOptionsMenu();
if (mTabCarousel != null) {
mTabCarousel.loadData(result);
}
diff --git a/src/com/android/contacts/activities/DialtactsActivity.java b/src/com/android/contacts/activities/DialtactsActivity.java
index 17d871a..a051dc3 100644
--- a/src/com/android/contacts/activities/DialtactsActivity.java
+++ b/src/com/android/contacts/activities/DialtactsActivity.java
@@ -99,19 +99,10 @@
private StrequentContactListFragment mStrequentFragment;
/**
- * The index of the tab that has last been manually selected (the user clicked on a tab).
- * This value does not keep track of programmatically set Tabs (e.g. Call Log after a Call)
- */
- private int mLastManuallySelectedTab;
-
- /**
* Fragment for searching phone numbers. Unlike the other Fragments, this doesn't correspond
* to tab but is shown by a search action.
*/
private PhoneNumberPickerFragment mPhoneNumberPickerFragment;
-
- private SearchView mSearchView;
-
/**
* True when this Activity is in its search UI (with a {@link SearchView} and
* {@link PhoneNumberPickerFragment}).
@@ -119,6 +110,14 @@
private boolean mInSearchUi;
/**
+ * The index of the tab that has last been manually selected (the user clicked on a tab).
+ * This value does not keep track of programmatically set Tabs (e.g. Call Log after a Call)
+ */
+ private int mLastManuallySelectedTab;
+
+ private SearchView mSearchView;
+
+ /**
* Listener used when one of phone numbers in search UI is selected. This will initiate a
* phone call using the phone number.
*/
@@ -206,6 +205,7 @@
.findFragmentById(R.id.phone_number_picker_fragment);
mPhoneNumberPickerFragment.setOnPhoneNumberPickerActionListener(
mPhoneNumberPickerActionListener);
+ mPhoneNumberPickerFragment.setHighlightSearchPrefix(true);
// Hide all tabs (the current tab will later be reshown once a tab is selected)
final FragmentTransaction transaction = fragmentManager.beginTransaction();
@@ -364,7 +364,9 @@
// overwritten by one of the programmatic tab selections
final int savedTabIndex = mLastManuallySelectedTab;
- if (recentCallsRequest) {
+ if (DialpadFragment.phoneIsInUse()) {
+ getActionBar().selectTab(getActionBar().getTabAt(TAB_INDEX_DIALER));
+ } else if (recentCallsRequest) {
getActionBar().selectTab(getActionBar().getTabAt(TAB_INDEX_CALL_LOG));
} else {
getActionBar().selectTab(getActionBar().getTabAt(mLastManuallySelectedTab));
@@ -511,10 +513,13 @@
ft.show(mFragment);
ft.hide(mPhoneNumberPickerFragment);
- // Remember this tab index. This function is also called, if the tab is set
- // automatically in which case the setter (setCurrentTab) has to set this to its old
- // value afterwards
- mLastManuallySelectedTab = tab.getPosition();
+ // During the call, we don't remember the tab position.
+ if (!DialpadFragment.phoneIsInUse()) {
+ // Remember this tab index. This function is also called, if the tab is set
+ // automatically in which case the setter (setCurrentTab) has to set this to its old
+ // value afterwards
+ mLastManuallySelectedTab = tab.getPosition();
+ }
}
@Override
@@ -653,7 +658,9 @@
final ActionBar actionBar = getActionBar();
final Tab tab = actionBar.getSelectedTab();
- if (tab != null) {
+
+ // User can search during the call, but we don't want to remember the status.
+ if (tab != null && !DialpadFragment.phoneIsInUse()) {
mLastManuallySelectedTab = tab.getPosition();
}
diff --git a/src/com/android/contacts/activities/PeopleActivity.java b/src/com/android/contacts/activities/PeopleActivity.java
index 548faa2..e373a6b 100644
--- a/src/com/android/contacts/activities/PeopleActivity.java
+++ b/src/com/android/contacts/activities/PeopleActivity.java
@@ -32,6 +32,7 @@
import com.android.contacts.list.ContactEntryListFragment;
import com.android.contacts.list.ContactListFilter;
import com.android.contacts.list.ContactListFilterController;
+import com.android.contacts.list.ContactTileAdapter.DisplayType;
import com.android.contacts.list.ContactsIntentResolver;
import com.android.contacts.list.ContactsRequest;
import com.android.contacts.list.ContactsUnavailableFragment;
@@ -53,7 +54,6 @@
import android.app.ActionBar.Tab;
import android.app.ActionBar.TabListener;
import android.app.Activity;
-import android.app.Dialog;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
@@ -93,6 +93,7 @@
private static final int SUBACTIVITY_NEW_CONTACT = 2;
private static final int SUBACTIVITY_EDIT_CONTACT = 3;
private static final int SUBACTIVITY_CUSTOMIZE_FILTER = 4;
+ private static final int FAVORITES_COLUMN_COUNT = 4;
private static final String KEY_SEARCH_MODE = "searchMode";
@@ -134,8 +135,13 @@
private DefaultContactBrowseListFragment mContactsFragment;
private StrequentContactListFragment mFavoritesFragment;
+ private StrequentContactListFragment mFrequentFragment;
private GroupBrowseListFragment mGroupsFragment;
+ private View mFavoritesView;
+ private View mBrowserView;
+ private View mDetailsView;
+
private enum TabState {
FAVORITES, CONTACTS, GROUPS
}
@@ -182,6 +188,8 @@
} else if (fragment instanceof StrequentContactListFragment) {
mFavoritesFragment = (StrequentContactListFragment) fragment;
mFavoritesFragment.setListener(mFavoritesFragmentListener);
+ mFavoritesFragment.setColumnCount(FAVORITES_COLUMN_COUNT);
+ mFavoritesFragment.setDisplayType(DisplayType.STARRED_ONLY);
}
}
@@ -218,19 +226,27 @@
if (createContentView) {
setContentView(R.layout.people_activity);
+ mFavoritesView = findViewById(R.id.favorites_view);
+ mDetailsView = findViewById(R.id.details_view);
+ mBrowserView = findViewById(R.id.browse_view);
+
final FragmentManager fragmentManager = getFragmentManager();
mFavoritesFragment = (StrequentContactListFragment) fragmentManager
.findFragmentById(R.id.favorites_fragment);
+ mFrequentFragment = (StrequentContactListFragment) fragmentManager
+ .findFragmentById(R.id.frequent_fragment);
mContactsFragment = (DefaultContactBrowseListFragment) fragmentManager
.findFragmentById(R.id.contacts_fragment);
mGroupsFragment = (GroupBrowseListFragment) fragmentManager
.findFragmentById(R.id.groups_fragment);
-
// Hide all tabs (the current tab will later be reshown once a tab is selected)
final FragmentTransaction transaction = fragmentManager.beginTransaction();
- transaction.hide(mFavoritesFragment);
transaction.hide(mContactsFragment);
transaction.hide(mGroupsFragment);
+
+ if (mFrequentFragment != null) {
+ mFrequentFragment.setDisplayType(DisplayType.FREQUENT_ONLY);
+ }
if (mContactDetailFragment != null) {
transaction.hide(mContactDetailFragment);
}
@@ -261,7 +277,7 @@
Tab favoritesTab = actionBar.newTab();
favoritesTab.setText(getString(R.string.strequentList));
favoritesTab.setTabListener(new TabChangeListener(mFavoritesFragment,
- mContactDetailFragment, TabState.FAVORITES));
+ mFrequentFragment, TabState.FAVORITES));
actionBar.addTab(favoritesTab);
Tab peopleTab = actionBar.newTab();
@@ -333,6 +349,22 @@
private void setSelectedTab(TabState tab) {
mSelectedTab = tab;
+
+ if (mFrequentFragment != null) {
+ switch (mSelectedTab) {
+ case FAVORITES:
+ mFavoritesView.setVisibility(View.VISIBLE);
+ mBrowserView.setVisibility(View.GONE);
+ mDetailsView.setVisibility(View.GONE);
+ break;
+ case GROUPS:
+ case CONTACTS:
+ mFavoritesView.setVisibility(View.GONE);
+ mBrowserView.setVisibility(View.VISIBLE);
+ mDetailsView.setVisibility(View.VISIBLE);
+ break;
+ }
+ }
}
@Override
@@ -461,6 +493,9 @@
switch (action) {
case START_SEARCH_MODE:
// Bring the contact list fragment (and detail fragment if applicable) to the front
+ mFavoritesView.setVisibility(View.GONE);
+ mBrowserView.setVisibility(View.VISIBLE);
+ mDetailsView.setVisibility(View.VISIBLE);
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.show(mContactsFragment);
if (mContactDetailFragment != null) ft.show(mContactDetailFragment);
@@ -480,6 +515,7 @@
if (mContactDetailFragment != null) transaction.hide(mContactDetailFragment);
transaction.commit();
}
+ if (mSelectedTab != null) setSelectedTab(mSelectedTab);
break;
case CHANGE_SEARCH_QUERY:
loadSearch(mActionBarAdapter.getQueryString());
diff --git a/src/com/android/contacts/calllog/CallLogFragment.java b/src/com/android/contacts/calllog/CallLogFragment.java
index bcffd1c..31373cc 100644
--- a/src/com/android/contacts/calllog/CallLogFragment.java
+++ b/src/com/android/contacts/calllog/CallLogFragment.java
@@ -21,6 +21,7 @@
import com.android.contacts.ContactPhotoManager;
import com.android.contacts.ContactsUtils;
import com.android.contacts.R;
+import com.android.contacts.util.ExpirableCache;
import com.android.internal.telephony.CallerInfo;
import com.google.common.annotations.VisibleForTesting;
@@ -68,7 +69,6 @@
import android.widget.TextView;
import java.lang.ref.WeakReference;
-import java.util.HashMap;
import java.util.LinkedList;
/**
@@ -78,6 +78,11 @@
implements View.OnCreateContextMenuListener {
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 */
private static final class CallLogQuery {
public static final String[] _PROJECTION = new String[] {
@@ -164,7 +169,7 @@
/** Adapter class to fill in data for the Call Log */
public final class CallLogAdapter extends GroupingListAdapter
implements Runnable, ViewTreeObserver.OnPreDrawListener, View.OnClickListener {
- HashMap<String,ContactInfo> mContactInfo;
+ ExpirableCache<String, ContactInfo> mContactInfoCache;
private final LinkedList<CallerInfoQuery> mRequests;
private volatile boolean mDone;
private boolean mLoading = true;
@@ -228,7 +233,7 @@
public CallLogAdapter() {
super(getActivity());
- mContactInfo = new HashMap<String,ContactInfo>();
+ mContactInfoCache = ExpirableCache.create(CONTACT_INFO_CACHE_SIZE);
mRequests = new LinkedList<CallerInfoQuery>();
mPreDrawListener = null;
@@ -268,7 +273,7 @@
}
public ContactInfo getContactInfo(String number) {
- return mContactInfo.get(number);
+ return mContactInfoCache.getPossiblyExpired(number);
}
public void startRequestProcessing() {
@@ -292,10 +297,8 @@
if (mCallerIdThread != null) mCallerIdThread.interrupt();
}
- public void clearCache() {
- synchronized (mContactInfo) {
- mContactInfo.clear();
- }
+ public void invalidateCache() {
+ mContactInfoCache.expireAll();
}
private void updateCallLog(CallerInfoQuery ciq, ContactInfo ci) {
@@ -341,7 +344,7 @@
private boolean queryContactInfo(CallerInfoQuery ciq) {
// First check if there was a prior request for the same number
// that was already satisfied
- ContactInfo info = mContactInfo.get(ciq.number);
+ ContactInfo info = mContactInfoCache.get(ciq.number);
boolean needNotify = false;
if (info != null && info != ContactInfo.EMPTY) {
return true;
@@ -446,7 +449,7 @@
// cache. Any cache fills happen only on the GUI thread.
info.formattedNumber = null;
- mContactInfo.put(ciq.number, info);
+ mContactInfoCache.put(ciq.number, info);
// Inform list to update this item, if in view
needNotify = true;
@@ -639,12 +642,15 @@
}
// Lookup contacts with this number
- ContactInfo info = mContactInfo.get(number);
- if (info == null) {
+ 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;
- mContactInfo.put(number, info);
+ mContactInfoCache.put(number, info);
+ Log.d(TAG, "Contact info missing: " + number);
enqueueRequest(number, c.getPosition(),
callerName, callerNumberType, callerNumberLabel, 0L);
} else if (info != ContactInfo.EMPTY) { // Has been queried
@@ -655,8 +661,17 @@
|| info.type != callerNumberType
|| !TextUtils.equals(info.label, callerNumberLabel)) {
// Something is amiss, so sync up.
+ Log.w(TAG, "Contact info inconsistent: " + number);
enqueueRequest(number, c.getPosition(),
callerName, callerNumberType, callerNumberLabel, info.photoId);
+ } else if (cachedInfo.isExpired()) {
+ Log.d(TAG, "Contact info expired: " + number);
+ // Put it back in the cache, therefore marking it as not expired, so that other
+ // entries with the same number will not re-request it.
+ mContactInfoCache.put(number, info);
+ // The contact info is no longer up to date, we should request it.
+ enqueueRequest(number, c.getPosition(), info.name, info.type, info.label,
+ info.photoId);
}
// Format and cache phone number for found contact
@@ -809,10 +824,10 @@
@Override
public void onResume() {
- // The adapter caches looked up numbers, clear it so they will get
- // looked up again.
+ // Mark all entries in the contact info cache as out of date, so they will be looked up
+ // again once being shown.
if (mAdapter != null) {
- mAdapter.clearCache();
+ mAdapter.invalidateCache();
}
startQuery();
@@ -1032,7 +1047,7 @@
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.mContactInfo.get(number);
+ ContactInfo ci = mAdapter.mContactInfoCache.getPossiblyExpired(number);
if (ci != null && ci != ContactInfo.EMPTY) {
matchingNumber = ci.number;
} else {
diff --git a/src/com/android/contacts/detail/ContactDetailTabCarousel.java b/src/com/android/contacts/detail/ContactDetailTabCarousel.java
index a12106f..b253b25 100644
--- a/src/com/android/contacts/detail/ContactDetailTabCarousel.java
+++ b/src/com/android/contacts/detail/ContactDetailTabCarousel.java
@@ -39,16 +39,13 @@
* TODO: Create custom views for the tabs so their width can be programatically set as 2/3 of the
* screen width.
*/
-public class ContactDetailTabCarousel extends HorizontalScrollView
- implements View.OnClickListener, OnTouchListener {
+public class ContactDetailTabCarousel extends HorizontalScrollView implements OnTouchListener {
private static final String TAG = "ContactDetailTabCarousel";
- private CheckBox mStarredView;
private ImageView mPhotoView;
private TextView mStatusView;
private TextView mStatusDateView;
- private Uri mContactUri;
private Listener mListener;
private View[] mTabs = new View[2];
@@ -157,8 +154,6 @@
* from the outside to fully setup the View
*/
public void loadData(ContactLoader.Result contactData) {
- mContactUri = contactData.getLookupUri();
-
View aboutView = findViewById(R.id.tab_about);
View updateView = findViewById(R.id.tab_update);
@@ -187,17 +182,14 @@
mTabs[0] = aboutTab;
mTabs[1] = updatesTab;
- // Retrieve the photo and star views for the "about" tab
+ // Retrieve the photo view for the "about" tab
mPhotoView = (ImageView) aboutView.findViewById(R.id.photo);
- mStarredView = (CheckBox) aboutView.findViewById(R.id.star);
- mStarredView.setOnClickListener(this);
// Retrieve the social update views for the "updates" tab
mStatusView = (TextView) updateView.findViewById(R.id.status);
mStatusDateView = (TextView) updateView.findViewById(R.id.status_date);
ContactDetailDisplayUtils.setPhoto(mContext, contactData, mPhotoView);
- ContactDetailDisplayUtils.setStarred(contactData, mStarredView);
ContactDetailDisplayUtils.setSocialSnippetAndDate(mContext, contactData, mStatusView,
mStatusDateView);
}
@@ -209,23 +201,6 @@
mListener = listener;
}
- // TODO: The starred icon needs to move to the action bar.
- @Override
- public void onClick(View view) {
- switch (view.getId()) {
- case R.id.star: {
- // Toggle "starred" state
- // Make sure there is a contact
- if (mContactUri != null) {
- Intent intent = ContactSaveService.createSetStarredIntent(
- getContext(), mContactUri, mStarredView.isChecked());
- getContext().startService(intent);
- }
- break;
- }
- }
- }
-
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
diff --git a/src/com/android/contacts/dialpad/DialpadFragment.java b/src/com/android/contacts/dialpad/DialpadFragment.java
index 778fe42..fd557d5 100644
--- a/src/com/android/contacts/dialpad/DialpadFragment.java
+++ b/src/com/android/contacts/dialpad/DialpadFragment.java
@@ -20,7 +20,6 @@
import com.android.contacts.R;
import com.android.contacts.SpecialCharSequenceMgr;
import com.android.contacts.activities.DialtactsActivity;
-import com.android.contacts.list.StrequentContactListFragment.Listener;
import com.android.internal.telephony.ITelephony;
import com.android.phone.CallLogAsync;
import com.android.phone.HapticFeedback;
@@ -111,9 +110,11 @@
//Member variables for dialpad options
private MenuItem m2SecPauseMenuItem;
private MenuItem mWaitMenuItem;
+ private MenuItem mCallSettingsItem;
private static final int MENU_ADD_CONTACTS = 1;
private static final int MENU_2S_PAUSE = 2;
private static final int MENU_WAIT = 3;
+ private static final int MENU_CALL_SETTINGS = 4;
private boolean mHasVoicemail = false;
@@ -529,18 +530,27 @@
.setIcon(R.drawable.ic_menu_2sec_pause);
mWaitMenuItem = menu.add(0, MENU_WAIT, 0, R.string.add_wait)
.setIcon(R.drawable.ic_menu_wait);
+ // TODO: icon
+ mCallSettingsItem = menu.add(0, MENU_CALL_SETTINGS, 0, R.string.call_settings);
}
@Override
public void onPrepareOptionsMenu(Menu menu) {
- // If we have not been inflated yet, there is no menu
- if (mDialpadChooser == null) return;
+ if (mDialpadChooser == null || mDigits == null) {
+ // The layout itself isn't ready yet. Let's ignore this call.
+ return;
+ }
+ // We show "Call Settings" menu every time
+ mCallSettingsItem.setVisible(true);
+ Intent settingsIntent = new Intent(Intent.ACTION_MAIN);
+ settingsIntent.setClassName("com.android.phone", "com.android.phone.CallFeaturesSetting");
+ mCallSettingsItem.setIntent(settingsIntent);
+
+ // We show "add to contacts", "2sec pause", and "add wait" menus only when the user is
+ // seeing usual dialpads and has typed at least one digit.
// We never show a menu if the "choose dialpad" UI is up.
- // Otherwise the menu is allowed (see onPrepareOptionsMenu() below.)
- if (!dialpadChooserVisible()) return;
-
- if (isDigitsEmpty()) {
+ if (dialpadChooserVisible() || isDigitsEmpty()) {
mAddToContactMenuItem.setVisible(false);
m2SecPauseMenuItem.setVisible(false);
mWaitMenuItem.setVisible(false);
@@ -1020,7 +1030,7 @@
* @return true if the phone is "in use", meaning that at least one line
* is active (ie. off hook or ringing or dialing).
*/
- private boolean phoneIsInUse() {
+ public static boolean phoneIsInUse() {
boolean phoneInUse = false;
try {
ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
diff --git a/src/com/android/contacts/editor/ContactEditorFragment.java b/src/com/android/contacts/editor/ContactEditorFragment.java
index ba2794d..91792df 100644
--- a/src/com/android/contacts/editor/ContactEditorFragment.java
+++ b/src/com/android/contacts/editor/ContactEditorFragment.java
@@ -272,7 +272,9 @@
// If anything was left unsaved, save it now but keep the editor open.
if (!getActivity().isChangingConfigurations() && mStatus == Status.EDITING) {
- save(SaveMode.RELOAD);
+ if (mStatus != SaveMode.JOIN) {
+ save(SaveMode.RELOAD);
+ }
}
}
diff --git a/src/com/android/contacts/list/ContactEntryListAdapter.java b/src/com/android/contacts/list/ContactEntryListAdapter.java
index 95a8c2b..62b678c 100644
--- a/src/com/android/contacts/list/ContactEntryListAdapter.java
+++ b/src/com/android/contacts/list/ContactEntryListAdapter.java
@@ -31,6 +31,7 @@
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Directory;
import android.text.TextUtils;
+import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -320,9 +321,16 @@
/**
* Updates partitions according to the directory meta-data contained in the supplied
- * cursor. Takes ownership of the cursor and will close it.
+ * cursor.
*/
public void changeDirectories(Cursor cursor) {
+ if (cursor.getCount() == 0) {
+ // Directory table must have at least local directory, without which this adapter will
+ // enter very weird state.
+ Log.e(TAG, "Directory search loader returned an empty cursor, which implies we have " +
+ "no directory entries.", new RuntimeException());
+ return;
+ }
HashSet<Long> directoryIds = new HashSet<Long>();
int idColumnIndex = cursor.getColumnIndex(Directory._ID);
diff --git a/src/com/android/contacts/list/ContactTileAdapter.java b/src/com/android/contacts/list/ContactTileAdapter.java
index feb308c..86f6b33 100644
--- a/src/com/android/contacts/list/ContactTileAdapter.java
+++ b/src/com/android/contacts/list/ContactTileAdapter.java
@@ -102,7 +102,7 @@
DisplayType displayType) {
mListener = listener;
mContext = context;
- mColumnCount = numCols;
+ mColumnCount = (displayType == DisplayType.FREQUENT_ONLY ? 1 : numCols);
mDisplayType = displayType;
bindColumnIndices();
@@ -112,6 +112,14 @@
mPhotoManager = photoLoader;
}
+ public void setColumnCount(int columnCount) {
+ mColumnCount = columnCount;
+ }
+
+ public void setDisplayType(DisplayType displayType) {
+ mDisplayType = displayType;
+ }
+
/**
* Sets the column indices for expected {@link Cursor}
* based on {@link DisplayType}.
@@ -202,7 +210,6 @@
// Adding Containter that has multi columns
rowCount += getNumRows(mContacts.size());
}
-
// Adding Divider Row if Neccessary
if (mDisplayType == DisplayType.STREQUENT && mContacts.size() > 0) rowCount++;
@@ -232,9 +239,8 @@
if (contactIndex < mContacts2.size()) {
contactList = mContacts2;
} else {
- if (mDisplayType == DisplayType.STREQUENT) {
- contactIndex = (position - mDividerRowIndex - 1) * mColumnCount;
-
+ if (mDisplayType == DisplayType.STREQUENT ||
+ mDisplayType == DisplayType.FREQUENT_ONLY) {
resultList.add(mContacts.get(position - mDividerRowIndex - 1));
return resultList;
}
@@ -385,9 +391,9 @@
for (int columnCounter = 0; columnCounter < columnCount; columnCounter++) {
ContactEntry entry =
columnCounter < list.size() ? list.get(columnCounter) : null;
- addTileFromEntry(entry, columnCounter);
+ addTileFromEntry(entry, columnCounter);
+ }
}
- }
private void addTileFromEntry(ContactEntry entry, int tileIndex) {
ContactTileView contactTile;
diff --git a/src/com/android/contacts/list/ContactTileView.java b/src/com/android/contacts/list/ContactTileView.java
index da1351f..883e3f0 100644
--- a/src/com/android/contacts/list/ContactTileView.java
+++ b/src/com/android/contacts/list/ContactTileView.java
@@ -45,7 +45,6 @@
private ContactPhotoManager mPhotoManager = null;
/**
* Is set to true if the {@link ContactTileView} is a square.
- * A {@link ViewType#REGULAR} is displayed as a square.
*/
private boolean mIsSquare;
diff --git a/src/com/android/contacts/list/PhoneNumberListAdapter.java b/src/com/android/contacts/list/PhoneNumberListAdapter.java
index c159ac8..fee1064 100644
--- a/src/com/android/contacts/list/PhoneNumberListAdapter.java
+++ b/src/com/android/contacts/list/PhoneNumberListAdapter.java
@@ -60,6 +60,7 @@
private CharSequence mUnknownNameText;
private int mDisplayNameColumnIndex;
private int mAlternativeDisplayNameColumnIndex;
+ private boolean mHighlightSearchPrefix;
public PhoneNumberListAdapter(Context context) {
super(context);
@@ -157,6 +158,9 @@
protected void bindView(View itemView, int partition, Cursor cursor, int position) {
ContactListItemView view = (ContactListItemView)itemView;
+ view.setHighlightedPrefix(mHighlightSearchPrefix && isSearchMode() ?
+ getUpperCaseQueryString() : null);
+
// Look at elements before and after this position, checking if contact IDs are same.
// If they have one same contact ID, it means they can be grouped.
//
@@ -245,4 +249,8 @@
protected void unbindPhoto(final ContactListItemView view) {
view.removePhotoView(true, false);
}
+
+ public void setHighlightSearchPrefix(boolean highlight) {
+ mHighlightSearchPrefix = highlight;
+ }
}
diff --git a/src/com/android/contacts/list/PhoneNumberPickerFragment.java b/src/com/android/contacts/list/PhoneNumberPickerFragment.java
index a38f734..b40f80a 100644
--- a/src/com/android/contacts/list/PhoneNumberPickerFragment.java
+++ b/src/com/android/contacts/list/PhoneNumberPickerFragment.java
@@ -111,4 +111,13 @@
public void onPickerResult(Intent data) {
mListener.onPickPhoneNumberAction(data.getData());
}
+
+ public void setHighlightSearchPrefix(boolean highlight) {
+ if (!isLegacyCompatibilityMode()) {
+ PhoneNumberListAdapter adapter = (PhoneNumberListAdapter)getAdapter();
+ adapter.setHighlightSearchPrefix(highlight);
+ } else {
+ // Not supported.
+ }
+ }
}
diff --git a/src/com/android/contacts/list/StrequentContactListFragment.java b/src/com/android/contacts/list/StrequentContactListFragment.java
index d226950..e0f0dc1 100644
--- a/src/com/android/contacts/list/StrequentContactListFragment.java
+++ b/src/com/android/contacts/list/StrequentContactListFragment.java
@@ -73,6 +73,14 @@
getLoaderManager().restartLoader(LOADER_STREQUENT, null, mStrequentLoaderListener);
}
+ public void setColumnCount(int columnCount) {
+ mAdapter.setColumnCount(columnCount);
+ }
+
+ public void setDisplayType(DisplayType displayType) {
+ mAdapter.setDisplayType(displayType);
+ }
+
/**
* The listener for the strequent meta data loader.
*/
diff --git a/src/com/android/contacts/util/ExpirableCache.java b/src/com/android/contacts/util/ExpirableCache.java
new file mode 100644
index 0000000..0ee3bbe
--- /dev/null
+++ b/src/com/android/contacts/util/ExpirableCache.java
@@ -0,0 +1,263 @@
+/*
+ * 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.util;
+
+import android.util.LruCache;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.annotation.concurrent.Immutable;
+import javax.annotation.concurrent.ThreadSafe;
+
+/**
+ * An LRU cache in which all items can be marked as expired at a given time and it is possible to
+ * query whether a particular cached value is expired or not.
+ * <p>
+ * A typical use case for this is caching of values which are expensive to compute but which are
+ * still useful when out of date.
+ * <p>
+ * Consider a cache for contact information:
+ * <pre>{@code
+ * private ExpirableCache<String, Contact> mContactCache;}</pre>
+ * which stores the contact information for a given phone number.
+ * <p>
+ * When we need to store contact information for a given phone number, we can look up the info in
+ * the cache:
+ * <pre>{@code
+ * CachedValue<Contact> cachedContact = mContactCache.getCachedValue(phoneNumber);
+ * }</pre>
+ * We might also want to fetch the contact information again if the item is expired.
+ * <pre>
+ * if (cachedContact.isExpired()) {
+ * fetchContactForNumber(phoneNumber,
+ * new FetchListener() {
+ * @Override
+ * public void onFetched(Contact contact) {
+ * mContactCache.put(phoneNumber, contact);
+ * }
+ * });
+ * }</pre>
+ * and insert it back into the cache when the fetch completes.
+ * <p>
+ * At a certain point we want to expire the content of the cache because we know the content may
+ * no longer be up-to-date, for instance, when resuming the activity this is shown into:
+ * <pre>
+ * @Override
+ * protected onResume() {
+ * // We were paused for some time, the cached value might no longer be up to date.
+ * mContactCache.expireAll();
+ * super.onResume();
+ * }
+ * </pre>
+ * The values will be still available from the cache, but they will be expired.
+ * <p>
+ * If interested only in the value itself, not whether it is expired or not, one should use the
+ * {@link #getPossiblyExpired(Object)} method. If interested only in non-expired values, one should
+ * use the {@link #get(Object)} method instead.
+ * <p>
+ * This class wraps around an {@link LruCache} instance: it follows the {@link LruCache} behavior
+ * for evicting items when the cache is full. It is possible to supply your own subclass of LruCache
+ * by using the {@link #create(LruCache)} method, which can define a custom expiration policy.
+ * Since the underlying cache maps keys to cached values it can determine which items are expired
+ * and which are not, allowing for an implementation that evicts expired items before non expired
+ * ones.
+ * <p>
+ * This class is thread-safe.
+ *
+ * @param <K> the type of the keys
+ * @param <V> the type of the values
+ */
+@ThreadSafe
+public class ExpirableCache<K, V> {
+ /**
+ * A cached value stored inside the cache.
+ * <p>
+ * It provides access to the value stored in the cache but also allows to check whether the
+ * value is expired.
+ *
+ * @param <V> the type of value stored in the cache
+ */
+ public interface CachedValue<V> {
+ /** Returns the value stored in the cache for a given key. */
+ public V getValue();
+
+ /**
+ * Checks whether the value, while still being present in the cache, is expired.
+ *
+ * @return true if the value is expired
+ */
+ public boolean isExpired();
+ }
+
+ /**
+ * Cached values storing the generation at which they were added.
+ */
+ @Immutable
+ private static class GenerationalCachedValue<V> implements ExpirableCache.CachedValue<V> {
+ /** The value stored in the cache. */
+ public final V mValue;
+ /** The generation at which the value was added to the cache. */
+ private final int mGeneration;
+ /** The atomic integer storing the current generation of the cache it belongs to. */
+ private final AtomicInteger mCacheGeneration;
+
+ /**
+ * @param cacheGeneration the atomic integer storing the generation of the cache in which
+ * this value will be stored
+ */
+ public GenerationalCachedValue(V value, AtomicInteger cacheGeneration) {
+ mValue = value;
+ mCacheGeneration = cacheGeneration;
+ // Snapshot the current generation.
+ mGeneration = mCacheGeneration.get();
+ }
+
+ @Override
+ public V getValue() {
+ return mValue;
+ }
+
+ @Override
+ public boolean isExpired() {
+ return mGeneration != mCacheGeneration.get();
+ }
+ }
+
+ /** The underlying cache used to stored the cached values. */
+ private LruCache<K, CachedValue<V>> mCache;
+
+ /**
+ * The current generation of items added to the cache.
+ * <p>
+ * Items in the cache can belong to a previous generation, but in that case they would be
+ * expired.
+ *
+ * @see ExpirableCache.CachedValue#isExpired()
+ */
+ private final AtomicInteger mGeneration;
+
+ private ExpirableCache(LruCache<K, CachedValue<V>> cache) {
+ mCache = cache;
+ mGeneration = new AtomicInteger(0);
+ }
+
+ /**
+ * Returns the cached value for the given key, or null if no value exists.
+ * <p>
+ * The cached value gives access both to the value associated with the key and whether it is
+ * expired or not.
+ * <p>
+ * If not interested in whether the value is expired, use {@link #getPossiblyExpired(Object)}
+ * instead.
+ * <p>
+ * If only wants values that are not expired, use {@link #get(Object)} instead.
+ *
+ * @param key the key to look up
+ */
+ public CachedValue<V> getCachedValue(K key) {
+ return mCache.get(key);
+ }
+
+ /**
+ * Returns the value for the given key, or null if no value exists.
+ * <p>
+ * When using this method, it is not possible to determine whether the value is expired or not.
+ * Use {@link #getCachedValue(Object)} to achieve that instead. However, if using
+ * {@link #getCachedValue(Object)} to determine if an item is expired, one should use the item
+ * within the {@link CachedValue} and not call {@link #getPossiblyExpired(Object)} to get the
+ * value afterwards, since that is not guaranteed to return the same value or that the newly
+ * returned value is in the same state.
+ *
+ * @param key the key to look up
+ */
+ public V getPossiblyExpired(K key) {
+ CachedValue<V> cachedValue = getCachedValue(key);
+ return cachedValue == null ? null : cachedValue.getValue();
+ }
+
+ /**
+ * Returns the value for the given key only if it is not expired, or null if no value exists or
+ * is expired.
+ * <p>
+ * This method will return null if either there is no value associated with this key or if the
+ * associated value is expired.
+ *
+ * @param key the key to look up
+ */
+ public V get(K key) {
+ CachedValue<V> cachedValue = getCachedValue(key);
+ return cachedValue == null || cachedValue.isExpired() ? null : cachedValue.getValue();
+ }
+
+ /**
+ * Puts an item in the cache.
+ * <p>
+ * Newly added item will not be expired until {@link #expireAll()} is next called.
+ *
+ * @param key the key to look up
+ * @param value the value to associate with the key
+ */
+ public void put(K key, V value) {
+ mCache.put(key, newCachedValue(value));
+ }
+
+ /**
+ * Mark all items currently in the cache as expired.
+ * <p>
+ * Newly added items after this call will be marked as not expired.
+ * <p>
+ * Expiring the items in the cache does not imply they will be evicted.
+ */
+ public void expireAll() {
+ mGeneration.incrementAndGet();
+ }
+
+ /**
+ * Creates a new {@link CachedValue} instance to be stored in this cache.
+ * <p>
+ * Implementation of {@link LruCache#create(K)} can use this method to create a new entry.
+ */
+ public CachedValue<V> newCachedValue(V value) {
+ return new GenerationalCachedValue<V>(value, mGeneration);
+ }
+
+ /**
+ * Creates a new {@link ExpirableCache} that wraps the given {@link LruCache}.
+ * <p>
+ * The created cache takes ownership of the cache passed in as an argument.
+ *
+ * @param <K> the type of the keys
+ * @param <V> the type of the values
+ * @param cache the cache to store the value in
+ * @return the newly created expirable cache
+ * @throws IllegalArgumentException if the cache is not empty
+ */
+ public static <K, V> ExpirableCache<K, V> create(LruCache<K, CachedValue<V>> cache) {
+ return new ExpirableCache<K, V>(cache);
+ }
+
+ /**
+ * Creates a new {@link ExpirableCache} with the given maximum size.
+ *
+ * @param <K> the type of the keys
+ * @param <V> the type of the values
+ * @return the newly created expirable cache
+ */
+ public static <K, V> ExpirableCache<K, V> create(int maxSize) {
+ return create(new LruCache<K, CachedValue<V>>(maxSize));
+ }
+}
diff --git a/tests/src/com/android/contacts/util/ExpirableCacheTest.java b/tests/src/com/android/contacts/util/ExpirableCacheTest.java
new file mode 100644
index 0000000..309cc0e
--- /dev/null
+++ b/tests/src/com/android/contacts/util/ExpirableCacheTest.java
@@ -0,0 +1,125 @@
+/*
+ * 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.util;
+
+import com.android.contacts.util.ExpirableCache.CachedValue;
+
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.LruCache;
+
+/**
+ * Unit tests for {@link ExpirableCache}.
+ */
+@SmallTest
+public class ExpirableCacheTest extends AndroidTestCase {
+ /** The object under test. */
+ private ExpirableCache<String, Integer> mCache;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ LruCache<String, CachedValue<Integer>> lruCache =
+ new LruCache<String, ExpirableCache.CachedValue<Integer>>(20);
+ mCache = ExpirableCache.create(lruCache);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ mCache = null;
+ super.tearDown();
+ }
+
+ public void testPut() {
+ mCache.put("a", 1);
+ mCache.put("b", 2);
+ assertEquals(1, mCache.getPossiblyExpired("a").intValue());
+ assertEquals(2, mCache.getPossiblyExpired("b").intValue());
+ mCache.put("a", 3);
+ assertEquals(3, mCache.getPossiblyExpired("a").intValue());
+ }
+
+ public void testGet_NotExisting() {
+ assertNull(mCache.getPossiblyExpired("a"));
+ mCache.put("b", 1);
+ assertNull(mCache.getPossiblyExpired("a"));
+ }
+
+ public void testGet_Expired() {
+ mCache.put("a", 1);
+ assertEquals(1, mCache.getPossiblyExpired("a").intValue());
+ mCache.expireAll();
+ assertEquals(1, mCache.getPossiblyExpired("a").intValue());
+ }
+
+ public void testGetNotExpired_NotExisting() {
+ assertNull(mCache.get("a"));
+ mCache.put("b", 1);
+ assertNull(mCache.get("a"));
+ }
+
+ public void testGetNotExpired_Expired() {
+ mCache.put("a", 1);
+ assertEquals(1, mCache.get("a").intValue());
+ mCache.expireAll();
+ assertNull(mCache.get("a"));
+ }
+
+ public void testGetCachedValue_NotExisting() {
+ assertNull(mCache.getCachedValue("a"));
+ mCache.put("b", 1);
+ assertNull(mCache.getCachedValue("a"));
+ }
+
+ public void testGetCachedValue_Expired() {
+ mCache.put("a", 1);
+ assertFalse("Should not be expired", mCache.getCachedValue("a").isExpired());
+ mCache.expireAll();
+ assertTrue("Should be expired", mCache.getCachedValue("a").isExpired());
+ }
+
+ public void testGetChangedValue_PutAfterExpired() {
+ mCache.put("a", 1);
+ mCache.expireAll();
+ mCache.put("a", 1);
+ assertFalse("Should not be expired", mCache.getCachedValue("a").isExpired());
+ }
+
+ public void testComputingCache() {
+ // Creates a cache in which all unknown values default to zero.
+ mCache = ExpirableCache.create(
+ new LruCache<String, ExpirableCache.CachedValue<Integer>>(10) {
+ @Override
+ protected CachedValue<Integer> create(String key) {
+ return mCache.newCachedValue(0);
+ }
+ });
+
+ // The first time we request a new value, we add it to the cache.
+ CachedValue<Integer> cachedValue = mCache.getCachedValue("a");
+ assertNotNull("Should have been created implicitly", cachedValue);
+ assertEquals(0, cachedValue.getValue().intValue());
+ assertFalse("Should not be expired", cachedValue.isExpired());
+
+ // If we expire all the values, the implicitly created value will also be marked as expired.
+ mCache.expireAll();
+ CachedValue<Integer> expiredCachedValue = mCache.getCachedValue("a");
+ assertNotNull("Should have been created implicitly", expiredCachedValue);
+ assertEquals(0, expiredCachedValue.getValue().intValue());
+ assertTrue("Should be expired", expiredCachedValue.isExpired());
+ }
+}