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