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