TextFieldsEditorView.java revision 9db59fe67d9f2624120c718499a74eab7fea3c03
1/*
2 * Copyright (C) 2010 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
17package com.android.contacts.editor;
18
19import com.android.contacts.ContactsUtils;
20import com.android.contacts.R;
21import com.android.contacts.model.AccountType.EditField;
22import com.android.contacts.model.DataKind;
23import com.android.contacts.model.EntityDelta;
24import com.android.contacts.model.EntityDelta.ValuesDelta;
25import com.android.contacts.util.NameConverter;
26import com.android.contacts.util.PhoneNumberFormatter;
27
28import android.content.Context;
29import android.content.Entity;
30import android.graphics.Rect;
31import android.graphics.Typeface;
32import android.os.Parcel;
33import android.os.Parcelable;
34import android.provider.ContactsContract.CommonDataKinds.StructuredName;
35import android.telephony.PhoneNumberFormattingTextWatcher;
36import android.text.Editable;
37import android.text.InputType;
38import android.text.Spannable;
39import android.text.TextUtils;
40import android.text.TextWatcher;
41import android.text.style.StyleSpan;
42import android.util.AttributeSet;
43import android.view.Gravity;
44import android.view.View;
45import android.view.ViewGroup;
46import android.widget.EditText;
47import android.widget.ImageView;
48import android.widget.LinearLayout;
49
50import java.util.Map;
51
52/**
53 * Simple editor that handles labels and any {@link EditField} defined for the
54 * entry. Uses {@link ValuesDelta} to read any existing {@link Entity} values,
55 * and to correctly write any changes values.
56 */
57public class TextFieldsEditorView extends LabeledEditorView {
58    private EditText[] mFieldEditTexts = null;
59    private ViewGroup mFields = null;
60    private View mExpansionViewContainer;
61    private ImageView mExpansionView;
62    private boolean mHideOptional = true;
63    private boolean mHasShortAndLongForms;
64    private int mMinFieldHeight;
65
66    public TextFieldsEditorView(Context context) {
67        super(context);
68    }
69
70    public TextFieldsEditorView(Context context, AttributeSet attrs) {
71        super(context, attrs);
72    }
73
74    public TextFieldsEditorView(Context context, AttributeSet attrs, int defStyle) {
75        super(context, attrs, defStyle);
76    }
77
78    /** {@inheritDoc} */
79    @Override
80    protected void onFinishInflate() {
81        super.onFinishInflate();
82
83        setDrawingCacheEnabled(true);
84        setAlwaysDrawnWithCacheEnabled(true);
85
86        mMinFieldHeight = mContext.getResources().getDimensionPixelSize(
87                R.dimen.editor_min_line_item_height);
88        mFields = (ViewGroup) findViewById(R.id.editors);
89        mExpansionView = (ImageView) findViewById(R.id.expansion_view);
90        mExpansionViewContainer = findViewById(R.id.expansion_view_container);
91        mExpansionViewContainer.setOnClickListener(new OnClickListener() {
92            @Override
93            public void onClick(View v) {
94                // Save focus
95                final View focusedChild = getFocusedChild();
96                final int focusedViewId = focusedChild == null ? -1 : focusedChild.getId();
97
98                // Reconfigure GUI
99                mHideOptional = !mHideOptional;
100                onOptionalFieldVisibilityChange();
101                rebuildValues();
102
103                // Restore focus
104                View newFocusView = findViewById(focusedViewId);
105                if (newFocusView == null || newFocusView.getVisibility() == GONE) {
106                    // find first visible child
107                    newFocusView = TextFieldsEditorView.this;
108                }
109                newFocusView.requestFocus();
110            }
111        });
112    }
113
114    @Override
115    public void setEnabled(boolean enabled) {
116        super.setEnabled(enabled);
117
118        if (mFieldEditTexts != null) {
119            for (int index = 0; index < mFieldEditTexts.length; index++) {
120                mFieldEditTexts[index].setEnabled(!isReadOnly() && enabled);
121            }
122        }
123        mExpansionView.setEnabled(!isReadOnly() && enabled);
124    }
125
126    /**
127     * Creates or removes the type/label button. Doesn't do anything if already correctly configured
128     */
129    private void setupExpansionView(boolean shouldExist, boolean collapsed) {
130        if (shouldExist) {
131            mExpansionViewContainer.setVisibility(View.VISIBLE);
132            mExpansionView.setImageResource(collapsed
133                    ? R.drawable.ic_menu_expander_minimized_holo_light
134                    : R.drawable.ic_menu_expander_maximized_holo_light);
135        } else {
136            mExpansionViewContainer.setVisibility(View.GONE);
137        }
138    }
139
140    @Override
141    protected void requestFocusForFirstEditField() {
142        if (mFieldEditTexts != null && mFieldEditTexts.length != 0) {
143            EditText firstField = null;
144            boolean anyFieldHasFocus = false;
145            for (EditText editText : mFieldEditTexts) {
146                if (firstField == null && editText.getVisibility() == View.VISIBLE) {
147                    firstField = editText;
148                }
149                if (editText.hasFocus()) {
150                    anyFieldHasFocus = true;
151                    break;
152                }
153            }
154            if (!anyFieldHasFocus && firstField != null) {
155                firstField.requestFocus();
156            }
157        }
158    }
159
160    @Override
161    public void setValues(DataKind kind, ValuesDelta entry, EntityDelta state, boolean readOnly,
162            ViewIdGenerator vig) {
163        super.setValues(kind, entry, state, readOnly, vig);
164        // Remove edit texts that we currently have
165        if (mFieldEditTexts != null) {
166            for (EditText fieldEditText : mFieldEditTexts) {
167                mFields.removeView(fieldEditText);
168            }
169        }
170        boolean hidePossible = false;
171
172        int fieldCount = kind.fieldList.size();
173        mFieldEditTexts = new EditText[fieldCount];
174        for (int index = 0; index < fieldCount; index++) {
175            final EditField field = kind.fieldList.get(index);
176            final EditText fieldView = new EditText(mContext);
177            fieldView.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT,
178                    field.isMultiLine() ? LayoutParams.WRAP_CONTENT : mMinFieldHeight));
179            // Set either a minimum line requirement or a minimum height (because {@link TextView}
180            // only takes one or the other at a single time).
181            if (field.minLines != 0) {
182                fieldView.setMinLines(field.minLines);
183            } else {
184                fieldView.setMinHeight(mMinFieldHeight);
185            }
186            fieldView.setTextAppearance(getContext(), kind.textAppearanceResourceId);
187            fieldView.setGravity(Gravity.TOP);
188            mFieldEditTexts[index] = fieldView;
189            fieldView.setId(vig.getId(state, kind, entry, index));
190            if (field.titleRes > 0) {
191                fieldView.setHint(field.titleRes);
192            }
193            int inputType = field.inputType;
194            fieldView.setInputType(inputType);
195            if (inputType == InputType.TYPE_CLASS_PHONE) {
196                PhoneNumberFormatter.setPhoneNumberFormattingTextWatcher(mContext, fieldView);
197            }
198
199            // Read current value from state
200            final String column = field.column;
201            final String value = entry.getAsString(column);
202            fieldView.setText(value);
203
204            // Show the delete button if we have a non-null value
205            setDeleteButtonVisible(value != null);
206
207            // Prepare listener for writing changes
208            fieldView.addTextChangedListener(new TextWatcher() {
209                @Override
210                public void afterTextChanged(Editable s) {
211                    // Trigger event for newly changed value
212                    onFieldChanged(column, s.toString());
213                }
214
215                @Override
216                public void beforeTextChanged(CharSequence s, int start, int count, int after) {
217                }
218
219                @Override
220                public void onTextChanged(CharSequence s, int start, int before, int count) {
221                }
222            });
223
224            fieldView.setEnabled(isEnabled() && !readOnly);
225
226            if (field.shortForm) {
227                hidePossible = true;
228                mHasShortAndLongForms = true;
229                fieldView.setVisibility(mHideOptional ? View.VISIBLE : View.GONE);
230            } else if (field.longForm) {
231                hidePossible = true;
232                mHasShortAndLongForms = true;
233                fieldView.setVisibility(mHideOptional ? View.GONE : View.VISIBLE);
234            } else {
235                // Hide field when empty and optional value
236                final boolean couldHide = (!ContactsUtils.isGraphic(value) && field.optional);
237                final boolean willHide = (mHideOptional && couldHide);
238                fieldView.setVisibility(willHide ? View.GONE : View.VISIBLE);
239                hidePossible = hidePossible || couldHide;
240            }
241
242            mFields.addView(fieldView);
243        }
244
245        // When hiding fields, place expandable
246        setupExpansionView(hidePossible, mHideOptional);
247        mExpansionView.setEnabled(!readOnly && isEnabled());
248    }
249
250    @Override
251    public boolean isEmpty() {
252        for (int i = 0; i < mFields.getChildCount(); i++) {
253            EditText editText = (EditText) mFields.getChildAt(i);
254            if (!TextUtils.isEmpty(editText.getText())) {
255                return false;
256            }
257        }
258        return true;
259    }
260
261    /**
262     * Returns true if the editor is currently configured to show optional fields.
263     */
264    public boolean areOptionalFieldsVisible() {
265        return !mHideOptional;
266    }
267
268    public boolean hasShortAndLongForms() {
269        return mHasShortAndLongForms;
270    }
271
272    /**
273     * Populates the bound rectangle with the bounds of the last editor field inside this view.
274     */
275    public void acquireEditorBounds(Rect bounds) {
276        if (mFieldEditTexts != null) {
277            for (int i = mFieldEditTexts.length; --i >= 0;) {
278                EditText editText = mFieldEditTexts[i];
279                if (editText.getVisibility() == View.VISIBLE) {
280                    bounds.set(editText.getLeft(), editText.getTop(), editText.getRight(),
281                            editText.getBottom());
282                    return;
283                }
284            }
285        }
286    }
287
288    /**
289     * Saves the visibility of the child EditTexts, and mHideOptional.
290     */
291    @Override
292    protected Parcelable onSaveInstanceState() {
293        Parcelable superState = super.onSaveInstanceState();
294        SavedState ss = new SavedState(superState);
295
296        ss.mHideOptional = mHideOptional;
297
298        final int numChildren = mFieldEditTexts == null ? 0 : mFieldEditTexts.length;
299        ss.mVisibilities = new int[numChildren];
300        for (int i = 0; i < numChildren; i++) {
301            ss.mVisibilities[i] = mFieldEditTexts[i].getVisibility();
302        }
303
304        return ss;
305    }
306
307    /**
308     * Restores the visibility of the child EditTexts, and mHideOptional.
309     */
310    @Override
311    protected void onRestoreInstanceState(Parcelable state) {
312        SavedState ss = (SavedState) state;
313        super.onRestoreInstanceState(ss.getSuperState());
314
315        mHideOptional = ss.mHideOptional;
316
317        int numChildren = Math.min(mFieldEditTexts.length, ss.mVisibilities.length);
318        for (int i = 0; i < numChildren; i++) {
319            mFieldEditTexts[i].setVisibility(ss.mVisibilities[i]);
320        }
321    }
322
323    private static class SavedState extends BaseSavedState {
324        public boolean mHideOptional;
325        public int[] mVisibilities;
326
327        SavedState(Parcelable superState) {
328            super(superState);
329        }
330
331        private SavedState(Parcel in) {
332            super(in);
333            mVisibilities = new int[in.readInt()];
334            in.readIntArray(mVisibilities);
335        }
336
337        @Override
338        public void writeToParcel(Parcel out, int flags) {
339            super.writeToParcel(out, flags);
340            out.writeInt(mVisibilities.length);
341            out.writeIntArray(mVisibilities);
342        }
343
344        @SuppressWarnings({"unused", "hiding" })
345        public static final Parcelable.Creator<SavedState> CREATOR
346                = new Parcelable.Creator<SavedState>() {
347            @Override
348            public SavedState createFromParcel(Parcel in) {
349                return new SavedState(in);
350            }
351
352            @Override
353            public SavedState[] newArray(int size) {
354                return new SavedState[size];
355            }
356        };
357    }
358
359    @Override
360    public void clearAllFields() {
361        if (mFieldEditTexts != null) {
362            for (EditText fieldEditText : mFieldEditTexts) {
363                // Update UI (which will trigger a state change through the {@link TextWatcher})
364                fieldEditText.setText("");
365            }
366        }
367    }
368}
369