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