blob: ca2aa9300f497850881769991412e7a646fa1baf [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;
26import com.android.contacts.common.model.account.AccountType.EditField;
Walter Jang2d3f31c2015-06-18 23:15:31 -070027import com.android.contacts.common.model.account.AccountWithDataSet;
Walter Jangcab3dce2015-02-09 17:48:03 -080028import com.android.contacts.common.model.dataitem.DataKind;
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 Jangcab3dce2015-02-09 17:48:03 -080031
32import android.content.Context;
Walter Jang3efae4a2015-02-18 11:12:00 -080033import android.graphics.Bitmap;
Walter Jang41b3ea12015-03-09 17:30:06 -070034import android.net.Uri;
Walter Jangcab3dce2015-02-09 17:48:03 -080035import android.provider.ContactsContract.CommonDataKinds.Email;
36import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
Walter Jang3efae4a2015-02-18 11:12:00 -080037import android.provider.ContactsContract.CommonDataKinds.Nickname;
Walter Jangcab3dce2015-02-09 17:48:03 -080038import android.provider.ContactsContract.CommonDataKinds.Phone;
39import android.provider.ContactsContract.CommonDataKinds.Photo;
Walter Jangcab3dce2015-02-09 17:48:03 -080040import android.provider.ContactsContract.CommonDataKinds.StructuredName;
41import android.text.TextUtils;
42import android.util.AttributeSet;
43import android.util.Log;
Walter Jang2d3f31c2015-06-18 23:15:31 -070044import android.util.Pair;
Walter Jangcab3dce2015-02-09 17:48:03 -080045import android.view.LayoutInflater;
Walter Jangb1c87622015-02-13 17:51:38 -080046import android.view.View;
Walter Jangcab3dce2015-02-09 17:48:03 -080047import android.view.ViewGroup;
48import android.widget.LinearLayout;
Walter Jang2d3f31c2015-06-18 23:15:31 -070049import android.widget.TextView;
Walter Jangcab3dce2015-02-09 17:48:03 -080050
51import java.util.ArrayList;
52import java.util.List;
53
54/**
55 * View to display information from multiple {@link RawContactDelta}s grouped together
56 * (e.g. all the phone numbers from a {@link com.android.contacts.common.model.Contact} together.
57 */
Walter Jangb6ca2722015-02-20 11:10:25 -080058public class CompactRawContactsEditorView extends LinearLayout implements View.OnClickListener {
Walter Jangcab3dce2015-02-09 17:48:03 -080059
60 private static final String TAG = "CompactEditorView";
61
Walter Jangb6ca2722015-02-20 11:10:25 -080062 /**
63 * Callbacks for hosts of {@link CompactRawContactsEditorView}s.
64 */
65 public interface Listener {
66
67 /**
68 * Invoked when the compact editor should be expanded to show all fields.
69 */
70 public void onExpandEditor();
Walter Jang151f3e62015-02-26 15:29:40 -080071
72 /**
73 * Invoked when the structured name editor field has changed.
74 *
75 * @param rawContactId The raw contact ID from the underlying {@link RawContactDelta}.
76 * @param valuesDelta The values from the underlying {@link RawContactDelta}.
77 */
78 public void onNameFieldChanged(long rawContactId, ValuesDelta valuesDelta);
79 }
80
81 /**
82 * Marks a name as super primary when it is changed.
83 *
84 * This is for the case when two or more raw contacts with names are joined where neither is
85 * marked as super primary. If the user hits back (which causes a save) after changing the
86 * name that was arbitrarily displayed, we want that to be the name that is used.
87 *
88 * Should only be set when a super primary name does not already exist since we only show
89 * one name field.
90 */
91 static final class NameEditorListener implements Editor.EditorListener {
92
93 private final ValuesDelta mValuesDelta;
94 private final long mRawContactId;
95 private final Listener mListener;
96
97 public NameEditorListener(ValuesDelta valuesDelta, long rawContactId,
98 Listener listener) {
99 mValuesDelta = valuesDelta;
100 mRawContactId = rawContactId;
101 mListener = listener;
102 }
103
104 @Override
105 public void onRequest(int request) {
106 if (request == Editor.EditorListener.FIELD_CHANGED) {
107 mValuesDelta.setSuperPrimary(true);
108 if (mListener != null) {
109 mListener.onNameFieldChanged(mRawContactId, mValuesDelta);
110 }
111 } else if (request == Editor.EditorListener.FIELD_TURNED_EMPTY) {
112 mValuesDelta.setSuperPrimary(false);
113 }
114 }
115
116 @Override
117 public void onDeleteRequested(Editor editor) {
118 }
Walter Jangb6ca2722015-02-20 11:10:25 -0800119 }
120
121 private Listener mListener;
122
Walter Jangcab3dce2015-02-09 17:48:03 -0800123 private AccountTypeManager mAccountTypeManager;
124 private LayoutInflater mLayoutInflater;
Walter Jangd35e5ef2015-02-24 09:18:16 -0800125
Walter Jangcab3dce2015-02-09 17:48:03 -0800126 private ViewIdGenerator mViewIdGenerator;
Walter Jangf46abd82015-02-20 16:52:04 -0800127 private MaterialColorMapUtils.MaterialPalette mMaterialPalette;
Walter Jangcab3dce2015-02-09 17:48:03 -0800128
Walter Jang2d3f31c2015-06-18 23:15:31 -0700129 private View mAccountContainer;
130 private TextView mAccountTypeView;
131 private TextView mAccountNameView;
Walter Janga5e4bb22015-02-24 13:08:16 -0800132 private CompactPhotoEditorView mPhoto;
Walter Jangcab3dce2015-02-09 17:48:03 -0800133 private ViewGroup mNames;
Walter Jangb1c87622015-02-13 17:51:38 -0800134 private ViewGroup mPhoneticNames;
135 private ViewGroup mNicknames;
Walter Jangcab3dce2015-02-09 17:48:03 -0800136 private ViewGroup mPhoneNumbers;
137 private ViewGroup mEmails;
138 private ViewGroup mOther;
Walter Jangb6ca2722015-02-20 11:10:25 -0800139 private View mMoreFields;
Walter Jangcab3dce2015-02-09 17:48:03 -0800140
Walter Jang151f3e62015-02-26 15:29:40 -0800141 // The ValuesDelta for the non super primary name that was displayed to the user.
142 private ValuesDelta mNameValuesDelta;
143
Walter Jang3efae4a2015-02-18 11:12:00 -0800144 private long mPhotoRawContactId;
145
Walter Jang06f73a12015-06-17 11:15:48 -0700146 private StructuredNameEditorView mDefaultNameEditorView;
147
Walter Jangcab3dce2015-02-09 17:48:03 -0800148 public CompactRawContactsEditorView(Context context) {
149 super(context);
150 }
151
152 public CompactRawContactsEditorView(Context context, AttributeSet attrs) {
153 super(context, attrs);
154 }
155
Walter Jangb6ca2722015-02-20 11:10:25 -0800156 /**
157 * Sets the receiver for {@link CompactRawContactsEditorView} callbacks.
158 */
159 public void setListener(Listener listener) {
160 mListener = listener;
161 }
162
Walter Jangcab3dce2015-02-09 17:48:03 -0800163 @Override
164 protected void onFinishInflate() {
165 super.onFinishInflate();
166
167 mAccountTypeManager = AccountTypeManager.getInstance(getContext());
168 mLayoutInflater = (LayoutInflater)
169 getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
170
Walter Jang2d3f31c2015-06-18 23:15:31 -0700171 mAccountContainer = findViewById(R.id.account_container);
172 mAccountTypeView = (TextView) findViewById(R.id.account_type);
173 mAccountNameView = (TextView) findViewById(R.id.account_name);
Walter Janga5e4bb22015-02-24 13:08:16 -0800174 mPhoto = (CompactPhotoEditorView) findViewById(R.id.photo_editor);
Walter Jangcab3dce2015-02-09 17:48:03 -0800175 mNames = (LinearLayout) findViewById(R.id.names);
Walter Jangb1c87622015-02-13 17:51:38 -0800176 mPhoneticNames = (LinearLayout) findViewById(R.id.phonetic_names);
177 mNicknames = (LinearLayout) findViewById(R.id.nicknames);
Walter Jangcab3dce2015-02-09 17:48:03 -0800178 mPhoneNumbers = (LinearLayout) findViewById(R.id.phone_numbers);
179 mEmails = (LinearLayout) findViewById(R.id.emails);
180 mOther = (LinearLayout) findViewById(R.id.other);
Walter Jangb6ca2722015-02-20 11:10:25 -0800181 mMoreFields = findViewById(R.id.more_fields);
182 mMoreFields.setOnClickListener(this);
183 }
184
Walter Jangb6ca2722015-02-20 11:10:25 -0800185 @Override
186 public void onClick(View view) {
187 if (view.getId() == R.id.more_fields && mListener != null ) {
188 mListener.onExpandEditor();
189 }
Walter Jangcab3dce2015-02-09 17:48:03 -0800190 }
191
192 @Override
193 public void setEnabled(boolean enabled) {
194 super.setEnabled(enabled);
195 setEnabled(enabled, mNames);
Walter Jangb1c87622015-02-13 17:51:38 -0800196 setEnabled(enabled, mPhoneticNames);
197 setEnabled(enabled, mNicknames);
Walter Jangcab3dce2015-02-09 17:48:03 -0800198 setEnabled(enabled, mPhoneNumbers);
199 setEnabled(enabled, mEmails);
200 setEnabled(enabled, mOther);
201 }
202
203 private void setEnabled(boolean enabled, ViewGroup viewGroup) {
204 if (viewGroup != null) {
205 final int childCount = viewGroup.getChildCount();
206 for (int i = 0; i < childCount; i++) {
207 viewGroup.getChildAt(i).setEnabled(enabled);
208 }
209 }
210 }
211
Walter Jang3efae4a2015-02-18 11:12:00 -0800212 /**
Walter Janga5e4bb22015-02-24 13:08:16 -0800213 * Pass through to {@link CompactPhotoEditorView#setPhotoHandler}.
Walter Jang3efae4a2015-02-18 11:12:00 -0800214 */
215 public void setPhotoHandler(PhotoHandler photoHandler) {
Walter Janga5e4bb22015-02-24 13:08:16 -0800216 mPhoto.setPhotoHandler(photoHandler);
Walter Jang3efae4a2015-02-18 11:12:00 -0800217 }
218
219 /**
Walter Janga5e4bb22015-02-24 13:08:16 -0800220 * Pass through to {@link CompactPhotoEditorView#setPhoto}.
Walter Jang3efae4a2015-02-18 11:12:00 -0800221 */
222 public void setPhoto(Bitmap bitmap) {
Walter Janga5e4bb22015-02-24 13:08:16 -0800223 mPhoto.setPhoto(bitmap);
Walter Jang3efae4a2015-02-18 11:12:00 -0800224 }
225
226 /**
Walter Jang41b3ea12015-03-09 17:30:06 -0700227 * Pass through to {@link CompactPhotoEditorView#setFullSizedPhoto(Uri)}.
228 */
229 public void setFullSizePhoto(Uri photoUri) {
230 mPhoto.setFullSizedPhoto(photoUri);
231 }
232
233 /**
Walter Janga5e4bb22015-02-24 13:08:16 -0800234 * Pass through to {@link CompactPhotoEditorView#isWritablePhotoSet}.
Walter Jang3efae4a2015-02-18 11:12:00 -0800235 */
236 public boolean isWritablePhotoSet() {
Walter Janga5e4bb22015-02-24 13:08:16 -0800237 return mPhoto.isWritablePhotoSet();
Walter Jang3efae4a2015-02-18 11:12:00 -0800238 }
239
240 /**
Walter Jang3efae4a2015-02-18 11:12:00 -0800241 * Get the raw contact ID for the CompactHeaderView photo.
242 */
Walter Jang3efae4a2015-02-18 11:12:00 -0800243 public long getPhotoRawContactId() {
244 return mPhotoRawContactId;
245 }
246
Walter Jang06f73a12015-06-17 11:15:48 -0700247 public StructuredNameEditorView getDefaultNameEditorView() {
248 return mDefaultNameEditorView;
249 }
250
Walter Jangd35e5ef2015-02-24 09:18:16 -0800251 public StructuredNameEditorView getStructuredNameEditorView() {
252 // We only ever show one StructuredName
253 return mNames.getChildCount() == 0
254 ? null : (StructuredNameEditorView) mNames.getChildAt(0);
255 }
256
Walter Jangbf63a6d2015-05-05 09:14:35 -0700257 public PhoneticNameEditorView getFirstPhoneticNameEditorView() {
258 // There should only ever be one phonetic name
259 return mPhoneticNames.getChildCount() == 0
260 ? null : (PhoneticNameEditorView) mPhoneticNames.getChildAt(0);
261 }
262
Walter Jangd35e5ef2015-02-24 09:18:16 -0800263 public View getAggregationAnchorView() {
264 // Since there is only one structured name we can just return it as the anchor for
265 // the aggregation suggestions popup
266 if (mNames.getChildCount() == 0) {
267 return null;
268 }
269 return mNames.getChildAt(0).findViewById(R.id.anchor_view);
270 }
271
Walter Jang06f73a12015-06-17 11:15:48 -0700272 /**
273 * @param readOnlyDisplayName The display name to set on the new raw contact created in order
274 * to edit a read-only contact.
275 */
Walter Jangf46abd82015-02-20 16:52:04 -0800276 public void setState(RawContactDeltaList rawContactDeltas,
Walter Jang06f73a12015-06-17 11:15:48 -0700277 MaterialColorMapUtils.MaterialPalette materialPalette, ViewIdGenerator viewIdGenerator,
Walter Jang2d3f31c2015-06-18 23:15:31 -0700278 long photoId, long nameId, String readOnlyDisplayName, boolean hasNewContact,
279 boolean isUserProfile) {
Walter Jangcab3dce2015-02-09 17:48:03 -0800280 mNames.removeAllViews();
Walter Jangb1c87622015-02-13 17:51:38 -0800281 mPhoneticNames.removeAllViews();
282 mNicknames.removeAllViews();
Walter Jangcab3dce2015-02-09 17:48:03 -0800283 mPhoneNumbers.removeAllViews();
284 mEmails.removeAllViews();
285 mOther.removeAllViews();
286
287 if (rawContactDeltas == null || rawContactDeltas.isEmpty()) {
288 return;
289 }
290
291 mViewIdGenerator = viewIdGenerator;
Walter Jangcab3dce2015-02-09 17:48:03 -0800292 setId(mViewIdGenerator.getId(rawContactDeltas.get(0), /* dataKind =*/ null,
293 /* valuesDelta =*/ null, ViewIdGenerator.NO_VIEW_INDEX));
Walter Jangf46abd82015-02-20 16:52:04 -0800294 mMaterialPalette = materialPalette;
Walter Jangcab3dce2015-02-09 17:48:03 -0800295
Walter Jangbf63a6d2015-05-05 09:14:35 -0700296 vlog("Setting compact editor state from " + rawContactDeltas);
Walter Jang2d3f31c2015-06-18 23:15:31 -0700297 addAccountInfo(rawContactDeltas, hasNewContact, readOnlyDisplayName, isUserProfile);
Walter Jangfa127a12015-06-18 09:48:18 -0700298 addPhotoView(rawContactDeltas, viewIdGenerator, photoId, readOnlyDisplayName);
Walter Jang06f73a12015-06-17 11:15:48 -0700299 addStructuredNameView(rawContactDeltas, nameId, readOnlyDisplayName);
Walter Jang3efae4a2015-02-18 11:12:00 -0800300 addEditorViews(rawContactDeltas);
Walter Jange720fde2015-02-17 10:54:14 -0800301 removeExtraEmptyTextFields(mPhoneNumbers);
302 removeExtraEmptyTextFields(mEmails);
Walter Jangcab3dce2015-02-09 17:48:03 -0800303 }
304
Walter Jang2d3f31c2015-06-18 23:15:31 -0700305 private void addAccountInfo(RawContactDeltaList rawContactDeltas, boolean hasNewContact,
306 String readOnlyDisplayName, boolean isUserProfile) {
307 // Only show account info for inserts and first time edits of read-only accounts
308 if (!hasNewContact && TextUtils.isEmpty(readOnlyDisplayName)) {
309 vlog("Account info hidden");
310 mAccountContainer.setVisibility(View.GONE);
311 return;
312 }
313
314 // Get the default account
315 final AccountWithDataSet defaultAccountWithDataSet =
316 ContactEditorUtils.getInstance(getContext()).getDefaultAccount();
317 if (defaultAccountWithDataSet == null) {
318 vlog("Account info hidden because default account not set");
319 mAccountContainer.setVisibility(View.GONE);
320 return;
321 }
322
323 // We can assume the first writable raw contact is the newly created one because
324 // inserts have a delta list of size 1 and read-only contacts are should be of size 2
325 RawContactDelta defaultAccountRawContactDelta = null;
326 AccountType accountType = null;
327 for (RawContactDelta rawContactDelta : rawContactDeltas) {
328 if (!rawContactDelta.isVisible()) continue;
329 accountType = rawContactDelta.getAccountType(mAccountTypeManager);
330 if (accountType != null && accountType.areContactsWritable()) {
331 defaultAccountRawContactDelta = rawContactDelta;
332 break;
333 }
334 }
335 if (defaultAccountRawContactDelta == null) {
336 vlog("Account info hidden because no raw contact delta matched");
337 mAccountContainer.setVisibility(View.GONE);
338 return;
339 }
340 if (accountType == null) {
341 vlog("Account info hidden because no account type returned from raw contact delta");
342 mAccountContainer.setVisibility(View.GONE);
343 return;
344 }
345
346 // Get the account information for the default account RawContactDelta
347 final Pair<String,String> accountInfo = EditorUiUtils.getAccountInfo(getContext(),
348 isUserProfile, defaultAccountRawContactDelta.getAccountName(),
349 accountType.getDisplayLabel(getContext()));
350
351 // Set the account information already
352 if (accountInfo == null) {
353 vlog("Account info hidden because no account info could be composed");
354 mAccountContainer.setVisibility(View.GONE);
355 return;
356 }
357 vlog("Account info loaded");
358 if (accountInfo.first == null) {
359 mAccountNameView.setVisibility(View.GONE);
360 } else {
361 mAccountNameView.setVisibility(View.VISIBLE);
362 mAccountNameView.setText(accountInfo.first);
363 }
364 mAccountTypeView.setText(accountInfo.second);
365
366 mAccountContainer.setContentDescription(EditorUiUtils.getAccountInfoContentDescription(
367 accountInfo.first, accountInfo.second));
368 }
369
Walter Jang151f3e62015-02-26 15:29:40 -0800370 private void addPhotoView(RawContactDeltaList rawContactDeltas,
Walter Jangfa127a12015-06-18 09:48:18 -0700371 ViewIdGenerator viewIdGenerator, long photoId, String readOnlyDisplayName) {
372 // If we're editing a read-only contact, the display name from the read-only
373 // contact is non empty and we can use it determine whether to back the photo editor with
374 // the empty new raw contact delta. See go/editing-read-only-contacts
375 final boolean readOnlyContact = !TextUtils.isEmpty(readOnlyDisplayName);
376 if (readOnlyContact) {
377 for (RawContactDelta rawContactDelta : rawContactDeltas) {
378 if (!rawContactDelta.isVisible()) continue;
379 final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
380
381 // Make sure we have a photo
382 RawContactModifier.ensureKindExists(
383 rawContactDelta, accountType, Photo.CONTENT_ITEM_TYPE);
384
385 final DataKind dataKind = accountType.getKindForMimetype(Photo.CONTENT_ITEM_TYPE);
386 if (accountType.areContactsWritable()) {
387 for (ValuesDelta valuesDelta : rawContactDelta.getMimeEntries(
388 Photo.CONTENT_ITEM_TYPE)) {
389 if (valuesDelta != null) {
390 // Break the loop but don't return because we need to keep going to
391 // in order to show the photo from the read-only contact.
392 mPhotoRawContactId = rawContactDelta.getRawContactId();
393 mPhoto.setValues(dataKind, valuesDelta, rawContactDelta,
394 /* readOnly =*/ false, mMaterialPalette, viewIdGenerator);
395 break;
396 }
397 }
398 }
399 }
400 }
401
Walter Jangac679af2015-06-01 12:17:06 -0700402 // Look for a match for the photo ID that was passed in
Walter Jangcab3dce2015-02-09 17:48:03 -0800403 for (RawContactDelta rawContactDelta : rawContactDeltas) {
Walter Jang3b210272015-03-04 11:34:11 -0800404 if (!rawContactDelta.isVisible()) continue;
Walter Jangcab3dce2015-02-09 17:48:03 -0800405 final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
Walter Jange720fde2015-02-17 10:54:14 -0800406
Walter Jang3efae4a2015-02-18 11:12:00 -0800407 // Make sure we have a photo
408 RawContactModifier.ensureKindExists(
409 rawContactDelta, accountType, Photo.CONTENT_ITEM_TYPE);
410
411 final DataKind dataKind = accountType.getKindForMimetype(Photo.CONTENT_ITEM_TYPE);
Walter Jangab50e6f2015-06-15 08:57:22 -0700412 if (dataKind != null && dataKind.editable) {
Walter Jangac679af2015-06-01 12:17:06 -0700413 for (ValuesDelta valuesDelta
414 : rawContactDelta.getMimeEntries(Photo.CONTENT_ITEM_TYPE)) {
415 if (valuesDelta != null && valuesDelta.getId() != null
416 && valuesDelta.getId().equals(photoId)) {
Walter Jangfa127a12015-06-18 09:48:18 -0700417 if (readOnlyContact) {
418 mPhoto.setPhoto(valuesDelta);
419 } else {
420 mPhotoRawContactId = rawContactDelta.getRawContactId();
421 mPhoto.setValues(dataKind, valuesDelta, rawContactDelta,
422 !accountType.areContactsWritable(),
423 mMaterialPalette, viewIdGenerator);
424 }
Walter Jangac679af2015-06-01 12:17:06 -0700425 return;
426 }
427 }
428 }
429 }
430
431 // Look for a non-empty super primary photo
432 for (RawContactDelta rawContactDelta : rawContactDeltas) {
433 if (!rawContactDelta.isVisible()) continue;
434 final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
435 final DataKind dataKind = accountType.getKindForMimetype(Photo.CONTENT_ITEM_TYPE);
Walter Jangab50e6f2015-06-15 08:57:22 -0700436 if (dataKind != null && dataKind.editable) {
Walter Jang3b210272015-03-04 11:34:11 -0800437 final ValuesDelta valuesDelta = getNonEmptySuperPrimaryValuesDeltas(
438 rawContactDelta, Photo.CONTENT_ITEM_TYPE, dataKind);
439 if (valuesDelta != null) {
Walter Jangfa127a12015-06-18 09:48:18 -0700440 if (readOnlyContact) {
441 mPhoto.setPhoto(valuesDelta);
442 } else {
443 mPhotoRawContactId = rawContactDelta.getRawContactId();
444 mPhoto.setValues(dataKind, valuesDelta, rawContactDelta,
445 !accountType.areContactsWritable(), mMaterialPalette,
446 viewIdGenerator);
447 }
Walter Jang3efae4a2015-02-18 11:12:00 -0800448 return;
449 }
450 }
451 }
Walter Jang3b210272015-03-04 11:34:11 -0800452 // We didn't find a non-empty super primary photo, use the first non-empty one
453 for (RawContactDelta rawContactDelta : rawContactDeltas) {
454 if (!rawContactDelta.isVisible()) continue;
455 final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
456 final DataKind dataKind = accountType.getKindForMimetype(Photo.CONTENT_ITEM_TYPE);
Walter Jangab50e6f2015-06-15 08:57:22 -0700457 if (dataKind != null && dataKind.editable) {
Walter Jang3b210272015-03-04 11:34:11 -0800458 final List<ValuesDelta> valuesDeltas = getNonEmptyValuesDeltas(
459 rawContactDelta, Photo.CONTENT_ITEM_TYPE, dataKind);
460 if (valuesDeltas != null && !valuesDeltas.isEmpty()) {
Walter Jangfa127a12015-06-18 09:48:18 -0700461 if (readOnlyContact) {
462 mPhoto.setPhoto(valuesDeltas.get(0));
463 } else {
464 mPhotoRawContactId = rawContactDelta.getRawContactId();
465 mPhoto.setValues(dataKind, valuesDeltas.get(0), rawContactDelta,
466 !accountType.areContactsWritable(), mMaterialPalette,
467 viewIdGenerator);
468 }
Walter Jang3b210272015-03-04 11:34:11 -0800469 return;
470 }
471 }
472 }
473 // No suitable non-empty photo
474 for (RawContactDelta rawContactDelta : rawContactDeltas) {
475 if (!rawContactDelta.isVisible()) continue;
476 final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
477 final DataKind dataKind = accountType.getKindForMimetype(Photo.CONTENT_ITEM_TYPE);
Walter Jangab50e6f2015-06-15 08:57:22 -0700478 if (dataKind != null && dataKind.editable) {
Walter Jang3b210272015-03-04 11:34:11 -0800479 final ValuesDelta valuesDelta = rawContactDelta.getSuperPrimaryEntry(
480 dataKind.mimeType, /* forceSelection =*/ true);
481 if (valuesDelta != null) {
Walter Jangfa127a12015-06-18 09:48:18 -0700482 if (readOnlyContact) {
483 mPhoto.setPhoto(valuesDelta);
484 } else {
485 mPhotoRawContactId = rawContactDelta.getRawContactId();
486 mPhoto.setValues(dataKind, valuesDelta, rawContactDelta,
487 !accountType.areContactsWritable(), mMaterialPalette,
488 viewIdGenerator);
489 }
Walter Jang3b210272015-03-04 11:34:11 -0800490 return;
491 }
492 }
493 }
494 // Should not happen since we ensure the kind exists but if we unexpectedly get here
495 // we must remove the photo section so that it does not take up the entire view
496 mPhoto.setVisibility(View.GONE);
Walter Jang3efae4a2015-02-18 11:12:00 -0800497 }
498
Walter Jang06f73a12015-06-17 11:15:48 -0700499 private void addStructuredNameView(RawContactDeltaList rawContactDeltas, long nameId,
500 String readOnlyDisplayName) {
501 // If we're editing a read-only contact we want to display the name from the read-only
502 // contact in a structured name editor backed by the new raw contact that was created.
503 // The new raw contact is writable and merging it with the read-only contact allows us
504 // to edit the read-only contact. See go/editing-read-only-contacts
505 if (!TextUtils.isEmpty(readOnlyDisplayName)) {
506 for (RawContactDelta rawContactDelta : rawContactDeltas) {
507 if (!rawContactDelta.isVisible()) continue;
508 final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
509
510 // Make sure we have a structured name
511 RawContactModifier.ensureKindExists(
512 rawContactDelta, accountType, StructuredName.CONTENT_ITEM_TYPE);
513
514 if (accountType.areContactsWritable()) {
515 for (ValuesDelta valuesDelta : rawContactDelta.getMimeEntries(
516 StructuredName.CONTENT_ITEM_TYPE)) {
517 if (valuesDelta != null) {
518 mNameValuesDelta = valuesDelta;
519 final NameEditorListener nameEditorListener = new NameEditorListener(
520 mNameValuesDelta, rawContactDelta.getRawContactId(), mListener);
521 final StructuredNameEditorView nameEditorView =
522 inflateStructuredNameEditorView(mNames, accountType,
523 mNameValuesDelta, rawContactDelta, nameEditorListener,
524 !accountType.areContactsWritable());
525 nameEditorView.setDisplayName(readOnlyDisplayName);
526 mNames.addView(nameEditorView);
527 mDefaultNameEditorView = nameEditorView;
528 return;
529 }
530 }
531 }
532 }
533 }
534
Walter Jang65d35202015-06-16 13:08:34 -0700535 // Look for a match for the name ID that was passed in
Walter Jang10446452015-02-20 13:51:16 -0800536 for (RawContactDelta rawContactDelta : rawContactDeltas) {
Walter Jang151f3e62015-02-26 15:29:40 -0800537 if (!rawContactDelta.isVisible()) continue;
Walter Jang10446452015-02-20 13:51:16 -0800538 final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
539
540 // Make sure we have a structured name
541 RawContactModifier.ensureKindExists(
542 rawContactDelta, accountType, StructuredName.CONTENT_ITEM_TYPE);
543
Walter Jang151f3e62015-02-26 15:29:40 -0800544 // Note use of pseudo mime type to get the DataKind and StructuredName to get value
Walter Jang10446452015-02-20 13:51:16 -0800545 final DataKind dataKind = accountType.getKindForMimetype(
Walter Jang151f3e62015-02-26 15:29:40 -0800546 DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME);
547 if (dataKind == null || !dataKind.editable) continue;
548
Walter Jang398cd4b2015-06-16 11:17:53 -0700549 for (ValuesDelta valuesDelta : rawContactDelta.getMimeEntries(
550 StructuredName.CONTENT_ITEM_TYPE)) {
551 if (valuesDelta != null && valuesDelta.getId() != null
552 && valuesDelta.getId().equals(nameId)) {
553 mNameValuesDelta = valuesDelta;
554 final NameEditorListener nameEditorListener = new NameEditorListener(
555 mNameValuesDelta, rawContactDelta.getRawContactId(), mListener);
556 mNames.addView(inflateStructuredNameEditorView(mNames, accountType,
Walter Jang65d35202015-06-16 13:08:34 -0700557 mNameValuesDelta, rawContactDelta, nameEditorListener,
558 !accountType.areContactsWritable()));
Walter Jang398cd4b2015-06-16 11:17:53 -0700559 return;
560 }
561 }
562 }
563 // Look for a super primary name
564 for (RawContactDelta rawContactDelta : rawContactDeltas) {
565 if (!rawContactDelta.isVisible()) continue;
566 final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
567
568 final DataKind dataKind = accountType.getKindForMimetype(
569 DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME);
570 if (dataKind == null || !dataKind.editable) continue;
571
Walter Jang151f3e62015-02-26 15:29:40 -0800572 final ValuesDelta superPrimaryValuesDelta = getNonEmptySuperPrimaryValuesDeltas(
573 rawContactDelta, StructuredName.CONTENT_ITEM_TYPE, dataKind);
574 if (superPrimaryValuesDelta != null) {
575 // Our first preference is for a non-empty super primary name
Walter Janga26490b2015-05-27 13:08:24 -0700576 final NameEditorListener nameEditorListener = new NameEditorListener(
577 superPrimaryValuesDelta, rawContactDelta.getRawContactId(), mListener);
Walter Jang151f3e62015-02-26 15:29:40 -0800578 mNames.addView(inflateStructuredNameEditorView(mNames, accountType,
Walter Jang65d35202015-06-16 13:08:34 -0700579 superPrimaryValuesDelta, rawContactDelta, nameEditorListener,
580 !accountType.areContactsWritable()));
Walter Jang151f3e62015-02-26 15:29:40 -0800581 return;
582 }
583 }
584 // We didn't find a super primary name
585 for (RawContactDelta rawContactDelta : rawContactDeltas) {
586 if (!rawContactDelta.isVisible()) continue;
587 final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
588
589 final DataKind dataKind = accountType.getKindForMimetype(
590 DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME);
591 if (dataKind == null || !dataKind.editable) continue;
592
593 final List<ValuesDelta> nonEmptyValuesDeltas = getNonEmptyValuesDeltas(
594 rawContactDelta, StructuredName.CONTENT_ITEM_TYPE, dataKind);
595 if (nonEmptyValuesDeltas != null && !nonEmptyValuesDeltas.isEmpty()) {
Walter Jang65d35202015-06-16 13:08:34 -0700596 // Take the first non-empty name
Walter Jang151f3e62015-02-26 15:29:40 -0800597 mNameValuesDelta = nonEmptyValuesDeltas.get(0);
598 final NameEditorListener nameEditorListener = new NameEditorListener(
599 mNameValuesDelta, rawContactDelta.getRawContactId(), mListener);
600 mNames.addView(inflateStructuredNameEditorView(mNames, accountType,
Walter Jang65d35202015-06-16 13:08:34 -0700601 mNameValuesDelta, rawContactDelta, nameEditorListener,
602 !accountType.areContactsWritable()));
Walter Jang151f3e62015-02-26 15:29:40 -0800603 return;
604 }
605 }
606 for (RawContactDelta rawContactDelta : rawContactDeltas) {
607 if (!rawContactDelta.isVisible()) continue;
608 final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
609
610 final DataKind dataKind = accountType.getKindForMimetype(
611 DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME);
612 if (dataKind == null || !dataKind.editable) continue;
613
614 // Fall back to the first entry
615 final ArrayList<ValuesDelta> valuesDeltas = rawContactDelta.getMimeEntries(
Walter Jang10446452015-02-20 13:51:16 -0800616 StructuredName.CONTENT_ITEM_TYPE);
Walter Jang151f3e62015-02-26 15:29:40 -0800617 if (valuesDeltas != null && !valuesDeltas.isEmpty()) {
618 mNameValuesDelta = valuesDeltas.get(0);
619 final NameEditorListener nameEditorListener = new NameEditorListener(
620 mNameValuesDelta, rawContactDelta.getRawContactId(), mListener);
621 mNames.addView(inflateStructuredNameEditorView(mNames, accountType,
Walter Jang65d35202015-06-16 13:08:34 -0700622 mNameValuesDelta, rawContactDelta, nameEditorListener,
623 !accountType.areContactsWritable()));
Walter Jang151f3e62015-02-26 15:29:40 -0800624 return;
Walter Jang10446452015-02-20 13:51:16 -0800625 }
626 }
627 }
628
Walter Jang3efae4a2015-02-18 11:12:00 -0800629 private void addEditorViews(RawContactDeltaList rawContactDeltas) {
630 for (RawContactDelta rawContactDelta : rawContactDeltas) {
Walter Jangab50e6f2015-06-15 08:57:22 -0700631 if (!rawContactDelta.isVisible()) continue;
Walter Jang3efae4a2015-02-18 11:12:00 -0800632 final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
633
Walter Jangcab3dce2015-02-09 17:48:03 -0800634 for (DataKind dataKind : accountType.getSortedDataKinds()) {
Walter Jangab50e6f2015-06-15 08:57:22 -0700635 if (!dataKind.editable) continue;
636
Walter Jangcab3dce2015-02-09 17:48:03 -0800637 final String mimeType = dataKind.mimeType;
Walter Jangbf63a6d2015-05-05 09:14:35 -0700638 vlog(mimeType + " " + dataKind.fieldList.size() + " field(s)");
Walter Jangcab3dce2015-02-09 17:48:03 -0800639 if (Photo.CONTENT_ITEM_TYPE.equals(mimeType)
Walter Jang10446452015-02-20 13:51:16 -0800640 || StructuredName.CONTENT_ITEM_TYPE.equals(mimeType)
Walter Jangcab3dce2015-02-09 17:48:03 -0800641 || GroupMembership.CONTENT_ITEM_TYPE.equals(mimeType)) {
Walter Jang151f3e62015-02-26 15:29:40 -0800642 // Photos and structured names are handled separately and
643 // group membership is not supported
Walter Jangcab3dce2015-02-09 17:48:03 -0800644 continue;
Walter Jangcab3dce2015-02-09 17:48:03 -0800645 } else if (DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME.equals(mimeType)) {
Walter Jang151f3e62015-02-26 15:29:40 -0800646 // Only add phonetic names if there is a non-empty one. Note the use of
647 // StructuredName mimeType below, even though we matched a pseudo mime type.
Walter Jang3a37a1a2015-03-04 07:41:32 -0800648 final ValuesDelta valuesDelta = rawContactDelta.getSuperPrimaryEntry(
649 StructuredName.CONTENT_ITEM_TYPE, /* forceSelection =*/ true);
650 if (hasNonEmptyValue(dataKind, valuesDelta)) {
651 mPhoneticNames.addView(inflatePhoneticNameEditorView(
652 mPhoneticNames, accountType, valuesDelta, rawContactDelta));
Walter Jangcab3dce2015-02-09 17:48:03 -0800653 }
654 } else if (Nickname.CONTENT_ITEM_TYPE.equals(mimeType)) {
Walter Jang151f3e62015-02-26 15:29:40 -0800655 // Only add nicknames if there is a non-empty one
Walter Jangcab3dce2015-02-09 17:48:03 -0800656 if (hasNonEmptyValuesDelta(rawContactDelta, mimeType, dataKind)) {
Walter Jang28c98b72015-06-01 15:56:37 -0700657 mNicknames.addView(inflateKindSectionView(mNicknames, dataKind,
Walter Jang88b0a8c2015-06-08 13:15:56 -0700658 rawContactDelta));
Walter Jangcab3dce2015-02-09 17:48:03 -0800659 }
660 } else if (Phone.CONTENT_ITEM_TYPE.equals(mimeType)) {
Walter Jang28c98b72015-06-01 15:56:37 -0700661 mPhoneNumbers.addView(inflateKindSectionView(mPhoneNumbers, dataKind,
Walter Jang88b0a8c2015-06-08 13:15:56 -0700662 rawContactDelta));
Walter Jangcab3dce2015-02-09 17:48:03 -0800663 } else if (Email.CONTENT_ITEM_TYPE.equals(mimeType)) {
Walter Jang88b0a8c2015-06-08 13:15:56 -0700664 mEmails.addView(inflateKindSectionView(mEmails, dataKind, rawContactDelta));
Walter Jangcab3dce2015-02-09 17:48:03 -0800665 } else if (hasNonEmptyValuesDelta(rawContactDelta, mimeType, dataKind)) {
Walter Jang88b0a8c2015-06-08 13:15:56 -0700666 mOther.addView(inflateKindSectionView(mOther, dataKind, rawContactDelta));
Walter Jangcab3dce2015-02-09 17:48:03 -0800667 }
668 }
669 }
670 }
671
Walter Jange720fde2015-02-17 10:54:14 -0800672 // TODO: avoid inflating extra views and deleting them
673 private void removeExtraEmptyTextFields(ViewGroup viewGroup) {
674 // If there is one (or less) editors, leave it whether it is empty or not
675 if (viewGroup.getChildCount() <= 1) {
Walter Jangb1c87622015-02-13 17:51:38 -0800676 return;
677 }
Walter Jange720fde2015-02-17 10:54:14 -0800678 // Determine if there are any non-empty editors
679 boolean hasAtLeastOneNonEmptyEditorView = false;
680 for (int i = 0; i < viewGroup.getChildCount(); i++) {
681 if (!isEmptyEditorView(viewGroup.getChildAt(i))) {
682 hasAtLeastOneNonEmptyEditorView = true;
Walter Jangb1c87622015-02-13 17:51:38 -0800683 break;
684 }
685 }
Walter Jange720fde2015-02-17 10:54:14 -0800686 if (hasAtLeastOneNonEmptyEditorView) {
687 // There is at least one non-empty editor, remove all the empty ones
688 for (int i = 0; i < viewGroup.getChildCount(); i++) {
689 if (isEmptyEditorView(viewGroup.getChildAt(i))) {
690 viewGroup.getChildAt(i).setVisibility(View.GONE);
Walter Jangb1c87622015-02-13 17:51:38 -0800691 }
692 }
693 } else {
Walter Jange720fde2015-02-17 10:54:14 -0800694 // There is no non-empty editor, keep the first empty view and remove the rest
695 for (int i = 1; i < viewGroup.getChildCount(); i++) {
696 viewGroup.getChildAt(i).setVisibility(View.GONE);
Walter Jangb1c87622015-02-13 17:51:38 -0800697 }
698 }
699 }
700
Walter Jange720fde2015-02-17 10:54:14 -0800701 private static boolean isEmptyEditorView(View view) {
702 if (view instanceof TextFieldsEditorView) {
703 final TextFieldsEditorView textFieldsEditorView = (TextFieldsEditorView) view;
704 return textFieldsEditorView.isEmpty();
705 }
706 if (view instanceof KindSectionView) {
707 final KindSectionView kindSectionView = (KindSectionView) view;
708 return kindSectionView.hasEmptyEditor();
709 }
710 return false;
711 }
712
Walter Jangcab3dce2015-02-09 17:48:03 -0800713 private static boolean hasNonEmptyValuesDelta(RawContactDelta rawContactDelta,
714 String mimeType, DataKind dataKind) {
715 return !getNonEmptyValuesDeltas(rawContactDelta, mimeType, dataKind).isEmpty();
716 }
717
Walter Jang151f3e62015-02-26 15:29:40 -0800718 private static ValuesDelta getNonEmptySuperPrimaryValuesDeltas(RawContactDelta rawContactDelta,
719 String mimeType, DataKind dataKind) {
720 for (ValuesDelta valuesDelta : getNonEmptyValuesDeltas(
721 rawContactDelta, mimeType, dataKind)) {
722 if (valuesDelta.isSuperPrimary()) {
723 return valuesDelta;
724 }
725 }
726 return null;
727 }
728
729 static List<ValuesDelta> getNonEmptyValuesDeltas(RawContactDelta rawContactDelta,
Walter Jangcab3dce2015-02-09 17:48:03 -0800730 String mimeType, DataKind dataKind) {
731 final List<ValuesDelta> result = new ArrayList<>();
732 if (rawContactDelta == null) {
Walter Jangbf63a6d2015-05-05 09:14:35 -0700733 vlog("Null RawContactDelta");
Walter Jangcab3dce2015-02-09 17:48:03 -0800734 return result;
735 }
736 if (!rawContactDelta.hasMimeEntries(mimeType)) {
Walter Jangbf63a6d2015-05-05 09:14:35 -0700737 vlog("No ValueDeltas");
Walter Jangcab3dce2015-02-09 17:48:03 -0800738 return result;
739 }
740 for (ValuesDelta valuesDelta : rawContactDelta.getMimeEntries(mimeType)) {
Walter Jang3a37a1a2015-03-04 07:41:32 -0800741 if (hasNonEmptyValue(dataKind, valuesDelta)) {
742 result.add(valuesDelta);
Walter Jangcab3dce2015-02-09 17:48:03 -0800743 }
744 }
745 return result;
746 }
747
Walter Jang3a37a1a2015-03-04 07:41:32 -0800748 private static boolean hasNonEmptyValue(DataKind dataKind, ValuesDelta valuesDelta) {
749 if (valuesDelta == null) {
Walter Jangbf63a6d2015-05-05 09:14:35 -0700750 vlog("Null valuesDelta");
Walter Jang3a37a1a2015-03-04 07:41:32 -0800751 return false;
752 }
753 for (EditField editField : dataKind.fieldList) {
754 final String column = editField.column;
755 final String value = valuesDelta == null ? null : valuesDelta.getAsString(column);
Walter Jangbf63a6d2015-05-05 09:14:35 -0700756 vlog("Field " + column + " empty=" + TextUtils.isEmpty(value) + " value=" + value);
Walter Jang3a37a1a2015-03-04 07:41:32 -0800757 if (!TextUtils.isEmpty(value)) {
758 return true;
759 }
760 }
761 return false;
762 }
763
Walter Jangcab3dce2015-02-09 17:48:03 -0800764 private StructuredNameEditorView inflateStructuredNameEditorView(ViewGroup viewGroup,
Walter Jang151f3e62015-02-26 15:29:40 -0800765 AccountType accountType, ValuesDelta valuesDelta, RawContactDelta rawContactDelta,
Walter Jang65d35202015-06-16 13:08:34 -0700766 NameEditorListener nameEditorListener, boolean readOnly) {
Walter Jangcab3dce2015-02-09 17:48:03 -0800767 final StructuredNameEditorView result = (StructuredNameEditorView) mLayoutInflater.inflate(
768 R.layout.structured_name_editor_view, viewGroup, /* attachToRoot =*/ false);
Walter Jang151f3e62015-02-26 15:29:40 -0800769 if (nameEditorListener != null) {
770 result.setEditorListener(nameEditorListener);
771 }
Walter Jang3a37a1a2015-03-04 07:41:32 -0800772 result.setDeletable(false);
Walter Jangcab3dce2015-02-09 17:48:03 -0800773 result.setValues(
774 accountType.getKindForMimetype(DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME),
775 valuesDelta,
776 rawContactDelta,
Walter Jang65d35202015-06-16 13:08:34 -0700777 readOnly,
Walter Jangcab3dce2015-02-09 17:48:03 -0800778 mViewIdGenerator);
779 return result;
780 }
781
782 private PhoneticNameEditorView inflatePhoneticNameEditorView(ViewGroup viewGroup,
783 AccountType accountType, ValuesDelta valuesDelta, RawContactDelta rawContactDelta) {
784 final PhoneticNameEditorView result = (PhoneticNameEditorView) mLayoutInflater.inflate(
785 R.layout.phonetic_name_editor_view, viewGroup, /* attachToRoot =*/ false);
Walter Jang3a37a1a2015-03-04 07:41:32 -0800786 result.setDeletable(false);
Walter Jangcab3dce2015-02-09 17:48:03 -0800787 result.setValues(
788 accountType.getKindForMimetype(DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME),
789 valuesDelta,
790 rawContactDelta,
791 /* readOnly =*/ false,
792 mViewIdGenerator);
793 return result;
794 }
795
Walter Jangcab3dce2015-02-09 17:48:03 -0800796 private KindSectionView inflateKindSectionView(ViewGroup viewGroup, DataKind dataKind,
Walter Jang88b0a8c2015-06-08 13:15:56 -0700797 RawContactDelta rawContactDelta) {
Walter Jangcab3dce2015-02-09 17:48:03 -0800798 final KindSectionView result = (KindSectionView) mLayoutInflater.inflate(
799 R.layout.item_kind_section, viewGroup, /* attachToRoot =*/ false);
800 result.setState(
801 dataKind,
802 rawContactDelta,
803 /* readOnly =*/ false,
Walter Jangcab3dce2015-02-09 17:48:03 -0800804 mViewIdGenerator);
805 return result;
806 }
807
Walter Jangbf63a6d2015-05-05 09:14:35 -0700808 private static void vlog(String message) {
809 if (Log.isLoggable(TAG, Log.VERBOSE)) {
810 Log.v(TAG, message);
Walter Jangcab3dce2015-02-09 17:48:03 -0800811 }
812 }
813}