Merge "Fix bugs on Contact editor." into ub-contactsdialer-a-dev
diff --git a/src/com/android/contacts/editor/CompactContactEditorFragment.java b/src/com/android/contacts/editor/CompactContactEditorFragment.java
index d073f68..f081d54 100644
--- a/src/com/android/contacts/editor/CompactContactEditorFragment.java
+++ b/src/com/android/contacts/editor/CompactContactEditorFragment.java
@@ -21,17 +21,12 @@
import com.android.contacts.activities.CompactContactEditorActivity;
import com.android.contacts.activities.ContactEditorActivity;
import com.android.contacts.activities.ContactEditorBaseActivity;
-import com.android.contacts.common.model.AccountTypeManager;
import com.android.contacts.common.model.RawContactDelta;
-import com.android.contacts.common.model.RawContactDeltaList;
import com.android.contacts.common.model.ValuesDelta;
-import com.android.contacts.common.model.account.AccountType;
import com.android.contacts.common.model.account.AccountWithDataSet;
-import com.android.contacts.detail.PhotoSelectionHandler;
import com.android.contacts.util.ContactPhotoUtils;
import android.app.Activity;
-import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
@@ -114,7 +109,10 @@
final CompactRawContactsEditorView editorView = getContent();
editorView.setListener(this);
editorView.setState(mState, getMaterialPalette(), mViewIdGenerator, mPhotoId,
- mHasNewContact, mIsUserProfile, mAccountWithDataSet);
+ mReadOnlyDisplayName, mHasNewContact, mIsUserProfile, mAccountWithDataSet);
+ if (mReadOnlyDisplayName != null) {
+ mReadOnlyNameEditorView = editorView.getPrimaryNameEditorView();
+ }
// Set up the photo widget
editorView.setPhotoListener(this);
diff --git a/src/com/android/contacts/editor/CompactKindSectionView.java b/src/com/android/contacts/editor/CompactKindSectionView.java
index 448e7c5..3ebe27b 100644
--- a/src/com/android/contacts/editor/CompactKindSectionView.java
+++ b/src/com/android/contacts/editor/CompactKindSectionView.java
@@ -18,7 +18,9 @@
import android.content.Context;
import android.database.Cursor;
+import android.provider.ContactsContract.CommonDataKinds.Event;
import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
+import android.provider.ContactsContract.CommonDataKinds.Nickname;
import android.provider.ContactsContract.CommonDataKinds.StructuredName;
import android.util.AttributeSet;
import android.view.LayoutInflater;
@@ -30,6 +32,7 @@
import com.android.contacts.R;
import com.android.contacts.common.model.RawContactDelta;
+import com.android.contacts.common.model.RawContactDeltaList;
import com.android.contacts.common.model.RawContactModifier;
import com.android.contacts.common.model.ValuesDelta;
import com.android.contacts.common.model.account.AccountType;
@@ -145,7 +148,7 @@
}
}
- private List<KindSectionData> mKindSectionDataList;
+ private KindSectionDataList mKindSectionDataList;
private ViewIdGenerator mViewIdGenerator;
private CompactRawContactsEditorView.Listener mListener;
@@ -216,24 +219,78 @@
}
/**
+ * Whether this is a name kind section view and all name fields (structured, phonetic,
+ * and nicknames) are empty.
+ */
+ public boolean isEmptyName() {
+ if (!StructuredName.CONTENT_ITEM_TYPE.equals(mKindSectionDataList.getMimeType())) {
+ return false;
+ }
+ for (int i = 0; i < mEditors.getChildCount(); i++) {
+ final View view = mEditors.getChildAt(i);
+ if (view instanceof Editor) {
+ final Editor editor = (Editor) view;
+ if (!editor.isEmpty()) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Sets the given display name as the structured name as if the user input it, but
+ * without informing editor listeners.
+ */
+ public void setName(String displayName) {
+ if (!StructuredName.CONTENT_ITEM_TYPE.equals(mKindSectionDataList.getMimeType())) {
+ return;
+ }
+ for (int i = 0; i < mEditors.getChildCount(); i++) {
+ final View view = mEditors.getChildAt(i);
+ if (view instanceof StructuredNameEditorView) {
+ final StructuredNameEditorView editor = (StructuredNameEditorView) view;
+
+ // Detach listeners since so we don't show suggested aggregations
+ final Editor.EditorListener editorListener = editor.getEditorListener();
+ editor.setEditorListener(null);
+
+ editor.setDisplayName(displayName);
+
+ // Reattach listeners
+ editor.setEditorListener(editorListener);
+
+ return;
+ }
+ }
+ }
+
+ public StructuredNameEditorView getPrimaryNameEditorView() {
+ if (!StructuredName.CONTENT_ITEM_TYPE.equals(mKindSectionDataList.getMimeType())
+ || mEditors.getChildCount() == 0) {
+ return null;
+ }
+ return (StructuredNameEditorView) mEditors.getChildAt(0);
+ }
+
+ /**
* Binds views for the given {@link KindSectionData} list.
*
* We create a structured name and phonetic name editor for each {@link DataKind} with a
- * {@link }StructuredName#CONTENT_ITEM_TYPE} mime type. The number and order of editors are
+ * {@link StructuredName#CONTENT_ITEM_TYPE} mime type. The number and order of editors are
* rendered as they are given to {@link #setState}.
*
* Empty name editors are never added and at least one structured name editor is always
* displayed, even if it is empty.
*/
- public void setState(List<KindSectionData> kindSectionDataList,
+ public void setState(KindSectionDataList kindSectionDataList,
ViewIdGenerator viewIdGenerator, CompactRawContactsEditorView.Listener listener) {
mKindSectionDataList = kindSectionDataList;
mViewIdGenerator = viewIdGenerator;
mListener = listener;
// Set the icon using the first DataKind
- final DataKind dataKind = mKindSectionDataList.isEmpty()
- ? null : mKindSectionDataList.get(0).getDataKind();
+ final DataKind dataKind = mKindSectionDataList.getDataKind();
if (dataKind != null) {
mIcon.setImageDrawable(EditorUiUtils.getMimeTypeDrawable(getContext(),
dataKind.mimeType));
@@ -263,9 +320,9 @@
kindSectionData.getDataKind());
} else {
final Editor.EditorListener editorListener;
- if (kindSectionData.isNicknameDataKind()) {
+ if (Nickname.CONTENT_ITEM_TYPE.equals(kindSectionData.getDataKind().mimeType)) {
editorListener = new OtherNameKindEditorListener();
- } else if (kindSectionData.isEventDataKind()) {
+ } else if (Event.CONTENT_ITEM_TYPE.equals(kindSectionData.getDataKind().mimeType)) {
editorListener = new EventEditorListener();
} else {
editorListener = new NonNameEditorListener();
@@ -379,14 +436,14 @@
* then the entire section is hidden.
*/
public void updateEmptyEditors(boolean shouldAnimate) {
- final boolean isNameKindSection = mKindSectionDataList.get(0).isNameDataKind();
+ final boolean isNameKindSection = StructuredName.CONTENT_ITEM_TYPE.equals(
+ mKindSectionDataList.getMimeType());
final boolean isGroupKindSection = GroupMembership.CONTENT_ITEM_TYPE.equals(
- mKindSectionDataList.get(0).getDataKind().mimeType);
+ mKindSectionDataList.getMimeType());
if (isNameKindSection) {
// The name kind section is always visible
setVisibility(VISIBLE);
-
updateEmptyNameEditors(shouldAnimate);
} else if (isGroupKindSection) {
// Check whether metadata has been bound for all group views
@@ -425,29 +482,37 @@
for (int i = 0; i < mEditors.getChildCount(); i++) {
final View view = mEditors.getChildAt(i);
- if (!(view instanceof Editor)) continue; // Skip read-only names
- final Editor editor = (Editor) view;
- if (view instanceof StructuredNameEditorView) {
- // We always show one empty structured name view
- if (editor.isEmpty()) {
- if (isEmptyNameEditorVisible) {
- // If we're already showing an empty editor then hide any other empties
- if (mHideIfEmpty) {
- view.setVisibility(View.GONE);
+ if (view instanceof Editor) {
+ final Editor editor = (Editor) view;
+ if (view instanceof StructuredNameEditorView) {
+ // We always show one empty structured name view
+ if (editor.isEmpty()) {
+ if (isEmptyNameEditorVisible) {
+ // If we're already showing an empty editor then hide any other empties
+ if (mHideIfEmpty) {
+ view.setVisibility(View.GONE);
+ }
+ } else {
+ isEmptyNameEditorVisible = true;
}
} else {
+ showView(view, shouldAnimate);
isEmptyNameEditorVisible = true;
}
} else {
- showView(view, shouldAnimate);
- isEmptyNameEditorVisible = true;
+ // Since we can't add phonetic names and nicknames, just show or hide them
+ if (mHideIfEmpty && editor.isEmpty()) {
+ hideView(view);
+ } else {
+ showView(view, /* shouldAnimate =*/ false); // Animation here causes jank
+ }
}
} else {
- // For phonetic names and nicknames, which can't be added, just show or hide them
- if (mHideIfEmpty && editor.isEmpty()) {
+ // For read only names, only show them if we're not hiding empty views
+ if (mHideIfEmpty) {
hideView(view);
} else {
- showView(view, /* shouldAnimate =*/ false); // Animation here causes jank
+ showView(view, shouldAnimate);
}
}
}
@@ -488,8 +553,9 @@
final RawContactDelta rawContactDelta =
mKindSectionDataList.get(0).getRawContactDelta();
final ValuesDelta values = RawContactModifier.insertChild(rawContactDelta, dataKind);
- final Editor.EditorListener editorListener = mKindSectionDataList.get(0)
- .isEventDataKind() ? new EventEditorListener() : new NonNameEditorListener();
+ final String mimeType = mKindSectionDataList.getMimeType();
+ final Editor.EditorListener editorListener = Event.CONTENT_ITEM_TYPE.equals(mimeType)
+ ? new EventEditorListener() : new NonNameEditorListener();
final View view = addNonNameEditorView(rawContactDelta, dataKind, values,
editorListener);
showView(view, shouldAnimate);
diff --git a/src/com/android/contacts/editor/CompactRawContactsEditorView.java b/src/com/android/contacts/editor/CompactRawContactsEditorView.java
index a6f2d1c..ed5dc3b 100644
--- a/src/com/android/contacts/editor/CompactRawContactsEditorView.java
+++ b/src/com/android/contacts/editor/CompactRawContactsEditorView.java
@@ -17,7 +17,6 @@
package com.android.contacts.editor;
import com.android.contacts.R;
-import com.android.contacts.common.ContactsUtils;
import com.android.contacts.common.model.AccountTypeManager;
import com.android.contacts.common.model.RawContactDelta;
import com.android.contacts.common.model.RawContactDeltaList;
@@ -28,7 +27,6 @@
import com.android.contacts.common.model.dataitem.DataKind;
import com.android.contacts.common.util.AccountsListAdapter;
import com.android.contacts.common.util.MaterialColorMapUtils;
-import com.android.contacts.util.ContactPhotoUtils;
import com.android.contacts.util.UiClosables;
import android.content.ContentUris;
@@ -69,7 +67,6 @@
import android.widget.ListPopupWindow;
import android.widget.TextView;
-import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.Arrays;
@@ -79,7 +76,6 @@
import java.util.HashSet;
import java.util.List;
import java.util.Map;
-import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;
@@ -88,7 +84,7 @@
*/
public class CompactRawContactsEditorView extends LinearLayout implements View.OnClickListener {
- private static final String TAG = "CompactEditorView";
+ static final String TAG = "CompactEditorView";
private static final KindSectionDataMapEntryComparator
KIND_SECTION_DATA_MAP_ENTRY_COMPARATOR = new KindSectionDataMapEntryComparator();
@@ -193,13 +189,13 @@
/** Used to sort entire kind sections. */
private static final class KindSectionDataMapEntryComparator implements
- Comparator<Map.Entry<String,List<KindSectionData>>> {
+ Comparator<Map.Entry<String,KindSectionDataList>> {
final MimeTypeComparator mMimeTypeComparator = new MimeTypeComparator();
@Override
- public int compare(Map.Entry<String, List<KindSectionData>> entry1,
- Map.Entry<String, List<KindSectionData>> entry2) {
+ public int compare(Map.Entry<String, KindSectionDataList> entry1,
+ Map.Entry<String, KindSectionDataList> entry2) {
if (entry1 == entry2) return 0;
if (entry1 == null) return -1;
if (entry2 == null) return 1;
@@ -291,9 +287,9 @@
*/
private static final class NameEditorComparator implements Comparator<KindSectionData> {
- private RawContactDeltaComparator mRawContactDeltaComparator;
- private MimeTypeComparator mMimeTypeComparator;
- private RawContactDelta mPrimaryRawContactDelta;
+ private final RawContactDeltaComparator mRawContactDeltaComparator;
+ private final MimeTypeComparator mMimeTypeComparator;
+ private final RawContactDelta mPrimaryRawContactDelta;
private NameEditorComparator(Context context, RawContactDelta primaryRawContactDelta) {
mRawContactDeltaComparator = new RawContactDeltaComparator(context);
@@ -372,11 +368,12 @@
private ViewIdGenerator mViewIdGenerator;
private MaterialColorMapUtils.MaterialPalette mMaterialPalette;
private long mPhotoId;
+ private String mReadOnlyDisplayName;
private boolean mHasNewContact;
private boolean mIsUserProfile;
private AccountWithDataSet mPrimaryAccount;
private RawContactDelta mPrimaryRawContactDelta;
- private Map<String,List<KindSectionData>> mKindSectionDataMap = new HashMap<>();
+ private Map<String,KindSectionDataList> mKindSectionDataMap = new HashMap<>();
// Account header
private View mAccountHeaderContainer;
@@ -401,8 +398,10 @@
private View mMoreFields;
private boolean mIsExpanded;
+
private long mPhotoRawContactId;
private ValuesDelta mPhotoValuesDelta;
+ private StructuredNameEditorView mPrimaryNameEditorView;
public CompactRawContactsEditorView(Context context) {
super(context);
@@ -545,6 +544,10 @@
return mPhotoRawContactId;
}
+ public StructuredNameEditorView getPrimaryNameEditorView() {
+ return mPrimaryNameEditorView;
+ }
+
/**
* Returns a data holder for every non-default/non-empty photo from each raw contact, whether
* the raw contact is writable or not.
@@ -639,8 +642,8 @@
public void setState(RawContactDeltaList rawContactDeltas,
MaterialColorMapUtils.MaterialPalette materialPalette, ViewIdGenerator viewIdGenerator,
- long photoId, boolean hasNewContact, boolean isUserProfile,
- AccountWithDataSet primaryAccount) {
+ long photoId, String readOnlyDisplayName, boolean hasNewContact,
+ boolean isUserProfile, AccountWithDataSet primaryAccount) {
mKindSectionDataMap.clear();
mKindSectionViews.removeAllViews();
mMoreFields.setVisibility(View.VISIBLE);
@@ -648,6 +651,7 @@
mMaterialPalette = materialPalette;
mViewIdGenerator = viewIdGenerator;
mPhotoId = photoId;
+ mReadOnlyDisplayName = readOnlyDisplayName;
mHasNewContact = hasNewContact;
mIsUserProfile = isUserProfile;
mPrimaryAccount = primaryAccount;
@@ -662,53 +666,14 @@
if (mListener != null) mListener.onBindEditorsFailed();
return;
}
- parseRawContactDeltas(rawContactDeltas, mPrimaryAccount);
+ parseRawContactDeltas(rawContactDeltas);
if (mKindSectionDataMap.isEmpty()) {
elog("No kind section data parsed from RawContactDelta(s)");
if (mListener != null) mListener.onBindEditorsFailed();
return;
}
-
- // Setup the view
- addAccountInfo(rawContactDeltas);
- addPhotoView();
- addKindSectionViews();
-
- if (mIsExpanded) {
- showAllFields();
- }
-
- if (mListener != null) mListener.onEditorsBound();
- }
-
- private void parseRawContactDeltas(RawContactDeltaList rawContactDeltas,
- AccountWithDataSet primaryAccount) {
- if (primaryAccount != null) {
- // Use the first writable contact that matches the primary account
- for (RawContactDelta rawContactDelta : rawContactDeltas) {
- if (!rawContactDelta.isVisible()) continue;
- final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
- if (accountType == null || !accountType.areContactsWritable()) continue;
- if (matchesAccount(primaryAccount, rawContactDelta)) {
- vlog("parse: matched primary account raw contact");
- mPrimaryRawContactDelta = rawContactDelta;
- break;
- }
- }
- }
- if (mPrimaryRawContactDelta == null) {
- // Fall back to the first writable raw contact
- for (RawContactDelta rawContactDelta : rawContactDeltas) {
- if (!rawContactDelta.isVisible()) continue;
- final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
- if (accountType != null && accountType.areContactsWritable()) {
- vlog("parse: falling back to the first writable raw contact as primary");
- mPrimaryRawContactDelta = rawContactDelta;
- break;
- }
- }
- }
-
+ mPrimaryRawContactDelta = mKindSectionDataMap.get(StructuredName.CONTENT_ITEM_TYPE)
+ .getEntryToWrite(mPrimaryAccount, mHasNewContact).first.getRawContactDelta();
if (mPrimaryRawContactDelta != null) {
RawContactModifier.ensureKindExists(mPrimaryRawContactDelta,
mPrimaryRawContactDelta.getAccountType(mAccountTypeManager),
@@ -718,6 +683,19 @@
Photo.CONTENT_ITEM_TYPE);
}
+ // Setup the view
+ addAccountInfo(rawContactDeltas);
+ addPhotoView();
+ addKindSectionViews();
+ if (mHasNewContact) {
+ maybeCopyPrimaryDisplayName();
+ }
+ if (mIsExpanded) showAllFields();
+
+ if (mListener != null) mListener.onEditorsBound();
+ }
+
+ private void parseRawContactDeltas(RawContactDeltaList rawContactDeltas) {
// Build the kind section data list map
vlog("parse: " + rawContactDeltas.size() + " rawContactDelta(s)");
for (int j = 0; j < rawContactDeltas.size(); j++) {
@@ -745,7 +723,7 @@
}
final List<KindSectionData> kindSectionDataList =
- getKindSectionDataList(mimeType);
+ getOrCreateKindSectionDataList(mimeType);
final KindSectionData kindSectionData =
new KindSectionData(accountType, dataKind, rawContactDelta);
kindSectionDataList.add(kindSectionData);
@@ -762,27 +740,18 @@
}
}
- private List<KindSectionData> getKindSectionDataList(String mimeType) {
+ private List<KindSectionData> getOrCreateKindSectionDataList(String mimeType) {
// Put structured names and nicknames together
mimeType = Nickname.CONTENT_ITEM_TYPE.equals(mimeType)
? StructuredName.CONTENT_ITEM_TYPE : mimeType;
- List<KindSectionData> kindSectionDataList = mKindSectionDataMap.get(mimeType);
+ KindSectionDataList kindSectionDataList = mKindSectionDataMap.get(mimeType);
if (kindSectionDataList == null) {
- kindSectionDataList = new ArrayList<>();
+ kindSectionDataList = new KindSectionDataList();
mKindSectionDataMap.put(mimeType, kindSectionDataList);
}
return kindSectionDataList;
}
- /** Whether the given RawContactDelta belong to the given account. */
- private boolean matchesAccount(AccountWithDataSet accountWithDataSet,
- RawContactDelta rawContactDelta) {
- if (accountWithDataSet == null) return false;
- return Objects.equals(accountWithDataSet.name, rawContactDelta.getAccountName())
- && Objects.equals(accountWithDataSet.type, rawContactDelta.getAccountType())
- && Objects.equals(accountWithDataSet.dataSet, rawContactDelta.getDataSet());
- }
-
private void addAccountInfo(RawContactDeltaList rawContactDeltas) {
if (mPrimaryRawContactDelta == null) {
mAccountHeaderContainer.setVisibility(View.GONE);
@@ -967,123 +936,44 @@
private void addPhotoView() {
// Get the kind section data and values delta that we will display in the photo view
- Pair<KindSectionData,ValuesDelta> pair = getPrimaryPhotoKindSectionData(mPhotoId);
- if (pair == null) {
+ final KindSectionDataList kindSectionDataList =
+ mKindSectionDataMap.get(Photo.CONTENT_ITEM_TYPE);
+ final Pair<KindSectionData,ValuesDelta> photoToDisplay =
+ kindSectionDataList.getEntryToDisplay(mPhotoId);
+ if (photoToDisplay == null) {
wlog("photo: no kind section data parsed");
- mPhotoView.setReadOnly(true);
+ mPhotoView.setVisibility(View.GONE);
return;
}
// Set the photo view
- final ValuesDelta primaryValuesDelta = pair.second;
- mPhotoView.setPhoto(primaryValuesDelta, mMaterialPalette);
+ mPhotoView.setPhoto(photoToDisplay.second, mMaterialPalette);
// Find the raw contact ID and values delta that will be written when the photo is edited
- final KindSectionData primaryKindSectionData = pair.first;
- if (mHasNewContact && mPrimaryRawContactDelta != null
- && !primaryKindSectionData.getValuesDeltas().isEmpty()) {
- // If we're editing a read-only contact we want to display the photo from the
- // read-only contact in a photo editor view, but update the new raw contact
- // that was created.
- mPhotoRawContactId = mPrimaryRawContactDelta.getRawContactId();
- mPhotoValuesDelta = primaryKindSectionData.getValuesDeltas().get(0);
- mPhotoView.setReadOnly(false);
- return;
- }
- if (primaryKindSectionData.getAccountType().areContactsWritable() &&
- !primaryKindSectionData.getValuesDeltas().isEmpty()) {
- mPhotoRawContactId = primaryKindSectionData.getRawContactDelta().getRawContactId();
- mPhotoValuesDelta = primaryKindSectionData.getValuesDeltas().get(0);
- mPhotoView.setReadOnly(false);
- return;
- }
-
- final KindSectionData writableKindSectionData = getFirstWritablePhotoKindSectionData();
- if (writableKindSectionData == null
- || writableKindSectionData.getValuesDeltas().isEmpty()) {
+ final Pair<KindSectionData,ValuesDelta> photoToWrite = kindSectionDataList.getEntryToWrite(
+ mPrimaryAccount, mHasNewContact);
+ if (photoToWrite == null) {
mPhotoView.setReadOnly(true);
return;
}
- mPhotoRawContactId = writableKindSectionData.getRawContactDelta().getRawContactId();
- mPhotoValuesDelta = writableKindSectionData.getValuesDeltas().get(0);
mPhotoView.setReadOnly(false);
- }
-
- private Pair<KindSectionData,ValuesDelta> getPrimaryPhotoKindSectionData(long id) {
- final String mimeType = Photo.CONTENT_ITEM_TYPE;
- final List<KindSectionData> kindSectionDataList = mKindSectionDataMap.get(mimeType);
-
- KindSectionData resultKindSectionData = null;
- ValuesDelta resultValuesDelta = null;
- if (id > 0) {
- // Look for a match for the ID that was passed in
- for (KindSectionData kindSectionData : kindSectionDataList) {
- resultValuesDelta = kindSectionData.getValuesDeltaById(id);
- if (resultValuesDelta != null) {
- vlog("photo: matched kind section data by ID");
- resultKindSectionData = kindSectionData;
- break;
- }
- }
- }
- if (resultKindSectionData == null) {
- // Look for a super primary photo
- for (KindSectionData kindSectionData : kindSectionDataList) {
- resultValuesDelta = kindSectionData.getSuperPrimaryValuesDelta();
- if (resultValuesDelta != null) {
- wlog("photo: matched super primary kind section data");
- resultKindSectionData = kindSectionData;
- break;
- }
- }
- }
- if (resultKindSectionData == null) {
- // Fall back to the first non-empty value
- for (KindSectionData kindSectionData : kindSectionDataList) {
- resultValuesDelta = kindSectionData.getFirstNonEmptyValuesDelta();
- if (resultValuesDelta != null) {
- vlog("photo: using first non empty value");
- resultKindSectionData = kindSectionData;
- break;
- }
- }
- }
- if (resultKindSectionData == null || resultValuesDelta == null) {
- final List<ValuesDelta> valuesDeltaList = kindSectionDataList.get(0).getValuesDeltas();
- if (valuesDeltaList != null && !valuesDeltaList.isEmpty()) {
- vlog("photo: falling back to first empty entry");
- resultValuesDelta = valuesDeltaList.get(0);
- resultKindSectionData = kindSectionDataList.get(0);
- }
- }
- return resultKindSectionData != null && resultValuesDelta != null
- ? new Pair<>(resultKindSectionData, resultValuesDelta) : null;
- }
-
- private KindSectionData getFirstWritablePhotoKindSectionData() {
- final String mimeType = Photo.CONTENT_ITEM_TYPE;
- final List<KindSectionData> kindSectionDataList = mKindSectionDataMap.get(mimeType);
- for (KindSectionData kindSectionData : kindSectionDataList) {
- if (kindSectionData.getAccountType().areContactsWritable()) {
- return kindSectionData;
- }
- }
- return null;
+ mPhotoRawContactId = photoToWrite.first.getRawContactDelta().getRawContactId();
+ mPhotoValuesDelta = photoToWrite.second;
}
private void addKindSectionViews() {
// Sort the kinds
- final TreeSet<Map.Entry<String,List<KindSectionData>>> entries =
+ final TreeSet<Map.Entry<String,KindSectionDataList>> entries =
new TreeSet<>(KIND_SECTION_DATA_MAP_ENTRY_COMPARATOR);
entries.addAll(mKindSectionDataMap.entrySet());
vlog("kind: " + entries.size() + " kindSection(s)");
int i = -1;
- for (Map.Entry<String, List<KindSectionData>> entry : entries) {
+ for (Map.Entry<String, KindSectionDataList> entry : entries) {
i++;
final String mimeType = entry.getKey();
- final List<KindSectionData> kindSectionDataList = entry.getValue();
+ final KindSectionDataList kindSectionDataList = entry.getValue();
// Ignore mime types that we've already handled
if (Photo.CONTENT_ITEM_TYPE.equals(mimeType)) {
@@ -1115,7 +1005,7 @@
}
private CompactKindSectionView inflateKindSectionView(ViewGroup viewGroup,
- List<KindSectionData> kindSectionDataList, String mimeType) {
+ KindSectionDataList kindSectionDataList, String mimeType) {
final CompactKindSectionView kindSectionView = (CompactKindSectionView)
mLayoutInflater.inflate(R.layout.compact_item_kind_section, viewGroup,
/* attachToRoot =*/ false);
@@ -1144,6 +1034,19 @@
return kindSectionView;
}
+ private void maybeCopyPrimaryDisplayName() {
+ if (TextUtils.isEmpty(mReadOnlyDisplayName)) return;
+ final List<CompactKindSectionView> kindSectionViews
+ = mKindSectionViewsMap.get(StructuredName.CONTENT_ITEM_TYPE);
+ if (kindSectionViews.isEmpty()) return;
+ final CompactKindSectionView primaryNameKindSectionView = kindSectionViews.get(0);
+ if (primaryNameKindSectionView.isEmptyName()) {
+ vlog("name: using read only display name as primary name");
+ primaryNameKindSectionView.setName(mReadOnlyDisplayName);
+ mPrimaryNameEditorView = primaryNameKindSectionView.getPrimaryNameEditorView();
+ }
+ }
+
private void showAllFields() {
// Stop hiding empty editors and allow the user to enter values for all kinds now
for (int i = 0; i < mKindSectionViews.getChildCount(); i++) {
diff --git a/src/com/android/contacts/editor/ContactEditorBaseFragment.java b/src/com/android/contacts/editor/ContactEditorBaseFragment.java
index 02ac40b..54c3c07 100644
--- a/src/com/android/contacts/editor/ContactEditorBaseFragment.java
+++ b/src/com/android/contacts/editor/ContactEditorBaseFragment.java
@@ -373,6 +373,17 @@
// Join Activity
protected long mContactIdForJoin;
+ //
+ // Not saved/restored on rotates
+ //
+
+ // Used to pre-populate the editor with a display name when a user edits a read-only contact.
+ protected String mReadOnlyDisplayName;
+
+ // The name editor view for the new raw contact that was created so that the user can
+ // edit a read-only contact (to which the new raw contact was joined)
+ protected StructuredNameEditorView mReadOnlyNameEditorView;
+
/**
* The contact data loader listener.
*/
@@ -963,12 +974,42 @@
* Return true if there are any edits to the current contact which need to
* be saved.
*/
- protected boolean hasPendingChanges() {
+ protected boolean hasPendingRawContactChanges() {
final AccountTypeManager accountTypes = AccountTypeManager.getInstance(mContext);
return RawContactModifier.hasChanges(mState, accountTypes);
}
/**
+ * Determines if changes were made in the editor that need to be saved, while taking into
+ * account that name changes are not real for read-only contacts.
+ * See go/editing-read-only-contacts
+ */
+ protected boolean hasPendingChanges() {
+ if (mReadOnlyNameEditorView == null || mReadOnlyDisplayName == null) {
+ return hasPendingRawContactChanges();
+ }
+ // We created a new raw contact delta with a default display name.
+ // We must test for pending changes while ignoring the default display name.
+ final String displayName = mReadOnlyNameEditorView.getDisplayName();
+ if (mReadOnlyDisplayName.equals(displayName)) {
+ // The user did not modify the default display name, erase it and
+ // check if the user made any other changes
+ mReadOnlyNameEditorView.clearAllFields();
+ if (hasPendingRawContactChanges()) {
+ // Other changes were made to the aggregate contact, restore
+ // the display name and proceed.
+ mReadOnlyNameEditorView.setDisplayName(displayName);
+ return true;
+ } else {
+ // No other changes were made to the aggregate contact. Don't add back
+ // the displayName so that a "bogus" contact is not created.
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
* Whether editor inputs and the options menu should be enabled.
*/
protected boolean isEnabled() {
@@ -1078,6 +1119,7 @@
}
}
+ String readOnlyDisplayName = null;
// Check for writable raw contacts. If there are none, then we need to create one so user
// can edit. For the user profile case, there is already an editable contact.
if (!contact.isUserProfile() && !contact.isWritableContact(mContext)) {
@@ -1085,11 +1127,13 @@
// This is potentially an asynchronous call and will add deltas to list.
selectAccountAndCreateContact();
+
+ readOnlyDisplayName = contact.getDisplayName();
}
// This also adds deltas to list. If readOnlyDisplayName is null at this point it is
// simply ignored later on by the editor.
- setStateForExistingContact(contact.isUserProfile(), mRawContacts);
+ setStateForExistingContact(readOnlyDisplayName, contact.isUserProfile(), mRawContacts);
}
/**
@@ -1160,9 +1204,10 @@
/**
* Prepare {@link #mState} for an existing contact.
*/
- protected void setStateForExistingContact(boolean isUserProfile,
+ protected void setStateForExistingContact(String readOnlyDisplayName, boolean isUserProfile,
ImmutableList<RawContact> rawContacts) {
setEnabled(true);
+ mReadOnlyDisplayName = readOnlyDisplayName;
mState.addAll(rawContacts.iterator());
setIntentExtras(mIntentExtras);
@@ -1274,7 +1319,8 @@
setStateForNewContact(newAccount, newAccountType, oldState, oldAccountType,
isEditingUserProfile());
if (mIsEdit) {
- setStateForExistingContact(isEditingUserProfile(), mRawContacts);
+ setStateForExistingContact(mReadOnlyDisplayName, isEditingUserProfile(),
+ mRawContacts);
}
}
}
diff --git a/src/com/android/contacts/editor/KindSectionData.java b/src/com/android/contacts/editor/KindSectionData.java
index 6c33601..8921099 100644
--- a/src/com/android/contacts/editor/KindSectionData.java
+++ b/src/com/android/contacts/editor/KindSectionData.java
@@ -97,18 +97,6 @@
return mDataKind;
}
- public boolean isNameDataKind() {
- return StructuredName.CONTENT_ITEM_TYPE.equals(mDataKind.mimeType);
- }
-
- public boolean isNicknameDataKind() {
- return Nickname.CONTENT_ITEM_TYPE.equals(mDataKind.mimeType);
- }
-
- public boolean isEventDataKind() {
- return Event.CONTENT_ITEM_TYPE.equals(mDataKind.mimeType);
- }
-
public RawContactDelta getRawContactDelta() {
return mRawContactDelta;
}
diff --git a/src/com/android/contacts/editor/KindSectionDataList.java b/src/com/android/contacts/editor/KindSectionDataList.java
new file mode 100644
index 0000000..3658f21
--- /dev/null
+++ b/src/com/android/contacts/editor/KindSectionDataList.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2015 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.editor;
+
+import com.android.contacts.common.model.RawContactDelta;
+import com.android.contacts.common.model.ValuesDelta;
+import com.android.contacts.common.model.account.AccountWithDataSet;
+import com.android.contacts.common.model.dataitem.DataKind;
+
+import android.provider.ContactsContract.CommonDataKinds.Nickname;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.util.Log;
+import android.util.Pair;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Container for multiple {@link KindSectionData} objects. Provides convenience methods for
+ * interrogating the collection for a certain KindSectionData item (e.g. the first writable, or
+ * "primary", one. Also enforces that only items with the same DataKind/mime-type are added.
+ */
+public class KindSectionDataList extends ArrayList<KindSectionData> {
+
+ private static final String TAG = CompactRawContactsEditorView.TAG;
+
+ /**
+ * Returns the mime type for all DataKinds in this List.
+ */
+ public String getMimeType() {
+ if (isEmpty()) return null;
+ final String mimeType = get(0).getDataKind().mimeType;
+ // StructuredNames and Nicknames are a special case and go together under the
+ // StructuredName mime type
+ if (Nickname.CONTENT_ITEM_TYPE.equals(mimeType)) {
+ return StructuredName.CONTENT_ITEM_TYPE;
+ }
+ return mimeType;
+ }
+
+ /**
+ * Returns the DataKind for all entries in this List.
+ */
+ public DataKind getDataKind() {
+ return isEmpty() ? null : get(0).getDataKind();
+ }
+
+ /**
+ * Returns the "primary" KindSectionData and ValuesDelta that should be written for this List.
+ */
+ public Pair<KindSectionData,ValuesDelta> getEntryToWrite(AccountWithDataSet primaryAccount,
+ boolean hasNewContact) {
+ // Use the first writable contact that matches the primary account
+ if (primaryAccount != null && !hasNewContact) {
+ for (KindSectionData kindSectionData : this) {
+ if (kindSectionData.getAccountType().areContactsWritable()
+ && !kindSectionData.getValuesDeltas().isEmpty()) {
+ if (matchesAccount(primaryAccount, kindSectionData.getRawContactDelta())) {
+ return new Pair<>(kindSectionData,
+ kindSectionData.getValuesDeltas().get(0));
+ }
+ }
+ }
+ }
+
+ // If no writable raw contact matched the primary account, or we're editing a read-only
+ // contact, just return the first writable entry.
+ for (KindSectionData kindSectionData : this) {
+ if (kindSectionData.getAccountType().areContactsWritable()) {
+ if (!kindSectionData.getValuesDeltas().isEmpty()) {
+ return new Pair<>(kindSectionData, kindSectionData.getValuesDeltas().get(0));
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /** Whether the given RawContactDelta belong to the given account. */
+ private static boolean matchesAccount(AccountWithDataSet accountWithDataSet,
+ RawContactDelta rawContactDelta) {
+ if (accountWithDataSet == null) return false;
+ return Objects.equals(accountWithDataSet.name, rawContactDelta.getAccountName())
+ && Objects.equals(accountWithDataSet.type, rawContactDelta.getAccountType())
+ && Objects.equals(accountWithDataSet.dataSet, rawContactDelta.getDataSet());
+ }
+
+ /**
+ * Returns the "primary" KindSectionData and ValuesDelta that should be displayed to the user.
+ */
+ public Pair<KindSectionData,ValuesDelta> getEntryToDisplay(long id) {
+ final String mimeType = getMimeType();
+ if (mimeType == null) return null;
+
+ KindSectionData resultKindSectionData = null;
+ ValuesDelta resultValuesDelta = null;
+ if (id > 0) {
+ // Look for a match for the ID that was passed in
+ for (KindSectionData kindSectionData : this) {
+ resultValuesDelta = kindSectionData.getValuesDeltaById(id);
+ if (resultValuesDelta != null) {
+ vlog(mimeType + ": matched kind section data by ID");
+ resultKindSectionData = kindSectionData;
+ break;
+ }
+ }
+ }
+ if (resultKindSectionData == null) {
+ // Look for a super primary entry
+ for (KindSectionData kindSectionData : this) {
+ resultValuesDelta = kindSectionData.getSuperPrimaryValuesDelta();
+ if (resultValuesDelta != null) {
+ vlog(mimeType + ": matched super primary kind section data");
+ resultKindSectionData = kindSectionData;
+ break;
+ }
+ }
+ }
+ if (resultKindSectionData == null) {
+ // Fall back to the first non-empty value
+ for (KindSectionData kindSectionData : this) {
+ resultValuesDelta = kindSectionData.getFirstNonEmptyValuesDelta();
+ if (resultValuesDelta != null) {
+ vlog(mimeType + ": using first non empty value");
+ resultKindSectionData = kindSectionData;
+ break;
+ }
+ }
+ }
+ if (resultKindSectionData == null || resultValuesDelta == null) {
+ final List<ValuesDelta> valuesDeltaList = get(0).getValuesDeltas();
+ if (valuesDeltaList != null && !valuesDeltaList.isEmpty()) {
+ vlog(mimeType + ": falling back to first empty entry");
+ resultValuesDelta = valuesDeltaList.get(0);
+ resultKindSectionData = get(0);
+ }
+ }
+ return resultKindSectionData != null && resultValuesDelta != null
+ ? new Pair<>(resultKindSectionData, resultValuesDelta) : null;
+ }
+
+ @Override
+ public boolean add(KindSectionData kindSectionData) {
+ if (kindSectionData == null) throw new NullPointerException();
+
+ // Enforce that only entries of the same type are added to this list
+ final String listMimeType = getMimeType();
+ if (listMimeType != null) {
+ final String newEntryMimeType = kindSectionData.getDataKind().mimeType;
+ if (isNameMimeType(listMimeType)) {
+ if (!isNameMimeType(newEntryMimeType)) {
+ throw new IllegalArgumentException(
+ "Can't add " + newEntryMimeType + " to list with type " + listMimeType);
+ }
+ } else if (!listMimeType.equals(newEntryMimeType)) {
+ throw new IllegalArgumentException(
+ "Can't add " + newEntryMimeType + " to list with type " + listMimeType);
+ }
+ }
+ return super.add(kindSectionData);
+ }
+
+ // StructuredNames and Nicknames are a special case and go together
+ private static boolean isNameMimeType(String mimeType) {
+ return StructuredName.CONTENT_ITEM_TYPE.equals(mimeType)
+ || Nickname.CONTENT_ITEM_TYPE.equals(mimeType);
+ }
+
+ private static void vlog(String message) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, message);
+ }
+ }
+}
diff --git a/src/com/android/contacts/editor/LabeledEditorView.java b/src/com/android/contacts/editor/LabeledEditorView.java
index 244b682..b68310a 100644
--- a/src/com/android/contacts/editor/LabeledEditorView.java
+++ b/src/com/android/contacts/editor/LabeledEditorView.java
@@ -380,7 +380,9 @@
mWasEmpty = isEmpty;
// Update the label text color
- mEditTypeAdapter.notifyDataSetChanged();
+ if (mEditTypeAdapter != null) {
+ mEditTypeAdapter.notifyDataSetChanged();
+ }
}
}
diff --git a/src/com/android/contacts/editor/StructuredNameEditorView.java b/src/com/android/contacts/editor/StructuredNameEditorView.java
index 4cc8003..23cb4c1 100644
--- a/src/com/android/contacts/editor/StructuredNameEditorView.java
+++ b/src/com/android/contacts/editor/StructuredNameEditorView.java
@@ -225,6 +225,22 @@
super.setValue(0, name);
}
+ /**
+ * Returns the display name currently displayed in the editor.
+ */
+ public String getDisplayName() {
+ final ValuesDelta valuesDelta = getValues();
+ if (hasShortAndLongForms() && areOptionalFieldsVisible()) {
+ final Map<String, String> structuredNameMap = valuesToStructuredNameMap(valuesDelta);
+ final String displayName = NameConverter.structuredNameToDisplayName(
+ getContext(), structuredNameMap);
+ if (!TextUtils.isEmpty(displayName)) {
+ return displayName;
+ }
+ }
+ return valuesDelta.getDisplayName();
+ }
+
@Override
protected Parcelable onSaveInstanceState() {
SavedState state = new SavedState(super.onSaveInstanceState());