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.Context; 20 import android.graphics.Rect; 21 import android.os.Parcel; 22 import android.os.Parcelable; 23 import android.text.Editable; 24 import android.text.InputType; 25 import android.text.TextUtils; 26 import android.text.TextWatcher; 27 import android.util.AttributeSet; 28 import android.util.Log; 29 import android.view.Gravity; 30 import android.view.View; 31 import android.view.ViewGroup; 32 import android.view.inputmethod.EditorInfo; 33 import android.view.inputmethod.InputMethodManager; 34 import android.widget.EditText; 35 import android.widget.ImageView; 36 import android.widget.LinearLayout; 37 38 import com.android.contacts.ContactsUtils; 39 import com.android.contacts.R; 40 import com.android.contacts.model.RawContactDelta; 41 import com.android.contacts.model.RawContactDelta.ValuesDelta; 42 import com.android.contacts.model.account.AccountType.EditField; 43 import com.android.contacts.model.dataitem.DataKind; 44 import com.android.contacts.util.PhoneNumberFormatter; 45 46 /** 47 * Simple editor that handles labels and any {@link EditField} defined for the 48 * entry. Uses {@link ValuesDelta} to read any existing {@link RawContact} values, 49 * and to correctly write any changes values. 50 */ 51 public class TextFieldsEditorView extends LabeledEditorView { 52 private static final String TAG = TextFieldsEditorView.class.getSimpleName(); 53 54 private EditText[] mFieldEditTexts = null; 55 private ViewGroup mFields = null; 56 private View mExpansionViewContainer; 57 private ImageView mExpansionView; 58 private boolean mHideOptional = true; 59 private boolean mHasShortAndLongForms; 60 private int mMinFieldHeight; 61 62 public TextFieldsEditorView(Context context) { 63 super(context); 64 } 65 66 public TextFieldsEditorView(Context context, AttributeSet attrs) { 67 super(context, attrs); 68 } 69 70 public TextFieldsEditorView(Context context, AttributeSet attrs, int defStyle) { 71 super(context, attrs, defStyle); 72 } 73 74 /** {@inheritDoc} */ 75 @Override 76 protected void onFinishInflate() { 77 super.onFinishInflate(); 78 79 setDrawingCacheEnabled(true); 80 setAlwaysDrawnWithCacheEnabled(true); 81 82 mMinFieldHeight = mContext.getResources().getDimensionPixelSize( 83 R.dimen.editor_min_line_item_height); 84 mFields = (ViewGroup) findViewById(R.id.editors); 85 mExpansionView = (ImageView) findViewById(R.id.expansion_view); 86 mExpansionViewContainer = findViewById(R.id.expansion_view_container); 87 mExpansionViewContainer.setOnClickListener(new OnClickListener() { 88 @Override 89 public void onClick(View v) { 90 // Save focus 91 final View focusedChild = getFocusedChild(); 92 final int focusedViewId = focusedChild == null ? -1 : focusedChild.getId(); 93 94 // Reconfigure GUI 95 mHideOptional = !mHideOptional; 96 onOptionalFieldVisibilityChange(); 97 rebuildValues(); 98 99 // Restore focus 100 View newFocusView = findViewById(focusedViewId); 101 if (newFocusView == null || newFocusView.getVisibility() == GONE) { 102 // find first visible child 103 newFocusView = TextFieldsEditorView.this; 104 } 105 newFocusView.requestFocus(); 106 } 107 }); 108 } 109 110 @Override 111 public void editNewlyAddedField() { 112 // Some editors may have multiple fields (eg: first-name/last-name), but since the user 113 // has not selected a particular one, it is reasonable to simply pick the first. 114 final View editor = mFields.getChildAt(0); 115 116 // Show the soft-keyboard. 117 InputMethodManager imm = 118 (InputMethodManager)getContext().getSystemService(Context.INPUT_METHOD_SERVICE); 119 if (imm != null) { 120 if (!imm.showSoftInput(editor, InputMethodManager.SHOW_IMPLICIT)) { 121 Log.w(TAG, "Failed to show soft input method."); 122 } 123 } 124 } 125 126 @Override 127 public void setEnabled(boolean enabled) { 128 super.setEnabled(enabled); 129 130 if (mFieldEditTexts != null) { 131 for (int index = 0; index < mFieldEditTexts.length; index++) { 132 mFieldEditTexts[index].setEnabled(!isReadOnly() && enabled); 133 } 134 } 135 mExpansionView.setEnabled(!isReadOnly() && enabled); 136 } 137 138 /** 139 * Creates or removes the type/label button. Doesn't do anything if already correctly configured 140 */ 141 private void setupExpansionView(boolean shouldExist, boolean collapsed) { 142 if (shouldExist) { 143 mExpansionViewContainer.setVisibility(View.VISIBLE); 144 mExpansionView.setImageResource(collapsed 145 ? R.drawable.ic_menu_expander_minimized_holo_light 146 : R.drawable.ic_menu_expander_maximized_holo_light); 147 } else { 148 mExpansionViewContainer.setVisibility(View.GONE); 149 } 150 } 151 152 @Override 153 protected void requestFocusForFirstEditField() { 154 if (mFieldEditTexts != null && mFieldEditTexts.length != 0) { 155 EditText firstField = null; 156 boolean anyFieldHasFocus = false; 157 for (EditText editText : mFieldEditTexts) { 158 if (firstField == null && editText.getVisibility() == View.VISIBLE) { 159 firstField = editText; 160 } 161 if (editText.hasFocus()) { 162 anyFieldHasFocus = true; 163 break; 164 } 165 } 166 if (!anyFieldHasFocus && firstField != null) { 167 firstField.requestFocus(); 168 } 169 } 170 } 171 172 @Override 173 public void setValues(DataKind kind, ValuesDelta entry, RawContactDelta state, boolean readOnly, 174 ViewIdGenerator vig) { 175 super.setValues(kind, entry, state, readOnly, vig); 176 // Remove edit texts that we currently have 177 if (mFieldEditTexts != null) { 178 for (EditText fieldEditText : mFieldEditTexts) { 179 mFields.removeView(fieldEditText); 180 } 181 } 182 boolean hidePossible = false; 183 184 int fieldCount = kind.fieldList.size(); 185 mFieldEditTexts = new EditText[fieldCount]; 186 for (int index = 0; index < fieldCount; index++) { 187 final EditField field = kind.fieldList.get(index); 188 final EditText fieldView = new EditText(mContext); 189 fieldView.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, 190 field.isMultiLine() ? LayoutParams.WRAP_CONTENT : mMinFieldHeight)); 191 // Set either a minimum line requirement or a minimum height (because {@link TextView} 192 // only takes one or the other at a single time). 193 if (field.minLines != 0) { 194 fieldView.setMinLines(field.minLines); 195 } else { 196 fieldView.setMinHeight(mMinFieldHeight); 197 } 198 fieldView.setTextAppearance(getContext(), android.R.style.TextAppearance_Medium); 199 fieldView.setGravity(Gravity.TOP); 200 mFieldEditTexts[index] = fieldView; 201 fieldView.setId(vig.getId(state, kind, entry, index)); 202 if (field.titleRes > 0) { 203 fieldView.setHint(field.titleRes); 204 } 205 int inputType = field.inputType; 206 fieldView.setInputType(inputType); 207 if (inputType == InputType.TYPE_CLASS_PHONE) { 208 PhoneNumberFormatter.setPhoneNumberFormattingTextWatcher(mContext, fieldView); 209 } 210 211 // Show the "next" button in IME to navigate between text fields 212 // TODO: Still need to properly navigate to/from sections without text fields, 213 // See Bug: 5713510 214 fieldView.setImeOptions(EditorInfo.IME_ACTION_NEXT); 215 216 // Read current value from state 217 final String column = field.column; 218 final String value = entry.getAsString(column); 219 fieldView.setText(value); 220 221 // Show the delete button if we have a non-null value 222 setDeleteButtonVisible(value != null); 223 224 // Prepare listener for writing changes 225 fieldView.addTextChangedListener(new TextWatcher() { 226 @Override 227 public void afterTextChanged(Editable s) { 228 // Trigger event for newly changed value 229 onFieldChanged(column, s.toString()); 230 } 231 232 @Override 233 public void beforeTextChanged(CharSequence s, int start, int count, int after) { 234 } 235 236 @Override 237 public void onTextChanged(CharSequence s, int start, int before, int count) { 238 } 239 }); 240 241 fieldView.setEnabled(isEnabled() && !readOnly); 242 243 if (field.shortForm) { 244 hidePossible = true; 245 mHasShortAndLongForms = true; 246 fieldView.setVisibility(mHideOptional ? View.VISIBLE : View.GONE); 247 } else if (field.longForm) { 248 hidePossible = true; 249 mHasShortAndLongForms = true; 250 fieldView.setVisibility(mHideOptional ? View.GONE : View.VISIBLE); 251 } else { 252 // Hide field when empty and optional value 253 final boolean couldHide = (!ContactsUtils.isGraphic(value) && field.optional); 254 final boolean willHide = (mHideOptional && couldHide); 255 fieldView.setVisibility(willHide ? View.GONE : View.VISIBLE); 256 hidePossible = hidePossible || couldHide; 257 } 258 259 mFields.addView(fieldView); 260 } 261 262 // When hiding fields, place expandable 263 setupExpansionView(hidePossible, mHideOptional); 264 mExpansionView.setEnabled(!readOnly && isEnabled()); 265 } 266 267 @Override 268 public boolean isEmpty() { 269 for (int i = 0; i < mFields.getChildCount(); i++) { 270 EditText editText = (EditText) mFields.getChildAt(i); 271 if (!TextUtils.isEmpty(editText.getText())) { 272 return false; 273 } 274 } 275 return true; 276 } 277 278 /** 279 * Returns true if the editor is currently configured to show optional fields. 280 */ 281 public boolean areOptionalFieldsVisible() { 282 return !mHideOptional; 283 } 284 285 public boolean hasShortAndLongForms() { 286 return mHasShortAndLongForms; 287 } 288 289 /** 290 * Populates the bound rectangle with the bounds of the last editor field inside this view. 291 */ 292 public void acquireEditorBounds(Rect bounds) { 293 if (mFieldEditTexts != null) { 294 for (int i = mFieldEditTexts.length; --i >= 0;) { 295 EditText editText = mFieldEditTexts[i]; 296 if (editText.getVisibility() == View.VISIBLE) { 297 bounds.set(editText.getLeft(), editText.getTop(), editText.getRight(), 298 editText.getBottom()); 299 return; 300 } 301 } 302 } 303 } 304 305 /** 306 * Saves the visibility of the child EditTexts, and mHideOptional. 307 */ 308 @Override 309 protected Parcelable onSaveInstanceState() { 310 Parcelable superState = super.onSaveInstanceState(); 311 SavedState ss = new SavedState(superState); 312 313 ss.mHideOptional = mHideOptional; 314 315 final int numChildren = mFieldEditTexts == null ? 0 : mFieldEditTexts.length; 316 ss.mVisibilities = new int[numChildren]; 317 for (int i = 0; i < numChildren; i++) { 318 ss.mVisibilities[i] = mFieldEditTexts[i].getVisibility(); 319 } 320 321 return ss; 322 } 323 324 /** 325 * Restores the visibility of the child EditTexts, and mHideOptional. 326 */ 327 @Override 328 protected void onRestoreInstanceState(Parcelable state) { 329 SavedState ss = (SavedState) state; 330 super.onRestoreInstanceState(ss.getSuperState()); 331 332 mHideOptional = ss.mHideOptional; 333 334 int numChildren = Math.min(mFieldEditTexts.length, ss.mVisibilities.length); 335 for (int i = 0; i < numChildren; i++) { 336 mFieldEditTexts[i].setVisibility(ss.mVisibilities[i]); 337 } 338 } 339 340 private static class SavedState extends BaseSavedState { 341 public boolean mHideOptional; 342 public int[] mVisibilities; 343 344 SavedState(Parcelable superState) { 345 super(superState); 346 } 347 348 private SavedState(Parcel in) { 349 super(in); 350 mVisibilities = new int[in.readInt()]; 351 in.readIntArray(mVisibilities); 352 } 353 354 @Override 355 public void writeToParcel(Parcel out, int flags) { 356 super.writeToParcel(out, flags); 357 out.writeInt(mVisibilities.length); 358 out.writeIntArray(mVisibilities); 359 } 360 361 @SuppressWarnings({"unused", "hiding" }) 362 public static final Parcelable.Creator<SavedState> CREATOR 363 = new Parcelable.Creator<SavedState>() { 364 @Override 365 public SavedState createFromParcel(Parcel in) { 366 return new SavedState(in); 367 } 368 369 @Override 370 public SavedState[] newArray(int size) { 371 return new SavedState[size]; 372 } 373 }; 374 } 375 376 @Override 377 public void clearAllFields() { 378 if (mFieldEditTexts != null) { 379 for (EditText fieldEditText : mFieldEditTexts) { 380 // Update UI (which will trigger a state change through the {@link TextWatcher}) 381 fieldEditText.setText(""); 382 } 383 } 384 } 385 } 386