Merge "Add a test activity to add entries to the call log."
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 47340a0..d298fbe 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -25,6 +25,8 @@
<uses-permission android:name="android.permission.CALL_PRIVILEGED" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
+ <uses-permission android:name="android.permission.READ_PROFILE" />
+ <uses-permission android:name="android.permission.WRITE_PROFILE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />
diff --git a/res/layout/custom_action_bar.xml b/res/layout/custom_action_bar.xml
new file mode 100644
index 0000000..2357756
--- /dev/null
+++ b/res/layout/custom_action_bar.xml
@@ -0,0 +1,28 @@
+<?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="match_parent"
+ android:layout_height="match_parent" >
+
+ <SearchView
+ android:id="@+id/search_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:iconifiedByDefault="false" />
+
+</FrameLayout>
diff --git a/res/layout/group_browse_list_fragment.xml b/res/layout/group_browse_list_fragment.xml
index 50c02c8..d41772d 100644
--- a/res/layout/group_browse_list_fragment.xml
+++ b/res/layout/group_browse_list_fragment.xml
@@ -26,7 +26,9 @@
android:layout_height="0dip"
android:fastScrollEnabled="true"
android:scrollbarStyle="outsideOverlay"
- android:layout_weight="1" />
+ android:layout_weight="1"
+ android:cacheColorHint="@android:color/transparent"
+ android:divider="@null" />
<TextView
android:id="@+id/empty"
diff --git a/res/layout/group_browse_list_item.xml b/res/layout/group_browse_list_item.xml
index 28f4e17..d94d444 100644
--- a/res/layout/group_browse_list_item.xml
+++ b/res/layout/group_browse_list_item.xml
@@ -19,24 +19,15 @@
class="com.android.contacts.group.GroupBrowseListAdapter$GroupListItem"
android:orientation="horizontal"
android:layout_width="match_parent"
- android:layout_height="wrap_content">
-
- <ImageView
- android:id="@+id/icon"
- android:scaleType="center"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:layout_marginLeft="10dip"
- android:layout_marginRight="10dip"
- android:layout_gravity="center_vertical"
- android:src="@drawable/ic_menu_display_all_holo_light" />
+ android:layout_height="wrap_content"
+ style="@style/GroupBrowseListItem">
<LinearLayout
android:orientation="vertical"
- android:layout_width="wrap_content"
+ android:layout_width="0dip"
android:layout_height="match_parent"
- android:paddingTop="5dip"
- android:paddingBottom="5dip">
+ android:layout_weight="1"
+ android:padding="5dip">
<TextView
android:id="@+id/label"
@@ -60,4 +51,13 @@
</LinearLayout>
+ <ImageView
+ android:id="@+id/icon"
+ android:scaleType="center"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_marginRight="20dip"
+ android:layout_gravity="center_vertical"
+ android:src="@drawable/ic_menu_display_all_holo_light" />
+
</view>
diff --git a/res/layout/navigation_bar.xml b/res/layout/navigation_bar.xml
deleted file mode 100644
index 803705c..0000000
--- a/res/layout/navigation_bar.xml
+++ /dev/null
@@ -1,73 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2010 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- style="?android:attr/actionBarTabBarStyle"
- android:id="@+id/navigation_bar"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="horizontal" >
-
- <TextView
- android:id="@+id/search_label"
- style="?android:attr/actionButtonStyle"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:gravity="center_vertical"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:text="@string/search_label"
- android:minWidth="@dimen/action_bar_filter_min_width" />
-
- <view
- class="com.android.contacts.list.ContactListFilterView"
- style="?android:attr/actionDropDownStyle"
- android:id="@+id/filter_view"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:layout_gravity="left|center_vertical"
- android:focusable="true"
- android:minWidth="@dimen/action_bar_filter_min_width"
- >
- <ImageView
- android:id="@+id/icon"
- android:scaleType="fitCenter"
- android:layout_width="24dip"
- android:layout_height="24dip"
- android:layout_marginRight="7dip"
- android:layout_gravity="center_vertical" />
-
- <TextView
- android:id="@+id/label"
- android:layout_height="match_parent"
- android:layout_width="wrap_content"
- android:gravity="center_vertical"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:ellipsize="end"
- android:singleLine="true"
- android:maxWidth="@dimen/action_bar_filter_max_width" />
- </view>
-
- <SearchView
- android:id="@+id/search_view"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_gravity="center_vertical"
- android:layout_marginLeft="@dimen/action_bar_search_spacing"
- android:maxWidth="@dimen/action_bar_search_max_width"
- android:iconifiedByDefault="false" />
-
-</LinearLayout>
diff --git a/res/menu-xlarge/actions.xml b/res/menu-xlarge/actions.xml
new file mode 100644
index 0000000..60788e0
--- /dev/null
+++ b/res/menu-xlarge/actions.xml
@@ -0,0 +1,54 @@
+<?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_search"
+ android:showAsAction="always"
+ android:actionViewClass="android.widget.SearchView" />
+
+ <item
+ android:id="@+id/menu_add_contact"
+ android:icon="@drawable/ic_menu_add_contact_holo_light"
+ android:title="@string/menu_new_contact_action_bar"
+ android:showAsAction="withText|always" />
+
+ <item
+ android:id="@+id/menu_add_group"
+ android:icon="@drawable/ic_menu_display_all_holo_light"
+ android:title="@string/menu_new_contact_action_bar"
+ android:showAsAction="withText|always" />
+
+ <item
+ android:id="@+id/menu_contacts_filter"
+ android:icon="@drawable/ic_menu_settings_holo_light"
+ android:title="@string/menu_contacts_filter" />
+
+ <item
+ android:id="@+id/menu_settings"
+ android:icon="@drawable/ic_menu_settings_holo_light"
+ android:title="@string/menu_settings" />
+
+ <item
+ android:id="@+id/menu_accounts"
+ android:icon="@drawable/ic_menu_accounts_holo_light"
+ android:title="@string/menu_accounts" />
+
+ <item
+ android:id="@+id/menu_import_export"
+ android:icon="@drawable/ic_menu_import_export_holo_light"
+ android:title="@string/menu_import_export" />
+
+</menu>
diff --git a/res/menu/view.xml b/res/menu-xlarge/view_contact.xml
similarity index 100%
rename from res/menu/view.xml
rename to res/menu-xlarge/view_contact.xml
diff --git a/res/menu-xlarge/view_group.xml b/res/menu-xlarge/view_group.xml
new file mode 100644
index 0000000..8b0867b
--- /dev/null
+++ b/res/menu-xlarge/view_group.xml
@@ -0,0 +1,34 @@
+<?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_edit_group"
+ android:icon="@drawable/ic_menu_compose_holo_light"
+ android:title="@string/menu_editGroup"
+ android:alphabeticShortcut="e"
+ android:showAsAction="always" />
+
+ <item
+ android:id="@+id/menu_rename_group"
+ android:icon="@drawable/ic_menu_settings_holo_light"
+ android:title="@string/menu_renameGroup" />
+
+ <item
+ android:id="@+id/menu_delete_group"
+ android:icon="@drawable/ic_menu_trash_holo_light"
+ android:title="@string/menu_deleteGroup" />
+</menu>
diff --git a/res/menu/actions.xml b/res/menu/actions.xml
index 083d352..996cfef 100644
--- a/res/menu/actions.xml
+++ b/res/menu/actions.xml
@@ -16,12 +16,18 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/menu_search"
- android:showAsAction="always"
- android:actionViewClass="android.widget.SearchView" />
+ android:icon="@android:drawable/ic_menu_search"
+ android:title="@string/menu_search" />
<item
- android:id="@+id/menu_add"
- android:showAsAction="always" />
+ android:id="@+id/menu_add_contact"
+ android:icon="@drawable/ic_menu_add_contact_holo_light"
+ android:title="@string/menu_new_contact_action_bar" />
+
+ <item
+ android:id="@+id/menu_add_group"
+ android:icon="@drawable/ic_menu_display_all_holo_light"
+ android:title="@string/menu_new_contact_action_bar" />
<item
android:id="@+id/menu_contacts_filter"
@@ -43,13 +49,4 @@
android:icon="@drawable/ic_menu_import_export_holo_light"
android:title="@string/menu_import_export" />
- <item
- android:id="@+id/menu_rename_group"
- android:icon="@drawable/ic_menu_settings_holo_light"
- android:title="@string/menu_renameGroup" />
-
- <item
- android:id="@+id/menu_delete_group"
- android:icon="@drawable/ic_menu_trash_holo_light"
- android:title="@string/menu_deleteGroup" />
</menu>
diff --git a/res/menu/view_contact.xml b/res/menu/view_contact.xml
new file mode 100644
index 0000000..7cf17d6
--- /dev/null
+++ b/res/menu/view_contact.xml
@@ -0,0 +1,39 @@
+<?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_edit"
+ android:icon="@drawable/ic_menu_compose_holo_light"
+ android:title="@string/menu_editContact"
+ android:alphabeticShortcut="e" />
+
+ <item
+ android:id="@+id/menu_share"
+ android:icon="@drawable/ic_menu_share_holo_light"
+ android:title="@string/menu_share"
+ android:alphabeticShortcut="s" />
+
+ <item
+ android:id="@+id/menu_options"
+ android:icon="@drawable/ic_menu_mark"
+ android:title="@string/menu_contactOptions" />
+
+ <item
+ android:id="@+id/menu_delete"
+ android:icon="@drawable/ic_menu_trash_holo_light"
+ android:title="@string/menu_deleteContact" />
+</menu>
diff --git a/res/menu/view_group.xml b/res/menu/view_group.xml
new file mode 100644
index 0000000..3ff6ac6
--- /dev/null
+++ b/res/menu/view_group.xml
@@ -0,0 +1,33 @@
+<?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_edit_group"
+ android:icon="@drawable/ic_menu_compose_holo_light"
+ android:title="@string/menu_editGroup"
+ android:alphabeticShortcut="e" />
+
+ <item
+ android:id="@+id/menu_rename_group"
+ android:icon="@drawable/ic_menu_settings_holo_light"
+ android:title="@string/menu_renameGroup" />
+
+ <item
+ android:id="@+id/menu_delete_group"
+ android:icon="@drawable/ic_menu_trash_holo_light"
+ android:title="@string/menu_deleteGroup" />
+</menu>
diff --git a/res/values-xlarge/styles.xml b/res/values-xlarge/styles.xml
index 0179f6a..cbef91a 100644
--- a/res/values-xlarge/styles.xml
+++ b/res/values-xlarge/styles.xml
@@ -31,6 +31,7 @@
<item name="list_item_vertical_divider_margin">5dip</item>
<item name="list_item_presence_icon_margin">30dip</item>
<item name="list_item_photo_size">64dip</item>
+ <item name="list_item_profile_photo_size">80dip</item>
<item name="list_item_prefix_highlight_color">#729a27</item>
<item name="list_item_header_text_indent">77dip</item>
<item name="list_item_header_text_color">?color/section_header_text_color</item>
@@ -53,6 +54,7 @@
<item name="list_item_vertical_divider_margin">5dip</item>
<item name="list_item_presence_icon_margin">30dip</item>
<item name="list_item_photo_size">64dip</item>
+ <item name="list_item_profile_photo_size">80dip</item>
<item name="list_item_header_text_indent">77dip</item>
<item name="list_item_header_text_color">?color/section_header_text_color</item>
<item name="list_item_header_text_size">14sp</item>
@@ -116,4 +118,8 @@
<item name="android:gravity">center_vertical</item>
<item name="android:paddingTop">5dip</item>
</style>
+
+ <style name="GroupBrowseListItem">
+ <item name="android:background">@drawable/list_item_activated_background</item>
+ </style>
</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 77863d5..f259ea7 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -118,6 +118,9 @@
<!-- Menu item that splits an item from the contact detail into a separate aggregate -->
<string name="menu_splitAggregate">Separate</string>
+ <!-- Menu item that edits the currently selected group [CHAR LIMIT=30] -->
+ <string name="menu_editGroup">Edit group</string>
+
<!-- Menu item that renames the currently selected group [CHAR LIMIT=30] -->
<string name="menu_renameGroup">Rename group</string>
@@ -1497,6 +1500,12 @@
<!-- Title of the dialog that allows deletion of a contact group [CHAR LIMIT=128] -->
<string name="delete_group_dialog_title">Delete group</string>
+ <!-- Shows how many groups are from the specified account [CHAR LIMIT=15] -->
+ <plurals name="num_groups_in_account">
+ <item quantity="one">1 group</item>
+ <item quantity="other"><xliff:g id="count">%0$d</xliff:g> groups</item>
+ </plurals>
+
<!-- Confirmation message of the dialog that allows deletion of a contact group [CHAR LIMIT=256] -->
<string name="delete_group_dialog_message">Are you sure you want to delete the group
\'<xliff:g id="group_label" example="Friends">%1$s</xliff:g>\'?
@@ -1528,4 +1537,8 @@
<string name="call_type_and_date">
<xliff:g id="call_type" example="Friends">%1$s</xliff:g> <xliff:g id="call_short_date" example="Friends">%2$s</xliff:g>
</string>
+
+ <!-- Text displayed in place of the display name for the contact that represents the user's
+ personal profile entry [CHAR LIMIT=64] -->
+ <string name="profile_display_name">My profile</string>
</resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index f18a340..36f7ca8 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -31,6 +31,7 @@
<item name="list_item_vertical_divider_margin">5dip</item>
<item name="list_item_presence_icon_margin">5dip</item>
<item name="list_item_photo_size">56dip</item>
+ <item name="list_item_profile_photo_size">70dip</item>
<item name="list_item_prefix_highlight_color">#729a27</item>
<item name="list_item_header_text_indent">56dip</item>
<item name="list_item_header_text_color">?color/section_header_text_color</item>
@@ -114,6 +115,7 @@
<attr name="list_item_vertical_divider_margin" format="dimension"/>
<attr name="list_item_presence_icon_margin" format="dimension"/>
<attr name="list_item_photo_size" format="dimension"/>
+ <attr name="list_item_profile_photo_size" format="dimension"/>
<attr name="list_item_prefix_highlight_color" format="color"/>
<attr name="list_item_header_text_indent" format="dimension" />
<attr name="list_item_header_text_color" format="color" />
@@ -153,6 +155,7 @@
<item name="list_item_vertical_divider_margin">5dip</item>
<item name="list_item_presence_icon_margin">5dip</item>
<item name="list_item_photo_size">56dip</item>
+ <item name="list_item_profile_photo_size">70dip</item>
<item name="list_item_prefix_highlight_color">#729a27</item>
<item name="list_item_header_text_indent">56dip</item>
<item name="list_item_header_text_color">?color/section_header_text_color</item>
@@ -178,6 +181,7 @@
<item name="list_item_vertical_divider_margin">5dip</item>
<item name="list_item_presence_icon_margin">5dip</item>
<item name="list_item_photo_size">56dip</item>
+ <item name="list_item_profile_photo_size">70dip</item>
<item name="list_item_prefix_highlight_color">#729a27</item>
<item name="list_item_header_text_indent">56dip</item>
<item name="list_item_header_text_color">?color/section_header_text_color</item>
@@ -198,6 +202,7 @@
<item name="list_item_vertical_divider_margin">5dip</item>
<item name="list_item_presence_icon_margin">5dip</item>
<item name="list_item_photo_size">56dip</item>
+ <item name="list_item_profile_photo_size">70dip</item>
<item name="list_item_prefix_highlight_color">#729a27</item>
<item name="list_item_header_text_indent">56dip</item>
<item name="list_item_header_text_color">?color/section_header_text_color</item>
@@ -322,4 +327,8 @@
<item name="android:padding">5dip</item>
<item name="android:background">@drawable/list_selector</item>
</style>
+
+ <style name="GroupBrowseListItem">
+ <item name="android:paddingRight">20dip</item>
+ </style>
</resources>
diff --git a/src/com/android/contacts/CallDetailActivity.java b/src/com/android/contacts/CallDetailActivity.java
index 0931e3d..f3f7073 100644
--- a/src/com/android/contacts/CallDetailActivity.java
+++ b/src/com/android/contacts/CallDetailActivity.java
@@ -16,7 +16,6 @@
package com.android.contacts;
-import com.android.contacts.format.FormatUtils;
import com.android.internal.telephony.CallerInfo;
import android.app.ListActivity;
@@ -26,18 +25,15 @@
import android.content.Intent;
import android.content.res.Resources;
import android.database.Cursor;
-import android.graphics.Typeface;
import android.net.Uri;
import android.os.Bundle;
import android.provider.CallLog;
import android.provider.CallLog.Calls;
import android.provider.Contacts.Intents.Insert;
-import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.PhoneLookup;
import android.telephony.PhoneNumberUtils;
import android.telephony.TelephonyManager;
-import android.text.Spanned;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.view.KeyEvent;
@@ -60,9 +56,8 @@
AdapterView.OnItemClickListener {
private static final String TAG = "CallDetail";
- private TextView mNameView;
- private TextView mCallTypeView;
- private TextView mNumberView;
+ /** The views representing the details of a phone call. */
+ PhoneCallDetailsViews mPhoneCallDetailsViews;
private TextView mCallTimeView;
private TextView mCallDurationView;
private View mCallActionView;
@@ -118,9 +113,7 @@
mInflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
mResources = getResources();
- mNameView = (TextView) findViewById(R.id.name);
- mCallTypeView = (TextView) findViewById(R.id.call_type);
- mNumberView = (TextView) findViewById(R.id.number);
+ mPhoneCallDetailsViews = new PhoneCallDetailsViews(getWindow().getDecorView());
mCallActionView = findViewById(R.id.call);
mContactPhotoView = (ImageView) findViewById(R.id.contact_photo);
mContactBackgroundView = (ImageView) findViewById(R.id.contact_background);
@@ -189,46 +182,18 @@
mCallDurationView.setText(formatDuration(duration));
}
- CharSequence shortDateText =
- DateUtils.getRelativeTimeSpanString(date,
- System.currentTimeMillis(),
- DateUtils.MINUTE_IN_MILLIS,
- DateUtils.FORMAT_ABBREV_RELATIVE);
-
- CharSequence callTypeText = "";
- switch (callType) {
- case Calls.INCOMING_TYPE:
- callTypeText = getString(R.string.type_incoming);
- break;
-
- case Calls.OUTGOING_TYPE:
- callTypeText = getString(R.string.type_outgoing);
- break;
-
- case Calls.MISSED_TYPE:
- callTypeText = getString(R.string.type_missed);
- break;
- }
-
- mCallTypeView.setText(
- getString(R.string.call_type_and_date,
- FormatUtils.applyStyleToSpan(Typeface.BOLD,
- callTypeText, 0, callTypeText.length(),
- Spanned.SPAN_EXCLUSIVE_EXCLUSIVE),
- shortDateText));
-
long photoId = 0L;
CharSequence nameText = "";
- CharSequence numberText = "";
+ final CharSequence numberText;
+ int numberType = 0;
+ CharSequence numberLabel = "";
if (mNumber.equals(CallerInfo.UNKNOWN_NUMBER) ||
mNumber.equals(CallerInfo.PRIVATE_NUMBER)) {
- nameText = getString(mNumber.equals(CallerInfo.PRIVATE_NUMBER)
+ numberText = getString(mNumber.equals(CallerInfo.PRIVATE_NUMBER)
? R.string.private_num : R.string.unknown);
- numberText = "";
mCallActionView.setVisibility(View.GONE);
} else {
// Perform a reverse-phonebook lookup to find the PERSON_ID
- CharSequence callLabel = null;
Uri personUri = null;
Uri phoneUri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI,
Uri.encode(mNumber));
@@ -245,15 +210,15 @@
phonesCursor.getString(COLUMN_INDEX_NUMBER),
phonesCursor.getString(COLUMN_INDEX_NORMALIZED_NUMBER),
countryIso);
- callLabel = Phone.getTypeLabel(getResources(),
- phonesCursor.getInt(COLUMN_INDEX_TYPE),
- phonesCursor.getString(COLUMN_INDEX_LABEL));
+ numberType = phonesCursor.getInt(COLUMN_INDEX_TYPE);
+ numberLabel = phonesCursor.getString(COLUMN_INDEX_LABEL);
} else {
mNumber = PhoneNumberUtils.formatNumber(mNumber, countryIso);
}
} finally {
if (phonesCursor != null) phonesCursor.close();
}
+ numberText = mNumber;
mCallActionView.setVisibility(View.VISIBLE);
mCallActionView.setOnClickListener(new View.OnClickListener() {
@@ -265,15 +230,6 @@
}
});
- if (callLabel != null) {
- numberText = FormatUtils.applyStyleToSpan(Typeface.BOLD,
- callLabel + " " + mNumber, 0,
- callLabel.length(),
- Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
- } else {
- numberText = mNumber;
- }
-
// Build list of various available actions
List<ViewEntry> actions = new ArrayList<ViewEntry>();
@@ -299,8 +255,8 @@
ViewAdapter adapter = new ViewAdapter(this, actions);
setListAdapter(adapter);
}
- mNameView.setText(nameText);
- mNumberView.setText(numberText);
+ mPhoneCallDetailsViews.setPhoneCallDetails(getResources(), date, callType, nameText,
+ numberText, numberType, numberLabel);
loadContactPhotos(photoId);
} else {
diff --git a/src/com/android/contacts/PhoneCallDetailsViews.java b/src/com/android/contacts/PhoneCallDetailsViews.java
new file mode 100644
index 0000000..713a667
--- /dev/null
+++ b/src/com/android/contacts/PhoneCallDetailsViews.java
@@ -0,0 +1,124 @@
+/*
+ * 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;
+
+import com.android.contacts.format.FormatUtils;
+
+import android.content.res.Resources;
+import android.graphics.Typeface;
+import android.provider.CallLog.Calls;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.telephony.PhoneNumberUtils;
+import android.text.Spanned;
+import android.text.TextUtils;
+import android.text.format.DateUtils;
+import android.view.View;
+import android.widget.TextView;
+
+/**
+ * Encapsulates the views that are used to display the details of a phone call in the call log.
+ */
+public final class PhoneCallDetailsViews {
+ public final TextView mNameView;
+ public final TextView mCallTypeAndDateView;
+ public final TextView mNumberView;
+
+ /**
+ * Creates a new instance and caches its views.
+ *
+ * @param view the view which contains the elements to fill
+ */
+ public PhoneCallDetailsViews(View view) {
+ mNameView = (TextView) view.findViewById(R.id.name);
+ mCallTypeAndDateView = (TextView) view.findViewById(R.id.call_type);
+ mNumberView = (TextView) view.findViewById(R.id.number);
+ }
+
+ /**
+ * Fills the call details views with content.
+ *
+ * @param resources used to look up strings
+ * @param date the date of the call, in milliseconds since the epoch
+ * @param callType the type of call, as defined in the call log table
+ * @param name the name of the contact, if available
+ * @param number the number of the other party involved in the call
+ * @param numberType the type of phone, e.g., {@link Phone#TYPE_HOME}, 0 if not available
+ * @param numberLabel the custom label associated with the phone number in the contact
+ */
+ public void setPhoneCallDetails(Resources resources, long date, int callType,
+ CharSequence name, CharSequence number, int numberType, CharSequence numberLabel) {
+ CharSequence callTypeText = "";
+ switch (callType) {
+ case Calls.INCOMING_TYPE:
+ callTypeText = resources.getString(R.string.type_incoming);
+ break;
+
+ case Calls.OUTGOING_TYPE:
+ callTypeText = resources.getString(R.string.type_outgoing);
+ break;
+
+ case Calls.MISSED_TYPE:
+ callTypeText = resources.getString(R.string.type_missed);
+ break;
+ }
+
+ CharSequence shortDateText =
+ DateUtils.getRelativeTimeSpanString(date,
+ System.currentTimeMillis(),
+ DateUtils.MINUTE_IN_MILLIS,
+ DateUtils.FORMAT_ABBREV_RELATIVE);
+
+ CharSequence callTypeAndDateText = FormatUtils.applyStyleToSpan(Typeface.BOLD,
+ callTypeText + " " + shortDateText, 0, callTypeText.length(),
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+ CharSequence numberFormattedLabel = null;
+ // Only show a label if the number is shown and it is not a SIP address.
+ if (!TextUtils.isEmpty(number) && !PhoneNumberUtils.isUriNumber(number.toString())) {
+ numberFormattedLabel = Phone.getTypeLabel(resources, numberType, numberLabel);
+ }
+
+ CharSequence nameText;
+ CharSequence numberText;
+ if (TextUtils.isEmpty(name)) {
+ nameText = number;
+ numberText = "";
+ } else {
+ nameText = name;
+ numberText = number;
+ if (callType != 0 && numberFormattedLabel != null) {
+ numberText = FormatUtils.applyStyleToSpan(Typeface.BOLD,
+ numberFormattedLabel + " " + number, 0,
+ numberFormattedLabel.length(),
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+ }
+
+ mCallTypeAndDateView.setText(callTypeAndDateText);
+ mCallTypeAndDateView.setVisibility(View.VISIBLE);
+ mNameView.setText(nameText);
+ mNameView.setVisibility(View.VISIBLE);
+ // Do not show the number if it is not available. This happens if we have only the number,
+ // in which case the number is shown in the name field instead.
+ if (!TextUtils.isEmpty(numberText)) {
+ mNumberView.setText(numberText);
+ mNumberView.setVisibility(View.VISIBLE);
+ } else {
+ mNumberView.setVisibility(View.GONE);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/contacts/activities/ActionBarAdapter.java b/src/com/android/contacts/activities/ActionBarAdapter.java
index 0541edb..30e445e 100644
--- a/src/com/android/contacts/activities/ActionBarAdapter.java
+++ b/src/com/android/contacts/activities/ActionBarAdapter.java
@@ -23,9 +23,12 @@
import com.android.contacts.list.ContactsRequest;
import android.app.ActionBar;
+import android.app.ActionBar.LayoutParams;
import android.content.Context;
import android.os.Bundle;
import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
import android.widget.SearchView;
import android.widget.SearchView.OnCloseListener;
import android.widget.SearchView.OnQueryTextListener;
@@ -60,14 +63,21 @@
private ActionBar mActionBar;
- public ActionBarAdapter(Context context) {
+ private View mCustomSearchView;
+ private LayoutParams mLayoutParams;
+ private boolean mIsSearchInOverflowMenu;
+
+ public ActionBarAdapter(Context context, Listener listener) {
mContext = context;
+ mListener = listener;
mSearchLabelText = mContext.getString(R.string.search_label);
}
- public void onCreate(Bundle savedState, ContactsRequest request, ActionBar actionBar) {
+ public void onCreate(Bundle savedState, ContactsRequest request, ActionBar actionBar,
+ boolean searchInOverflowMenu) {
mActionBar = actionBar;
mQueryString = null;
+ mIsSearchInOverflowMenu = searchInOverflowMenu;
if (savedState != null) {
mSearchMode = savedState.getBoolean(EXTRA_KEY_SEARCH_MODE);
@@ -100,6 +110,10 @@
mFilterController.addListener(this);
}
+ public boolean isSearchInOverflowMenu() {
+ return mIsSearchInOverflowMenu;
+ }
+
public boolean isSearchMode() {
return mSearchMode;
}
@@ -132,12 +146,31 @@
public void update() {
if (mSearchMode) {
+ // If the search icon was in the overflow menu, then inflate a custom view containing
+ // a search view for the action bar (and hide the tabs).
+ if (mIsSearchInOverflowMenu) {
+ if (mCustomSearchView == null) {
+ mCustomSearchView = LayoutInflater.from(mContext).inflate(
+ R.layout.custom_action_bar, null);
+ mLayoutParams = new LayoutParams(LayoutParams.MATCH_PARENT,
+ LayoutParams.WRAP_CONTENT);
+ SearchView searchView = (SearchView) mCustomSearchView.
+ findViewById(R.id.search_view);
+ searchView.setQueryHint(mContext.getString(R.string.hint_findContacts));
+ setSearchView(searchView);
+ }
+ mActionBar.setDisplayShowCustomEnabled(true);
+ mActionBar.setCustomView(mCustomSearchView, mLayoutParams);
+ mSearchView.requestFocus();
+ } else {
+ mActionBar.setTitle(mSearchLabelText);
+ }
mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
- mActionBar.setTitle(mSearchLabelText);
if (mListener != null) {
mListener.onAction(Action.START_SEARCH_MODE);
}
} else {
+ mActionBar.setDisplayShowCustomEnabled(false);
mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
mActionBar.setTitle(null);
if (mListener != null) {
diff --git a/src/com/android/contacts/activities/ContactSelectionActivity.java b/src/com/android/contacts/activities/ContactSelectionActivity.java
index c255ba1..c63f76a 100644
--- a/src/com/android/contacts/activities/ContactSelectionActivity.java
+++ b/src/com/android/contacts/activities/ContactSelectionActivity.java
@@ -217,6 +217,7 @@
case ContactsRequest.ACTION_PICK_CONTACT: {
ContactPickerFragment fragment = new ContactPickerFragment();
fragment.setSearchMode(mRequest.isSearchMode());
+ fragment.setIncludeProfile(mRequest.shouldIncludeProfile());
mListFragment = fragment;
break;
}
diff --git a/src/com/android/contacts/activities/PeopleActivity.java b/src/com/android/contacts/activities/PeopleActivity.java
index 502c31d..53dd306 100644
--- a/src/com/android/contacts/activities/PeopleActivity.java
+++ b/src/com/android/contacts/activities/PeopleActivity.java
@@ -18,17 +18,12 @@
import com.android.contacts.ContactSaveService;
import com.android.contacts.ContactsActivity;
-import com.android.contacts.GroupMetaData;
import com.android.contacts.R;
-import com.android.contacts.calllog.CallLogFragment;
import com.android.contacts.detail.ContactDetailFragment;
-import com.android.contacts.dialpad.DialpadFragment;
import com.android.contacts.group.GroupBrowseListFragment;
import com.android.contacts.group.GroupBrowseListFragment.OnGroupBrowserActionListener;
import com.android.contacts.group.GroupDetailFragment;
import com.android.contacts.interactions.ContactDeletionInteraction;
-import com.android.contacts.interactions.GroupDeletionDialogFragment;
-import com.android.contacts.interactions.GroupRenamingDialogFragment;
import com.android.contacts.interactions.ImportExportDialogFragment;
import com.android.contacts.interactions.PhoneNumberInteraction;
import com.android.contacts.list.ContactBrowseListContextMenuAdapter;
@@ -47,7 +42,6 @@
import com.android.contacts.list.ProviderStatusLoader;
import com.android.contacts.list.ProviderStatusLoader.ProviderStatusListener;
import com.android.contacts.list.StrequentContactListFragment;
-import com.android.contacts.model.AccountTypeManager;
import com.android.contacts.preference.ContactsPreferenceActivity;
import com.android.contacts.util.AccountSelectionUtil;
import com.android.contacts.util.DialogManager;
@@ -70,7 +64,6 @@
import android.os.Bundle;
import android.provider.ContactsContract;
import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.Intents;
import android.provider.ContactsContract.ProviderStatus;
import android.provider.Settings;
import android.util.Log;
@@ -79,7 +72,6 @@
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
-import android.view.View.OnClickListener;
import android.view.Window;
import android.widget.SearchView;
import android.widget.Toast;
@@ -133,19 +125,22 @@
private ContactListFilterController mContactListFilterController;
- private View mAddContactImageView;
-
private ContactsUnavailableFragment mContactsUnavailableFragment;
private ProviderStatusLoader mProviderStatusLoader;
private int mProviderStatus = -1;
private boolean mOptionsMenuContactsAvailable;
- private boolean mOptionsMenuGroupActionsEnabled;
private DefaultContactBrowseListFragment mContactsFragment;
private StrequentContactListFragment mFavoritesFragment;
private GroupBrowseListFragment mGroupsFragment;
+ private enum TabState {
+ FAVORITES, CONTACTS, GROUPS
+ }
+
+ private TabState mSelectedTab;
+
public PeopleActivity() {
mIntentResolver = new ContactsIntentResolver(this);
// TODO: Get rid of the ContactListFilterController class because there aren't any
@@ -190,17 +185,6 @@
protected void onCreate(Bundle savedState) {
super.onCreate(savedState);
- mAddContactImageView = getLayoutInflater().inflate(
- R.layout.add_contact_menu_item, null, false);
- View item = mAddContactImageView.findViewById(R.id.menu_item);
- item.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- final Intent intent = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI);
- startActivityForResult(intent, SUBACTIVITY_NEW_CONTACT);
- }
- });
-
configureContentView(true, savedState);
}
@@ -264,8 +248,8 @@
setTitle(mRequest.getActivityTitle());
ActionBar actionBar = getActionBar();
- mActionBarAdapter = new ActionBarAdapter(this);
- mActionBarAdapter.onCreate(savedState, mRequest, getActionBar());
+ mActionBarAdapter = new ActionBarAdapter(this, this);
+ mActionBarAdapter.onCreate(savedState, mRequest, getActionBar(), !mContentPaneDisplayed);
mActionBarAdapter.setContactListFilterController(mContactListFilterController);
if (createContentView) {
@@ -273,19 +257,19 @@
Tab favoritesTab = actionBar.newTab();
favoritesTab.setText(getString(R.string.strequentList));
favoritesTab.setTabListener(new TabChangeListener(mFavoritesFragment,
- mContactDetailFragment));
+ mContactDetailFragment, TabState.FAVORITES));
actionBar.addTab(favoritesTab);
Tab peopleTab = actionBar.newTab();
peopleTab.setText(getString(R.string.people));
peopleTab.setTabListener(new TabChangeListener(mContactsFragment,
- mContactDetailFragment));
+ mContactDetailFragment, TabState.CONTACTS));
actionBar.addTab(peopleTab);
Tab groupsTab = actionBar.newTab();
groupsTab.setText(getString(R.string.contactsGroupsLabel));
groupsTab.setTabListener(new TabChangeListener(mGroupsFragment,
- mGroupDetailFragment));
+ mGroupDetailFragment, TabState.GROUPS));
actionBar.addTab(groupsTab);
actionBar.setDisplayShowTitleEnabled(true);
@@ -293,7 +277,7 @@
boolean showHomeIcon = a.getBoolean(R.styleable.ActionBarHomeIcon_show_home_icon, true);
actionBar.setDisplayShowHomeEnabled(showHomeIcon);
- invalidateOptionsMenu();
+ invalidateOptionsMenuIfNeeded();
}
configureFragments(savedState == null);
@@ -312,10 +296,12 @@
* null for smaller screen sizes).
*/
private final Fragment mDetailFragment;
+ private final TabState mTabState;
- public TabChangeListener(Fragment listFragment, Fragment detailFragment) {
+ public TabChangeListener(Fragment listFragment, Fragment detailFragment, TabState state) {
mBrowseListFragment = listFragment;
mDetailFragment = detailFragment;
+ mTabState = state;
}
@Override
@@ -332,6 +318,8 @@
if (mDetailFragment != null) {
ft.show(mDetailFragment);
}
+ setSelectedTab(mTabState);
+ invalidateOptionsMenu();
}
@Override
@@ -339,6 +327,10 @@
}
}
+ private void setSelectedTab(TabState tab) {
+ mSelectedTab = tab;
+ }
+
@Override
protected void onPause() {
if (mActionBarAdapter != null) {
@@ -346,7 +338,6 @@
}
mOptionsMenuContactsAvailable = false;
- mOptionsMenuGroupActionsEnabled = false;
mProviderStatus = -1;
mProviderStatusLoader.setProviderStatusListener(null);
@@ -408,15 +399,16 @@
}
mListFragment.setContactsRequest(mRequest);
- configureListFragmentForRequest();
+ configureContactListFragmentForRequest();
} else {
mSearchMode = mActionBarAdapter.isSearchMode();
}
- configureListFragment();
+ configureContactListFragment();
+ configureGroupListFragment();
- invalidateOptionsMenu();
+ invalidateOptionsMenuIfNeeded();
}
@Override
@@ -427,7 +419,7 @@
mListFragment.setFilter(mContactListFilterController.getFilter());
- invalidateOptionsMenu();
+ invalidateOptionsMenuIfNeeded();
}
@Override
@@ -438,7 +430,7 @@
mListFragment.setFilter(mContactListFilterController.getFilter());
- invalidateOptionsMenu();
+ invalidateOptionsMenuIfNeeded();
}
@Override
@@ -449,10 +441,12 @@
private void setupContactDetailFragment(final Uri contactLookupUri) {
mContactDetailFragment.loadUri(contactLookupUri);
+ invalidateOptionsMenuIfNeeded();
}
private void setupGroupDetailFragment(Uri groupUri) {
mGroupDetailFragment.loadGroup(groupUri);
+ invalidateOptionsMenuIfNeeded();
}
/**
@@ -462,23 +456,45 @@
public void onAction(Action action) {
switch (action) {
case START_SEARCH_MODE:
- // Bring the contact list fragment to the front.
+ // Bring the contact list fragment (and detail fragment if applicable) to the front
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.show(mContactsFragment);
+ if (mContactDetailFragment != null) ft.show(mContactDetailFragment);
ft.commit();
+ clearSearch();
break;
case STOP_SEARCH_MODE:
+ // Refresh the fragments because search mode was using them to display search
+ // results.
+ clearSearch();
+
+ // If the last selected tab was not the "All contacts" tab, then hide these
+ // fragments because we need to show favorites or groups.
+ if (mSelectedTab != null && !mSelectedTab.equals(TabState.CONTACTS)) {
+ FragmentTransaction transaction = getFragmentManager().beginTransaction();
+ transaction.hide(mContactsFragment);
+ if (mContactDetailFragment != null) transaction.hide(mContactDetailFragment);
+ transaction.commit();
+ }
+ break;
case CHANGE_SEARCH_QUERY:
- // Refresh the contact list fragment.
- configureFragments(false /* from request */);
- mListFragment.setQueryString(mActionBarAdapter.getQueryString(), true);
+ loadSearch(mActionBarAdapter.getQueryString());
break;
default:
throw new IllegalStateException("Unkonwn ActionBarAdapter action: " + action);
}
}
- private void configureListFragmentForRequest() {
+ private void clearSearch() {
+ loadSearch("");
+ }
+
+ private void loadSearch(String query) {
+ configureFragments(false /* from request */);
+ mListFragment.setQueryString(query, true);
+ }
+
+ private void configureContactListFragmentForRequest() {
Uri contactUri = mRequest.getContactUri();
if (contactUri != null) {
mListFragment.setSelectedContactUri(contactUri);
@@ -498,7 +514,7 @@
}
}
- private void configureListFragment() {
+ private void configureContactListFragment() {
mListFragment.setSearchMode(mSearchMode);
mListFragment.setVisibleScrollbarEnabled(!mSearchMode);
@@ -510,6 +526,14 @@
mListFragment.setQuickContactEnabled(!mContentPaneDisplayed);
}
+ private void configureGroupListFragment() {
+ mGroupsFragment.setVerticalScrollbarPosition(
+ mContentPaneDisplayed
+ ? View.SCROLLBAR_POSITION_LEFT
+ : View.SCROLLBAR_POSITION_RIGHT);
+ mGroupsFragment.setSelectionVisible(mContentPaneDisplayed);
+ }
+
@Override
public void onProviderStatusChange() {
updateFragmentVisibility();
@@ -555,7 +579,7 @@
}
}
- invalidateOptionsMenu();
+ invalidateOptionsMenuIfNeeded();
}
private final class ContactBrowserActionListener implements OnContactBrowserActionListener {
@@ -768,8 +792,6 @@
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.actions, menu);
- // TODO: Figure out if R.menu.list or R.menu.search are necessary according to the overflow
- // menus on the UX mocks.
MenuItem searchMenuItem = menu.findItem(R.id.menu_search);
if (searchMenuItem != null && searchMenuItem.getActionView() instanceof SearchView) {
SearchView searchView = (SearchView) searchMenuItem.getActionView();
@@ -780,18 +802,12 @@
mActionBarAdapter.setSearchView(searchView);
}
}
-
- // TODO: Can remove this as a custom view because the account selector is in the editor now.
- // Change add contact button to button with a custom view
- final MenuItem addContact = menu.findItem(R.id.menu_add);
- addContact.setActionView(mAddContactImageView);
return true;
}
- @Override
- public void invalidateOptionsMenu() {
+ private void invalidateOptionsMenuIfNeeded() {
if (isOptionsMenuChanged()) {
- super.invalidateOptionsMenu();
+ invalidateOptionsMenu();
}
}
@@ -800,10 +816,6 @@
return true;
}
- if (mOptionsMenuGroupActionsEnabled != areGroupActionsEnabled()) {
- return true;
- }
-
if (mListFragment != null && mListFragment.isOptionsMenuChanged()) {
return true;
}
@@ -812,6 +824,10 @@
return true;
}
+ if (mGroupDetailFragment != null && mGroupDetailFragment.isOptionsMenuChanged()) {
+ return true;
+ }
+
return false;
}
@@ -822,39 +838,42 @@
return false;
}
+ final MenuItem addContactMenu = menu.findItem(R.id.menu_add_contact);
+ final MenuItem addGroupMenu = menu.findItem(R.id.menu_add_group);
+ final MenuItem searchMenu = menu.findItem(R.id.menu_search);
+
+ if (mActionBarAdapter.isSearchMode()) {
+ addContactMenu.setVisible(false);
+ addGroupMenu.setVisible(false);
+ // If search is normally in the overflow menu, when we are in search
+ // mode, hide this option.
+ if (mActionBarAdapter.isSearchInOverflowMenu()) {
+ searchMenu.setVisible(false);
+ }
+ } else {
+ switch (mSelectedTab) {
+ case FAVORITES:
+ // TODO: Fall through until we determine what the menu items should be for
+ // this tab
+ case CONTACTS:
+ addContactMenu.setVisible(true);
+ addGroupMenu.setVisible(false);
+ break;
+ case GROUPS:
+ addContactMenu.setVisible(false);
+ addGroupMenu.setVisible(true);
+ break;
+ }
+ }
+
MenuItem settings = menu.findItem(R.id.menu_settings);
if (settings != null) {
settings.setVisible(!ContactsPreferenceActivity.isEmpty(this));
}
- mOptionsMenuGroupActionsEnabled = areGroupActionsEnabled();
-
- MenuItem renameGroup = menu.findItem(R.id.menu_rename_group);
- if (renameGroup != null) {
- renameGroup.setVisible(mOptionsMenuGroupActionsEnabled);
- }
-
- MenuItem deleteGroup = menu.findItem(R.id.menu_delete_group);
- if (deleteGroup != null) {
- deleteGroup.setVisible(mOptionsMenuGroupActionsEnabled);
- }
-
return true;
}
- private boolean areGroupActionsEnabled() {
- boolean groupActionsEnabled = false;
- if (mListFragment != null) {
- ContactListFilter filter = mListFragment.getFilter();
- if (filter != null
- && filter.filterType == ContactListFilter.FILTER_TYPE_GROUP
- && !filter.groupReadOnly) {
- groupActionsEnabled = true;
- }
- }
- return groupActionsEnabled;
- }
-
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
@@ -872,11 +891,16 @@
onSearchRequested();
return true;
}
- case R.id.menu_add: {
+ case R.id.menu_add_contact: {
final Intent intent = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI);
startActivityForResult(intent, SUBACTIVITY_NEW_CONTACT);
return true;
}
+ case R.id.menu_add_group: {
+ // TODO: Hook up "new group" functionality
+ Toast.makeText(this, "NEW GROUP", Toast.LENGTH_SHORT).show();
+ return true;
+ }
case R.id.menu_import_export: {
ImportExportDialogFragment.show(getFragmentManager());
return true;
@@ -890,18 +914,6 @@
startActivity(intent);
return true;
}
- case R.id.menu_rename_group: {
- ContactListFilter filter = mListFragment.getFilter();
- GroupRenamingDialogFragment.show(getFragmentManager(), filter.groupId,
- filter.title);
- return true;
- }
- case R.id.menu_delete_group: {
- ContactListFilter filter = mListFragment.getFilter();
- GroupDeletionDialogFragment.show(getFragmentManager(), filter.groupId,
- filter.title);
- return true;
- }
}
return false;
}
diff --git a/src/com/android/contacts/detail/ContactDetailFragment.java b/src/com/android/contacts/detail/ContactDetailFragment.java
index f3a4805..41ac0ad 100644
--- a/src/com/android/contacts/detail/ContactDetailFragment.java
+++ b/src/com/android/contacts/detail/ContactDetailFragment.java
@@ -1159,7 +1159,7 @@
@Override
public void onCreateOptionsMenu(Menu menu, final MenuInflater inflater) {
- inflater.inflate(R.menu.view, menu);
+ inflater.inflate(R.menu.view_contact, menu);
}
public boolean isOptionsMenuChanged() {
diff --git a/src/com/android/contacts/group/GroupBrowseListAdapter.java b/src/com/android/contacts/group/GroupBrowseListAdapter.java
index c17fc08..f530186 100644
--- a/src/com/android/contacts/group/GroupBrowseListAdapter.java
+++ b/src/com/android/contacts/group/GroupBrowseListAdapter.java
@@ -18,6 +18,7 @@
import com.android.contacts.GroupMetaData;
import com.android.contacts.R;
+import com.android.contacts.list.ContactListPinnedHeaderView;
import android.content.ContentUris;
import android.content.Context;
@@ -31,19 +32,55 @@
import android.widget.LinearLayout;
import android.widget.TextView;
+import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
/**
* Adapter to populate the list of groups.
*/
public class GroupBrowseListAdapter extends BaseAdapter {
+ private Context mContext;
private LayoutInflater mLayoutInflater;
- private List<GroupMetaData> mGroupList;
- public GroupBrowseListAdapter(Context context, List<GroupMetaData> groupList) {
+ private List<GroupListEntry> mGroupList = new ArrayList<GroupListEntry>();
+ private boolean mSelectionVisible;
+ private Uri mSelectedGroupUri;
+
+ enum ViewType {
+ HEADER, ITEM;
+ }
+
+ private static final int VIEW_TYPE_COUNT = ViewType.values().length;
+
+ public GroupBrowseListAdapter(Context context, Map<String, List<GroupMetaData>> groupMap) {
+ mContext = context;
mLayoutInflater = LayoutInflater.from(context);
- mGroupList = groupList;
+ for (String accountName : groupMap.keySet()) {
+ List<GroupMetaData> groupsListForAccount = groupMap.get(accountName);
+
+ // Add account name as header for section
+ mGroupList.add(GroupListEntry.createEntryForHeader(accountName,
+ groupsListForAccount.size()));
+
+ // Add groups within that account as subsequent list items.
+ for (GroupMetaData singleGroup : groupsListForAccount) {
+ mGroupList.add(GroupListEntry.createEntryForGroup(singleGroup));
+ }
+ }
+ }
+
+ public void setSelectionVisible(boolean flag) {
+ mSelectionVisible = flag;
+ }
+
+ public void setSelectedGroup(Uri groupUri) {
+ mSelectedGroupUri = groupUri;
+ }
+
+ private boolean isSelectedGroup(Uri groupUri) {
+ return mSelectedGroupUri != null && mSelectedGroupUri.equals(groupUri);
}
@Override
@@ -53,24 +90,109 @@
@Override
public long getItemId(int position) {
- return getItem(position).getGroupId();
+ return mGroupList.get(position).id;
}
@Override
- public GroupMetaData getItem(int position) {
+ public GroupListEntry getItem(int position) {
return mGroupList.get(position);
}
@Override
+ public int getItemViewType(int position) {
+ return mGroupList.get(position).type.ordinal();
+ }
+
+ @Override
+ public int getViewTypeCount() {
+ return VIEW_TYPE_COUNT;
+ }
+
+ @Override
+ public boolean areAllItemsEnabled() {
+ return false;
+ }
+
+ @Override
+ public boolean isEnabled(int position) {
+ return mGroupList.get(position).type == ViewType.ITEM;
+ }
+
+ @Override
public View getView(int position, View convertView, ViewGroup parent) {
+ GroupListEntry item = getItem(position);
+ switch (item.type) {
+ case HEADER:
+ return getHeaderView(item, convertView, parent);
+ case ITEM:
+ return getGroupListItemView(item, convertView, parent);
+ default:
+ throw new IllegalStateException("Invalid GroupListEntry item type " + item.type);
+ }
+
+ }
+
+ private View getHeaderView(GroupListEntry entry, View convertView, ViewGroup parent) {
+ ContactListPinnedHeaderView result = (ContactListPinnedHeaderView) (convertView == null ?
+ new ContactListPinnedHeaderView(mContext, null) :
+ convertView);
+ String groupCountString = mContext.getResources().getQuantityString(
+ R.plurals.num_groups_in_account, entry.count, entry.count);
+ // TODO: Format this correctly by using 2 TextViews when the
+ // ContactListPinnedHeaderView is refactored.
+ result.setSectionHeader(entry.title + " " + groupCountString);
+ return result;
+ }
+
+ private View getGroupListItemView(GroupListEntry entry, View convertView, ViewGroup parent) {
GroupListItem result = (GroupListItem) (convertView == null ?
mLayoutInflater.inflate(R.layout.group_browse_list_item, parent, false) :
convertView);
- result.loadFromGroup(getItem(position));
+ result.loadFromGroup(entry.groupData);
+ if (mSelectionVisible) {
+ result.setActivated(isSelectedGroup(result.getUri()));
+ }
return result;
}
/**
+ * This is a data model object to represent one row in the list of groups were the entry
+ * could be a header or group item.
+ */
+ public static class GroupListEntry {
+ public final ViewType type;
+ public final String title;
+ public final int count;
+ public final GroupMetaData groupData;
+ /**
+ * The id is equal to the group ID (if groupData is available), otherwise it is -1 for
+ * header entries.
+ */
+ public final long id;
+
+ private GroupListEntry(ViewType entryType, String headerTitle, int headerGroupCount,
+ GroupMetaData groupMetaData, long entryId) {
+ type = entryType;
+ title = headerTitle;
+ count = headerGroupCount;
+ groupData = groupMetaData;
+ id = entryId;
+ }
+
+ public static GroupListEntry createEntryForHeader(String headerTitle, int groupCount) {
+ return new GroupListEntry(ViewType.HEADER, headerTitle, groupCount, null, -1);
+ }
+
+ public static GroupListEntry createEntryForGroup(GroupMetaData groupMetaData) {
+ if (groupMetaData == null) {
+ throw new IllegalStateException("Cannot create list entry for a null group");
+ }
+ return new GroupListEntry(ViewType.ITEM, null, 0, groupMetaData,
+ groupMetaData.getGroupId());
+ }
+ }
+
+ /**
* A row in a list of groups, where this row displays a single group's title
* and associated account.
*/
diff --git a/src/com/android/contacts/group/GroupBrowseListFragment.java b/src/com/android/contacts/group/GroupBrowseListFragment.java
index 150a00f..958d9f8 100644
--- a/src/com/android/contacts/group/GroupBrowseListFragment.java
+++ b/src/com/android/contacts/group/GroupBrowseListFragment.java
@@ -44,7 +44,9 @@
import android.widget.ListView;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
/**
* Fragment to display the list of groups.
@@ -71,12 +73,24 @@
private Context mContext;
private Cursor mGroupListCursor;
- private List<GroupMetaData> mGroupList = new ArrayList<GroupMetaData>();
+
+ /**
+ * Map of account name to a list of {@link GroupMetaData} objects
+ * representing groups within that account.
+ * TODO: Change account name string into a wrapper object that has
+ * account name, type, and authority.
+ */
+ private Map<String, List<GroupMetaData>> mGroupMap = new HashMap<String, List<GroupMetaData>>();
private View mRootView;
private ListView mListView;
private View mEmptyView;
+ private GroupBrowseListAdapter mAdapter;
+ private boolean mSelectionVisible;
+
+ private int mVerticalScrollbarPosition = View.SCROLLBAR_POSITION_RIGHT;
+
private OnGroupBrowserActionListener mListener;
public GroupBrowseListFragment() {
@@ -93,6 +107,31 @@
return mRootView;
}
+ public void setVerticalScrollbarPosition(int position) {
+ if (mVerticalScrollbarPosition != position) {
+ mVerticalScrollbarPosition = position;
+ configureVerticalScrollbar();
+ }
+ }
+
+ private void configureVerticalScrollbar() {
+ mListView.setFastScrollEnabled(true);
+ mListView.setFastScrollAlwaysVisible(true);
+ mListView.setVerticalScrollbarPosition(mVerticalScrollbarPosition);
+ mListView.setScrollBarStyle(ListView.SCROLLBARS_OUTSIDE_OVERLAY);
+ int leftPadding = 0;
+ int rightPadding = 0;
+ if (mVerticalScrollbarPosition == View.SCROLLBAR_POSITION_LEFT) {
+ leftPadding = mContext.getResources().getDimensionPixelOffset(
+ R.dimen.list_visible_scrollbar_padding);
+ } else {
+ rightPadding = mContext.getResources().getDimensionPixelOffset(
+ R.dimen.list_visible_scrollbar_padding);
+ }
+ mListView.setPadding(leftPadding, mListView.getPaddingTop(),
+ rightPadding, mListView.getPaddingBottom());
+ }
+
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
@@ -136,7 +175,7 @@
if (mGroupListCursor == null) {
return;
}
- mGroupList.clear();
+ mGroupMap.clear();
mGroupListCursor.moveToPosition(-1);
while (mGroupListCursor.moveToNext()) {
String accountName = mGroupListCursor.getString(GroupMetaDataLoader.ACCOUNT_NAME);
@@ -150,12 +189,24 @@
? false
: mGroupListCursor.getInt(GroupMetaDataLoader.FAVORITES) != 0;
- // TODO: Separate groups according to account name and type.
- mGroupList.add(new GroupMetaData(
- accountName, accountType, groupId, title, defaultGroup, favorites));
+ GroupMetaData newGroup = new GroupMetaData(accountName, accountType, groupId, title,
+ defaultGroup, favorites);
+
+ if (mGroupMap.containsKey(accountName)) {
+ List<GroupMetaData> groups = mGroupMap.get(accountName);
+ groups.add(newGroup);
+ } else {
+ List<GroupMetaData> groups = new ArrayList<GroupMetaData>();
+ groups.add(newGroup);
+ mGroupMap.put(accountName, groups);
+ }
+
}
- mListView.setAdapter(new GroupBrowseListAdapter(mContext, mGroupList));
+ mAdapter = new GroupBrowseListAdapter(mContext, mGroupMap);
+ mAdapter.setSelectionVisible(mSelectionVisible);
+
+ mListView.setAdapter(mAdapter);
mListView.setEmptyView(mEmptyView);
mListView.setOnItemClickListener(new OnItemClickListener() {
@Override
@@ -170,7 +221,17 @@
mListener = listener;
}
+ public void setSelectionVisible(boolean flag) {
+ mSelectionVisible = flag;
+ }
+
+ private void setSelectedGroup(Uri groupUri) {
+ mAdapter.setSelectedGroup(groupUri);
+ mListView.invalidateViews();
+ }
+
private void viewGroup(Uri groupUri) {
+ setSelectedGroup(groupUri);
if (mListener != null) mListener.onViewGroupAction(groupUri);
}
diff --git a/src/com/android/contacts/group/GroupDetailFragment.java b/src/com/android/contacts/group/GroupDetailFragment.java
index 8cffd05..8d049d5 100644
--- a/src/com/android/contacts/group/GroupDetailFragment.java
+++ b/src/com/android/contacts/group/GroupDetailFragment.java
@@ -17,23 +17,21 @@
package com.android.contacts.group;
import com.android.contacts.ContactPhotoManager;
-import com.android.contacts.GroupMetaData;
import com.android.contacts.GroupMetaDataLoader;
import com.android.contacts.R;
-import com.android.contacts.activities.GroupDetailActivity;
+import com.android.contacts.interactions.GroupDeletionDialogFragment;
+import com.android.contacts.interactions.GroupRenamingDialogFragment;
import com.android.contacts.list.ContactListAdapter;
import com.android.contacts.list.ContactListFilter;
import com.android.contacts.list.DefaultContactListAdapter;
+import com.android.contacts.util.PhoneCapabilityTester;
-import android.accounts.Account;
import android.app.Activity;
import android.app.Fragment;
import android.app.LoaderManager;
import android.app.LoaderManager.LoaderCallbacks;
-import android.content.ContentValues;
import android.content.Context;
import android.content.CursorLoader;
-import android.content.Intent;
import android.content.Loader;
import android.database.Cursor;
import android.net.Uri;
@@ -41,6 +39,9 @@
import android.provider.ContactsContract;
import android.provider.ContactsContract.Directory;
import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
@@ -49,8 +50,7 @@
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ListView;
import android.widget.TextView;
-
-import java.util.ArrayList;
+import android.widget.Toast;
/**
* Displays the details of a group and shows a list of actions possible for the group.
@@ -88,6 +88,9 @@
private Uri mGroupUri;
private long mGroupId;
+ private String mGroupName;
+
+ private boolean mOptionsMenuEditable;
public GroupDetailFragment() {
}
@@ -107,6 +110,7 @@
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
+ setHasOptionsMenu(true);
mRootView = inflater.inflate(R.layout.group_detail_fragment, container, false);
mGroupTitle = (TextView) mRootView.findViewById(R.id.group_title);
mGroupSize = (TextView) mRootView.findViewById(R.id.group_size);
@@ -224,7 +228,8 @@
cursor.moveToPosition(-1);
if (cursor.moveToNext()) {
mGroupId = cursor.getLong(GroupMetaDataLoader.GROUP_ID);
- updateTitle(cursor.getString(GroupMetaDataLoader.TITLE));
+ mGroupName = cursor.getString(GroupMetaDataLoader.TITLE);
+ updateTitle(mGroupName);
}
}
@@ -257,4 +262,53 @@
mPhotoManager.resume();
}
}
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, final MenuInflater inflater) {
+ inflater.inflate(R.menu.view_group, menu);
+ }
+
+ public boolean isOptionsMenuChanged() {
+ return mOptionsMenuEditable != isGroupEditable();
+ }
+
+ public boolean isGroupEditable() {
+ // TODO: This should check the group_is_read_only flag. Modify GroupMetaDataLoader.
+ // Bug: 4601729.
+ return mGroupUri != null;
+ }
+
+ @Override
+ public void onPrepareOptionsMenu(Menu menu) {
+ mOptionsMenuEditable = isGroupEditable();
+
+ final MenuItem editMenu = menu.findItem(R.id.menu_edit_group);
+ editMenu.setVisible(mOptionsMenuEditable);
+
+ final MenuItem renameMenu = menu.findItem(R.id.menu_rename_group);
+ renameMenu.setVisible(mOptionsMenuEditable);
+
+ final MenuItem deleteMenu = menu.findItem(R.id.menu_delete_group);
+ deleteMenu.setVisible(mOptionsMenuEditable);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.menu_edit_group: {
+ // TODO: Open group editor
+ Toast.makeText(mContext, "EDIT GROUP", Toast.LENGTH_SHORT).show();
+ break;
+ }
+ case R.id.menu_rename_group: {
+ GroupRenamingDialogFragment.show(getFragmentManager(), mGroupId, mGroupName);
+ return true;
+ }
+ case R.id.menu_delete_group: {
+ GroupDeletionDialogFragment.show(getFragmentManager(), mGroupId, mGroupName);
+ return true;
+ }
+ }
+ return false;
+ }
}
diff --git a/src/com/android/contacts/list/ContactBrowseListFragment.java b/src/com/android/contacts/list/ContactBrowseListFragment.java
index 7f7dad4..fc9abe7 100644
--- a/src/com/android/contacts/list/ContactBrowseListFragment.java
+++ b/src/com/android/contacts/list/ContactBrowseListFragment.java
@@ -386,6 +386,9 @@
mSelectedContactDirectoryId, mSelectedContactLookupKey, mSelectedContactId);
}
}
+
+ // Display the user's profile.
+ adapter.setIncludeProfile(true);
}
@Override
diff --git a/src/com/android/contacts/list/ContactEntryListAdapter.java b/src/com/android/contacts/list/ContactEntryListAdapter.java
index a2b264b..95a8c2b 100644
--- a/src/com/android/contacts/list/ContactEntryListAdapter.java
+++ b/src/com/android/contacts/list/ContactEntryListAdapter.java
@@ -28,6 +28,7 @@
import android.os.Bundle;
import android.provider.ContactsContract;
import android.provider.ContactsContract.ContactCounts;
+import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Directory;
import android.text.TextUtils;
import android.view.LayoutInflater;
@@ -62,6 +63,7 @@
private boolean mDisplayPhotos;
private boolean mQuickContactEnabled;
+ private boolean mIncludeProfile;
private ContactPhotoManager mPhotoLoader;
private String mQueryString;
@@ -266,6 +268,14 @@
mQuickContactEnabled = quickContactEnabled;
}
+ public boolean shouldIncludeProfile() {
+ return mIncludeProfile;
+ }
+
+ public void setIncludeProfile(boolean includeProfile) {
+ mIncludeProfile = includeProfile;
+ }
+
public boolean isDataRestrictedByCallingPackage() {
return mDataRestrictedByCallingPackage;
}
@@ -411,7 +421,9 @@
@Override
public int getItemViewType(int partitionIndex, int position) {
int type = super.getItemViewType(partitionIndex, position);
- if (isSectionHeaderDisplayEnabled() && partitionIndex == getIndexedPartition()) {
+ if (!isUserProfile(position)
+ && isSectionHeaderDisplayEnabled()
+ && partitionIndex == getIndexedPartition()) {
Placement placement = getItemPlacementInSection(position);
return placement.firstInSection ? type : getItemViewTypeCount() + type;
} else {
@@ -526,6 +538,33 @@
}
}
+ /**
+ * Checks whether the contact entry at the given position represents the user's profile.
+ */
+ protected boolean isUserProfile(int position) {
+ // The profile only ever appears in the first position if it is present. So if the position
+ // is anything beyond 0, it can't be the profile.
+ boolean isUserProfile = false;
+ if (position == 0) {
+ int partition = getPartitionForPosition(position);
+ if (partition >= 0) {
+ // Save the old cursor position - the call to getItem() may modify the cursor
+ // position.
+ int offset = getCursor(partition).getPosition();
+ Cursor cursor = (Cursor) getItem(position);
+ if (cursor != null) {
+ int profileColumnIndex = cursor.getColumnIndex(Contacts.IS_USER_PROFILE);
+ if (profileColumnIndex != -1) {
+ isUserProfile = cursor.getInt(profileColumnIndex) == 1;
+ }
+ // Restore the old cursor position.
+ cursor.moveToPosition(offset);
+ }
+ }
+ }
+ return isUserProfile;
+ }
+
// TODO: fix PluralRules to handle zero correctly and use Resources.getQuantityText directly
public String getQuantityText(int count, int zeroResourceId, int pluralResourceId) {
if (count == 0) {
@@ -544,4 +583,30 @@
}
return true;
}
+
+ @Override
+ public Placement getItemPlacementInSection(int position) {
+ // Special case code to prevent a section header from being displayed above the user's
+ // profile entry.
+ if (isUserProfile(position)) {
+ // The user profile entry shouldn't display a section header above; the header should be
+ // displayed on top of the item below.
+ Placement placement = new Placement();
+ placement.firstInSection = false;
+ placement.lastInSection = false;
+ placement.sectionHeader = null;
+ return placement;
+ } else if (position > 0 && isUserProfile(position - 1)) {
+ // If the item in the previous position is the user's profile, behave as if this entry
+ // is the first in the section.
+ Placement profilePlacement = super.getItemPlacementInSection(position - 1);
+ String profileHeader = profilePlacement.sectionHeader;
+ Placement placement = super.getItemPlacementInSection(position);
+ placement.firstInSection = true;
+ placement.sectionHeader = profileHeader;
+ return placement;
+ } else {
+ return super.getItemPlacementInSection(position);
+ }
+ }
}
diff --git a/src/com/android/contacts/list/ContactEntryListFragment.java b/src/com/android/contacts/list/ContactEntryListFragment.java
index a080b37..0937114 100644
--- a/src/com/android/contacts/list/ContactEntryListFragment.java
+++ b/src/com/android/contacts/list/ContactEntryListFragment.java
@@ -78,6 +78,7 @@
private static final String KEY_SECTION_HEADER_DISPLAY_ENABLED = "sectionHeaderDisplayEnabled";
private static final String KEY_PHOTO_LOADER_ENABLED = "photoLoaderEnabled";
private static final String KEY_QUICK_CONTACT_ENABLED = "quickContactEnabled";
+ private static final String KEY_INCLUDE_PROFILE = "includeProfile";
private static final String KEY_SEARCH_MODE = "searchMode";
private static final String KEY_VISIBLE_SCROLLBAR_ENABLED = "visibleScrollbarEnabled";
private static final String KEY_SCROLLBAR_POSITION = "scrollbarPosition";
@@ -100,6 +101,7 @@
private boolean mSectionHeaderDisplayEnabled;
private boolean mPhotoLoaderEnabled;
private boolean mQuickContactEnabled = true;
+ private boolean mIncludeProfile;
private boolean mSearchMode;
private boolean mVisibleScrollbarEnabled;
private int mVerticalScrollbarPosition = View.SCROLLBAR_POSITION_RIGHT;
@@ -235,6 +237,7 @@
outState.putBoolean(KEY_SECTION_HEADER_DISPLAY_ENABLED, mSectionHeaderDisplayEnabled);
outState.putBoolean(KEY_PHOTO_LOADER_ENABLED, mPhotoLoaderEnabled);
outState.putBoolean(KEY_QUICK_CONTACT_ENABLED, mQuickContactEnabled);
+ outState.putBoolean(KEY_INCLUDE_PROFILE, mIncludeProfile);
outState.putBoolean(KEY_SEARCH_MODE, mSearchMode);
outState.putBoolean(KEY_VISIBLE_SCROLLBAR_ENABLED, mVisibleScrollbarEnabled);
outState.putInt(KEY_SCROLLBAR_POSITION, mVerticalScrollbarPosition);
@@ -266,6 +269,7 @@
mSectionHeaderDisplayEnabled = savedState.getBoolean(KEY_SECTION_HEADER_DISPLAY_ENABLED);
mPhotoLoaderEnabled = savedState.getBoolean(KEY_PHOTO_LOADER_ENABLED);
mQuickContactEnabled = savedState.getBoolean(KEY_QUICK_CONTACT_ENABLED);
+ mIncludeProfile = savedState.getBoolean(KEY_INCLUDE_PROFILE);
mSearchMode = savedState.getBoolean(KEY_SEARCH_MODE);
mVisibleScrollbarEnabled = savedState.getBoolean(KEY_VISIBLE_SCROLLBAR_ENABLED);
mVerticalScrollbarPosition = savedState.getInt(KEY_SCROLLBAR_POSITION);
@@ -579,6 +583,10 @@
this.mQuickContactEnabled = flag;
}
+ public void setIncludeProfile(boolean flag) {
+ mIncludeProfile = flag;
+ }
+
public void setSearchMode(boolean flag) {
if (mSearchMode != flag) {
mSearchMode = flag;
@@ -782,6 +790,7 @@
}
mAdapter.setQuickContactEnabled(mQuickContactEnabled);
+ mAdapter.setIncludeProfile(mIncludeProfile);
mAdapter.setQueryString(mQueryString);
mAdapter.setDirectorySearchMode(mDirectorySearchMode);
mAdapter.setPinnedPartitionHeadersEnabled(mSearchMode);
diff --git a/src/com/android/contacts/list/ContactListAdapter.java b/src/com/android/contacts/list/ContactListAdapter.java
index 2c57983..cc4bea5 100644
--- a/src/com/android/contacts/list/ContactListAdapter.java
+++ b/src/com/android/contacts/list/ContactListAdapter.java
@@ -23,6 +23,7 @@
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.Directory;
+import android.provider.ContactsContract.Profile;
import android.provider.ContactsContract.SearchSnippetColumns;
import android.text.TextUtils;
import android.view.View;
@@ -30,9 +31,10 @@
import android.widget.ListView;
import android.widget.QuickContactBadge;
-
/**
* A cursor adapter for the {@link ContactsContract.Contacts#CONTENT_TYPE} content type.
+ * Also includes support for including the {@link ContactsContract.Profile} record in the
+ * list.
*/
public abstract class ContactListAdapter extends ContactEntryListAdapter {
@@ -49,6 +51,7 @@
Contacts.LOOKUP_KEY, // 9
Contacts.PHONETIC_NAME, // 10
Contacts.HAS_PHONE_NUMBER, // 11
+ Contacts.IS_USER_PROFILE, // 12
};
protected static final String[] PROJECTION_DATA = new String[] {
@@ -79,7 +82,8 @@
Contacts.LOOKUP_KEY, // 9
Contacts.PHONETIC_NAME, // 10
Contacts.HAS_PHONE_NUMBER, // 11
- SearchSnippetColumns.SNIPPET, // 12
+ Contacts.IS_USER_PROFILE, // 12
+ SearchSnippetColumns.SNIPPET, // 13
};
protected static final int CONTACT_ID_COLUMN_INDEX = 0;
@@ -94,7 +98,8 @@
protected static final int CONTACT_LOOKUP_KEY_COLUMN_INDEX = 9;
protected static final int CONTACT_PHONETIC_NAME_COLUMN_INDEX = 10;
protected static final int CONTACT_HAS_PHONE_COLUMN_INDEX = 11;
- protected static final int CONTACT_SNIPPET_COLUMN_INDEX = 12;
+ protected static final int CONTACT_IS_USER_PROFILE = 12;
+ protected static final int CONTACT_SNIPPET_COLUMN_INDEX = 13;
private CharSequence mUnknownNameText;
private int mDisplayNameColumnIndex;
@@ -106,10 +111,15 @@
private ContactListFilter mFilter;
+ // View types for entries in the list view.
+ private final int mViewTypeProfileEntry;
+
+
public ContactListAdapter(Context context) {
super(context);
mUnknownNameText = context.getText(android.R.string.unknownName);
+ mViewTypeProfileEntry = getViewTypeCount() - 1;
}
public CharSequence getUnknownNameText() {
@@ -150,6 +160,11 @@
.appendQueryParameter(ContactCounts.ADDRESS_BOOK_INDEX_EXTRAS, "true").build();
}
+ protected static Uri includeProfileEntry(Uri uri) {
+ return uri.buildUpon()
+ .appendQueryParameter(ContactsContract.INCLUDE_PROFILE, "true").build();
+ }
+
public boolean getHasPhoneNumber(int position) {
return ((Cursor)getItem(position)).getInt(CONTACT_HAS_PHONE_COLUMN_INDEX) != 0;
}
@@ -221,7 +236,12 @@
@Override
protected View newView(Context context, int partition, Cursor cursor, int position,
ViewGroup parent) {
- final ContactListItemView view = new ContactListItemView(context, null);
+ ContactListItemView view;
+ if (getItemViewType(position) == mViewTypeProfileEntry) {
+ view = new ContactListProfileItemView(context, null);
+ } else {
+ view = new ContactListItemView(context, null);
+ }
view.setUnknownNameText(mUnknownNameText);
view.setTextWithHighlightingFactory(getTextWithHighlightingFactory());
view.setQuickContactEnabled(isQuickContactEnabled());
@@ -229,6 +249,24 @@
return view;
}
+ @Override
+ public int getItemViewType(int position) {
+ return isUserProfile(position)
+ ? mViewTypeProfileEntry
+ : super.getItemViewType(position);
+ }
+
+ @Override
+ public int getItemViewTypeCount() {
+ return super.getItemViewTypeCount() + 1;
+ }
+
+ @Override
+ public int getViewTypeCount() {
+ // One extra view type - the user's profile entry view.
+ return super.getViewTypeCount() + 1;
+ }
+
protected void bindSectionHeaderAndDivider(ContactListItemView view, int position) {
if (isSectionHeaderDisplayEnabled()) {
Placement placement = getItemPlacementInSection(position);
diff --git a/src/com/android/contacts/list/ContactListItemView.java b/src/com/android/contacts/list/ContactListItemView.java
index bc81cd3..032b60f 100644
--- a/src/com/android/contacts/list/ContactListItemView.java
+++ b/src/com/android/contacts/list/ContactListItemView.java
@@ -472,11 +472,19 @@
ViewGroup.LayoutParams.WRAP_CONTENT);
a.recycle();
} else {
- mPhotoViewWidth = mPhotoViewHeight = mDefaultPhotoViewSize;
+ mPhotoViewWidth = mPhotoViewHeight = getDefaultPhotoViewSize();
}
}
}
+ protected void setDefaultPhotoViewSize(int pixels) {
+ mDefaultPhotoViewSize = pixels;
+ }
+
+ protected int getDefaultPhotoViewSize() {
+ return mDefaultPhotoViewSize;
+ }
+
@Override
protected void drawableStateChanged() {
super.drawableStateChanged();
diff --git a/src/com/android/contacts/list/ContactListProfileItemView.java b/src/com/android/contacts/list/ContactListProfileItemView.java
new file mode 100644
index 0000000..f189366
--- /dev/null
+++ b/src/com/android/contacts/list/ContactListProfileItemView.java
@@ -0,0 +1,51 @@
+/*
+ * 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.list;
+
+import com.android.contacts.R;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.database.Cursor;
+import android.util.AttributeSet;
+import android.widget.TextView;
+
+/**
+ * Contact list entry that represents the user's personal profile data.
+ */
+public class ContactListProfileItemView extends ContactListItemView {
+
+ public ContactListProfileItemView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.ContactListItemView);
+ setDefaultPhotoViewSize(a.getDimensionPixelOffset(
+ R.styleable.ContactListItemView_list_item_profile_photo_size, 0));
+ }
+
+ @Override
+ public TextView getNameTextView() {
+ TextView nameTextView = super.getNameTextView();
+ nameTextView.setTextAppearance(getContext(), android.R.style.TextAppearance_Large);
+ return nameTextView;
+ }
+
+ @Override
+ public void showDisplayName(Cursor cursor, int nameColumnIndex, int alternativeNameColumnIndex,
+ boolean highlightingEnabled, int displayOrder) {
+ getNameTextView().setText(getContext().getText(R.string.profile_display_name));
+ }
+}
diff --git a/src/com/android/contacts/list/ContactsRequest.java b/src/com/android/contacts/list/ContactsRequest.java
index e20d189..469cd1d 100644
--- a/src/com/android/contacts/list/ContactsRequest.java
+++ b/src/com/android/contacts/list/ContactsRequest.java
@@ -83,6 +83,7 @@
private CharSequence mTitle;
private boolean mSearchMode;
private String mQueryString;
+ private boolean mIncludeProfile;
private String mGroupName;
private boolean mLegacyCompatibilityMode;
private boolean mDirectorySearchEnabled = true;
@@ -98,6 +99,7 @@
mTitle = request.mTitle;
mSearchMode = request.mSearchMode;
mQueryString = request.mQueryString;
+ mIncludeProfile = request.mIncludeProfile;
mGroupName = request.mGroupName;
mLegacyCompatibilityMode = request.mLegacyCompatibilityMode;
mDirectorySearchEnabled = request.mDirectorySearchEnabled;
@@ -119,6 +121,7 @@
request.mTitle = source.readCharSequence();
request.mSearchMode = source.readInt() != 0;
request.mQueryString = source.readString();
+ request.mIncludeProfile = source.readInt() != 0;
request.mGroupName = source.readString();
request.mLegacyCompatibilityMode = source.readInt() != 0;
request.mDirectorySearchEnabled = source.readInt() != 0;
@@ -134,6 +137,7 @@
dest.writeCharSequence(mTitle);
dest.writeInt(mSearchMode ? 1 : 0);
dest.writeString(mQueryString);
+ dest.writeInt(mIncludeProfile ? 1 : 0);
dest.writeString(mGroupName);
dest.writeInt(mLegacyCompatibilityMode ? 1 : 0);
dest.writeInt(mDirectorySearchEnabled ? 1 : 0);
@@ -192,6 +196,14 @@
mQueryString = string;
}
+ public boolean shouldIncludeProfile() {
+ return mIncludeProfile;
+ }
+
+ public void setIncludeProfile(boolean includeProfile) {
+ mIncludeProfile = includeProfile;
+ }
+
public String getGroupName() {
return mGroupName;
}
diff --git a/src/com/android/contacts/list/DefaultContactListAdapter.java b/src/com/android/contacts/list/DefaultContactListAdapter.java
index 3e8c6f1..6916514 100644
--- a/src/com/android/contacts/list/DefaultContactListAdapter.java
+++ b/src/com/android/contacts/list/DefaultContactListAdapter.java
@@ -131,6 +131,11 @@
}
uri = applyDataRestriction(uri);
+ // Include the user's personal profile.
+ if (shouldIncludeProfile()) {
+ uri = includeProfileEntry(uri);
+ }
+
loader.setUri(uri);
}