1 /*
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 
17 package com.android.contacts.editor;
18 
19 import android.content.Context;
20 import android.database.Cursor;
21 import android.provider.ContactsContract.CommonDataKinds.Event;
22 import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
23 import android.provider.ContactsContract.CommonDataKinds.Nickname;
24 import android.provider.ContactsContract.CommonDataKinds.StructuredName;
25 import android.util.AttributeSet;
26 import android.view.LayoutInflater;
27 import android.view.View;
28 import android.view.ViewGroup;
29 import android.widget.ImageView;
30 import android.widget.LinearLayout;
31 import android.widget.TextView;
32 
33 import com.android.contacts.R;
34 import com.android.contacts.model.RawContactDelta;
35 import com.android.contacts.model.RawContactModifier;
36 import com.android.contacts.model.ValuesDelta;
37 import com.android.contacts.model.account.AccountType;
38 import com.android.contacts.model.dataitem.DataKind;
39 import com.android.contacts.preference.ContactsPreferences;
40 
41 import java.util.ArrayList;
42 import java.util.List;
43 
44 /**
45  * Custom view for an entire section of data as segmented by
46  * {@link DataKind} around a {@link Data#MIMETYPE}. This view shows a
47  * section header and a trigger for adding new {@link Data} rows.
48  */
49 public class KindSectionView extends LinearLayout {
50 
51     /** Callbacks for hosts of {@link KindSectionView}s. */
52     public interface Listener {
53 
54         /** Invoked when all fields in a legacy {@link KindSectionView} are removed. */
onEmptyLegacyKindSectionView()55         void onEmptyLegacyKindSectionView();
56     }
57 
58     /**
59      * Marks a name as super primary when it is changed.
60      *
61      * This is for the case when two or more raw contacts with names are joined where neither is
62      * marked as super primary.
63      */
64     private static final class StructuredNameEditorListener implements Editor.EditorListener {
65 
66         private final ValuesDelta mValuesDelta;
67         private final long mRawContactId;
68         private final RawContactEditorView.Listener mEditorViewListener;
69 
StructuredNameEditorListener(ValuesDelta valuesDelta, long rawContactId, RawContactEditorView.Listener editorViewListener)70         public StructuredNameEditorListener(ValuesDelta valuesDelta, long rawContactId,
71             RawContactEditorView.Listener editorViewListener) {
72             mValuesDelta = valuesDelta;
73             mRawContactId = rawContactId;
74             mEditorViewListener = editorViewListener;
75         }
76 
77         @Override
onRequest(int request)78         public void onRequest(int request) {
79             if (request == Editor.EditorListener.FIELD_CHANGED) {
80                 mValuesDelta.setSuperPrimary(true);
81                 if (mEditorViewListener != null) {
82                     mEditorViewListener.onNameFieldChanged(mRawContactId, mValuesDelta);
83                 }
84             } else if (request == Editor.EditorListener.FIELD_TURNED_EMPTY) {
85                 mValuesDelta.setSuperPrimary(false);
86             }
87         }
88 
89         @Override
onDeleteRequested(Editor editor)90         public void onDeleteRequested(Editor editor) {
91             editor.clearAllFields();
92         }
93     }
94 
95     /**
96      * Clears fields when deletes are requested (on phonetic and nickename fields);
97      * does not change the number of editors.
98      */
99     private static final class OtherNameKindEditorListener implements Editor.EditorListener {
100 
101         @Override
onRequest(int request)102         public void onRequest(int request) {
103         }
104 
105         @Override
onDeleteRequested(Editor editor)106         public void onDeleteRequested(Editor editor) {
107             editor.clearAllFields();
108         }
109     }
110 
111     /**
112      * Updates empty fields when fields are deleted or turns empty.
113      * Whether a new empty editor is added is controlled by {@link #setShowOneEmptyEditor} and
114      * {@link #setHideWhenEmpty}.
115      */
116     private class NonNameEditorListener implements Editor.EditorListener {
117 
118         @Override
onRequest(int request)119         public void onRequest(int request) {
120             // If a field has become empty or non-empty, then check if another row
121             // can be added dynamically.
122             if (request == FIELD_TURNED_EMPTY || request == FIELD_TURNED_NON_EMPTY) {
123                 updateEmptyEditors(/* shouldAnimate = */ true);
124             }
125         }
126 
127         @Override
onDeleteRequested(Editor editor)128         public void onDeleteRequested(Editor editor) {
129             if (mIsLegacyField && mEditors.getChildCount() == 1) {
130                 editor.deleteEditor();
131                 mListener.onEmptyLegacyKindSectionView();
132                 return;
133             }
134             if (mShowOneEmptyEditor && mEditors.getChildCount() == 1) {
135                 // If there is only 1 editor in the section, then don't allow the user to
136                 // delete it.  Just clear the fields in the editor.
137                 editor.clearAllFields();
138             } else {
139                 editor.deleteEditor();
140             }
141         }
142     }
143 
144     private class EventEditorListener extends NonNameEditorListener {
145 
146         @Override
onRequest(int request)147         public void onRequest(int request) {
148             super.onRequest(request);
149         }
150 
151         @Override
onDeleteRequested(Editor editor)152         public void onDeleteRequested(Editor editor) {
153             if (editor instanceof EventFieldEditorView){
154                 final EventFieldEditorView delView = (EventFieldEditorView) editor;
155                 if (delView.isBirthdayType() && mEditors.getChildCount() > 1) {
156                     final EventFieldEditorView bottomView = (EventFieldEditorView) mEditors
157                             .getChildAt(mEditors.getChildCount() - 1);
158                     bottomView.restoreBirthday();
159                 }
160             }
161             super.onDeleteRequested(editor);
162         }
163     }
164 
165     private KindSectionData mKindSectionData;
166     private ViewIdGenerator mViewIdGenerator;
167     private RawContactEditorView.Listener mEditorViewListener;
168     private Listener mListener;
169 
170     private boolean mIsUserProfile;
171     private boolean mShowOneEmptyEditor = false;
172     private boolean mHideIfEmpty = true;
173     private boolean mIsLegacyField = false;
174 
175     private LayoutInflater mLayoutInflater;
176     private ViewGroup mEditors;
177     private ImageView mIcon;
178 
KindSectionView(Context context)179     public KindSectionView(Context context) {
180         this(context, /* attrs =*/ null);
181     }
182 
KindSectionView(Context context, AttributeSet attrs)183     public KindSectionView(Context context, AttributeSet attrs) {
184         super(context, attrs);
185     }
186 
187     @Override
setEnabled(boolean enabled)188     public void setEnabled(boolean enabled) {
189         super.setEnabled(enabled);
190         if (mEditors != null) {
191             int childCount = mEditors.getChildCount();
192             for (int i = 0; i < childCount; i++) {
193                 mEditors.getChildAt(i).setEnabled(enabled);
194             }
195         }
196     }
197 
198     @Override
onFinishInflate()199     protected void onFinishInflate() {
200         super.onFinishInflate();
201         setDrawingCacheEnabled(true);
202         setAlwaysDrawnWithCacheEnabled(true);
203 
204         mLayoutInflater = (LayoutInflater) getContext().getSystemService(
205                 Context.LAYOUT_INFLATER_SERVICE);
206 
207         mEditors = (ViewGroup) findViewById(R.id.kind_editors);
208         mIcon = (ImageView) findViewById(R.id.kind_icon);
209     }
210 
setIsUserProfile(boolean isUserProfile)211     public void setIsUserProfile(boolean isUserProfile) {
212         mIsUserProfile = isUserProfile;
213     }
214 
215     /**
216      * @param showOneEmptyEditor If true, we will always show one empty editor, otherwise an empty
217      *         editor will not be shown until the user enters a value.  Note, this does not apply
218      *         to name editors since those are always displayed.
219      */
setShowOneEmptyEditor(boolean showOneEmptyEditor)220     public void setShowOneEmptyEditor(boolean showOneEmptyEditor) {
221         mShowOneEmptyEditor = showOneEmptyEditor;
222     }
223 
224     /**
225      * @param hideWhenEmpty If true, the entire section will be hidden if all inputs are empty,
226      *         otherwise one empty input will always be displayed.  Note, this does not apply
227      *         to name editors since those are always displayed.
228      */
setHideWhenEmpty(boolean hideWhenEmpty)229     public void setHideWhenEmpty(boolean hideWhenEmpty) {
230         mHideIfEmpty = hideWhenEmpty;
231     }
232 
233     /** Binds the given group data to every {@link GroupMembershipView}. */
setGroupMetaData(Cursor cursor)234     public void setGroupMetaData(Cursor cursor) {
235         for (int i = 0; i < mEditors.getChildCount(); i++) {
236             final View view = mEditors.getChildAt(i);
237             if (view instanceof GroupMembershipView) {
238                 ((GroupMembershipView) view).setGroupMetaData(cursor);
239             }
240         }
241     }
242 
243     /**
244      * When {@code isLegacyField} is true, prevent users from editing the field.
245      */
setLegacyField(boolean isLegacyField)246     void setLegacyField(boolean isLegacyField) {
247         this.mIsLegacyField = isLegacyField;
248     }
249 
250     /**
251      * Whether this is a name kind section view and all name fields (structured, phonetic,
252      * and nicknames) are empty.
253      */
isEmptyName()254     public boolean isEmptyName() {
255         if (!StructuredName.CONTENT_ITEM_TYPE.equals(mKindSectionData.getMimeType())) {
256             return false;
257         }
258         for (int i = 0; i < mEditors.getChildCount(); i++) {
259             final View view = mEditors.getChildAt(i);
260             if (view instanceof Editor) {
261                 final Editor editor = (Editor) view;
262                 if (!editor.isEmpty()) {
263                     return false;
264                 }
265             }
266         }
267         return true;
268     }
269 
getNameEditorView()270     public StructuredNameEditorView getNameEditorView() {
271         if (!StructuredName.CONTENT_ITEM_TYPE.equals(mKindSectionData.getMimeType())
272             || mEditors.getChildCount() == 0) {
273             return null;
274         }
275         return (StructuredNameEditorView) mEditors.getChildAt(0);
276     }
277 
getPhoneticEditorView()278     public TextFieldsEditorView getPhoneticEditorView() {
279         if (!StructuredName.CONTENT_ITEM_TYPE.equals(mKindSectionData.getMimeType())) {
280             return null;
281         }
282         for (int i = 0; i < mEditors.getChildCount(); i++) {
283             final View view = mEditors.getChildAt(i);
284             if (!(view instanceof StructuredNameEditorView)) {
285                 return (TextFieldsEditorView) view;
286             }
287         }
288         return null;
289     }
290 
291     /**
292      * Binds views for the given {@link KindSectionData}.
293      *
294      * We create a structured name and phonetic name editor for each {@link DataKind} with a
295      * {@link StructuredName#CONTENT_ITEM_TYPE} mime type.  The number and order of editors are
296      * rendered as they are given to {@link #setState}.
297      *
298      * Empty name editors are never added and at least one structured name editor is always
299      * displayed, even if it is empty.
300      */
setState( KindSectionData kindSectionData, ViewIdGenerator viewIdGenerator, RawContactEditorView.Listener editorViewListener, Listener listener)301     public void setState(
302         KindSectionData kindSectionData,
303         ViewIdGenerator viewIdGenerator,
304         RawContactEditorView.Listener editorViewListener,
305         Listener listener) {
306         mKindSectionData = kindSectionData;
307         mViewIdGenerator = viewIdGenerator;
308         mEditorViewListener = editorViewListener;
309         mListener = listener;
310 
311         // Set the icon using the DataKind
312         final DataKind dataKind = mKindSectionData.getDataKind();
313         if (dataKind != null) {
314             mIcon.setImageDrawable(EditorUiUtils.getMimeTypeDrawable(getContext(),
315                     dataKind.mimeType));
316             if (mIcon.getDrawable() != null) {
317                 mIcon.setContentDescription(dataKind.titleRes == -1 || dataKind.titleRes == 0
318                         ? "" : getResources().getString(dataKind.titleRes));
319             }
320             if (mIsLegacyField) {
321                 mIcon.setEnabled(false);
322             }
323         }
324 
325         rebuildFromState();
326 
327         updateEmptyEditors(/* shouldAnimate = */ false);
328     }
329 
rebuildFromState()330     private void rebuildFromState() {
331         mEditors.removeAllViews();
332 
333         final String mimeType = mKindSectionData.getMimeType();
334         if (StructuredName.CONTENT_ITEM_TYPE.equals(mimeType)) {
335             addNameEditorViews(mKindSectionData.getAccountType(),
336                     mKindSectionData.getRawContactDelta());
337         } else if (GroupMembership.CONTENT_ITEM_TYPE.equals(mimeType)) {
338             addGroupEditorView(mKindSectionData.getRawContactDelta(),
339                     mKindSectionData.getDataKind());
340         } else {
341             final Editor.EditorListener editorListener;
342             if (Nickname.CONTENT_ITEM_TYPE.equals(mimeType)) {
343                 editorListener = new OtherNameKindEditorListener();
344             } else if (Event.CONTENT_ITEM_TYPE.equals(mimeType)) {
345                 editorListener = new EventEditorListener();
346             } else {
347                 editorListener = new NonNameEditorListener();
348             }
349             final List<ValuesDelta> valuesDeltas = mKindSectionData.getVisibleValuesDeltas();
350             for (int i = 0; i < valuesDeltas.size(); i++ ) {
351                 addNonNameEditorView(mKindSectionData.getRawContactDelta(),
352                         mKindSectionData.getDataKind(), valuesDeltas.get(i), editorListener);
353             }
354         }
355     }
356 
addNameEditorViews(AccountType accountType, RawContactDelta rawContactDelta)357     private void addNameEditorViews(AccountType accountType, RawContactDelta rawContactDelta) {
358         final boolean readOnly = !accountType.areContactsWritable();
359         final ValuesDelta nameValuesDelta = rawContactDelta
360                 .getSuperPrimaryEntry(StructuredName.CONTENT_ITEM_TYPE);
361 
362         if (readOnly) {
363             final View nameView = mLayoutInflater.inflate(
364                     R.layout.structured_name_readonly_editor_view, mEditors,
365                     /* attachToRoot =*/ false);
366 
367             // Display name
368             ((TextView) nameView.findViewById(R.id.display_name))
369                     .setText(nameValuesDelta.getDisplayName());
370 
371             // Account type info
372             final LinearLayout accountTypeLayout = (LinearLayout)
373                     nameView.findViewById(R.id.account_type);
374             accountTypeLayout.setVisibility(View.VISIBLE);
375             ((ImageView) accountTypeLayout.findViewById(R.id.account_type_icon))
376                     .setImageDrawable(accountType.getDisplayIcon(getContext()));
377             ((TextView) accountTypeLayout.findViewById(R.id.account_type_name))
378                     .setText(accountType.getDisplayLabel(getContext()));
379 
380             mEditors.addView(nameView);
381             return;
382         }
383 
384         // Structured name
385         final StructuredNameEditorView nameView = (StructuredNameEditorView) mLayoutInflater
386                 .inflate(R.layout.structured_name_editor_view, mEditors, /* attachToRoot =*/ false);
387         if (!mIsUserProfile) {
388             // Don't set super primary for the me contact
389             nameView.setEditorListener(new StructuredNameEditorListener(
390                     nameValuesDelta, rawContactDelta.getRawContactId(), mEditorViewListener));
391         }
392         nameView.setDeletable(false);
393         nameView.setValues(accountType.getKindForMimetype(DataKind.PSEUDO_MIME_TYPE_NAME),
394                 nameValuesDelta, rawContactDelta, /* readOnly =*/ false, mViewIdGenerator);
395 
396         // Correct start margin since there is a second icon in the structured name layout
397         nameView.findViewById(R.id.kind_icon).setVisibility(View.GONE);
398         mEditors.addView(nameView);
399 
400         // Phonetic name
401         final DataKind phoneticNameKind = accountType
402                 .getKindForMimetype(DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME);
403         // The account type doesn't support phonetic name.
404         if (phoneticNameKind == null) return;
405 
406         final TextFieldsEditorView phoneticNameView = (TextFieldsEditorView) mLayoutInflater
407                 .inflate(R.layout.text_fields_editor_view, mEditors, /* attachToRoot =*/ false);
408         phoneticNameView.setEditorListener(new OtherNameKindEditorListener());
409         phoneticNameView.setDeletable(false);
410         phoneticNameView.setValues(
411                 accountType.getKindForMimetype(DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME),
412                 nameValuesDelta, rawContactDelta, /* readOnly =*/ false, mViewIdGenerator);
413 
414         // Fix the start margin for phonetic name views
415         final LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
416                 LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
417         layoutParams.setMargins(0, 0, 0, 0);
418         phoneticNameView.setLayoutParams(layoutParams);
419         mEditors.addView(phoneticNameView);
420         // Display of phonetic name fields is controlled from settings preferences.
421         mHideIfEmpty = new ContactsPreferences(getContext()).shouldHidePhoneticNamesIfEmpty();
422     }
423 
addGroupEditorView(RawContactDelta rawContactDelta, DataKind dataKind)424     private void addGroupEditorView(RawContactDelta rawContactDelta, DataKind dataKind) {
425         final GroupMembershipView view = (GroupMembershipView) mLayoutInflater.inflate(
426                 R.layout.item_group_membership, mEditors, /* attachToRoot =*/ false);
427         view.setKind(dataKind);
428         view.setEnabled(isEnabled());
429         view.setState(rawContactDelta);
430 
431         // Correct start margin since there is a second icon in the group layout
432         view.findViewById(R.id.kind_icon).setVisibility(View.GONE);
433 
434         mEditors.addView(view);
435     }
436 
addNonNameEditorView(RawContactDelta rawContactDelta, DataKind dataKind, ValuesDelta valuesDelta, Editor.EditorListener editorListener)437     private View addNonNameEditorView(RawContactDelta rawContactDelta, DataKind dataKind,
438             ValuesDelta valuesDelta, Editor.EditorListener editorListener) {
439         // Inflate the layout
440         final View view = mLayoutInflater.inflate(
441                 EditorUiUtils.getLayoutResourceId(dataKind.mimeType), mEditors, false);
442         view.setEnabled(isEnabled());
443         if (view instanceof Editor) {
444             final Editor editor = (Editor) view;
445             editor.setLegacyField(mIsLegacyField);
446             editor.setDeletable(true);
447             editor.setEditorListener(editorListener);
448             editor.setValues(dataKind, valuesDelta, rawContactDelta, !dataKind.editable,
449                     mViewIdGenerator);
450         }
451         mEditors.addView(view);
452 
453         return view;
454     }
455 
456     /**
457      * Updates the editors being displayed to the user removing extra empty
458      * {@link Editor}s, so there is only max 1 empty {@link Editor} view at a time.
459      * If there is only 1 empty editor and {@link #setHideWhenEmpty} was set to true,
460      * then the entire section is hidden.
461      */
updateEmptyEditors(boolean shouldAnimate)462     public void updateEmptyEditors(boolean shouldAnimate) {
463         final boolean isNameKindSection = StructuredName.CONTENT_ITEM_TYPE.equals(
464                 mKindSectionData.getMimeType());
465         final boolean isGroupKindSection = GroupMembership.CONTENT_ITEM_TYPE.equals(
466                 mKindSectionData.getMimeType());
467 
468         if (isNameKindSection) {
469             // The name kind section is always visible
470             setVisibility(VISIBLE);
471             updateEmptyNameEditors(shouldAnimate);
472         } else if (isGroupKindSection) {
473             // Check whether metadata has been bound for all group views
474             for (int i = 0; i < mEditors.getChildCount(); i++) {
475                 final View view = mEditors.getChildAt(i);
476                 if (view instanceof GroupMembershipView) {
477                     final GroupMembershipView groupView = (GroupMembershipView) view;
478                     if (!groupView.wasGroupMetaDataBound() || !groupView.accountHasGroups()) {
479                         setVisibility(GONE);
480                         return;
481                     }
482                 }
483             }
484             // Check that the user has selected to display all fields
485             if (mHideIfEmpty) {
486                 setVisibility(GONE);
487                 return;
488             }
489             setVisibility(VISIBLE);
490 
491             // We don't check the emptiness of the group views
492         } else {
493             // Determine if the entire kind section should be visible
494             final int editorCount = mEditors.getChildCount();
495             final List<View> emptyEditors = getEmptyEditors();
496             if (editorCount == emptyEditors.size() && mHideIfEmpty) {
497                 setVisibility(GONE);
498                 return;
499             }
500             setVisibility(VISIBLE);
501 
502             updateEmptyNonNameEditors(shouldAnimate);
503         }
504     }
505 
updateEmptyNameEditors(boolean shouldAnimate)506     private void updateEmptyNameEditors(boolean shouldAnimate) {
507         boolean isEmptyNameEditorVisible = false;
508 
509         for (int i = 0; i < mEditors.getChildCount(); i++) {
510             final View view = mEditors.getChildAt(i);
511             if (view instanceof Editor) {
512                 final Editor editor = (Editor) view;
513                 if (view instanceof StructuredNameEditorView) {
514                     // We always show one empty structured name view
515                     if (editor.isEmpty()) {
516                         if (isEmptyNameEditorVisible) {
517                             // If we're already showing an empty editor then hide any other empties
518                             if (mHideIfEmpty) {
519                                 view.setVisibility(View.GONE);
520                             }
521                         } else {
522                             isEmptyNameEditorVisible = true;
523                         }
524                     } else {
525                         showView(view, shouldAnimate);
526                         isEmptyNameEditorVisible = true;
527                     }
528                 } else {
529                     // Since we can't add phonetic names and nicknames, just show or hide them
530                     if (mHideIfEmpty && editor.isEmpty()) {
531                         hideView(view);
532                     } else {
533                         showView(view, /* shouldAnimate =*/ false); // Animation here causes jank
534                     }
535                 }
536             } else {
537                 // For read only names, only show them if we're not hiding empty views
538                 if (mHideIfEmpty) {
539                     hideView(view);
540                 } else {
541                     showView(view, shouldAnimate);
542                 }
543             }
544         }
545     }
546 
updateEmptyNonNameEditors(boolean shouldAnimate)547     private void updateEmptyNonNameEditors(boolean shouldAnimate) {
548         // Prune excess empty editors
549         final List<View> emptyEditors = getEmptyEditors();
550         if (emptyEditors.size() > 1) {
551             // If there is more than 1 empty editor, then remove it from the list of editors.
552             int deleted = 0;
553             for (int i = 0; i < emptyEditors.size(); i++) {
554                 final View view = emptyEditors.get(i);
555                 // If no child {@link View}s are being focused on within this {@link View}, then
556                 // remove this empty editor. We can assume that at least one empty editor has
557                 // focus. One way to get two empty editors is by deleting characters from a
558                 // non-empty editor, in which case this editor has focus.  Another way is if
559                 // there is more values delta so we must also count number of editors deleted.
560                 if (view.findFocus() == null) {
561                     deleteView(view, shouldAnimate);
562                     deleted++;
563                     if (deleted == emptyEditors.size() - 1) break;
564                 }
565             }
566             return;
567         }
568         // Determine if we should add a new empty editor
569         final DataKind dataKind = mKindSectionData.getDataKind();
570         final RawContactDelta rawContactDelta = mKindSectionData.getRawContactDelta();
571         if (dataKind == null // There is nothing we can do.
572                 // We have already reached the maximum number of editors, don't add any more.
573                 || !RawContactModifier.canInsert(rawContactDelta, dataKind)
574                 // We have already reached the maximum number of empty editors, don't add any more.
575                 || emptyEditors.size() == 1) {
576             return;
577         }
578         // Add a new empty editor
579         if (mShowOneEmptyEditor) {
580             final String mimeType = mKindSectionData.getMimeType();
581             if (Nickname.CONTENT_ITEM_TYPE.equals(mimeType) && mEditors.getChildCount() > 0) {
582                 return;
583             }
584             final ValuesDelta values = RawContactModifier.insertChild(rawContactDelta, dataKind);
585             final Editor.EditorListener editorListener = Event.CONTENT_ITEM_TYPE.equals(mimeType)
586                     ? new EventEditorListener() : new NonNameEditorListener();
587             final View view = addNonNameEditorView(rawContactDelta, dataKind, values,
588                     editorListener);
589             showView(view, shouldAnimate);
590         }
591     }
592 
hideView(View view)593     private void hideView(View view) {
594         view.setVisibility(View.GONE);
595     }
596 
deleteView(View view, boolean shouldAnimate)597     private void deleteView(View view, boolean shouldAnimate) {
598         if (shouldAnimate) {
599             final Editor editor = (Editor) view;
600             editor.deleteEditor();
601         } else {
602             mEditors.removeView(view);
603         }
604     }
605 
showView(View view, boolean shouldAnimate)606     private void showView(View view, boolean shouldAnimate) {
607         if (shouldAnimate) {
608             view.setVisibility(View.GONE);
609             EditorAnimator.getInstance().showFieldFooter(view);
610         } else {
611             view.setVisibility(View.VISIBLE);
612         }
613     }
614 
getEmptyEditors()615     private List<View> getEmptyEditors() {
616         final List<View> emptyEditors = new ArrayList<>();
617         for (int i = 0; i < mEditors.getChildCount(); i++) {
618             final View view = mEditors.getChildAt(i);
619             if (view instanceof Editor && ((Editor) view).isEmpty()) {
620                 emptyEditors.add(view);
621             }
622         }
623         return emptyEditors;
624     }
625 
isEditorEmpty()626     public boolean isEditorEmpty() {
627         return mKindSectionData.getVisibleValuesDeltas().isEmpty();
628     }
629 }
630