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