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.net.Uri; 22import android.os.Parcel; 23import android.os.Parcelable; 24import android.provider.ContactsContract.CommonDataKinds.StructuredName; 25import android.text.TextUtils; 26import android.util.AttributeSet; 27 28import com.android.contacts.common.model.RawContactDelta; 29import com.android.contacts.common.model.ValuesDelta; 30import com.android.contacts.common.model.dataitem.DataItem; 31import com.android.contacts.common.model.dataitem.DataKind; 32import com.android.contacts.common.util.NameConverter; 33import com.android.contacts.common.model.dataitem.StructuredNameDataItem; 34 35import java.util.HashMap; 36import 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 */ 48public class StructuredNameEditorView extends TextFieldsEditorView { 49 50 private StructuredNameDataItem mSnapshot; 51 private boolean mChanged; 52 53 public StructuredNameEditorView(Context context) { 54 super(context); 55 } 56 57 public StructuredNameEditorView(Context context, AttributeSet attrs) { 58 super(context, attrs); 59 } 60 61 public StructuredNameEditorView(Context context, AttributeSet attrs, int defStyle) { 62 super(context, attrs, defStyle); 63 } 64 65 @Override 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 } 77 78 @Override 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 103 protected void onOptionalFieldVisibilityChange() { 104 if (hasShortAndLongForms()) { 105 if (areOptionalFieldsVisible()) { 106 switchFromFullNameToStructuredName(); 107 } else { 108 switchFromStructuredNameToFullName(); 109 } 110 } 111 112 super.onOptionalFieldVisibilityChange(); 113 } 114 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 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 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 172 private void eraseFullName(ValuesDelta values) { 173 values.setDisplayName(null); 174 } 175 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 183 private void eraseStructuredName(ValuesDelta values) { 184 for (String field : NameConverter.STRUCTURED_NAME_FIELDS) { 185 values.putNull(field); 186 } 187 } 188 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 private static void appendQueryParameter(Uri.Builder builder, String field, String value) { 199 if (!TextUtils.isEmpty(value)) { 200 builder.appendQueryParameter(field, value); 201 } 202 } 203 204 /** 205 * Set the display name onto the text field directly. This does not affect the underlying 206 * data structure so it is similar to the user typing the value in on the field directly. 207 * 208 * @param name The name to set on the text field. 209 */ 210 public void setDisplayName(String name) { 211 // For now, assume the first text field is the name. 212 // TODO: Find a better way to get a hold of the name field. 213 super.setValue(0, name); 214 } 215 216 @Override 217 protected Parcelable onSaveInstanceState() { 218 SavedState state = new SavedState(super.onSaveInstanceState()); 219 state.mChanged = mChanged; 220 state.mSnapshot = mSnapshot.getContentValues(); 221 return state; 222 } 223 224 @Override 225 protected void onRestoreInstanceState(Parcelable state) { 226 SavedState ss = (SavedState) state; 227 super.onRestoreInstanceState(ss.mSuperState); 228 229 mChanged = ss.mChanged; 230 mSnapshot = (StructuredNameDataItem) DataItem.createFrom(ss.mSnapshot); 231 } 232 233 private static class SavedState implements Parcelable { 234 public boolean mChanged; 235 public ContentValues mSnapshot; 236 public Parcelable mSuperState; 237 238 SavedState(Parcelable superState) { 239 mSuperState = superState; 240 } 241 242 private SavedState(Parcel in) { 243 ClassLoader loader = getClass().getClassLoader(); 244 mSuperState = in.readParcelable(loader); 245 246 mChanged = in.readInt() != 0; 247 mSnapshot = in.readParcelable(loader); 248 } 249 250 @Override 251 public void writeToParcel(Parcel out, int flags) { 252 out.writeParcelable(mSuperState, 0); 253 254 out.writeInt(mChanged ? 1 : 0); 255 out.writeParcelable(mSnapshot, 0); 256 } 257 258 @SuppressWarnings({"unused"}) 259 public static final Parcelable.Creator<SavedState> CREATOR 260 = new Parcelable.Creator<SavedState>() { 261 @Override 262 public SavedState createFromParcel(Parcel in) { 263 return new SavedState(in); 264 } 265 266 @Override 267 public SavedState[] newArray(int size) { 268 return new SavedState[size]; 269 } 270 }; 271 272 @Override 273 public int describeContents() { 274 return 0; 275 } 276 } 277} 278