blob: ca15bfa406c47fd7b0e562f5662d6ddd2c1866e6 [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 android.content.Context;
import android.database.Cursor;
import android.os.Bundle;
import android.os.Parcelable;
import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
import android.provider.ContactsContract.CommonDataKinds.Nickname;
import android.provider.ContactsContract.CommonDataKinds.Photo;
import android.provider.ContactsContract.CommonDataKinds.StructuredName;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Data;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.android.contacts.GroupMetaDataLoader;
import com.android.contacts.R;
import com.android.contacts.common.model.account.AccountType;
import com.android.contacts.common.model.account.AccountType.EditType;
import com.android.contacts.common.model.dataitem.DataKind;
import com.android.contacts.common.model.RawContactDelta;
import com.android.contacts.common.model.ValuesDelta;
import com.android.contacts.common.model.RawContactModifier;
import com.google.common.base.Objects;
import java.util.ArrayList;
/**
* Custom view that provides all the editor interaction for a specific
* {@link Contacts} represented through an {@link RawContactDelta}. Callers can
* reuse this view and quickly rebuild its contents through
* {@link #setState(RawContactDelta, AccountType, ViewIdGenerator)}.
* <p>
* Internal updates are performed against {@link ValuesDelta} so that the
* source {@link RawContact} can be swapped out. Any state-based changes, such as
* adding {@link Data} rows or changing {@link EditType}, are performed through
* {@link RawContactModifier} to ensure that {@link AccountType} are enforced.
*/
public class RawContactEditorView extends BaseRawContactEditorView {
private static final String KEY_SUPER_INSTANCE_STATE = "superInstanceState";
private LayoutInflater mInflater;
private StructuredNameEditorView mName;
private PhoneticNameEditorView mPhoneticName;
private TextFieldsEditorView mNickName;
private GroupMembershipView mGroupMembershipView;
private ViewGroup mFields;
private View mAccountSelector;
private TextView mAccountSelectorTypeTextView;
private TextView mAccountSelectorNameTextView;
private View mAccountHeader;
private TextView mAccountHeaderTypeTextView;
private TextView mAccountHeaderNameTextView;
private long mRawContactId = -1;
private boolean mAutoAddToDefaultGroup = true;
private Cursor mGroupMetaData;
private DataKind mGroupMembershipKind;
private RawContactDelta mState;
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);
}
}
@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);
mNickName = (TextFieldsEditorView)findViewById(R.id.edit_nick_name);
mFields = (ViewGroup)findViewById(R.id.sect_fields);
mAccountHeader = findViewById(R.id.account_header_container);
mAccountHeaderTypeTextView = (TextView) findViewById(R.id.account_type);
mAccountHeaderNameTextView = (TextView) findViewById(R.id.account_name);
mAccountSelector = findViewById(R.id.account_selector_container);
mAccountSelectorTypeTextView = (TextView) findViewById(R.id.account_type_selector);
mAccountSelectorNameTextView = (TextView) findViewById(R.id.account_name_selector);
}
@Override
protected Parcelable onSaveInstanceState() {
Bundle bundle = new Bundle();
// super implementation of onSaveInstanceState returns null
bundle.putParcelable(KEY_SUPER_INSTANCE_STATE, super.onSaveInstanceState());
return bundle;
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
if (state instanceof Bundle) {
Bundle bundle = (Bundle) state;
super.onRestoreInstanceState(bundle.getParcelable(KEY_SUPER_INSTANCE_STATE));
return;
}
super.onRestoreInstanceState(state);
}
/**
* Set the internal state for this view, given a current
* {@link RawContactDelta} state and the {@link AccountType} that
* apply to that state.
*/
@Override
public void setState(RawContactDelta 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
RawContactModifier.ensureKindExists(state, type, StructuredName.CONTENT_ITEM_TYPE);
mRawContactId = state.getRawContactId();
// Fill in the account info
if (isProfile) {
String accountName = state.getAccountName();
if (TextUtils.isEmpty(accountName)) {
mAccountHeaderNameTextView.setVisibility(View.GONE);
mAccountHeaderTypeTextView.setText(R.string.local_profile_title);
} else {
CharSequence accountType = type.getDisplayLabel(mContext);
mAccountHeaderTypeTextView.setText(mContext.getString(R.string.external_profile_title,
accountType));
mAccountHeaderNameTextView.setText(accountName);
}
} else {
String accountName = state.getAccountName();
CharSequence accountType = type.getDisplayLabel(mContext);
if (TextUtils.isEmpty(accountType)) {
accountType = mContext.getString(R.string.account_phone);
}
if (!TextUtils.isEmpty(accountName)) {
mAccountHeaderNameTextView.setVisibility(View.VISIBLE);
mAccountHeaderNameTextView.setText(
mContext.getString(R.string.from_account_format, accountName));
} else {
// Hide this view so the other text view will be centered vertically
mAccountHeaderNameTextView.setVisibility(View.GONE);
}
mAccountHeaderTypeTextView.setText(
mContext.getString(R.string.account_type_format, accountType));
}
updateAccountHeaderContentDescription();
// The account selector and header are both used to display the same information.
mAccountSelectorTypeTextView.setText(mAccountHeaderTypeTextView.getText());
mAccountSelectorTypeTextView.setVisibility(mAccountHeaderTypeTextView.getVisibility());
mAccountSelectorNameTextView.setText(mAccountHeaderNameTextView.getText());
mAccountSelectorNameTextView.setVisibility(mAccountHeaderNameTextView.getVisibility());
// Showing the account header at the same time as the account selector drop down is
// confusing. They should be mutually exclusive.
mAccountHeader.setVisibility(mAccountSelector.getVisibility() == View.GONE
? View.VISIBLE : View.GONE);
// Show photo editor when supported
RawContactModifier.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);
// It is useful to use Nickname outside of a KindSectionView so that we can treat it
// as a part of StructuredName's fake KindSectionView, even though it uses a
// different CP2 mime-type. We do a bit of extra work below to make this possible.
final DataKind nickNameKind = type.getKindForMimetype(Nickname.CONTENT_ITEM_TYPE);
if (nickNameKind != null) {
ValuesDelta primaryNickNameEntry = state.getPrimaryEntry(nickNameKind.mimeType);
if (primaryNickNameEntry == null) {
primaryNickNameEntry = RawContactModifier.insertChild(state, nickNameKind);
}
mNickName.setValues(nickNameKind, primaryNickNameEntry, state, false, vig);
mNickName.setDeletable(false);
} else {
mPhoneticName.setPadding(0, 0, 0, (int) getResources().getDimension(
R.dimen.editor_padding_between_editor_views));
mNickName.setVisibility(View.GONE);
}
} 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);
mFields.addView(mGroupMembershipView);
}
} else if (DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME.equals(mimeType)
|| DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME.equals(mimeType)
|| Nickname.CONTENT_ITEM_TYPE.equals(mimeType)) {
// Don't create fields for each of these mime-types. They are handled specially.
continue;
} 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);
}
}
addToDefaultGroupIfNeeded();
}
@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.getGroupRowId();
if (id != null && id.longValue() != 0) {
hasGroupMembership = true;
break;
}
}
}
if (!hasGroupMembership) {
long defaultGroupId = getDefaultGroupId();
if (defaultGroupId != -1) {
ValuesDelta entry = RawContactModifier.insertChild(mState, mGroupMembershipKind);
if (entry != null) {
entry.setGroupRowId(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.getAccountType();
String accountName = mState.getAccountName();
String accountDataSet = mState.getDataSet();
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 StructuredNameEditorView getNameEditor() {
return mName;
}
public TextFieldsEditorView getPhoneticNameEditor() {
return mPhoneticName;
}
public TextFieldsEditorView getNickNameEditor() {
return mNickName;
}
@Override
public long getRawContactId() {
return mRawContactId;
}
}