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