blob: c51e47a0767e02c7bc6315f3d84d7f45611ae6f5 [file] [log] [blame]
Walter Jangcab3dce2015-02-09 17:48:03 -08001/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.contacts.editor;
18
19import com.android.contacts.R;
20import com.android.contacts.common.model.AccountTypeManager;
21import com.android.contacts.common.model.RawContactDelta;
22import com.android.contacts.common.model.RawContactDeltaList;
Walter Jange720fde2015-02-17 10:54:14 -080023import com.android.contacts.common.model.RawContactModifier;
Walter Jangcab3dce2015-02-09 17:48:03 -080024import com.android.contacts.common.model.ValuesDelta;
25import com.android.contacts.common.model.account.AccountType;
Walter Jang2d3f31c2015-06-18 23:15:31 -070026import com.android.contacts.common.model.account.AccountWithDataSet;
Walter Jangcab3dce2015-02-09 17:48:03 -080027import com.android.contacts.common.model.dataitem.DataKind;
Walter Jang708ea9e2015-09-10 15:42:05 -070028import com.android.contacts.common.util.AccountsListAdapter;
Walter Jangf46abd82015-02-20 16:52:04 -080029import com.android.contacts.common.util.MaterialColorMapUtils;
Walter Jang3efae4a2015-02-18 11:12:00 -080030import com.android.contacts.editor.CompactContactEditorFragment.PhotoHandler;
Walter Jang708ea9e2015-09-10 15:42:05 -070031import com.android.contacts.util.UiClosables;
Walter Jangcab3dce2015-02-09 17:48:03 -080032
33import android.content.Context;
Walter Jang3efae4a2015-02-18 11:12:00 -080034import android.graphics.Bitmap;
Walter Jang41b3ea12015-03-09 17:30:06 -070035import android.net.Uri;
Walter Jangcab3dce2015-02-09 17:48:03 -080036import android.provider.ContactsContract.CommonDataKinds.Email;
Walter Jangf5dfea42015-09-16 12:30:36 -070037import android.provider.ContactsContract.CommonDataKinds.Event;
Walter Jangcab3dce2015-02-09 17:48:03 -080038import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
Walter Jangf5dfea42015-09-16 12:30:36 -070039import android.provider.ContactsContract.CommonDataKinds.Im;
Walter Jang3efae4a2015-02-18 11:12:00 -080040import android.provider.ContactsContract.CommonDataKinds.Nickname;
Walter Jangf5dfea42015-09-16 12:30:36 -070041import android.provider.ContactsContract.CommonDataKinds.Note;
42import android.provider.ContactsContract.CommonDataKinds.Organization;
Walter Jangcab3dce2015-02-09 17:48:03 -080043import android.provider.ContactsContract.CommonDataKinds.Phone;
44import android.provider.ContactsContract.CommonDataKinds.Photo;
Walter Jangf5dfea42015-09-16 12:30:36 -070045import android.provider.ContactsContract.CommonDataKinds.Relation;
46import android.provider.ContactsContract.CommonDataKinds.SipAddress;
Walter Jangcab3dce2015-02-09 17:48:03 -080047import android.provider.ContactsContract.CommonDataKinds.StructuredName;
Walter Jangf5dfea42015-09-16 12:30:36 -070048import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
49import android.provider.ContactsContract.CommonDataKinds.Website;
Walter Jangcab3dce2015-02-09 17:48:03 -080050import android.text.TextUtils;
51import android.util.AttributeSet;
52import android.util.Log;
Walter Jang2d3f31c2015-06-18 23:15:31 -070053import android.util.Pair;
Walter Jangcab3dce2015-02-09 17:48:03 -080054import android.view.LayoutInflater;
Walter Jangb1c87622015-02-13 17:51:38 -080055import android.view.View;
Walter Jangcab3dce2015-02-09 17:48:03 -080056import android.view.ViewGroup;
Walter Jang708ea9e2015-09-10 15:42:05 -070057import android.widget.AdapterView;
Walter Jangcab3dce2015-02-09 17:48:03 -080058import android.widget.LinearLayout;
Walter Jang708ea9e2015-09-10 15:42:05 -070059import android.widget.ListPopupWindow;
Walter Jang2d3f31c2015-06-18 23:15:31 -070060import android.widget.TextView;
Walter Jangcab3dce2015-02-09 17:48:03 -080061
62import java.util.ArrayList;
Walter Jangf5dfea42015-09-16 12:30:36 -070063import java.util.Arrays;
64import java.util.Comparator;
Walter Jang19d985f2015-07-01 13:36:27 -070065import java.util.HashMap;
Walter Jangcab3dce2015-02-09 17:48:03 -080066import java.util.List;
Walter Jang19d985f2015-07-01 13:36:27 -070067import java.util.Map;
Walter Jang708ea9e2015-09-10 15:42:05 -070068import java.util.Objects;
Walter Jangf5dfea42015-09-16 12:30:36 -070069import java.util.TreeSet;
Walter Jangcab3dce2015-02-09 17:48:03 -080070
71/**
Walter Jangf5dfea42015-09-16 12:30:36 -070072 * View to display information from multiple {@link RawContactDelta}s grouped together.
Walter Jangcab3dce2015-02-09 17:48:03 -080073 */
Walter Jangb6ca2722015-02-20 11:10:25 -080074public class CompactRawContactsEditorView extends LinearLayout implements View.OnClickListener {
Walter Jangcab3dce2015-02-09 17:48:03 -080075
76 private static final String TAG = "CompactEditorView";
77
Walter Jangf5dfea42015-09-16 12:30:36 -070078 private static final MimeTypeComparator MIME_TYPE_COMPARATOR = new MimeTypeComparator();
79
Walter Jangb6ca2722015-02-20 11:10:25 -080080 /**
81 * Callbacks for hosts of {@link CompactRawContactsEditorView}s.
82 */
83 public interface Listener {
84
85 /**
Walter Jang151f3e62015-02-26 15:29:40 -080086 * Invoked when the structured name editor field has changed.
87 *
88 * @param rawContactId The raw contact ID from the underlying {@link RawContactDelta}.
89 * @param valuesDelta The values from the underlying {@link RawContactDelta}.
90 */
91 public void onNameFieldChanged(long rawContactId, ValuesDelta valuesDelta);
Walter Jang708ea9e2015-09-10 15:42:05 -070092
93 /**
94 * Invoked when the compact editor should rebind editors for a new account.
95 *
96 * @param oldState Old data being edited.
97 * @param oldAccount Old account associated with oldState.
98 * @param newAccount New account to be used.
99 */
100 public void onRebindEditorsForNewContact(RawContactDelta oldState,
101 AccountWithDataSet oldAccount, AccountWithDataSet newAccount);
Walter Jang151f3e62015-02-26 15:29:40 -0800102 }
103
104 /**
105 * Marks a name as super primary when it is changed.
106 *
107 * This is for the case when two or more raw contacts with names are joined where neither is
108 * marked as super primary. If the user hits back (which causes a save) after changing the
109 * name that was arbitrarily displayed, we want that to be the name that is used.
110 *
111 * Should only be set when a super primary name does not already exist since we only show
112 * one name field.
113 */
114 static final class NameEditorListener implements Editor.EditorListener {
115
116 private final ValuesDelta mValuesDelta;
117 private final long mRawContactId;
118 private final Listener mListener;
119
120 public NameEditorListener(ValuesDelta valuesDelta, long rawContactId,
121 Listener listener) {
122 mValuesDelta = valuesDelta;
123 mRawContactId = rawContactId;
124 mListener = listener;
125 }
126
127 @Override
128 public void onRequest(int request) {
129 if (request == Editor.EditorListener.FIELD_CHANGED) {
130 mValuesDelta.setSuperPrimary(true);
131 if (mListener != null) {
132 mListener.onNameFieldChanged(mRawContactId, mValuesDelta);
133 }
134 } else if (request == Editor.EditorListener.FIELD_TURNED_EMPTY) {
135 mValuesDelta.setSuperPrimary(false);
136 }
137 }
138
139 @Override
140 public void onDeleteRequested(Editor editor) {
141 }
Walter Jangb6ca2722015-02-20 11:10:25 -0800142 }
143
Walter Jangf5dfea42015-09-16 12:30:36 -0700144 /** Used to sort kind sections. */
145 private static final class MimeTypeComparator implements
146 Comparator<Map.Entry<String,List<KindSectionData>>> {
147
148 // Order of kinds roughly matches quick contacts; we diverge in the following ways:
149 // 1) all names are together at the top (in quick contacts the nickname and phonetic name
150 // are in the about card)
151 // 2) IM is moved up after addresses
152 // 3) SIP addresses are moved to below phone numbers
153 private static final List<String> MIME_TYPE_ORDER = Arrays.asList(new String[] {
154 StructuredName.CONTENT_ITEM_TYPE,
155 DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME,
156 DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME,
157 Nickname.CONTENT_ITEM_TYPE,
158 Phone.CONTENT_ITEM_TYPE,
159 SipAddress.CONTENT_ITEM_TYPE,
160 Email.CONTENT_ITEM_TYPE,
161 StructuredPostal.CONTENT_ITEM_TYPE,
162 Im.CONTENT_ITEM_TYPE,
163 Website.CONTENT_ITEM_TYPE,
164 Organization.CONTENT_ITEM_TYPE,
165 Event.CONTENT_ITEM_TYPE,
166 Relation.CONTENT_ITEM_TYPE,
167 Note.CONTENT_ITEM_TYPE
168 });
169
170 @Override
171 public int compare(Map.Entry<String, List<KindSectionData>> entry1,
172 Map.Entry<String, List<KindSectionData>> entry2) {
173 if (entry1 == entry2) return 0;
174 if (entry1 == null) return -1;
175 if (entry2 == null) return 1;
176
177 final String mimeType1 = entry1.getKey();
178 final String mimeType2 = entry2.getKey();
179
180 int index1 = MIME_TYPE_ORDER.indexOf(mimeType1);
181 int index2 = MIME_TYPE_ORDER.indexOf(mimeType2);
182
183 // Fallback to alphabetical ordering of the mime type if both are not found
184 if (index1 < 0 && index2 < 0) return mimeType1.compareTo(mimeType2);
185 if (index1 < 0) return 1;
186 if (index2 < 0) return -1;
187
188 return index1 < index2 ? -1 : 1;
189 }
190 }
191
192
Walter Jangb6ca2722015-02-20 11:10:25 -0800193 private Listener mListener;
194
Walter Jangcab3dce2015-02-09 17:48:03 -0800195 private AccountTypeManager mAccountTypeManager;
196 private LayoutInflater mLayoutInflater;
Walter Jangd35e5ef2015-02-24 09:18:16 -0800197
Walter Jangcab3dce2015-02-09 17:48:03 -0800198 private ViewIdGenerator mViewIdGenerator;
Walter Jangf46abd82015-02-20 16:52:04 -0800199 private MaterialColorMapUtils.MaterialPalette mMaterialPalette;
Walter Jang708ea9e2015-09-10 15:42:05 -0700200 private long mPhotoId;
201 private long mNameId;
202 private String mReadOnlyDisplayName;
203 private boolean mHasNewContact;
204 private boolean mIsUserProfile;
205 private AccountWithDataSet mPrimaryAccount;
206 private RawContactDelta mPrimaryRawContactDelta;
Walter Jang363d3fd2015-09-16 10:29:07 -0700207 private Map<String,List<KindSectionData>> mKindSectionDataMap = new HashMap<>();
Walter Jangcab3dce2015-02-09 17:48:03 -0800208
Walter Jang708ea9e2015-09-10 15:42:05 -0700209 // Account header
210 private View mAccountHeaderContainer;
211 private TextView mAccountHeaderType;
212 private TextView mAccountHeaderName;
213
214 // Account selector
215 private View mAccountSelectorContainer;
216 private View mAccountSelector;
217 private TextView mAccountSelectorType;
218 private TextView mAccountSelectorName;
219
Walter Janga5e4bb22015-02-24 13:08:16 -0800220 private CompactPhotoEditorView mPhoto;
Walter Jangcab3dce2015-02-09 17:48:03 -0800221 private ViewGroup mNames;
Walter Jangf5dfea42015-09-16 12:30:36 -0700222 private ViewGroup mKindSectionViews;
223 // Map of mime types to KindSectionViews
224 private Map<String,LinearLayout> mKindSectionViewsMap = new HashMap<>();
Walter Jangb6ca2722015-02-20 11:10:25 -0800225 private View mMoreFields;
Walter Jangcab3dce2015-02-09 17:48:03 -0800226
Walter Jang151f3e62015-02-26 15:29:40 -0800227 // The ValuesDelta for the non super primary name that was displayed to the user.
228 private ValuesDelta mNameValuesDelta;
229
Walter Jang3efae4a2015-02-18 11:12:00 -0800230 private long mPhotoRawContactId;
231
Walter Jang363d3fd2015-09-16 10:29:07 -0700232 private boolean mUsingDefaultNameEditorView;
Walter Jang06f73a12015-06-17 11:15:48 -0700233 private StructuredNameEditorView mDefaultNameEditorView;
234
Walter Jangcab3dce2015-02-09 17:48:03 -0800235 public CompactRawContactsEditorView(Context context) {
236 super(context);
237 }
238
239 public CompactRawContactsEditorView(Context context, AttributeSet attrs) {
240 super(context, attrs);
241 }
242
Walter Jangb6ca2722015-02-20 11:10:25 -0800243 /**
244 * Sets the receiver for {@link CompactRawContactsEditorView} callbacks.
245 */
246 public void setListener(Listener listener) {
247 mListener = listener;
248 }
249
Walter Jangcab3dce2015-02-09 17:48:03 -0800250 @Override
251 protected void onFinishInflate() {
252 super.onFinishInflate();
253
254 mAccountTypeManager = AccountTypeManager.getInstance(getContext());
255 mLayoutInflater = (LayoutInflater)
256 getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
257
Walter Jang708ea9e2015-09-10 15:42:05 -0700258 // Account header
259 mAccountHeaderContainer = findViewById(R.id.account_container);
260 mAccountHeaderType = (TextView) findViewById(R.id.account_type);
261 mAccountHeaderName = (TextView) findViewById(R.id.account_name);
262
263 // Account selector
264 mAccountSelectorContainer = findViewById(R.id.account_selector_container);
265 mAccountSelector = findViewById(R.id.account);
266 mAccountSelectorType = (TextView) findViewById(R.id.account_type_selector);
267 mAccountSelectorName = (TextView) findViewById(R.id.account_name_selector);
268
Walter Janga5e4bb22015-02-24 13:08:16 -0800269 mPhoto = (CompactPhotoEditorView) findViewById(R.id.photo_editor);
Walter Jangcab3dce2015-02-09 17:48:03 -0800270 mNames = (LinearLayout) findViewById(R.id.names);
Walter Jangf5dfea42015-09-16 12:30:36 -0700271 mKindSectionViews = (LinearLayout) findViewById(R.id.kind_section_views);
Walter Jangb6ca2722015-02-20 11:10:25 -0800272 mMoreFields = findViewById(R.id.more_fields);
273 mMoreFields.setOnClickListener(this);
274 }
275
Walter Jangb6ca2722015-02-20 11:10:25 -0800276 @Override
277 public void onClick(View view) {
Walter Jangf5dfea42015-09-16 12:30:36 -0700278 if (view.getId() == R.id.more_fields) {
279 // Stop hiding empty editors and allow the user to enter values for all kinds now
280 for (int i = 0; i < mKindSectionViews.getChildCount(); i++) {
281 final CompactKindSectionView kindSectionView =
282 (CompactKindSectionView) mKindSectionViews.getChildAt(i);
283 kindSectionView.setHideWhenEmpty(false);
284 kindSectionView.setShowOneEmptyEditor(true);
285 kindSectionView.updateEmptyEditors(/* shouldAnimate =*/ false);
286 }
287
288 updateMoreFieldsButton();
Walter Jangb6ca2722015-02-20 11:10:25 -0800289 }
Walter Jangcab3dce2015-02-09 17:48:03 -0800290 }
291
292 @Override
293 public void setEnabled(boolean enabled) {
294 super.setEnabled(enabled);
295 setEnabled(enabled, mNames);
Walter Jangf5dfea42015-09-16 12:30:36 -0700296 setEnabled(enabled, mKindSectionViews);
Walter Jangcab3dce2015-02-09 17:48:03 -0800297 }
298
299 private void setEnabled(boolean enabled, ViewGroup viewGroup) {
300 if (viewGroup != null) {
301 final int childCount = viewGroup.getChildCount();
302 for (int i = 0; i < childCount; i++) {
303 viewGroup.getChildAt(i).setEnabled(enabled);
304 }
305 }
306 }
307
Walter Jang3efae4a2015-02-18 11:12:00 -0800308 /**
Walter Janga5e4bb22015-02-24 13:08:16 -0800309 * Pass through to {@link CompactPhotoEditorView#setPhotoHandler}.
Walter Jang3efae4a2015-02-18 11:12:00 -0800310 */
311 public void setPhotoHandler(PhotoHandler photoHandler) {
Walter Janga5e4bb22015-02-24 13:08:16 -0800312 mPhoto.setPhotoHandler(photoHandler);
Walter Jang3efae4a2015-02-18 11:12:00 -0800313 }
314
315 /**
Walter Janga5e4bb22015-02-24 13:08:16 -0800316 * Pass through to {@link CompactPhotoEditorView#setPhoto}.
Walter Jang3efae4a2015-02-18 11:12:00 -0800317 */
318 public void setPhoto(Bitmap bitmap) {
Walter Janga5e4bb22015-02-24 13:08:16 -0800319 mPhoto.setPhoto(bitmap);
Walter Jang3efae4a2015-02-18 11:12:00 -0800320 }
321
322 /**
Walter Jang41b3ea12015-03-09 17:30:06 -0700323 * Pass through to {@link CompactPhotoEditorView#setFullSizedPhoto(Uri)}.
324 */
325 public void setFullSizePhoto(Uri photoUri) {
326 mPhoto.setFullSizedPhoto(photoUri);
327 }
328
329 /**
Walter Janga5e4bb22015-02-24 13:08:16 -0800330 * Pass through to {@link CompactPhotoEditorView#isWritablePhotoSet}.
Walter Jang3efae4a2015-02-18 11:12:00 -0800331 */
332 public boolean isWritablePhotoSet() {
Walter Janga5e4bb22015-02-24 13:08:16 -0800333 return mPhoto.isWritablePhotoSet();
Walter Jang3efae4a2015-02-18 11:12:00 -0800334 }
335
336 /**
Walter Jang3efae4a2015-02-18 11:12:00 -0800337 * Get the raw contact ID for the CompactHeaderView photo.
338 */
Walter Jang3efae4a2015-02-18 11:12:00 -0800339 public long getPhotoRawContactId() {
340 return mPhotoRawContactId;
341 }
342
Walter Jang06f73a12015-06-17 11:15:48 -0700343 public StructuredNameEditorView getDefaultNameEditorView() {
344 return mDefaultNameEditorView;
345 }
346
Walter Jangd35e5ef2015-02-24 09:18:16 -0800347 public StructuredNameEditorView getStructuredNameEditorView() {
348 // We only ever show one StructuredName
349 return mNames.getChildCount() == 0
350 ? null : (StructuredNameEditorView) mNames.getChildAt(0);
351 }
352
Walter Jangbf63a6d2015-05-05 09:14:35 -0700353 public PhoneticNameEditorView getFirstPhoneticNameEditorView() {
Walter Jangf5dfea42015-09-16 12:30:36 -0700354 // There should only ever be one phonetic name, it is enforced by dataKind.typeOverallMax
355 final CompactKindSectionView kindSectionView = (CompactKindSectionView)
356 mKindSectionViewsMap.get(DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME);
357 if (kindSectionView != null && kindSectionView.getChildCount() > 0) {
358 final View editorView = kindSectionView.getEditorView(0);
359 if (editorView instanceof PhoneticNameEditorView) {
360 return ((PhoneticNameEditorView) editorView);
361 }
362 }
363 return null;
Walter Jangbf63a6d2015-05-05 09:14:35 -0700364 }
365
Walter Jangd35e5ef2015-02-24 09:18:16 -0800366 public View getAggregationAnchorView() {
367 // Since there is only one structured name we can just return it as the anchor for
368 // the aggregation suggestions popup
369 if (mNames.getChildCount() == 0) {
370 return null;
371 }
372 return mNames.getChildAt(0).findViewById(R.id.anchor_view);
373 }
374
Walter Jang06f73a12015-06-17 11:15:48 -0700375 /**
376 * @param readOnlyDisplayName The display name to set on the new raw contact created in order
377 * to edit a read-only contact.
378 */
Walter Jangf46abd82015-02-20 16:52:04 -0800379 public void setState(RawContactDeltaList rawContactDeltas,
Walter Jang06f73a12015-06-17 11:15:48 -0700380 MaterialColorMapUtils.MaterialPalette materialPalette, ViewIdGenerator viewIdGenerator,
Walter Jang2d3f31c2015-06-18 23:15:31 -0700381 long photoId, long nameId, String readOnlyDisplayName, boolean hasNewContact,
Walter Jang708ea9e2015-09-10 15:42:05 -0700382 boolean isUserProfile, AccountWithDataSet primaryAccount) {
Walter Jang363d3fd2015-09-16 10:29:07 -0700383 mKindSectionDataMap.clear();
384
Walter Jangcab3dce2015-02-09 17:48:03 -0800385 mNames.removeAllViews();
Walter Jangf5dfea42015-09-16 12:30:36 -0700386 mKindSectionViews.removeAllViews();
387 mKindSectionViewsMap.clear();
Walter Jang363d3fd2015-09-16 10:29:07 -0700388 mMoreFields.setVisibility(View.VISIBLE);
Walter Jangcab3dce2015-02-09 17:48:03 -0800389
390 if (rawContactDeltas == null || rawContactDeltas.isEmpty()) {
391 return;
392 }
393
394 mViewIdGenerator = viewIdGenerator;
Walter Jangcab3dce2015-02-09 17:48:03 -0800395 setId(mViewIdGenerator.getId(rawContactDeltas.get(0), /* dataKind =*/ null,
396 /* valuesDelta =*/ null, ViewIdGenerator.NO_VIEW_INDEX));
Walter Jangf46abd82015-02-20 16:52:04 -0800397 mMaterialPalette = materialPalette;
Walter Jang708ea9e2015-09-10 15:42:05 -0700398 mPhotoId = photoId;
399 mNameId = nameId;
400 mReadOnlyDisplayName = readOnlyDisplayName;
401 mHasNewContact = hasNewContact;
402 mIsUserProfile = isUserProfile;
403 mPrimaryAccount = primaryAccount;
404 if (mPrimaryAccount == null) {
405 mPrimaryAccount = ContactEditorUtils.getInstance(getContext()).getDefaultAccount();
406 }
407 vlog("state: primary " + mPrimaryAccount);
Walter Jangcab3dce2015-02-09 17:48:03 -0800408
Walter Jangf5dfea42015-09-16 12:30:36 -0700409
410 vlog("state: setting editor state from " + rawContactDeltas.size() + " RawContactDelta(s)");
Walter Jang708ea9e2015-09-10 15:42:05 -0700411 parseRawContactDeltas(rawContactDeltas);
Walter Jangf5dfea42015-09-16 12:30:36 -0700412 if (mKindSectionDataMap == null || mKindSectionDataMap.isEmpty()) {
413 elog("No kind section data parsed from raw contact deltas");
414 return;
415 }
Walter Jang708ea9e2015-09-10 15:42:05 -0700416 addAccountInfo();
Walter Jang363d3fd2015-09-16 10:29:07 -0700417 addPhotoView();
Walter Jangf5dfea42015-09-16 12:30:36 -0700418 addNameView();
419 addKindSectionViews();
420
421 updateMoreFieldsButton();
Walter Jangcab3dce2015-02-09 17:48:03 -0800422 }
423
Walter Jang708ea9e2015-09-10 15:42:05 -0700424 private void parseRawContactDeltas(RawContactDeltaList rawContactDeltas) {
425 // Get the raw contact delta for the primary account (the one displayed at the top)
Walter Jang03cea2e2015-09-18 17:04:21 -0700426 if (mPrimaryAccount == null || TextUtils.isEmpty(mPrimaryAccount.name)
427 || !TextUtils.isEmpty(mReadOnlyDisplayName)) {
428 // Use the first writable contact if this is an insert for a read-only contact.
429 // In this case we can assume the first writable raw contact is the newly created one
430 // because inserts have a raw contact delta list of size 1 and read-only contacts have
431 // a list of size 2.
Walter Jang708ea9e2015-09-10 15:42:05 -0700432 for (RawContactDelta rawContactDelta : rawContactDeltas) {
433 if (!rawContactDelta.isVisible()) continue;
434 final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
435 if (accountType != null && accountType.areContactsWritable()) {
436 vlog("parse: using first writable raw contact as primary");
437 mPrimaryRawContactDelta = rawContactDelta;
438 break;
439 }
440 }
441 } else {
442 // Use the first writable contact that matches the primary account
443 for (RawContactDelta rawContactDelta : rawContactDeltas) {
444 if (!rawContactDelta.isVisible()) continue;
445 final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
446 if (accountType != null && accountType.areContactsWritable()
447 && Objects.equals(mPrimaryAccount.name, rawContactDelta.getAccountName())
448 && Objects.equals(mPrimaryAccount.type, rawContactDelta.getAccountType())
449 && Objects.equals(mPrimaryAccount.dataSet, rawContactDelta.getDataSet())) {
450 vlog("parse: matched the primary account raw contact");
451 mPrimaryRawContactDelta = rawContactDelta;
452 break;
453 }
Walter Jang2d3f31c2015-06-18 23:15:31 -0700454 }
455 }
Walter Jang708ea9e2015-09-10 15:42:05 -0700456 if (mPrimaryRawContactDelta == null) {
457 // Fall back to the first writable raw contact
458 for (RawContactDelta rawContactDelta : rawContactDeltas) {
459 if (!rawContactDelta.isVisible()) continue;
460 final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
461 if (accountType != null && accountType.areContactsWritable()) {
462 vlog("parse: falling back to the first writable raw contact as primary");
463 mPrimaryRawContactDelta = rawContactDelta;
464 break;
465 }
466 }
Walter Jang2d3f31c2015-06-18 23:15:31 -0700467 }
Walter Jang2d3f31c2015-06-18 23:15:31 -0700468
Walter Jang363d3fd2015-09-16 10:29:07 -0700469 RawContactModifier.ensureKindExists(mPrimaryRawContactDelta,
470 mPrimaryRawContactDelta.getAccountType(mAccountTypeManager),
471 StructuredName.CONTENT_ITEM_TYPE);
Walter Jangfa127a12015-06-18 09:48:18 -0700472
Walter Jang363d3fd2015-09-16 10:29:07 -0700473 RawContactModifier.ensureKindExists(mPrimaryRawContactDelta,
474 mPrimaryRawContactDelta.getAccountType(mAccountTypeManager),
475 Photo.CONTENT_ITEM_TYPE);
Walter Jangfa127a12015-06-18 09:48:18 -0700476
Walter Jang363d3fd2015-09-16 10:29:07 -0700477 // Build the kind section data list map
Walter Jangcab3dce2015-02-09 17:48:03 -0800478 for (RawContactDelta rawContactDelta : rawContactDeltas) {
Walter Jang363d3fd2015-09-16 10:29:07 -0700479 if (rawContactDelta == null || !rawContactDelta.isVisible()) continue;
Walter Jangcab3dce2015-02-09 17:48:03 -0800480 final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
Walter Jang363d3fd2015-09-16 10:29:07 -0700481 if (accountType == null) continue;
482 final List<DataKind> dataKinds = accountType.getSortedDataKinds();
483 final int dataKindSize = dataKinds == null ? 0 : dataKinds.size();
484 vlog("parse: " + dataKindSize + " dataKinds(s)");
485 for (int i = 0; i < dataKindSize; i++) {
486 final DataKind dataKind = dataKinds.get(i);
487 // Don't show read only values
488 if (dataKind == null || !dataKind.editable) continue;
489 // Get the kind section data list to add the new field to
490 List<KindSectionData> kindSectionDataList =
491 mKindSectionDataMap.get(dataKind.mimeType);
492 if (kindSectionDataList == null) {
493 kindSectionDataList = new ArrayList<>();
494 mKindSectionDataMap.put(dataKind.mimeType, kindSectionDataList);
Walter Jangac679af2015-06-01 12:17:06 -0700495 }
Walter Jang363d3fd2015-09-16 10:29:07 -0700496 final KindSectionData kindSectionData =
497 new KindSectionData(accountType, dataKind, rawContactDelta);
498 kindSectionDataList.add(kindSectionData);
499 vlog("parse: " + i + " " + dataKind.mimeType + " " +
500 (kindSectionData.hasValuesDeltas()
501 ? kindSectionData.getValuesDeltas().size() : null) + " value(s)");
Walter Jangac679af2015-06-01 12:17:06 -0700502 }
503 }
Walter Jang3efae4a2015-02-18 11:12:00 -0800504 }
505
Walter Jang708ea9e2015-09-10 15:42:05 -0700506 private void addAccountInfo() {
507 if (mPrimaryRawContactDelta == null) {
Walter Jang708ea9e2015-09-10 15:42:05 -0700508 mAccountHeaderContainer.setVisibility(View.GONE);
509 mAccountSelectorContainer.setVisibility(View.GONE);
510 return;
511 }
512
513 // Get the account information for the primary raw contact delta
514 final Pair<String,String> accountInfo = EditorUiUtils.getAccountInfo(getContext(),
515 mIsUserProfile, mPrimaryRawContactDelta.getAccountName(),
516 mPrimaryRawContactDelta.getAccountType(mAccountTypeManager));
517
518 // The account header and selector show the same information so both shouldn't be visible
519 // at the same time
520 final List<AccountWithDataSet> accounts =
521 AccountTypeManager.getInstance(getContext()).getAccounts(true);
522 if (mHasNewContact && !mIsUserProfile && accounts.size() > 1) {
523 mAccountHeaderContainer.setVisibility(View.GONE);
524 addAccountSelector(accountInfo);
525 } else {
526 addAccountHeader(accountInfo);
527 mAccountSelectorContainer.setVisibility(View.GONE);
528 }
529 }
530
531 private void addAccountHeader(Pair<String,String> accountInfo) {
Walter Jang03cea2e2015-09-18 17:04:21 -0700532 if (TextUtils.isEmpty(accountInfo.first)) {
Walter Jang708ea9e2015-09-10 15:42:05 -0700533 // Hide this view so the other text view will be centered vertically
534 mAccountHeaderName.setVisibility(View.GONE);
535 } else {
536 mAccountHeaderName.setVisibility(View.VISIBLE);
537 mAccountHeaderName.setText(accountInfo.first);
538 }
539 mAccountHeaderType.setText(accountInfo.second);
540
541 mAccountHeaderContainer.setContentDescription(
542 EditorUiUtils.getAccountInfoContentDescription(
543 accountInfo.first, accountInfo.second));
544 }
545
546 private void addAccountSelector(Pair<String,String> accountInfo) {
547 mAccountSelectorContainer.setVisibility(View.VISIBLE);
548
Walter Jang03cea2e2015-09-18 17:04:21 -0700549 if (TextUtils.isEmpty(accountInfo.first)) {
Walter Jang708ea9e2015-09-10 15:42:05 -0700550 // Hide this view so the other text view will be centered vertically
551 mAccountSelectorName.setVisibility(View.GONE);
552 } else {
553 mAccountSelectorName.setVisibility(View.VISIBLE);
554 mAccountSelectorName.setText(accountInfo.first);
555 }
556 mAccountSelectorType.setText(accountInfo.second);
557
558 mAccountSelectorContainer.setContentDescription(
559 EditorUiUtils.getAccountInfoContentDescription(
560 accountInfo.first, accountInfo.second));
561
562 mAccountSelector.setOnClickListener(new View.OnClickListener() {
563 @Override
564 public void onClick(View v) {
565 final ListPopupWindow popup = new ListPopupWindow(getContext(), null);
566 final AccountsListAdapter adapter =
567 new AccountsListAdapter(getContext(),
568 AccountsListAdapter.AccountListFilter.ACCOUNTS_CONTACT_WRITABLE,
569 mPrimaryAccount);
570 popup.setWidth(mAccountSelectorContainer.getWidth());
571 popup.setAnchorView(mAccountSelectorContainer);
572 popup.setAdapter(adapter);
573 popup.setModal(true);
574 popup.setInputMethodMode(ListPopupWindow.INPUT_METHOD_NOT_NEEDED);
575 popup.setOnItemClickListener(new AdapterView.OnItemClickListener() {
576 @Override
577 public void onItemClick(AdapterView<?> parent, View view, int position,
578 long id) {
579 UiClosables.closeQuietly(popup);
580 final AccountWithDataSet newAccount = adapter.getItem(position);
581 if (mListener != null && !mPrimaryAccount.equals(newAccount)) {
582 mListener.onRebindEditorsForNewContact(
583 mPrimaryRawContactDelta,
584 mPrimaryAccount,
585 newAccount);
586 }
587 }
588 });
589 popup.show();
590 }
591 });
592 }
593
Walter Jang363d3fd2015-09-16 10:29:07 -0700594 private void addPhotoView() {
595 // Get the kind section data and values delta that will back the photo view
596 final String mimeType = Photo.CONTENT_ITEM_TYPE;
597 Pair<KindSectionData,ValuesDelta> pair = getPrimaryKindSectionData(mimeType, mPhotoId);
598 if (pair == null) {
599 wlog(mimeType + ": no kind section data parsed");
600 return;
601 }
602 final KindSectionData kindSectionData = pair.first;
603 final ValuesDelta valuesDelta = pair.second;
Walter Jang06f73a12015-06-17 11:15:48 -0700604
Walter Jang363d3fd2015-09-16 10:29:07 -0700605 // If we're editing a read-only contact we want to display the photo from the
606 // read-only contact in a photo editor backed by the new raw contact
607 // that was created. The new raw contact is the first writable one.
608 // See go/editing-read-only-contacts
609 if (mReadOnlyDisplayName != null) {
610 mPhotoRawContactId = mPrimaryRawContactDelta.getRawContactId();
611 }
Walter Jang06f73a12015-06-17 11:15:48 -0700612
Walter Jang363d3fd2015-09-16 10:29:07 -0700613 mPhotoRawContactId = kindSectionData.getRawContactDelta().getRawContactId();
614 mPhoto.setValues(kindSectionData.getDataKind(), valuesDelta,
615 kindSectionData.getRawContactDelta(),
616 !kindSectionData.getAccountType().areContactsWritable(), mMaterialPalette,
617 mViewIdGenerator);
618 }
619
Walter Jangf5dfea42015-09-16 12:30:36 -0700620 private void addNameView() {
621 // Get the kind section data and values delta that will back the photo view
Walter Jang363d3fd2015-09-16 10:29:07 -0700622 final String mimeType = StructuredName.CONTENT_ITEM_TYPE;
623 Pair<KindSectionData,ValuesDelta> pair = getPrimaryKindSectionData(mimeType, mNameId);
624 if (pair == null) {
625 wlog(mimeType + ": no kind section data parsed");
626 return;
627 }
628 final KindSectionData kindSectionData = pair.first;
629 final ValuesDelta valuesDelta = pair.second;
630
631 // If we're editing a read-only contact we want to display the name from the
632 // read-only contact in the name editor backed by the new raw contact
633 // that was created. The new raw contact is the first writable one.
634 // See go/editing-read-only-contacts
Walter Jang363d3fd2015-09-16 10:29:07 -0700635 if (!TextUtils.isEmpty(mReadOnlyDisplayName)) {
636 for (KindSectionData data : mKindSectionDataMap.get(mimeType)) {
637 if (data.getAccountType().areContactsWritable()) {
638 vlog(mimeType + ": using name from read-only contact");
639 mUsingDefaultNameEditorView = true;
640 break;
Walter Jang06f73a12015-06-17 11:15:48 -0700641 }
642 }
643 }
644
Walter Jang363d3fd2015-09-16 10:29:07 -0700645 final NameEditorListener nameEditorListener = new NameEditorListener(valuesDelta,
646 kindSectionData.getRawContactDelta().getRawContactId(), mListener);
647 final StructuredNameEditorView nameEditorView = inflateStructuredNameEditorView(
648 mNames, kindSectionData.getAccountType(), valuesDelta,
649 kindSectionData.getRawContactDelta(), nameEditorListener,
650 !kindSectionData.getAccountType().areContactsWritable());
651 mNames.addView(nameEditorView);
Walter Jang10446452015-02-20 13:51:16 -0800652
Walter Jang363d3fd2015-09-16 10:29:07 -0700653 // TODO: Remove this after eliminating the full editor
654 mNameValuesDelta = valuesDelta;
655 if (mUsingDefaultNameEditorView) {
656 mDefaultNameEditorView = nameEditorView;
657 }
658 }
Walter Jang10446452015-02-20 13:51:16 -0800659
Walter Jang363d3fd2015-09-16 10:29:07 -0700660 private Pair<KindSectionData,ValuesDelta> getPrimaryKindSectionData(String mimeType, long id) {
661 final List<KindSectionData> kindSectionDataList = mKindSectionDataMap.get(mimeType);
662 if (kindSectionDataList == null || kindSectionDataList.isEmpty()) {
663 wlog(mimeType + ": no kind section data parsed");
664 return null;
665 }
Walter Jang151f3e62015-02-26 15:29:40 -0800666
Walter Jang363d3fd2015-09-16 10:29:07 -0700667 KindSectionData resultKindSectionData = null;
668 ValuesDelta resultValuesDelta = null;
669 if (id > 0) {
670 // Look for a match for the ID that was passed in
671 for (KindSectionData kindSectionData : kindSectionDataList) {
672 resultValuesDelta = kindSectionData.getValuesDeltaById(id);
673 if (resultValuesDelta != null) {
674 vlog(mimeType + ": matched kind section data by ID");
675 resultKindSectionData = kindSectionData;
676 break;
Walter Jang398cd4b2015-06-16 11:17:53 -0700677 }
678 }
679 }
Walter Jang363d3fd2015-09-16 10:29:07 -0700680 if (resultKindSectionData == null) {
681 // Look for a super primary photo
682 for (KindSectionData kindSectionData : kindSectionDataList) {
683 resultValuesDelta = kindSectionData.getSuperPrimaryValuesDelta();
684 if (resultValuesDelta != null) {
685 wlog(mimeType + ": matched super primary kind section data");
686 resultKindSectionData = kindSectionData;
687 break;
688 }
Walter Jang151f3e62015-02-26 15:29:40 -0800689 }
690 }
Walter Jang363d3fd2015-09-16 10:29:07 -0700691 if (resultKindSectionData == null) {
692 // Fall back to the first non-empty value
693 for (KindSectionData kindSectionData : kindSectionDataList) {
694 resultValuesDelta = kindSectionData.getFirstNonEmptyValuesDelta();
695 if (resultValuesDelta != null) {
696 vlog(mimeType + ": using first non empty value");
697 resultKindSectionData = kindSectionData;
698 break;
699 }
Walter Jang151f3e62015-02-26 15:29:40 -0800700 }
701 }
Walter Jang363d3fd2015-09-16 10:29:07 -0700702 if (resultKindSectionData == null || resultValuesDelta == null) {
703 final List<ValuesDelta> valuesDeltaList = kindSectionDataList.get(0).getValuesDeltas();
704 if (valuesDeltaList != null && !valuesDeltaList.isEmpty()) {
705 vlog(mimeType + ": falling back to first empty entry");
706 resultValuesDelta = valuesDeltaList.get(0);
707 resultKindSectionData = kindSectionDataList.get(0);
Walter Jang10446452015-02-20 13:51:16 -0800708 }
709 }
Walter Jang363d3fd2015-09-16 10:29:07 -0700710 return resultKindSectionData != null && resultValuesDelta != null
711 ? new Pair<>(resultKindSectionData, resultValuesDelta) : null;
Walter Jang10446452015-02-20 13:51:16 -0800712 }
713
Walter Jangf5dfea42015-09-16 12:30:36 -0700714 private void addKindSectionViews() {
715 // Sort the kinds
716 final TreeSet<Map.Entry<String,List<KindSectionData>>> entries =
717 new TreeSet<>(MIME_TYPE_COMPARATOR);
718 entries.addAll(mKindSectionDataMap.entrySet());
Walter Jang3efae4a2015-02-18 11:12:00 -0800719
Walter Jangf5dfea42015-09-16 12:30:36 -0700720 vlog("kind: " + entries.size() + " kindSection(s)");
721 int i = 0;
722 for (Map.Entry<String, List<KindSectionData>> entry : entries) {
723 final String mimeType = entry.getKey();
724 final List<KindSectionData> kindSectionDataList = entry.getValue();
725 vlog("kind: " + i++ + " " + mimeType + ": " + (kindSectionDataList == null ? 0
726 : kindSectionDataList.size()) + " kindSectionData(s)");
Walter Jangab50e6f2015-06-15 08:57:22 -0700727
Walter Jangf5dfea42015-09-16 12:30:36 -0700728 // Ignore mime types that we've already handled
729 if (Photo.CONTENT_ITEM_TYPE.equals(mimeType)
730 || StructuredName.CONTENT_ITEM_TYPE.equals(mimeType)
731 || DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME.equals(mimeType)) continue;
732
733 // Ignore mime types that we don't handle
734 if (GroupMembership.CONTENT_ITEM_TYPE.equals(mimeType)) continue;
735
736 if (kindSectionDataList != null) {
737 final CompactKindSectionView kindSectionView = inflateKindSectionView(
738 mKindSectionViews, kindSectionDataList, mimeType);
739 mKindSectionViewsMap.put(mimeType, kindSectionView);
740 mKindSectionViews.addView(kindSectionView);
Walter Jangcab3dce2015-02-09 17:48:03 -0800741 }
742 }
743 }
744
Walter Jangcab3dce2015-02-09 17:48:03 -0800745 private StructuredNameEditorView inflateStructuredNameEditorView(ViewGroup viewGroup,
Walter Jang151f3e62015-02-26 15:29:40 -0800746 AccountType accountType, ValuesDelta valuesDelta, RawContactDelta rawContactDelta,
Walter Jang65d35202015-06-16 13:08:34 -0700747 NameEditorListener nameEditorListener, boolean readOnly) {
Walter Jangcab3dce2015-02-09 17:48:03 -0800748 final StructuredNameEditorView result = (StructuredNameEditorView) mLayoutInflater.inflate(
749 R.layout.structured_name_editor_view, viewGroup, /* attachToRoot =*/ false);
Walter Jang151f3e62015-02-26 15:29:40 -0800750 if (nameEditorListener != null) {
751 result.setEditorListener(nameEditorListener);
752 }
Walter Jang3a37a1a2015-03-04 07:41:32 -0800753 result.setDeletable(false);
Walter Jangf5dfea42015-09-16 12:30:36 -0700754 result.setValues(accountType.getKindForMimetype(DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME),
755 valuesDelta, rawContactDelta, readOnly, mViewIdGenerator);
Walter Jangcab3dce2015-02-09 17:48:03 -0800756 return result;
757 }
758
Walter Jangf5dfea42015-09-16 12:30:36 -0700759 private CompactKindSectionView inflateKindSectionView(ViewGroup viewGroup,
760 List<KindSectionData> kindSectionDataList, String mimeType) {
761 final CompactKindSectionView kindSectionView = (CompactKindSectionView)
762 mLayoutInflater.inflate(R.layout.compact_item_kind_section, viewGroup,
763 /* attachToRoot =*/ false);
764
765 if (Phone.CONTENT_ITEM_TYPE.equals(mimeType)
766 || Email.CONTENT_ITEM_TYPE.equals(mimeType)) {
767 // Phone numbers and emails address are always displayed and are
768 // the only types you add new values to initially
769 kindSectionView.setHideWhenEmpty(false);
770 }
771 // TODO: talk to Ricardo about when to allow adding new entries in compact mode
772 kindSectionView.setShowOneEmptyEditor(true);
773
774 kindSectionView.setState(kindSectionDataList, /* readOnly =*/ false,
Walter Jangcab3dce2015-02-09 17:48:03 -0800775 mViewIdGenerator);
Walter Jangf5dfea42015-09-16 12:30:36 -0700776
777 return kindSectionView;
Walter Jangcab3dce2015-02-09 17:48:03 -0800778 }
779
Walter Jangf5dfea42015-09-16 12:30:36 -0700780 private void updateMoreFieldsButton() {
781 // If any kind section views are hidden then show the link
782 for (int i = 0; i < mKindSectionViews.getChildCount(); i++) {
783 final CompactKindSectionView kindSectionView =
784 (CompactKindSectionView) mKindSectionViews.getChildAt(i);
785 if (kindSectionView.getVisibility() == View.GONE) {
786 // Show the more fields button
787 mMoreFields.setVisibility(View.VISIBLE);
788 return;
789 }
790 }
791 // Hide the more fields button
792 mMoreFields.setVisibility(View.GONE);
Walter Jangcab3dce2015-02-09 17:48:03 -0800793 }
794
Walter Jangbf63a6d2015-05-05 09:14:35 -0700795 private static void vlog(String message) {
796 if (Log.isLoggable(TAG, Log.VERBOSE)) {
797 Log.v(TAG, message);
Walter Jangcab3dce2015-02-09 17:48:03 -0800798 }
799 }
Walter Jang363d3fd2015-09-16 10:29:07 -0700800
801 private static void wlog(String message) {
802 if (Log.isLoggable(TAG, Log.WARN)) {
803 Log.w(TAG, message);
804 }
805 }
806
807 private static void elog(String message) {
808 Log.e(TAG, message);
809 }
Walter Jangcab3dce2015-02-09 17:48:03 -0800810}