• 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.view.Gravity;
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 mEditTextTopPadding;
62     private int mEditTextBottomPadding;
63     private int mPreviousViewHeight;
64 
TextFieldsEditorView(Context context)65     public TextFieldsEditorView(Context context) {
66         super(context);
67     }
68 
TextFieldsEditorView(Context context, AttributeSet attrs)69     public TextFieldsEditorView(Context context, AttributeSet attrs) {
70         super(context, attrs);
71     }
72 
TextFieldsEditorView(Context context, AttributeSet attrs, int defStyle)73     public TextFieldsEditorView(Context context, AttributeSet attrs, int defStyle) {
74         super(context, attrs, defStyle);
75     }
76 
77     /** {@inheritDoc} */
78     @Override
onFinishInflate()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
editNewlyAddedField()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
setEnabled(boolean enabled)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      */
setupExpansionView(boolean shouldExist, boolean collapsed)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
requestFocusForFirstEditField()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 
setValue(int field, String value)183     public void setValue(int field, String value) {
184         mFieldEditTexts[field].setText(value);
185     }
186 
187     @Override
setValues(DataKind kind, ValuesDelta entry, RawContactDelta state, boolean readOnly, ViewIdGenerator vig)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
isEmpty()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      */
areOptionalFieldsVisible()300     public boolean areOptionalFieldsVisible() {
301         return !mHideOptional;
302     }
303 
hasShortAndLongForms()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      */
acquireEditorBounds(Rect bounds)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
onSaveInstanceState()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
onRestoreInstanceState(Parcelable state)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 
SavedState(Parcelable superState)363         SavedState(Parcelable superState) {
364             super(superState);
365         }
366 
SavedState(Parcel in)367         private SavedState(Parcel in) {
368             super(in);
369             mVisibilities = new int[in.readInt()];
370             in.readIntArray(mVisibilities);
371         }
372 
373         @Override
writeToParcel(Parcel out, int flags)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
clearAllFields()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