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.ContentValues;
     20 import android.content.Context;
     21 import android.net.Uri;
     22 import android.os.Parcel;
     23 import android.os.Parcelable;
     24 import android.provider.ContactsContract.CommonDataKinds.StructuredName;
     25 import android.text.TextUtils;
     26 import android.util.AttributeSet;
     27 
     28 import com.android.contacts.common.model.RawContactDelta;
     29 import com.android.contacts.common.model.ValuesDelta;
     30 import com.android.contacts.common.model.dataitem.DataItem;
     31 import com.android.contacts.common.model.dataitem.DataKind;
     32 import com.android.contacts.common.util.NameConverter;
     33 import com.android.contacts.common.model.dataitem.StructuredNameDataItem;
     34 
     35 import java.util.HashMap;
     36 import java.util.Map;
     37 
     38 /**
     39  * A dedicated editor for structured name.  When the user collapses/expands
     40  * the structured name, it will reparse or recompose the name, but only
     41  * if the user has made changes.  This distinction will be particularly
     42  * obvious if the name has a non-standard structure. Consider this structure:
     43  * first name="John Doe", family name="".  As long as the user does not change
     44  * the full name, expand and collapse will preserve this.  However, if the user
     45  * changes "John Doe" to "Jane Doe" and then expands the view, we will reparse
     46  * and show first name="Jane", family name="Doe".
     47  */
     48 public class StructuredNameEditorView extends TextFieldsEditorView {
     49 
     50     private StructuredNameDataItem mSnapshot;
     51     private boolean mChanged;
     52 
     53     public StructuredNameEditorView(Context context) {
     54         super(context);
     55     }
     56 
     57     public StructuredNameEditorView(Context context, AttributeSet attrs) {
     58         super(context, attrs);
     59     }
     60 
     61     public StructuredNameEditorView(Context context, AttributeSet attrs, int defStyle) {
     62         super(context, attrs, defStyle);
     63     }
     64 
     65     @Override
     66     public void setValues(DataKind kind, ValuesDelta entry, RawContactDelta state, boolean readOnly,
     67             ViewIdGenerator vig) {
     68         super.setValues(kind, entry, state, readOnly, vig);
     69         if (mSnapshot == null) {
     70             mSnapshot = (StructuredNameDataItem) DataItem.createFrom(
     71                     new ContentValues(getValues().getCompleteValues()));
     72             mChanged = entry.isInsert();
     73         } else {
     74             mChanged = false;
     75         }
     76         updateEmptiness();
     77     }
     78 
     79     @Override
     80     public void onFieldChanged(String column, String value) {
     81         if (!isFieldChanged(column, value)) {
     82             return;
     83         }
     84 
     85         // First save the new value for the column.
     86         saveValue(column, value);
     87         mChanged = true;
     88 
     89         // Next make sure the display name and the structured name are synced
     90         if (hasShortAndLongForms()) {
     91             if (areOptionalFieldsVisible()) {
     92                 rebuildFullName(getValues());
     93             } else {
     94                 rebuildStructuredName(getValues());
     95             }
     96         }
     97 
     98         // Then notify the listener, which will rely on the display and structured names to be
     99         // synced (in order to provide aggregate suggestions).
    100         notifyEditorListener();
    101     }
    102 
    103     @Override
    104     protected void onOptionalFieldVisibilityChange() {
    105         if (hasShortAndLongForms()) {
    106             if (areOptionalFieldsVisible()) {
    107                 switchFromFullNameToStructuredName();
    108             } else {
    109                 switchFromStructuredNameToFullName();
    110             }
    111         }
    112 
    113         super.onOptionalFieldVisibilityChange();
    114     }
    115 
    116     private void switchFromFullNameToStructuredName() {
    117         ValuesDelta values = getValues();
    118 
    119         if (!mChanged) {
    120             for (String field : NameConverter.STRUCTURED_NAME_FIELDS) {
    121                 values.put(field, mSnapshot.getContentValues().getAsString(field));
    122             }
    123             return;
    124         }
    125 
    126         String displayName = values.getDisplayName();
    127         Map<String, String> structuredNameMap = NameConverter.displayNameToStructuredName(
    128                 getContext(), displayName);
    129         if (!structuredNameMap.isEmpty()) {
    130             eraseFullName(values);
    131             for (String field : structuredNameMap.keySet()) {
    132                 values.put(field, structuredNameMap.get(field));
    133             }
    134         }
    135 
    136         mSnapshot.getContentValues().clear();
    137         mSnapshot.getContentValues().putAll(values.getCompleteValues());
    138         mSnapshot.setDisplayName(displayName);
    139     }
    140 
    141     private void switchFromStructuredNameToFullName() {
    142         ValuesDelta values = getValues();
    143 
    144         if (!mChanged) {
    145             values.setDisplayName(mSnapshot.getDisplayName());
    146             return;
    147         }
    148 
    149         Map<String, String> structuredNameMap = valuesToStructuredNameMap(values);
    150         String displayName = NameConverter.structuredNameToDisplayName(getContext(),
    151                 structuredNameMap);
    152         if (!TextUtils.isEmpty(displayName)) {
    153             eraseStructuredName(values);
    154             values.put(StructuredName.DISPLAY_NAME, displayName);
    155         }
    156 
    157         mSnapshot.getContentValues().clear();
    158         mSnapshot.setDisplayName(values.getDisplayName());
    159         mSnapshot.setMimeType(StructuredName.CONTENT_ITEM_TYPE);
    160         for (String field : structuredNameMap.keySet()) {
    161             mSnapshot.getContentValues().put(field, structuredNameMap.get(field));
    162         }
    163     }
    164 
    165     private Map<String, String> valuesToStructuredNameMap(ValuesDelta values) {
    166         Map<String, String> structuredNameMap = new HashMap<String, String>();
    167         for (String key : NameConverter.STRUCTURED_NAME_FIELDS) {
    168             structuredNameMap.put(key, values.getAsString(key));
    169         }
    170         return structuredNameMap;
    171     }
    172 
    173     private void eraseFullName(ValuesDelta values) {
    174         values.setDisplayName(null);
    175     }
    176 
    177     private void rebuildFullName(ValuesDelta values) {
    178         Map<String, String> structuredNameMap = valuesToStructuredNameMap(values);
    179         String displayName = NameConverter.structuredNameToDisplayName(getContext(),
    180                 structuredNameMap);
    181         values.setDisplayName(displayName);
    182     }
    183 
    184     private void eraseStructuredName(ValuesDelta values) {
    185         for (String field : NameConverter.STRUCTURED_NAME_FIELDS) {
    186             values.putNull(field);
    187         }
    188     }
    189 
    190     private void rebuildStructuredName(ValuesDelta values) {
    191         String displayName = values.getDisplayName();
    192         Map<String, String> structuredNameMap = NameConverter.displayNameToStructuredName(
    193                 getContext(), displayName);
    194         for (String field : structuredNameMap.keySet()) {
    195             values.put(field, structuredNameMap.get(field));
    196         }
    197     }
    198 
    199     private static void appendQueryParameter(Uri.Builder builder, String field, String value) {
    200         if (!TextUtils.isEmpty(value)) {
    201             builder.appendQueryParameter(field, value);
    202         }
    203     }
    204 
    205     /**
    206      * Set the display name onto the text field directly.  This does not affect the underlying
    207      * data structure so it is similar to the user typing the value in on the field directly.
    208      *
    209      * @param name The name to set on the text field.
    210      */
    211     public void setDisplayName(String name) {
    212         // For now, assume the first text field is the name.
    213         // TODO: Find a better way to get a hold of the name field.
    214         super.setValue(0, name);
    215     }
    216 
    217     @Override
    218     protected Parcelable onSaveInstanceState() {
    219         SavedState state = new SavedState(super.onSaveInstanceState());
    220         state.mChanged = mChanged;
    221         state.mSnapshot = mSnapshot.getContentValues();
    222         return state;
    223     }
    224 
    225     @Override
    226     protected void onRestoreInstanceState(Parcelable state) {
    227         SavedState ss = (SavedState) state;
    228         super.onRestoreInstanceState(ss.mSuperState);
    229 
    230         mChanged = ss.mChanged;
    231         mSnapshot = (StructuredNameDataItem) DataItem.createFrom(ss.mSnapshot);
    232     }
    233 
    234     private static class SavedState implements Parcelable {
    235         public boolean mChanged;
    236         public ContentValues mSnapshot;
    237         public Parcelable mSuperState;
    238 
    239         SavedState(Parcelable superState) {
    240             mSuperState = superState;
    241         }
    242 
    243         private SavedState(Parcel in) {
    244             ClassLoader loader = getClass().getClassLoader();
    245             mSuperState = in.readParcelable(loader);
    246 
    247             mChanged = in.readInt() != 0;
    248             mSnapshot = in.readParcelable(loader);
    249         }
    250 
    251         @Override
    252         public void writeToParcel(Parcel out, int flags) {
    253             out.writeParcelable(mSuperState, 0);
    254 
    255             out.writeInt(mChanged ? 1 : 0);
    256             out.writeParcelable(mSnapshot, 0);
    257         }
    258 
    259         @SuppressWarnings({"unused"})
    260         public static final Parcelable.Creator<SavedState> CREATOR
    261                 = new Parcelable.Creator<SavedState>() {
    262             @Override
    263             public SavedState createFromParcel(Parcel in) {
    264                 return new SavedState(in);
    265             }
    266 
    267             @Override
    268             public SavedState[] newArray(int size) {
    269                 return new SavedState[size];
    270             }
    271         };
    272 
    273         @Override
    274         public int describeContents() {
    275             return 0;
    276         }
    277     }
    278 
    279     /** {@inheritDoc} */
    280     @Override
    281     protected void onFinishInflate() {
    282         super.onFinishInflate();
    283         // Remove padding below this view.
    284         setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), 0);
    285     }
    286 }
    287