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