Home | History | Annotate | Download | only in editor
      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 mHintTextColor;
     63     private int mHintTextColorUnfocused;
     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         mFields = (ViewGroup) findViewById(R.id.editors);
     88         mHintTextColor = getResources().getColor(R.color.secondary_text_color);
     89         mHintTextColorUnfocused = getResources().getColor(R.color.editor_disabled_text_color);
     90         mExpansionView = (ImageView) findViewById(R.id.expansion_view);
     91         mExpansionViewContainer = findViewById(R.id.expansion_view_container);
     92         if (mExpansionViewContainer != null) {
     93             mExpansionViewContainer.setOnClickListener(new OnClickListener() {
     94                 @Override
     95                 public void onClick(View v) {
     96                     mPreviousViewHeight = mFields.getHeight();
     97 
     98                     // Save focus
     99                     final View focusedChild = getFocusedChild();
    100                     final int focusedViewId = focusedChild == null ? -1 : focusedChild.getId();
    101 
    102                     // Reconfigure GUI
    103                     mHideOptional = !mHideOptional;
    104                     onOptionalFieldVisibilityChange();
    105                     rebuildValues();
    106 
    107                     // Restore focus
    108                     View newFocusView = findViewById(focusedViewId);
    109                     if (newFocusView == null || newFocusView.getVisibility() == GONE) {
    110                         // find first visible child
    111                         newFocusView = TextFieldsEditorView.this;
    112                     }
    113                     newFocusView.requestFocus();
    114 
    115                     EditorAnimator.getInstance().slideAndFadeIn(mFields, mPreviousViewHeight);
    116                 }
    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         if (mExpansionView != null) {
    147             mExpansionView.setEnabled(!isReadOnly() && enabled);
    148         }
    149     }
    150 
    151     private OnFocusChangeListener mTextFocusChangeListener = new OnFocusChangeListener() {
    152         @Override
    153         public void onFocusChange(View v, boolean hasFocus) {
    154             // Check whether this field contains focus by calling findFocus() instead of
    155             // hasFocus(). The hasFocus() value is not necessarily up to date.
    156             setHintColorDark(TextFieldsEditorView.this.findFocus() != null);
    157             if (getEditorListener() != null) {
    158                 getEditorListener().onRequest(EditorListener.EDITOR_FOCUS_CHANGED);
    159             }
    160             // Rebuild the label spinner using the new colors.
    161             rebuildLabel();
    162         }
    163     };
    164 
    165     /**
    166      * Set the hint color. If {@param isHintDark} is TRUE, then the hint color is set to a
    167      * a darker color.
    168      */
    169     public void setHintColorDark(boolean isHintDark) {
    170         if (mFieldEditTexts != null) {
    171             for (EditText text : mFieldEditTexts) {
    172                 if (isHintDark) {
    173                     text.setHintTextColor(mHintTextColor);
    174                 } else {
    175                     text.setHintTextColor(mHintTextColorUnfocused);
    176                 }
    177             }
    178         }
    179     }
    180 
    181     /**
    182      * Creates or removes the type/label button. Doesn't do anything if already correctly configured
    183      */
    184     private void setupExpansionView(boolean shouldExist, boolean collapsed) {
    185         if (shouldExist) {
    186             mExpansionViewContainer.setVisibility(View.VISIBLE);
    187             mExpansionView.setImageResource(collapsed
    188                     ? R.drawable.ic_menu_expander_minimized_holo_light
    189                     : R.drawable.ic_menu_expander_maximized_holo_light);
    190         } else {
    191             mExpansionViewContainer.setVisibility(View.GONE);
    192         }
    193     }
    194 
    195     @Override
    196     protected void requestFocusForFirstEditField() {
    197         if (mFieldEditTexts != null && mFieldEditTexts.length != 0) {
    198             EditText firstField = null;
    199             boolean anyFieldHasFocus = false;
    200             for (EditText editText : mFieldEditTexts) {
    201                 if (firstField == null && editText.getVisibility() == View.VISIBLE) {
    202                     firstField = editText;
    203                 }
    204                 if (editText.hasFocus()) {
    205                     anyFieldHasFocus = true;
    206                     break;
    207                 }
    208             }
    209             if (!anyFieldHasFocus && firstField != null) {
    210                 firstField.requestFocus();
    211             }
    212         }
    213     }
    214 
    215     public void setValue(int field, String value) {
    216         mFieldEditTexts[field].setText(value);
    217     }
    218 
    219     @Override
    220     public void setValues(DataKind kind, ValuesDelta entry, RawContactDelta state, boolean readOnly,
    221             ViewIdGenerator vig) {
    222         super.setValues(kind, entry, state, readOnly, vig);
    223         // Remove edit texts that we currently have
    224         if (mFieldEditTexts != null) {
    225             for (EditText fieldEditText : mFieldEditTexts) {
    226                 mFields.removeView(fieldEditText);
    227             }
    228         }
    229         boolean hidePossible = false;
    230 
    231         int fieldCount = kind.fieldList.size();
    232         mFieldEditTexts = new EditText[fieldCount];
    233         for (int index = 0; index < fieldCount; index++) {
    234             final EditField field = kind.fieldList.get(index);
    235             final EditText fieldView = new EditText(mContext);
    236             fieldView.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT,
    237                     LayoutParams.WRAP_CONTENT));
    238             fieldView.setTextSize(TypedValue.COMPLEX_UNIT_PX,
    239                     getResources().getDimension(R.dimen.editor_form_text_size));
    240             fieldView.setHintTextColor(mHintTextColorUnfocused);
    241             mFieldEditTexts[index] = fieldView;
    242             fieldView.setId(vig.getId(state, kind, entry, index));
    243             if (field.titleRes > 0) {
    244                 fieldView.setHint(field.titleRes);
    245             }
    246             int inputType = field.inputType;
    247             fieldView.setInputType(inputType);
    248             if (inputType == InputType.TYPE_CLASS_PHONE) {
    249                 PhoneNumberFormatter.setPhoneNumberFormattingTextWatcher(mContext, fieldView);
    250                 fieldView.setTextDirection(View.TEXT_DIRECTION_LTR);
    251             }
    252 
    253             // Set either a minimum line requirement or a minimum height (because {@link TextView}
    254             // only takes one or the other at a single time).
    255             if (field.minLines > 1) {
    256                 fieldView.setMinLines(field.minLines);
    257             } else {
    258                 // This needs to be called after setInputType. Otherwise, calling setInputType
    259                 // will unset this value.
    260                 fieldView.setMinHeight(mMinFieldHeight);
    261             }
    262 
    263             // Show the "next" button in IME to navigate between text fields
    264             // TODO: Still need to properly navigate to/from sections without text fields,
    265             // See Bug: 5713510
    266             fieldView.setImeOptions(EditorInfo.IME_ACTION_NEXT);
    267 
    268             // Read current value from state
    269             final String column = field.column;
    270             final String value = entry.getAsString(column);
    271             fieldView.setText(value);
    272 
    273             // Show the delete button if we have a non-null value
    274             setDeleteButtonVisible(value != null);
    275 
    276             // Prepare listener for writing changes
    277             fieldView.addTextChangedListener(new TextWatcher() {
    278                 @Override
    279                 public void afterTextChanged(Editable s) {
    280                     // Trigger event for newly changed value
    281                     onFieldChanged(column, s.toString());
    282                 }
    283 
    284                 @Override
    285                 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
    286                 }
    287 
    288                 @Override
    289                 public void onTextChanged(CharSequence s, int start, int before, int count) {
    290                 }
    291             });
    292 
    293             fieldView.setEnabled(isEnabled() && !readOnly);
    294             fieldView.setOnFocusChangeListener(mTextFocusChangeListener);
    295 
    296             if (field.shortForm) {
    297                 hidePossible = true;
    298                 mHasShortAndLongForms = true;
    299                 fieldView.setVisibility(mHideOptional ? View.VISIBLE : View.GONE);
    300             } else if (field.longForm) {
    301                 hidePossible = true;
    302                 mHasShortAndLongForms = true;
    303                 fieldView.setVisibility(mHideOptional ? View.GONE : View.VISIBLE);
    304             } else {
    305                 // Hide field when empty and optional value
    306                 final boolean couldHide = (!ContactsUtils.isGraphic(value) && field.optional);
    307                 final boolean willHide = (mHideOptional && couldHide);
    308                 fieldView.setVisibility(willHide ? View.GONE : View.VISIBLE);
    309                 hidePossible = hidePossible || couldHide;
    310             }
    311 
    312             mFields.addView(fieldView);
    313         }
    314 
    315         if (mExpansionView != null) {
    316             // When hiding fields, place expandable
    317             setupExpansionView(hidePossible, mHideOptional);
    318             mExpansionView.setEnabled(!readOnly && isEnabled());
    319         }
    320         updateEmptiness();
    321     }
    322 
    323     @Override
    324     public boolean isEmpty() {
    325         for (int i = 0; i < mFields.getChildCount(); i++) {
    326             EditText editText = (EditText) mFields.getChildAt(i);
    327             if (!TextUtils.isEmpty(editText.getText())) {
    328                 return false;
    329             }
    330         }
    331         return true;
    332     }
    333 
    334     /**
    335      * Returns true if the editor is currently configured to show optional fields.
    336      */
    337     public boolean areOptionalFieldsVisible() {
    338         return !mHideOptional;
    339     }
    340 
    341     public boolean hasShortAndLongForms() {
    342         return mHasShortAndLongForms;
    343     }
    344 
    345     /**
    346      * Populates the bound rectangle with the bounds of the last editor field inside this view.
    347      */
    348     public void acquireEditorBounds(Rect bounds) {
    349         if (mFieldEditTexts != null) {
    350             for (int i = mFieldEditTexts.length; --i >= 0;) {
    351                 EditText editText = mFieldEditTexts[i];
    352                 if (editText.getVisibility() == View.VISIBLE) {
    353                     bounds.set(editText.getLeft(), editText.getTop(), editText.getRight(),
    354                             editText.getBottom());
    355                     return;
    356                 }
    357             }
    358         }
    359     }
    360 
    361     /**
    362      * Saves the visibility of the child EditTexts, and mHideOptional.
    363      */
    364     @Override
    365     protected Parcelable onSaveInstanceState() {
    366         Parcelable superState = super.onSaveInstanceState();
    367         SavedState ss = new SavedState(superState);
    368 
    369         ss.mHideOptional = mHideOptional;
    370 
    371         final int numChildren = mFieldEditTexts == null ? 0 : mFieldEditTexts.length;
    372         ss.mVisibilities = new int[numChildren];
    373         for (int i = 0; i < numChildren; i++) {
    374             ss.mVisibilities[i] = mFieldEditTexts[i].getVisibility();
    375         }
    376 
    377         return ss;
    378     }
    379 
    380     /**
    381      * Restores the visibility of the child EditTexts, and mHideOptional.
    382      */
    383     @Override
    384     protected void onRestoreInstanceState(Parcelable state) {
    385         SavedState ss = (SavedState) state;
    386         super.onRestoreInstanceState(ss.getSuperState());
    387 
    388         mHideOptional = ss.mHideOptional;
    389 
    390         int numChildren = Math.min(mFieldEditTexts.length, ss.mVisibilities.length);
    391         for (int i = 0; i < numChildren; i++) {
    392             mFieldEditTexts[i].setVisibility(ss.mVisibilities[i]);
    393         }
    394     }
    395 
    396     private static class SavedState extends BaseSavedState {
    397         public boolean mHideOptional;
    398         public int[] mVisibilities;
    399 
    400         SavedState(Parcelable superState) {
    401             super(superState);
    402         }
    403 
    404         private SavedState(Parcel in) {
    405             super(in);
    406             mVisibilities = new int[in.readInt()];
    407             in.readIntArray(mVisibilities);
    408         }
    409 
    410         @Override
    411         public void writeToParcel(Parcel out, int flags) {
    412             super.writeToParcel(out, flags);
    413             out.writeInt(mVisibilities.length);
    414             out.writeIntArray(mVisibilities);
    415         }
    416 
    417         @SuppressWarnings({"unused", "hiding" })
    418         public static final Parcelable.Creator<SavedState> CREATOR
    419                 = new Parcelable.Creator<SavedState>() {
    420             @Override
    421             public SavedState createFromParcel(Parcel in) {
    422                 return new SavedState(in);
    423             }
    424 
    425             @Override
    426             public SavedState[] newArray(int size) {
    427                 return new SavedState[size];
    428             }
    429         };
    430     }
    431 
    432     @Override
    433     public void clearAllFields() {
    434         if (mFieldEditTexts != null) {
    435             for (EditText fieldEditText : mFieldEditTexts) {
    436                 // Update UI (which will trigger a state change through the {@link TextWatcher})
    437                 fieldEditText.setText("");
    438             }
    439         }
    440     }
    441 }
    442