Merge "Allow sync adapter to rename the "View Updates" 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/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_detail_fragment.xml b/res/layout/group_detail_fragment.xml
index d95a6db..2b020c9 100644
--- a/res/layout/group_detail_fragment.xml
+++ b/res/layout/group_detail_fragment.xml
@@ -40,7 +40,8 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fadingEdge="none"
- android:scrollbarStyle="outsideOverlay"/>
+ android:scrollbarStyle="outsideOverlay"
+ android:divider="@null"/>
<!--
Shadow overlay over the list of group members (since we have a fake stacked
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 5a94324..4decc19 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -156,8 +156,12 @@
<dimen name="contact_browser_list_item_text_indent">8dip</dimen>
<dimen name="contact_browser_list_top_margin">8dip</dimen>
- <!-- ContactTile Layouts -->
- <dimen name="contact_tile_shadowbox_height">48dip</dimen>
+ <!-- ContactTile Layouts -->
+ <!--
+ Use sp instead of dip so that the shadowbox heights can all scale uniformly
+ when the font size is scaled for accessibility purposes
+ -->
+ <dimen name="contact_tile_shadowbox_height">48sp</dimen>
<!-- Call Log -->
<dimen name="call_log_call_action_size">32dip</dimen>
diff --git a/src/com/android/contacts/ContactsUtils.java b/src/com/android/contacts/ContactsUtils.java
index 4de62b6..9a3f2ef 100644
--- a/src/com/android/contacts/ContactsUtils.java
+++ b/src/com/android/contacts/ContactsUtils.java
@@ -16,6 +16,7 @@
package com.android.contacts;
+import com.android.contacts.model.AccountType;
import com.android.contacts.model.AccountTypeManager;
import com.android.contacts.model.AccountWithDataSet;
import com.android.contacts.test.NeededForTesting;
@@ -172,11 +173,15 @@
return detector.detectCountry().getCountryIso();
}
- public static boolean areAccountsAvailable(Context context) {
+ public static boolean areContactWritableAccountsAvailable(Context context) {
final List<AccountWithDataSet> accounts =
AccountTypeManager.getInstance(context).getAccounts(true /* writeable */);
return !accounts.isEmpty();
}
-
+ public static boolean areGroupWritableAccountsAvailable(Context context) {
+ final List<AccountWithDataSet> accounts =
+ AccountTypeManager.getInstance(context).getGroupWritableAccounts();
+ return !accounts.isEmpty();
+ }
}
diff --git a/src/com/android/contacts/activities/ContactEditorAccountsChangedActivity.java b/src/com/android/contacts/activities/ContactEditorAccountsChangedActivity.java
index d63ea6a..3e2a893 100644
--- a/src/com/android/contacts/activities/ContactEditorAccountsChangedActivity.java
+++ b/src/com/android/contacts/activities/ContactEditorAccountsChangedActivity.java
@@ -16,7 +16,6 @@
package com.android.contacts.activities;
-import android.accounts.Account;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
@@ -34,6 +33,7 @@
import com.android.contacts.model.AccountTypeManager;
import com.android.contacts.model.AccountWithDataSet;
import com.android.contacts.util.AccountsListAdapter;
+import com.android.contacts.util.AccountsListAdapter.AccountListFilter;
import java.util.List;
@@ -97,7 +97,8 @@
button.setOnClickListener(mAddAccountClickListener);
final ListView accountListView = (ListView) findViewById(R.id.account_list);
- mAccountListAdapter = new AccountsListAdapter(this, true);
+ mAccountListAdapter = new AccountsListAdapter(this,
+ AccountListFilter.ACCOUNTS_CONTACT_WRITABLE);
accountListView.setAdapter(mAccountListAdapter);
accountListView.setOnItemClickListener(mAccountListItemClickListener);
} else if (numAccounts == 1) {
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/activities/PeopleActivity.java b/src/com/android/contacts/activities/PeopleActivity.java
index 3430109..b088b89 100644
--- a/src/com/android/contacts/activities/PeopleActivity.java
+++ b/src/com/android/contacts/activities/PeopleActivity.java
@@ -56,6 +56,7 @@
import com.android.contacts.util.AccountPromptUtils;
import com.android.contacts.util.AccountSelectionUtil;
import com.android.contacts.util.AccountsListAdapter;
+import com.android.contacts.util.AccountsListAdapter.AccountListFilter;
import com.android.contacts.util.Constants;
import com.android.contacts.util.DialogManager;
import com.android.contacts.util.PhoneCapabilityTester;
@@ -200,10 +201,13 @@
return mProviderStatus == ProviderStatus.STATUS_NORMAL;
}
- private boolean areAccountsAvailable() {
- return ContactsUtils.areAccountsAvailable(this);
+ private boolean areContactWritableAccountsAvailable() {
+ return ContactsUtils.areContactWritableAccountsAvailable(this);
}
+ private boolean areGroupWritableAccountsAvailable() {
+ return ContactsUtils.areGroupWritableAccountsAvailable(this);
+ }
/**
* Initialize fragments that are (or may not be) in the layout.
@@ -604,7 +608,7 @@
invalidateOptionsMenu();
showEmptyStateForTab(tab);
if (tab == TabState.GROUPS) {
- mGroupsFragment.setAddAccountsVisibility(!areAccountsAvailable());
+ mGroupsFragment.setAddAccountsVisibility(!areGroupWritableAccountsAvailable());
}
return;
}
@@ -625,7 +629,7 @@
mFavoritesView.setVisibility(View.GONE);
mBrowserView.setVisibility(View.VISIBLE);
mDetailsView.setVisibility(View.VISIBLE);
- mGroupsFragment.setAddAccountsVisibility(!areAccountsAvailable());
+ mGroupsFragment.setAddAccountsVisibility(!areGroupWritableAccountsAvailable());
break;
case ALL:
mFavoritesView.setVisibility(View.GONE);
@@ -686,7 +690,7 @@
break;
case GROUPS:
mContactsUnavailableFragment.setMessageText(R.string.noGroups,
- areAccountsAvailable() ? -1 : R.string.noAccounts);
+ areGroupWritableAccountsAvailable() ? -1 : R.string.noAccounts);
break;
case ALL:
mContactsUnavailableFragment.setMessageText(R.string.noContacts, -1);
@@ -712,7 +716,7 @@
mActionBarAdapter.setCurrentTab(selectedTab, false);
showEmptyStateForTab(selectedTab);
if (selectedTab == TabState.GROUPS) {
- mGroupsFragment.setAddAccountsVisibility(!areAccountsAvailable());
+ mGroupsFragment.setAddAccountsVisibility(!areGroupWritableAccountsAvailable());
}
invalidateOptionsMenu();
}
@@ -922,7 +926,8 @@
// If there are no accounts on the device and we should show the "no account" prompt
// (based on {@link SharedPreferences}), then launch the account setup activity so the
// user can sign-in or create an account.
- if (!areAccountsAvailable() && AccountPromptUtils.shouldShowAccountPrompt(this)) {
+ if (!areContactWritableAccountsAvailable() &&
+ AccountPromptUtils.shouldShowAccountPrompt(this)) {
AccountPromptUtils.launchAccountPrompt(this);
return;
}
@@ -1304,7 +1309,7 @@
break;
case GROUPS:
// Do not display the "new group" button if no accounts are available
- if (areAccountsAvailable()) {
+ if (areGroupWritableAccountsAvailable()) {
addGroupMenu.setVisible(true);
} else {
addGroupMenu.setVisible(false);
@@ -1410,7 +1415,8 @@
popup.setAnchorView(mAddGroupImageView);
// Create a list adapter with all writeable accounts (assume that the writeable accounts all
// allow group creation).
- final AccountsListAdapter adapter = new AccountsListAdapter(this, true);
+ final AccountsListAdapter adapter = new AccountsListAdapter(this,
+ AccountListFilter.ACCOUNTS_GROUP_WRITABLE);
popup.setAdapter(adapter);
popup.setOnItemClickListener(new OnItemClickListener() {
@Override
diff --git a/src/com/android/contacts/calllog/CallLogFragment.java b/src/com/android/contacts/calllog/CallLogFragment.java
index 2d93a98..0628db4 100644
--- a/src/com/android/contacts/calllog/CallLogFragment.java
+++ b/src/com/android/contacts/calllog/CallLogFragment.java
@@ -337,7 +337,15 @@
@Override
public void onVisibilityChanged(boolean visible) {
- mShowOptionsMenu = visible;
+ if (mShowOptionsMenu != visible) {
+ mShowOptionsMenu = visible;
+ // Invalidate the options menu since we are changing the list of options shown in it.
+ Activity activity = getActivity();
+ if (activity != null) {
+ activity.invalidateOptionsMenu();
+ }
+ }
+
if (visible && isResumed()) {
refreshData();
}
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 412ceff..d1c5868 100644
--- a/src/com/android/contacts/dialpad/DialpadFragment.java
+++ b/src/com/android/contacts/dialpad/DialpadFragment.java
@@ -44,6 +44,7 @@
import android.os.Bundle;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.SystemProperties;
import android.provider.Contacts.Intents.Insert;
import android.provider.Contacts.People;
import android.provider.Contacts.Phones;
@@ -882,9 +883,13 @@
} else {
final String number = mDigits.getText().toString();
+ // "persist.radio.otaspdial" is a temporary hack needed for one carrier's automated
+ // test equipment.
+ // TODO: clean it up.
if (number != null
&& !TextUtils.isEmpty(mProhibitedPhoneNumberRegexp)
- && number.matches(mProhibitedPhoneNumberRegexp)) {
+ && number.matches(mProhibitedPhoneNumberRegexp)
+ && (SystemProperties.getInt("persist.radio.otaspdial", 0) != 1)) {
Log.i(TAG, "The phone number is prohibited explicitly by a rule.");
if (getActivity() != null) {
DialogFragment dialogFragment = CallProhibitedDialogFragment.newInstance();
@@ -894,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 062c021..b341f83 100644
--- a/src/com/android/contacts/editor/ContactEditorFragment.java
+++ b/src/com/android/contacts/editor/ContactEditorFragment.java
@@ -34,6 +34,7 @@
import com.android.contacts.model.EntityModifier;
import com.android.contacts.model.GoogleAccountType;
import com.android.contacts.util.AccountsListAdapter;
+import com.android.contacts.util.AccountsListAdapter.AccountListFilter;
import android.accounts.Account;
import android.app.Activity;
@@ -826,7 +827,8 @@
public void onClick(View v) {
final ListPopupWindow popup = new ListPopupWindow(mContext, null);
final AccountsListAdapter adapter =
- new AccountsListAdapter(mContext, true, currentAccount);
+ new AccountsListAdapter(mContext,
+ AccountListFilter.ACCOUNTS_CONTACT_WRITABLE, currentAccount);
popup.setWidth(anchorView.getWidth());
popup.setAnchorView(anchorView);
popup.setAdapter(adapter);
diff --git a/src/com/android/contacts/editor/SelectAccountDialogFragment.java b/src/com/android/contacts/editor/SelectAccountDialogFragment.java
index 9dbe20a..3a8681a 100644
--- a/src/com/android/contacts/editor/SelectAccountDialogFragment.java
+++ b/src/com/android/contacts/editor/SelectAccountDialogFragment.java
@@ -19,6 +19,7 @@
import com.android.contacts.R;
import com.android.contacts.model.AccountWithDataSet;
import com.android.contacts.util.AccountsListAdapter;
+import com.android.contacts.util.AccountsListAdapter.AccountListFilter;
import android.app.AlertDialog;
import android.app.Dialog;
@@ -37,13 +38,18 @@
public class SelectAccountDialogFragment extends DialogFragment {
public static final String TAG = "SelectAccountDialogFragment";
- private int mTitleResourceId = R.string.dialog_new_contact_account;
+ // TODO: This dialog is used in the context of group editing by default, but should be generic
+ // to work for contact editing as well. Save/restore the resource ID and account list filter
+ // that are passed in as parameters on device rotation. Bug: 5369853
+ private int mTitleResourceId = R.string.dialog_new_group_account;
+ private AccountListFilter mAccountListFilter = AccountListFilter.ACCOUNTS_GROUP_WRITABLE;
public SelectAccountDialogFragment() {
}
- public SelectAccountDialogFragment(int titleResourceId) {
+ public SelectAccountDialogFragment(int titleResourceId, AccountListFilter accountListFilter) {
mTitleResourceId = titleResourceId;
+ mAccountListFilter = accountListFilter;
}
@Override
@@ -51,7 +57,7 @@
final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
final AccountsListAdapter accountAdapter = new AccountsListAdapter(builder.getContext(),
- true);
+ mAccountListFilter);
final DialogInterface.OnClickListener clickListener =
new DialogInterface.OnClickListener() {
diff --git a/src/com/android/contacts/group/GroupBrowseListFragment.java b/src/com/android/contacts/group/GroupBrowseListFragment.java
index 82539e8..79bdd09 100644
--- a/src/com/android/contacts/group/GroupBrowseListFragment.java
+++ b/src/com/android/contacts/group/GroupBrowseListFragment.java
@@ -144,7 +144,7 @@
startActivity(intent);
}
});
- setAddAccountsVisibility(!ContactsUtils.areAccountsAvailable(mContext));
+ setAddAccountsVisibility(!ContactsUtils.areGroupWritableAccountsAvailable(mContext));
return mRootView;
}
@@ -214,7 +214,7 @@
private void bindGroupList() {
mEmptyView.setText(R.string.noGroups);
- setAddAccountsVisibility(!ContactsUtils.areAccountsAvailable(mContext));
+ setAddAccountsVisibility(!ContactsUtils.areGroupWritableAccountsAvailable(mContext));
if (mGroupListCursor == null) {
return;
}
diff --git a/src/com/android/contacts/group/GroupEditorFragment.java b/src/com/android/contacts/group/GroupEditorFragment.java
index 99e6b48..1d1237e 100644
--- a/src/com/android/contacts/group/GroupEditorFragment.java
+++ b/src/com/android/contacts/group/GroupEditorFragment.java
@@ -28,6 +28,7 @@
import com.android.contacts.model.AccountType;
import com.android.contacts.model.AccountTypeManager;
import com.android.contacts.model.AccountWithDataSet;
+import com.android.contacts.util.AccountsListAdapter.AccountListFilter;
import com.android.internal.util.Objects;
import android.accounts.Account;
@@ -332,7 +333,7 @@
mStatus = Status.SELECTING_ACCOUNT;
final SelectAccountDialogFragment dialog = new SelectAccountDialogFragment(
- R.string.dialog_new_group_account);
+ R.string.dialog_new_group_account, AccountListFilter.ACCOUNTS_GROUP_WRITABLE);
dialog.setTargetFragment(this, 0);
dialog.show(getFragmentManager(), SelectAccountDialogFragment.TAG);
}
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/model/AccountTypeManager.java b/src/com/android/contacts/model/AccountTypeManager.java
index dc2fb0d..5443196 100644
--- a/src/com/android/contacts/model/AccountTypeManager.java
+++ b/src/com/android/contacts/model/AccountTypeManager.java
@@ -85,7 +85,17 @@
return new AccountTypeManagerImpl(context);
}
- public abstract List<AccountWithDataSet> getAccounts(boolean writableOnly);
+ /**
+ * Returns the list of all accounts (if contactWritableOnly is false) or just the list of
+ * contact writable accounts (if contactWritableOnly is true).
+ */
+ // TODO: Consider splitting this into getContactWritableAccounts() and getAllAccounts()
+ public abstract List<AccountWithDataSet> getAccounts(boolean contactWritableOnly);
+
+ /**
+ * Returns the list of accounts that are group writable.
+ */
+ public abstract List<AccountWithDataSet> getGroupWritableAccounts();
public abstract AccountType getAccountType(AccountTypeWithDataSet accountTypeWithDataSet);
@@ -130,7 +140,8 @@
private AccountType mFallbackAccountType;
private List<AccountWithDataSet> mAccounts = Lists.newArrayList();
- private List<AccountWithDataSet> mWritableAccounts = Lists.newArrayList();
+ private List<AccountWithDataSet> mContactWritableAccounts = Lists.newArrayList();
+ private List<AccountWithDataSet> mGroupWritableAccounts = Lists.newArrayList();
private Map<AccountTypeWithDataSet, AccountType> mAccountTypesWithDataSets = Maps.newHashMap();
private Map<AccountTypeWithDataSet, AccountType> mInvitableAccountTypes =
Collections.unmodifiableMap(new HashMap<AccountTypeWithDataSet, AccountType>());
@@ -288,16 +299,18 @@
final long startTimeWall = SystemClock.elapsedRealtime();
// Account types, keyed off the account type and data set concatenation.
- Map<AccountTypeWithDataSet, AccountType> accountTypesByTypeAndDataSet = Maps.newHashMap();
+ final Map<AccountTypeWithDataSet, AccountType> accountTypesByTypeAndDataSet =
+ Maps.newHashMap();
// The same AccountTypes, but keyed off {@link RawContacts#ACCOUNT_TYPE}. Since there can
// be multiple account types (with different data sets) for the same type of account, each
// type string may have multiple AccountType entries.
- Map<String, List<AccountType>> accountTypesByType = Maps.newHashMap();
+ final Map<String, List<AccountType>> accountTypesByType = Maps.newHashMap();
- List<AccountWithDataSet> allAccounts = Lists.newArrayList();
- List<AccountWithDataSet> writableAccounts = Lists.newArrayList();
- Set<String> extensionPackages = Sets.newHashSet();
+ final List<AccountWithDataSet> allAccounts = Lists.newArrayList();
+ final List<AccountWithDataSet> contactWritableAccounts = Lists.newArrayList();
+ final List<AccountWithDataSet> groupWritableAccounts = Lists.newArrayList();
+ final Set<String> extensionPackages = Sets.newHashSet();
final AccountManager am = mAccountManager;
final IContentService cs = ContentResolver.getContentService();
@@ -402,7 +415,10 @@
account.name, account.type, accountType.dataSet);
allAccounts.add(accountWithDataSet);
if (accountType.areContactsWritable()) {
- writableAccounts.add(accountWithDataSet);
+ contactWritableAccounts.add(accountWithDataSet);
+ }
+ if (accountType.isGroupMembershipEditable()) {
+ groupWritableAccounts.add(accountWithDataSet);
}
}
}
@@ -410,14 +426,16 @@
}
Collections.sort(allAccounts, ACCOUNT_COMPARATOR);
- Collections.sort(writableAccounts, ACCOUNT_COMPARATOR);
+ Collections.sort(contactWritableAccounts, ACCOUNT_COMPARATOR);
+ Collections.sort(groupWritableAccounts, ACCOUNT_COMPARATOR);
timings.addSplit("Loaded accounts");
synchronized (this) {
mAccountTypesWithDataSets = accountTypesByTypeAndDataSet;
mAccounts = allAccounts;
- mWritableAccounts = writableAccounts;
+ mContactWritableAccounts = contactWritableAccounts;
+ mGroupWritableAccounts = groupWritableAccounts;
mInvitableAccountTypes = findInvitableAccountTypes(
mContext, allAccounts, accountTypesByTypeAndDataSet);
}
@@ -467,12 +485,20 @@
}
/**
- * Return list of all known, writable {@link AccountWithDataSet}'s.
+ * Return list of all known, contact writable {@link AccountWithDataSet}'s.
*/
@Override
- public List<AccountWithDataSet> getAccounts(boolean writableOnly) {
+ public List<AccountWithDataSet> getAccounts(boolean contactWritableOnly) {
ensureAccountsLoaded();
- return writableOnly ? mWritableAccounts : mAccounts;
+ return contactWritableOnly ? mContactWritableAccounts : mAccounts;
+ }
+
+ /**
+ * Return the list of all known, group writable {@link AccountWithDataSet}'s.
+ */
+ public List<AccountWithDataSet> getGroupWritableAccounts() {
+ ensureAccountsLoaded();
+ return mGroupWritableAccounts;
}
/**
diff --git a/src/com/android/contacts/util/AccountsListAdapter.java b/src/com/android/contacts/util/AccountsListAdapter.java
index fc48e72..058cf84 100644
--- a/src/com/android/contacts/util/AccountsListAdapter.java
+++ b/src/com/android/contacts/util/AccountsListAdapter.java
@@ -42,20 +42,28 @@
private final AccountTypeManager mAccountTypes;
private final Context mContext;
- public AccountsListAdapter(Context context, boolean writableOnly) {
- this(context, writableOnly, null);
+ /**
+ * Filters that affect the list of accounts that is displayed by this adapter.
+ */
+ public enum AccountListFilter {
+ ALL_ACCOUNTS, // All read-only and writable accounts
+ ACCOUNTS_CONTACT_WRITABLE, // Only where the account type is contact writable
+ ACCOUNTS_GROUP_WRITABLE // Only accounts where the account type is group writable
+ }
+
+ public AccountsListAdapter(Context context, AccountListFilter accountListFilter) {
+ this(context, accountListFilter, null);
}
/**
* @param currentAccount the Account currently selected by the user, which should come
* first in the list. Can be null.
*/
- public AccountsListAdapter(Context context, boolean writableOnly,
+ public AccountsListAdapter(Context context, AccountListFilter accountListFilter,
AccountWithDataSet currentAccount) {
mContext = context;
mAccountTypes = AccountTypeManager.getInstance(context);
- // We don't want possible side-effect toward AccountTypeManager
- mAccounts = new ArrayList<AccountWithDataSet>(mAccountTypes.getAccounts(writableOnly));
+ mAccounts = getAccounts(accountListFilter);
if (currentAccount != null
&& !mAccounts.isEmpty()
&& !mAccounts.get(0).equals(currentAccount)
@@ -65,6 +73,14 @@
mInflater = LayoutInflater.from(context);
}
+ private List<AccountWithDataSet> getAccounts(AccountListFilter accountListFilter) {
+ if (accountListFilter == AccountListFilter.ACCOUNTS_GROUP_WRITABLE) {
+ return new ArrayList<AccountWithDataSet>(mAccountTypes.getGroupWritableAccounts());
+ }
+ return new ArrayList<AccountWithDataSet>(mAccountTypes.getAccounts(
+ accountListFilter == AccountListFilter.ACCOUNTS_CONTACT_WRITABLE));
+ }
+
@Override
public View getView(int position, View convertView, ViewGroup parent) {
final View resultView = convertView != null ? convertView
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/tests/mocks/MockAccountTypeManager.java b/tests/src/com/android/contacts/tests/mocks/MockAccountTypeManager.java
index 3b712c7..5ca1ccd 100644
--- a/tests/src/com/android/contacts/tests/mocks/MockAccountTypeManager.java
+++ b/tests/src/com/android/contacts/tests/mocks/MockAccountTypeManager.java
@@ -59,6 +59,11 @@
}
@Override
+ public List<AccountWithDataSet> getGroupWritableAccounts() {
+ return Arrays.asList(mAccounts);
+ }
+
+ @Override
public Map<AccountTypeWithDataSet, AccountType> getInvitableAccountTypes() {
return Maps.newHashMap(); // Always returns empty
}
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 {
+ }
+}