/*
* 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]);
}
}
}