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