• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 com.android.contacts.R;
20 import com.android.contacts.editor.Editor.EditorListener;
21 import com.android.contacts.model.DataKind;
22 import com.android.contacts.model.EntityDelta;
23 import com.android.contacts.model.EntityDelta.ValuesDelta;
24 import com.android.contacts.model.EntityModifier;
25 
26 import android.content.Context;
27 import android.text.TextUtils;
28 import android.util.AttributeSet;
29 import android.view.LayoutInflater;
30 import android.view.View;
31 import android.view.ViewGroup;
32 import android.widget.LinearLayout;
33 import android.widget.TextView;
34 
35 import java.util.ArrayList;
36 import java.util.List;
37 
38 /**
39  * Custom view for an entire section of data as segmented by
40  * {@link DataKind} around a {@link Data#MIMETYPE}. This view shows a
41  * section header and a trigger for adding new {@link Data} rows.
42  */
43 public class KindSectionView extends LinearLayout implements EditorListener {
44     private static final String TAG = "KindSectionView";
45 
46     private TextView mTitle;
47     private ViewGroup mEditors;
48     private View mAddFieldFooter;
49     private String mTitleString;
50 
51     private DataKind mKind;
52     private EntityDelta mState;
53     private boolean mReadOnly;
54 
55     private ViewIdGenerator mViewIdGenerator;
56 
57     private LayoutInflater mInflater;
58 
KindSectionView(Context context)59     public KindSectionView(Context context) {
60         this(context, null);
61     }
62 
KindSectionView(Context context, AttributeSet attrs)63     public KindSectionView(Context context, AttributeSet attrs) {
64         super(context, attrs);
65     }
66 
67     @Override
setEnabled(boolean enabled)68     public void setEnabled(boolean enabled) {
69         super.setEnabled(enabled);
70         if (mEditors != null) {
71             int childCount = mEditors.getChildCount();
72             for (int i = 0; i < childCount; i++) {
73                 mEditors.getChildAt(i).setEnabled(enabled);
74             }
75         }
76 
77         if (enabled && !mReadOnly) {
78             mAddFieldFooter.setVisibility(View.VISIBLE);
79         } else {
80             mAddFieldFooter.setVisibility(View.GONE);
81         }
82     }
83 
isReadOnly()84     public boolean isReadOnly() {
85         return mReadOnly;
86     }
87 
88     /** {@inheritDoc} */
89     @Override
onFinishInflate()90     protected void onFinishInflate() {
91         setDrawingCacheEnabled(true);
92         setAlwaysDrawnWithCacheEnabled(true);
93 
94         mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
95 
96         mTitle = (TextView) findViewById(R.id.kind_title);
97         mEditors = (ViewGroup) findViewById(R.id.kind_editors);
98         mAddFieldFooter = findViewById(R.id.add_field_footer);
99         mAddFieldFooter.setOnClickListener(new OnClickListener() {
100             @Override
101             public void onClick(View v) {
102                 // Setup click listener to add an empty field when the footer is clicked.
103                 mAddFieldFooter.setVisibility(View.GONE);
104                 addItem();
105             }
106         });
107     }
108 
109     @Override
onDeleteRequested(Editor editor)110     public void onDeleteRequested(Editor editor) {
111         // If there is only 1 editor in the section, then don't allow the user to delete it.
112         // Just clear the fields in the editor.
113         if (getEditorCount() == 1) {
114             editor.clearAllFields();
115         } else {
116             // Otherwise it's okay to delete this {@link Editor}
117             editor.deleteEditor();
118         }
119         updateAddFooterVisible();
120     }
121 
122     @Override
onRequest(int request)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             updateAddFooterVisible();
128         }
129     }
130 
setState(DataKind kind, EntityDelta state, boolean readOnly, ViewIdGenerator vig)131     public void setState(DataKind kind, EntityDelta state, boolean readOnly, ViewIdGenerator vig) {
132         mKind = kind;
133         mState = state;
134         mReadOnly = readOnly;
135         mViewIdGenerator = vig;
136 
137         setId(mViewIdGenerator.getId(state, kind, null, ViewIdGenerator.NO_VIEW_INDEX));
138 
139         // TODO: handle resources from remote packages
140         mTitleString = (kind.titleRes == -1 || kind.titleRes == 0)
141                 ? ""
142                 : getResources().getString(kind.titleRes);
143         mTitle.setText(mTitleString);
144 
145         rebuildFromState();
146         updateAddFooterVisible();
147         updateSectionVisible();
148     }
149 
getTitle()150     public String getTitle() {
151         return mTitleString;
152     }
153 
setTitleVisible(boolean visible)154     public void setTitleVisible(boolean visible) {
155         findViewById(R.id.kind_title_layout).setVisibility(visible ? View.VISIBLE : View.GONE);
156     }
157 
158     /**
159      * Build editors for all current {@link #mState} rows.
160      */
rebuildFromState()161     public void rebuildFromState() {
162         // Remove any existing editors
163         mEditors.removeAllViews();
164 
165         // Check if we are displaying anything here
166         boolean hasEntries = mState.hasMimeEntries(mKind.mimeType);
167 
168         if (hasEntries) {
169             for (ValuesDelta entry : mState.getMimeEntries(mKind.mimeType)) {
170                 // Skip entries that aren't visible
171                 if (!entry.isVisible()) continue;
172                 if (isEmptyNoop(entry)) continue;
173 
174                 createEditorView(entry);
175             }
176         }
177     }
178 
179 
180     /**
181      * Creates an EditorView for the given entry. This function must be used while constructing
182      * the views corresponding to the the object-model. The resulting EditorView is also added
183      * to the end of mEditors
184      */
createEditorView(ValuesDelta entry)185     private View createEditorView(ValuesDelta entry) {
186         final View view;
187         try {
188             view = mInflater.inflate(mKind.editorLayoutResourceId, mEditors, false);
189         } catch (Exception e) {
190             throw new RuntimeException(
191                     "Cannot allocate editor with layout resource ID " +
192                     mKind.editorLayoutResourceId + " for MIME type " + mKind.mimeType +
193                     " with error " + e.toString());
194         }
195 
196         view.setEnabled(isEnabled());
197 
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      */
isEmptyNoop(ValuesDelta item)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 
updateSectionVisible()222     private void updateSectionVisible() {
223         setVisibility(getEditorCount() != 0 ? VISIBLE : GONE);
224     }
225 
updateAddFooterVisible()226     protected void updateAddFooterVisible() {
227         if (!mReadOnly && (mKind.typeOverallMax != 1)) {
228             // First determine whether there are any existing empty editors.
229             updateEmptyEditors();
230             // If there are no existing empty editors and it's possible to add
231             // another field, then make the "add footer" field visible.
232             if (!hasEmptyEditor() && EntityModifier.canInsert(mState, mKind)) {
233                 mAddFieldFooter.setVisibility(View.VISIBLE);
234                 return;
235             }
236         }
237         mAddFieldFooter.setVisibility(View.GONE);
238     }
239 
240     /**
241      * Updates the editors being displayed to the user removing extra empty
242      * {@link Editor}s, so there is only max 1 empty {@link Editor} view at a time.
243      */
updateEmptyEditors()244     private void updateEmptyEditors() {
245         List<View> emptyEditors = getEmptyEditors();
246 
247         // If there is more than 1 empty editor, then remove it from the list of editors.
248         if (emptyEditors.size() > 1) {
249             for (View emptyEditorView : emptyEditors) {
250                 // If no child {@link View}s are being focused on within
251                 // this {@link View}, then remove this empty editor.
252                 if (emptyEditorView.findFocus() == null) {
253                     mEditors.removeView(emptyEditorView);
254                 }
255             }
256         }
257     }
258 
259     /**
260      * Returns a list of empty editor views in this section.
261      */
getEmptyEditors()262     private List<View> getEmptyEditors() {
263         List<View> emptyEditorViews = new ArrayList<View>();
264         for (int i = 0; i < mEditors.getChildCount(); i++) {
265             View view = mEditors.getChildAt(i);
266             if (((Editor) view).isEmpty()) {
267                 emptyEditorViews.add(view);
268             }
269         }
270         return emptyEditorViews;
271     }
272 
273     /**
274      * Returns true if one of the editors has all of its fields empty, or false
275      * otherwise.
276      */
hasEmptyEditor()277     private boolean hasEmptyEditor() {
278         return getEmptyEditors().size() > 0;
279     }
280 
281     /**
282      * Returns true if all editors are empty.
283      */
isEmpty()284     public boolean isEmpty() {
285         for (int i = 0; i < mEditors.getChildCount(); i++) {
286             View view = mEditors.getChildAt(i);
287             if (!((Editor) view).isEmpty()) {
288                 return false;
289             }
290         }
291         return true;
292     }
293 
addItem()294     public void addItem() {
295         ValuesDelta values = null;
296         // If this is a list, we can freely add. If not, only allow adding the first.
297         if (mKind.typeOverallMax == 1) {
298             if (getEditorCount() == 1) {
299                 return;
300             }
301 
302             // If we already have an item, just make it visible
303             ArrayList<ValuesDelta> entries = mState.getMimeEntries(mKind.mimeType);
304             if (entries != null && entries.size() > 0) {
305                 values = entries.get(0);
306             }
307         }
308 
309         // Insert a new child, create its view and set its focus
310         if (values == null) {
311             values = EntityModifier.insertChild(mState, mKind);
312         }
313 
314         final View newField = createEditorView(values);
315         post(new Runnable() {
316 
317             @Override
318             public void run() {
319                 newField.requestFocus();
320             }
321         });
322 
323         // Hide the "add field" footer because there is now a blank field.
324         mAddFieldFooter.setVisibility(View.GONE);
325 
326         // Ensure we are visible
327         updateSectionVisible();
328     }
329 
getEditorCount()330     public int getEditorCount() {
331         return mEditors.getChildCount();
332     }
333 
getKind()334     public DataKind getKind() {
335         return mKind;
336     }
337 }
338