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.os.Parcel; 22 import android.os.Parcelable; 23 import android.provider.ContactsContract.CommonDataKinds.StructuredName; 24 import android.text.TextUtils; 25 import android.util.AttributeSet; 26 27 import com.android.contacts.common.model.RawContactDelta; 28 import com.android.contacts.common.model.ValuesDelta; 29 import com.android.contacts.common.model.dataitem.DataItem; 30 import com.android.contacts.common.model.dataitem.DataKind; 31 import com.android.contacts.common.util.NameConverter; 32 import com.android.contacts.common.model.dataitem.StructuredNameDataItem; 33 34 import java.util.HashMap; 35 import 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 */ 47 public class StructuredNameEditorView extends TextFieldsEditorView { 48 49 private StructuredNameDataItem mSnapshot; 50 private boolean mChanged; 51 StructuredNameEditorView(Context context)52 public StructuredNameEditorView(Context context) { 53 super(context); 54 } 55 StructuredNameEditorView(Context context, AttributeSet attrs)56 public StructuredNameEditorView(Context context, AttributeSet attrs) { 57 super(context, attrs); 58 } 59 StructuredNameEditorView(Context context, AttributeSet attrs, int defStyle)60 public StructuredNameEditorView(Context context, AttributeSet attrs, int defStyle) { 61 super(context, attrs, defStyle); 62 } 63 64 @Override setValues(DataKind kind, ValuesDelta entry, RawContactDelta state, boolean readOnly, ViewIdGenerator vig)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 onFieldChanged(String column, String value)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 onOptionalFieldVisibilityChange()103 protected void onOptionalFieldVisibilityChange() { 104 if (hasShortAndLongForms()) { 105 if (areOptionalFieldsVisible()) { 106 switchFromFullNameToStructuredName(); 107 } else { 108 switchFromStructuredNameToFullName(); 109 } 110 } 111 112 super.onOptionalFieldVisibilityChange(); 113 } 114 switchFromFullNameToStructuredName()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 switchFromStructuredNameToFullName()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 valuesToStructuredNameMap(ValuesDelta values)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 eraseFullName(ValuesDelta values)172 private void eraseFullName(ValuesDelta values) { 173 values.setDisplayName(null); 174 } 175 rebuildFullName(ValuesDelta values)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 eraseStructuredName(ValuesDelta values)183 private void eraseStructuredName(ValuesDelta values) { 184 for (String field : NameConverter.STRUCTURED_NAME_FIELDS) { 185 values.putNull(field); 186 } 187 } 188 rebuildStructuredName(ValuesDelta values)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 */ setDisplayName(String name)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 */ getDisplayName()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 isEmpty()234 public boolean isEmpty() { 235 return TextUtils.isEmpty(getDisplayName()); 236 } 237 238 @Override onSaveInstanceState()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 onRestoreInstanceState(Parcelable state)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 SavedState(Parcelable superState)260 SavedState(Parcelable superState) { 261 mSuperState = superState; 262 } 263 SavedState(Parcel in)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 writeToParcel(Parcel out, int flags)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 describeContents()295 public int describeContents() { 296 return 0; 297 } 298 } 299 } 300