blob: c9c6699e495a3ddcde5da3bc8f6aa4b1ae48b85f [file] [log] [blame]
/*
* 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.editor;
import com.android.contacts.GroupMetaDataLoader;
import com.android.contacts.R;
import com.android.contacts.model.AccountType;
import com.android.contacts.model.AccountType.EditType;
import com.android.contacts.model.DataKind;
import com.android.contacts.model.EntityDelta;
import com.android.contacts.model.EntityDelta.ValuesDelta;
import com.android.contacts.model.EntityModifier;
import com.android.internal.util.Objects;
import android.content.Context;
import android.content.Entity;
import android.database.Cursor;
import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
import android.provider.ContactsContract.CommonDataKinds.Organization;
import android.provider.ContactsContract.CommonDataKinds.Photo;
import android.provider.ContactsContract.CommonDataKinds.StructuredName;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.RawContacts;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.PopupMenu;
import android.widget.TextView;
import java.util.ArrayList;
/**
* Custom view that provides all the editor interaction for a specific
* {@link Contacts} represented through an {@link EntityDelta}. Callers can
* reuse this view and quickly rebuild its contents through
* {@link #setState(EntityDelta, AccountType, ViewIdGenerator)}.
* <p>
* Internal updates are performed against {@link ValuesDelta} so that the
* source {@link Entity} can be swapped out. Any state-based changes, such as
* adding {@link Data} rows or changing {@link EditType}, are performed through
* {@link EntityModifier} to ensure that {@link AccountType} are enforced.
*/
public class RawContactEditorView extends BaseRawContactEditorView {
private LayoutInflater mInflater;
private StructuredNameEditorView mName;
private PhoneticNameEditorView mPhoneticName;
private GroupMembershipView mGroupMembershipView;
private ViewGroup mFields;
private ImageView mAccountIcon;
private TextView mAccountTypeTextView;
private TextView mAccountNameTextView;
private Button mAddFieldButton;
private long mRawContactId = -1;
private boolean mAutoAddToDefaultGroup = true;
private Cursor mGroupMetaData;
private DataKind mGroupMembershipKind;
private EntityDelta mState;
private boolean mPhoneticNameAdded;
public RawContactEditorView(Context context) {
super(context);
}
public RawContactEditorView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
View view = getPhotoEditor();
if (view != null) {
view.setEnabled(enabled);
}
if (mName != null) {
mName.setEnabled(enabled);
}
if (mPhoneticName != null) {
mPhoneticName.setEnabled(enabled);
}
if (mFields != null) {
int count = mFields.getChildCount();
for (int i = 0; i < count; i++) {
mFields.getChildAt(i).setEnabled(enabled);
}
}
if (mGroupMembershipView != null) {
mGroupMembershipView.setEnabled(enabled);
}
mAddFieldButton.setEnabled(enabled);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mInflater = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mName = (StructuredNameEditorView)findViewById(R.id.edit_name);
mName.setDeletable(false);
mPhoneticName = (PhoneticNameEditorView)findViewById(R.id.edit_phonetic_name);
mPhoneticName.setDeletable(false);
mFields = (ViewGroup)findViewById(R.id.sect_fields);
mAccountIcon = (ImageView) findViewById(R.id.account_icon);
mAccountTypeTextView = (TextView) findViewById(R.id.account_type);
mAccountNameTextView = (TextView) findViewById(R.id.account_name);
mAddFieldButton = (Button) findViewById(R.id.button_add_field);
mAddFieldButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
showAddInformationPopupWindow();
}
});
}
/**
* Set the internal state for this view, given a current
* {@link EntityDelta} state and the {@link AccountType} that
* apply to that state.
*/
@Override
public void setState(EntityDelta state, AccountType type, ViewIdGenerator vig,
boolean isProfile) {
mState = state;
// Remove any existing sections
mFields.removeAllViews();
// Bail if invalid state or account type
if (state == null || type == null) return;
setId(vig.getId(state, null, null, ViewIdGenerator.NO_VIEW_INDEX));
// Make sure we have a StructuredName and Organization
EntityModifier.ensureKindExists(state, type, StructuredName.CONTENT_ITEM_TYPE);
EntityModifier.ensureKindExists(state, type, Organization.CONTENT_ITEM_TYPE);
ValuesDelta values = state.getValues();
mRawContactId = values.getAsLong(RawContacts._ID);
// Fill in the account info
if (isProfile) {
String accountName = values.getAsString(RawContacts.ACCOUNT_NAME);
if (TextUtils.isEmpty(accountName)) {
mAccountNameTextView.setVisibility(View.GONE);
mAccountTypeTextView.setText(R.string.local_profile_title);
} else {
CharSequence accountType = type.getDisplayLabel(mContext);
mAccountTypeTextView.setText(mContext.getString(R.string.external_profile_title,
accountType));
mAccountNameTextView.setText(accountName);
}
} else {
String accountName = values.getAsString(RawContacts.ACCOUNT_NAME);
CharSequence accountType = type.getDisplayLabel(mContext);
if (TextUtils.isEmpty(accountType)) {
accountType = mContext.getString(R.string.account_phone);
}
if (!TextUtils.isEmpty(accountName)) {
mAccountNameTextView.setVisibility(View.VISIBLE);
mAccountNameTextView.setText(
mContext.getString(R.string.from_account_format, accountName));
} else {
// Hide this view so the other text view will be centered vertically
mAccountNameTextView.setVisibility(View.GONE);
}
mAccountTypeTextView.setText(
mContext.getString(R.string.account_type_format, accountType));
}
mAccountIcon.setImageDrawable(type.getDisplayIcon(mContext));
// Show photo editor when supported
EntityModifier.ensureKindExists(state, type, Photo.CONTENT_ITEM_TYPE);
setHasPhotoEditor((type.getKindForMimetype(Photo.CONTENT_ITEM_TYPE) != null));
getPhotoEditor().setEnabled(isEnabled());
mName.setEnabled(isEnabled());
mPhoneticName.setEnabled(isEnabled());
// Show and hide the appropriate views
mFields.setVisibility(View.VISIBLE);
mName.setVisibility(View.VISIBLE);
mPhoneticName.setVisibility(View.VISIBLE);
mGroupMembershipKind = type.getKindForMimetype(GroupMembership.CONTENT_ITEM_TYPE);
if (mGroupMembershipKind != null) {
mGroupMembershipView = (GroupMembershipView)mInflater.inflate(
R.layout.item_group_membership, mFields, false);
mGroupMembershipView.setKind(mGroupMembershipKind);
mGroupMembershipView.setEnabled(isEnabled());
}
// Create editor sections for each possible data kind
for (DataKind kind : type.getSortedDataKinds()) {
// Skip kind of not editable
if (!kind.editable) continue;
final String mimeType = kind.mimeType;
if (StructuredName.CONTENT_ITEM_TYPE.equals(mimeType)) {
// Handle special case editor for structured name
final ValuesDelta primary = state.getPrimaryEntry(mimeType);
mName.setValues(
type.getKindForMimetype(DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME),
primary, state, false, vig);
mPhoneticName.setValues(
type.getKindForMimetype(DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME),
primary, state, false, vig);
} else if (Photo.CONTENT_ITEM_TYPE.equals(mimeType)) {
// Handle special case editor for photos
final ValuesDelta primary = state.getPrimaryEntry(mimeType);
getPhotoEditor().setValues(kind, primary, state, false, vig);
} else if (GroupMembership.CONTENT_ITEM_TYPE.equals(mimeType)) {
if (mGroupMembershipView != null) {
mGroupMembershipView.setState(state);
}
} else if (Organization.CONTENT_ITEM_TYPE.equals(mimeType)) {
// Create the organization section
final KindSectionView section = (KindSectionView) mInflater.inflate(
R.layout.item_kind_section, mFields, false);
section.setTitleVisible(false);
section.setEnabled(isEnabled());
section.setState(kind, state, false, vig);
// If there is organization info for the contact already, display it
if (!section.isEmpty()) {
mFields.addView(section);
} else {
// Otherwise provide the user with an "add organization" button that shows the
// EditText fields only when clicked
final View organizationView = mInflater.inflate(
R.layout.organization_editor_view_switcher, mFields, false);
final View addOrganizationButton = organizationView.findViewById(
R.id.add_organization_button);
final ViewGroup organizationSectionViewContainer =
(ViewGroup) organizationView.findViewById(R.id.container);
organizationSectionViewContainer.addView(section);
// Setup the click listener for the "add organization" button
addOrganizationButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// Once the user expands the organization field, the user cannot
// collapse them again.
addOrganizationButton.setVisibility(View.GONE);
organizationSectionViewContainer.setVisibility(View.VISIBLE);
organizationSectionViewContainer.requestFocus();
}
});
mFields.addView(organizationView);
}
} else {
// Otherwise use generic section-based editors
if (kind.fieldList == null) continue;
final KindSectionView section = (KindSectionView)mInflater.inflate(
R.layout.item_kind_section, mFields, false);
section.setEnabled(isEnabled());
section.setState(kind, state, false, vig);
mFields.addView(section);
}
}
if (mGroupMembershipView != null) {
mFields.addView(mGroupMembershipView);
}
updatePhoneticNameVisibility();
addToDefaultGroupIfNeeded();
mAddFieldButton.setEnabled(isEnabled());
}
@Override
public void setGroupMetaData(Cursor groupMetaData) {
mGroupMetaData = groupMetaData;
addToDefaultGroupIfNeeded();
if (mGroupMembershipView != null) {
mGroupMembershipView.setGroupMetaData(groupMetaData);
}
}
public void setAutoAddToDefaultGroup(boolean flag) {
this.mAutoAddToDefaultGroup = flag;
}
/**
* If automatic addition to the default group was requested (see
* {@link #setAutoAddToDefaultGroup}, checks if the raw contact is in any
* group and if it is not adds it to the default group (in case of Google
* contacts that's "My Contacts").
*/
private void addToDefaultGroupIfNeeded() {
if (!mAutoAddToDefaultGroup || mGroupMetaData == null || mGroupMetaData.isClosed()
|| mState == null) {
return;
}
boolean hasGroupMembership = false;
ArrayList<ValuesDelta> entries = mState.getMimeEntries(GroupMembership.CONTENT_ITEM_TYPE);
if (entries != null) {
for (ValuesDelta values : entries) {
Long id = values.getAsLong(GroupMembership.GROUP_ROW_ID);
if (id != null && id.longValue() != 0) {
hasGroupMembership = true;
break;
}
}
}
if (!hasGroupMembership) {
long defaultGroupId = getDefaultGroupId();
if (defaultGroupId != -1) {
ValuesDelta entry = EntityModifier.insertChild(mState, mGroupMembershipKind);
entry.put(GroupMembership.GROUP_ROW_ID, defaultGroupId);
}
}
}
/**
* Returns the default group (e.g. "My Contacts") for the current raw contact's
* account. Returns -1 if there is no such group.
*/
private long getDefaultGroupId() {
String accountType = mState.getValues().getAsString(RawContacts.ACCOUNT_TYPE);
String accountName = mState.getValues().getAsString(RawContacts.ACCOUNT_NAME);
String accountDataSet = mState.getValues().getAsString(RawContacts.DATA_SET);
mGroupMetaData.moveToPosition(-1);
while (mGroupMetaData.moveToNext()) {
String name = mGroupMetaData.getString(GroupMetaDataLoader.ACCOUNT_NAME);
String type = mGroupMetaData.getString(GroupMetaDataLoader.ACCOUNT_TYPE);
String dataSet = mGroupMetaData.getString(GroupMetaDataLoader.DATA_SET);
if (name.equals(accountName) && type.equals(accountType)
&& Objects.equal(dataSet, accountDataSet)) {
long groupId = mGroupMetaData.getLong(GroupMetaDataLoader.GROUP_ID);
if (!mGroupMetaData.isNull(GroupMetaDataLoader.AUTO_ADD)
&& mGroupMetaData.getInt(GroupMetaDataLoader.AUTO_ADD) != 0) {
return groupId;
}
}
}
return -1;
}
public TextFieldsEditorView getNameEditor() {
return mName;
}
public TextFieldsEditorView getPhoneticNameEditor() {
return mPhoneticName;
}
private void updatePhoneticNameVisibility() {
boolean showByDefault =
getContext().getResources().getBoolean(R.bool.config_editor_include_phonetic_name);
if (showByDefault || mPhoneticName.hasData() || mPhoneticNameAdded) {
mPhoneticName.setVisibility(View.VISIBLE);
} else {
mPhoneticName.setVisibility(View.GONE);
}
}
@Override
public long getRawContactId() {
return mRawContactId;
}
private void showAddInformationPopupWindow() {
final ArrayList<KindSectionView> fields =
new ArrayList<KindSectionView>(mFields.getChildCount());
final PopupMenu popupMenu = new PopupMenu(getContext(), mAddFieldButton);
final Menu menu = popupMenu.getMenu();
for (int i = 0; i < mFields.getChildCount(); i++) {
View child = mFields.getChildAt(i);
if (child instanceof KindSectionView) {
final KindSectionView sectionView = (KindSectionView) child;
// If the section is already visible (has 1 or more editors), then don't offer the
// option to add this type of field in the popup menu
if (sectionView.getEditorCount() > 0) {
continue;
}
DataKind kind = sectionView.getKind();
// not a list and already exists? ignore
if ((kind.typeOverallMax == 1) && sectionView.getEditorCount() != 0) {
continue;
}
if (DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME.equals(kind.mimeType)) {
continue;
}
if (DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME.equals(kind.mimeType)
&& mPhoneticName.getVisibility() == View.VISIBLE) {
continue;
}
menu.add(Menu.NONE, fields.size(), Menu.NONE, sectionView.getTitle());
fields.add(sectionView);
}
}
popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
final KindSectionView view = fields.get(item.getItemId());
if (DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME.equals(view.getKind().mimeType)) {
mPhoneticNameAdded = true;
updatePhoneticNameVisibility();
} else {
view.addItem();
}
return true;
}
});
popupMenu.show();
}
}