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