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