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.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 
     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