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