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