• 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 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.LinearLayout;
27 import android.widget.TextView;
28 
29 import com.android.contacts.R;
30 import com.android.contacts.editor.Editor.EditorListener;
31 import com.android.contacts.common.model.RawContactModifier;
32 import com.android.contacts.common.model.RawContactDelta;
33 import com.android.contacts.common.model.ValuesDelta;
34 import com.android.contacts.common.model.dataitem.DataKind;
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     private static final String TAG = "KindSectionView";
46 
47     private TextView mTitle;
48     private ViewGroup mEditors;
49     private View mAddFieldFooter;
50     private String mTitleString;
51 
52     private DataKind mKind;
53     private RawContactDelta mState;
54     private boolean mReadOnly;
55 
56     private ViewIdGenerator mViewIdGenerator;
57 
58     private LayoutInflater mInflater;
59 
60     private final ArrayList<Runnable> mRunWhenWindowFocused = new ArrayList<Runnable>(1);
61 
KindSectionView(Context context)62     public KindSectionView(Context context) {
63         this(context, null);
64     }
65 
KindSectionView(Context context, AttributeSet attrs)66     public KindSectionView(Context context, AttributeSet attrs) {
67         super(context, attrs);
68     }
69 
70     @Override
setEnabled(boolean enabled)71     public void setEnabled(boolean enabled) {
72         super.setEnabled(enabled);
73         if (mEditors != null) {
74             int childCount = mEditors.getChildCount();
75             for (int i = 0; i < childCount; i++) {
76                 mEditors.getChildAt(i).setEnabled(enabled);
77             }
78         }
79 
80         if (enabled && !mReadOnly) {
81             mAddFieldFooter.setVisibility(View.VISIBLE);
82         } else {
83             mAddFieldFooter.setVisibility(View.GONE);
84         }
85     }
86 
isReadOnly()87     public boolean isReadOnly() {
88         return mReadOnly;
89     }
90 
91     /** {@inheritDoc} */
92     @Override
onFinishInflate()93     protected void onFinishInflate() {
94         setDrawingCacheEnabled(true);
95         setAlwaysDrawnWithCacheEnabled(true);
96 
97         mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
98 
99         mTitle = (TextView) findViewById(R.id.kind_title);
100         mEditors = (ViewGroup) findViewById(R.id.kind_editors);
101         mAddFieldFooter = findViewById(R.id.add_field_footer);
102         mAddFieldFooter.setOnClickListener(new OnClickListener() {
103             @Override
104             public void onClick(View v) {
105                 // Setup click listener to add an empty field when the footer is clicked.
106                 mAddFieldFooter.setVisibility(View.GONE);
107                 addItem();
108             }
109         });
110     }
111 
112     @Override
onDeleteRequested(Editor editor)113     public void onDeleteRequested(Editor editor) {
114         // If there is only 1 editor in the section, then don't allow the user to delete it.
115         // Just clear the fields in the editor.
116         if (getEditorCount() == 1) {
117             editor.clearAllFields();
118         } else {
119             // Otherwise it's okay to delete this {@link Editor}
120             editor.deleteEditor();
121         }
122     }
123 
124     @Override
onRequest(int request)125     public void onRequest(int request) {
126         // If a field has become empty or non-empty, then check if another row
127         // can be added dynamically.
128         if (request == FIELD_TURNED_EMPTY || request == FIELD_TURNED_NON_EMPTY) {
129             updateAddFooterVisible(true);
130         }
131     }
132 
setState(DataKind kind, RawContactDelta state, boolean readOnly, ViewIdGenerator vig)133     public void setState(DataKind kind, RawContactDelta state, boolean readOnly, ViewIdGenerator vig) {
134         mKind = kind;
135         mState = state;
136         mReadOnly = readOnly;
137         mViewIdGenerator = vig;
138 
139         setId(mViewIdGenerator.getId(state, kind, null, ViewIdGenerator.NO_VIEW_INDEX));
140 
141         // TODO: handle resources from remote packages
142         mTitleString = (kind.titleRes == -1 || kind.titleRes == 0)
143                 ? ""
144                 : getResources().getString(kind.titleRes);
145         mTitle.setText(mTitleString);
146 
147         rebuildFromState();
148         updateAddFooterVisible(false);
149         updateSectionVisible();
150     }
151 
getTitle()152     public String getTitle() {
153         return mTitleString;
154     }
155 
setTitleVisible(boolean visible)156     public void setTitleVisible(boolean visible) {
157         findViewById(R.id.kind_title_layout).setVisibility(visible ? View.VISIBLE : View.GONE);
158     }
159 
160     /**
161      * Build editors for all current {@link #mState} rows.
162      */
rebuildFromState()163     public void rebuildFromState() {
164         // Remove any existing editors
165         mEditors.removeAllViews();
166 
167         // Check if we are displaying anything here
168         boolean hasEntries = mState.hasMimeEntries(mKind.mimeType);
169 
170         if (hasEntries) {
171             for (ValuesDelta entry : mState.getMimeEntries(mKind.mimeType)) {
172                 // Skip entries that aren't visible
173                 if (!entry.isVisible()) continue;
174                 if (isEmptyNoop(entry)) continue;
175 
176                 createEditorView(entry);
177             }
178         }
179     }
180 
181 
182     /**
183      * Creates an EditorView for the given entry. This function must be used while constructing
184      * the views corresponding to the the object-model. The resulting EditorView is also added
185      * to the end of mEditors
186      */
createEditorView(ValuesDelta entry)187     private View createEditorView(ValuesDelta entry) {
188         final View view;
189         final int layoutResId = EditorUiUtils.getLayoutResourceId(mKind.mimeType);
190         try {
191             view = mInflater.inflate(layoutResId, mEditors, false);
192         } catch (Exception e) {
193             throw new RuntimeException(
194                     "Cannot allocate editor with layout resource ID " +
195                     layoutResId + " for MIME type " + mKind.mimeType +
196                     " with error " + e.toString());
197         }
198 
199         view.setEnabled(isEnabled());
200 
201         if (view instanceof Editor) {
202             Editor editor = (Editor) view;
203             editor.setDeletable(true);
204             editor.setValues(mKind, entry, mState, mReadOnly, mViewIdGenerator);
205             editor.setEditorListener(this);
206         }
207         mEditors.addView(view);
208         return view;
209     }
210 
211     /**
212      * Tests whether the given item has no changes (so it exists in the database) but is empty
213      */
isEmptyNoop(ValuesDelta item)214     private boolean isEmptyNoop(ValuesDelta item) {
215         if (!item.isNoop()) return false;
216         final int fieldCount = mKind.fieldList.size();
217         for (int i = 0; i < fieldCount; i++) {
218             final String column = mKind.fieldList.get(i).column;
219             final String value = item.getAsString(column);
220             if (!TextUtils.isEmpty(value)) return false;
221         }
222         return true;
223     }
224 
updateSectionVisible()225     private void updateSectionVisible() {
226         setVisibility(getEditorCount() != 0 ? VISIBLE : GONE);
227     }
228 
updateAddFooterVisible(boolean animate)229     protected void updateAddFooterVisible(boolean animate) {
230         if (!mReadOnly && (mKind.typeOverallMax != 1)) {
231             // First determine whether there are any existing empty editors.
232             updateEmptyEditors();
233             // If there are no existing empty editors and it's possible to add
234             // another field, then make the "add footer" field visible.
235             if (!hasEmptyEditor() && RawContactModifier.canInsert(mState, mKind)) {
236                 if (animate) {
237                     EditorAnimator.getInstance().showAddFieldFooter(mAddFieldFooter);
238                 } else {
239                     mAddFieldFooter.setVisibility(View.VISIBLE);
240                 }
241                 return;
242             }
243         }
244         if (animate) {
245             EditorAnimator.getInstance().hideAddFieldFooter(mAddFieldFooter);
246         } else {
247             mAddFieldFooter.setVisibility(View.GONE);
248         }
249     }
250 
251     /**
252      * Updates the editors being displayed to the user removing extra empty
253      * {@link Editor}s, so there is only max 1 empty {@link Editor} view at a time.
254      */
updateEmptyEditors()255     private void updateEmptyEditors() {
256         List<View> emptyEditors = getEmptyEditors();
257 
258         // If there is more than 1 empty editor, then remove it from the list of editors.
259         if (emptyEditors.size() > 1) {
260             for (View emptyEditorView : emptyEditors) {
261                 // If no child {@link View}s are being focused on within
262                 // this {@link View}, then remove this empty editor.
263                 if (emptyEditorView.findFocus() == null) {
264                     mEditors.removeView(emptyEditorView);
265                 }
266             }
267         }
268     }
269 
270     /**
271      * Returns a list of empty editor views in this section.
272      */
getEmptyEditors()273     private List<View> getEmptyEditors() {
274         List<View> emptyEditorViews = new ArrayList<View>();
275         for (int i = 0; i < mEditors.getChildCount(); i++) {
276             View view = mEditors.getChildAt(i);
277             if (((Editor) view).isEmpty()) {
278                 emptyEditorViews.add(view);
279             }
280         }
281         return emptyEditorViews;
282     }
283 
284     /**
285      * Returns true if one of the editors has all of its fields empty, or false
286      * otherwise.
287      */
hasEmptyEditor()288     private boolean hasEmptyEditor() {
289         return getEmptyEditors().size() > 0;
290     }
291 
292     /**
293      * Returns true if all editors are empty.
294      */
isEmpty()295     public boolean isEmpty() {
296         for (int i = 0; i < mEditors.getChildCount(); i++) {
297             View view = mEditors.getChildAt(i);
298             if (!((Editor) view).isEmpty()) {
299                 return false;
300             }
301         }
302         return true;
303     }
304 
305     /**
306      * Extends superclass implementation to also run tasks
307      * enqueued by {@link #runWhenWindowFocused}.
308      */
309     @Override
onWindowFocusChanged(boolean hasWindowFocus)310     public void onWindowFocusChanged(boolean hasWindowFocus) {
311         super.onWindowFocusChanged(hasWindowFocus);
312         if (hasWindowFocus) {
313             for (Runnable r: mRunWhenWindowFocused) {
314                 r.run();
315             }
316             mRunWhenWindowFocused.clear();
317         }
318     }
319 
320     /**
321      * Depending on whether we are in the currently-focused window, either run
322      * the argument immediately, or stash it until our window becomes focused.
323      */
runWhenWindowFocused(Runnable r)324     private void runWhenWindowFocused(Runnable r) {
325         if (hasWindowFocus()) {
326             r.run();
327         } else {
328             mRunWhenWindowFocused.add(r);
329         }
330     }
331 
332     /**
333      * Simple wrapper around {@link #runWhenWindowFocused}
334      * to ensure that it runs in the UI thread.
335      */
postWhenWindowFocused(final Runnable r)336     private void postWhenWindowFocused(final Runnable r) {
337         post(new Runnable() {
338             @Override
339             public void run() {
340                 runWhenWindowFocused(r);
341             }
342         });
343     }
344 
addItem()345     public void addItem() {
346         ValuesDelta values = null;
347         // If this is a list, we can freely add. If not, only allow adding the first.
348         if (mKind.typeOverallMax == 1) {
349             if (getEditorCount() == 1) {
350                 return;
351             }
352 
353             // If we already have an item, just make it visible
354             ArrayList<ValuesDelta> entries = mState.getMimeEntries(mKind.mimeType);
355             if (entries != null && entries.size() > 0) {
356                 values = entries.get(0);
357             }
358         }
359 
360         // Insert a new child, create its view and set its focus
361         if (values == null) {
362             values = RawContactModifier.insertChild(mState, mKind);
363         }
364 
365         final View newField = createEditorView(values);
366         if (newField instanceof Editor) {
367             postWhenWindowFocused(new Runnable() {
368                 @Override
369                 public void run() {
370                     newField.requestFocus();
371                     ((Editor)newField).editNewlyAddedField();
372                 }
373             });
374         }
375 
376         // Hide the "add field" footer because there is now a blank field.
377         mAddFieldFooter.setVisibility(View.GONE);
378 
379         // Ensure we are visible
380         updateSectionVisible();
381     }
382 
getEditorCount()383     public int getEditorCount() {
384         return mEditors.getChildCount();
385     }
386 
getKind()387     public DataKind getKind() {
388         return mKind;
389     }
390 }
391