summaryrefslogtreecommitdiffstats
path: root/src/com/android/contacts
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/contacts')
-rw-r--r--src/com/android/contacts/common/CallUtil.java7
-rw-r--r--src/com/android/contacts/common/ContactsUtils.java17
-rw-r--r--src/com/android/contacts/common/interactions/ImportExportDialogFragment.java130
-rwxr-xr-xsrc/com/android/contacts/common/list/ContactEntryListFragment.java6
-rwxr-xr-xsrc/com/android/contacts/common/list/ContactListAdapter.java3
-rwxr-xr-xsrc/com/android/contacts/common/list/ContactListItemView.java19
-rw-r--r--src/com/android/contacts/common/list/ContactTileAdapter.java3
-rwxr-xr-xsrc/com/android/contacts/common/list/CustomContactListFilterActivity.java7
-rw-r--r--src/com/android/contacts/common/list/PhoneNumberPickerFragment.java1
-rw-r--r--src/com/android/contacts/common/list/ProfileAndContactsLoader.java4
-rw-r--r--src/com/android/contacts/common/list/ViewPagerTabs.java7
-rw-r--r--src/com/android/contacts/common/location/UpdateCountryService.java4
-rw-r--r--src/com/android/contacts/common/model/ContactLoader.java36
-rw-r--r--src/com/android/contacts/common/model/account/BaseAccountType.java52
-rw-r--r--src/com/android/contacts/common/model/account/ExchangeAccountType.java8
-rw-r--r--src/com/android/contacts/common/model/account/ExternalAccountType.java62
-rw-r--r--src/com/android/contacts/common/model/account/GoogleAccountType.java4
-rw-r--r--src/com/android/contacts/common/model/dataitem/EventDataItem.java7
-rw-r--r--src/com/android/contacts/common/model/dataitem/ImDataItem.java25
-rw-r--r--src/com/android/contacts/common/model/dataitem/RelationDataItem.java7
-rw-r--r--src/com/android/contacts/common/util/AccountSelectionUtil.java43
-rw-r--r--src/com/android/contacts/common/util/BitmapUtil.java52
-rw-r--r--src/com/android/contacts/common/util/ContactDisplayUtils.java84
-rw-r--r--src/com/android/contacts/common/util/LocalizedNameResolver.java25
-rw-r--r--src/com/android/contacts/common/util/MaterialColorMapUtils.java1
-rw-r--r--src/com/android/contacts/common/vcard/ExportVCardActivity.java19
-rw-r--r--src/com/android/contacts/common/vcard/ImportProcessor.java12
-rw-r--r--src/com/android/contacts/common/vcard/ImportVCardActivity.java10
-rw-r--r--src/com/android/contacts/common/vcard/NotificationImportExportListener.java15
-rw-r--r--src/com/android/contacts/common/vcard/VCardService.java9
-rw-r--r--src/com/android/contacts/common/widget/SelectPhoneAccountDialogFragment.java220
-rw-r--r--src/com/android/contacts/commonbind/analytics/AnalyticsUtil.java25
32 files changed, 722 insertions, 202 deletions
diff --git a/src/com/android/contacts/common/CallUtil.java b/src/com/android/contacts/common/CallUtil.java
index 7c491664..3d5f7eab 100644
--- a/src/com/android/contacts/common/CallUtil.java
+++ b/src/com/android/contacts/common/CallUtil.java
@@ -141,6 +141,13 @@ public class CallUtil {
}
/**
+ * A variant of {@link #getCallIntent(android.net.Uri)} for calling Voicemail.
+ */
+ public static Intent getVoicemailIntent() {
+ return getCallIntent(Uri.fromParts(PhoneAccount.SCHEME_VOICEMAIL, "", null));
+ }
+
+ /**
* A variant of {@link #getCallIntent(android.net.Uri)} but also accept a call
* origin and {@code Account} and {@code VideoCallProfile} state.
* For more information about call origin, see comments in Phone package (PhoneApp).
diff --git a/src/com/android/contacts/common/ContactsUtils.java b/src/com/android/contacts/common/ContactsUtils.java
index 857450d9..a6e0e0e8 100644
--- a/src/com/android/contacts/common/ContactsUtils.java
+++ b/src/com/android/contacts/common/ContactsUtils.java
@@ -41,6 +41,8 @@ public class ContactsUtils {
public static final String SCHEME_MAILTO = "mailto";
public static final String SCHEME_SMSTO = "smsto";
+ private static final int DEFAULT_THUMBNAIL_SIZE = 96;
+
private static int sThumbnailSize = -1;
// TODO find a proper place for the canonical version of these
@@ -139,14 +141,17 @@ public class ContactsUtils {
final Cursor c = context.getContentResolver().query(
DisplayPhoto.CONTENT_MAX_DIMENSIONS_URI,
new String[] { DisplayPhoto.THUMBNAIL_MAX_DIM }, null, null, null);
- try {
- c.moveToFirst();
- sThumbnailSize = c.getInt(0);
- } finally {
- c.close();
+ if (c != null) {
+ try {
+ if (c.moveToFirst()) {
+ sThumbnailSize = c.getInt(0);
+ }
+ } finally {
+ c.close();
+ }
}
}
- return sThumbnailSize;
+ return sThumbnailSize != -1 ? sThumbnailSize : DEFAULT_THUMBNAIL_SIZE;
}
private static Intent getCustomImIntent(ImDataItem im, int protocol) {
diff --git a/src/com/android/contacts/common/interactions/ImportExportDialogFragment.java b/src/com/android/contacts/common/interactions/ImportExportDialogFragment.java
index 6908b09d..68fb7df4 100644
--- a/src/com/android/contacts/common/interactions/ImportExportDialogFragment.java
+++ b/src/com/android/contacts/common/interactions/ImportExportDialogFragment.java
@@ -22,6 +22,7 @@ import android.accounts.Account;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
+import android.app.DialogFragment;
import android.app.FragmentManager;
import android.app.ProgressDialog;
import android.content.BroadcastReceiver;
@@ -52,8 +53,11 @@ import android.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.CommonDataKinds.StructuredName;
import android.provider.Settings;
+import android.telephony.PhoneNumberUtils;
import android.telephony.TelephonyManager;
+import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
+import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@@ -75,8 +79,9 @@ import com.android.contacts.common.util.AccountSelectionUtil;
import com.android.contacts.common.util.AccountsListAdapter.AccountListFilter;
import com.android.contacts.common.vcard.ExportVCardActivity;
import com.android.contacts.common.vcard.VCardCommonArguments;
-import com.android.dialerbind.analytics.AnalyticsDialogFragment;
+import com.android.contacts.commonbind.analytics.AnalyticsUtil;
+import java.util.Collections;
import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;
@@ -84,13 +89,12 @@ import java.util.Iterator;
/**
* An dialog invoked to import/export contacts.
*/
-public class ImportExportDialogFragment extends AnalyticsDialogFragment
+public class ImportExportDialogFragment extends DialogFragment
implements SelectAccountDialogFragment.Listener {
public static final String TAG = "ImportExportDialogFragment";
- private static final String SIM_INDEX = "sim_index";
-
private static final String KEY_RES_ID = "resourceId";
+ private static final String KEY_SUBSCRIPTION_ID = "subscriptionId";
private static final String ARG_CONTACTS_ARE_AVAILABLE = "CONTACTS_ARE_AVAILABLE";
private static int SIM_ID_INVALID = -1;
private static int mSelectedSim = SIM_ID_INVALID;
@@ -155,6 +159,9 @@ public class ImportExportDialogFragment extends AnalyticsDialogFragment
public void showExportToSIMProgressDialog(Activity activity){
mExportThread.showExportProgressDialog(activity);
}
+
+ private SubscriptionManager mSubscriptionManager;
+
/** Preferred way to show this dialog */
public static void show(FragmentManager fragmentManager, boolean contactsAreAvailable,
Class callingActivity) {
@@ -169,7 +176,7 @@ public class ImportExportDialogFragment extends AnalyticsDialogFragment
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
- sendScreenView();
+ AnalyticsUtil.sendScreenView(this);
}
@Override
@@ -184,35 +191,70 @@ public class ImportExportDialogFragment extends AnalyticsDialogFragment
VCardCommonArguments.ARG_CALLING_ACTIVITY);
// Adapter that shows a list of string resources
- mAdapter = new ArrayAdapter<Integer>(getActivity(),
+ final ArrayAdapter<AdapterEntry> adapter = new ArrayAdapter<AdapterEntry>(getActivity(),
R.layout.select_dialog_item) {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
final TextView result = (TextView)(convertView != null ? convertView :
dialogInflater.inflate(R.layout.select_dialog_item, parent, false));
- final int resId = getItem(position);
- result.setText(resId);
+ result.setText(getItem(position).mLabel);
return result;
}
};
// Manually call notifyDataSetChanged() to refresh the list.
- mAdapter.setNotifyOnChange(false);
+ adapter.setNotifyOnChange(false);
loadData(contactsAreAvailable);
+ final TelephonyManager manager =
+ (TelephonyManager) getActivity().getSystemService(Context.TELEPHONY_SERVICE);
+
+ mSubscriptionManager = SubscriptionManager.from(getActivity());
+
+ if (res.getBoolean(R.bool.config_allow_import_from_sdcard)) {
+ adapter.add(new AdapterEntry(getString(R.string.import_from_sdcard),
+ R.string.import_from_sdcard));
+ }
+ if (manager != null && res.getBoolean(R.bool.config_allow_sim_import)) {
+ final List<SubscriptionInfo> subInfoRecords =
+ mSubscriptionManager.getActiveSubscriptionInfoList();
+ if (subInfoRecords != null) {
+ if (subInfoRecords.size() == 1) {
+ adapter.add(new AdapterEntry(getString(R.string.import_from_sim),
+ R.string.import_from_sim, subInfoRecords.get(0).getSubscriptionId()));
+ } else {
+ for (SubscriptionInfo record : subInfoRecords) {
+ adapter.add(new AdapterEntry(getSubDescription(record),
+ R.string.import_from_sim, record.getSubscriptionId()));
+ }
+ }
+ }
+ }
+ if (res.getBoolean(R.bool.config_allow_export_to_sdcard)) {
+ if (contactsAreAvailable) {
+ adapter.add(new AdapterEntry(getString(R.string.export_to_sdcard),
+ R.string.export_to_sdcard));
+ }
+ }
+ if (res.getBoolean(R.bool.config_allow_share_visible_contacts)) {
+ if (contactsAreAvailable) {
+ adapter.add(new AdapterEntry(getString(R.string.share_visible_contacts),
+ R.string.share_visible_contacts));
+ }
+ }
+
final DialogInterface.OnClickListener clickListener =
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
- final int resId = mAdapter.getItem(which);
+ boolean dismissDialog;
+ final int resId = adapter.getItem(which).mChoiceResourceId;
switch (resId) {
- case R.string.import_from_sim: {
- handleImportFromSimRequest(resId);
- break;
- }
+ case R.string.import_from_sim:
case R.string.import_from_sdcard: {
- handleImportRequest(resId);
+ dismissDialog = handleImportRequest(resId,
+ adapter.getItem(which).mSubscriptionId);
break;
}
case R.string.export_to_sim: {
@@ -308,9 +350,10 @@ public class ImportExportDialogFragment extends AnalyticsDialogFragment
*
* @return {@code true} if the dialog show be closed. {@code false} otherwise.
*/
- private boolean handleImportRequest(int resId) {
- // There are two possibilities:
- // - one or more than one accounts -> ask the user (user can select phone-local also)
+ private boolean handleImportRequest(int resId, int subscriptionId) {
+ // There are three possibilities:
+ // - more than one accounts -> ask the user
+ // - just one account -> use the account without asking the user
// - no account -> use phone-local storage without asking the user
final AccountTypeManager accountTypes = AccountTypeManager.getInstance(mActivity);
final List<AccountWithDataSet> accountList = accountTypes.getAccounts(true);
@@ -319,6 +362,7 @@ public class ImportExportDialogFragment extends AnalyticsDialogFragment
// Send over to the account selector
final Bundle args = new Bundle();
args.putInt(KEY_RES_ID, resId);
+ args.putInt(KEY_SUBSCRIPTION_ID, subscriptionId);
SelectAccountDialogFragment.show(
mActivity.getFragmentManager(), this,
R.string.dialog_new_contact_account,
@@ -330,7 +374,8 @@ public class ImportExportDialogFragment extends AnalyticsDialogFragment
return false;
}
- AccountSelectionUtil.doImport(mActivity, resId, null);
+ AccountSelectionUtil.doImport(getActivity(), resId,
+ (size == 1 ? accountList.get(0) : null), subscriptionId);
return true; // Close the dialog.
}
@@ -339,7 +384,8 @@ public class ImportExportDialogFragment extends AnalyticsDialogFragment
*/
@Override
public void onAccountChosen(AccountWithDataSet account, Bundle extraArgs) {
- AccountSelectionUtil.doImport(mActivity, extraArgs.getInt(KEY_RES_ID), account);
+ AccountSelectionUtil.doImport(getActivity(), extraArgs.getInt(KEY_RES_ID),
+ account, extraArgs.getInt(KEY_SUBSCRIPTION_ID));
// At this point the dialog is still showing (which is why we can use getActivity() above)
// So close it.
@@ -375,7 +421,7 @@ public class ImportExportDialogFragment extends AnalyticsDialogFragment
if (which >= 0) {
AccountSelectionUtil.setImportSubscription(which);
} else if (which == DialogInterface.BUTTON_POSITIVE) {
- handleImportRequest(R.string.import_from_sim);
+ handleImportRequest(R.string.import_from_sim, which);
}
}
}
@@ -750,20 +796,6 @@ public class ImportExportDialogFragment extends AnalyticsDialogFragment
.setSingleChoiceItems(items, 0, listener).create().show();
}
- private void handleImportFromSimRequest(int Id) {
- if (TelephonyManager.getDefault().isMultiSimEnabled()) {
- if (MoreContactUtils.getEnabledSimCount() > 1) {
- displayImportExportDialog(R.string.import_from_sim_select
- ,null);
- } else {
- AccountSelectionUtil.setImportSubscription(getEnabledIccCard());
- handleImportRequest(Id);
- }
- } else {
- handleImportRequest(Id);
- }
- }
-
private void handleExportToSimRequest(int Id) {
if (MoreContactUtils.getEnabledSimCount() >1) {
//has two enalbed sim cards, prompt dialog to select one
@@ -796,4 +828,32 @@ public class ImportExportDialogFragment extends AnalyticsDialogFragment
}
return SimContactsConstants.SUB_1;
}
+
+ private CharSequence getSubDescription(SubscriptionInfo record) {
+ CharSequence name = record.getDisplayName();
+ if (TextUtils.isEmpty(record.getNumber())) {
+ // Don't include the phone number in the description, since we don't know the number.
+ return getString(R.string.import_from_sim_summary_no_number, name);
+ }
+ return TextUtils.expandTemplate(
+ getString(R.string.import_from_sim_summary),
+ name,
+ PhoneNumberUtils.ttsSpanAsPhoneNumber(record.getNumber()));
+ }
+
+ private static class AdapterEntry {
+ public final CharSequence mLabel;
+ public final int mChoiceResourceId;
+ public final int mSubscriptionId;
+
+ public AdapterEntry(CharSequence label, int resId, int subId) {
+ mLabel = label;
+ mChoiceResourceId = resId;
+ mSubscriptionId = subId;
+ }
+
+ public AdapterEntry(String label, int resId) {
+ this(label, resId, SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+ }
+ }
}
diff --git a/src/com/android/contacts/common/list/ContactEntryListFragment.java b/src/com/android/contacts/common/list/ContactEntryListFragment.java
index 1819c0c8..c7204931 100755
--- a/src/com/android/contacts/common/list/ContactEntryListFragment.java
+++ b/src/com/android/contacts/common/list/ContactEntryListFragment.java
@@ -17,6 +17,7 @@
package com.android.contacts.common.list;
import android.app.Activity;
+import android.app.Fragment;
import android.app.LoaderManager;
import android.app.LoaderManager.LoaderCallbacks;
import android.content.BroadcastReceiver;
@@ -25,7 +26,6 @@ import android.content.CursorLoader;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.Loader;
-import android.content.res.Resources;
import android.database.Cursor;
import android.os.Bundle;
import android.os.Handler;
@@ -47,12 +47,10 @@ import android.widget.AdapterView.OnItemClickListener;
import android.widget.ListView;
import com.android.common.widget.CompositeCursorAdapter.Partition;
-import com.android.contacts.common.R;
import com.android.contacts.common.ContactPhotoManager;
import com.android.contacts.common.preference.ContactsPreferences;
import com.android.contacts.common.util.ContactListViewUtils;
import com.android.contacts.common.util.SchedulingUtils;
-import com.android.dialerbind.analytics.AnalyticsFragment;
import com.android.internal.telephony.TelephonyIntents;
import java.util.Locale;
@@ -61,7 +59,7 @@ import java.util.Locale;
* Common base class for various contact-related list fragments.
*/
public abstract class ContactEntryListFragment<T extends ContactEntryListAdapter>
- extends AnalyticsFragment
+ extends Fragment
implements OnItemClickListener, OnScrollListener, OnFocusChangeListener, OnTouchListener,
LoaderCallbacks<Cursor> {
private static final String TAG = "ContactEntryListFragment";
diff --git a/src/com/android/contacts/common/list/ContactListAdapter.java b/src/com/android/contacts/common/list/ContactListAdapter.java
index b987e4d8..ea2d8539 100755
--- a/src/com/android/contacts/common/list/ContactListAdapter.java
+++ b/src/com/android/contacts/common/list/ContactListAdapter.java
@@ -407,8 +407,7 @@ public abstract class ContactListAdapter extends ContactEntryListAdapter {
super.changeCursor(partitionIndex, cursor);
// Check if a profile exists
- if (cursor != null && cursor.getCount() > 0) {
- cursor.moveToFirst();
+ if (cursor != null && cursor.moveToFirst()) {
setProfileExists(cursor.getInt(ContactQuery.CONTACT_IS_USER_PROFILE) == 1);
}
}
diff --git a/src/com/android/contacts/common/list/ContactListItemView.java b/src/com/android/contacts/common/list/ContactListItemView.java
index e6369977..dd869b0c 100755
--- a/src/com/android/contacts/common/list/ContactListItemView.java
+++ b/src/com/android/contacts/common/list/ContactListItemView.java
@@ -50,6 +50,7 @@ import com.android.contacts.common.ContactPresenceIconUtil;
import com.android.contacts.common.ContactStatusUtil;
import com.android.contacts.common.R;
import com.android.contacts.common.format.TextHighlighter;
+import com.android.contacts.common.util.ContactDisplayUtils;
import com.android.contacts.common.util.SearchUtil;
import com.android.contacts.common.util.ViewUtil;
import com.android.contacts.common.widget.CheckableImageView;
@@ -1031,6 +1032,7 @@ public class ContactListItemView extends ViewGroup
mPhoneticNameTextView.setSingleLine(true);
mPhoneticNameTextView.setEllipsize(getTextEllipsis());
mPhoneticNameTextView.setTextAppearance(getContext(), android.R.style.TextAppearance_Small);
+ mPhoneticNameTextView.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_START);
mPhoneticNameTextView.setTypeface(mPhoneticNameTextView.getTypeface(), Typeface.BOLD);
mPhoneticNameTextView.setActivated(isActivated());
mPhoneticNameTextView.setId(R.id.cliv_phoneticname_textview);
@@ -1156,6 +1158,7 @@ public class ContactListItemView extends ViewGroup
mDataView.setSingleLine(true);
mDataView.setEllipsize(getTextEllipsis());
mDataView.setTextAppearance(getContext(), R.style.TextAppearanceSmall);
+ mDataView.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_START);
mDataView.setActivated(isActivated());
mDataView.setId(R.id.cliv_data_view);
mDataView.setElegantTextHeight(false);
@@ -1175,6 +1178,13 @@ public class ContactListItemView extends ViewGroup
} else {
mTextHighlighter.setPrefixText(getSnippetView(), text, mHighlightedPrefix);
mSnippetView.setVisibility(VISIBLE);
+ if (ContactDisplayUtils.isPossiblePhoneNumber(text)) {
+ // Give the text-to-speech engine a hint that it's a phone number
+ mSnippetView.setContentDescription(
+ ContactDisplayUtils.getTelephoneTtsSpannable(text));
+ } else {
+ mSnippetView.setContentDescription(null);
+ }
}
}
@@ -1187,6 +1197,7 @@ public class ContactListItemView extends ViewGroup
mSnippetView.setSingleLine(true);
mSnippetView.setEllipsize(getTextEllipsis());
mSnippetView.setTextAppearance(getContext(), android.R.style.TextAppearance_Small);
+ mSnippetView.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_START);
mSnippetView.setActivated(isActivated());
addView(mSnippetView);
}
@@ -1300,6 +1311,14 @@ public class ContactListItemView extends ViewGroup
name = mUnknownNameText;
}
setMarqueeText(getNameTextView(), name);
+
+ if (ContactDisplayUtils.isPossiblePhoneNumber(name)) {
+ // Give the text-to-speech engine a hint that it's a phone number
+ mNameTextView.setContentDescription(
+ ContactDisplayUtils.getTelephoneTtsSpannable(name.toString()));
+ } else {
+ mNameTextView.setContentDescription(null);
+ }
}
public void hideDisplayName() {
diff --git a/src/com/android/contacts/common/list/ContactTileAdapter.java b/src/com/android/contacts/common/list/ContactTileAdapter.java
index 70bdebf2..35f9f185 100644
--- a/src/com/android/contacts/common/list/ContactTileAdapter.java
+++ b/src/com/android/contacts/common/list/ContactTileAdapter.java
@@ -583,7 +583,8 @@ public class ContactTileAdapter extends BaseAdapter {
// Just line up children horizontally.
for (int i = 0; i < count; i++) {
- final View child = getChildAt(i);
+ final int rtlAdjustedIndex = isLayoutRtl() ? count - i - 1 : i;
+ final View child = getChildAt(rtlAdjustedIndex);
// Note MeasuredWidth includes the padding.
final int childWidth = child.getMeasuredWidth();
diff --git a/src/com/android/contacts/common/list/CustomContactListFilterActivity.java b/src/com/android/contacts/common/list/CustomContactListFilterActivity.java
index b0bf9c3d..5066b294 100755
--- a/src/com/android/contacts/common/list/CustomContactListFilterActivity.java
+++ b/src/com/android/contacts/common/list/CustomContactListFilterActivity.java
@@ -148,9 +148,12 @@ public class CustomContactListFilterActivity extends Activity
if (account.dataSet != null) {
groupsUri.appendQueryParameter(Groups.DATA_SET, account.dataSet).build();
}
+ final Cursor cursor = resolver.query(groupsUri.build(), null, null, null, null);
+ if (cursor == null) {
+ continue;
+ }
android.content.EntityIterator iterator =
- ContactsContract.Groups.newEntityIterator(resolver.query(
- groupsUri.build(), null, null, null, null));
+ ContactsContract.Groups.newEntityIterator(cursor);
try {
boolean hasGroups = false;
diff --git a/src/com/android/contacts/common/list/PhoneNumberPickerFragment.java b/src/com/android/contacts/common/list/PhoneNumberPickerFragment.java
index 5f850933..06a4f50c 100644
--- a/src/com/android/contacts/common/list/PhoneNumberPickerFragment.java
+++ b/src/com/android/contacts/common/list/PhoneNumberPickerFragment.java
@@ -15,7 +15,6 @@
*/
package com.android.contacts.common.list;
-import android.content.CursorLoader;
import android.content.Intent;
import android.content.Loader;
import android.database.Cursor;
diff --git a/src/com/android/contacts/common/list/ProfileAndContactsLoader.java b/src/com/android/contacts/common/list/ProfileAndContactsLoader.java
index c19737d9..698ef96f 100644
--- a/src/com/android/contacts/common/list/ProfileAndContactsLoader.java
+++ b/src/com/android/contacts/common/list/ProfileAndContactsLoader.java
@@ -61,8 +61,8 @@ public class ProfileAndContactsLoader extends CursorLoader {
Cursor cursor = null;
try {
cursor = super.loadInBackground();
- } catch (NullPointerException e) {
- // Ignore NPEs thrown by providers
+ } catch (NullPointerException | SecurityException e) {
+ // Ignore NPEs and SecurityExceptions thrown by providers
}
final Cursor contactsCursor = cursor;
cursors.add(contactsCursor);
diff --git a/src/com/android/contacts/common/list/ViewPagerTabs.java b/src/com/android/contacts/common/list/ViewPagerTabs.java
index ec95de6f..006d6321 100644
--- a/src/com/android/contacts/common/list/ViewPagerTabs.java
+++ b/src/com/android/contacts/common/list/ViewPagerTabs.java
@@ -198,7 +198,12 @@ public class ViewPagerTabs extends HorizontalScrollView implements ViewPager.OnP
@Override
public void onPageSelected(int position) {
position = getRtlPosition(position);
- if (mPrevSelected >= 0) {
+ int tabStripChildCount = mTabStrip.getChildCount();
+ if ((tabStripChildCount == 0) || (position < 0) || (position >= tabStripChildCount)) {
+ return;
+ }
+
+ if (mPrevSelected >= 0 && mPrevSelected < tabStripChildCount) {
mTabStrip.getChildAt(mPrevSelected).setSelected(false);
}
final View selectedChild = mTabStrip.getChildAt(position);
diff --git a/src/com/android/contacts/common/location/UpdateCountryService.java b/src/com/android/contacts/common/location/UpdateCountryService.java
index e339306f..9403187e 100644
--- a/src/com/android/contacts/common/location/UpdateCountryService.java
+++ b/src/com/android/contacts/common/location/UpdateCountryService.java
@@ -38,6 +38,10 @@ public class UpdateCountryService extends IntentService {
@Override
protected void onHandleIntent(Intent intent) {
+ if (intent == null) {
+ Log.d(TAG, "onHandleIntent: could not handle null intent");
+ return;
+ }
if (ACTION_UPDATE_COUNTRY.equals(intent.getAction())) {
final Location location = (Location) intent.getParcelableExtra(KEY_INTENT_LOCATION);
final String country = getCountryFromLocation(getApplicationContext(), location);
diff --git a/src/com/android/contacts/common/model/ContactLoader.java b/src/com/android/contacts/common/model/ContactLoader.java
index bea72107..59ab292e 100644
--- a/src/com/android/contacts/common/model/ContactLoader.java
+++ b/src/com/android/contacts/common/model/ContactLoader.java
@@ -63,9 +63,11 @@ import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
/**
@@ -773,6 +775,34 @@ public class ContactLoader extends AsyncTaskLoader<Contact> {
}
}
+ static private class AccountKey {
+ private final String mAccountName;
+ private final String mAccountType;
+ private final String mDataSet;
+
+ public AccountKey(String accountName, String accountType, String dataSet) {
+ mAccountName = accountName;
+ mAccountType = accountType;
+ mDataSet = dataSet;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mAccountName, mAccountType, mDataSet);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof AccountKey)) {
+ return false;
+ }
+ final AccountKey other = (AccountKey) obj;
+ return Objects.equals(mAccountName, other.mAccountName)
+ && Objects.equals(mAccountType, other.mAccountType)
+ && Objects.equals(mDataSet, other.mDataSet);
+ }
+ }
+
/**
* Loads groups meta-data for all groups associated with all constituent raw contacts'
* accounts.
@@ -780,11 +810,15 @@ public class ContactLoader extends AsyncTaskLoader<Contact> {
private void loadGroupMetaData(Contact result) {
StringBuilder selection = new StringBuilder();
ArrayList<String> selectionArgs = new ArrayList<String>();
+ final HashSet<AccountKey> accountsSeen = new HashSet<>();
for (RawContact rawContact : result.getRawContacts()) {
final String accountName = rawContact.getAccountName();
final String accountType = rawContact.getAccountTypeString();
final String dataSet = rawContact.getDataSet();
- if (accountName != null && accountType != null) {
+ final AccountKey accountKey = new AccountKey(accountName, accountType, dataSet);
+ if (accountName != null && accountType != null &&
+ !accountsSeen.contains(accountKey)) {
+ accountsSeen.add(accountKey);
if (selection.length() != 0) {
selection.append(" OR ");
}
diff --git a/src/com/android/contacts/common/model/account/BaseAccountType.java b/src/com/android/contacts/common/model/account/BaseAccountType.java
index 8b4c955c..1ead103f 100644
--- a/src/com/android/contacts/common/model/account/BaseAccountType.java
+++ b/src/com/android/contacts/common/model/account/BaseAccountType.java
@@ -99,20 +99,20 @@ public abstract class BaseAccountType extends AccountType {
static final String TYPE = "type";
}
- private interface Weight {
+ protected interface Weight {
static final int NONE = -1;
- static final int ORGANIZATION = 5;
static final int PHONE = 10;
static final int EMAIL = 15;
- static final int IM = 20;
static final int STRUCTURED_POSTAL = 25;
- static final int NOTE = 110;
- static final int NICKNAME = 115;
- static final int WEBSITE = 120;
- static final int SIP_ADDRESS = 130;
- static final int EVENT = 150;
- static final int RELATIONSHIP = 160;
- static final int GROUP_MEMBERSHIP = 999;
+ static final int NICKNAME = 111;
+ static final int EVENT = 120;
+ static final int ORGANIZATION = 125;
+ static final int NOTE = 130;
+ static final int IM = 140;
+ static final int SIP_ADDRESS = 145;
+ static final int GROUP_MEMBERSHIP = 150;
+ static final int WEBSITE = 160;
+ static final int RELATIONSHIP = 999;
}
public BaseAccountType() {
@@ -148,7 +148,7 @@ public abstract class BaseAccountType extends AccountType {
protected DataKind addDataKindStructuredName(Context context) throws DefinitionException {
DataKind kind = addKind(new DataKind(StructuredName.CONTENT_ITEM_TYPE,
- R.string.nameLabelsGroup, -1, true));
+ R.string.nameLabelsGroup, Weight.NONE, true));
kind.actionHeader = new SimpleInflater(R.string.nameLabelsGroup);
kind.actionBody = new SimpleInflater(Nickname.NAME);
kind.typeOverallMax = 1;
@@ -178,7 +178,7 @@ public abstract class BaseAccountType extends AccountType {
protected DataKind addDataKindDisplayName(Context context) throws DefinitionException {
DataKind kind = addKind(new DataKind(DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME,
- R.string.nameLabelsGroup, -1, true));
+ R.string.nameLabelsGroup, Weight.NONE, true));
kind.actionHeader = new SimpleInflater(R.string.nameLabelsGroup);
kind.actionBody = new SimpleInflater(Nickname.NAME);
kind.typeOverallMax = 1;
@@ -219,7 +219,7 @@ public abstract class BaseAccountType extends AccountType {
protected DataKind addDataKindPhoneticName(Context context) throws DefinitionException {
DataKind kind = addKind(new DataKind(DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME,
- R.string.name_phonetic, -1, true));
+ R.string.name_phonetic, Weight.NONE, true));
kind.actionHeader = new SimpleInflater(R.string.nameLabelsGroup);
kind.actionBody = new SimpleInflater(Nickname.NAME);
kind.typeOverallMax = 1;
@@ -239,7 +239,7 @@ public abstract class BaseAccountType extends AccountType {
protected DataKind addDataKindNickname(Context context) throws DefinitionException {
DataKind kind = addKind(new DataKind(Nickname.CONTENT_ITEM_TYPE,
- R.string.nicknameLabelsGroup, 115, true));
+ R.string.nicknameLabelsGroup, Weight.NICKNAME, true));
kind.typeOverallMax = 1;
kind.actionHeader = new SimpleInflater(R.string.nicknameLabelsGroup);
kind.actionBody = new SimpleInflater(Nickname.NAME);
@@ -255,7 +255,7 @@ public abstract class BaseAccountType extends AccountType {
protected DataKind addDataKindPhone(Context context) throws DefinitionException {
DataKind kind = addKind(new DataKind(Phone.CONTENT_ITEM_TYPE, R.string.phoneLabelsGroup,
- 10, true));
+ Weight.PHONE, true));
kind.iconAltRes = R.drawable.ic_text_holo_light;
kind.iconAltDescriptionRes = R.string.sms;
kind.actionHeader = new PhoneActionInflater();
@@ -294,7 +294,7 @@ public abstract class BaseAccountType extends AccountType {
protected DataKind addDataKindEmail(Context context) throws DefinitionException {
DataKind kind = addKind(new DataKind(Email.CONTENT_ITEM_TYPE, R.string.emailLabelsGroup,
- 15, true));
+ Weight.EMAIL, true));
kind.actionHeader = new EmailActionInflater();
kind.actionBody = new SimpleInflater(Email.DATA);
kind.typeColumn = Email.TYPE;
@@ -314,7 +314,7 @@ public abstract class BaseAccountType extends AccountType {
protected DataKind addDataKindStructuredPostal(Context context) throws DefinitionException {
DataKind kind = addKind(new DataKind(StructuredPostal.CONTENT_ITEM_TYPE,
- R.string.postalLabelsGroup, 25, true));
+ R.string.postalLabelsGroup, Weight.STRUCTURED_POSTAL, true));
kind.actionHeader = new PostalActionInflater();
kind.actionBody = new SimpleInflater(StructuredPostal.FORMATTED_ADDRESS);
kind.typeColumn = StructuredPostal.TYPE;
@@ -336,8 +336,8 @@ public abstract class BaseAccountType extends AccountType {
}
protected DataKind addDataKindIm(Context context) throws DefinitionException {
- DataKind kind = addKind(new DataKind(Im.CONTENT_ITEM_TYPE, R.string.imLabelsGroup, 20,
- true));
+ DataKind kind = addKind(new DataKind(Im.CONTENT_ITEM_TYPE, R.string.imLabelsGroup,
+ Weight.IM, true));
kind.actionHeader = new ImActionInflater();
kind.actionBody = new SimpleInflater(Im.DATA);
@@ -368,7 +368,7 @@ public abstract class BaseAccountType extends AccountType {
protected DataKind addDataKindOrganization(Context context) throws DefinitionException {
DataKind kind = addKind(new DataKind(Organization.CONTENT_ITEM_TYPE,
- R.string.organizationLabelsGroup, 5, true));
+ R.string.organizationLabelsGroup, Weight.ORGANIZATION, true));
kind.actionHeader = new SimpleInflater(R.string.organizationLabelsGroup);
kind.actionBody = ORGANIZATION_BODY_INFLATER;
kind.typeOverallMax = 1;
@@ -383,7 +383,7 @@ public abstract class BaseAccountType extends AccountType {
}
protected DataKind addDataKindPhoto(Context context) throws DefinitionException {
- DataKind kind = addKind(new DataKind(Photo.CONTENT_ITEM_TYPE, -1, -1, true));
+ DataKind kind = addKind(new DataKind(Photo.CONTENT_ITEM_TYPE, -1, Weight.NONE, true));
kind.typeOverallMax = 1;
kind.fieldList = Lists.newArrayList();
kind.fieldList.add(new EditField(Photo.PHOTO, -1, -1));
@@ -391,8 +391,8 @@ public abstract class BaseAccountType extends AccountType {
}
protected DataKind addDataKindNote(Context context) throws DefinitionException {
- DataKind kind = addKind(new DataKind(Note.CONTENT_ITEM_TYPE, R.string.label_notes, 110,
- true));
+ DataKind kind = addKind(new DataKind(Note.CONTENT_ITEM_TYPE, R.string.label_notes,
+ Weight.NOTE, true));
kind.typeOverallMax = 1;
kind.actionHeader = new SimpleInflater(R.string.label_notes);
kind.actionBody = new SimpleInflater(Note.NOTE);
@@ -406,7 +406,7 @@ public abstract class BaseAccountType extends AccountType {
protected DataKind addDataKindWebsite(Context context) throws DefinitionException {
DataKind kind = addKind(new DataKind(Website.CONTENT_ITEM_TYPE,
- R.string.websiteLabelsGroup, 120, true));
+ R.string.websiteLabelsGroup, Weight.WEBSITE, true));
kind.actionHeader = new SimpleInflater(R.string.websiteLabelsGroup);
kind.actionBody = new SimpleInflater(Website.URL);
kind.defaultValues = new ContentValues();
@@ -420,7 +420,7 @@ public abstract class BaseAccountType extends AccountType {
protected DataKind addDataKindSipAddress(Context context) throws DefinitionException {
DataKind kind = addKind(new DataKind(SipAddress.CONTENT_ITEM_TYPE,
- R.string.label_sip_address, 130, true));
+ R.string.label_sip_address, Weight.SIP_ADDRESS, true));
kind.typeOverallMax = 1;
kind.actionHeader = new SimpleInflater(R.string.label_sip_address);
@@ -434,7 +434,7 @@ public abstract class BaseAccountType extends AccountType {
protected DataKind addDataKindGroupMembership(Context context) throws DefinitionException {
DataKind kind = addKind(new DataKind(GroupMembership.CONTENT_ITEM_TYPE,
- R.string.groupsLabel, 999, true));
+ R.string.groupsLabel, Weight.GROUP_MEMBERSHIP, true));
kind.actionHeader = new SimpleInflater(R.string.label_groups);
kind.actionBody = new SimpleInflater(GroupMembership.GROUP_ROW_ID);
diff --git a/src/com/android/contacts/common/model/account/ExchangeAccountType.java b/src/com/android/contacts/common/model/account/ExchangeAccountType.java
index 04b5263a..7020836d 100644
--- a/src/com/android/contacts/common/model/account/ExchangeAccountType.java
+++ b/src/com/android/contacts/common/model/account/ExchangeAccountType.java
@@ -80,7 +80,7 @@ public class ExchangeAccountType extends BaseAccountType {
@Override
protected DataKind addDataKindStructuredName(Context context) throws DefinitionException {
DataKind kind = addKind(new DataKind(StructuredName.CONTENT_ITEM_TYPE,
- R.string.nameLabelsGroup, -1, true));
+ R.string.nameLabelsGroup, Weight.NONE, true));
kind.actionHeader = new SimpleInflater(R.string.nameLabelsGroup);
kind.actionBody = new SimpleInflater(Nickname.NAME);
@@ -109,7 +109,7 @@ public class ExchangeAccountType extends BaseAccountType {
@Override
protected DataKind addDataKindDisplayName(Context context) throws DefinitionException {
DataKind kind = addKind(new DataKind(DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME,
- R.string.nameLabelsGroup, -1, true));
+ R.string.nameLabelsGroup, Weight.NONE, true));
boolean displayOrderPrimary =
context.getResources().getBoolean(R.bool.config_editor_field_order_primary);
@@ -142,7 +142,7 @@ public class ExchangeAccountType extends BaseAccountType {
@Override
protected DataKind addDataKindPhoneticName(Context context) throws DefinitionException {
DataKind kind = addKind(new DataKind(DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME,
- R.string.name_phonetic, -1, true));
+ R.string.name_phonetic, Weight.NONE, true));
kind.actionHeader = new SimpleInflater(R.string.nameLabelsGroup);
kind.actionBody = new SimpleInflater(Nickname.NAME);
@@ -307,7 +307,7 @@ public class ExchangeAccountType extends BaseAccountType {
protected DataKind addDataKindEvent(Context context) throws DefinitionException {
DataKind kind = addKind(new DataKind(Event.CONTENT_ITEM_TYPE, R.string.eventLabelsGroup,
- 150, true));
+ Weight.EVENT, true));
kind.actionHeader = new EventActionInflater();
kind.actionBody = new SimpleInflater(Event.START_DATE);
diff --git a/src/com/android/contacts/common/model/account/ExternalAccountType.java b/src/com/android/contacts/common/model/account/ExternalAccountType.java
index e4cef522..53089b84 100644
--- a/src/com/android/contacts/common/model/account/ExternalAccountType.java
+++ b/src/com/android/contacts/common/model/account/ExternalAccountType.java
@@ -17,9 +17,10 @@
package com.android.contacts.common.model.account;
import android.content.Context;
-import android.content.pm.PackageInfo;
+import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.res.Resources;
import android.content.res.TypedArray;
@@ -48,12 +49,15 @@ import java.util.List;
public class ExternalAccountType extends BaseAccountType {
private static final String TAG = "ExternalAccountType";
+ private static final String SYNC_META_DATA = "android.content.SyncAdapter";
+
/**
* The metadata name for so-called "contacts.xml".
*
* On LMP and later, we also accept the "alternate" name.
* This is to allow sync adapters to have a contacts.xml without making it visible on older
- * platforms.
+ * platforms. If you modify this also update the corresponding list in
+ * ContactsProvider/PhotoPriorityResolver
*/
private static final String[] METADATA_CONTACTS_NAMES = new String[] {
"android.provider.ALTERNATE_CONTACTS_STRUCTURE",
@@ -114,15 +118,9 @@ public class ExternalAccountType extends BaseAccountType {
this.resourcePackageName = packageName;
this.syncAdapterPackageName = packageName;
- final PackageManager pm = context.getPackageManager();
final XmlResourceParser parser;
if (injectedMetadata == null) {
- try {
- parser = loadContactsXml(context, packageName);
- } catch (NameNotFoundException e1) {
- // If the package name is not found, we can't initialize this account type.
- return;
- }
+ parser = loadContactsXml(context, packageName);
} else {
parser = injectedMetadata;
}
@@ -181,35 +179,41 @@ public class ExternalAccountType extends BaseAccountType {
/**
* Returns the CONTACTS_STRUCTURE metadata (aka "contacts.xml") in the given apk package.
*
- * Unfortunately, there's no public way to determine which service defines a sync service for
- * which account type, so this method looks through all services in the package, and just
- * returns the first CONTACTS_STRUCTURE metadata defined in any of them.
+ * This method looks through all services in the package that handle sync adapter
+ * intents for the first one that contains CONTACTS_STRUCTURE metadata. We have to look
+ * through all sync adapters in the package in case there are contacts and other sync
+ * adapters (eg, calendar) in the same package.
*
* Returns {@code null} if the package has no CONTACTS_STRUCTURE metadata. In this case
* the account type *will* be initialized with minimal configuration.
- *
- * On the other hand, if the package is not found, it throws a {@link NameNotFoundException},
- * in which case the account type will *not* be initialized.
*/
- private XmlResourceParser loadContactsXml(Context context, String resPackageName)
- throws NameNotFoundException {
+ public static XmlResourceParser loadContactsXml(Context context, String resPackageName) {
final PackageManager pm = context.getPackageManager();
- PackageInfo packageInfo = pm.getPackageInfo(resPackageName,
- PackageManager.GET_SERVICES|PackageManager.GET_META_DATA);
- for (ServiceInfo serviceInfo : packageInfo.services) {
- for (String metadataName : METADATA_CONTACTS_NAMES) {
- final XmlResourceParser parser = serviceInfo.loadXmlMetaData(pm,
- metadataName);
- if (parser != null) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, String.format("Metadata loaded from: %s, %s, %s",
- serviceInfo.packageName, serviceInfo.name,
- metadataName));
+ final Intent intent = new Intent(SYNC_META_DATA).setPackage(resPackageName);
+ final List<ResolveInfo> intentServices = pm.queryIntentServices(intent,
+ PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
+
+ if (intentServices != null) {
+ for (final ResolveInfo resolveInfo : intentServices) {
+ final ServiceInfo serviceInfo = resolveInfo.serviceInfo;
+ if (serviceInfo == null) {
+ continue;
+ }
+ for (String metadataName : METADATA_CONTACTS_NAMES) {
+ final XmlResourceParser parser = serviceInfo.loadXmlMetaData(
+ pm, metadataName);
+ if (parser != null) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, String.format("Metadata loaded from: %s, %s, %s",
+ serviceInfo.packageName, serviceInfo.name,
+ metadataName));
+ }
+ return parser;
}
- return parser;
}
}
}
+
// Package was found, but that doesn't contain the CONTACTS_STRUCTURE metadata.
return null;
}
diff --git a/src/com/android/contacts/common/model/account/GoogleAccountType.java b/src/com/android/contacts/common/model/account/GoogleAccountType.java
index 8705ae34..68771874 100644
--- a/src/com/android/contacts/common/model/account/GoogleAccountType.java
+++ b/src/com/android/contacts/common/model/account/GoogleAccountType.java
@@ -122,7 +122,7 @@ public class GoogleAccountType extends BaseAccountType {
private DataKind addDataKindRelation(Context context) throws DefinitionException {
DataKind kind = addKind(new DataKind(Relation.CONTENT_ITEM_TYPE,
- R.string.relationLabelsGroup, 160, true));
+ R.string.relationLabelsGroup, Weight.RELATIONSHIP, true));
kind.actionHeader = new RelationActionInflater();
kind.actionBody = new SimpleInflater(Relation.NAME);
@@ -157,7 +157,7 @@ public class GoogleAccountType extends BaseAccountType {
private DataKind addDataKindEvent(Context context) throws DefinitionException {
DataKind kind = addKind(new DataKind(Event.CONTENT_ITEM_TYPE,
- R.string.eventLabelsGroup, 150, true));
+ R.string.eventLabelsGroup, Weight.EVENT, true));
kind.actionHeader = new EventActionInflater();
kind.actionBody = new SimpleInflater(Event.START_DATE);
diff --git a/src/com/android/contacts/common/model/dataitem/EventDataItem.java b/src/com/android/contacts/common/model/dataitem/EventDataItem.java
index aae00e97..5096fea2 100644
--- a/src/com/android/contacts/common/model/dataitem/EventDataItem.java
+++ b/src/com/android/contacts/common/model/dataitem/EventDataItem.java
@@ -20,6 +20,7 @@ import android.content.ContentValues;
import android.content.Context;
import android.provider.ContactsContract;
import android.provider.ContactsContract.CommonDataKinds.Event;
+import android.text.TextUtils;
/**
* Represents an event data item, wrapping the columns in
@@ -46,12 +47,14 @@ public class EventDataItem extends DataItem {
}
final EventDataItem that = (EventDataItem) t;
// Events can be different (anniversary, birthday) but have the same start date
- if (!getStartDate().equals(that.getStartDate())) {
+ if (!TextUtils.equals(getStartDate(), that.getStartDate())) {
return false;
+ } else if (!hasKindTypeColumn(mKind) || !that.hasKindTypeColumn(that.getDataKind())) {
+ return hasKindTypeColumn(mKind) == that.hasKindTypeColumn(that.getDataKind());
} else if (getKindTypeColumn(mKind) != that.getKindTypeColumn(that.getDataKind())) {
return false;
} else if (getKindTypeColumn(mKind) == Event.TYPE_CUSTOM &&
- !getLabel().equals(that.getLabel())) {
+ !TextUtils.equals(getLabel(), that.getLabel())) {
// Check if custom types are not the same
return false;
}
diff --git a/src/com/android/contacts/common/model/dataitem/ImDataItem.java b/src/com/android/contacts/common/model/dataitem/ImDataItem.java
index d1af2462..f89e5c6a 100644
--- a/src/com/android/contacts/common/model/dataitem/ImDataItem.java
+++ b/src/com/android/contacts/common/model/dataitem/ImDataItem.java
@@ -21,6 +21,7 @@ import android.content.Context;
import android.provider.ContactsContract;
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.CommonDataKinds.Im;
+import android.text.TextUtils;
/**
* Represents an IM data item, wrapping the columns in
@@ -91,21 +92,21 @@ public class ImDataItem extends DataItem {
// IM can have the same data put different protocol. These should not collapse.
if (!getData().equals(that.getData())) {
return false;
- } else if (isProtocolValid() && that.isProtocolValid() &&
- getProtocol() != that.getProtocol()) {
+ } else if (!isProtocolValid() || !that.isProtocolValid()) {
+ // Deal with invalid protocol as if it was custom. If either has a non valid
+ // protocol, check to see if the other has a valid that is not custom
+ if (isProtocolValid()) {
+ return getProtocol() == Im.PROTOCOL_CUSTOM;
+ } else if (that.isProtocolValid()) {
+ return that.getProtocol() == Im.PROTOCOL_CUSTOM;
+ }
+ return true;
+ } else if (getProtocol() != that.getProtocol()) {
return false;
- } else if (isProtocolValid() && that.isProtocolValid() &&
- getProtocol() == Im.PROTOCOL_CUSTOM &&
- !getCustomProtocol().equals(that.getCustomProtocol())) {
+ } else if (getProtocol() == Im.PROTOCOL_CUSTOM &&
+ !TextUtils.equals(getCustomProtocol(), that.getCustomProtocol())) {
// Check if custom protocols are not the same
return false;
- } else if ((isProtocolValid() && !that.isProtocolValid() &&
- getProtocol() != Im.PROTOCOL_CUSTOM) ||
- (that.isProtocolValid() && !isProtocolValid() &&
- that.getProtocol() != Im.PROTOCOL_CUSTOM)) {
- // Deal with invalid protocol as if it was custom. If either has a non valid protocol,
- // check to see if the other has a valid that is not custom
- return false;
}
return true;
}
diff --git a/src/com/android/contacts/common/model/dataitem/RelationDataItem.java b/src/com/android/contacts/common/model/dataitem/RelationDataItem.java
index 1ba3fcfb..9e883fef 100644
--- a/src/com/android/contacts/common/model/dataitem/RelationDataItem.java
+++ b/src/com/android/contacts/common/model/dataitem/RelationDataItem.java
@@ -20,6 +20,7 @@ import android.content.ContentValues;
import android.content.Context;
import android.provider.ContactsContract;
import android.provider.ContactsContract.CommonDataKinds.Relation;
+import android.text.TextUtils;
/**
* Represents a relation data item, wrapping the columns in
@@ -46,12 +47,14 @@ public class RelationDataItem extends DataItem {
}
final RelationDataItem that = (RelationDataItem) t;
// Relations can have different types (assistant, father) but have the same name
- if (!getName().equals(that.getName())) {
+ if (!TextUtils.equals(getName(), that.getName())) {
return false;
+ } else if (!hasKindTypeColumn(mKind) || !that.hasKindTypeColumn(that.getDataKind())) {
+ return hasKindTypeColumn(mKind) == that.hasKindTypeColumn(that.getDataKind());
} else if (getKindTypeColumn(mKind) != that.getKindTypeColumn(that.getDataKind())) {
return false;
} else if (getKindTypeColumn(mKind) == Relation.TYPE_CUSTOM &&
- !getLabel().equals(that.getLabel())) {
+ !TextUtils.equals(getLabel(), that.getLabel())) {
// Check if custom types are not the same
return false;
}
diff --git a/src/com/android/contacts/common/util/AccountSelectionUtil.java b/src/com/android/contacts/common/util/AccountSelectionUtil.java
index 2ea53f11..ed77da22 100644
--- a/src/com/android/contacts/common/util/AccountSelectionUtil.java
+++ b/src/com/android/contacts/common/util/AccountSelectionUtil.java
@@ -24,6 +24,7 @@ import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
+import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.util.Log;
import android.view.ContextThemeWrapper;
@@ -53,7 +54,6 @@ public class AccountSelectionUtil {
public static boolean mVCardShare = false;
private static int SIM_ID_INVALID = -1;
private static int mSelectedSim = SIM_ID_INVALID;
- private static final String SIM_INDEX = "sim_index";
// Constant value to know option is import from all SIM's
private static int IMPORT_FROM_ALL = 8;
@@ -66,22 +66,31 @@ public class AccountSelectionUtil {
final private Context mContext;
final private int mResId;
+ final private int mSubscriptionId;
protected List<AccountWithDataSet> mAccountList;
public AccountSelectedListener(Context context, List<AccountWithDataSet> accountList,
- int resId) {
+ int resId, int subscriptionId) {
if (accountList == null || accountList.size() == 0) {
Log.e(LOG_TAG, "The size of Account list is 0.");
}
mContext = context;
mAccountList = accountList;
mResId = resId;
+ mSubscriptionId = subscriptionId;
+ }
+
+ public AccountSelectedListener(Context context, List<AccountWithDataSet> accountList,
+ int resId) {
+ // Subscription id is only needed for importing from SIM card. We can safely ignore
+ // its value for SD card importing.
+ this(context, accountList, resId, SubscriptionManager.INVALID_SUBSCRIPTION_ID);
}
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
- doImport(mContext, mResId, mAccountList.get(which));
+ doImport(mContext, mResId, mAccountList.get(which), mSubscriptionId);
}
/**
* Reset the account list for this listener, to make sure the selected
@@ -195,10 +204,11 @@ public class AccountSelectionUtil {
.create();
}
- public static void doImport(Context context, int resId, AccountWithDataSet account) {
+ public static void doImport(Context context, int resId, AccountWithDataSet account,
+ int subscriptionId) {
switch (resId) {
case R.string.import_from_sim: {
- doImportFromSim(context, account);
+ doImportFromSim(context, account, subscriptionId);
break;
}
case R.string.import_from_sdcard: {
@@ -208,23 +218,8 @@ public class AccountSelectionUtil {
}
}
- public static void doImportFromSim(Context context, AccountWithDataSet account) {
- Intent importIntent = new Intent(SimContactsConstants.ACTION_MULTI_PICK_SIM);
- if (account != null) {
- importIntent.putExtra(SimContactsConstants.ACCOUNT_NAME, account.name);
- importIntent.putExtra(SimContactsConstants.ACCOUNT_TYPE, account.type);
- importIntent.putExtra(SimContactsConstants.ACCOUNT_DATA, account.dataSet);
- }
- if (TelephonyManager.getDefault().isMultiSimEnabled()) {
- importIntent.putExtra(SimContactsConstants.SUB, mImportSub);
- } else {
- importIntent.putExtra(SimContactsConstants.SUB,SimContactsConstants.SUB_1);
- }
- context.startActivity(importIntent);
- }
-
- public static void doImportFromMultiSim(Context context, AccountWithDataSet account,
- int selectedSim) {
+ public static void doImportFromSim(Context context, AccountWithDataSet account,
+ int subscriptionId) {
Intent importIntent = new Intent(Intent.ACTION_VIEW);
importIntent.setType("vnd.android.cursor.item/sim-contact");
if (account != null) {
@@ -232,8 +227,8 @@ public class AccountSelectionUtil {
importIntent.putExtra("account_type", account.type);
importIntent.putExtra("data_set", account.dataSet);
}
+ importIntent.putExtra("subscription_id", (Integer) subscriptionId);
importIntent.setClassName("com.android.phone", "com.android.phone.SimContacts");
- importIntent.putExtra(SIM_INDEX, selectedSim);
context.startActivity(importIntent);
}
@@ -268,7 +263,7 @@ public class AccountSelectionUtil {
public void onClick(DialogInterface dialog, int which) {
Log.d(LOG_TAG, "onClick OK: mSelectedSim = " + mSelectedSim);
if (mSelectedSim != SIM_ID_INVALID) {
- doImportFromMultiSim(mContext, mAccount, mSelectedSim);
+ doImportFromSim(mContext, mAccount, mSelectedSim);
}
}
}
diff --git a/src/com/android/contacts/common/util/BitmapUtil.java b/src/com/android/contacts/common/util/BitmapUtil.java
index a70831ec..66ab00f5 100644
--- a/src/com/android/contacts/common/util/BitmapUtil.java
+++ b/src/com/android/contacts/common/util/BitmapUtil.java
@@ -19,6 +19,11 @@ package com.android.contacts.common.util;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.PorterDuff.Mode;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
+import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.BitmapDrawable;
@@ -107,4 +112,51 @@ public class BitmapUtil {
return new BitmapDrawable(resources,rotated);
}
+
+ /**
+ * Given an input bitmap, scales it to the given width/height and makes it round.
+ *
+ * @param input {@link Bitmap} to scale and crop
+ * @param targetWidth desired output width
+ * @param targetHeight desired output height
+ * @return output bitmap scaled to the target width/height and cropped to an oval. The
+ * cropping algorithm will try to fit as much of the input into the output as possible,
+ * while preserving the target width/height ratio.
+ */
+ public static Bitmap getRoundedBitmap(Bitmap input, int targetWidth, int targetHeight) {
+ if (input == null) {
+ return null;
+ }
+ final Bitmap result = Bitmap.createBitmap(targetWidth, targetHeight, input.getConfig());
+ final Canvas canvas = new Canvas(result);
+ final Paint paint = new Paint();
+ canvas.drawARGB(0, 0, 0, 0);
+ paint.setAntiAlias(true);
+ canvas.drawOval(0, 0, targetWidth, targetHeight, paint);
+
+ // Specifies that only pixels present in the destination (i.e. the drawn oval) should
+ // be overwritten with pixels from the input bitmap.
+ paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
+
+ final int inputWidth = input.getWidth();
+ final int inputHeight = input.getHeight();
+
+ // Choose the largest scale factor that will fit inside the dimensions of the
+ // input bitmap.
+ final float scaleBy = Math.min((float) inputWidth / targetWidth,
+ (float) inputHeight / targetHeight);
+
+ final int xCropAmountHalved = (int) (scaleBy * targetWidth / 2);
+ final int yCropAmountHalved = (int) (scaleBy * targetHeight / 2);
+
+ final Rect src = new Rect(
+ inputWidth / 2 - xCropAmountHalved,
+ inputHeight / 2 - yCropAmountHalved,
+ inputWidth / 2 + xCropAmountHalved,
+ inputHeight / 2 + yCropAmountHalved);
+
+ final RectF dst = new RectF(0, 0, targetWidth, targetHeight);
+ canvas.drawBitmap(input, src, dst, paint);
+ return result;
+ }
}
diff --git a/src/com/android/contacts/common/util/ContactDisplayUtils.java b/src/com/android/contacts/common/util/ContactDisplayUtils.java
index 7ec751a2..bb91b531 100644
--- a/src/com/android/contacts/common/util/ContactDisplayUtils.java
+++ b/src/com/android/contacts/common/util/ContactDisplayUtils.java
@@ -19,10 +19,18 @@ package com.android.contacts.common.util;
import static android.provider.ContactsContract.CommonDataKinds.Phone;
import android.content.Context;
+import android.telephony.PhoneNumberUtils;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.style.TtsSpan;
import android.util.Log;
+import android.util.Patterns;
import com.android.contacts.common.R;
+import com.android.i18n.phonenumbers.NumberParseException;
+import com.android.i18n.phonenumbers.PhoneNumberUtil;
+import com.android.i18n.phonenumbers.Phonenumber.PhoneNumber;
import com.google.common.base.Preconditions;
/**
@@ -187,4 +195,80 @@ public class ContactDisplayUtils {
}
}
+ /**
+ * Whether the given text could be a phone number.
+ *
+ * Note this will miss many things that are legitimate phone numbers, for example,
+ * phone numbers with letters.
+ */
+ public static boolean isPossiblePhoneNumber(CharSequence text) {
+ return text == null ? false : Patterns.PHONE.matcher(text.toString()).matches();
+ }
+
+ /**
+ * Returns a Spannable for the given phone number with a telephone {@link TtsSpan} set over
+ * the entire length of the given phone number.
+ */
+ public static Spannable getTelephoneTtsSpannable(String phoneNumber) {
+ if (phoneNumber == null) {
+ return null;
+ }
+ final Spannable spannable = new SpannableString(phoneNumber);
+ final TtsSpan ttsSpan = getTelephoneTtsSpan(phoneNumber);
+ spannable.setSpan(ttsSpan, 0, phoneNumber.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ return spannable;
+ }
+
+ /**
+ * Returns a Spannable for the given message with a telephone {@link TtsSpan} set for
+ * the given phone number text wherever it is found within the message.
+ */
+ public static Spannable getTelephoneTtsSpannable(String message, String phoneNumber) {
+ if (message == null) {
+ return null;
+ }
+ final Spannable spannable = new SpannableString(message);
+ int start = phoneNumber == null ? -1 : message.indexOf(phoneNumber);
+ while (start >= 0) {
+ final int end = start + phoneNumber.length();
+ final TtsSpan ttsSpan = getTelephoneTtsSpan(phoneNumber);
+ spannable.setSpan(ttsSpan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ start = message.indexOf(phoneNumber, end);
+ }
+ return spannable;
+ }
+
+ /**
+ * Returns a telephone {@link TtsSpan} for the given phone number.
+ */
+ public static TtsSpan getTelephoneTtsSpan(String phoneNumberString) {
+ if (phoneNumberString == null) {
+ throw new NullPointerException();
+ }
+
+ // Parse the phone number
+ final PhoneNumberUtil phoneNumberUtil = PhoneNumberUtil.getInstance();
+ PhoneNumber phoneNumber = null;
+ try {
+ // Don't supply a defaultRegion so this fails for non-international numbers because
+ // we don't want to TalkBalk to read a country code (e.g. +1) if it is not already
+ // present
+ phoneNumber = phoneNumberUtil.parse(phoneNumberString, /* defaultRegion */ null);
+ } catch (NumberParseException ignored) {
+ }
+
+ // Build a telephone tts span
+ final TtsSpan.TelephoneBuilder builder = new TtsSpan.TelephoneBuilder();
+ if (phoneNumber == null) {
+ // Strip separators otherwise TalkBack will be silent
+ // (this behavior was observed with TalkBalk 4.0.2 from their alpha channel)
+ builder.setNumberParts(PhoneNumberUtils.stripSeparators(phoneNumberString));
+ } else {
+ if (phoneNumber.hasCountryCode()) {
+ builder.setCountryCode(Integer.toString(phoneNumber.getCountryCode()));
+ }
+ builder.setNumberParts(Long.toString(phoneNumber.getNationalNumber()));
+ }
+ return builder.build();
+ }
}
diff --git a/src/com/android/contacts/common/util/LocalizedNameResolver.java b/src/com/android/contacts/common/util/LocalizedNameResolver.java
index 3c21946a..92104c44 100644
--- a/src/com/android/contacts/common/util/LocalizedNameResolver.java
+++ b/src/com/android/contacts/common/util/LocalizedNameResolver.java
@@ -19,10 +19,8 @@ package com.android.contacts.common.util;
import android.accounts.AccountManager;
import android.accounts.AuthenticatorDescription;
import android.content.Context;
-import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.ServiceInfo;
import android.content.res.Resources;
import android.content.res.Resources.NotFoundException;
import android.content.res.TypedArray;
@@ -32,6 +30,7 @@ import android.util.Log;
import android.util.Xml;
import com.android.contacts.common.R;
+import com.android.contacts.common.model.account.ExternalAccountType;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -45,11 +44,6 @@ import java.io.IOException;
public class LocalizedNameResolver {
private static final String TAG = "LocalizedNameResolver";
- /**
- * Meta-data key for the contacts configuration associated with a sync service.
- */
- private static final String METADATA_CONTACTS = "android.provider.CONTACTS_STRUCTURE";
-
private static final String CONTACTS_DATA_KIND = "ContactsDataKind";
/**
@@ -82,20 +76,9 @@ public class LocalizedNameResolver {
* reads the picture priority from that file.
*/
private static String resolveAllContactsNameFromMetaData(Context context, String packageName) {
- final PackageManager pm = context.getPackageManager();
- try {
- PackageInfo pi = pm.getPackageInfo(packageName, PackageManager.GET_SERVICES
- | PackageManager.GET_META_DATA);
- if (pi != null && pi.services != null) {
- for (ServiceInfo si : pi.services) {
- final XmlResourceParser parser = si.loadXmlMetaData(pm, METADATA_CONTACTS);
- if (parser != null) {
- return loadAllContactsNameFromXml(context, parser, packageName);
- }
- }
- }
- } catch (NameNotFoundException e) {
- Log.w(TAG, "Problem loading \"All Contacts\"-name: " + e.toString());
+ final XmlResourceParser parser = ExternalAccountType.loadContactsXml(context, packageName);
+ if (parser != null) {
+ return loadAllContactsNameFromXml(context, parser, packageName);
}
return null;
}
diff --git a/src/com/android/contacts/common/util/MaterialColorMapUtils.java b/src/com/android/contacts/common/util/MaterialColorMapUtils.java
index 7827ae36..a8fbf421 100644
--- a/src/com/android/contacts/common/util/MaterialColorMapUtils.java
+++ b/src/com/android/contacts/common/util/MaterialColorMapUtils.java
@@ -25,7 +25,6 @@ import android.os.Parcelable;
import android.os.Trace;
public class MaterialColorMapUtils {
-
private final TypedArray sPrimaryColors;
private final TypedArray sSecondaryColors;
diff --git a/src/com/android/contacts/common/vcard/ExportVCardActivity.java b/src/com/android/contacts/common/vcard/ExportVCardActivity.java
index 625412e8..029561d0 100644
--- a/src/com/android/contacts/common/vcard/ExportVCardActivity.java
+++ b/src/com/android/contacts/common/vcard/ExportVCardActivity.java
@@ -30,6 +30,8 @@ import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
+import android.text.BidiFormatter;
+import android.text.TextDirectionHeuristics;
import android.text.TextUtils;
import android.util.Log;
@@ -113,6 +115,7 @@ public class ExportVCardActivity extends Activity implements ServiceConnection,
private VCardService mService;
private final Messenger mIncomingMessenger = new Messenger(new IncomingHandler());
+ private static final BidiFormatter mBidiFormatter = BidiFormatter.getInstance();
// Used temporarily when asking users to confirm the file name
private String mTargetFileName;
@@ -268,13 +271,25 @@ public class ExportVCardActivity extends Activity implements ServiceConnection,
}
}
+ /**
+ * Returns the name of the target path with additional formatting characters to improve its
+ * appearance in bidirectional text.
+ */
+ private String getTargetFileForDisplay() {
+ if (mTargetFileName == null) {
+ return null;
+ }
+ return mBidiFormatter.unicodeWrap(mTargetFileName, TextDirectionHeuristics.LTR);
+ }
+
@Override
protected Dialog onCreateDialog(int id, Bundle bundle) {
switch (id) {
case R.id.dialog_export_confirmation: {
return new AlertDialog.Builder(this)
.setTitle(R.string.confirm_export_title)
- .setMessage(getString(R.string.confirm_export_message, mTargetFileName))
+ .setMessage(getString(R.string.confirm_export_message,
+ getTargetFileForDisplay()))
.setPositiveButton(android.R.string.ok,
new ExportConfirmationListener(mTargetFileName))
.setNegativeButton(android.R.string.cancel, this)
@@ -318,7 +333,7 @@ public class ExportVCardActivity extends Activity implements ServiceConnection,
((AlertDialog)dialog).setMessage(mErrorReason);
} else if (id == R.id.dialog_export_confirmation) {
((AlertDialog)dialog).setMessage(
- getString(R.string.confirm_export_message, mTargetFileName));
+ getString(R.string.confirm_export_message, getTargetFileForDisplay()));
} else {
super.onPrepareDialog(id, dialog, args);
}
diff --git a/src/com/android/contacts/common/vcard/ImportProcessor.java b/src/com/android/contacts/common/vcard/ImportProcessor.java
index 37128755..219ec144 100644
--- a/src/com/android/contacts/common/vcard/ImportProcessor.java
+++ b/src/com/android/contacts/common/vcard/ImportProcessor.java
@@ -194,14 +194,14 @@ public class ImportProcessor extends ProcessorBase implements VCardEntryHandler
Log.i(LOG_TAG, "Successfully finished importing one vCard file: " + uri);
List<Uri> uris = committer.getCreatedUris();
if (mListener != null) {
- if (uris != null && uris.size() > 0) {
- // TODO: construct intent showing a list of imported contact list.
+ if (uris != null && uris.size() == 1) {
mListener.onImportFinished(mImportRequest, mJobId, uris.get(0));
} else {
- // Not critical, but suspicious.
- Log.w(LOG_TAG,
- "Created Uris is null or 0 length " +
- "though the creation itself is successful.");
+ if (uris == null || uris.size() == 0) {
+ // Not critical, but suspicious.
+ Log.w(LOG_TAG, "Created Uris is null or 0 length " +
+ "though the creation itself is successful.");
+ }
mListener.onImportFinished(mImportRequest, mJobId, null);
}
}
diff --git a/src/com/android/contacts/common/vcard/ImportVCardActivity.java b/src/com/android/contacts/common/vcard/ImportVCardActivity.java
index 24f24dd1..d36dcaf3 100644
--- a/src/com/android/contacts/common/vcard/ImportVCardActivity.java
+++ b/src/com/android/contacts/common/vcard/ImportVCardActivity.java
@@ -110,8 +110,6 @@ public class ImportVCardActivity extends Activity {
final static String CACHED_URIS = "cached_uris";
- private AccountSelectionUtil.AccountSelectedListener mAccountSelectionListener;
-
private AccountWithDataSet mAccount;
private ProgressDialog mProgressDialogForScanVCard;
@@ -946,14 +944,6 @@ public class ImportVCardActivity extends Activity {
@Override
protected Dialog onCreateDialog(int resId, Bundle bundle) {
switch (resId) {
- case R.string.import_from_sdcard: {
- if (mAccountSelectionListener == null) {
- throw new NullPointerException(
- "mAccountSelectionListener must not be null.");
- }
- return AccountSelectionUtil.getSelectAccountDialog(this, resId,
- mAccountSelectionListener, mCancelListener);
- }
case R.id.dialog_searching_vcard: {
if (mProgressDialogForScanVCard == null) {
String message = getString(R.string.searching_vcard_message);
diff --git a/src/com/android/contacts/common/vcard/NotificationImportExportListener.java b/src/com/android/contacts/common/vcard/NotificationImportExportListener.java
index 7117f9f5..63420026 100644
--- a/src/com/android/contacts/common/vcard/NotificationImportExportListener.java
+++ b/src/com/android/contacts/common/vcard/NotificationImportExportListener.java
@@ -26,12 +26,15 @@ import android.content.Intent;
import android.net.Uri;
import android.os.Handler;
import android.os.Message;
+import android.provider.ContactsContract;
import android.provider.ContactsContract.RawContacts;
import android.widget.Toast;
import com.android.contacts.common.R;
import com.android.vcard.VCardEntry;
+import java.text.NumberFormat;
+
public class NotificationImportExportListener implements VCardImportExportListener,
Handler.Callback {
/** The tag used by vCard-related notifications. */
@@ -123,7 +126,8 @@ public class NotificationImportExportListener implements VCardImportExportListen
RawContacts.CONTENT_URI, rawContactId));
intent = new Intent(Intent.ACTION_VIEW, contactUri);
} else {
- intent = null;
+ intent = new Intent(Intent.ACTION_VIEW);
+ intent.setType(ContactsContract.Contacts.CONTENT_TYPE);
}
final Notification notification =
NotificationImportExportListener.constructFinishNotification(mContext,
@@ -218,13 +222,15 @@ public class NotificationImportExportListener implements VCardImportExportListen
.setProgress(totalCount, currentCount, totalCount == - 1)
.setTicker(tickerText)
.setContentTitle(description)
+ .setColor(context.getResources().getColor(R.color.dialtacts_theme_color))
.setSmallIcon(type == VCardService.TYPE_IMPORT
? android.R.drawable.stat_sys_download
: android.R.drawable.stat_sys_upload)
.setContentIntent(PendingIntent.getActivity(context, 0, intent, 0));
if (totalCount > 0) {
- builder.setContentText(context.getString(R.string.percentage,
- String.valueOf(currentCount * 100 / totalCount)));
+ String percentage =
+ NumberFormat.getPercentInstance().format((double) currentCount / totalCount);
+ builder.setContentText(percentage);
}
return builder.getNotification();
}
@@ -240,6 +246,7 @@ public class NotificationImportExportListener implements VCardImportExportListen
return new Notification.Builder(context)
.setAutoCancel(true)
.setSmallIcon(android.R.drawable.stat_notify_error)
+ .setColor(context.getResources().getColor(R.color.dialtacts_theme_color))
.setContentTitle(description)
.setContentText(description)
.setContentIntent(PendingIntent.getActivity(context, 0, new Intent(), 0))
@@ -260,6 +267,7 @@ public class NotificationImportExportListener implements VCardImportExportListen
.setSmallIcon(type == VCardService.TYPE_IMPORT
? android.R.drawable.stat_sys_download_done
: android.R.drawable.stat_sys_upload_done)
+ .setColor(context.getResources().getColor(R.color.dialtacts_theme_color))
.setContentTitle(title)
.setContentText(description)
.setContentIntent(PendingIntent.getActivity(context, 0,
@@ -277,6 +285,7 @@ public class NotificationImportExportListener implements VCardImportExportListen
Context context, String reason) {
return new Notification.Builder(context)
.setAutoCancel(true)
+ .setColor(context.getResources().getColor(R.color.dialtacts_theme_color))
.setSmallIcon(android.R.drawable.stat_notify_error)
.setContentTitle(context.getString(R.string.vcard_import_failed))
.setContentText(reason)
diff --git a/src/com/android/contacts/common/vcard/VCardService.java b/src/com/android/contacts/common/vcard/VCardService.java
index f11598a1..7b90eddb 100644
--- a/src/com/android/contacts/common/vcard/VCardService.java
+++ b/src/com/android/contacts/common/vcard/VCardService.java
@@ -38,6 +38,7 @@ import java.io.File;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
+import java.util.Locale;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -496,7 +497,8 @@ public class VCardService extends Service {
* This method increments "index" part from 1 to maximum, and checks whether any file name
* following naming rule is available. If there's no file named /mnt/sdcard/00001.vcf, the
* name will be returned to a caller. If there are 00001.vcf 00002.vcf, 00003.vcf is
- * returned.
+ * returned. We format these numbers in the US locale to ensure we they appear as
+ * english numerals.
*
* There may not be any appropriate file name. If there are 99999 vCard files in the
* storage, for example, there's no appropriate name, so this method returns
@@ -519,7 +521,7 @@ public class VCardService extends Service {
if (!ALLOW_LONG_FILE_NAME) {
final String possibleBody =
- String.format(bodyFormat, mFileNamePrefix, 1, mFileNameSuffix);
+ String.format(Locale.US, bodyFormat, mFileNamePrefix, 1, mFileNameSuffix);
if (possibleBody.length() > 8 || mFileNameExtension.length() > 3) {
Log.e(LOG_TAG, "This code does not allow any long file name.");
mErrorReason = getString(R.string.fail_reason_too_long_filename,
@@ -531,7 +533,8 @@ public class VCardService extends Service {
for (int i = mFileIndexMinimum; i <= mFileIndexMaximum; i++) {
boolean numberIsAvailable = true;
- final String body = String.format(bodyFormat, mFileNamePrefix, i, mFileNameSuffix);
+ final String body
+ = String.format(Locale.US, bodyFormat, mFileNamePrefix, i, mFileNameSuffix);
// Make sure that none of the extensions of mExtensionsToConsider matches. If this
// number is free, we'll go ahead with mFileNameExtension (which is included in
// mExtensionsToConsider)
diff --git a/src/com/android/contacts/common/widget/SelectPhoneAccountDialogFragment.java b/src/com/android/contacts/common/widget/SelectPhoneAccountDialogFragment.java
new file mode 100644
index 00000000..08702543
--- /dev/null
+++ b/src/com/android/contacts/common/widget/SelectPhoneAccountDialogFragment.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2014 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.common.widget;
+
+import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.app.FragmentManager;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.telecom.TelecomManager;
+import android.telephony.PhoneNumberUtils;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.ListAdapter;
+import android.widget.TextView;
+
+import com.android.contacts.common.R;
+
+import java.util.List;
+
+/**
+ * Dialog that allows the user to select a phone accounts for a given action. Optionally provides
+ * the choice to set the phone account as default.
+ */
+public class SelectPhoneAccountDialogFragment extends DialogFragment {
+ private int mTitleResId;
+ private boolean mCanSetDefault;
+ private List<PhoneAccountHandle> mAccountHandles;
+ private boolean mIsSelected;
+ private boolean mIsDefaultChecked;
+ private TelecomManager mTelecomManager;
+ private SelectPhoneAccountListener mListener;
+
+ /**
+ * Shows the account selection dialog.
+ * This is the preferred way to show this dialog.
+ *
+ * @param fragmentManager The fragment manager.
+ * @param accountHandles The {@code PhoneAccountHandle}s available to select from.
+ * @param listener The listener for the results of the account selection.
+ */
+ public static void showAccountDialog(FragmentManager fragmentManager,
+ List<PhoneAccountHandle> accountHandles, SelectPhoneAccountListener listener) {
+ showAccountDialog(fragmentManager, R.string.select_account_dialog_title, false,
+ accountHandles, listener);
+ }
+
+ /**
+ * Shows the account selection dialog.
+ * This is the preferred way to show this dialog.
+ * This method also allows specifying a custom title and "set default" checkbox.
+ *
+ * @param fragmentManager The fragment manager.
+ * @param titleResId The resource ID for the string to use in the title of the dialog.
+ * @param canSetDefault {@code true} if the dialog should include an option to set the selection
+ * as the default. False otherwise.
+ * @param accountHandles The {@code PhoneAccountHandle}s available to select from.
+ * @param listener The listener for the results of the account selection.
+ */
+ public static void showAccountDialog(FragmentManager fragmentManager, int titleResId,
+ boolean canSetDefault, List<PhoneAccountHandle> accountHandles,
+ SelectPhoneAccountListener listener) {
+ SelectPhoneAccountDialogFragment fragment =
+ new SelectPhoneAccountDialogFragment(
+ titleResId, canSetDefault, accountHandles, listener);
+ fragment.show(fragmentManager, "selectAccount");
+ }
+
+ public SelectPhoneAccountDialogFragment(int titleResId, boolean canSetDefault,
+ List<PhoneAccountHandle> accountHandles, SelectPhoneAccountListener listener) {
+ super();
+ mTitleResId = titleResId;
+ mCanSetDefault = canSetDefault;
+ mAccountHandles = accountHandles;
+ mListener = listener;
+ }
+
+ public interface SelectPhoneAccountListener {
+ void onPhoneAccountSelected(PhoneAccountHandle selectedAccountHandle, boolean setDefault);
+ void onDialogDismissed();
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ mIsSelected = false;
+ mIsDefaultChecked = false;
+ mTelecomManager =
+ (TelecomManager) getActivity().getSystemService(Context.TELECOM_SERVICE);
+
+ final DialogInterface.OnClickListener selectionListener =
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ mIsSelected = true;
+ PhoneAccountHandle selectedAccountHandle = mAccountHandles.get(which);
+ mListener.onPhoneAccountSelected(selectedAccountHandle, mIsDefaultChecked);
+ }
+ };
+
+ final CompoundButton.OnCheckedChangeListener checkListener =
+ new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton check, boolean isChecked) {
+ mIsDefaultChecked = isChecked;
+ }
+ };
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+ ListAdapter selectAccountListAdapter = new SelectAccountListAdapter(
+ builder.getContext(),
+ R.layout.select_account_list_item,
+ mAccountHandles);
+
+ AlertDialog dialog = builder.setTitle(mTitleResId)
+ .setAdapter(selectAccountListAdapter, selectionListener)
+ .create();
+
+ if (mCanSetDefault) {
+ // Generate custom checkbox view
+ LinearLayout checkboxLayout = (LinearLayout) getActivity()
+ .getLayoutInflater()
+ .inflate(R.layout.default_account_checkbox, null);
+
+ CheckBox cb =
+ (CheckBox) checkboxLayout.findViewById(R.id.default_account_checkbox_view);
+ cb.setOnCheckedChangeListener(checkListener);
+
+ dialog.getListView().addFooterView(checkboxLayout);
+ }
+
+ return dialog;
+ }
+
+ private class SelectAccountListAdapter extends ArrayAdapter<PhoneAccountHandle> {
+ private int mResId;
+
+ public SelectAccountListAdapter(
+ Context context, int resource, List<PhoneAccountHandle> accountHandles) {
+ super(context, resource, accountHandles);
+ mResId = resource;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ LayoutInflater inflater = (LayoutInflater)
+ getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+
+ View rowView;
+ final ViewHolder holder;
+
+ if (convertView == null) {
+ // Cache views for faster scrolling
+ rowView = inflater.inflate(mResId, null);
+ holder = new ViewHolder();
+ holder.labelTextView = (TextView) rowView.findViewById(R.id.label);
+ holder.numberTextView = (TextView) rowView.findViewById(R.id.number);
+ holder.imageView = (ImageView) rowView.findViewById(R.id.icon);
+ rowView.setTag(holder);
+ }
+ else {
+ rowView = convertView;
+ holder = (ViewHolder) rowView.getTag();
+ }
+
+ PhoneAccountHandle accountHandle = getItem(position);
+ PhoneAccount account = mTelecomManager.getPhoneAccount(accountHandle);
+ holder.labelTextView.setText(account.getLabel());
+ if (account.getAddress() == null ||
+ TextUtils.isEmpty(account.getAddress().getSchemeSpecificPart())) {
+ holder.numberTextView.setVisibility(View.GONE);
+ } else {
+ holder.numberTextView.setVisibility(View.VISIBLE);
+ holder.numberTextView.setText(
+ PhoneNumberUtils.ttsSpanAsPhoneNumber(
+ account.getAddress().getSchemeSpecificPart()));
+ }
+ holder.imageView.setImageDrawable(account.createIconDrawable(getContext()));
+ return rowView;
+ }
+
+ private class ViewHolder {
+ TextView labelTextView;
+ TextView numberTextView;
+ ImageView imageView;
+ }
+ }
+
+ @Override
+ public void onPause() {
+ if (!mIsSelected) {
+ mListener.onDialogDismissed();
+ }
+ super.onPause();
+ }
+}
diff --git a/src/com/android/contacts/commonbind/analytics/AnalyticsUtil.java b/src/com/android/contacts/commonbind/analytics/AnalyticsUtil.java
new file mode 100644
index 00000000..59650aa9
--- /dev/null
+++ b/src/com/android/contacts/commonbind/analytics/AnalyticsUtil.java
@@ -0,0 +1,25 @@
+package com.android.contacts.commonbind.analytics;
+
+import android.app.Activity;
+import android.app.Application;
+import android.app.Fragment;
+import android.text.TextUtils;
+
+public class AnalyticsUtil {
+
+ /**
+ * Initialize this class and setup automatic activity tracking.
+ */
+ public static void initialize(Application application) { }
+
+ /**
+ * Log a screen view for {@param fragment}.
+ */
+ public static void sendScreenView(Fragment fragment) {}
+
+ public static void sendScreenView(Fragment fragment, Activity activity) {}
+
+ public static void sendScreenView(Fragment fragment, Activity activity, String tag) {}
+
+ public static void sendScreenView(String fragmentName, Activity activity, String tag) {}
+} \ No newline at end of file