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 for (String field : structuredNameMap.keySet()) { 159 mSnapshot.getContentValues().put(field, structuredNameMap.get(field)); 160 } 161 } 162 163 private Map<String, String> valuesToStructuredNameMap(ValuesDelta values) { 164 Map<String, String> structuredNameMap = new HashMap<String, String>(); 165 for (String key : NameConverter.STRUCTURED_NAME_FIELDS) { 166 structuredNameMap.put(key, values.getAsString(key)); 167 } 168 return structuredNameMap; 169 } 170 171 private void eraseFullName(ValuesDelta values) { 172 values.setDisplayName(null); 173 } 174 175 private void rebuildFullName(ValuesDelta values) { 176 Map<String, String> structuredNameMap = valuesToStructuredNameMap(values); 177 String displayName = NameConverter.structuredNameToDisplayName(getContext(), 178 structuredNameMap); 179 values.setDisplayName(displayName); 180 } 181 182 private void eraseStructuredName(ValuesDelta values) { 183 for (String field : NameConverter.STRUCTURED_NAME_FIELDS) { 184 values.putNull(field); 185 } 186 } 187 188 private void rebuildStructuredName(ValuesDelta values) { 189 String displayName = values.getDisplayName(); 190 Map<String, String> structuredNameMap = NameConverter.displayNameToStructuredName( 191 getContext(), displayName); 192 for (String field : structuredNameMap.keySet()) { 193 values.put(field, structuredNameMap.get(field)); 194 } 195 } 196 197 private static void appendQueryParameter(Uri.Builder builder, String field, String value) { 198 if (!TextUtils.isEmpty(value)) { 199 builder.appendQueryParameter(field, value); 200 } 201 } 202 203 /** 204 * Set the display name onto the text field directly. This does not affect the underlying 205 * data structure so it is similar to the user typing the value in on the field directly. 206 * 207 * @param name The name to set on the text field. 208 */ 209 public void setDisplayName(String name) { 210 // For now, assume the first text field is the name. 211 // TODO: Find a better way to get a hold of the name field. 212 super.setValue(0, name); 213 } 214 215 @Override 216 protected Parcelable onSaveInstanceState() { 217 SavedState state = new SavedState(super.onSaveInstanceState()); 218 state.mChanged = mChanged; 219 state.mSnapshot = mSnapshot.getContentValues(); 220 return state; 221 } 222 223 @Override 224 protected void onRestoreInstanceState(Parcelable state) { 225 SavedState ss = (SavedState) state; 226 super.onRestoreInstanceState(ss.mSuperState); 227 228 mChanged = ss.mChanged; 229 mSnapshot = (StructuredNameDataItem) DataItem.createFrom(ss.mSnapshot); 230 } 231 232 private static class SavedState implements Parcelable { 233 public boolean mChanged; 234 public ContentValues mSnapshot; 235 public Parcelable mSuperState; 236 237 SavedState(Parcelable superState) { 238 mSuperState = superState; 239 } 240 241 private SavedState(Parcel in) { 242 ClassLoader loader = getClass().getClassLoader(); 243 mSuperState = in.readParcelable(loader); 244 245 mChanged = in.readInt() != 0; 246 mSnapshot = in.readParcelable(loader); 247 } 248 249 @Override 250 public void writeToParcel(Parcel out, int flags) { 251 out.writeParcelable(mSuperState, 0); 252 253 out.writeInt(mChanged ? 1 : 0); 254 out.writeParcelable(mSnapshot, 0); 255 } 256 257 @SuppressWarnings({"unused"}) 258 public static final Parcelable.Creator<SavedState> CREATOR 259 = new Parcelable.Creator<SavedState>() { 260 @Override 261 public SavedState createFromParcel(Parcel in) { 262 return new SavedState(in); 263 } 264 265 @Override 266 public SavedState[] newArray(int size) { 267 return new SavedState[size]; 268 } 269 }; 270 271 @Override 272 public int describeContents() { 273 return 0; 274 } 275 } 276} 277