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