Merge "Improve accessibility of call log's call button."
diff --git a/res/drawable-hdpi/ic_menu_overflow.png b/res/drawable-hdpi/ic_menu_overflow.png
index b028095..a12aedf 100644
--- a/res/drawable-hdpi/ic_menu_overflow.png
+++ b/res/drawable-hdpi/ic_menu_overflow.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_menu_overflow.png b/res/drawable-mdpi/ic_menu_overflow.png
index 74dd41a..4a3bde3 100644
--- a/res/drawable-mdpi/ic_menu_overflow.png
+++ b/res/drawable-mdpi/ic_menu_overflow.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_menu_overflow.png b/res/drawable-xhdpi/ic_menu_overflow.png
index c88c4a4..715cff8 100644
--- a/res/drawable-xhdpi/ic_menu_overflow.png
+++ b/res/drawable-xhdpi/ic_menu_overflow.png
Binary files differ
diff --git a/res/layout-sw580dp/group_source_button.xml b/res/layout-sw580dp/group_source_button.xml
index a058990..e0fe4a9 100644
--- a/res/layout-sw580dp/group_source_button.xml
+++ b/res/layout-sw580dp/group_source_button.xml
@@ -29,12 +29,12 @@
android:padding="10dip" >
<TextView
+ android:id="@android:id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:duplicateParentState="true"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:text="@string/view_updates_from_group"/>
+ android:textAppearance="?android:attr/textAppearanceMedium"/>
<ImageView
android:id="@android:id/icon"
diff --git a/res/layout-w470dp/group_source_button.xml b/res/layout-w470dp/group_source_button.xml
index 1acd510..af62c2c 100644
--- a/res/layout-w470dp/group_source_button.xml
+++ b/res/layout-w470dp/group_source_button.xml
@@ -34,12 +34,12 @@
android:orientation="horizontal">
<TextView
+ android:id="@android:id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="@color/action_bar_button_text_color"
- android:text="@string/view_updates_from_group"
style="@android:style/Widget.Holo.ActionBar.TabText"/>
<ImageView
diff --git a/res/layout/editor_account_header.xml b/res/layout/editor_account_header.xml
index 6dd55fd..c255209 100644
--- a/res/layout/editor_account_header.xml
+++ b/res/layout/editor_account_header.xml
@@ -26,7 +26,7 @@
android:paddingBottom="8dip"
android:gravity="center_vertical"
android:paddingLeft="@dimen/account_container_left_padding"
- android:paddingRight="32dip">
+ android:paddingRight="28dip">
<LinearLayout
android:id="@+id/account"
diff --git a/res/layout/editor_account_header_with_dropdown.xml b/res/layout/editor_account_header_with_dropdown.xml
index 12c2a84..311a783 100644
--- a/res/layout/editor_account_header_with_dropdown.xml
+++ b/res/layout/editor_account_header_with_dropdown.xml
@@ -24,7 +24,7 @@
android:orientation="horizontal"
android:gravity="center_vertical"
android:paddingLeft="@dimen/account_container_left_padding"
- android:paddingRight="32dip">
+ android:paddingRight="28dip">
<LinearLayout
android:id="@+id/account"
diff --git a/res/layout/group_source_button.xml b/res/layout/group_source_button.xml
index 49aa2db..8d09033 100644
--- a/res/layout/group_source_button.xml
+++ b/res/layout/group_source_button.xml
@@ -30,6 +30,7 @@
android:paddingRight="16dip" >
<TextView
+ android:id="@android:id/title"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1"
@@ -37,7 +38,6 @@
android:duplicateParentState="true"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="@color/action_bar_button_text_color"
- android:text="@string/view_updates_from_group"
style="@android:style/Widget.Holo.ActionBar.TabText"/>
<FrameLayout
diff --git a/res/values/strings.xml b/res/values/strings.xml
index fde7b87..f4b94af 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1579,7 +1579,7 @@
<!-- Label to instruct the user to type in a contact's name to add the contact as a member of the current group. [CHAR LIMIT=64] -->
<string name="enter_contact_name">Type person\'s name</string>
- <!-- Button to view the updates from the current group on the group detail page [CHAR LIMIT=20] -->
+ <!-- Button to view the updates from the current group on the group detail page [CHAR LIMIT=25] -->
<string name="view_updates_from_group">View updates</string>
<!-- Title of the notification of new voicemails. [CHAR LIMIT=30] -->
diff --git a/src/com/android/contacts/ContactSaveService.java b/src/com/android/contacts/ContactSaveService.java
index 2697589..be84cc4 100644
--- a/src/com/android/contacts/ContactSaveService.java
+++ b/src/com/android/contacts/ContactSaveService.java
@@ -79,6 +79,7 @@
public static final String EXTRA_CONTACT_STATE = "state";
public static final String EXTRA_SAVE_MODE = "saveMode";
public static final String EXTRA_SAVE_IS_PROFILE = "saveIsProfile";
+ public static final String EXTRA_SAVE_SUCCEEDED = "saveSucceeded";
public static final String ACTION_CREATE_GROUP = "createGroup";
public static final String ACTION_RENAME_GROUP = "renameGroup";
@@ -345,6 +346,10 @@
lookupUri = RawContacts.getContactLookupUri(resolver, rawContactUri);
}
Log.v(TAG, "Saved contact. New URI: " + lookupUri);
+ // Mark the intent to indicate that the save was successful (even if the lookup URI
+ // is now null). For local contacts or the local profile, it's possible that the
+ // save triggered removal of the contact, so no lookup URI would exist..
+ callbackIntent.putExtra(EXTRA_SAVE_SUCCEEDED, true);
break;
} catch (RemoteException e) {
diff --git a/src/com/android/contacts/activities/ContactEditorActivity.java b/src/com/android/contacts/activities/ContactEditorActivity.java
index abdde87..07f340e 100644
--- a/src/com/android/contacts/activities/ContactEditorActivity.java
+++ b/src/com/android/contacts/activities/ContactEditorActivity.java
@@ -16,6 +16,7 @@
package com.android.contacts.activities;
+import com.android.contacts.ContactSaveService;
import com.android.contacts.ContactsActivity;
import com.android.contacts.R;
import com.android.contacts.editor.ContactEditorFragment;
@@ -115,6 +116,7 @@
} else if (ACTION_SAVE_COMPLETED.equals(action)) {
mFragment.onSaveCompleted(true,
intent.getIntExtra(ContactEditorFragment.SAVE_MODE_EXTRA_KEY, SaveMode.CLOSE),
+ intent.getBooleanExtra(ContactSaveService.EXTRA_SAVE_SUCCEEDED, false),
intent.getData());
} else if (ACTION_JOIN_COMPLETED.equals(action)) {
mFragment.onJoinCompleted(intent.getData());
diff --git a/src/com/android/contacts/activities/DialtactsActivity.java b/src/com/android/contacts/activities/DialtactsActivity.java
index 50d6f17..baa4b4b 100644
--- a/src/com/android/contacts/activities/DialtactsActivity.java
+++ b/src/com/android/contacts/activities/DialtactsActivity.java
@@ -84,6 +84,13 @@
"com.android.phone.CallFeaturesSetting";
/**
+ * Copied from PhoneApp. See comments in Phone app for more detail.
+ */
+ public static final String EXTRA_CALL_ORIGIN = "com.android.phone.CALL_ORIGIN";
+ public static final String CALL_ORIGIN_DIALTACTS =
+ "com.android.contacts.activities.DialtactsActivity";
+
+ /**
* Just for backward compatibility. Should behave as same as {@link Intent#ACTION_DIAL}.
*/
private static final String ACTION_TOUCH_DIALER = "com.android.phone.action.TOUCH_DIALER";
@@ -697,7 +704,8 @@
@Override
public void onContactSelected(Uri contactUri) {
PhoneNumberInteraction.startInteractionForPhoneCall(
- DialtactsActivity.this, contactUri);
+ DialtactsActivity.this, contactUri,
+ CALL_ORIGIN_DIALTACTS);
}
};
diff --git a/src/com/android/contacts/detail/ContactDetailDisplayUtils.java b/src/com/android/contacts/detail/ContactDetailDisplayUtils.java
index ce5bf8d..b81cebf 100644
--- a/src/com/android/contacts/detail/ContactDetailDisplayUtils.java
+++ b/src/com/android/contacts/detail/ContactDetailDisplayUtils.java
@@ -350,15 +350,16 @@
R.id.stream_item_attribution);
TextView commentsView = (TextView) rootView.findViewById(R.id.stream_item_comments);
ImageGetter imageGetter = new DefaultImageGetter(context.getPackageManager());
- htmlView.setText(HtmlUtils.fromHtml(context, streamItem.getText(), imageGetter, null));
- attributionView.setText(ContactBadgeUtil.getSocialDate(streamItem, context));
- if (streamItem.getComments() != null) {
- commentsView.setText(HtmlUtils.fromHtml(context, streamItem.getComments(), imageGetter,
- null));
- commentsView.setVisibility(View.VISIBLE);
- } else {
- commentsView.setVisibility(View.GONE);
- }
+
+ // Stream item text
+ setDataOrHideIfNone(HtmlUtils.fromHtml(context, streamItem.getText(), imageGetter, null),
+ htmlView);
+ // Attribution
+ setDataOrHideIfNone(ContactBadgeUtil.getSocialDate(streamItem, context),
+ attributionView);
+ // Comments
+ setDataOrHideIfNone(HtmlUtils.fromHtml(context, streamItem.getComments(), imageGetter,
+ null), commentsView);
return rootView;
}
diff --git a/src/com/android/contacts/dialpad/DialpadFragment.java b/src/com/android/contacts/dialpad/DialpadFragment.java
index e9fbbbb..d1c5868 100644
--- a/src/com/android/contacts/dialpad/DialpadFragment.java
+++ b/src/com/android/contacts/dialpad/DialpadFragment.java
@@ -899,7 +899,12 @@
// Clear the digits just in case.
mDigits.getText().clear();
} else {
- startActivity(newDialNumberIntent(number));
+ final Intent intent = newDialNumberIntent(number);
+ if (getActivity() instanceof DialtactsActivity) {
+ intent.putExtra(DialtactsActivity.EXTRA_CALL_ORIGIN,
+ DialtactsActivity.CALL_ORIGIN_DIALTACTS);
+ }
+ startActivity(intent);
mDigits.getText().clear(); // TODO: Fix bug 1745781
getActivity().finish();
}
diff --git a/src/com/android/contacts/editor/ContactEditorFragment.java b/src/com/android/contacts/editor/ContactEditorFragment.java
index b341f83..f7e0c23 100644
--- a/src/com/android/contacts/editor/ContactEditorFragment.java
+++ b/src/com/android/contacts/editor/ContactEditorFragment.java
@@ -398,7 +398,7 @@
mAutoAddToDefaultGroup = mIntentExtras != null
&& mIntentExtras.containsKey(INTENT_EXTRA_ADD_TO_DEFAULT_DIRECTORY);
mNewLocalProfile = mIntentExtras != null
- && mIntentExtras.getBoolean(INTENT_EXTRA_NEW_LOCAL_PROFILE);
+ && mIntentExtras.getBoolean(INTENT_EXTRA_NEW_LOCAL_PROFILE);
}
public void setListener(Listener value) {
@@ -1033,7 +1033,7 @@
final AccountTypeManager accountTypes = AccountTypeManager.getInstance(mContext);
if (!EntityModifier.hasChanges(mState, accountTypes)) {
- onSaveCompleted(false, saveMode, mLookupUri);
+ onSaveCompleted(false, saveMode, mLookupUri != null, mLookupUri);
return true;
}
@@ -1099,14 +1099,14 @@
}
public void onJoinCompleted(Uri uri) {
- onSaveCompleted(false, SaveMode.RELOAD, uri);
+ onSaveCompleted(false, SaveMode.RELOAD, uri != null, uri);
}
- public void onSaveCompleted(boolean hadChanges, int saveMode, Uri contactLookupUri) {
- boolean success = contactLookupUri != null;
+ public void onSaveCompleted(boolean hadChanges, int saveMode, boolean saveSucceeded,
+ Uri contactLookupUri) {
Log.d(TAG, "onSaveCompleted(" + saveMode + ", " + contactLookupUri);
if (hadChanges) {
- if (success) {
+ if (saveSucceeded) {
if (saveMode != SaveMode.JOIN) {
Toast.makeText(mContext, R.string.contactSavedToast, Toast.LENGTH_SHORT).show();
}
@@ -1118,7 +1118,7 @@
case SaveMode.CLOSE:
case SaveMode.HOME:
final Intent resultIntent;
- if (success && contactLookupUri != null) {
+ if (saveSucceeded && contactLookupUri != null) {
final String requestAuthority =
mLookupUri == null ? null : mLookupUri.getAuthority();
@@ -1149,7 +1149,7 @@
case SaveMode.RELOAD:
case SaveMode.JOIN:
- if (success && contactLookupUri != null) {
+ if (saveSucceeded && contactLookupUri != null) {
// If it was a JOIN, we are now ready to bring up the join activity.
if (saveMode == SaveMode.JOIN) {
showJoinAggregateActivity(contactLookupUri);
diff --git a/src/com/android/contacts/group/GroupDetailDisplayUtils.java b/src/com/android/contacts/group/GroupDetailDisplayUtils.java
index bb4cd5c..da5e0e9 100644
--- a/src/com/android/contacts/group/GroupDetailDisplayUtils.java
+++ b/src/com/android/contacts/group/GroupDetailDisplayUtils.java
@@ -24,6 +24,7 @@
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
+import android.widget.TextView;
public class GroupDetailDisplayUtils {
@@ -39,13 +40,21 @@
public static void bindGroupSourceView(Context context, View view, String accountTypeString,
String dataSet) {
- ImageView accountIcon = (ImageView) view.findViewById(android.R.id.icon);
- if (accountIcon == null) {
- throw new IllegalStateException("Group source view must contain view with id"
- + "android.R.id.icon");
- }
AccountTypeManager accountTypeManager = AccountTypeManager.getInstance(context);
AccountType accountType = accountTypeManager.getAccountType(accountTypeString, dataSet);
+
+ TextView label = (TextView) view.findViewById(android.R.id.title);
+ if (label == null) {
+ throw new IllegalStateException("Group source view must contain a TextView with id"
+ + "android.R.id.label");
+ }
+ label.setText(accountType.getViewGroupLabel(context));
+
+ ImageView accountIcon = (ImageView) view.findViewById(android.R.id.icon);
+ if (accountIcon == null) {
+ throw new IllegalStateException("Group source view must contain an ImageView with id"
+ + "android.R.id.icon");
+ }
accountIcon.setImageDrawable(accountType.getDisplayIcon(context));
}
}
\ No newline at end of file
diff --git a/src/com/android/contacts/interactions/PhoneNumberInteraction.java b/src/com/android/contacts/interactions/PhoneNumberInteraction.java
index a42456c..d10ec06 100644
--- a/src/com/android/contacts/interactions/PhoneNumberInteraction.java
+++ b/src/com/android/contacts/interactions/PhoneNumberInteraction.java
@@ -21,14 +21,11 @@
import com.android.contacts.ContactSaveService;
import com.android.contacts.ContactsUtils;
import com.android.contacts.R;
+import com.android.contacts.activities.DialtactsActivity;
import com.android.contacts.model.AccountType;
import com.android.contacts.model.AccountType.StringInflater;
import com.android.contacts.model.AccountTypeManager;
import com.android.contacts.model.DataKind;
-import com.android.i18n.phonenumbers.NumberParseException;
-import com.android.i18n.phonenumbers.PhoneNumberUtil;
-import com.android.i18n.phonenumbers.PhoneNumberUtil.MatchType;
-import com.android.i18n.phonenumbers.Phonenumber.PhoneNumber;
import com.google.common.annotations.VisibleForTesting;
import android.app.Activity;
@@ -53,7 +50,6 @@
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.RawContacts;
-import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -197,17 +193,21 @@
private static final String ARG_PHONE_LIST = "phoneList";
private static final String ARG_INTERACTION_TYPE = "interactionType";
+ private static final String ARG_CALL_ORIGIN = "callOrigin";
private InteractionType mInteractionType;
private ListAdapter mPhonesAdapter;
private List<PhoneItem> mPhoneList;
+ private String mCallOrigin;
public static void show(FragmentManager fragmentManager,
- ArrayList<PhoneItem> phoneList, InteractionType interactionType) {
+ ArrayList<PhoneItem> phoneList, InteractionType interactionType,
+ String callOrigin) {
PhoneDisambiguationDialogFragment fragment = new PhoneDisambiguationDialogFragment();
Bundle bundle = new Bundle();
bundle.putParcelableArrayList(ARG_PHONE_LIST, phoneList);
bundle.putSerializable(ARG_INTERACTION_TYPE, interactionType);
+ bundle.putString(ARG_CALL_ORIGIN, callOrigin);
fragment.setArguments(bundle);
fragment.show(fragmentManager, TAG);
}
@@ -218,6 +218,8 @@
mPhoneList = getArguments().getParcelableArrayList(ARG_PHONE_LIST);
mInteractionType =
(InteractionType) getArguments().getSerializable(ARG_INTERACTION_TYPE);
+ mCallOrigin = getArguments().getString(ARG_CALL_ORIGIN);
+
mPhonesAdapter = new PhoneItemAdapter(activity, mPhoneList, mInteractionType);
final LayoutInflater inflater = activity.getLayoutInflater();
final View setPrimaryView = inflater.inflate(R.layout.set_primary_checkbox, null);
@@ -242,7 +244,7 @@
}
PhoneNumberInteraction.performAction(getActivity(), phoneItem.phoneNumber,
- mInteractionType);
+ mInteractionType, mCallOrigin);
} else {
dialog.dismiss();
}
@@ -266,22 +268,31 @@
private final OnDismissListener mDismissListener;
private final InteractionType mInteractionType;
+ private final String mCallOrigin;
+
private CursorLoader mLoader;
@VisibleForTesting
/* package */ PhoneNumberInteraction(Context context, InteractionType interactionType,
DialogInterface.OnDismissListener dismissListener) {
+ this(context, interactionType, dismissListener, null);
+ }
+
+ private PhoneNumberInteraction(Context context, InteractionType interactionType,
+ DialogInterface.OnDismissListener dismissListener, String callOrigin) {
mContext = context;
mInteractionType = interactionType;
mDismissListener = dismissListener;
+ mCallOrigin = callOrigin;
}
private void performAction(String phoneNumber) {
- PhoneNumberInteraction.performAction(mContext, phoneNumber, mInteractionType);
+ PhoneNumberInteraction.performAction(mContext, phoneNumber, mInteractionType, mCallOrigin);
}
private static void performAction(
- Context context, String phoneNumber, InteractionType interactionType) {
+ Context context, String phoneNumber, InteractionType interactionType,
+ String callOrigin) {
Intent intent;
switch (interactionType) {
case SMS:
@@ -291,6 +302,9 @@
default:
intent = new Intent(
Intent.ACTION_CALL_PRIVILEGED, Uri.fromParts("tel", phoneNumber, null));
+ if (callOrigin != null) {
+ intent.putExtra(DialtactsActivity.EXTRA_CALL_ORIGIN, callOrigin);
+ }
break;
}
context.startActivity(intent);
@@ -402,6 +416,17 @@
}
/**
+ * @param callOrigin If non null, {@link DialtactsActivity#EXTRA_CALL_ORIGIN} will be
+ * appended to the Intent initiating phone call. See comments in Phone package (PhoneApp)
+ * for more detail.
+ */
+ public static void startInteractionForPhoneCall(Activity activity, Uri uri,
+ String callOrigin) {
+ (new PhoneNumberInteraction(activity, InteractionType.PHONE_CALL, null, callOrigin))
+ .startInteraction(uri);
+ }
+
+ /**
* Start text messaging (a.k.a SMS) action using given contact Uri. If there are multiple
* candidates for the phone call, dialog is automatically shown and the user is asked to choose
* one.
@@ -422,6 +447,6 @@
@VisibleForTesting
/* package */ void showDisambiguationDialog(ArrayList<PhoneItem> phoneList) {
PhoneDisambiguationDialogFragment.show(((Activity)mContext).getFragmentManager(),
- phoneList, mInteractionType);
+ phoneList, mInteractionType, mCallOrigin);
}
}
diff --git a/src/com/android/contacts/list/DefaultContactBrowseListFragment.java b/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
index 608d3ad..30c3c48 100644
--- a/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
+++ b/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
@@ -49,7 +49,7 @@
private View mProfileHeader;
private Button mProfileMessage;
private FrameLayout mMessageContainer;
- private View mProfileTitle;
+ private TextView mProfileTitle;
private View mPaddingView;
@@ -273,7 +273,8 @@
mProfileHeaderContainer = new FrameLayout(inflater.getContext());
mProfileHeader = inflater.inflate(R.layout.user_profile_header, null, false);
mCounterHeaderView = (TextView) mProfileHeader.findViewById(R.id.contacts_count);
- mProfileTitle = mProfileHeader.findViewById(R.id.profile_title);
+ mProfileTitle = (TextView) mProfileHeader.findViewById(R.id.profile_title);
+ mProfileTitle.setAllCaps(true);
mProfileHeaderContainer.addView(mProfileHeader);
list.addHeaderView(mProfileHeaderContainer, null, false);
diff --git a/src/com/android/contacts/model/AccountType.java b/src/com/android/contacts/model/AccountType.java
index 21e17bd..15158dc 100644
--- a/src/com/android/contacts/model/AccountType.java
+++ b/src/com/android/contacts/model/AccountType.java
@@ -16,6 +16,7 @@
package com.android.contacts.model;
+import com.android.contacts.R;
import com.google.android.collect.Lists;
import com.google.android.collect.Maps;
import com.google.common.annotations.VisibleForTesting;
@@ -148,7 +149,14 @@
/**
* @return resource ID for the "invite contact" action label, or -1 if not defined.
*/
- protected int getInviteContactActionResId(Context context) {
+ protected int getInviteContactActionResId() {
+ return -1;
+ }
+
+ /**
+ * @return resource ID for the "view group" label, or -1 if not defined.
+ */
+ protected int getViewGroupLabelResId() {
return -1;
}
@@ -174,8 +182,20 @@
* the contact card. (If not defined, returns null.)
*/
public CharSequence getInviteContactActionLabel(Context context) {
- return getResourceText(context, summaryResPackageName, getInviteContactActionResId(context),
- "");
+ return getResourceText(context, summaryResPackageName, getInviteContactActionResId(), "");
+ }
+
+ /**
+ * Returns a label for the "view group" action. If not defined, this falls back to our
+ * own "View Updates" string
+ */
+ public CharSequence getViewGroupLabel(Context context) {
+ final CharSequence customTitle =
+ getResourceText(context, summaryResPackageName, getViewGroupLabelResId(), null);
+
+ return customTitle == null
+ ? context.getText(R.string.view_updates_from_group)
+ : customTitle;
}
/**
diff --git a/src/com/android/contacts/model/ExternalAccountType.java b/src/com/android/contacts/model/ExternalAccountType.java
index ca064c7..0518ea5 100644
--- a/src/com/android/contacts/model/ExternalAccountType.java
+++ b/src/com/android/contacts/model/ExternalAccountType.java
@@ -57,6 +57,7 @@
private static final String ATTR_INVITE_CONTACT_ACTION_LABEL = "inviteContactActionLabel";
private static final String ATTR_VIEW_CONTACT_NOTIFY_SERVICE = "viewContactNotifyService";
private static final String ATTR_VIEW_GROUP_ACTIVITY = "viewGroupActivity";
+ private static final String ATTR_VIEW_GROUP_ACTION_LABEL = "viewGroupActionLabel";
private static final String ATTR_VIEW_STREAM_ITEM_ACTIVITY = "viewStreamItemActivity";
private static final String ATTR_VIEW_STREAM_ITEM_PHOTO_ACTIVITY =
"viewStreamItemPhotoActivity";
@@ -75,12 +76,14 @@
private String mCreateContactActivityClassName;
private String mInviteContactActivity;
private String mInviteActionLabelAttribute;
+ private int mInviteActionLabelResId;
private String mViewContactNotifyService;
private String mViewGroupActivity;
+ private String mViewGroupLabelAttribute;
+ private int mViewGroupLabelResId;
private String mViewStreamItemActivity;
private String mViewStreamItemPhotoActivity;
private List<String> mExtensionPackageNames;
- private int mInviteActionLabelResId;
private String mAccountTypeLabelAttribute;
private String mAccountTypeIconAttribute;
private boolean mInitSuccessful;
@@ -111,6 +114,8 @@
mExtensionPackageNames = new ArrayList<String>();
mInviteActionLabelResId = resolveExternalResId(context, mInviteActionLabelAttribute,
summaryResPackageName, ATTR_INVITE_CONTACT_ACTION_LABEL);
+ mViewGroupLabelResId = resolveExternalResId(context, mViewGroupLabelAttribute,
+ summaryResPackageName, ATTR_VIEW_GROUP_ACTION_LABEL);
titleRes = resolveExternalResId(context, mAccountTypeLabelAttribute,
this.resPackageName, ATTR_ACCOUNT_LABEL);
iconRes = resolveExternalResId(context, mAccountTypeIconAttribute,
@@ -167,7 +172,7 @@
}
@Override
- protected int getInviteContactActionResId(Context context) {
+ protected int getInviteContactActionResId() {
return mInviteActionLabelResId;
}
@@ -182,6 +187,11 @@
}
@Override
+ protected int getViewGroupLabelResId() {
+ return mViewGroupLabelResId;
+ }
+
+ @Override
public String getViewStreamItemActivity() {
return mViewStreamItemActivity;
}
@@ -242,6 +252,8 @@
mViewContactNotifyService = value;
} else if (ATTR_VIEW_GROUP_ACTIVITY.equals(attr)) {
mViewGroupActivity = value;
+ } else if (ATTR_VIEW_GROUP_ACTION_LABEL.equals(attr)) {
+ mViewGroupLabelAttribute = value;
} else if (ATTR_VIEW_STREAM_ITEM_ACTIVITY.equals(attr)) {
mViewStreamItemActivity = value;
} else if (ATTR_VIEW_STREAM_ITEM_PHOTO_ACTIVITY.equals(attr)) {
diff --git a/src/com/android/contacts/util/HtmlUtils.java b/src/com/android/contacts/util/HtmlUtils.java
index faaa9c2..c89e8c2 100644
--- a/src/com/android/contacts/util/HtmlUtils.java
+++ b/src/com/android/contacts/util/HtmlUtils.java
@@ -1,5 +1,8 @@
package com.android.contacts.util;
+import com.android.contacts.R;
+import com.google.common.annotations.VisibleForTesting;
+
import android.content.Context;
import android.content.res.Resources;
import android.text.Html;
@@ -11,8 +14,6 @@
import android.text.style.ImageSpan;
import android.text.style.QuoteSpan;
-import com.android.contacts.R;
-
/**
* Provides static functions to perform custom HTML to text conversions.
* Specifically, it adjusts the color and padding of the vertical
@@ -21,43 +22,53 @@
public class HtmlUtils {
/**
- * Converts HTML string to a {@link Spanned} text, adjusting formatting.
+ * Converts HTML string to a {@link Spanned} text, adjusting formatting. Any extra new line
+ * characters at the end of the text will be trimmed.
*/
public static Spanned fromHtml(Context context, String text) {
if (TextUtils.isEmpty(text)) {
return null;
}
Spanned spanned = Html.fromHtml(text);
- postprocess(context, spanned);
- return spanned;
+ return postprocess(context, spanned);
}
/**
* Converts HTML string to a {@link Spanned} text, adjusting formatting and using a custom
- * image getter.
+ * image getter. Any extra new line characters at the end of the text will be trimmed.
*/
public static CharSequence fromHtml(Context context, String text, ImageGetter imageGetter,
TagHandler tagHandler) {
if (TextUtils.isEmpty(text)) {
return null;
}
- Spanned spanned = Html.fromHtml(text, imageGetter, tagHandler);
- postprocess(context, spanned);
- return spanned;
+ return postprocess(context, Html.fromHtml(text, imageGetter, tagHandler));
}
/**
- * Replaces some spans with custom versions of those.
+ * Replaces some spans with custom versions of those. Any extra new line characters at the end
+ * of the text will be trimmed.
*/
- private static void postprocess(Context context, Spanned spanned) {
- if (!(spanned instanceof SpannableStringBuilder)) {
- return;
+ @VisibleForTesting
+ static Spanned postprocess(Context context, Spanned original) {
+ if (original == null) {
+ return null;
+ }
+ final int length = original.length();
+ if (length == 0) {
+ return original; // Bail early.
}
- int length = spanned.length();
+ // If it's a SpannableStringBuilder, just use it. Otherwise, create a new
+ // SpannableStringBuilder based on the passed Spanned.
+ final SpannableStringBuilder builder;
+ if (original instanceof SpannableStringBuilder) {
+ builder = (SpannableStringBuilder) original;
+ } else {
+ builder = new SpannableStringBuilder(original);
+ }
- SpannableStringBuilder builder = (SpannableStringBuilder)spanned;
- QuoteSpan[] quoteSpans = spanned.getSpans(0, length, QuoteSpan.class);
+ final QuoteSpan[] quoteSpans = builder.getSpans(0, length, QuoteSpan.class);
if (quoteSpans != null && quoteSpans.length != 0) {
Resources resources = context.getResources();
int color = resources.getColor(R.color.stream_item_stripe_color);
@@ -67,7 +78,7 @@
}
}
- ImageSpan[] imageSpans = spanned.getSpans(0, length, ImageSpan.class);
+ final ImageSpan[] imageSpans = builder.getSpans(0, length, ImageSpan.class);
if (imageSpans != null) {
for (int i = 0; i < imageSpans.length; i++) {
ImageSpan span = imageSpans[i];
@@ -75,6 +86,25 @@
ImageSpan.ALIGN_BASELINE));
}
}
+
+ // Trim the trailing new line characters at the end of the text (which can be added
+ // when HTML block quote tags are turned into new line characters).
+ int end = length;
+ for (int i = builder.length() - 1; i >= 0; i--) {
+ if (builder.charAt(i) != '\n') {
+ break;
+ }
+ end = i;
+ }
+
+ // If there's no trailing newlines, just return it.
+ if (end == length) {
+ return builder;
+ }
+
+ // Otherwise, Return a substring of the original {@link Spanned} text
+ // from the start index (inclusive) to the end index (exclusive).
+ return new SpannableStringBuilder(builder, 0, end);
}
/**
diff --git a/tests/src/com/android/contacts/model/AccountTypeTest.java b/tests/src/com/android/contacts/model/AccountTypeTest.java
index 9f7e7a2..42fe200 100644
--- a/tests/src/com/android/contacts/model/AccountTypeTest.java
+++ b/tests/src/com/android/contacts/model/AccountTypeTest.java
@@ -69,7 +69,7 @@
resPackageName = packageName;
summaryResPackageName = packageName;
}
- @Override protected int getInviteContactActionResId(Context conext) {
+ @Override protected int getInviteContactActionResId() {
return externalResID;
}
diff --git a/tests/src/com/android/contacts/util/HtmlUtilsTest.java b/tests/src/com/android/contacts/util/HtmlUtilsTest.java
new file mode 100644
index 0000000..115f289
--- /dev/null
+++ b/tests/src/com/android/contacts/util/HtmlUtilsTest.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.contacts.util;
+
+import com.android.contacts.util.HtmlUtils.StreamItemQuoteSpan;
+
+import android.graphics.drawable.ColorDrawable;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.SpannedString;
+import android.text.style.ImageSpan;
+import android.text.style.QuoteSpan;
+
+/**
+ * Tests for {@link HtmlUtils}.
+ *
+ * adb shell am instrument -w -e class com.android.contacts.util.HtmlUtilsTest \
+ com.android.contacts.tests/android.test.InstrumentationTestRunner
+ */
+@SmallTest
+public class HtmlUtilsTest extends AndroidTestCase {
+ /**
+ * Test for {@link HtmlUtils#postprocess} specifically about trimming newlines.
+ */
+ public void testPostProcess_trimNewLines() {
+ checkTrimNewLines("", "");
+ checkTrimNewLines("", "\n");
+ checkTrimNewLines("", "\n\n");
+ checkTrimNewLines("a", "a");
+ checkTrimNewLines("abc", "abc");
+ checkTrimNewLines("abc", "abc\n");
+ checkTrimNewLines("abc", "abc\n\n\n");
+ checkTrimNewLines("ab\nc", "ab\nc\n");
+
+ assertNull(HtmlUtils.postprocess(getContext(), null));
+ }
+
+ private final void checkTrimNewLines(String expectedString, CharSequence text) {
+ // Test with both SpannedString and SpannableStringBuilder.
+ assertEquals(expectedString,
+ HtmlUtils.postprocess(getContext(), new SpannedString(text)).toString());
+
+ assertEquals(expectedString,
+ HtmlUtils.postprocess(getContext(), new SpannableStringBuilder(text)).toString());
+ }
+
+ public void testPostProcess_with_newlines() {
+ final SpannableStringBuilder builder = new SpannableStringBuilder("01234\n\n");
+
+ setSpans(builder);
+
+ // First test with a SpannableStringBuilder, as opposed to SpannedString
+ checkPostProcess(HtmlUtils.postprocess(getContext(), builder));
+
+ // Then pass a SpannedString, which is immutable, but the method should still work.
+ checkPostProcess(HtmlUtils.postprocess(getContext(), new SpannedString(builder)));
+ }
+
+ /**
+ * Same as {@link #testPostProcess_with_newlines}, but text has no newlines.
+ * (The internal code path is slightly different.)
+ */
+ public void testPostProcess_no_newlines() {
+ final SpannableStringBuilder builder = new SpannableStringBuilder("01234");
+
+ setSpans(builder);
+
+ // First test with a SpannableStringBuilder, as opposed to SpannedString
+ checkPostProcess(HtmlUtils.postprocess(getContext(), builder));
+
+ // Then pass a SpannedString, which is immutable, but the method should still work.
+ checkPostProcess(HtmlUtils.postprocess(getContext(), new SpannedString(builder)));
+ }
+
+ private void setSpans(SpannableStringBuilder builder) {
+ builder.setSpan(new ImageSpan(new ColorDrawable(), ImageSpan.ALIGN_BOTTOM), 0, 2, 0);
+ builder.setSpan(new QuoteSpan(), 2, 4, 0);
+ builder.setSpan(new CustomSpan(), 4, builder.length(), 0);
+ }
+
+ private void checkPostProcess(Spanned ret) {
+ // Newlines should be trimmed.
+ assertEquals("01234", ret.toString());
+
+ // First, check the image span.
+ // - Vertical alignment should be changed to ALIGN_BASELINE
+ // - Drawable shouldn't be changed.
+ ImageSpan[] imageSpans = ret.getSpans(0, ret.length(), ImageSpan.class);
+ assertEquals(1, imageSpans.length);
+ assertEquals(ImageSpan.ALIGN_BASELINE, imageSpans[0].getVerticalAlignment());
+ assertEquals(ColorDrawable.class, imageSpans[0].getDrawable().getClass());
+
+ // QuoteSpans should be replaced with StreamItemQuoteSpans.
+ QuoteSpan[] quoteSpans = ret.getSpans(0, ret.length(), QuoteSpan.class);
+ assertEquals(1, quoteSpans.length);
+ assertEquals(StreamItemQuoteSpan.class, quoteSpans[0].getClass());
+
+ // Other spans should be preserved.
+ CustomSpan[] customSpans = ret.getSpans(0, ret.length(), CustomSpan.class);
+ assertEquals(1, customSpans.length);
+ }
+
+ /** Custom span class used in {@link #testPostProcess} */
+ private static class CustomSpan {
+ }
+}