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 com.android.contacts.R; 20 import android.content.ContentValues; 21 import android.content.Context; 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 import android.view.View; 28 import android.widget.ImageView; 29 import android.widget.LinearLayout; 30 import android.widget.TextView; 31 32 import com.android.contacts.common.model.RawContactDelta; 33 import com.android.contacts.common.model.ValuesDelta; 34 import com.android.contacts.common.model.account.AccountType; 35 import com.android.contacts.common.model.dataitem.DataItem; 36 import com.android.contacts.common.model.dataitem.DataKind; 37 import com.android.contacts.common.util.NameConverter; 38 import com.android.contacts.common.model.dataitem.StructuredNameDataItem; 39 40 import java.util.HashMap; 41 import java.util.Map; 42 43 /** 44 * A dedicated editor for structured name. When the user collapses/expands 45 * the structured name, it will reparse or recompose the name, but only 46 * if the user has made changes. This distinction will be particularly 47 * obvious if the name has a non-standard structure. Consider this structure: 48 * first name="John Doe", family name="". As long as the user does not change 49 * the full name, expand and collapse will preserve this. However, if the user 50 * changes "John Doe" to "Jane Doe" and then expands the view, we will reparse 51 * and show first name="Jane", family name="Doe". 52 */ 53 public class StructuredNameEditorView extends TextFieldsEditorView { 54 55 private StructuredNameDataItem mSnapshot; 56 private boolean mChanged; 57 StructuredNameEditorView(Context context)58 public StructuredNameEditorView(Context context) { 59 super(context); 60 } 61 StructuredNameEditorView(Context context, AttributeSet attrs)62 public StructuredNameEditorView(Context context, AttributeSet attrs) { 63 super(context, attrs); 64 } 65 StructuredNameEditorView(Context context, AttributeSet attrs, int defStyle)66 public StructuredNameEditorView(Context context, AttributeSet attrs, int defStyle) { 67 super(context, attrs, defStyle); 68 } 69 70 @Override setValues(DataKind kind, ValuesDelta entry, RawContactDelta state, boolean readOnly, ViewIdGenerator vig)71 public void setValues(DataKind kind, ValuesDelta entry, RawContactDelta state, boolean readOnly, 72 ViewIdGenerator vig) { 73 super.setValues(kind, entry, state, readOnly, vig); 74 if (mSnapshot == null) { 75 mSnapshot = (StructuredNameDataItem) DataItem.createFrom( 76 new ContentValues(getValues().getCompleteValues())); 77 mChanged = entry.isInsert(); 78 } else { 79 mChanged = false; 80 } 81 updateEmptiness(); 82 } 83 84 /** 85 * Displays the icon and name for the given account under the name name input fields. 86 */ setAccountType(AccountType accountType)87 public void setAccountType(AccountType accountType) { 88 final LinearLayout layout = (LinearLayout) findViewById(R.id.account_type); 89 layout.setVisibility(View.VISIBLE); 90 final ImageView imageView = (ImageView) layout.findViewById(R.id.account_type_icon); 91 imageView.setImageDrawable(accountType.getDisplayIcon(getContext())); 92 final TextView textView = (TextView) layout.findViewById(R.id.account_type_name); 93 textView.setText(accountType.getDisplayLabel(getContext())); 94 } 95 96 @Override onFieldChanged(String column, String value)97 public void onFieldChanged(String column, String value) { 98 if (!isFieldChanged(column, value)) { 99 return; 100 } 101 102 // First save the new value for the column. 103 saveValue(column, value); 104 mChanged = true; 105 106 // Next make sure the display name and the structured name are synced 107 if (hasShortAndLongForms()) { 108 if (areOptionalFieldsVisible()) { 109 rebuildFullName(getValues()); 110 } else { 111 rebuildStructuredName(getValues()); 112 } 113 } 114 115 // Then notify the listener, which will rely on the display and structured names to be 116 // synced (in order to provide aggregate suggestions). 117 notifyEditorListener(); 118 } 119 120 @Override onOptionalFieldVisibilityChange()121 protected void onOptionalFieldVisibilityChange() { 122 if (hasShortAndLongForms()) { 123 if (areOptionalFieldsVisible()) { 124 switchFromFullNameToStructuredName(); 125 } else { 126 switchFromStructuredNameToFullName(); 127 } 128 } 129 130 super.onOptionalFieldVisibilityChange(); 131 } 132 switchFromFullNameToStructuredName()133 private void switchFromFullNameToStructuredName() { 134 ValuesDelta values = getValues(); 135 136 if (!mChanged) { 137 for (String field : NameConverter.STRUCTURED_NAME_FIELDS) { 138 values.put(field, mSnapshot.getContentValues().getAsString(field)); 139 } 140 return; 141 } 142 143 String displayName = values.getDisplayName(); 144 Map<String, String> structuredNameMap = NameConverter.displayNameToStructuredName( 145 getContext(), displayName); 146 if (!structuredNameMap.isEmpty()) { 147 eraseFullName(values); 148 for (String field : structuredNameMap.keySet()) { 149 values.put(field, structuredNameMap.get(field)); 150 } 151 } 152 153 mSnapshot.getContentValues().clear(); 154 mSnapshot.getContentValues().putAll(values.getCompleteValues()); 155 mSnapshot.setDisplayName(displayName); 156 } 157 switchFromStructuredNameToFullName()158 private void switchFromStructuredNameToFullName() { 159 ValuesDelta values = getValues(); 160 161 if (!mChanged) { 162 values.setDisplayName(mSnapshot.getDisplayName()); 163 return; 164 } 165 166 Map<String, String> structuredNameMap = valuesToStructuredNameMap(values); 167 String displayName = NameConverter.structuredNameToDisplayName(getContext(), 168 structuredNameMap); 169 if (!TextUtils.isEmpty(displayName)) { 170 eraseStructuredName(values); 171 values.put(StructuredName.DISPLAY_NAME, displayName); 172 } 173 174 mSnapshot.getContentValues().clear(); 175 mSnapshot.setDisplayName(values.getDisplayName()); 176 mSnapshot.setMimeType(StructuredName.CONTENT_ITEM_TYPE); 177 for (String field : structuredNameMap.keySet()) { 178 mSnapshot.getContentValues().put(field, structuredNameMap.get(field)); 179 } 180 } 181 valuesToStructuredNameMap(ValuesDelta values)182 private Map<String, String> valuesToStructuredNameMap(ValuesDelta values) { 183 Map<String, String> structuredNameMap = new HashMap<String, String>(); 184 for (String key : NameConverter.STRUCTURED_NAME_FIELDS) { 185 structuredNameMap.put(key, values.getAsString(key)); 186 } 187 return structuredNameMap; 188 } 189 eraseFullName(ValuesDelta values)190 private void eraseFullName(ValuesDelta values) { 191 values.setDisplayName(null); 192 } 193 rebuildFullName(ValuesDelta values)194 private void rebuildFullName(ValuesDelta values) { 195 Map<String, String> structuredNameMap = valuesToStructuredNameMap(values); 196 String displayName = NameConverter.structuredNameToDisplayName(getContext(), 197 structuredNameMap); 198 values.setDisplayName(displayName); 199 } 200 eraseStructuredName(ValuesDelta values)201 private void eraseStructuredName(ValuesDelta values) { 202 for (String field : NameConverter.STRUCTURED_NAME_FIELDS) { 203 values.putNull(field); 204 } 205 } 206 rebuildStructuredName(ValuesDelta values)207 private void rebuildStructuredName(ValuesDelta values) { 208 String displayName = values.getDisplayName(); 209 Map<String, String> structuredNameMap = NameConverter.displayNameToStructuredName( 210 getContext(), displayName); 211 for (String field : structuredNameMap.keySet()) { 212 values.put(field, structuredNameMap.get(field)); 213 } 214 } 215 216 /** 217 * Set the display name onto the text field directly. This does not affect the underlying 218 * data structure so it is similar to the user typing the value in on the field directly. 219 * 220 * @param name The name to set on the text field. 221 */ setDisplayName(String name)222 public void setDisplayName(String name) { 223 // For now, assume the first text field is the name. 224 // TODO: Find a better way to get a hold of the name field, 225 // including given_name and family_name. 226 super.setValue(0, name); 227 getValues().setDisplayName(name); 228 rebuildStructuredName(getValues()); 229 super.setValue(1, getValues().getAsString(StructuredName.GIVEN_NAME)); 230 super.setValue(3, getValues().getAsString(StructuredName.FAMILY_NAME)); 231 } 232 233 /** 234 * Returns the display name currently displayed in the editor. 235 */ getDisplayName()236 public String getDisplayName() { 237 final ValuesDelta valuesDelta = getValues(); 238 rebuildFullName(valuesDelta); 239 if (hasShortAndLongForms() && areOptionalFieldsVisible()) { 240 final Map<String, String> structuredNameMap = valuesToStructuredNameMap(valuesDelta); 241 final String displayName = NameConverter.structuredNameToDisplayName( 242 getContext(), structuredNameMap); 243 if (!TextUtils.isEmpty(displayName)) { 244 return displayName; 245 } 246 } 247 return valuesDelta.getDisplayName(); 248 } 249 250 @Override onSaveInstanceState()251 protected Parcelable onSaveInstanceState() { 252 SavedState state = new SavedState(super.onSaveInstanceState()); 253 state.mChanged = mChanged; 254 state.mSnapshot = mSnapshot.getContentValues(); 255 return state; 256 } 257 258 @Override onRestoreInstanceState(Parcelable state)259 protected void onRestoreInstanceState(Parcelable state) { 260 SavedState ss = (SavedState) state; 261 super.onRestoreInstanceState(ss.mSuperState); 262 263 mChanged = ss.mChanged; 264 mSnapshot = (StructuredNameDataItem) DataItem.createFrom(ss.mSnapshot); 265 } 266 267 private static class SavedState implements Parcelable { 268 public boolean mChanged; 269 public ContentValues mSnapshot; 270 public Parcelable mSuperState; 271 SavedState(Parcelable superState)272 SavedState(Parcelable superState) { 273 mSuperState = superState; 274 } 275 SavedState(Parcel in)276 private SavedState(Parcel in) { 277 ClassLoader loader = getClass().getClassLoader(); 278 mSuperState = in.readParcelable(loader); 279 280 mChanged = in.readInt() != 0; 281 mSnapshot = in.readParcelable(loader); 282 } 283 284 @Override writeToParcel(Parcel out, int flags)285 public void writeToParcel(Parcel out, int flags) { 286 out.writeParcelable(mSuperState, 0); 287 288 out.writeInt(mChanged ? 1 : 0); 289 out.writeParcelable(mSnapshot, 0); 290 } 291 292 @SuppressWarnings({"unused"}) 293 public static final Parcelable.Creator<SavedState> CREATOR 294 = new Parcelable.Creator<SavedState>() { 295 @Override 296 public SavedState createFromParcel(Parcel in) { 297 return new SavedState(in); 298 } 299 300 @Override 301 public SavedState[] newArray(int size) { 302 return new SavedState[size]; 303 } 304 }; 305 306 @Override describeContents()307 public int describeContents() { 308 return 0; 309 } 310 } 311 } 312