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