Merge "Import revised translations."
diff --git a/res/drawable-hdpi/ic_dial_action_search.png b/res/drawable-hdpi/ic_dial_action_search.png
new file mode 100644
index 0000000..898ce11
--- /dev/null
+++ b/res/drawable-hdpi/ic_dial_action_search.png
Binary files differ
diff --git a/res/drawable-hdpi/sym_action_email_holo_light.png b/res/drawable-hdpi/sym_action_email_holo_light.png
deleted file mode 100644
index e1ff65f..0000000
--- a/res/drawable-hdpi/sym_action_email_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/sym_action_goto_website_holo_light.png b/res/drawable-hdpi/sym_action_goto_website_holo_light.png
deleted file mode 100644
index 4c5c614..0000000
--- a/res/drawable-hdpi/sym_action_goto_website_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/sym_action_show_map_holo_light.png b/res/drawable-hdpi/sym_action_show_map_holo_light.png
deleted file mode 100644
index 4c6ba98..0000000
--- a/res/drawable-hdpi/sym_action_show_map_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/sym_action_talk_holo_light.png b/res/drawable-hdpi/sym_action_talk_holo_light.png
deleted file mode 100644
index 62ad687..0000000
--- a/res/drawable-hdpi/sym_action_talk_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_dial_action_search.png b/res/drawable-mdpi/ic_dial_action_search.png
new file mode 100644
index 0000000..88cba92
--- /dev/null
+++ b/res/drawable-mdpi/ic_dial_action_search.png
Binary files differ
diff --git a/res/drawable-mdpi/sym_action_email_holo_light.png b/res/drawable-mdpi/sym_action_email_holo_light.png
deleted file mode 100644
index 3160adb..0000000
--- a/res/drawable-mdpi/sym_action_email_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/sym_action_goto_website_holo_light.png b/res/drawable-mdpi/sym_action_goto_website_holo_light.png
deleted file mode 100644
index 5b17bd2..0000000
--- a/res/drawable-mdpi/sym_action_goto_website_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/sym_action_show_map_holo_light.png b/res/drawable-mdpi/sym_action_show_map_holo_light.png
deleted file mode 100644
index 059e821..0000000
--- a/res/drawable-mdpi/sym_action_show_map_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/sym_action_talk_holo_light.png b/res/drawable-mdpi/sym_action_talk_holo_light.png
deleted file mode 100644
index ff444d8..0000000
--- a/res/drawable-mdpi/sym_action_talk_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_dial_action_search.png b/res/drawable-xhdpi/ic_dial_action_search.png
new file mode 100644
index 0000000..fe3aa24
--- /dev/null
+++ b/res/drawable-xhdpi/ic_dial_action_search.png
Binary files differ
diff --git a/res/drawable-xhdpi/sym_action_email_holo_light.png b/res/drawable-xhdpi/sym_action_email_holo_light.png
deleted file mode 100644
index e64a7ae..0000000
--- a/res/drawable-xhdpi/sym_action_email_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/sym_action_goto_website_holo_light.png b/res/drawable-xhdpi/sym_action_goto_website_holo_light.png
deleted file mode 100644
index 48873f4..0000000
--- a/res/drawable-xhdpi/sym_action_goto_website_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/sym_action_show_map_holo_light.png b/res/drawable-xhdpi/sym_action_show_map_holo_light.png
deleted file mode 100644
index 1c948f6..0000000
--- a/res/drawable-xhdpi/sym_action_show_map_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/sym_action_talk_holo_light.png b/res/drawable-xhdpi/sym_action_talk_holo_light.png
deleted file mode 100644
index 759c7e8..0000000
--- a/res/drawable-xhdpi/sym_action_talk_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/res/layout/account_filter_header.xml b/res/layout/account_filter_header.xml
new file mode 100644
index 0000000..4d45d25
--- /dev/null
+++ b/res/layout/account_filter_header.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- Layout showing the type of account filter
+ (e.g. All contacts filter, custom filter, etc.),
+ which is the header of all contact lists. -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/account_filter_header_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?attr/list_item_header_height"
+ android:orientation="vertical"
+ android:paddingTop="@dimen/contact_browser_list_top_margin"
+ android:layout_marginLeft="@dimen/contact_browser_list_header_left_margin"
+ android:layout_marginRight="@dimen/contact_browser_list_header_right_margin"
+ android:background="?android:attr/selectableItemBackground"
+ android:visibility="gone">
+ <TextView
+ android:id="@+id/account_filter_header"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:textStyle="bold"
+ android:textAllCaps="true"
+ android:paddingLeft="@dimen/contact_browser_list_item_text_indent"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textColor="?android:attr/textColorSecondary" />
+ <View
+ android:id="@+id/account_filter_header_bottom_divider"
+ android:layout_height="1dip"
+ style="@style/SectionDivider" />
+</LinearLayout>
diff --git a/res/layout/contacts_list_content.xml b/res/layout/contacts_list_content.xml
index dfa99f6..05f1930 100644
--- a/res/layout/contacts_list_content.xml
+++ b/res/layout/contacts_list_content.xml
@@ -29,33 +29,9 @@
<!-- Shown only when an Account filter is set.
- paddingTop should be here to show "shade" effect correctly. -->
- <LinearLayout
+ <include
android:id="@+id/account_filter_header_container"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:minHeight="?attr/list_item_header_height"
- android:orientation="vertical"
- android:paddingTop="@dimen/contact_browser_list_top_margin"
- android:layout_marginLeft="@dimen/contact_browser_list_header_left_margin"
- android:layout_marginRight="@dimen/contact_browser_list_header_right_margin"
- android:background="?android:attr/selectableItemBackground"
- android:visibility="gone">
- <TextView
- android:id="@+id/account_filter_header"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:singleLine="true"
- android:ellipsize="end"
- android:textStyle="bold"
- android:textAllCaps="true"
- android:paddingLeft="@dimen/contact_browser_list_item_text_indent"
- android:textAppearance="?android:attr/textAppearanceSmall"
- android:textColor="?android:attr/textColorSecondary" />
- <View
- android:id="@+id/account_filter_header_bottom_divider"
- android:layout_height="1dip"
- style="@style/SectionDivider" />
- </LinearLayout>
+ layout="@layout/account_filter_header" />
<view
class="com.android.contacts.widget.PinnedHeaderListView"
diff --git a/res/layout/custom_action_bar.xml b/res/layout/custom_action_bar.xml
index f749586..af104fe 100644
--- a/res/layout/custom_action_bar.xml
+++ b/res/layout/custom_action_bar.xml
@@ -32,6 +32,7 @@
android:id="@+id/search_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:iconifiedByDefault="false" />
+ android:iconifiedByDefault="false"
+ android:inputType="textFilter" />
</FrameLayout>
diff --git a/res/layout/dialpad_additional_buttons.xml b/res/layout/dialpad_additional_buttons.xml
index 2d42ad0..836187e 100644
--- a/res/layout/dialpad_additional_buttons.xml
+++ b/res/layout/dialpad_additional_buttons.xml
@@ -34,7 +34,7 @@
android:state_enabled="false"
android:background="?android:attr/selectableItemBackground"
android:contentDescription="@string/description_search_button"
- android:src="@drawable/ic_see_contacts_holo_dark"/>
+ android:src="@drawable/ic_dial_action_search"/>
<View
android:layout_width="1dip"
diff --git a/res/layout/dialtacts_custom_action_bar.xml b/res/layout/dialtacts_custom_action_bar.xml
index 0709626..0af8eaa 100644
--- a/res/layout/dialtacts_custom_action_bar.xml
+++ b/res/layout/dialtacts_custom_action_bar.xml
@@ -26,7 +26,8 @@
android:layout_width="0px"
android:layout_height="match_parent"
android:layout_weight="1"
- android:iconifiedByDefault="false" />
+ android:iconifiedByDefault="false"
+ android:inputType="textFilter" />
<ImageButton
android:id="@+id/search_option"
diff --git a/res/layout/editor_custom_action_bar.xml b/res/layout/editor_custom_action_bar.xml
index c22b089..1040da3 100644
--- a/res/layout/editor_custom_action_bar.xml
+++ b/res/layout/editor_custom_action_bar.xml
@@ -26,13 +26,14 @@
android:divider="?android:attr/dividerVertical"
android:showDividers="end"
android:dividerPadding="12dip"
- android:orientation="horizontal"
- style="?android:attr/actionButtonStyle">
+ android:orientation="horizontal">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
- android:orientation="horizontal">
+ android:orientation="horizontal"
+ android:duplicateParentState="true"
+ style="?android:attr/actionButtonStyle">
<ImageView
android:id="@+id/icon"
diff --git a/res/layout/quickcontact_photo_container.xml b/res/layout/quickcontact_photo_container.xml
index 1ba939a..e970934 100644
--- a/res/layout/quickcontact_photo_container.xml
+++ b/res/layout/quickcontact_photo_container.xml
@@ -61,30 +61,6 @@
android:ellipsize="end"
android:textColor="@android:color/white"
android:textAppearance="?android:attr/textAppearanceMedium" />
- <TextView
- android:id="@+id/status"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:singleLine="true"
- android:ellipsize="end"
- android:textColor="@*android:color/secondary_text_light"
- android:textSize="15dip"
- android:layout_marginTop="-3dip" />
- <TextView
- android:id="@+id/timestamp"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:singleLine="true"
- android:ellipsize="end"
- android:textColor="@*android:color/secondary_text_light"
- android:textSize="12dip"
- android:layout_marginTop="-2dip" />
- <ImageView
- android:id="@+id/presence"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginRight="15dip"
- android:scaleType="centerInside" />
<ImageButton
android:id="@+id/open_details_push_layer"
android:layout_width="match_parent"
diff --git a/res/menu/dialtacts_options.xml b/res/menu/dialtacts_options.xml
index f653242..54ca086 100644
--- a/res/menu/dialtacts_options.xml
+++ b/res/menu/dialtacts_options.xml
@@ -17,6 +17,7 @@
<item
android:id="@+id/search_on_action_bar"
android:title="@string/menu_all_contacts"
+ android:icon="@drawable/ic_dial_action_search"
android:showAsAction="ifRoom" />
<!-- This should come after the other menus in CallLog and Dialpad -->
diff --git a/src/com/android/contacts/CallDetailActivity.java b/src/com/android/contacts/CallDetailActivity.java
index 113a18b..012acc7 100644
--- a/src/com/android/contacts/CallDetailActivity.java
+++ b/src/com/android/contacts/CallDetailActivity.java
@@ -19,6 +19,8 @@
import com.android.contacts.BackScrollManager.ScrollableHeader;
import com.android.contacts.calllog.CallDetailHistoryAdapter;
import com.android.contacts.calllog.CallTypeHelper;
+import com.android.contacts.calllog.ContactInfo;
+import com.android.contacts.calllog.ContactInfoHelper;
import com.android.contacts.calllog.PhoneNumberHelper;
import com.android.contacts.util.AsyncTaskExecutor;
import com.android.contacts.util.AsyncTaskExecutors;
@@ -44,7 +46,6 @@
import android.provider.Contacts.Intents.Insert;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.PhoneLookup;
import android.provider.VoicemailContract.Voicemails;
import android.telephony.PhoneNumberUtils;
import android.telephony.TelephonyManager;
@@ -101,6 +102,7 @@
private ImageButton mMainActionPushLayerView;
private ImageView mContactBackgroundView;
private AsyncTaskExecutor mAsyncTaskExecutor;
+ private ContactInfoHelper mContactInfoHelper;
private String mNumber = null;
private String mDefaultCountryIso;
@@ -191,25 +193,6 @@
static final int COUNTRY_ISO_COLUMN_INDEX = 4;
static final int GEOCODED_LOCATION_COLUMN_INDEX = 5;
- static final String[] PHONES_PROJECTION = new String[] {
- PhoneLookup._ID,
- PhoneLookup.DISPLAY_NAME,
- PhoneLookup.TYPE,
- PhoneLookup.LABEL,
- PhoneLookup.NUMBER,
- PhoneLookup.NORMALIZED_NUMBER,
- PhoneLookup.PHOTO_URI,
- PhoneLookup.LOOKUP_KEY,
- };
- static final int COLUMN_INDEX_ID = 0;
- static final int COLUMN_INDEX_NAME = 1;
- static final int COLUMN_INDEX_TYPE = 2;
- static final int COLUMN_INDEX_LABEL = 3;
- static final int COLUMN_INDEX_NUMBER = 4;
- static final int COLUMN_INDEX_NORMALIZED_NUMBER = 5;
- static final int COLUMN_INDEX_PHOTO_URI = 6;
- static final int COLUMN_INDEX_LOOKUP_KEY = 7;
-
private final View.OnClickListener mPrimaryActionListener = new View.OnClickListener() {
@Override
public void onClick(View view) {
@@ -251,6 +234,7 @@
mDefaultCountryIso = ContactsUtils.getCurrentCountryIso(this);
mContactPhotoManager = ContactPhotoManager.getInstance(this);
mProximitySensorManager = new ProximitySensorManager(this, mProximitySensorListener);
+ mContactInfoHelper = new ContactInfoHelper(this, ContactsUtils.getCurrentCountryIso(this));
configureActionBar();
optionallyHandleVoicemail();
}
@@ -570,53 +554,36 @@
}
// Formatted phone number.
- final CharSequence numberText;
+ final CharSequence formattedNumber;
// Read contact specifics.
- CharSequence nameText = "";
- int numberType = 0;
- CharSequence numberLabel = "";
- Uri photoUri = null;
- Uri contactUri = null;
+ final CharSequence nameText;
+ final int numberType;
+ final CharSequence numberLabel;
+ final Uri photoUri;
+ final Uri lookupUri;
// If this is not a regular number, there is no point in looking it up in the contacts.
- if (!mPhoneNumberHelper.canPlaceCallsTo(number)) {
- numberText = mPhoneNumberHelper.getDisplayNumber(number, null);
+ ContactInfo info =
+ mPhoneNumberHelper.canPlaceCallsTo(number)
+ ? mContactInfoHelper.lookupNumber(number, countryIso)
+ : null;
+ if (info == null) {
+ formattedNumber = mPhoneNumberHelper.getDisplayNumber(number, null);
+ nameText = "";
+ numberType = 0;
+ numberLabel = "";
+ photoUri = null;
+ lookupUri = null;
} else {
- // Perform a reverse-phonebook lookup to find the contact details.
- Uri phoneUri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI,
- Uri.encode(number));
- Cursor phonesCursor = resolver.query(phoneUri, PHONES_PROJECTION, null, null, null);
- String candidateNumberText = number;
- try {
- if (phonesCursor != null && phonesCursor.moveToFirst()) {
- nameText = phonesCursor.getString(COLUMN_INDEX_NAME);
- String photoUriString = phonesCursor.getString(COLUMN_INDEX_PHOTO_URI);
- photoUri = photoUriString == null ? null : Uri.parse(photoUriString);
- candidateNumberText = PhoneNumberUtils.formatNumber(
- phonesCursor.getString(COLUMN_INDEX_NUMBER),
- phonesCursor.getString(COLUMN_INDEX_NORMALIZED_NUMBER),
- countryIso);
- numberType = phonesCursor.getInt(COLUMN_INDEX_TYPE);
- numberLabel = phonesCursor.getString(COLUMN_INDEX_LABEL);
- long personId = phonesCursor.getLong(COLUMN_INDEX_ID);
- if (personId > 0) {
- contactUri = Contacts.getLookupUri(personId,
- phonesCursor.getString(COLUMN_INDEX_LOOKUP_KEY));
- }
- } else {
- // We could not find this contact in the contacts, just format the phone
- // number as best as we can. All the other fields will have their default
- // values.
- candidateNumberText =
- PhoneNumberUtils.formatNumber(number, countryIso);
- }
- } finally {
- if (phonesCursor != null) phonesCursor.close();
- numberText = candidateNumberText;
- }
+ formattedNumber = info.formattedNumber;
+ nameText = info.name;
+ numberType = info.type;
+ numberLabel = info.label;
+ photoUri = info.photoUri;
+ lookupUri = info.lookupUri;
}
- return new PhoneCallDetails(number, numberText, countryIso, geocode,
+ return new PhoneCallDetails(number, formattedNumber, countryIso, geocode,
new int[]{ callType }, date, duration,
- nameText, numberType, numberLabel, contactUri, photoUri);
+ nameText, numberType, numberLabel, lookupUri, photoUri);
} finally {
if (callCursor != null) {
callCursor.close();
diff --git a/src/com/android/contacts/ContactPresenceIconUtil.java b/src/com/android/contacts/ContactPresenceIconUtil.java
index 8e1af72..0cb5b93 100644
--- a/src/com/android/contacts/ContactPresenceIconUtil.java
+++ b/src/com/android/contacts/ContactPresenceIconUtil.java
@@ -18,7 +18,6 @@
import android.content.Context;
import android.graphics.drawable.Drawable;
-import android.provider.ContactsContract.CommonDataKinds.Im;
import android.provider.ContactsContract.StatusUpdates;
/**
@@ -46,43 +45,4 @@
return null;
}
}
-
- public static Drawable getChatCapabilityIcon(Context context, int status, int chatCapability) {
- int resourceId = 0;
- if ((chatCapability & Im.CAPABILITY_HAS_CAMERA) != 0) {
- switch(status) {
- case StatusUpdates.AVAILABLE:
- resourceId = android.R.drawable.presence_video_online;
- break;
- case StatusUpdates.IDLE:
- case StatusUpdates.AWAY:
- resourceId = android.R.drawable.presence_video_away;
- break;
- case StatusUpdates.DO_NOT_DISTURB:
- resourceId = android.R.drawable.presence_video_busy;
- break;
- }
- } else if ((chatCapability & Im.CAPABILITY_HAS_VOICE) != 0) {
- switch(status) {
- case StatusUpdates.AVAILABLE:
- resourceId = android.R.drawable.presence_audio_online;
- break;
- case StatusUpdates.IDLE:
- case StatusUpdates.AWAY:
- resourceId = android.R.drawable.presence_audio_away;
- break;
- case StatusUpdates.DO_NOT_DISTURB:
- resourceId = android.R.drawable.presence_audio_busy;
- break;
- }
- } else if (status != StatusUpdates.OFFLINE) {
- resourceId = StatusUpdates.getPresenceIconResourceId(status);
- }
-
- if (resourceId != 0) {
- return context.getResources().getDrawable(resourceId);
- }
-
- return null;
- }
}
diff --git a/src/com/android/contacts/activities/DialtactsActivity.java b/src/com/android/contacts/activities/DialtactsActivity.java
index baa4b4b..f06b227 100644
--- a/src/com/android/contacts/activities/DialtactsActivity.java
+++ b/src/com/android/contacts/activities/DialtactsActivity.java
@@ -27,6 +27,7 @@
import com.android.contacts.list.ContactTileAdapter.DisplayType;
import com.android.contacts.list.ContactTileListFragment;
import com.android.contacts.list.OnPhoneNumberPickerActionListener;
+import com.android.contacts.list.PhoneFavoriteFragment;
import com.android.contacts.list.PhoneNumberPickerFragment;
import com.android.internal.telephony.ITelephony;
@@ -120,7 +121,7 @@
public class ViewPagerAdapter extends FragmentPagerAdapter {
private DialpadFragment mDialpadFragment;
private CallLogFragment mCallLogFragment;
- private ContactTileListFragment mContactTileListFragment;
+ private PhoneFavoriteFragment mPhoneFavoriteFragment;
public ViewPagerAdapter(FragmentManager fm) {
super(fm);
@@ -140,10 +141,10 @@
}
return mCallLogFragment;
case TAB_INDEX_FAVORITES:
- if (mContactTileListFragment == null) {
- mContactTileListFragment = new ContactTileListFragment();
+ if (mPhoneFavoriteFragment == null) {
+ mPhoneFavoriteFragment = new PhoneFavoriteFragment();
}
- return mContactTileListFragment;
+ return mPhoneFavoriteFragment;
}
throw new IllegalStateException("No fragment at position " + position);
}
@@ -206,14 +207,13 @@
}
private String mFilterText;
- private Uri mDialUri;
/** Enables horizontal swipe between Fragments. */
private ViewPager mViewPager;
private final PageChangeListener mPageChangeListener = new PageChangeListener();
private DialpadFragment mDialpadFragment;
private CallLogFragment mCallLogFragment;
- private ContactTileListFragment mStrequentFragment;
+ private PhoneFavoriteFragment mPhoneFavoriteFragment;
private final TabListener mTabListener = new TabListener() {
@Override
@@ -373,13 +373,23 @@
mContactListFilterController.addListener(new ContactListFilterListener() {
@Override
public void onContactListFilterChanged() {
- if (mSearchFragment == null || !mSearchFragment.isAdded()) {
- Log.w(TAG, "Search Fragment isn't available when ContactListFilter is changed");
- return;
- }
- mSearchFragment.setFilter(mContactListFilterController.getFilter());
+ boolean doInvalidateOptionsMenu = false;
- invalidateOptionsMenu();
+ if (mPhoneFavoriteFragment != null && mPhoneFavoriteFragment.isAdded()) {
+ mPhoneFavoriteFragment.setFilter(mContactListFilterController.getFilter());
+ doInvalidateOptionsMenu = true;
+ }
+
+ if (mSearchFragment != null && mSearchFragment.isAdded()) {
+ mSearchFragment.setFilter(mContactListFilterController.getFilter());
+ doInvalidateOptionsMenu = true;
+ } else {
+ Log.w(TAG, "Search Fragment isn't available when ContactListFilter is changed");
+ }
+
+ if (doInvalidateOptionsMenu) {
+ invalidateOptionsMenu();
+ }
}
});
@@ -423,6 +433,9 @@
// ContactListFilterController and they should be able to receive filter change event
// from the same controller (Bug 5165507)
mContactListFilterController.onStart(true);
+ if (mPhoneFavoriteFragment != null) {
+ mPhoneFavoriteFragment.setFilter(mContactListFilterController.getFilter());
+ }
if (mSearchFragment != null) {
mSearchFragment.setFilter(mContactListFilterController.getFilter());
}
@@ -480,11 +493,13 @@
if (currentPosition == TAB_INDEX_CALL_LOG) {
mCallLogFragment.onVisibilityChanged(true);
}
- } else if (fragment instanceof ContactTileListFragment) {
- mStrequentFragment = (ContactTileListFragment) fragment;
- mStrequentFragment.enableQuickContact(false);
- mStrequentFragment.setListener(mStrequentListener);
- mStrequentFragment.setDisplayType(DisplayType.STREQUENT_PHONE_ONLY);
+ } else if (fragment instanceof PhoneFavoriteFragment) {
+ mPhoneFavoriteFragment = (PhoneFavoriteFragment) fragment;
+ mPhoneFavoriteFragment.setListener(mPhoneFavoriteListener);
+ if (mContactListFilterController != null
+ && mContactListFilterController.getFilter() != null) {
+ mPhoneFavoriteFragment.setFilter(mContactListFilterController.getFilter());
+ }
} else if (fragment instanceof PhoneNumberPickerFragment) {
mSearchFragment = (PhoneNumberPickerFragment) fragment;
mSearchFragment.setOnPhoneNumberPickerActionListener(mPhoneNumberPickerActionListener);
@@ -699,8 +714,8 @@
}
};
- private ContactTileListFragment.Listener mStrequentListener =
- new ContactTileListFragment.Listener() {
+ private PhoneFavoriteFragment.Listener mPhoneFavoriteListener =
+ new PhoneFavoriteFragment.Listener() {
@Override
public void onContactSelected(Uri contactUri) {
PhoneNumberInteraction.startInteractionForPhoneCall(
@@ -750,7 +765,13 @@
searchMenuItem.setOnMenuItemClickListener(mSearchMenuItemClickListener);
showCallSettingsMenu = true;
}
- filterOptionMenuItem.setVisible(false);
+ if (tab != null && tab.getPosition() == TAB_INDEX_FAVORITES) {
+ filterOptionMenuItem.setVisible(true);
+ filterOptionMenuItem.setOnMenuItemClickListener(
+ mFilterOptionsMenuItemClickListener);
+ } else {
+ filterOptionMenuItem.setVisible(false);
+ }
addContactOptionMenuItem.setVisible(false);
if (showCallSettingsMenu) {
@@ -814,9 +835,6 @@
// layout instead of asking the search menu item to take care of SearchView.
mSearchView.onActionViewExpanded();
mInSearchUi = true;
-
- // Clear focus and suppress keyboard show-up.
- mSearchView.clearFocus();
}
private void showInputMethod(View view) {
@@ -872,7 +890,7 @@
case TAB_INDEX_CALL_LOG:
return mCallLogFragment;
case TAB_INDEX_FAVORITES:
- return mStrequentFragment;
+ return mPhoneFavoriteFragment;
default:
throw new IllegalStateException("Unknown fragment index: " + position);
}
diff --git a/src/com/android/contacts/activities/PeopleActivity.java b/src/com/android/contacts/activities/PeopleActivity.java
index b088b89..0c3f448 100644
--- a/src/com/android/contacts/activities/PeopleActivity.java
+++ b/src/com/android/contacts/activities/PeopleActivity.java
@@ -111,8 +111,6 @@
private static final int SUBACTIVITY_NEW_GROUP = 2;
private static final int SUBACTIVITY_EDIT_GROUP = 3;
- private static final String KEY_SEARCH_MODE = "searchMode";
-
private DialogManager mDialogManager = new DialogManager(this);
private ContactsIntentResolver mIntentResolver;
diff --git a/src/com/android/contacts/calllog/CallLogAdapter.java b/src/com/android/contacts/calllog/CallLogAdapter.java
index 17209b4..4f274a9 100644
--- a/src/com/android/contacts/calllog/CallLogAdapter.java
+++ b/src/com/android/contacts/calllog/CallLogAdapter.java
@@ -33,11 +33,7 @@
import android.os.Handler;
import android.os.Message;
import android.provider.CallLog.Calls;
-import android.provider.ContactsContract.CommonDataKinds.SipAddress;
-import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.PhoneLookup;
-import android.telephony.PhoneNumberUtils;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
@@ -65,7 +61,7 @@
private static final int CONTACT_INFO_CACHE_SIZE = 100;
private final Context mContext;
- private final String mCurrentCountryIso;
+ private final ContactInfoHelper mContactInfoHelper;
private final CallFetcher mCallFetcher;
/**
@@ -198,12 +194,12 @@
};
public CallLogAdapter(Context context, CallFetcher callFetcher,
- String currentCountryIso, String voicemailNumber) {
+ ContactInfoHelper contactInfoHelper, String voicemailNumber) {
super(context);
mContext = context;
- mCurrentCountryIso = currentCountryIso;
mCallFetcher = callFetcher;
+ mContactInfoHelper = contactInfoHelper;
mContactInfoCache = ExpirableCache.create(CONTACT_INFO_CACHE_SIZE);
mRequests = new LinkedList<ContactInfoRequest>();
@@ -305,134 +301,6 @@
}
/**
- * Determines the contact information for the given SIP address.
- * <p>
- * It returns the contact info if found.
- * <p>
- * If no contact corresponds to the given SIP address, returns {@link ContactInfo#EMPTY}.
- * <p>
- * If the lookup fails for some other reason, it returns null.
- */
- private ContactInfo queryContactInfoForSipAddress(String sipAddress) {
- final ContactInfo info;
-
- // TODO: This code is duplicated from the
- // CallerInfoAsyncQuery class. To avoid that, could the
- // code here just use CallerInfoAsyncQuery, rather than
- // manually running ContentResolver.query() itself?
-
- // We look up SIP addresses directly in the Data table:
- Uri contactRef = Data.CONTENT_URI;
-
- // Note Data.DATA1 and SipAddress.SIP_ADDRESS are equivalent.
- //
- // Also note we use "upper(data1)" in the WHERE clause, and
- // uppercase the incoming SIP address, in order to do a
- // case-insensitive match.
- //
- // TODO: May also need to normalize by adding "sip:" as a
- // prefix, if we start storing SIP addresses that way in the
- // database.
- String selection = "upper(" + Data.DATA1 + ")=?"
- + " AND "
- + Data.MIMETYPE + "='" + SipAddress.CONTENT_ITEM_TYPE + "'";
- String[] selectionArgs = new String[] { sipAddress.toUpperCase() };
-
- Cursor dataTableCursor =
- mContext.getContentResolver().query(
- contactRef,
- null, // projection
- selection, // selection
- selectionArgs, // selectionArgs
- null); // sortOrder
-
- if (dataTableCursor != null) {
- if (dataTableCursor.moveToFirst()) {
- info = new ContactInfo();
-
- // TODO: we could slightly speed this up using an
- // explicit projection (and thus not have to do
- // those getColumnIndex() calls) but the benefit is
- // very minimal.
-
- // Note the Data.CONTACT_ID column here is
- // equivalent to the PERSON_ID_COLUMN_INDEX column
- // we use with "phonesCursor" below.
- long contactId = dataTableCursor.getLong(
- dataTableCursor.getColumnIndex(Data.CONTACT_ID));
- String lookupKey = dataTableCursor.getString(
- dataTableCursor.getColumnIndex(Data.LOOKUP_KEY));
- info.lookupUri = Contacts.getLookupUri(contactId, lookupKey);
- info.name = dataTableCursor.getString(
- dataTableCursor.getColumnIndex(Data.DISPLAY_NAME));
- // "type" and "label" are currently unused for SIP addresses
- info.type = SipAddress.TYPE_OTHER;
- info.label = null;
-
- // And "number" is the SIP address.
- // Note Data.DATA1 and SipAddress.SIP_ADDRESS are equivalent.
- info.number = dataTableCursor.getString(dataTableCursor.getColumnIndex(Data.DATA1));
- info.normalizedNumber = null; // meaningless for SIP addresses
- info.photoId = dataTableCursor.getLong(
- dataTableCursor.getColumnIndex(Data.PHOTO_ID));
- info.formattedNumber = null; // meaningless for SIP addresses
- } else {
- info = ContactInfo.EMPTY;
- }
- dataTableCursor.close();
- } else {
- // Failed to fetch the data, ignore this request.
- info = null;
- }
- return info;
- }
-
- /**
- * Determines the contact information for the given phone number.
- * <p>
- * It returns the contact info if found.
- * <p>
- * If no contact corresponds to the given phone number, returns {@link ContactInfo#EMPTY}.
- * <p>
- * If the lookup fails for some other reason, it returns null.
- */
- private ContactInfo queryContactInfoForPhoneNumber(String number, String countryIso) {
- final ContactInfo info;
-
- // "number" is a regular phone number, so use the
- // PhoneLookup table:
- Cursor phonesCursor =
- mContext.getContentResolver().query(
- Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI,
- Uri.encode(number)),
- PhoneQuery._PROJECTION, null, null, null);
- if (phonesCursor != null) {
- if (phonesCursor.moveToFirst()) {
- info = new ContactInfo();
- long contactId = phonesCursor.getLong(PhoneQuery.PERSON_ID);
- String lookupKey = phonesCursor.getString(PhoneQuery.LOOKUP_KEY);
- info.lookupUri = Contacts.getLookupUri(contactId, lookupKey);
- info.name = phonesCursor.getString(PhoneQuery.NAME);
- info.type = phonesCursor.getInt(PhoneQuery.PHONE_TYPE);
- info.label = phonesCursor.getString(PhoneQuery.LABEL);
- info.number = phonesCursor.getString(PhoneQuery.MATCHED_NUMBER);
- info.normalizedNumber = phonesCursor.getString(PhoneQuery.NORMALIZED_NUMBER);
- info.photoId = phonesCursor.getLong(PhoneQuery.PHOTO_ID);
- info.formattedNumber = formatPhoneNumber(info.number, info.formattedNumber,
- countryIso);
-
- } else {
- info = ContactInfo.EMPTY;
- }
- phonesCursor.close();
- } else {
- // Failed to fetch the data, ignore this request.
- info = null;
- }
- return info;
- }
-
- /**
* Queries the appropriate content provider for the contact associated with the number.
* <p>
* Upon completion it also updates the cache in the call log, if it is different from
@@ -444,53 +312,25 @@
* view to update its content.
*/
private boolean queryContactInfo(String number, String countryIso, ContactInfo callLogInfo) {
- final ContactInfo info;
-
- // Determine the contact info.
- if (PhoneNumberUtils.isUriNumber(number)) {
- // This "number" is really a SIP address.
- ContactInfo sipInfo = queryContactInfoForSipAddress(number);
- if (sipInfo == null || sipInfo == ContactInfo.EMPTY) {
- // Check whether the username is actually a phone number of contact.
- String username = number.substring(0, number.indexOf('@'));
- if (PhoneNumberUtils.isGlobalPhoneNumber(username)) {
- sipInfo = queryContactInfoForPhoneNumber(username, countryIso);
- }
- }
- info = sipInfo;
- } else {
- info = queryContactInfoForPhoneNumber(number, countryIso);
- }
+ final ContactInfo info = mContactInfoHelper.lookupNumber(number, countryIso);
if (info == null) {
// The lookup failed, just return without requesting to update the view.
return false;
}
- final ContactInfo updatedInfo;
- // If we did not find a matching contact, generate an empty contact info for the number.
- if (info == ContactInfo.EMPTY) {
- // Did not find a matching contact.
- updatedInfo = new ContactInfo();
- updatedInfo.number = number;
- updatedInfo.formattedNumber = formatPhoneNumber(number, null, countryIso);
- } else {
- updatedInfo = info;
- }
-
// Check the existing entry in the cache: only if it has changed we should update the
// view.
ContactInfo existingInfo = mContactInfoCache.getPossiblyExpired(number);
- boolean updated = !updatedInfo.equals(existingInfo);
+ boolean updated = !info.equals(existingInfo);
// Store the data in the cache so that the UI thread can use to display it. Store it
// even if it has not changed so that it is marked as not expired.
- mContactInfoCache.put(number, updatedInfo);
+ mContactInfoCache.put(number, info);
// Update the call log even if the cache it is up-to-date: it is possible that the cache
// contains the value from a different call log entry.
- updateCallLogContactInfoCache(number, updatedInfo, callLogInfo);
+ updateCallLogContactInfoCache(number, info, callLogInfo);
return updated;
}
-
/*
* Handles requests for contact name and number type
* @see java.lang.Runnable#run()
@@ -804,6 +644,7 @@
info.number = matchedNumber == null ? c.getString(CallLogQuery.NUMBER) : matchedNumber;
info.normalizedNumber = c.getString(CallLogQuery.CACHED_NORMALIZED_NUMBER);
info.photoId = c.getLong(CallLogQuery.CACHED_PHOTO_ID);
+ info.photoUri = null; // We do not cache the photo URI.
info.formattedNumber = c.getString(CallLogQuery.CACHED_FORMATTED_NUMBER);
return info;
}
@@ -850,32 +691,6 @@
super.addGroup(cursorPosition, size, expanded);
}
- /**
- * Format the given phone number
- *
- * @param number the number to be formatted.
- * @param normalizedNumber the normalized number of the given number.
- * @param countryIso the ISO 3166-1 two letters country code, the country's
- * convention will be used to format the number if the normalized
- * phone is null.
- *
- * @return the formatted number, or the given number if it was formatted.
- */
- private String formatPhoneNumber(String number, String normalizedNumber,
- String countryIso) {
- if (TextUtils.isEmpty(number)) {
- return "";
- }
- // If "number" is really a SIP address, don't try to do any formatting at all.
- if (PhoneNumberUtils.isUriNumber(number)) {
- return number;
- }
- if (TextUtils.isEmpty(countryIso)) {
- countryIso = mCurrentCountryIso;
- }
- return PhoneNumberUtils.formatNumber(number, normalizedNumber, countryIso);
- }
-
/*
* Get the number from the Contacts, if available, since sometimes
* the number provided by caller id may not be formatted properly
diff --git a/src/com/android/contacts/calllog/CallLogFragment.java b/src/com/android/contacts/calllog/CallLogFragment.java
index c9a4b5b..f66bbd0 100644
--- a/src/com/android/contacts/calllog/CallLogFragment.java
+++ b/src/com/android/contacts/calllog/CallLogFragment.java
@@ -152,7 +152,8 @@
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
String currentCountryIso = ContactsUtils.getCurrentCountryIso(getActivity());
- mAdapter = new CallLogAdapter(getActivity(), this, currentCountryIso, mVoiceMailNumber);
+ mAdapter = new CallLogAdapter(getActivity(), this,
+ new ContactInfoHelper(getActivity(), currentCountryIso), mVoiceMailNumber);
setListAdapter(mAdapter);
getListView().setItemsCanFocus(true);
}
diff --git a/src/com/android/contacts/calllog/ContactInfo.java b/src/com/android/contacts/calllog/ContactInfo.java
index cf33d45..a02b457 100644
--- a/src/com/android/contacts/calllog/ContactInfo.java
+++ b/src/com/android/contacts/calllog/ContactInfo.java
@@ -34,6 +34,8 @@
public String normalizedNumber;
/** The photo for the contact, if available. */
public long photoId;
+ /** The high-res photo for the contact, if available. */
+ public Uri photoUri;
public static ContactInfo EMPTY = new ContactInfo();
@@ -63,6 +65,7 @@
if (!TextUtils.equals(formattedNumber, other.formattedNumber)) return false;
if (!TextUtils.equals(normalizedNumber, other.normalizedNumber)) return false;
if (photoId != other.photoId) return false;
+ if (!UriUtils.areEqual(photoUri, other.photoUri)) return false;
return true;
}
}
diff --git a/src/com/android/contacts/calllog/ContactInfoHelper.java b/src/com/android/contacts/calllog/ContactInfoHelper.java
new file mode 100644
index 0000000..f4b7daf
--- /dev/null
+++ b/src/com/android/contacts/calllog/ContactInfoHelper.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.contacts.calllog;
+
+import com.android.contacts.util.UriUtils;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.ContactsContract.CommonDataKinds.SipAddress;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.PhoneLookup;
+import android.telephony.PhoneNumberUtils;
+import android.text.TextUtils;
+
+/**
+ * Utility class to look up the contact information for a given number.
+ */
+public class ContactInfoHelper {
+ private final Context mContext;
+ private final String mCurrentCountryIso;
+
+ public ContactInfoHelper(Context context, String currentCountryIso) {
+ mContext = context;
+ mCurrentCountryIso = currentCountryIso;
+ }
+
+ /**
+ * Returns the contact information for the given number.
+ * <p>
+ * If the number does not match any contact, returns a contact info containing only the number
+ * and the formatted number.
+ * <p>
+ * If an error occurs during the lookup, it returns null.
+ *
+ * @param number the number to look up
+ * @param countryIso the country associated with this number
+ */
+ public ContactInfo lookupNumber(String number, String countryIso) {
+ final ContactInfo info;
+
+ // Determine the contact info.
+ if (PhoneNumberUtils.isUriNumber(number)) {
+ // This "number" is really a SIP address.
+ ContactInfo sipInfo = queryContactInfoForSipAddress(number);
+ if (sipInfo == null || sipInfo == ContactInfo.EMPTY) {
+ // Check whether the username is actually a phone number of contact.
+ String username = number.substring(0, number.indexOf('@'));
+ if (PhoneNumberUtils.isGlobalPhoneNumber(username)) {
+ sipInfo = queryContactInfoForPhoneNumber(username, countryIso);
+ }
+ }
+ info = sipInfo;
+ } else {
+ info = queryContactInfoForPhoneNumber(number, countryIso);
+ }
+
+ final ContactInfo updatedInfo;
+ if (info == null) {
+ // The lookup failed.
+ updatedInfo = null;
+ } else {
+ // If we did not find a matching contact, generate an empty contact info for the number.
+ if (info == ContactInfo.EMPTY) {
+ // Did not find a matching contact.
+ updatedInfo = new ContactInfo();
+ updatedInfo.number = number;
+ updatedInfo.formattedNumber = formatPhoneNumber(number, null, countryIso);
+ } else {
+ updatedInfo = info;
+ }
+ }
+ return updatedInfo;
+ }
+
+ /**
+ * Determines the contact information for the given SIP address.
+ * <p>
+ * It returns the contact info if found.
+ * <p>
+ * If no contact corresponds to the given SIP address, returns {@link ContactInfo#EMPTY}.
+ * <p>
+ * If the lookup fails for some other reason, it returns null.
+ */
+ private ContactInfo queryContactInfoForSipAddress(String sipAddress) {
+ final ContactInfo info;
+
+ // TODO: This code is duplicated from the
+ // CallerInfoAsyncQuery class. To avoid that, could the
+ // code here just use CallerInfoAsyncQuery, rather than
+ // manually running ContentResolver.query() itself?
+
+ // We look up SIP addresses directly in the Data table:
+ Uri contactRef = Data.CONTENT_URI;
+
+ // Note Data.DATA1 and SipAddress.SIP_ADDRESS are equivalent.
+ //
+ // Also note we use "upper(data1)" in the WHERE clause, and
+ // uppercase the incoming SIP address, in order to do a
+ // case-insensitive match.
+ //
+ // TODO: May also need to normalize by adding "sip:" as a
+ // prefix, if we start storing SIP addresses that way in the
+ // database.
+ String selection = "upper(" + Data.DATA1 + ")=?"
+ + " AND "
+ + Data.MIMETYPE + "='" + SipAddress.CONTENT_ITEM_TYPE + "'";
+ String[] selectionArgs = new String[] { sipAddress.toUpperCase() };
+
+ Cursor dataTableCursor =
+ mContext.getContentResolver().query(
+ contactRef,
+ null, // projection
+ selection, // selection
+ selectionArgs, // selectionArgs
+ null); // sortOrder
+
+ if (dataTableCursor != null) {
+ if (dataTableCursor.moveToFirst()) {
+ info = new ContactInfo();
+
+ // TODO: we could slightly speed this up using an
+ // explicit projection (and thus not have to do
+ // those getColumnIndex() calls) but the benefit is
+ // very minimal.
+
+ // Note the Data.CONTACT_ID column here is
+ // equivalent to the PERSON_ID_COLUMN_INDEX column
+ // we use with "phonesCursor" below.
+ long contactId = dataTableCursor.getLong(
+ dataTableCursor.getColumnIndex(Data.CONTACT_ID));
+ String lookupKey = dataTableCursor.getString(
+ dataTableCursor.getColumnIndex(Data.LOOKUP_KEY));
+ info.lookupUri = Contacts.getLookupUri(contactId, lookupKey);
+ info.name = dataTableCursor.getString(
+ dataTableCursor.getColumnIndex(Data.DISPLAY_NAME));
+ // "type" and "label" are currently unused for SIP addresses
+ info.type = SipAddress.TYPE_OTHER;
+ info.label = null;
+
+ // And "number" is the SIP address.
+ // Note Data.DATA1 and SipAddress.SIP_ADDRESS are equivalent.
+ info.number = dataTableCursor.getString(dataTableCursor.getColumnIndex(Data.DATA1));
+ info.normalizedNumber = null; // meaningless for SIP addresses
+ info.photoId = dataTableCursor.getLong(
+ dataTableCursor.getColumnIndex(Data.PHOTO_ID));
+ info.photoUri = UriUtils.parseUriOrNull(dataTableCursor.getString(
+ dataTableCursor.getColumnIndex(Data.PHOTO_URI)));
+ info.formattedNumber = null; // meaningless for SIP addresses
+ } else {
+ info = ContactInfo.EMPTY;
+ }
+ dataTableCursor.close();
+ } else {
+ // Failed to fetch the data, ignore this request.
+ info = null;
+ }
+ return info;
+ }
+
+ /**
+ * Determines the contact information for the given phone number.
+ * <p>
+ * It returns the contact info if found.
+ * <p>
+ * If no contact corresponds to the given phone number, returns {@link ContactInfo#EMPTY}.
+ * <p>
+ * If the lookup fails for some other reason, it returns null.
+ */
+ private ContactInfo queryContactInfoForPhoneNumber(String number, String countryIso) {
+ final ContactInfo info;
+
+ // "number" is a regular phone number, so use the
+ // PhoneLookup table:
+ Cursor phonesCursor =
+ mContext.getContentResolver().query(
+ Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI,
+ Uri.encode(number)),
+ PhoneQuery._PROJECTION, null, null, null);
+ if (phonesCursor != null) {
+ if (phonesCursor.moveToFirst()) {
+ info = new ContactInfo();
+ long contactId = phonesCursor.getLong(PhoneQuery.PERSON_ID);
+ String lookupKey = phonesCursor.getString(PhoneQuery.LOOKUP_KEY);
+ info.lookupUri = Contacts.getLookupUri(contactId, lookupKey);
+ info.name = phonesCursor.getString(PhoneQuery.NAME);
+ info.type = phonesCursor.getInt(PhoneQuery.PHONE_TYPE);
+ info.label = phonesCursor.getString(PhoneQuery.LABEL);
+ info.number = phonesCursor.getString(PhoneQuery.MATCHED_NUMBER);
+ info.normalizedNumber = phonesCursor.getString(PhoneQuery.NORMALIZED_NUMBER);
+ info.photoId = phonesCursor.getLong(PhoneQuery.PHOTO_ID);
+ info.photoUri =
+ UriUtils.parseUriOrNull(phonesCursor.getString(PhoneQuery.PHOTO_URI));
+ info.formattedNumber = formatPhoneNumber(info.number, info.formattedNumber,
+ countryIso);
+
+ } else {
+ info = ContactInfo.EMPTY;
+ }
+ phonesCursor.close();
+ } else {
+ // Failed to fetch the data, ignore this request.
+ info = null;
+ }
+ return info;
+ }
+
+ /**
+ * Format the given phone number
+ *
+ * @param number the number to be formatted.
+ * @param normalizedNumber the normalized number of the given number.
+ * @param countryIso the ISO 3166-1 two letters country code, the country's
+ * convention will be used to format the number if the normalized
+ * phone is null.
+ *
+ * @return the formatted number, or the given number if it was formatted.
+ */
+ private String formatPhoneNumber(String number, String normalizedNumber,
+ String countryIso) {
+ if (TextUtils.isEmpty(number)) {
+ return "";
+ }
+ // If "number" is really a SIP address, don't try to do any formatting at all.
+ if (PhoneNumberUtils.isUriNumber(number)) {
+ return number;
+ }
+ if (TextUtils.isEmpty(countryIso)) {
+ countryIso = mCurrentCountryIso;
+ }
+ return PhoneNumberUtils.formatNumber(number, normalizedNumber, countryIso);
+ }
+}
diff --git a/src/com/android/contacts/calllog/PhoneQuery.java b/src/com/android/contacts/calllog/PhoneQuery.java
index a53e5c8..af44add 100644
--- a/src/com/android/contacts/calllog/PhoneQuery.java
+++ b/src/com/android/contacts/calllog/PhoneQuery.java
@@ -30,7 +30,8 @@
PhoneLookup.NUMBER,
PhoneLookup.NORMALIZED_NUMBER,
PhoneLookup.PHOTO_ID,
- PhoneLookup.LOOKUP_KEY};
+ PhoneLookup.LOOKUP_KEY,
+ PhoneLookup.PHOTO_URI};
public static final int PERSON_ID = 0;
public static final int NAME = 1;
@@ -40,4 +41,5 @@
public static final int NORMALIZED_NUMBER = 5;
public static final int PHOTO_ID = 6;
public static final int LOOKUP_KEY = 7;
+ public static final int PHOTO_URI = 8;
}
\ No newline at end of file
diff --git a/src/com/android/contacts/detail/ContactDetailFragment.java b/src/com/android/contacts/detail/ContactDetailFragment.java
index aaeacab..aa49481 100644
--- a/src/com/android/contacts/detail/ContactDetailFragment.java
+++ b/src/com/android/contacts/detail/ContactDetailFragment.java
@@ -593,19 +593,18 @@
final Intent smsIntent = mHasSms ? new Intent(Intent.ACTION_SENDTO,
Uri.fromParts(Constants.SCHEME_SMSTO, entry.data, null)) : null;
- // Configure Icons and Intents. Notice actionIcon is already set to the phone
+ // Configure Icons and Intents.
if (mHasPhone && mHasSms) {
entry.intent = phoneIntent;
entry.secondaryIntent = smsIntent;
entry.secondaryActionIcon = kind.iconAltRes;
+ entry.secondaryActionDescription = kind.iconAltDescriptionRes;
} else if (mHasPhone) {
entry.intent = phoneIntent;
} else if (mHasSms) {
entry.intent = smsIntent;
- entry.actionIcon = kind.iconAltRes;
} else {
entry.intent = null;
- entry.actionIcon = -1;
}
// Remember super-primary phone
@@ -690,7 +689,6 @@
Uri.fromParts(Constants.SCHEME_SIP, entry.data, null));
} else {
entry.intent = null;
- entry.actionIcon = -1;
}
mSipEntries.add(entry);
// TODO: Now that SipAddress is in its own list of entries
@@ -983,20 +981,17 @@
entry.typeString = Im.getProtocolLabel(context.getResources(), Im.PROTOCOL_GOOGLE_TALK,
null).toString();
if ((chatCapability & Im.CAPABILITY_HAS_CAMERA) != 0) {
- entry.actionIcon = R.drawable.sym_action_talk_holo_light;
entry.intent =
new Intent(Intent.ACTION_SENDTO, Uri.parse("xmpp:" + data + "?message"));
entry.secondaryIntent =
new Intent(Intent.ACTION_SENDTO, Uri.parse("xmpp:" + data + "?call"));
} else if ((chatCapability & Im.CAPABILITY_HAS_VOICE) != 0) {
// Allow Talking and Texting
- entry.actionIcon = R.drawable.sym_action_talk_holo_light;
entry.intent =
new Intent(Intent.ACTION_SENDTO, Uri.parse("xmpp:" + data + "?message"));
entry.secondaryIntent =
new Intent(Intent.ACTION_SENDTO, Uri.parse("xmpp:" + data + "?call"));
} else {
- entry.actionIcon = R.drawable.sym_action_talk_holo_light;
entry.intent =
new Intent(Intent.ACTION_SENDTO, Uri.parse("xmpp:" + data + "?message"));
}
@@ -1013,7 +1008,6 @@
final String authority = host.toLowerCase();
final Uri imUri = new Uri.Builder().scheme(Constants.SCHEME_IMTO).authority(
authority).appendPath(data).build();
- entry.actionIcon = R.drawable.sym_action_talk_holo_light;
entry.intent = new Intent(Intent.ACTION_SENDTO, imUri);
}
}
@@ -1210,9 +1204,9 @@
public Context context = null;
public String resPackageName = null;
- public int actionIcon = -1;
public boolean isPrimary = false;
public int secondaryActionIcon = -1;
+ public int secondaryActionDescription = -1;
public Intent intent;
public Intent secondaryIntent = null;
public ArrayList<Long> ids = new ArrayList<Long>();
@@ -1247,6 +1241,7 @@
entry.kind = (kind.titleRes == -1 || kind.titleRes == 0) ? ""
: context.getString(kind.titleRes);
entry.data = buildDataString(kind, values, context);
+ entry.resPackageName = kind.resPackageName;
if (kind.typeColumn != null && values.containsKey(kind.typeColumn)) {
entry.type = values.getAsInteger(kind.typeColumn);
@@ -1269,11 +1264,6 @@
entry.typeString = "";
}
- if (kind.iconRes > 0) {
- entry.resPackageName = kind.resPackageName;
- entry.actionIcon = kind.iconRes;
- }
-
return entry;
}
@@ -1349,8 +1339,8 @@
if (!TextUtils.equals(mimetype, entry.mimetype)
|| !ContactsUtils.areIntentActionEqual(intent, entry.intent)
- || !ContactsUtils.areIntentActionEqual(secondaryIntent, entry.secondaryIntent)
- || actionIcon != entry.actionIcon) {
+ || !ContactsUtils.areIntentActionEqual(
+ secondaryIntent, entry.secondaryIntent)) {
return false;
}
@@ -1633,19 +1623,24 @@
// Set the secondary action button
final ImageView secondaryActionView = views.secondaryActionButton;
Drawable secondaryActionIcon = null;
+ String secondaryActionDescription = null;
if (entry.secondaryActionIcon != -1) {
secondaryActionIcon = resources.getDrawable(entry.secondaryActionIcon);
+ secondaryActionDescription = resources.getString(entry.secondaryActionDescription);
} else if ((entry.chatCapability & Im.CAPABILITY_HAS_CAMERA) != 0) {
secondaryActionIcon =
resources.getDrawable(R.drawable.sym_action_videochat_holo_light);
+ secondaryActionDescription = resources.getString(R.string.video_chat);
} else if ((entry.chatCapability & Im.CAPABILITY_HAS_VOICE) != 0) {
secondaryActionIcon =
resources.getDrawable(R.drawable.sym_action_audiochat_holo_light);
+ secondaryActionDescription = resources.getString(R.string.audio_chat);
}
final View secondaryActionViewContainer = views.secondaryActionViewContainer;
if (entry.secondaryIntent != null && secondaryActionIcon != null) {
secondaryActionView.setImageDrawable(secondaryActionIcon);
+ secondaryActionView.setContentDescription(secondaryActionDescription);
secondaryActionViewContainer.setTag(entry);
secondaryActionViewContainer.setVisibility(View.VISIBLE);
views.secondaryActionDivider.setVisibility(View.VISIBLE);
diff --git a/src/com/android/contacts/editor/ContactEditorUtils.java b/src/com/android/contacts/editor/ContactEditorUtils.java
index 0e27223..05a041d 100644
--- a/src/com/android/contacts/editor/ContactEditorUtils.java
+++ b/src/com/android/contacts/editor/ContactEditorUtils.java
@@ -19,7 +19,6 @@
import com.android.contacts.model.AccountType;
import com.android.contacts.model.AccountTypeManager;
import com.android.contacts.model.AccountWithDataSet;
-import com.android.contacts.test.NeededForTesting;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
@@ -32,6 +31,7 @@
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.text.TextUtils;
+import android.util.Log;
import java.util.ArrayList;
import java.util.List;
@@ -39,9 +39,6 @@
/**
* Utility methods for the "account changed" notification in the new contact creation flow.
- *
- * TODO Remove all the "@VisibleForTesting"s once they're actually used in the app.
- * (Until then we need them to avoid "no such method" in tests)
*/
public class ContactEditorUtils {
private static final String TAG = "ContactEditorUtils";
@@ -82,6 +79,18 @@
.remove(KEY_ANYTHING_SAVED).apply();
}
+ void removeDefaultAccountForTest() {
+ mPrefs.edit().remove(KEY_DEFAULT_ACCOUNT).apply();
+ }
+
+ /**
+ * Sets the {@link #KEY_KNOWN_ACCOUNTS} and {@link #KEY_DEFAULT_ACCOUNT} preference values to
+ * empty strings to reset the state of the preferences file.
+ */
+ private void resetPreferenceValues() {
+ mPrefs.edit().putString(KEY_KNOWN_ACCOUNTS, "").putString(KEY_DEFAULT_ACCOUNT, "").apply();
+ }
+
private List<AccountWithDataSet> getWritableAccounts() {
return mAccountTypes.getAccounts(true);
}
@@ -103,15 +112,23 @@
* @param defaultAccount the account used to save a newly created contact. Or pass {@code null}
* If the user selected "local only".
*/
- @NeededForTesting
public void saveDefaultAndAllAccounts(AccountWithDataSet defaultAccount) {
- mPrefs.edit()
- .putBoolean(KEY_ANYTHING_SAVED, true)
- .putString(
- KEY_KNOWN_ACCOUNTS,AccountWithDataSet.stringifyList(getWritableAccounts()))
- .putString(KEY_DEFAULT_ACCOUNT,
- (defaultAccount == null) ? "" : defaultAccount.stringify())
- .apply();
+ final SharedPreferences.Editor editor = mPrefs.edit()
+ .putBoolean(KEY_ANYTHING_SAVED, true);
+
+ if (defaultAccount == null) {
+ // If the default is "local only", there should be no writable accounts.
+ // This should always be the case with our spec, but because we load the account list
+ // asynchronously using a worker thread, it is possible that there are accounts at this
+ // point. So if the default is null always clear the account list.
+ editor.putString(KEY_KNOWN_ACCOUNTS, "");
+ editor.putString(KEY_DEFAULT_ACCOUNT, "");
+ } else {
+ editor.putString(KEY_KNOWN_ACCOUNTS,
+ AccountWithDataSet.stringifyList(getWritableAccounts()));
+ editor.putString(KEY_DEFAULT_ACCOUNT, defaultAccount.stringify());
+ }
+ editor.apply();
}
/**
@@ -123,13 +140,20 @@
*
* Also note that the returned account may have been removed already.
*/
- @NeededForTesting
public AccountWithDataSet getDefaultAccount() {
final String saved = mPrefs.getString(KEY_DEFAULT_ACCOUNT, null);
if (TextUtils.isEmpty(saved)) {
return null;
}
- return AccountWithDataSet.unstringify(saved);
+ try {
+ return AccountWithDataSet.unstringify(saved);
+ } catch (IllegalArgumentException exception) {
+ Log.e(TAG, "Error with retrieving default account " + exception.toString());
+ // unstringify()can throw an exception if the string is not in an expected format.
+ // Hence, if the preferences file is corrupt, just reset the preference values
+ resetPreferenceValues();
+ return null;
+ }
}
/**
@@ -153,7 +177,15 @@
if (TextUtils.isEmpty(saved)) {
return EMPTY_ACCOUNTS;
}
- return AccountWithDataSet.unstringifyList(saved);
+ try {
+ return AccountWithDataSet.unstringifyList(saved);
+ } catch (IllegalArgumentException exception) {
+ Log.e(TAG, "Error with retrieving saved accounts " + exception.toString());
+ // unstringifyList()can throw an exception if the string is not in an expected format.
+ // Hence, if the preferences file is corrupt, just reset the preference values
+ resetPreferenceValues();
+ return EMPTY_ACCOUNTS;
+ }
}
/**
@@ -161,12 +193,12 @@
* - If it's the first launch.
* - Or, if an account has been added.
* - Or, if the default account has been removed.
+ * (And some extra sanity check)
*
* Note if this method returns {@code false}, the caller can safely assume that
* {@link #getDefaultAccount} will return a valid account. (Either an account which still
* exists, or {@code null} which should be interpreted as "local only".)
*/
- @NeededForTesting
public boolean shouldShowAccountChangedNotification() {
if (isFirstLaunch()) {
return true;
@@ -174,14 +206,27 @@
// Account added?
final List<AccountWithDataSet> savedAccounts = getSavedAccounts();
- for (AccountWithDataSet account : getWritableAccounts()) {
+ final List<AccountWithDataSet> currentWritableAccounts = getWritableAccounts();
+ for (AccountWithDataSet account : currentWritableAccounts) {
if (!savedAccounts.contains(account)) {
return true; // New account found.
}
}
+ final AccountWithDataSet defaultAccount = getDefaultAccount();
+
// Does default account still exist?
- if (!isValidAccount(getDefaultAccount())) {
+ if (!isValidAccount(defaultAccount)) {
+ return true;
+ }
+
+ // If there is an inconsistent state in the preferences file - default account is null
+ // ("local" account) while there are multiple accounts, then show the notification dialog.
+ // This shouldn't ever happen, but this should allow the user can get back into a normal
+ // state after they respond to the notification.
+ if (defaultAccount == null && currentWritableAccounts.size() > 0) {
+ Log.e(TAG, "Preferences file in an inconsistent state, request that the default account"
+ + " and current writable accounts be saved again");
return true;
}
@@ -207,7 +252,6 @@
* {@link Activity#onActivityResult} or {@link android.app.Fragment#onActivityResult} to
* get the result.
*/
- @NeededForTesting
public Intent createAddWritableAccountIntent() {
return AccountManager.newChooseAccountIntent(
null, // selectedAccount
@@ -231,7 +275,6 @@
* will never have {@link AccountWithDataSet#dataSet} set, as there's no way to create an
* extension package account from setup wizard.
*/
- @NeededForTesting
public AccountWithDataSet getCreatedAccount(int resultCode, Intent resultData) {
// Javadoc doesn't say anything about resultCode but that the data intent will be non null
// on success.
@@ -246,4 +289,3 @@
return new AccountWithDataSet(accountName, accountType, null);
}
}
-
diff --git a/src/com/android/contacts/editor/KindSectionView.java b/src/com/android/contacts/editor/KindSectionView.java
index 686939c..eb8a0a7 100644
--- a/src/com/android/contacts/editor/KindSectionView.java
+++ b/src/com/android/contacts/editor/KindSectionView.java
@@ -224,7 +224,7 @@
}
protected void updateAddFooterVisible() {
- if (!mReadOnly && mKind.isList) {
+ if (!mReadOnly && (mKind.typeOverallMax != 1)) {
// First determine whether there are any existing empty editors.
updateEmptyEditors();
// If there are no existing empty editors and it's possible to add
@@ -294,7 +294,7 @@
public void addItem() {
ValuesDelta values = null;
// If this is a list, we can freely add. If not, only allow adding the first.
- if (!mKind.isList) {
+ if (mKind.typeOverallMax == 1) {
if (getEditorCount() == 1) {
return;
}
diff --git a/src/com/android/contacts/editor/RawContactEditorView.java b/src/com/android/contacts/editor/RawContactEditorView.java
index 3a40a0f..c9c6699 100644
--- a/src/com/android/contacts/editor/RawContactEditorView.java
+++ b/src/com/android/contacts/editor/RawContactEditorView.java
@@ -422,7 +422,7 @@
}
DataKind kind = sectionView.getKind();
// not a list and already exists? ignore
- if (!kind.isList && sectionView.getEditorCount() != 0) {
+ if ((kind.typeOverallMax == 1) && sectionView.getEditorCount() != 0) {
continue;
}
if (DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME.equals(kind.mimeType)) {
diff --git a/src/com/android/contacts/editor/TextFieldsEditorView.java b/src/com/android/contacts/editor/TextFieldsEditorView.java
index 062c25d..5627aa5 100644
--- a/src/com/android/contacts/editor/TextFieldsEditorView.java
+++ b/src/com/android/contacts/editor/TextFieldsEditorView.java
@@ -183,7 +183,7 @@
} else {
fieldView.setMinHeight(mMinFieldHeight);
}
- fieldView.setTextAppearance(getContext(), kind.textAppearanceResourceId);
+ fieldView.setTextAppearance(getContext(), android.R.style.TextAppearance_Medium);
fieldView.setGravity(Gravity.TOP);
mFieldEditTexts[index] = fieldView;
fieldView.setId(vig.getId(state, kind, entry, index));
diff --git a/src/com/android/contacts/list/ContactListItemView.java b/src/com/android/contacts/list/ContactListItemView.java
index 70493c5..295c2a1 100644
--- a/src/com/android/contacts/list/ContactListItemView.java
+++ b/src/com/android/contacts/list/ContactListItemView.java
@@ -74,10 +74,6 @@
// Style values for layout and appearance
private final int mPreferredHeight;
private final int mVerticalDividerMargin;
- private final int mPaddingTop;
- private final int mPaddingRight;
- private final int mPaddingBottom;
- private final int mPaddingLeft;
private final int mGapBetweenImageAndText;
private final int mGapBetweenLabelAndData;
private final int mCallButtonPadding;
@@ -93,11 +89,31 @@
private final int mTextIndent;
private Drawable mActivatedBackgroundDrawable;
+ // In the future we may need to merge these local padding to View's mPaddingXXX
+ private final int mExtraPaddingTop;
+ private final int mExtraPaddingBottom;
+ private int mExtraPaddingLeft;
+ private int mExtraPaddingRight;
+
+ // Will be used with adjustListItemSelectionBounds().
+ private int mSelectionBoundsMarginLeft;
+ private int mSelectionBoundsMarginRight;
+
// Horizontal divider between contact views.
private boolean mHorizontalDividerVisible = true;
private Drawable mHorizontalDividerDrawable;
private int mHorizontalDividerHeight;
+ /**
+ * Where to put contact photo. This affects the other Views' layout or look-and-feel.
+ */
+ public enum PhotoPosition {
+ LEFT,
+ RIGHT
+ }
+ public static final PhotoPosition DEFAULT_PHOTO_POSITION = PhotoPosition.RIGHT;
+ private PhotoPosition mPhotoPosition = DEFAULT_PHOTO_POSITION;
+
// Vertical divider between the call icon and the text.
private boolean mVerticalDividerVisible;
private Drawable mVerticalDividerDrawable;
@@ -209,13 +225,13 @@
R.styleable.ContactListItemView_list_item_divider);
mVerticalDividerMargin = a.getDimensionPixelOffset(
R.styleable.ContactListItemView_list_item_vertical_divider_margin, 0);
- mPaddingTop = a.getDimensionPixelOffset(
+ mExtraPaddingTop = a.getDimensionPixelOffset(
R.styleable.ContactListItemView_list_item_padding_top, 0);
- mPaddingBottom = a.getDimensionPixelOffset(
+ mExtraPaddingBottom = a.getDimensionPixelOffset(
R.styleable.ContactListItemView_list_item_padding_bottom, 0);
- mPaddingLeft = a.getDimensionPixelOffset(
+ mExtraPaddingLeft = a.getDimensionPixelOffset(
R.styleable.ContactListItemView_list_item_padding_left, 0);
- mPaddingRight = a.getDimensionPixelOffset(
+ mExtraPaddingRight = a.getDimensionPixelOffset(
R.styleable.ContactListItemView_list_item_padding_right, 0);
mGapBetweenImageAndText = a.getDimensionPixelOffset(
R.styleable.ContactListItemView_list_item_gap_between_image_and_text, 0);
@@ -336,7 +352,8 @@
// Calculate height including padding
height += mNameTextViewHeight + mPhoneticNameTextViewHeight + mLabelTextViewHeight +
- mSnippetTextViewHeight + mStatusTextViewHeight + mPaddingTop + mPaddingBottom;
+ mSnippetTextViewHeight + mStatusTextViewHeight +
+ mExtraPaddingTop + mExtraPaddingBottom;
if (isVisible(mCallButton)) {
mCallButton.measure(0, 0);
@@ -344,7 +361,7 @@
// Make sure the height is at least as high as the photo
ensurePhotoViewSize();
- height = Math.max(height, mPhotoViewHeight + mPaddingBottom + mPaddingTop);
+ height = Math.max(height, mPhotoViewHeight + mExtraPaddingBottom + mExtraPaddingTop);
// Add horizontal divider height
if (mHorizontalDividerVisible) {
@@ -375,29 +392,30 @@
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- int height = bottom - top;
- int width = right - left;
+ final int height = bottom - top;
+ final int width = right - left;
// Determine the vertical bounds by laying out the header first.
int topBound = 0;
int bottomBound = height;
- int leftBound = mPaddingLeft;
+ int leftBound = mExtraPaddingLeft;
+ int rightBound = width - mExtraPaddingRight;
// Put the header in the top of the contact view (Text + underline view)
if (mHeaderVisible) {
mHeaderTextView.layout(leftBound + mHeaderTextIndent,
0,
- width - mPaddingRight,
+ rightBound,
mHeaderBackgroundHeight);
if (mCountView != null) {
- mCountView.layout(width - mPaddingRight - mCountView.getMeasuredWidth(),
+ mCountView.layout(width - mExtraPaddingRight - mCountView.getMeasuredWidth(),
0,
- width - mPaddingRight,
+ rightBound,
mHeaderBackgroundHeight);
}
mHeaderDivider.layout(leftBound,
mHeaderBackgroundHeight,
- width - mPaddingRight,
+ rightBound,
mHeaderBackgroundHeight + mHeaderUnderlineHeight);
topBound += (mHeaderBackgroundHeight + mHeaderUnderlineHeight);
}
@@ -407,7 +425,7 @@
mHorizontalDividerDrawable.setBounds(
leftBound,
height - mHorizontalDividerHeight,
- width - mPaddingRight,
+ rightBound,
height);
bottomBound -= mHorizontalDividerHeight;
}
@@ -418,26 +436,55 @@
mActivatedBackgroundDrawable.setBounds(mBoundsWithoutHeader);
}
- // Set the top/bottom paddings
- topBound += mPaddingTop;
- bottomBound -= mPaddingBottom;
+ // Set the top/bottom padding
+ topBound += mExtraPaddingTop;
+ bottomBound -= mExtraPaddingBottom;
- // Set contact layout:
- // Photo on the right, call button to the left of the photo
- // Text aligned to the left along with the presence status.
+ final View photoView = mQuickContact != null ? mQuickContact : mPhotoView;
+ if (mPhotoPosition == PhotoPosition.LEFT) {
+ // Photo is the left most view. All the other Views should on the right of the photo.
+ if (photoView != null) {
+ // Center the photo vertically
+ final int photoTop = topBound + (bottomBound - topBound - mPhotoViewHeight) / 2;
+ photoView.layout(
+ leftBound,
+ photoTop,
+ leftBound + mPhotoViewWidth,
+ photoTop + mPhotoViewHeight);
+ leftBound += mPhotoViewWidth + mGapBetweenImageAndText;
+ } else if (mKeepHorizontalPaddingForPhotoView) {
+ // Draw nothing but keep the padding.
+ leftBound += mPhotoViewWidth + mGapBetweenImageAndText;
+ }
+ } else {
+ // Photo is the right most view. Right bound should be adjusted that way.
+ if (photoView != null) {
+ // Center the photo vertically
+ final int photoTop = topBound + (bottomBound - topBound - mPhotoViewHeight) / 2;
+ photoView.layout(
+ rightBound - mPhotoViewWidth,
+ photoTop,
+ rightBound,
+ photoTop + mPhotoViewHeight);
+ rightBound -= (mPhotoViewWidth + mGapBetweenImageAndText);
+ }
- // layout the photo and call button.
- int rightBound = layoutRightSide(height, topBound, bottomBound, width - mPaddingRight);
+ // Add indent between left-most padding and texts.
+ leftBound += mTextIndent;
+ }
+
+ // Layout the call button.
+ rightBound = layoutRightSide(height, topBound, bottomBound, rightBound);
// Center text vertically
- int totalTextHeight = mNameTextViewHeight + mPhoneticNameTextViewHeight +
+ final int totalTextHeight = mNameTextViewHeight + mPhoneticNameTextViewHeight +
mLabelTextViewHeight + mSnippetTextViewHeight + mStatusTextViewHeight;
int textTopBound = (bottomBound + topBound - totalTextHeight) / 2;
// Layout all text view and presence icon
// Put name TextView first
if (isVisible(mNameTextView)) {
- mNameTextView.layout(leftBound + mTextIndent,
+ mNameTextView.layout(leftBound,
textTopBound,
rightBound,
textTopBound + mNameTextViewHeight);
@@ -445,13 +492,13 @@
}
// Presence and status
- int statusLeftBound = leftBound + mTextIndent;
+ int statusLeftBound = leftBound;
if (isVisible(mPresenceIcon)) {
int iconWidth = mPresenceIcon.getMeasuredWidth();
mPresenceIcon.layout(
- leftBound + mTextIndent,
+ leftBound,
textTopBound,
- leftBound + iconWidth + mTextIndent,
+ leftBound + iconWidth,
textTopBound + mStatusTextViewHeight);
statusLeftBound += (iconWidth + mPresenceIconMargin);
}
@@ -470,7 +517,7 @@
// Rest of text views
int dataLeftBound = leftBound;
if (isVisible(mPhoneticNameTextView)) {
- mPhoneticNameTextView.layout(leftBound + mTextIndent,
+ mPhoneticNameTextView.layout(leftBound,
textTopBound,
rightBound,
textTopBound + mPhoneticNameTextViewHeight);
@@ -478,12 +525,21 @@
}
if (isVisible(mLabelView)) {
- dataLeftBound = leftBound + mLabelView.getMeasuredWidth() + mTextIndent;
- mLabelView.layout(leftBound + mTextIndent,
- textTopBound,
- dataLeftBound,
- textTopBound + mLabelTextViewHeight);
- dataLeftBound += mGapBetweenLabelAndData;
+ if (mPhotoPosition == PhotoPosition.LEFT) {
+ // When photo is on left, label is placed on the right edge of the list item.
+ mLabelView.layout(rightBound - mLabelView.getMeasuredWidth(),
+ textTopBound,
+ rightBound,
+ textTopBound + mLabelTextViewHeight);
+ } else {
+ // When photo is on right, label is placed on the left of data view.
+ dataLeftBound = leftBound + mLabelView.getMeasuredWidth();
+ mLabelView.layout(leftBound,
+ textTopBound,
+ dataLeftBound,
+ textTopBound + mLabelTextViewHeight);
+ dataLeftBound += mGapBetweenLabelAndData;
+ }
}
if (isVisible(mDataView)) {
@@ -497,7 +553,7 @@
}
if (isVisible(mSnippetView)) {
- mSnippetView.layout(leftBound + mTextIndent,
+ mSnippetView.layout(leftBound,
textTopBound,
rightBound,
textTopBound + mSnippetTextViewHeight);
@@ -510,21 +566,6 @@
* @return new right boundary
*/
protected int layoutRightSide(int height, int topBound, int bottomBound, int rightBound) {
-
- // Photo is the right most view, set it up
-
- View photoView = mQuickContact != null ? mQuickContact : mPhotoView;
- if (photoView != null) {
- // Center the photo vertically
- int photoTop = topBound + (bottomBound - topBound - mPhotoViewHeight) / 2;
- photoView.layout(
- rightBound - mPhotoViewWidth,
- photoTop,
- rightBound,
- photoTop + mPhotoViewHeight);
- rightBound -= (mPhotoViewWidth + mGapBetweenImageAndText);
- }
-
// Put call button and vertical divider
if (isVisible(mCallButton)) {
int buttonWidth = mCallButton.getMeasuredWidth();
@@ -553,6 +594,8 @@
public void adjustListItemSelectionBounds(Rect bounds) {
bounds.top += mBoundsWithoutHeader.top;
bounds.bottom = bounds.top + mBoundsWithoutHeader.height();
+ bounds.left += mSelectionBoundsMarginLeft;
+ bounds.right -= mSelectionBoundsMarginRight;
}
protected boolean isVisible(View view) {
@@ -874,7 +917,12 @@
mLabelView.setSingleLine(true);
mLabelView.setEllipsize(getTextEllipsis());
mLabelView.setTextAppearance(mContext, android.R.style.TextAppearance_Small);
- mLabelView.setTypeface(mLabelView.getTypeface(), Typeface.BOLD);
+ if (mPhotoPosition == PhotoPosition.LEFT) {
+ mLabelView.setTextSize(TypedValue.COMPLEX_UNIT_SP, mCountViewTextSize);
+ mLabelView.setAllCaps(true);
+ } else {
+ mLabelView.setTypeface(mLabelView.getTypeface(), Typeface.BOLD);
+ }
mLabelView.setActivated(isActivated());
addView(mLabelView);
}
@@ -1175,4 +1223,44 @@
// view (ListView).
forceLayout();
}
+
+ public void setPhotoPosition(PhotoPosition photoPosition) {
+ mPhotoPosition = photoPosition;
+ }
+
+ public PhotoPosition getPhotoPosition() {
+ return mPhotoPosition;
+ }
+
+ /**
+ * Sets custom padding inside this object. Do not use this method without any strong reason.
+ *
+ * Detail: we cannot simply override {@link #setPadding(int, int, int, int)}. {@link View}
+ * does *not* know this view's local padding but has completely different ones.
+ * See View#mPaddingLeft and View#mPaddingRight. View also has View#mUserPaddingLeft, and
+ * View#mUserPaddingRight in addition to View#mPaddingLeft and View#mPaddingRight, to handle
+ * {@link View#setPadding(int, int, int, int)} correctly. If setPadding() is overridden to
+ * reset our {@link #mExtraPaddingLeft} and {@link #mExtraPaddingRight} carelessly, the whole
+ * View layout gets confused.
+ *
+ * To simplify our implementation, this method just modify the local two padding without
+ * confusing its parent.
+ *
+ * If we want to fix this multiple padding issue correctly, we should merge local padding
+ * in this class into View's ones. Also we should remove "list_item_padding_left" and
+ * "list_item_padding_right" attributes, using "android:paddingLeft" and "android:paddingRight".
+ */
+ public void setExtraPadding(int left, int right) {
+ mExtraPaddingLeft = left;
+ mExtraPaddingRight = right;
+ }
+
+ /**
+ * Specifies left and right margin for selection bounds. See also
+ * {@link #adjustListItemSelectionBounds(Rect)}.
+ */
+ public void setSelectionBoundsHorizontalMargin(int left, int right) {
+ mSelectionBoundsMarginLeft = left;
+ mSelectionBoundsMarginRight = right;
+ }
}
diff --git a/src/com/android/contacts/list/ContactTileAdapter.java b/src/com/android/contacts/list/ContactTileAdapter.java
index a12b127..8de7fd5 100644
--- a/src/com/android/contacts/list/ContactTileAdapter.java
+++ b/src/com/android/contacts/list/ContactTileAdapter.java
@@ -201,6 +201,7 @@
switch (mDisplayType) {
case STREQUENT:
case STREQUENT_PHONE_ONLY:
+ cursor.moveToPosition(-1);
while (cursor.moveToNext()) {
if (cursor.getInt(mStarredIndex) == 0) {
return cursor.getPosition();
@@ -409,7 +410,7 @@
text.setText(mDisplayType == DisplayType.STREQUENT_PHONE_ONLY ?
mContext.getString(R.string.favoritesFrequentCalled) :
mContext.getString(R.string.favoritesFrequentContacted));
- return dividerView;
+ return dividerView;
}
private int getLayoutResourceId(int viewType) {
@@ -468,6 +469,14 @@
}
}
+ /**
+ * Returns the "frequent header" position. Only available when STREQUENT or
+ * STREQUENT_PHONE_ONLY is used for its display type.
+ */
+ public int getFrequentHeaderPosition() {
+ return getRowCount(mDividerPosition);
+ }
+
private ContactTileView.Listener mContactTileListener = new ContactTileView.Listener() {
@Override
public void onClick(ContactTileView contactTileView) {
diff --git a/src/com/android/contacts/list/PhoneFavoriteFragment.java b/src/com/android/contacts/list/PhoneFavoriteFragment.java
new file mode 100644
index 0000000..860e368
--- /dev/null
+++ b/src/com/android/contacts/list/PhoneFavoriteFragment.java
@@ -0,0 +1,436 @@
+/*
+ * 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.ContactPhotoManager;
+import com.android.contacts.ContactTileLoaderFactory;
+import com.android.contacts.R;
+import com.android.contacts.preference.ContactsPreferences;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.app.LoaderManager;
+import android.content.Context;
+import android.content.CursorLoader;
+import android.content.Intent;
+import android.content.Loader;
+import android.content.SharedPreferences;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.preference.PreferenceManager;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.Directory;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.ListView;
+import android.widget.TextView;
+
+/**
+ * Fragment for Phone UI's favorite screen.
+ *
+ * This fragment contains three kinds of contacts in one screen: "starred", "frequent", and "all"
+ * contacts. To show them at once, this merges results from {@link ContactTileAdapter} and
+ * {@link PhoneNumberListAdapter} into one unified list using {@link PhoneFavoriteMergedAdapter}.
+ * A contact filter header is also inserted between those adapters' results.
+ */
+public class PhoneFavoriteFragment extends Fragment implements OnItemClickListener {
+ private static final String TAG = PhoneFavoriteFragment.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
+ /**
+ * Used with LoaderManager.
+ */
+ private static int LOADER_ID_CONTACT_TILE = 1;
+ private static int LOADER_ID_ALL_CONTACTS = 2;
+
+ private static final String KEY_FILTER = "filter";
+
+ public interface Listener {
+ public void onContactSelected(Uri contactUri);
+ }
+
+ private class ContactTileLoaderListener implements LoaderManager.LoaderCallbacks<Cursor> {
+ @Override
+ public CursorLoader onCreateLoader(int id, Bundle args) {
+ if (DEBUG) Log.d(TAG, "ContactTileLoaderListener#onCreateLoader.");
+ return ContactTileLoaderFactory.createStrequentPhoneOnlyLoader(getActivity());
+ }
+
+ @Override
+ public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+ if (DEBUG) Log.d(TAG, "ContactTileLoaderListener#onLoadFinished");
+ mContactTileAdapter.setContactCursor(data);
+
+ if (mAllContactsForceReload) {
+ mAllContactsAdapter.onDataReload();
+ // Use restartLoader() to make LoaderManager to load the section again.
+ getLoaderManager().restartLoader(
+ LOADER_ID_ALL_CONTACTS, null, mAllContactsLoaderListener);
+ } else if (!mAllContactsLoaderStarted) {
+ // Load "all" contacts if not loaded yet.
+ getLoaderManager().initLoader(
+ LOADER_ID_ALL_CONTACTS, null, mAllContactsLoaderListener);
+ }
+ mAllContactsForceReload = false;
+ mAllContactsLoaderStarted = true;
+
+ // Show the filter header with "loading" state.
+ updateFilterHeaderView();
+ mAccountFilterHeaderContainer.setVisibility(View.VISIBLE);
+ }
+
+ @Override
+ public void onLoaderReset(Loader<Cursor> loader) {
+ if (DEBUG) Log.d(TAG, "ContactTileLoaderListener#onLoaderReset. ");
+ }
+ }
+
+ private class AllContactsLoaderListener implements LoaderManager.LoaderCallbacks<Cursor> {
+ @Override
+ public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+ if (DEBUG) Log.d(TAG, "AllContactsLoaderListener#onCreateLoader");
+ CursorLoader loader = new CursorLoader(getActivity(), null, null, null, null, null);
+ mAllContactsAdapter.configureLoader(loader, Directory.DEFAULT);
+ return loader;
+ }
+
+ @Override
+ public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+ if (DEBUG) Log.d(TAG, "AllContactsLoaderListener#onLoadFinished");
+ mAllContactsAdapter.changeCursor(0, data);
+ updateFilterHeaderView();
+ mAccountFilterHeaderContainer.setVisibility(View.VISIBLE);
+ }
+
+ @Override
+ public void onLoaderReset(Loader<Cursor> loader) {
+ if (DEBUG) Log.d(TAG, "AllContactsLoaderListener#onLoaderReset. ");
+ }
+ }
+
+ private class ContactTileAdapterListener implements ContactTileAdapter.Listener {
+ @Override
+ public void onContactSelected(Uri contactUri) {
+ if (mListener != null) {
+ mListener.onContactSelected(contactUri);
+ }
+ }
+ }
+
+ private class FilterHeaderClickListener implements OnClickListener {
+ @Override
+ public void onClick(View view) {
+ final Activity activity = getActivity();
+ if (activity != null) {
+ final Intent intent = new Intent(activity, AccountFilterActivity.class);
+ activity.startActivityForResult(
+ intent, AccountFilterActivity.DEFAULT_REQUEST_CODE);
+ }
+ }
+ }
+
+ private class ContactsPreferenceChangeListener
+ implements ContactsPreferences.ChangeListener {
+ @Override
+ public void onChange() {
+ if (loadContactsPreferences()) {
+ requestReloadAllContacts();
+ }
+ }
+ }
+
+ private Listener mListener;
+ private PhoneFavoriteMergedAdapter mAdapter;
+ private ContactTileAdapter mContactTileAdapter;
+ private PhoneNumberListAdapter mAllContactsAdapter;
+
+ /**
+ * true when the loader for {@link PhoneNumberListAdapter} has started already.
+ */
+ private boolean mAllContactsLoaderStarted;
+ /**
+ * true when the loader for {@link PhoneNumberListAdapter} must reload "all" contacts again.
+ * It typically happens when {@link ContactsPreferences} has changed its settings
+ * (display order and sort order)
+ */
+ private boolean mAllContactsForceReload;
+
+ private SharedPreferences mPrefs;
+ private ContactsPreferences mContactsPrefs;
+ private ContactListFilter mFilter;
+
+ private TextView mEmptyView;
+ private ListView mListView;
+ private View mAccountFilterHeaderContainer;
+ private TextView mAccountFilterHeaderView;
+
+ private final ContactTileAdapter.Listener mContactTileAdapterListener =
+ new ContactTileAdapterListener();
+ private final LoaderManager.LoaderCallbacks<Cursor> mContactTileLoaderListener =
+ new ContactTileLoaderListener();
+ private final LoaderManager.LoaderCallbacks<Cursor> mAllContactsLoaderListener =
+ new AllContactsLoaderListener();
+ private final OnClickListener mFilterHeaderClickListener = new FilterHeaderClickListener();
+ private final ContactsPreferenceChangeListener mContactsPreferenceChangeListener =
+ new ContactsPreferenceChangeListener();
+
+ @Override
+ public void onCreate(Bundle savedState) {
+ super.onCreate(savedState);
+ if (savedState != null) {
+ mFilter = savedState.getParcelable(KEY_FILTER);
+ }
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putParcelable(KEY_FILTER, mFilter);
+ }
+
+ @Override
+ public void onAttach(Activity activity) {
+ super.onAttach(activity);
+
+ mPrefs = PreferenceManager.getDefaultSharedPreferences(activity);
+ mContactsPrefs = new ContactsPreferences(activity);
+
+ // Create the account filter header but keep it hidden until "all" contacts are loaded.
+ mAccountFilterHeaderContainer = View.inflate(
+ activity, R.layout.account_filter_header, null);
+ mAccountFilterHeaderView =
+ (TextView) mAccountFilterHeaderContainer.findViewById(R.id.account_filter_header);
+ mAccountFilterHeaderContainer.setOnClickListener(mFilterHeaderClickListener);
+ mAccountFilterHeaderContainer.setVisibility(View.GONE);
+
+ initAdapters(activity);
+
+ mAllContactsAdapter.setFilter(mFilter);
+ mAllContactsForceReload = true;
+ updateFilterHeaderView();
+ }
+
+ /**
+ * Constructs and initializes {@link #mContactTileAdapter}, {@link #mAllContactsAdapter}, and
+ * {@link #mAllContactsAdapter}.
+ */
+ private void initAdapters(Context context) {
+ mContactTileAdapter = new ContactTileAdapter(context, mContactTileAdapterListener,
+ getResources().getInteger(R.integer.contact_tile_column_count),
+ ContactTileAdapter.DisplayType.STREQUENT_PHONE_ONLY);
+ mContactTileAdapter.setPhotoLoader(ContactPhotoManager.getInstance(context));
+
+ // Setup the "all" adapter manually. See also the setup logic in ContactEntryListFragment.
+ mAllContactsAdapter = new PhoneNumberListAdapter(context);
+ mAllContactsAdapter.setDisplayPhotos(true);
+ mAllContactsAdapter.setQuickContactEnabled(true);
+ mAllContactsAdapter.setSearchMode(false);
+ mAllContactsAdapter.setIncludeProfile(false);
+ mAllContactsAdapter.setSelectionVisible(false);
+ mAllContactsAdapter.setDarkTheme(true);
+ mAllContactsAdapter.setPhotoLoader(ContactPhotoManager.getInstance(context));
+ // Disable directory header.
+ mAllContactsAdapter.setHasHeader(0, false);
+ // Show A-Z section index.
+ mAllContactsAdapter.setSectionHeaderDisplayEnabled(true);
+ // Disable pinned header. It doesn't work with this fragment.
+ mAllContactsAdapter.setPinnedPartitionHeadersEnabled(false);
+ // Put photos on left for consistency with "frequent" contacts section.
+ mAllContactsAdapter.setPhotoPosition(ContactListItemView.PhotoPosition.LEFT);
+
+ mAdapter = new PhoneFavoriteMergedAdapter(
+ context, mContactTileAdapter, mAccountFilterHeaderContainer, mAllContactsAdapter);
+ }
+
+ @Override
+ public void onDetach() {
+ super.onDetach();
+ mPrefs = null;
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ final View listLayout = inflater.inflate(R.layout.contact_tile_list, container, false);
+
+ mListView = (ListView) listLayout.findViewById(R.id.contact_tile_list);
+
+ mListView.setItemsCanFocus(true);
+ mListView.setAdapter(mAdapter);
+ mListView.setOnItemClickListener(this);
+ mListView.setFastScrollEnabled(true);
+ // We want to hide the scroll bar after a while.
+ mListView.setFastScrollAlwaysVisible(false);
+ mListView.setVerticalScrollBarEnabled(true);
+ mListView.setVerticalScrollbarPosition(View.SCROLLBAR_POSITION_RIGHT);
+ mListView.setScrollBarStyle(ListView.SCROLLBARS_OUTSIDE_OVERLAY);
+
+ mEmptyView = (TextView) listLayout.findViewById(R.id.contact_tile_list_empty);
+ mEmptyView.setText(getString(R.string.listTotalAllContactsZero));
+ mListView.setEmptyView(mEmptyView);
+
+ updateFilterHeaderView();
+
+ return listLayout;
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+
+ mContactsPrefs.registerChangeListener(mContactsPreferenceChangeListener);
+
+ // If ContactsPreferences has changed, we need to reload "all" contacts with the new
+ // settings. If mAllContactsFoarceReload is already true, it should be kept.
+ if (loadContactsPreferences()) {
+ mAllContactsForceReload = true;
+ }
+
+ // Use initLoader() instead of reloadLoader() to refraing unnecessary reload.
+ // This method call implicitly assures ContactTileLoaderListener's onLoadFinished() will
+ // be called, on which we'll check if "all" contacts should be reloaded again or not.
+ getLoaderManager().initLoader(LOADER_ID_CONTACT_TILE, null, mContactTileLoaderListener);
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ mContactsPrefs.unregisterChangeListener();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * This is only effective for elements provided by {@link #mContactTileAdapter}.
+ * {@link #mContactTileAdapter} has its own logic for click events.
+ */
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ final int contactTileAdapterCount = mContactTileAdapter.getCount();
+ if (position <= contactTileAdapterCount) {
+ Log.e(TAG, "onItemClick() event for unexpected position. "
+ + "The position " + position + " is before \"all\" section. Ignored.");
+ } else {
+ final int localPosition = position - mContactTileAdapter.getCount() - 1;
+ if (mListener != null) {
+ mListener.onContactSelected(mAllContactsAdapter.getDataUri(localPosition));
+ }
+ }
+ }
+
+ private boolean loadContactsPreferences() {
+ if (mContactsPrefs == null || mAllContactsAdapter == null) {
+ return false;
+ }
+
+ boolean changed = false;
+ if (mAllContactsAdapter.getContactNameDisplayOrder() != mContactsPrefs.getDisplayOrder()) {
+ mAllContactsAdapter.setContactNameDisplayOrder(mContactsPrefs.getDisplayOrder());
+ changed = true;
+ }
+
+ if (mAllContactsAdapter.getSortOrder() != mContactsPrefs.getSortOrder()) {
+ mAllContactsAdapter.setSortOrder(mContactsPrefs.getSortOrder());
+ changed = true;
+ }
+
+ return changed;
+ }
+
+ /**
+ * Requests to reload "all" contacts. If the section is already loaded, this method will
+ * force reloading it now. If the section isn't loaded yet, the actual load may be done later
+ * (on {@link #onStart()}.
+ */
+ private void requestReloadAllContacts() {
+ if (DEBUG) {
+ Log.d(TAG, "requestReloadAllContacts()"
+ + " mAllContactsAdapter: " + mAllContactsAdapter
+ + ", mAllContactsLoaderStarted: " + mAllContactsLoaderStarted);
+ }
+
+ if (mAllContactsAdapter == null || !mAllContactsLoaderStarted) {
+ // Remember this request until next load on onStart().
+ mAllContactsForceReload = true;
+ return;
+ }
+
+ if (DEBUG) Log.d(TAG, "Reload \"all\" contacts now.");
+
+ mAllContactsAdapter.onDataReload();
+ // Use restartLoader() to make LoaderManager to load the section again.
+ getLoaderManager().restartLoader(LOADER_ID_ALL_CONTACTS, null, mAllContactsLoaderListener);
+ }
+
+ private void updateFilterHeaderView() {
+ if (mAccountFilterHeaderContainer == null || mAllContactsAdapter == null) {
+ return;
+ }
+
+ final ContactListFilter filter = getFilter();
+ if (mAllContactsAdapter.isLoading()) {
+ mAccountFilterHeaderView.setText(R.string.contact_list_loading);
+ } else if (filter != null) {
+ if (filter.filterType == ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS) {
+ mAccountFilterHeaderView.setText(R.string.list_filter_phones);
+ } else if (filter.filterType == ContactListFilter.FILTER_TYPE_ACCOUNT) {
+ mAccountFilterHeaderView.setText(getString(
+ R.string.listAllContactsInAccount, filter.accountName));
+ } else if (filter.filterType == ContactListFilter.FILTER_TYPE_CUSTOM) {
+ mAccountFilterHeaderView.setText(R.string.listCustomView);
+ } else {
+ Log.w(TAG, "Filter type \"" + filter.filterType + "\" isn't expected.");
+ }
+ } else {
+ Log.w(TAG, "Filter is null.");
+ }
+ }
+
+ public ContactListFilter getFilter() {
+ return mFilter;
+ }
+
+ public void setFilter(ContactListFilter filter) {
+ if ((mFilter == null && filter == null) || (mFilter != null && mFilter.equals(filter))) {
+ return;
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, "setFilter(). old filter (" + mFilter
+ + ") will be replaced with new filter (" + filter + ")");
+ }
+
+ mFilter = filter;
+ if (mPrefs != null) {
+ // Save the preference now.
+ ContactListFilter.storeToPreferences(mPrefs, mFilter);
+ }
+
+ mAllContactsAdapter.setFilter(mFilter);
+ requestReloadAllContacts();
+ updateFilterHeaderView();
+ }
+
+ public void setListener(Listener listener) {
+ mListener = listener;
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/contacts/list/PhoneFavoriteMergedAdapter.java b/src/com/android/contacts/list/PhoneFavoriteMergedAdapter.java
new file mode 100644
index 0000000..c3c96ec
--- /dev/null
+++ b/src/com/android/contacts/list/PhoneFavoriteMergedAdapter.java
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2011 Google Inc.
+ * Licensed to 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.Resources;
+import android.database.DataSetObserver;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.FrameLayout;
+import android.widget.SectionIndexer;
+
+/**
+ * An adapter that combines items from {@link ContactTileAdapter} and
+ * {@link ContactEntryListAdapter} into a single list. In between those two results,
+ * an account filter header will be inserted.
+ */
+public class PhoneFavoriteMergedAdapter extends BaseAdapter implements SectionIndexer {
+
+ // Should show nothing as SectionIndex.
+ // " " will suppress the section indexer itself: nothing will be shown during user's scroll.
+ private static final String SECTION_STRING_STARRED = " ";
+ private static final String SECTION_STRING_FREQUENT = " ";
+
+ private class CustomDataSetObserver extends DataSetObserver {
+ @Override
+ public void onChanged() {
+ notifyDataSetChanged();
+ }
+ }
+
+ private final ContactTileAdapter mContactTileAdapter;
+ private final ContactEntryListAdapter mContactEntryListAdapter;
+ private final View mAccountFilterHeaderContainer;
+
+ private final int mItemPaddingLeft;
+ private final int mItemPaddingRight;
+
+ private final DataSetObserver mObserver;
+
+ public PhoneFavoriteMergedAdapter(Context context,
+ ContactTileAdapter contactTileAdapter,
+ View accountFilterHeaderContainer,
+ ContactEntryListAdapter contactEntryListAdapter) {
+ Resources resources = context.getResources();
+ mItemPaddingLeft = resources.getDimensionPixelSize(R.dimen.detail_item_side_margin);
+ mItemPaddingRight = resources.getDimensionPixelSize(R.dimen.list_visible_scrollbar_padding);
+ mContactTileAdapter = contactTileAdapter;
+ mContactEntryListAdapter = contactEntryListAdapter;
+
+ mAccountFilterHeaderContainer = accountFilterHeaderContainer;
+
+ mObserver = new CustomDataSetObserver();
+ mContactTileAdapter.registerDataSetObserver(mObserver);
+ mContactEntryListAdapter.registerDataSetObserver(mObserver);
+ }
+
+ @Override
+ public int getCount() {
+ final int contactTileAdapterCount = mContactTileAdapter.getCount();
+ final int contactEntryListAdapterCount = mContactEntryListAdapter.getCount();
+ if (mContactEntryListAdapter.isLoading()) {
+ // Hide "all" contacts during its being loaded.
+ return contactTileAdapterCount + 1;
+ } else {
+ return contactTileAdapterCount + contactEntryListAdapterCount + 1;
+ }
+ }
+
+ @Override
+ public Object getItem(int position) {
+ final int contactTileAdapterCount = mContactTileAdapter.getCount();
+ final int contactEntryListAdapterCount = mContactEntryListAdapter.getCount();
+ if (position < contactTileAdapterCount) {
+ return mContactTileAdapter.getItem(position);
+ } else if (position == contactTileAdapterCount) {
+ return mAccountFilterHeaderContainer;
+ } else {
+ final int localPosition = position - contactTileAdapterCount - 1;
+ return mContactTileAdapter.getItem(localPosition);
+ }
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public int getViewTypeCount() {
+ return (mContactTileAdapter.getViewTypeCount()
+ + mContactEntryListAdapter.getViewTypeCount()
+ + 1);
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ final int contactTileAdapterCount = mContactTileAdapter.getCount();
+ final int contactEntryListAdapterCount = mContactEntryListAdapter.getCount();
+ if (position < contactTileAdapterCount) {
+ return mContactTileAdapter.getItemViewType(position);
+ } else if (position == contactTileAdapterCount) {
+ return mContactTileAdapter.getViewTypeCount()
+ + mContactEntryListAdapter.getViewTypeCount();
+ } else {
+ final int localPosition = position - contactTileAdapterCount - 1;
+ final int type = mContactEntryListAdapter.getItemViewType(localPosition);
+ // IGNORE_ITEM_VIEW_TYPE must be handled differently.
+ return (type < 0) ? type : type + mContactTileAdapter.getViewTypeCount();
+ }
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ final int contactTileAdapterCount = mContactTileAdapter.getCount();
+ final int contactEntryListAdapterCount = mContactEntryListAdapter.getCount();
+
+ // Obtain a View relevant for that position, and adjust its horizontal padding. Each
+ // View has different implementation, so we use different way to control those padding.
+ if (position < contactTileAdapterCount) {
+ final View view = mContactTileAdapter.getView(position, convertView, parent);
+ final int frequentHeaderPosition = mContactTileAdapter.getFrequentHeaderPosition();
+ if (position < frequentHeaderPosition) { // "starred" contacts
+ // No padding adjustment.
+ } else if (position == frequentHeaderPosition) {
+ view.setPadding(mItemPaddingLeft, view.getPaddingTop(),
+ mItemPaddingRight, view.getPaddingBottom());
+ } else {
+ // Views for "frequent" contacts use FrameLayout's margins instead of padding.
+ final FrameLayout frameLayout = (FrameLayout) view;
+ final View child = frameLayout.getChildAt(0);
+ FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
+ FrameLayout.LayoutParams.WRAP_CONTENT,
+ FrameLayout.LayoutParams.WRAP_CONTENT);
+ params.setMargins(mItemPaddingLeft, 0, mItemPaddingRight, 0);
+ child.setLayoutParams(params);
+ }
+ return view;
+ } else if (position == contactTileAdapterCount) {
+ mAccountFilterHeaderContainer.setPadding(mItemPaddingLeft,
+ mAccountFilterHeaderContainer.getPaddingTop(),
+ mItemPaddingRight,
+ mAccountFilterHeaderContainer.getPaddingBottom());
+ return mAccountFilterHeaderContainer;
+ } else {
+ final int localPosition = position - contactTileAdapterCount - 1;
+ final ContactListItemView itemView = (ContactListItemView)
+ mContactEntryListAdapter.getView(localPosition, convertView, null);
+ // We cannot simply use setPadding() because of ContactListItemView's restriction.
+ // See comments for setExtraPaddingPadding().
+ itemView.setExtraPadding(mItemPaddingLeft, mItemPaddingRight);
+ itemView.setSelectionBoundsHorizontalMargin(mItemPaddingLeft, mItemPaddingRight);
+ return itemView;
+ }
+ }
+
+ @Override
+ public boolean areAllItemsEnabled() {
+ return (mContactTileAdapter.areAllItemsEnabled()
+ && mAccountFilterHeaderContainer.isEnabled()
+ && mContactEntryListAdapter.areAllItemsEnabled());
+ }
+
+ @Override
+ public boolean isEnabled(int position) {
+ final int contactTileAdapterCount = mContactTileAdapter.getCount();
+ final int contactEntryListAdapterCount = mContactEntryListAdapter.getCount();
+ if (position < contactTileAdapterCount) {
+ return mContactTileAdapter.isEnabled(position);
+ } else if (position == contactTileAdapterCount) {
+ return mAccountFilterHeaderContainer.isEnabled();
+ } else {
+ final int localPosition = position - contactTileAdapterCount + 1;
+ return mContactEntryListAdapter.isEnabled(localPosition);
+ }
+ }
+
+ @Override
+ public int getPositionForSection(int sectionIndex) {
+ final int contactTileAdapterCount = mContactTileAdapter.getCount();
+ if (sectionIndex == 0) { // "starred" section
+ return 0;
+ } else if (sectionIndex == 1) { // "frequent" section
+ return mContactTileAdapter.getFrequentHeaderPosition();
+ } else {
+ final int localSection = sectionIndex - 2;
+ final int localPosition = mContactEntryListAdapter.getPositionForSection(localSection);
+ return contactTileAdapterCount + 1 + localPosition;
+ }
+ }
+
+ @Override
+ public int getSectionForPosition(int position) {
+ final int contactTileAdapterCount = mContactTileAdapter.getCount();
+ if (position < contactTileAdapterCount) {
+ return 0;
+ } else if (position == contactTileAdapterCount) {
+ return 1;
+ } else {
+ final int localPosition = position - contactTileAdapterCount - 1;
+ final int localSection = mContactEntryListAdapter.getSectionForPosition(localPosition);
+ return localSection + 2;
+ }
+ }
+
+ @Override
+ public Object[] getSections() {
+ // Copy sections from "all" contacts, and add two additional sections for "starred", and
+ // "frequent". Those new sections should not show anything as an indexer, but should
+ // let users select those sections with a vertical scroll.
+ final Object[] contactEntrySections = mContactEntryListAdapter.getSections();
+ final Object[] ret = new Object[contactEntrySections.length + 2];
+ System.arraycopy(contactEntrySections, 0, ret, 2, contactEntrySections.length);
+
+ ret[0] = SECTION_STRING_STARRED;
+ ret[1] = SECTION_STRING_FREQUENT;
+ return ret;
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/contacts/list/PhoneNumberListAdapter.java b/src/com/android/contacts/list/PhoneNumberListAdapter.java
index 5c12dd6..b16b10c 100644
--- a/src/com/android/contacts/list/PhoneNumberListAdapter.java
+++ b/src/com/android/contacts/list/PhoneNumberListAdapter.java
@@ -71,6 +71,8 @@
private int mDisplayNameColumnIndex;
private int mAlternativeDisplayNameColumnIndex;
+ private ContactListItemView.PhotoPosition mPhotoPosition;
+
public PhoneNumberListAdapter(Context context) {
super(context);
@@ -217,6 +219,7 @@
final ContactListItemView view = new ContactListItemView(context, null);
view.setUnknownNameText(mUnknownNameText);
view.setQuickContactEnabled(isQuickContactEnabled());
+ view.setPhotoPosition(mPhotoPosition);
return view;
}
@@ -315,4 +318,12 @@
getPhotoLoader().loadPhoto(view.getPhotoView(), photoId, false, false);
}
+
+ public void setPhotoPosition(ContactListItemView.PhotoPosition photoPosition) {
+ mPhotoPosition = photoPosition;
+ }
+
+ public ContactListItemView.PhotoPosition getPhotoPosition() {
+ return mPhotoPosition;
+ }
}
diff --git a/src/com/android/contacts/model/AccountType.java b/src/com/android/contacts/model/AccountType.java
index 15158dc..7871dbb 100644
--- a/src/com/android/contacts/model/AccountType.java
+++ b/src/com/android/contacts/model/AccountType.java
@@ -359,7 +359,6 @@
public boolean optional;
public boolean shortForm;
public boolean longForm;
- public boolean isFullName;
public EditField(String column, int titleRes) {
this.column = column;
@@ -391,11 +390,6 @@
return this;
}
- public EditField setIsFullName(boolean isFullName) {
- this.isFullName = isFullName;
- return this;
- }
-
public boolean isMultiLine() {
return (inputType & EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) != 0;
}
diff --git a/src/com/android/contacts/model/AccountWithDataSet.java b/src/com/android/contacts/model/AccountWithDataSet.java
index e379346..784e870 100644
--- a/src/com/android/contacts/model/AccountWithDataSet.java
+++ b/src/com/android/contacts/model/AccountWithDataSet.java
@@ -150,11 +150,13 @@
/**
* Unpack a string created by {@link #stringify}.
+ *
+ * @throws IllegalArgumentException if it's an invalid string.
*/
public static AccountWithDataSet unstringify(String s) {
final String[] array = STRINGIFY_SEPARATOR_PAT.split(s, 3);
if (array.length < 3) {
- throw new IllegalArgumentException("Invalid string");
+ throw new IllegalArgumentException("Invalid string " + s);
}
return new AccountWithDataSet(array[0], array[1],
TextUtils.isEmpty(array[2]) ? null : array[2]);
@@ -178,6 +180,8 @@
/**
* Unpack a list of {@link AccountWithDataSet} into a string.
+ *
+ * @throws IllegalArgumentException if it's an invalid string.
*/
public static List<AccountWithDataSet> unstringifyList(String s) {
final ArrayList<AccountWithDataSet> ret = Lists.newArrayList();
diff --git a/src/com/android/contacts/model/BaseAccountType.java b/src/com/android/contacts/model/BaseAccountType.java
index b599c66..a685dee 100644
--- a/src/com/android/contacts/model/BaseAccountType.java
+++ b/src/com/android/contacts/model/BaseAccountType.java
@@ -95,8 +95,7 @@
protected DataKind addDataKindStructuredName(Context context) {
DataKind kind = addKind(new DataKind(StructuredName.CONTENT_ITEM_TYPE,
- R.string.nameLabelsGroup, -1, -1, true, R.layout.structured_name_editor_view,
- android.R.style.TextAppearance_Medium));
+ R.string.nameLabelsGroup, -1, true, R.layout.structured_name_editor_view));
kind.actionHeader = new SimpleInflater(R.string.nameLabelsGroup);
kind.actionBody = new SimpleInflater(Nickname.NAME);
@@ -125,14 +124,13 @@
protected DataKind addDataKindDisplayName(Context context) {
DataKind kind = addKind(new DataKind(DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME,
- R.string.nameLabelsGroup, -1, -1, true, R.layout.text_fields_editor_view,
- android.R.style.TextAppearance_Medium));
+ R.string.nameLabelsGroup, -1, true, R.layout.text_fields_editor_view));
kind.actionHeader = new SimpleInflater(R.string.nameLabelsGroup);
kind.actionBody = new SimpleInflater(Nickname.NAME);
kind.fieldList = Lists.newArrayList();
kind.fieldList.add(new EditField(StructuredName.DISPLAY_NAME,
- R.string.full_name, FLAGS_PERSON_NAME).setShortForm(true).setIsFullName(true));
+ R.string.full_name, FLAGS_PERSON_NAME).setShortForm(true));
boolean displayOrderPrimary =
context.getResources().getBoolean(R.bool.config_editor_field_order_primary);
@@ -166,8 +164,7 @@
protected DataKind addDataKindPhoneticName(Context context) {
DataKind kind = addKind(new DataKind(DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME,
- R.string.name_phonetic, -1, -1, true, R.layout.phonetic_name_editor_view,
- android.R.style.TextAppearance_Medium));
+ R.string.name_phonetic, -1, true, R.layout.phonetic_name_editor_view));
kind.actionHeader = new SimpleInflater(R.string.nameLabelsGroup);
kind.actionBody = new SimpleInflater(Nickname.NAME);
@@ -186,9 +183,8 @@
protected DataKind addDataKindNickname(Context context) {
DataKind kind = addKind(new DataKind(Nickname.CONTENT_ITEM_TYPE,
- R.string.nicknameLabelsGroup, -1, 115, true,
- R.layout.text_fields_editor_view, android.R.style.TextAppearance_Medium));
- kind.isList = false;
+ R.string.nicknameLabelsGroup, 115, true, R.layout.text_fields_editor_view));
+ kind.typeOverallMax = 1;
kind.actionHeader = new SimpleInflater(R.string.nicknameLabelsGroup);
kind.actionBody = new SimpleInflater(Nickname.NAME);
kind.defaultValues = new ContentValues();
@@ -203,9 +199,9 @@
protected DataKind addDataKindPhone(Context context) {
DataKind kind = addKind(new DataKind(Phone.CONTENT_ITEM_TYPE, R.string.phoneLabelsGroup,
- android.R.drawable.sym_action_call, 10, true,
- R.layout.text_fields_editor_view, android.R.style.TextAppearance_Medium));
+ 10, true, R.layout.text_fields_editor_view));
kind.iconAltRes = R.drawable.ic_text_holo_light;
+ kind.iconAltDescriptionRes = R.string.sms;
kind.actionHeader = new PhoneActionInflater();
kind.actionAltHeader = new PhoneActionAltInflater();
kind.actionBody = new SimpleInflater(Phone.NUMBER);
@@ -243,8 +239,7 @@
protected DataKind addDataKindEmail(Context context) {
DataKind kind = addKind(new DataKind(Email.CONTENT_ITEM_TYPE, R.string.emailLabelsGroup,
- R.drawable.sym_action_email_holo_light, 15, true,
- R.layout.text_fields_editor_view, android.R.style.TextAppearance_Medium));
+ 15, true, R.layout.text_fields_editor_view));
kind.actionHeader = new EmailActionInflater();
kind.actionBody = new SimpleInflater(Email.DATA);
kind.typeColumn = Email.TYPE;
@@ -264,8 +259,7 @@
protected DataKind addDataKindStructuredPostal(Context context) {
DataKind kind = addKind(new DataKind(StructuredPostal.CONTENT_ITEM_TYPE,
- R.string.postalLabelsGroup, R.drawable.sym_action_show_map_holo_light, 25,
- true, R.layout.text_fields_editor_view, android.R.style.TextAppearance_Medium));
+ R.string.postalLabelsGroup, 25, true, R.layout.text_fields_editor_view));
kind.actionHeader = new PostalActionInflater();
kind.actionBody = new SimpleInflater(StructuredPostal.FORMATTED_ADDRESS);
kind.typeColumn = StructuredPostal.TYPE;
@@ -285,9 +279,8 @@
}
protected DataKind addDataKindIm(Context context) {
- DataKind kind = addKind(new DataKind(Im.CONTENT_ITEM_TYPE, R.string.imLabelsGroup,
- R.drawable.sym_action_talk_holo_light, 20, true,
- R.layout.text_fields_editor_view, android.R.style.TextAppearance_Medium));
+ DataKind kind = addKind(new DataKind(Im.CONTENT_ITEM_TYPE, R.string.imLabelsGroup, 20, true,
+ R.layout.text_fields_editor_view));
kind.actionHeader = new ImActionInflater();
kind.actionBody = new SimpleInflater(Im.DATA);
@@ -318,11 +311,11 @@
protected DataKind addDataKindOrganization(Context context) {
DataKind kind = addKind(new DataKind(Organization.CONTENT_ITEM_TYPE,
- R.string.organizationLabelsGroup, -1, 5, true,
- R.layout.text_fields_editor_view, android.R.style.TextAppearance_Medium));
+ R.string.organizationLabelsGroup, 5, true,
+ R.layout.text_fields_editor_view));
kind.actionHeader = new SimpleInflater(Organization.COMPANY);
kind.actionBody = new SimpleInflater(Organization.TITLE);
- kind.isList = false;
+ kind.typeOverallMax = 1;
kind.fieldList = Lists.newArrayList();
kind.fieldList.add(new EditField(Organization.COMPANY, R.string.ghostData_company,
@@ -334,8 +327,7 @@
}
protected DataKind addDataKindPhoto(Context context) {
- DataKind kind = addKind(new DataKind(Photo.CONTENT_ITEM_TYPE, -1, -1, -1, true, -1,
- android.R.style.TextAppearance_Medium));
+ DataKind kind = addKind(new DataKind(Photo.CONTENT_ITEM_TYPE, -1, -1, true, -1));
kind.fieldList = Lists.newArrayList();
kind.fieldList.add(new EditField(Photo.PHOTO, -1, -1));
return kind;
@@ -343,9 +335,8 @@
protected DataKind addDataKindNote(Context context) {
DataKind kind = addKind(new DataKind(Note.CONTENT_ITEM_TYPE,
- R.string.label_notes, -1, 110, true,
- R.layout.text_fields_editor_view, android.R.style.TextAppearance_Small));
- kind.isList = false;
+ R.string.label_notes, 110, true, R.layout.text_fields_editor_view));
+ kind.typeOverallMax = 1;
kind.actionHeader = new SimpleInflater(R.string.label_notes);
kind.actionBody = new SimpleInflater(Note.NOTE);
kind.fieldList = Lists.newArrayList();
@@ -356,8 +347,7 @@
protected DataKind addDataKindWebsite(Context context) {
DataKind kind = addKind(new DataKind(Website.CONTENT_ITEM_TYPE,
- R.string.websiteLabelsGroup, R.drawable.sym_action_goto_website_holo_light, 120,
- true, R.layout.text_fields_editor_view, android.R.style.TextAppearance_Medium));
+ R.string.websiteLabelsGroup, 120, true, R.layout.text_fields_editor_view));
kind.actionHeader = new SimpleInflater(R.string.websiteLabelsGroup);
kind.actionBody = new SimpleInflater(Website.URL);
kind.defaultValues = new ContentValues();
@@ -379,10 +369,9 @@
// the android:icon attribute of the SIP-related
// intent-filters in the Phone app's manifest.
DataKind kind = addKind(new DataKind(SipAddress.CONTENT_ITEM_TYPE,
- R.string.label_sip_address, android.R.drawable.sym_action_call, 130, true,
- R.layout.text_fields_editor_view, android.R.style.TextAppearance_Medium));
+ R.string.label_sip_address, 130, true, R.layout.text_fields_editor_view));
- kind.isList = false;
+ kind.typeOverallMax = 1;
kind.actionHeader = new SimpleInflater(R.string.label_sip_address);
kind.actionBody = new SimpleInflater(SipAddress.SIP_ADDRESS);
kind.fieldList = Lists.newArrayList();
@@ -395,10 +384,9 @@
protected DataKind addDataKindGroupMembership(Context context) {
DataKind kind = getKindForMimetype(GroupMembership.CONTENT_ITEM_TYPE);
kind = addKind(new DataKind(GroupMembership.CONTENT_ITEM_TYPE,
- R.string.groupsLabel, android.R.drawable.sym_contact_card, 999, true, -1,
- android.R.style.TextAppearance_Medium));
+ R.string.groupsLabel, 999, true, -1));
- kind.isList = false;
+ kind.typeOverallMax = 1;
kind.fieldList = Lists.newArrayList();
kind.fieldList.add(new EditField(GroupMembership.GROUP_ROW_ID, -1, -1));
diff --git a/src/com/android/contacts/model/DataKind.java b/src/com/android/contacts/model/DataKind.java
index b0b3f38..70f43ab 100644
--- a/src/com/android/contacts/model/DataKind.java
+++ b/src/com/android/contacts/model/DataKind.java
@@ -42,17 +42,11 @@
public String resPackageName;
public String mimeType;
public int titleRes;
- public int iconRes;
public int iconAltRes;
+ public int iconAltDescriptionRes;
public int weight;
public boolean editable;
- /**
- * If this is true (default), the user can add and remove values.
- * If false, the editor will always show a single field (which might be empty).
- */
- public boolean isList;
-
public StringInflater actionHeader;
public StringInflater actionAltHeader;
public StringInflater actionBody;
@@ -63,7 +57,6 @@
/**
* Maximum number of values allowed in the list. -1 represents infinity.
- * If {@link DataKind#isList} is false, this value is ignored.
*/
public int typeOverallMax;
@@ -75,9 +68,6 @@
/** Layout resource id for an editor view to edit this {@link DataKind}. */
public final int editorLayoutResourceId;
- /** Text appearance resource id for the value of a field in this {@link DataKind}. */
- public final int textAppearanceResourceId;
-
/**
* If this is a date field, this specifies the format of the date when saving. The
* date includes year, month and day. If this is not a date field or the date field is not
@@ -94,19 +84,15 @@
public DataKind() {
editorLayoutResourceId = R.layout.text_fields_editor_view;
- textAppearanceResourceId = android.R.style.TextAppearance_Medium;
}
- public DataKind(String mimeType, int titleRes, int iconRes, int weight, boolean editable,
- int editorLayoutResourceId, int textAppearanceResourceId) {
+ public DataKind(String mimeType, int titleRes, int weight, boolean editable,
+ int editorLayoutResourceId) {
this.mimeType = mimeType;
this.titleRes = titleRes;
- this.iconRes = iconRes;
this.weight = weight;
this.editable = editable;
- this.isList = true;
this.typeOverallMax = -1;
this.editorLayoutResourceId = editorLayoutResourceId;
- this.textAppearanceResourceId = textAppearanceResourceId;
}
}
\ No newline at end of file
diff --git a/src/com/android/contacts/model/EntityModifier.java b/src/com/android/contacts/model/EntityModifier.java
index 8582ed8..289ca54 100644
--- a/src/com/android/contacts/model/EntityModifier.java
+++ b/src/com/android/contacts/model/EntityModifier.java
@@ -694,7 +694,7 @@
ArrayList<ValuesDelta> entries = state.getMimeEntries(mimeType);
- if (kind.isList || GroupMembership.CONTENT_ITEM_TYPE.equals(mimeType)) {
+ if ((kind.typeOverallMax != 1) || GroupMembership.CONTENT_ITEM_TYPE.equals(mimeType)) {
// Check for duplicates
boolean addEntry = true;
int count = 0;
@@ -994,10 +994,7 @@
return null;
}
- int typeOverallMax = kind.typeOverallMax;
- if (!kind.isList) {
- typeOverallMax = 1;
- }
+ final int typeOverallMax = kind.typeOverallMax;
if (typeOverallMax >= 0 && (mimeEntries.size() > typeOverallMax)) {
ArrayList<ValuesDelta> newMimeEntries = new ArrayList<ValuesDelta>(typeOverallMax);
for (int i = 0; i < typeOverallMax; i++) {
@@ -1350,7 +1347,7 @@
Log.w(TAG, "Default type isn't available for mimetype " + newDataKind.mimeType);
}
- final int typeOverallMax = newDataKind.isList ? newDataKind.typeOverallMax : 1;
+ final int typeOverallMax = newDataKind.typeOverallMax;
// key: type, value: the number of current entries.
final Map<Integer, Integer> currentEntryCount = new HashMap<Integer, Integer>();
diff --git a/src/com/android/contacts/model/ExchangeAccountType.java b/src/com/android/contacts/model/ExchangeAccountType.java
index 4a0e7a0..c109fea 100644
--- a/src/com/android/contacts/model/ExchangeAccountType.java
+++ b/src/com/android/contacts/model/ExchangeAccountType.java
@@ -64,8 +64,7 @@
@Override
protected DataKind addDataKindStructuredName(Context context) {
DataKind kind = addKind(new DataKind(StructuredName.CONTENT_ITEM_TYPE,
- R.string.nameLabelsGroup, -1, -1, true,
- R.layout.structured_name_editor_view, android.R.style.TextAppearance_Medium));
+ R.string.nameLabelsGroup, -1, true, R.layout.structured_name_editor_view));
kind.actionHeader = new SimpleInflater(R.string.nameLabelsGroup);
kind.actionBody = new SimpleInflater(Nickname.NAME);
@@ -94,8 +93,7 @@
@Override
protected DataKind addDataKindDisplayName(Context context) {
DataKind kind = addKind(new DataKind(DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME,
- R.string.nameLabelsGroup, -1, -1, true,
- R.layout.text_fields_editor_view, android.R.style.TextAppearance_Medium));
+ R.string.nameLabelsGroup, -1, true, R.layout.text_fields_editor_view));
boolean displayOrderPrimary =
context.getResources().getBoolean(R.bool.config_editor_field_order_primary);
@@ -128,8 +126,7 @@
@Override
protected DataKind addDataKindPhoneticName(Context context) {
DataKind kind = addKind(new DataKind(DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME,
- R.string.name_phonetic, -1, -1, true,
- R.layout.phonetic_name_editor_view, android.R.style.TextAppearance_Medium));
+ R.string.name_phonetic, -1, true, R.layout.phonetic_name_editor_view));
kind.actionHeader = new SimpleInflater(R.string.nameLabelsGroup);
kind.actionBody = new SimpleInflater(Nickname.NAME);
@@ -148,7 +145,7 @@
protected DataKind addDataKindNickname(Context context) {
final DataKind kind = super.addDataKindNickname(context);
- kind.isList = false;
+ kind.typeOverallMax = 1;
kind.fieldList = Lists.newArrayList();
kind.fieldList.add(new EditField(Nickname.NAME, R.string.nicknameLabelsGroup,
@@ -259,7 +256,7 @@
protected DataKind addDataKindOrganization(Context context) {
final DataKind kind = super.addDataKindOrganization(context);
- kind.isList = false;
+ kind.typeOverallMax = 1;
kind.fieldList = Lists.newArrayList();
kind.fieldList.add(new EditField(Organization.COMPANY, R.string.ghostData_company,
@@ -294,12 +291,12 @@
protected DataKind addDataKindEvent(Context context) {
DataKind kind = addKind(
- new DataKind(Event.CONTENT_ITEM_TYPE, R.string.eventLabelsGroup, -1, 150, true,
- R.layout.event_field_editor_view, android.R.style.TextAppearance_Medium));
+ new DataKind(Event.CONTENT_ITEM_TYPE, R.string.eventLabelsGroup, 150, true,
+ R.layout.event_field_editor_view));
kind.actionHeader = new EventActionInflater();
kind.actionBody = new SimpleInflater(Event.START_DATE);
- kind.isList = false;
+ kind.typeOverallMax = 1;
kind.typeColumn = Event.TYPE;
kind.typeList = Lists.newArrayList();
@@ -317,7 +314,7 @@
protected DataKind addDataKindWebsite(Context context) {
final DataKind kind = super.addDataKindWebsite(context);
- kind.isList = false;
+ kind.typeOverallMax = 1;
kind.fieldList = Lists.newArrayList();
kind.fieldList.add(new EditField(Website.URL, R.string.websiteLabelsGroup, FLAGS_WEBSITE));
diff --git a/src/com/android/contacts/model/ExternalAccountType.java b/src/com/android/contacts/model/ExternalAccountType.java
index 0518ea5..73aa773 100644
--- a/src/com/android/contacts/model/ExternalAccountType.java
+++ b/src/com/android/contacts/model/ExternalAccountType.java
@@ -121,11 +121,13 @@
iconRes = resolveExternalResId(context, mAccountTypeIconAttribute,
this.resPackageName, ATTR_ACCOUNT_ICON);
- // Bring in name and photo from fallback source, which are non-optional
- addDataKindStructuredName(context);
- addDataKindDisplayName(context);
- addDataKindPhoneticName(context);
- addDataKindPhoto(context);
+ if (!mHasEditSchema) {
+ // Bring in name and photo from fallback source, which are non-optional
+ addDataKindStructuredName(context);
+ addDataKindDisplayName(context);
+ addDataKindPhoneticName(context);
+ addDataKindPhoto(context);
+ }
// If we reach this point, the account type has been successfully initialized.
mInitSuccessful = true;
@@ -287,8 +289,6 @@
kind.mimeType = a
.getString(com.android.internal.R.styleable.ContactsDataKind_mimeType);
- kind.iconRes = a.getResourceId(
- com.android.internal.R.styleable.ContactsDataKind_icon, -1);
final String summaryColumn = a.getString(
com.android.internal.R.styleable.ContactsDataKind_summaryColumn);
diff --git a/src/com/android/contacts/model/GoogleAccountType.java b/src/com/android/contacts/model/GoogleAccountType.java
index cee43dd..094312b 100644
--- a/src/com/android/contacts/model/GoogleAccountType.java
+++ b/src/com/android/contacts/model/GoogleAccountType.java
@@ -109,8 +109,7 @@
private DataKind addDataKindRelation(Context context) {
DataKind kind = addKind(new DataKind(Relation.CONTENT_ITEM_TYPE,
- R.string.relationLabelsGroup, -1, 160, true,
- R.layout.text_fields_editor_view, android.R.style.TextAppearance_Medium));
+ R.string.relationLabelsGroup, 160, true, R.layout.text_fields_editor_view));
kind.actionHeader = new RelationActionInflater();
kind.actionBody = new SimpleInflater(Relation.NAME);
@@ -145,8 +144,7 @@
private DataKind addDataKindEvent(Context context) {
DataKind kind = addKind(new DataKind(Event.CONTENT_ITEM_TYPE,
- R.string.eventLabelsGroup, -1, 150, true,
- R.layout.event_field_editor_view, android.R.style.TextAppearance_Medium));
+ R.string.eventLabelsGroup, 150, true, R.layout.event_field_editor_view));
kind.actionHeader = new EventActionInflater();
kind.actionBody = new SimpleInflater(Event.START_DATE);
diff --git a/src/com/android/contacts/quickcontact/Action.java b/src/com/android/contacts/quickcontact/Action.java
index f6e282c..b2d869d 100644
--- a/src/com/android/contacts/quickcontact/Action.java
+++ b/src/com/android/contacts/quickcontact/Action.java
@@ -35,6 +35,9 @@
/** Returns an icon that can be clicked for the alternate action. */
public Drawable getAlternateIcon();
+ /** Returns the content description of the icon for the alternate action. */
+ public String getAlternateIconDescription();
+
/** Build an {@link Intent} that will perform this action. */
public Intent getIntent();
diff --git a/src/com/android/contacts/quickcontact/DataAction.java b/src/com/android/contacts/quickcontact/DataAction.java
index 84a34bd..266fd02 100644
--- a/src/com/android/contacts/quickcontact/DataAction.java
+++ b/src/com/android/contacts/quickcontact/DataAction.java
@@ -57,6 +57,7 @@
private CharSequence mSubtitle;
private Intent mIntent;
private Intent mAlternateIntent;
+ private int mAlternateIconDescriptionRes;
private int mAlternateIconRes;
private Uri mDataUri;
@@ -125,6 +126,7 @@
mIntent = phoneIntent;
mAlternateIntent = smsIntent;
mAlternateIconRes = kind.iconAltRes;
+ mAlternateIconDescriptionRes = kind.iconAltDescriptionRes;
} else if (hasPhone) {
mIntent = phoneIntent;
} else if (hasSms) {
@@ -198,9 +200,13 @@
if (isVideoChatCapable || isAudioChatCapable) {
mAlternateIntent = new Intent(
Intent.ACTION_SENDTO, Uri.parse("xmpp:" + data + "?call"));
- mAlternateIconRes = (isVideoChatCapable
- ? R.drawable.sym_action_videochat_holo_light
- : R.drawable.sym_action_audiochat_holo_light);
+ if (isVideoChatCapable) {
+ mAlternateIconRes = R.drawable.sym_action_videochat_holo_light;
+ mAlternateIconDescriptionRes = R.string.video_chat;
+ } else {
+ mAlternateIconRes = R.drawable.sym_action_audiochat_holo_light;
+ mAlternateIconDescriptionRes = R.string.audio_chat;
+ }
}
}
}
@@ -290,6 +296,12 @@
}
@Override
+ public String getAlternateIconDescription() {
+ if (mAlternateIconDescriptionRes == 0) return null;
+ return mContext.getResources().getString(mAlternateIconDescriptionRes);
+ }
+
+ @Override
public Intent getIntent() {
return mIntent;
}
diff --git a/src/com/android/contacts/quickcontact/QuickContactActivity.java b/src/com/android/contacts/quickcontact/QuickContactActivity.java
index fbca129..0588c7d 100644
--- a/src/com/android/contacts/quickcontact/QuickContactActivity.java
+++ b/src/com/android/contacts/quickcontact/QuickContactActivity.java
@@ -225,9 +225,6 @@
// find and prepare correct header view
mPhotoContainer = findViewById(R.id.photo_container);
setHeaderNameText(R.id.name, R.string.missing_name);
- setHeaderText(R.id.status, null);
- setHeaderText(R.id.timestamp, null);
- setHeaderImage(R.id.presence, null);
// Start background query for data, but only select photo rows when they
// directly match the super-primary PHOTO_ID.
@@ -477,16 +474,9 @@
}
if (cursor.moveToLast()) {
- // Read contact information from last data row
+ // Read contact name from last data row
final String name = cursor.getString(DataQuery.DISPLAY_NAME);
- final int presence = cursor.getInt(DataQuery.CONTACT_PRESENCE);
- final int chatCapability = cursor.getInt(DataQuery.CONTACT_CHAT_CAPABILITY);
- final Drawable statusIcon = ContactPresenceIconUtil.getChatCapabilityIcon(
- context, presence, chatCapability);
-
setHeaderNameText(R.id.name, name);
- // TODO: Bring this back once we have a design
-// setHeaderImage(R.id.presence, statusIcon);
}
if (photoView != null) {
@@ -498,13 +488,6 @@
}
}
- // TODO: Bring this back once we have a design
-// if (status.isValid()) {
-// // Update status when valid was found
-// setHeaderText(R.id.status, status.getStatus());
-// setHeaderText(R.id.timestamp, status.getTimestampLabel(context));
-// }
-
// All the mime-types to add.
final Set<String> containedTypes = new HashSet<String>(mActions.keySet());
mSortedActionMimeTypes.clear();
@@ -680,8 +663,6 @@
RawContacts.DATA_SET,
Contacts.STARRED,
Contacts.DISPLAY_NAME,
- Contacts.CONTACT_PRESENCE,
- Contacts.CONTACT_CHAT_CAPABILITY,
Data.STATUS,
Data.STATUS_RES_PACKAGE,
@@ -708,20 +689,18 @@
final int DATA_SET = 2;
final int STARRED = 3;
final int DISPLAY_NAME = 4;
- final int CONTACT_PRESENCE = 5;
- final int CONTACT_CHAT_CAPABILITY = 6;
- final int STATUS = 7;
- final int STATUS_RES_PACKAGE = 8;
- final int STATUS_ICON = 9;
- final int STATUS_LABEL = 10;
- final int STATUS_TIMESTAMP = 11;
- final int PRESENCE = 12;
- final int CHAT_CAPABILITY = 13;
+ final int STATUS = 5;
+ final int STATUS_RES_PACKAGE = 6;
+ final int STATUS_ICON = 7;
+ final int STATUS_LABEL = 8;
+ final int STATUS_TIMESTAMP = 9;
+ final int PRESENCE = 10;
+ final int CHAT_CAPABILITY = 11;
- final int RES_PACKAGE = 14;
- final int MIMETYPE = 15;
- final int IS_PRIMARY = 16;
- final int IS_SUPER_PRIMARY = 17;
+ final int RES_PACKAGE = 12;
+ final int MIMETYPE = 13;
+ final int IS_PRIMARY = 14;
+ final int IS_SUPER_PRIMARY = 15;
}
}
diff --git a/src/com/android/contacts/quickcontact/QuickContactListFragment.java b/src/com/android/contacts/quickcontact/QuickContactListFragment.java
index 0d3b644..c6187e9 100644
--- a/src/com/android/contacts/quickcontact/QuickContactListFragment.java
+++ b/src/com/android/contacts/quickcontact/QuickContactListFragment.java
@@ -101,7 +101,6 @@
R.layout.quickcontact_list_item,
parent, false);
-
// TODO: Put those findViewByIds in a container
final TextView text1 = (TextView) resultView.findViewById(
android.R.id.text1);
@@ -121,6 +120,7 @@
final boolean hasAlternateAction = action.getAlternateIntent() != null;
alternateActionDivider.setVisibility(hasAlternateAction ? View.VISIBLE : View.GONE);
alternateActionButton.setImageDrawable(action.getAlternateIcon());
+ alternateActionButton.setContentDescription(action.getAlternateIconDescription());
alternateActionButton.setVisibility(hasAlternateAction ? View.VISIBLE : View.GONE);
// Special case for phone numbers in accessibility mode
diff --git a/tests/src/com/android/contacts/EntityModifierTests.java b/tests/src/com/android/contacts/EntityModifierTests.java
index 76d3d84..d6f99ce 100644
--- a/tests/src/com/android/contacts/EntityModifierTests.java
+++ b/tests/src/com/android/contacts/EntityModifierTests.java
@@ -90,14 +90,14 @@
this.accountType = TEST_ACCOUNT_TYPE;
final DataKind nameKind = new DataKind(StructuredName.CONTENT_ITEM_TYPE,
- R.string.nameLabelsGroup, -1, -1, true, -1, -1);
+ R.string.nameLabelsGroup, -1, true, -1);
nameKind.typeOverallMax = 1;
addKind(nameKind);
// Phone allows maximum 2 home, 1 work, and unlimited other, with
// constraint of 5 numbers maximum.
final DataKind phoneKind = new DataKind(
- Phone.CONTENT_ITEM_TYPE, -1, -1, 10, true, -1, -1);
+ Phone.CONTENT_ITEM_TYPE, -1, 10, true, -1);
phoneKind.typeOverallMax = 5;
phoneKind.typeColumn = Phone.TYPE;
@@ -115,15 +115,15 @@
// Email is unlimited
final DataKind emailKind = new DataKind(
- Email.CONTENT_ITEM_TYPE, -1, -1, 10, true, -1, -1);
+ Email.CONTENT_ITEM_TYPE, -1, 10, true, -1);
emailKind.typeOverallMax = -1;
emailKind.fieldList = Lists.newArrayList();
emailKind.fieldList.add(new EditField(Email.DATA, -1, -1));
addKind(emailKind);
// IM is only one
- final DataKind imKind = new DataKind(Im.CONTENT_ITEM_TYPE, -1, -1, 10,
- true, -1, -1);
+ final DataKind imKind = new DataKind(Im.CONTENT_ITEM_TYPE, -1, 10,
+ true, -1);
imKind.typeOverallMax = 1;
imKind.fieldList = Lists.newArrayList();
imKind.fieldList.add(new EditField(Im.DATA, -1, -1));
@@ -131,7 +131,7 @@
// Organization is only one
final DataKind orgKind = new DataKind(
- Organization.CONTENT_ITEM_TYPE, -1, -1, 10, true, -1, -1);
+ Organization.CONTENT_ITEM_TYPE, -1, 10, true, -1);
orgKind.typeOverallMax = 1;
orgKind.fieldList = Lists.newArrayList();
orgKind.fieldList.add(new EditField(Organization.COMPANY, -1, -1));
diff --git a/tests/src/com/android/contacts/calllog/CallLogAdapterTest.java b/tests/src/com/android/contacts/calllog/CallLogAdapterTest.java
index b159f61..909002a 100644
--- a/tests/src/com/android/contacts/calllog/CallLogAdapterTest.java
+++ b/tests/src/com/android/contacts/calllog/CallLogAdapterTest.java
@@ -53,7 +53,18 @@
public void fetchCalls() {}
};
- mAdapter = new TestCallLogAdapter(getContext(), fakeCallFetcher, TEST_COUNTRY_ISO,
+ ContactInfoHelper fakeContactInfoHelper =
+ new ContactInfoHelper(getContext(), TEST_COUNTRY_ISO) {
+ @Override
+ public ContactInfo lookupNumber(String number, String countryIso) {
+ ContactInfo info = new ContactInfo();
+ info.number = number;
+ info.formattedNumber = number;
+ return info;
+ }
+ };
+
+ mAdapter = new TestCallLogAdapter(getContext(), fakeCallFetcher, fakeContactInfoHelper,
TEST_VOICEMAIL_NUMBER);
// The cursor used in the tests to store the entries to display.
mCursor = new MatrixCursor(CallLogQuery.EXTENDED_PROJECTION);
@@ -202,8 +213,8 @@
public final List<Request> requests = Lists.newArrayList();
public TestCallLogAdapter(Context context, CallFetcher callFetcher,
- String currentCountryIso, String voicemailNumber) {
- super(context, callFetcher, currentCountryIso, voicemailNumber);
+ ContactInfoHelper contactInfoHelper, String voicemailNumber) {
+ super(context, callFetcher, contactInfoHelper, voicemailNumber);
}
@Override
diff --git a/tests/src/com/android/contacts/editor/ContactEditorUtilsTest.java b/tests/src/com/android/contacts/editor/ContactEditorUtilsTest.java
index 9f4e487..b2cb39c 100644
--- a/tests/src/com/android/contacts/editor/ContactEditorUtilsTest.java
+++ b/tests/src/com/android/contacts/editor/ContactEditorUtilsTest.java
@@ -132,7 +132,6 @@
Sets.newHashSet(mAccountTypes.mAccounts),
toSet(mTarget.getSavedAccounts()));
-
// 1 account
mAccountTypes.mAccounts = new AccountWithDataSet[]{ACCOUNT_1_A};
mTarget.saveDefaultAndAllAccounts(ACCOUNT_1_A);
@@ -141,13 +140,19 @@
Sets.newHashSet(mAccountTypes.mAccounts),
toSet(mTarget.getSavedAccounts()));
- // 2 account
+ // 2 accounts
mAccountTypes.mAccounts = new AccountWithDataSet[]{ACCOUNT_1_A, ACCOUNT_1_B};
mTarget.saveDefaultAndAllAccounts(ACCOUNT_1_B);
assertEquals(ACCOUNT_1_B, mTarget.getDefaultAccount());
MoreAsserts.assertEquals(
Sets.newHashSet(mAccountTypes.mAccounts),
toSet(mTarget.getSavedAccounts()));
+
+ // 2 accounts, and save null as the default. Even though there are accounts, the saved
+ // account list should be empty in this case.
+ mTarget.saveDefaultAndAllAccounts(null);
+ assertNull(mTarget.getDefaultAccount());
+ assertEquals(0, mTarget.getSavedAccounts().size());
}
public void testIsAccountValid() {
@@ -272,6 +277,21 @@
assertFalse(mTarget.shouldShowAccountChangedNotification());
}
+ public void testShouldShowAccountChangedNotification_sanity_check() {
+ // Prepare 1 account and save it as the default.
+ setAccountTypes(TYPE1);
+ setAccounts(ACCOUNT_1_A);
+
+ mTarget.saveDefaultAndAllAccounts(ACCOUNT_1_A);
+
+ // Right after a save, the dialog shouldn't show up.
+ assertFalse(mTarget.shouldShowAccountChangedNotification());
+
+ // Remove the default account to emulate broken preferences.
+ mTarget.removeDefaultAccountForTest();
+ assertTrue(mTarget.shouldShowAccountChangedNotification());
+ }
+
private static <T> Set<T> toSet(Collection<T> collection) {
Set<T> ret = Sets.newHashSet();
ret.addAll(collection);