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
17package com.android.contacts.editor;
18
19import android.content.ContentValues;
20import android.content.Context;
21import android.os.Parcel;
22import android.os.Parcelable;
23import android.provider.ContactsContract.CommonDataKinds.StructuredName;
24import android.text.TextUtils;
25import android.util.AttributeSet;
26
27import com.android.contacts.common.model.RawContactDelta;
28import com.android.contacts.common.model.ValuesDelta;
29import com.android.contacts.common.model.dataitem.DataItem;
30import com.android.contacts.common.model.dataitem.DataKind;
31import com.android.contacts.common.util.NameConverter;
32import com.android.contacts.common.model.dataitem.StructuredNameDataItem;
33
34import java.util.HashMap;
35import 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 */
47public 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