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 
StructuredNameEditorView(Context context)53     public StructuredNameEditorView(Context context) {
54         super(context);
55     }
56 
StructuredNameEditorView(Context context, AttributeSet attrs)57     public StructuredNameEditorView(Context context, AttributeSet attrs) {
58         super(context, attrs);
59     }
60 
StructuredNameEditorView(Context context, AttributeSet attrs, int defStyle)61     public StructuredNameEditorView(Context context, AttributeSet attrs, int defStyle) {
62         super(context, attrs, defStyle);
63     }
64 
65     @Override
setValues(DataKind kind, ValuesDelta entry, RawContactDelta state, boolean readOnly, ViewIdGenerator vig)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
onFieldChanged(String column, String value)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
onOptionalFieldVisibilityChange()104     protected void onOptionalFieldVisibilityChange() {
105         if (hasShortAndLongForms()) {
106             if (areOptionalFieldsVisible()) {
107                 switchFromFullNameToStructuredName();
108             } else {
109                 switchFromStructuredNameToFullName();
110             }
111         }
112 
113         super.onOptionalFieldVisibilityChange();
114     }
115 
switchFromFullNameToStructuredName()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 
switchFromStructuredNameToFullName()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 
valuesToStructuredNameMap(ValuesDelta values)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 
eraseFullName(ValuesDelta values)173     private void eraseFullName(ValuesDelta values) {
174         values.setDisplayName(null);
175     }
176 
rebuildFullName(ValuesDelta values)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 
eraseStructuredName(ValuesDelta values)184     private void eraseStructuredName(ValuesDelta values) {
185         for (String field : NameConverter.STRUCTURED_NAME_FIELDS) {
186             values.putNull(field);
187         }
188     }
189 
rebuildStructuredName(ValuesDelta values)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 
appendQueryParameter(Uri.Builder builder, String field, String value)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      */
setDisplayName(String name)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
onSaveInstanceState()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
onRestoreInstanceState(Parcelable state)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 
SavedState(Parcelable superState)239         SavedState(Parcelable superState) {
240             mSuperState = superState;
241         }
242 
SavedState(Parcel in)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
writeToParcel(Parcel out, int flags)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
describeContents()274         public int describeContents() {
275             return 0;
276         }
277     }
278 
279     /** {@inheritDoc} */
280     @Override
onFinishInflate()281     protected void onFinishInflate() {
282         super.onFinishInflate();
283         // Remove padding below this view.
284         setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), 0);
285     }
286 }
287