Home | History | Annotate | Download | only in editor
      1 /*
      2  * Copyright (C) 2009 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.provider.ContactsContract.Data;
     21 import android.text.TextUtils;
     22 import android.util.AttributeSet;
     23 import android.view.LayoutInflater;
     24 import android.view.View;
     25 import android.view.ViewGroup;
     26 import android.widget.ImageView;
     27 import android.widget.LinearLayout;
     28 
     29 import com.android.contacts.R;
     30 import com.android.contacts.common.model.RawContactDelta;
     31 import com.android.contacts.common.model.RawContactModifier;
     32 import com.android.contacts.common.model.ValuesDelta;
     33 import com.android.contacts.common.model.dataitem.DataKind;
     34 import com.android.contacts.editor.Editor.EditorListener;
     35 
     36 import java.util.ArrayList;
     37 import java.util.List;
     38 
     39 /**
     40  * Custom view for an entire section of data as segmented by
     41  * {@link DataKind} around a {@link Data#MIMETYPE}. This view shows a
     42  * section header and a trigger for adding new {@link Data} rows.
     43  */
     44 public class KindSectionView extends LinearLayout implements EditorListener {
     45 
     46     public interface Listener {
     47 
     48         /**
     49          * Invoked when any editor that is displayed in this section view is deleted by the user.
     50          */
     51         public void onDeleteRequested(Editor editor);
     52     }
     53 
     54     private ViewGroup mEditors;
     55     private ImageView mIcon;
     56 
     57     private DataKind mKind;
     58     private RawContactDelta mState;
     59     private boolean mReadOnly;
     60 
     61     private ViewIdGenerator mViewIdGenerator;
     62 
     63     private LayoutInflater mInflater;
     64 
     65     private Listener mListener;
     66 
     67     public KindSectionView(Context context) {
     68         this(context, null);
     69     }
     70 
     71     public KindSectionView(Context context, AttributeSet attrs) {
     72         super(context, attrs);
     73     }
     74 
     75     @Override
     76     public void setEnabled(boolean enabled) {
     77         super.setEnabled(enabled);
     78         if (mEditors != null) {
     79             int childCount = mEditors.getChildCount();
     80             for (int i = 0; i < childCount; i++) {
     81                 mEditors.getChildAt(i).setEnabled(enabled);
     82             }
     83         }
     84 
     85         updateEmptyEditors(/* shouldAnimate = */ true);
     86     }
     87 
     88     public boolean isReadOnly() {
     89         return mReadOnly;
     90     }
     91 
     92     /** {@inheritDoc} */
     93     @Override
     94     protected void onFinishInflate() {
     95         setDrawingCacheEnabled(true);
     96         setAlwaysDrawnWithCacheEnabled(true);
     97 
     98         mInflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
     99 
    100         mEditors = (ViewGroup) findViewById(R.id.kind_editors);
    101         mIcon = (ImageView) findViewById(R.id.kind_icon);
    102     }
    103 
    104     @Override
    105     public void onDeleteRequested(Editor editor) {
    106         if (getEditorCount() == 1) {
    107             // If there is only 1 editor in the section, then don't allow the user to delete it.
    108             // Just clear the fields in the editor.
    109             editor.clearAllFields();
    110         } else {
    111             // If there is a listener, let it decide whether to delete the Editor or the entire
    112             // KindSectionView so that there is no jank from both animations happening in succession.
    113             if (mListener != null) {
    114                 editor.markDeleted();
    115                 mListener.onDeleteRequested(editor);
    116             } else {
    117                 editor.deleteEditor();
    118             }
    119         }
    120     }
    121 
    122     @Override
    123     public void onRequest(int request) {
    124         // If a field has become empty or non-empty, then check if another row
    125         // can be added dynamically.
    126         if (request == FIELD_TURNED_EMPTY || request == FIELD_TURNED_NON_EMPTY) {
    127             updateEmptyEditors(/* shouldAnimate = */ true);
    128         }
    129     }
    130 
    131     public void setListener(Listener listener) {
    132         mListener = listener;
    133     }
    134 
    135     public void setState(DataKind kind, RawContactDelta state, boolean readOnly,
    136             ViewIdGenerator vig) {
    137         mKind = kind;
    138         mState = state;
    139         mReadOnly = readOnly;
    140         mViewIdGenerator = vig;
    141 
    142         setId(mViewIdGenerator.getId(state, kind, null, ViewIdGenerator.NO_VIEW_INDEX));
    143 
    144         // TODO: handle resources from remote packages
    145         final String titleString = (kind.titleRes == -1 || kind.titleRes == 0)
    146                 ? ""
    147                 : getResources().getString(kind.titleRes);
    148         mIcon.setContentDescription(titleString);
    149 
    150         mIcon.setImageDrawable(EditorUiUtils.getMimeTypeDrawable(getContext(), kind.mimeType));
    151         if (mIcon.getDrawable() == null) {
    152             mIcon.setContentDescription(null);
    153         }
    154 
    155         rebuildFromState();
    156         updateEmptyEditors(/* shouldAnimate = */ false);
    157     }
    158 
    159     /**
    160      * Build editors for all current {@link #mState} rows.
    161      */
    162     private void rebuildFromState() {
    163         // Remove any existing editors
    164         mEditors.removeAllViews();
    165 
    166         // Check if we are displaying anything here
    167         boolean hasEntries = mState.hasMimeEntries(mKind.mimeType);
    168 
    169         if (hasEntries) {
    170             for (ValuesDelta entry : mState.getMimeEntries(mKind.mimeType)) {
    171                 // Skip entries that aren't visible
    172                 if (!entry.isVisible()) continue;
    173                 if (isEmptyNoop(entry)) continue;
    174 
    175                 createEditorView(entry);
    176             }
    177         }
    178     }
    179 
    180 
    181     /**
    182      * Creates an EditorView for the given entry. This function must be used while constructing
    183      * the views corresponding to the the object-model. The resulting EditorView is also added
    184      * to the end of mEditors
    185      */
    186     private View createEditorView(ValuesDelta entry) {
    187         final View view;
    188         final int layoutResId = EditorUiUtils.getLayoutResourceId(mKind.mimeType);
    189         try {
    190             view = mInflater.inflate(layoutResId, mEditors, false);
    191         } catch (Exception e) {
    192             throw new RuntimeException(
    193                     "Cannot allocate editor with layout resource ID " +
    194                     layoutResId + " for MIME type " + mKind.mimeType +
    195                     " with error " + e.toString());
    196         }
    197         view.setEnabled(isEnabled());
    198         if (view instanceof Editor) {
    199             Editor editor = (Editor) view;
    200             editor.setDeletable(true);
    201             editor.setValues(mKind, entry, mState, mReadOnly, mViewIdGenerator);
    202             editor.setEditorListener(this);
    203         }
    204         mEditors.addView(view);
    205         return view;
    206     }
    207 
    208     /**
    209      * Tests whether the given item has no changes (so it exists in the database) but is empty
    210      */
    211     private boolean isEmptyNoop(ValuesDelta item) {
    212         if (!item.isNoop()) return false;
    213         final int fieldCount = mKind.fieldList.size();
    214         for (int i = 0; i < fieldCount; i++) {
    215             final String column = mKind.fieldList.get(i).column;
    216             final String value = item.getAsString(column);
    217             if (!TextUtils.isEmpty(value)) return false;
    218         }
    219         return true;
    220     }
    221 
    222     /**
    223      * Updates the editors being displayed to the user removing extra empty
    224      * {@link Editor}s, so there is only max 1 empty {@link Editor} view at a time.
    225      */
    226     public void updateEmptyEditors(boolean shouldAnimate) {
    227 
    228         final List<View> emptyEditors = getEmptyEditors();
    229 
    230         // If there is more than 1 empty editor, then remove it from the list of editors.
    231         if (emptyEditors.size() > 1) {
    232             for (final View emptyEditorView : emptyEditors) {
    233                 // If no child {@link View}s are being focused on within this {@link View}, then
    234                 // remove this empty editor. We can assume that at least one empty editor has focus.
    235                 // The only way to get two empty editors is by deleting characters from a non-empty
    236                 // editor, in which case this editor has focus.
    237                 if (emptyEditorView.findFocus() == null) {
    238                     final Editor editor = (Editor) emptyEditorView;
    239                     if (shouldAnimate) {
    240                         editor.deleteEditor();
    241                     } else {
    242                         mEditors.removeView(emptyEditorView);
    243                     }
    244                 }
    245             }
    246         } else if (mKind == null) {
    247             // There is nothing we can do.
    248             return;
    249         } else if (isReadOnly()) {
    250             // We don't show empty editors for read only data kinds.
    251             return;
    252         } else if (!RawContactModifier.canInsert(mState, mKind)) {
    253             // We have already reached the maximum number of editors. Lets not add any more.
    254             return;
    255         } else if (emptyEditors.size() == 1) {
    256             // We have already reached the maximum number of empty editors. Lets not add any more.
    257             return;
    258         } else {
    259             final ValuesDelta values = RawContactModifier.insertChild(mState, mKind);
    260             final View newField = createEditorView(values);
    261             if (shouldAnimate) {
    262                 newField.setVisibility(View.GONE);
    263                 EditorAnimator.getInstance().showFieldFooter(newField);
    264             }
    265         }
    266     }
    267 
    268     /**
    269      * Returns a list of empty editor views in this section.
    270      */
    271     private List<View> getEmptyEditors() {
    272         List<View> emptyEditorViews = new ArrayList<View>();
    273         for (int i = 0; i < mEditors.getChildCount(); i++) {
    274             View view = mEditors.getChildAt(i);
    275             if (((Editor) view).isEmpty()) {
    276                 emptyEditorViews.add(view);
    277             }
    278         }
    279         return emptyEditorViews;
    280     }
    281 
    282     public int getEditorCount() {
    283         return mEditors.getChildCount();
    284     }
    285 
    286     public DataKind getKind() {
    287         return mKind;
    288     }
    289 }
    290