/* * Copyright (C) 2009 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.contacts.ui.widget; import com.android.contacts.ContactsUtils; import com.android.contacts.R; import com.android.contacts.model.Editor; import com.android.contacts.model.EntityDelta; import com.android.contacts.model.EntityModifier; import com.android.contacts.model.ContactsSource.DataKind; import com.android.contacts.model.ContactsSource.EditField; import com.android.contacts.model.ContactsSource.EditType; import com.android.contacts.model.EntityDelta.ValuesDelta; import com.android.contacts.ui.ViewIdGenerator; import com.android.contacts.util.DialerKeyListener; import android.app.AlertDialog; import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; import android.content.Entity; import android.os.Parcel; import android.os.Parcelable; import android.provider.ContactsContract.CommonDataKinds.Event; import android.telephony.PhoneNumberFormattingTextWatcher; import android.text.Editable; import android.text.InputFilter; import android.text.InputType; import android.text.TextUtils; import android.text.TextWatcher; import android.text.method.NumberKeyListener; import android.util.AttributeSet; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; import android.widget.ArrayAdapter; import android.widget.EditText; import android.widget.ListAdapter; import android.widget.RelativeLayout; import android.widget.TextView; import java.util.List; import android.util.Log; /** * Simple editor that handles labels and any {@link EditField} defined for * the entry. Uses {@link ValuesDelta} to read any existing * {@link Entity} values, and to correctly write any changes values. */ public class GenericEditorView extends RelativeLayout implements Editor, View.OnClickListener { private static final String TAG = "GenericEditorView"; protected static final int RES_FIELD = R.layout.item_editor_field; protected static final int RES_LABEL_ITEM = android.R.layout.simple_list_item_1; public static final int MAX_NAME_LEN = 100; protected LayoutInflater mInflater; protected static final int INPUT_TYPE_CUSTOM = EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS; protected TextView mLabel; protected ViewGroup mFields; protected View mDelete; protected View mMore; protected View mLess; protected DataKind mKind; protected ValuesDelta mEntry; protected EntityDelta mState; protected boolean mReadOnly; protected boolean mHideOptional = true; protected EditType mType; // Used only when a user tries to use custom label. private EditType mPendingType; private ViewIdGenerator mViewIdGenerator; public GenericEditorView(Context context) { super(context); } public GenericEditorView(Context context, AttributeSet attrs) { super(context, attrs); } /** {@inheritDoc} */ @Override protected void onFinishInflate() { mInflater = (LayoutInflater)getContext().getSystemService( Context.LAYOUT_INFLATER_SERVICE); mLabel = (TextView)findViewById(R.id.edit_label); mLabel.setOnClickListener(this); mFields = (ViewGroup)findViewById(R.id.edit_fields); mDelete = findViewById(R.id.edit_delete); mDelete.setOnClickListener(this); mMore = findViewById(R.id.edit_more); mMore.setOnClickListener(this); mLess = findViewById(R.id.edit_less); mLess.setOnClickListener(this); } protected EditorListener mListener; public void setEditorListener(EditorListener listener) { mListener = listener; } public void setDeletable(boolean deletable) { mDelete.setVisibility(deletable ? View.VISIBLE : View.INVISIBLE); } @Override public void setEnabled(boolean enabled) { mLabel.setEnabled(enabled); final int count = mFields.getChildCount(); for (int pos = 0; pos < count; pos++) { final View v = mFields.getChildAt(pos); v.setEnabled(enabled); } mMore.setEnabled(enabled); mLess.setEnabled(enabled); } /** * Build the current label state based on selected {@link EditType} and * possible custom label string. */ private void rebuildLabel() { // Handle undetected types if (mType == null) { mLabel.setText(R.string.unknown); return; } if (mType.customColumn != null) { // Use custom label string when present final String customText = mEntry.getAsString(mType.customColumn); if (customText != null) { mLabel.setText(customText); return; } } // Otherwise fall back to using default label mLabel.setText(mType.labelRes); } /** {@inheritDoc} */ public void onFieldChanged(String column, String value) { // Field changes are saved directly mEntry.put(column, value); if (mListener != null) { mListener.onRequest(EditorListener.FIELD_CHANGED); } } public boolean isAnyFieldFilledOut() { int childCount = mFields.getChildCount(); for (int i = 0; i < childCount; i++) { EditText editorView = (EditText) mFields.getChildAt(i); if (!TextUtils.isEmpty(editorView.getText())) { return true; } } return false; } private void rebuildValues() { setValues(mKind, mEntry, mState, mReadOnly, mViewIdGenerator); } /** * Prepare this editor using the given {@link DataKind} for defining * structure and {@link ValuesDelta} describing the content to edit. */ public void setValues(DataKind kind, ValuesDelta entry, EntityDelta state, boolean readOnly, ViewIdGenerator vig) { mKind = kind; mEntry = entry; mState = state; mReadOnly = readOnly; mViewIdGenerator = vig; setId(vig.getId(state, kind, entry, ViewIdGenerator.NO_VIEW_INDEX)); final boolean enabled = !readOnly; if (!entry.isVisible()) { // Hide ourselves entirely if deleted setVisibility(View.GONE); return; } else { setVisibility(View.VISIBLE); } // Display label selector if multiple types available final boolean hasTypes = EntityModifier.hasEditTypes(kind); mLabel.setVisibility(hasTypes ? View.VISIBLE : View.GONE); mLabel.setEnabled(enabled); if (hasTypes) { mType = EntityModifier.getCurrentType(entry, kind); rebuildLabel(); } // Build out set of fields mFields.removeAllViews(); boolean hidePossible = false; int n = 0; for (EditField field : kind.fieldList) { // Inflate field from definition EditText fieldView = (EditText)mInflater.inflate(RES_FIELD, mFields, false); fieldView.setId(vig.getId(state, kind, entry, n++)); if (field.titleRes > 0) { fieldView.setHint(field.titleRes); } final int inputType = field.inputType; fieldView.setInputType(inputType); fieldView.setMinLines(field.minLines); String kind_mimeType = kind.mimeType; if(Event.CONTENT_ITEM_TYPE.equals(kind_mimeType)){ DateTimePickerHooker hooker = new DateTimePickerHooker(mContext, fieldView); fieldView.setOnTouchListener(hooker); } // Read current value from state final String column = field.column; final String value = entry.getAsString(column); if (inputType == InputType.TYPE_CLASS_PHONE) { // fieldView.addTextChangedListener(new PhoneNumberFormattingTextWatcher()); fieldView.setKeyListener(DialerKeyListener.getInstance()); fieldView.setText(ContactsUtils.CommaAndSemicolonTopAndw(value)); }else{ fieldView.setText(value); } // Prepare listener for writing changes fieldView.addTextChangedListener(new TextWatcher() { public void afterTextChanged(Editable s) { // Trigger event for newly changed value String value = null; if(inputType == InputType.TYPE_CLASS_PHONE){ value = ContactsUtils.pAndwToCommaAndSemicolon(s.toString()); }else{ value = s.toString(); } onFieldChanged(column, value); } public void beforeTextChanged(CharSequence s, int start, int count, int after) { } public void onTextChanged(CharSequence s, int start, int before, int count) { } }); //jun for CR: NEWMS00152194 fieldView.setFilters(new InputFilter[]{new InputFilter.LengthFilter(MAX_NAME_LEN)}); // Hide field when empty and optional value final boolean couldHide = (!ContactsUtils.isGraphic(value) && field.optional); final boolean willHide = (mHideOptional && couldHide); fieldView.setVisibility(willHide ? View.GONE : View.VISIBLE); fieldView.setEnabled(enabled); hidePossible = hidePossible || couldHide; mFields.addView(fieldView); } // When hiding fields, place expandable if (hidePossible) { mMore.setVisibility(mHideOptional ? View.VISIBLE : View.GONE); mLess.setVisibility(mHideOptional ? View.GONE : View.VISIBLE); } else { mMore.setVisibility(View.GONE); mLess.setVisibility(View.GONE); } mMore.setEnabled(enabled); mLess.setEnabled(enabled); } /** * Prepare dialog for entering a custom label. The input value is trimmed: white spaces before * and after the input text is removed. * <p> * If the final value is empty, this change request is ignored; * no empty text is allowed in any custom label. */ private Dialog createCustomDialog(EditType selected) { final EditText customType = new EditText(mContext); customType.setInputType(INPUT_TYPE_CUSTOM); customType.requestFocus(); final AlertDialog.Builder builder = new AlertDialog.Builder(mContext); //modify by dory.zheng for NEWMS00120000 begin if(selected.rawValue == 19){ builder.setTitle(R.string.assistantLabelPickerTitle); }else{ builder.setTitle(R.string.customLabelPickerTitle); } // builder.setTitle(R.string.customLabelPickerTitle); //modify by dory.zheng for NEWMS00120000 end builder.setView(customType); builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { final String customText = customType.getText().toString().trim(); if (ContactsUtils.isGraphic(customText)) { // Now we're sure it's ok to actually change the type value. mType = mPendingType; mPendingType = null; mEntry.put(mKind.typeColumn, mType.rawValue); mEntry.put(mType.customColumn, customText); rebuildLabel(); if (!mFields.hasFocus()) mFields.requestFocus(); } } }); builder.setNegativeButton(android.R.string.cancel, null); return builder.create(); } /** * Prepare dialog for picking a new {@link EditType} or entering a * custom label. This dialog is limited to the valid types as determined * by {@link EntityModifier}. */ public Dialog createLabelDialog() { // Build list of valid types, including the current value final List<EditType> validTypes = EntityModifier.getValidTypes(mState, mKind, mType); // Wrap our context to inflate list items using correct theme final Context dialogContext = new ContextThemeWrapper(mContext, android.R.style.Theme_Light); final LayoutInflater dialogInflater = mInflater.cloneInContext(dialogContext); final ListAdapter typeAdapter = new ArrayAdapter<EditType>(mContext, RES_LABEL_ITEM, validTypes) { @Override public View getView(int position, View convertView, ViewGroup parent) { if (convertView == null) { convertView = dialogInflater.inflate(RES_LABEL_ITEM, parent, false); } final EditType type = this.getItem(position); final TextView textView = (TextView)convertView; textView.setText(type.labelRes); return textView; } }; final DialogInterface.OnClickListener clickListener = new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); final EditType selected = validTypes.get(which); if (selected.customColumn != null) { // Show custom label dialog if requested by type. // // Only when the custum value input in the next step is correct one. // this method also set the type value to what the user requested here. mPendingType = selected; createCustomDialog(selected).show(); } else { // User picked type, and we're sure it's ok to actually write the entry. mType = selected; mEntry.put(mKind.typeColumn, mType.rawValue); rebuildLabel(); if (!mFields.hasFocus()) mFields.requestFocus(); } } }; final AlertDialog.Builder builder = new AlertDialog.Builder(mContext); builder.setTitle(R.string.selectLabel); builder.setSingleChoiceItems(typeAdapter, 0, clickListener); return builder.create(); } /** {@inheritDoc} */ public void onClick(View v) { switch (v.getId()) { case R.id.edit_label: { if(mKind.mimeType != Event.CONTENT_ITEM_TYPE) { createLabelDialog().show(); } break; } case R.id.edit_delete: { // Keep around in model, but mark as deleted mEntry.markDeleted(); // Hide soft keyboard, if visible InputMethodManager inputMethodManager = (InputMethodManager) getContext() .getSystemService(Context.INPUT_METHOD_SERVICE); inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0); // Remove editor from parent view final ViewGroup parent = (ViewGroup)getParent(); Log.i(TAG, "parent :"+parent ); if(parent != null){ parent.removeView(this); } if (mListener != null) { // Notify listener when present mListener.onDeleted(this); } break; } case R.id.edit_more: case R.id.edit_less: { mHideOptional = !mHideOptional; rebuildValues(); break; } } } private static class SavedState extends BaseSavedState { public boolean mHideOptional; public int[] mVisibilities; SavedState(Parcelable superState) { super(superState); } private SavedState(Parcel in) { super(in); mVisibilities = new int[in.readInt()]; in.readIntArray(mVisibilities); } @Override public void writeToParcel(Parcel out, int flags) { super.writeToParcel(out, flags); out.writeInt(mVisibilities.length); out.writeIntArray(mVisibilities); } public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() { public SavedState createFromParcel(Parcel in) { return new SavedState(in); } public SavedState[] newArray(int size) { return new SavedState[size]; } }; } /** * Saves the visibility of the child EditTexts, and mHideOptional. */ @Override protected Parcelable onSaveInstanceState() { Parcelable superState = super.onSaveInstanceState(); SavedState ss = new SavedState(superState); ss.mHideOptional = mHideOptional; final int numChildren = mFields.getChildCount(); ss.mVisibilities = new int[numChildren]; for (int i = 0; i < numChildren; i++) { ss.mVisibilities[i] = mFields.getChildAt(i).getVisibility(); } return ss; } /** * Restores the visibility of the child EditTexts, and mHideOptional. */ @Override protected void onRestoreInstanceState(Parcelable state) { SavedState ss = (SavedState) state; super.onRestoreInstanceState(ss.getSuperState()); mHideOptional = ss.mHideOptional; int numChildren = Math.min(mFields.getChildCount(), ss.mVisibilities.length); for (int i = 0; i < numChildren; i++) { mFields.getChildAt(i).setVisibility(ss.mVisibilities[i]); } } }