Merge "Use photoId of the raw contact in aggregation engine" into ub-contactsdialer-h-dev
diff --git a/res/layout/fragment_sim_import.xml b/res/layout/fragment_sim_import.xml
index d7a83f5..6688898 100644
--- a/res/layout/fragment_sim_import.xml
+++ b/res/layout/fragment_sim_import.xml
@@ -56,8 +56,8 @@
android:background="?android:colorBackground"
android:minHeight="48dp"
android:orientation="horizontal"
- android:paddingEnd="16dp"
- android:paddingStart="16dp">
+ android:paddingEnd="@dimen/activity_horizontal_margin"
+ android:paddingStart="@dimen/activity_horizontal_margin">
<ImageView
android:id="@+id/account_type_icon"
@@ -84,7 +84,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
- android:layout_marginEnd="10dp"
+ android:layout_marginEnd="5dp"
android:background="@null"
android:clickable="true"
android:contentDescription="@string/show_more_content_description"
diff --git a/res/layout/nav_header_main.xml b/res/layout/nav_header_main.xml
index 37a33f4..518e6e6 100644
--- a/res/layout/nav_header_main.xml
+++ b/res/layout/nav_header_main.xml
@@ -23,9 +23,9 @@
android:background="@color/contacts_accent_color"
android:gravity="bottom"
android:orientation="vertical"
- android:paddingBottom="@dimen/nav_activity_vertical_margin"
- android:paddingLeft="@dimen/nav_activity_horizontal_margin"
- android:paddingRight="@dimen/nav_activity_horizontal_margin"
- android:paddingTop="@dimen/nav_activity_vertical_margin"
+ android:paddingBottom="@dimen/activity_vertical_margin"
+ android:paddingLeft="@dimen/activity_horizontal_margin"
+ android:paddingRight="@dimen/activity_horizontal_margin"
+ android:paddingTop="@dimen/activity_vertical_margin"
android:theme="@style/ThemeOverlay.AppCompat.Dark">
</LinearLayout>
diff --git a/res/layout/sim_import_list_item.xml b/res/layout/sim_import_list_item.xml
new file mode 100644
index 0000000..86f427e
--- /dev/null
+++ b/res/layout/sim_import_list_item.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<CheckedTextView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/text"
+ android:layout_width="match_parent"
+ android:layout_height="?android:listPreferredItemHeight"
+ android:checkMark="?android:listChoiceIndicatorMultiple"
+ android:duplicateParentState="true"
+ android:gravity="center_vertical"
+ android:paddingEnd="@dimen/activity_horizontal_margin"
+ android:paddingStart="@dimen/activity_horizontal_margin"
+ android:textAppearance="?android:textAppearanceListItem"/>
diff --git a/res/layout/sim_import_list_item_disabled.xml b/res/layout/sim_import_list_item_disabled.xml
new file mode 100644
index 0000000..4647049
--- /dev/null
+++ b/res/layout/sim_import_list_item_disabled.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/text"
+ style="?android:checkedTextViewStyle"
+ android:layout_width="match_parent"
+ android:layout_height="?android:listPreferredItemHeight"
+ android:drawableEnd="?android:listChoiceIndicatorMultiple"
+ android:enabled="false"
+ android:gravity="center_vertical"
+ android:orientation="vertical"
+ android:paddingEnd="@dimen/activity_horizontal_margin"
+ android:paddingStart="@dimen/activity_horizontal_margin"
+ android:textAppearance="?android:textAppearanceListItem"
+ android:textColor="@color/contact_list_name_text_color"/>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index de9764d..d434ef2 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -244,9 +244,9 @@
<!-- Navigation drawer header height, the same as the status bar in landscape and portrait modes -->
<dimen name="nav_header_height">24dp</dimen>
- <!-- Navigation drawer margins, per the Android Design guidelines. -->
- <dimen name="nav_activity_horizontal_margin">16dp</dimen>
- <dimen name="nav_activity_vertical_margin">16dp</dimen>
+ <!-- Default activity margins, per the Android Material Design guidelines. -->
+ <dimen name="activity_horizontal_margin">16dp</dimen>
+ <dimen name="activity_vertical_margin">16dp</dimen>
<dimen name="nav_new_badge_corners">3dp</dimen>
<dimen name="nav_new_badge_vertical_pad">2dp</dimen>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 81871e4..a9f1dc4 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1337,13 +1337,13 @@
fields corresponding to each part of the name (Name Prefix, First Name,
Middle Name, Last Name, Name Suffix).
[CHAR LIMIT=NONE] -->
- <string name="expand_name_fields_description">Expand name fields</string>
+ <string name="expand_name_fields_description">Show more name fields</string>
<!-- Content description for the collapse name fields button. [CHAR LIMIT=NONE] -->
<string name="collapse_name_fields_description">Collapse name fields</string>
<!-- Content description for the expand phonetic name fields button. [CHAR LIMIT=NONE] -->
- <string name="expand_phonetic_name_fields_description">Expand phonetic name fields</string>
+ <string name="expand_phonetic_name_fields_description">Show more phonetic name fields</string>
<!-- Content description for the collapse phonetic name fields button. [CHAR LIMIT=NONE] -->
<string name="collapse_phonetic_name_fields_description">Collapse phonetic name fields</string>
diff --git a/src/com/android/contacts/SimImportFragment.java b/src/com/android/contacts/SimImportFragment.java
index a873842..5f5de3e 100644
--- a/src/com/android/contacts/SimImportFragment.java
+++ b/src/com/android/contacts/SimImportFragment.java
@@ -20,43 +20,40 @@
import android.app.LoaderManager;
import android.content.AsyncTaskLoader;
import android.content.Context;
-import android.content.CursorLoader;
import android.content.Loader;
-import android.database.Cursor;
import android.os.Bundle;
+import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.widget.Snackbar;
import android.support.v4.util.ArrayMap;
import android.support.v4.view.ViewCompat;
import android.support.v4.widget.ContentLoadingProgressBar;
import android.support.v7.widget.Toolbar;
+import android.util.SparseBooleanArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
import android.widget.ListView;
+import android.widget.TextView;
-import com.android.contacts.common.ContactPhotoManager;
import com.android.contacts.common.compat.CompatUtils;
import com.android.contacts.common.database.SimContactDao;
-import com.android.contacts.common.list.ContactListAdapter;
-import com.android.contacts.common.list.ContactListItemView;
-import com.android.contacts.common.list.MultiSelectEntryContactListAdapter;
import com.android.contacts.common.model.AccountTypeManager;
import com.android.contacts.common.model.SimCard;
import com.android.contacts.common.model.SimContact;
import com.android.contacts.common.model.account.AccountWithDataSet;
import com.android.contacts.common.preference.ContactsPreferences;
import com.android.contacts.editor.AccountHeaderPresenter;
-import com.google.common.primitives.Longs;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
-import java.util.TreeSet;
/**
* Dialog that presents a list of contacts from a SIM card that can be imported into a selected
@@ -64,10 +61,11 @@
*/
public class SimImportFragment extends DialogFragment
implements LoaderManager.LoaderCallbacks<SimImportFragment.LoaderResult>,
- MultiSelectEntryContactListAdapter.SelectedContactsListener, AbsListView.OnScrollListener {
+ AdapterView.OnItemClickListener, AbsListView.OnScrollListener {
private static final String KEY_SUFFIX_SELECTED_IDS = "_selectedIds";
private static final String ARG_SUBSCRIPTION_ID = "subscriptionId";
+ private static final String TAG = "SimImportFragment";
private ContactsPreferences mPreferences;
private AccountTypeManager mAccountTypeManager;
@@ -80,6 +78,8 @@
private ListView mListView;
private View mImportButton;
+ private final Map<AccountWithDataSet, long[]> mPerAccountCheckedIds = new ArrayMap<>();
+
private int mSubscriptionId;
@Override
@@ -91,12 +91,6 @@
mAccountTypeManager = AccountTypeManager.getInstance(getActivity());
mAdapter = new SimContactAdapter(getActivity());
- // This needs to be set even though photos aren't loaded because the adapter assumes it
- // will be non-null
- mAdapter.setPhotoLoader(ContactPhotoManager.getInstance(getActivity()));
- mAdapter.setDisplayCheckBoxes(true);
- mAdapter.setHasHeader(0, false);
-
final Bundle args = getArguments();
mSubscriptionId = args == null ? SimCard.NO_SUBSCRIPTION_ID :
args.getInt(ARG_SUBSCRIPTION_ID, SimCard.NO_SUBSCRIPTION_ID);
@@ -137,7 +131,10 @@
mAccountHeaderPresenter.setObserver(new AccountHeaderPresenter.Observer() {
@Override
public void onChange(AccountHeaderPresenter sender) {
+ rememberSelectionsForCurrentAccount();
mAdapter.setAccount(sender.getCurrentAccount());
+ showSelectionsForCurrentAccount();
+ updateToolbarWithCurrentSelections();
}
});
mAdapter.setAccount(mAccountHeaderPresenter.getCurrentAccount());
@@ -146,17 +143,8 @@
mListView = (ListView) view.findViewById(R.id.list);
mListView.setOnScrollListener(this);
mListView.setAdapter(mAdapter);
- mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
- @Override
- public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- if (mAdapter.existsInCurrentAccount(position)) {
- Snackbar.make(getView(), R.string.sim_import_contact_exists_toast,
- Snackbar.LENGTH_LONG).show();
- } else {
- mAdapter.toggleSelectionOfContactId(id);
- }
- }
- });
+ mListView.setChoiceMode(AbsListView.CHOICE_MODE_MULTIPLE);
+ mListView.setOnItemClickListener(this);
mImportButton = view.findViewById(R.id.import_button);
mImportButton.setOnClickListener(new View.OnClickListener() {
@Override
@@ -166,8 +154,6 @@
dismiss();
}
});
- mImportButton.setVisibility(mAdapter.getSelectedContactIds().size() > 0 ?
- View.VISIBLE : View.GONE);
mToolbar = (Toolbar) view.findViewById(R.id.toolbar);
mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
@@ -178,11 +164,55 @@
});
mLoadingIndicator = (ContentLoadingProgressBar) view.findViewById(R.id.loading_progress);
- mAdapter.setSelectedContactsListener(this);
return view;
}
+ private void rememberSelectionsForCurrentAccount() {
+ final AccountWithDataSet current = mAdapter.getAccount();
+ final long[] ids = mListView.getCheckedItemIds();
+ Arrays.sort(ids);
+ mPerAccountCheckedIds.put(current, ids);
+ }
+
+ private void showSelectionsForCurrentAccount() {
+ final long[] ids = mPerAccountCheckedIds.get(mAdapter.getAccount());
+ if (ids == null) {
+ selectAll();
+ return;
+ }
+ for (int i = 0, len = mListView.getCount(); i < len; i++) {
+ mListView.setItemChecked(i,
+ Arrays.binarySearch(ids, mListView.getItemIdAtPosition(i)) >= 0);
+ }
+ }
+
+ private void selectAll() {
+ for (int i = 0, len = mListView.getCount(); i < len; i++) {
+ mListView.setItemChecked(i, true);
+ }
+ }
+
+ private void updateToolbarWithCurrentSelections() {
+ // The ListView keeps checked state for items that are disabled but we only want to
+ // consider items that don't exist in the current account when updating the toolbar
+ int importableCount = 0;
+ final SparseBooleanArray checked = mListView.getCheckedItemPositions();
+ for (int i = 0; i < checked.size(); i++) {
+ if (checked.valueAt(i) && !mAdapter.existsInCurrentAccount(i)) {
+ importableCount++;
+ }
+ }
+
+ if (importableCount == 0) {
+ mImportButton.setVisibility(View.GONE);
+ mToolbar.setTitle(R.string.sim_import_title_none_selected);
+ } else {
+ mToolbar.setTitle(String.valueOf(importableCount));
+ mImportButton.setVisibility(View.VISIBLE);
+ }
+ }
+
@Override
public void onStart() {
super.onStart();
@@ -195,6 +225,7 @@
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
mAccountHeaderPresenter.onSaveInstanceState(outState);
+ rememberSelectionsForCurrentAccount();
saveAdapterSelectedStates(outState);
}
@@ -207,11 +238,14 @@
public void onLoadFinished(Loader<LoaderResult> loader,
LoaderResult data) {
mLoadingIndicator.hide();
- mListView.setEmptyView(getView().findViewById(R.id.empty_message));
if (data == null) {
return;
}
mAdapter.setData(data);
+ mListView.setEmptyView(getView().findViewById(R.id.empty_message));
+
+ showSelectionsForCurrentAccount();
+ updateToolbarWithCurrentSelections();
}
@Override
@@ -227,9 +261,7 @@
for (AccountWithDataSet account : accounts) {
final long[] selections = savedInstanceState.getLongArray(
account.stringify() + KEY_SUFFIX_SELECTED_IDS);
- if (selections != null) {
- mAdapter.setSelectionsForAccount(account, selections);
- }
+ mPerAccountCheckedIds.put(account, selections);
}
}
@@ -239,35 +271,34 @@
}
// Make sure the selections are up-to-date
- mAdapter.storeCurrentSelections();
- for (Map.Entry<AccountWithDataSet, TreeSet<Long>> entry :
- mAdapter.getSelectedIds().entrySet()) {
- final long[] ids = Longs.toArray(entry.getValue());
- outState.putLongArray(entry.getKey().stringify() + KEY_SUFFIX_SELECTED_IDS, ids);
+ for (Map.Entry<AccountWithDataSet, long[]> entry : mPerAccountCheckedIds.entrySet()) {
+ outState.putLongArray(entry.getKey().stringify() + KEY_SUFFIX_SELECTED_IDS,
+ entry.getValue());
}
}
private void importCurrentSelections() {
+ final SparseBooleanArray checked = mListView.getCheckedItemPositions();
+ final ArrayList<SimContact> importableContacts = new ArrayList<>(checked.size());
+ for (int i = 0; i < checked.size(); i++) {
+ // It's possible for existing contacts to be "checked" but we only want to import the
+ // ones that don't already exist
+ if (checked.valueAt(i) && !mAdapter.existsInCurrentAccount(i)) {
+ importableContacts.add(mAdapter.getItem(i));
+ }
+ }
ContactSaveService.startService(getContext(), ContactSaveService
.createImportFromSimIntent(getContext(), mSubscriptionId,
- mAdapter.getSelectedContacts(),
+ importableContacts,
mAccountHeaderPresenter.getCurrentAccount()));
}
- @Override
- public void onSelectedContactsChanged() {
- updateSelectedCount();
- }
-
- private void updateSelectedCount() {
- final int selectedCount = mAdapter.getSelectedContactIds().size();
- if (selectedCount == 0) {
- mToolbar.setTitle(R.string.sim_import_title_none_selected);
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ if (mAdapter.existsInCurrentAccount(position)) {
+ Snackbar.make(getView(), R.string.sim_import_contact_exists_toast,
+ Snackbar.LENGTH_LONG).show();
} else {
- mToolbar.setTitle(String.valueOf(selectedCount));
- }
- if (mImportButton != null) {
- mImportButton.setVisibility(selectedCount > 0 ? View.VISIBLE : View.GONE);
+ updateToolbarWithCurrentSelections();
}
}
@@ -315,108 +346,68 @@
return fragment;
}
- private static class SimContactAdapter extends ContactListAdapter {
- private ArrayList<SimContact> mContacts;
- private AccountWithDataSet mSelectedAccount;
+ private static class SimContactAdapter extends ArrayAdapter<SimContact> {
private Map<AccountWithDataSet, Set<SimContact>> mExistingMap;
- private Map<AccountWithDataSet, TreeSet<Long>> mPerAccountCheckedIds = new ArrayMap<>();
- private final int mCheckboxPaddingEnd;
+ private AccountWithDataSet mSelectedAccount;
+ private LayoutInflater mInflater;
public SimContactAdapter(Context context) {
- super(context);
- mCheckboxPaddingEnd = context.getResources()
- .getDimensionPixelOffset(R.dimen.sim_import_checkbox_end_padding);
+ super(context, 0);
+ mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
@Override
- public void configureLoader(CursorLoader loader, long directoryId) {
+ public long getItemId(int position) {
+ return getItem(position).getId();
}
@Override
- protected void bindView(View itemView, int partition, Cursor cursor, int position) {
- super.bindView(itemView, partition, cursor, position);
- ContactListItemView contactView = (ContactListItemView) itemView;
- bindNameAndViewId(contactView, cursor);
+ public boolean hasStableIds() {
+ return true;
+ }
- // For accessibility. Tapping the item checks this so we don't need it to be separately
- // clickable
- contactView.getCheckBox().setFocusable(false);
- contactView.getCheckBox().setClickable(false);
- // The default list pads the checkbox by a larger amount than we want.
- contactView.setPaddingRelative(contactView.getPaddingStart(),
- contactView.getPaddingTop(), mCheckboxPaddingEnd,
- contactView.getPaddingBottom());
- setViewEnabled(contactView, !existsInCurrentAccount(position));
+ @Override
+ public int getViewTypeCount() {
+ return 2;
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ return !existsInCurrentAccount(position) ? 0 : 1;
+ }
+
+ @NonNull
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ TextView text = (TextView) convertView;
+ if (text == null) {
+ final int layoutRes = existsInCurrentAccount(position) ?
+ R.layout.sim_import_list_item_disabled :
+ R.layout.sim_import_list_item;
+ text = (TextView) mInflater.inflate(layoutRes, parent, false);
+ }
+ text.setText(getItemLabel(getItem(position)));
+
+ return text;
}
public void setData(LoaderResult result) {
- mContacts = result.contacts;
+ clear();
+ addAll(result.contacts);
mExistingMap = result.accountsMap;
- changeCursor(SimContact.convertToContactsCursor(mContacts,
- ContactQuery.CONTACT_PROJECTION_PRIMARY));
- updateDisplayedSelections();
}
public void setAccount(AccountWithDataSet account) {
- if (mContacts == null) {
- mSelectedAccount = account;
- return;
- }
-
- // Save the checked state for the current account.
- storeCurrentSelections();
mSelectedAccount = account;
- updateDisplayedSelections();
- }
-
- public void storeCurrentSelections() {
- if (mSelectedAccount != null) {
- mPerAccountCheckedIds.put(mSelectedAccount, getSelectedContactIds());
- }
- }
-
- public Map<AccountWithDataSet, TreeSet<Long>> getSelectedIds() {
- return mPerAccountCheckedIds;
- }
-
- private void updateDisplayedSelections() {
- if (mContacts == null) {
- return;
- }
-
- TreeSet<Long> checked = mPerAccountCheckedIds.get(mSelectedAccount);
- if (checked == null) {
- checked = getEnabledIdsForCurrentAccount();
- mPerAccountCheckedIds.put(mSelectedAccount, checked);
- }
- setSelectedContactIds(checked);
-
notifyDataSetChanged();
}
- public ArrayList<SimContact> getSelectedContacts() {
- if (mContacts == null) return null;
-
- final Set<Long> selectedIds = getSelectedContactIds();
- final ArrayList<SimContact> selected = new ArrayList<>();
- for (SimContact contact : mContacts) {
- if (selectedIds.contains(contact.getId())) {
- selected.add(contact);
- }
- }
- return selected;
- }
-
- public void setSelectionsForAccount(AccountWithDataSet account, long[] contacts) {
- final TreeSet<Long> selected = new TreeSet<>(Longs.asList(contacts));
- mPerAccountCheckedIds.put(account, selected);
- if (account.equals(mSelectedAccount)) {
- updateDisplayedSelections();
- }
+ public AccountWithDataSet getAccount() {
+ return mSelectedAccount;
}
public boolean existsInCurrentAccount(int position) {
- return existsInCurrentAccount(mContacts.get(position));
+ return existsInCurrentAccount(getItem(position));
}
public boolean existsInCurrentAccount(SimContact contact) {
@@ -426,25 +417,17 @@
return mExistingMap.get(mSelectedAccount).contains(contact);
}
- private TreeSet<Long> getEnabledIdsForCurrentAccount() {
- final TreeSet<Long> result = new TreeSet<>();
- for (SimContact contact : mContacts) {
- if (!existsInCurrentAccount(contact)) {
- result.add(contact.getId());
- }
+ private String getItemLabel(SimContact contact) {
+ if (contact.hasName()) {
+ return contact.getName();
+ } else if (contact.hasPhone()) {
+ return contact.getPhone();
+ } else if (contact.hasEmails()) {
+ return contact.getEmails()[0];
+ } else {
+ // This isn't really possible because we skip empty SIM contacts during loading
+ return "";
}
- return result;
- }
-
- private void setViewEnabled(ContactListItemView itemView, boolean enabled) {
- itemView.getCheckBox().setEnabled(enabled);
- itemView.getNameTextView().setEnabled(enabled);
- // If the checkbox is left to default it's "unchecked" state will be announced when
- // it is clicked on instead of the snackbar which is not useful.
- int accessibilityMode = enabled ?
- View.IMPORTANT_FOR_ACCESSIBILITY_YES :
- View.IMPORTANT_FOR_ACCESSIBILITY_NO;
- itemView.getCheckBox().setImportantForAccessibility(accessibilityMode);
}
}