Home | History | Annotate | Download | only in editor
      1 /*
      2  * Copyright (C) 2009 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.contacts.editor;
     18 
     19 import com.android.contacts.GroupMetaDataLoader;
     20 import com.android.contacts.R;
     21 import com.android.contacts.model.AccountType;
     22 import com.android.contacts.model.AccountType.EditType;
     23 import com.android.contacts.model.DataKind;
     24 import com.android.contacts.model.EntityDelta;
     25 import com.android.contacts.model.EntityDelta.ValuesDelta;
     26 import com.android.contacts.model.EntityModifier;
     27 import com.android.internal.util.Objects;
     28 
     29 import android.content.Context;
     30 import android.content.Entity;
     31 import android.database.Cursor;
     32 import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
     33 import android.provider.ContactsContract.CommonDataKinds.Organization;
     34 import android.provider.ContactsContract.CommonDataKinds.Photo;
     35 import android.provider.ContactsContract.CommonDataKinds.StructuredName;
     36 import android.provider.ContactsContract.Contacts;
     37 import android.provider.ContactsContract.Data;
     38 import android.provider.ContactsContract.RawContacts;
     39 import android.text.TextUtils;
     40 import android.util.AttributeSet;
     41 import android.view.LayoutInflater;
     42 import android.view.Menu;
     43 import android.view.MenuItem;
     44 import android.view.View;
     45 import android.view.ViewGroup;
     46 import android.widget.Button;
     47 import android.widget.ImageView;
     48 import android.widget.PopupMenu;
     49 import android.widget.TextView;
     50 
     51 import java.util.ArrayList;
     52 
     53 /**
     54  * Custom view that provides all the editor interaction for a specific
     55  * {@link Contacts} represented through an {@link EntityDelta}. Callers can
     56  * reuse this view and quickly rebuild its contents through
     57  * {@link #setState(EntityDelta, AccountType, ViewIdGenerator)}.
     58  * <p>
     59  * Internal updates are performed against {@link ValuesDelta} so that the
     60  * source {@link Entity} can be swapped out. Any state-based changes, such as
     61  * adding {@link Data} rows or changing {@link EditType}, are performed through
     62  * {@link EntityModifier} to ensure that {@link AccountType} are enforced.
     63  */
     64 public class RawContactEditorView extends BaseRawContactEditorView {
     65     private LayoutInflater mInflater;
     66 
     67     private StructuredNameEditorView mName;
     68     private PhoneticNameEditorView mPhoneticName;
     69     private GroupMembershipView mGroupMembershipView;
     70 
     71     private ViewGroup mFields;
     72 
     73     private ImageView mAccountIcon;
     74     private TextView mAccountTypeTextView;
     75     private TextView mAccountNameTextView;
     76 
     77     private Button mAddFieldButton;
     78 
     79     private long mRawContactId = -1;
     80     private boolean mAutoAddToDefaultGroup = true;
     81     private Cursor mGroupMetaData;
     82     private DataKind mGroupMembershipKind;
     83     private EntityDelta mState;
     84 
     85     private boolean mPhoneticNameAdded;
     86 
     87     public RawContactEditorView(Context context) {
     88         super(context);
     89     }
     90 
     91     public RawContactEditorView(Context context, AttributeSet attrs) {
     92         super(context, attrs);
     93     }
     94 
     95     @Override
     96     public void setEnabled(boolean enabled) {
     97         super.setEnabled(enabled);
     98 
     99         View view = getPhotoEditor();
    100         if (view != null) {
    101             view.setEnabled(enabled);
    102         }
    103 
    104         if (mName != null) {
    105             mName.setEnabled(enabled);
    106         }
    107 
    108         if (mPhoneticName != null) {
    109             mPhoneticName.setEnabled(enabled);
    110         }
    111 
    112         if (mFields != null) {
    113             int count = mFields.getChildCount();
    114             for (int i = 0; i < count; i++) {
    115                 mFields.getChildAt(i).setEnabled(enabled);
    116             }
    117         }
    118 
    119         if (mGroupMembershipView != null) {
    120             mGroupMembershipView.setEnabled(enabled);
    121         }
    122 
    123         mAddFieldButton.setEnabled(enabled);
    124     }
    125 
    126     @Override
    127     protected void onFinishInflate() {
    128         super.onFinishInflate();
    129 
    130         mInflater = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    131 
    132         mName = (StructuredNameEditorView)findViewById(R.id.edit_name);
    133         mName.setDeletable(false);
    134 
    135         mPhoneticName = (PhoneticNameEditorView)findViewById(R.id.edit_phonetic_name);
    136         mPhoneticName.setDeletable(false);
    137 
    138         mFields = (ViewGroup)findViewById(R.id.sect_fields);
    139 
    140         mAccountIcon = (ImageView) findViewById(R.id.account_icon);
    141         mAccountTypeTextView = (TextView) findViewById(R.id.account_type);
    142         mAccountNameTextView = (TextView) findViewById(R.id.account_name);
    143 
    144         mAddFieldButton = (Button) findViewById(R.id.button_add_field);
    145         mAddFieldButton.setOnClickListener(new OnClickListener() {
    146             @Override
    147             public void onClick(View v) {
    148                 showAddInformationPopupWindow();
    149             }
    150         });
    151     }
    152 
    153     /**
    154      * Set the internal state for this view, given a current
    155      * {@link EntityDelta} state and the {@link AccountType} that
    156      * apply to that state.
    157      */
    158     @Override
    159     public void setState(EntityDelta state, AccountType type, ViewIdGenerator vig,
    160             boolean isProfile) {
    161 
    162         mState = state;
    163 
    164         // Remove any existing sections
    165         mFields.removeAllViews();
    166 
    167         // Bail if invalid state or account type
    168         if (state == null || type == null) return;
    169 
    170         setId(vig.getId(state, null, null, ViewIdGenerator.NO_VIEW_INDEX));
    171 
    172         // Make sure we have a StructuredName and Organization
    173         EntityModifier.ensureKindExists(state, type, StructuredName.CONTENT_ITEM_TYPE);
    174         EntityModifier.ensureKindExists(state, type, Organization.CONTENT_ITEM_TYPE);
    175 
    176         ValuesDelta values = state.getValues();
    177         mRawContactId = values.getAsLong(RawContacts._ID);
    178 
    179         // Fill in the account info
    180         if (isProfile) {
    181             String accountName = values.getAsString(RawContacts.ACCOUNT_NAME);
    182             if (TextUtils.isEmpty(accountName)) {
    183                 mAccountNameTextView.setVisibility(View.GONE);
    184                 mAccountTypeTextView.setText(R.string.local_profile_title);
    185             } else {
    186                 CharSequence accountType = type.getDisplayLabel(mContext);
    187                 mAccountTypeTextView.setText(mContext.getString(R.string.external_profile_title,
    188                         accountType));
    189                 mAccountNameTextView.setText(accountName);
    190             }
    191         } else {
    192             String accountName = values.getAsString(RawContacts.ACCOUNT_NAME);
    193             CharSequence accountType = type.getDisplayLabel(mContext);
    194             if (TextUtils.isEmpty(accountType)) {
    195                 accountType = mContext.getString(R.string.account_phone);
    196             }
    197             if (!TextUtils.isEmpty(accountName)) {
    198                 mAccountNameTextView.setVisibility(View.VISIBLE);
    199                 mAccountNameTextView.setText(
    200                         mContext.getString(R.string.from_account_format, accountName));
    201             } else {
    202                 // Hide this view so the other text view will be centered vertically
    203                 mAccountNameTextView.setVisibility(View.GONE);
    204             }
    205             mAccountTypeTextView.setText(
    206                     mContext.getString(R.string.account_type_format, accountType));
    207         }
    208         mAccountIcon.setImageDrawable(type.getDisplayIcon(mContext));
    209 
    210         // Show photo editor when supported
    211         EntityModifier.ensureKindExists(state, type, Photo.CONTENT_ITEM_TYPE);
    212         setHasPhotoEditor((type.getKindForMimetype(Photo.CONTENT_ITEM_TYPE) != null));
    213         getPhotoEditor().setEnabled(isEnabled());
    214         mName.setEnabled(isEnabled());
    215 
    216         mPhoneticName.setEnabled(isEnabled());
    217 
    218         // Show and hide the appropriate views
    219         mFields.setVisibility(View.VISIBLE);
    220         mName.setVisibility(View.VISIBLE);
    221         mPhoneticName.setVisibility(View.VISIBLE);
    222 
    223         mGroupMembershipKind = type.getKindForMimetype(GroupMembership.CONTENT_ITEM_TYPE);
    224         if (mGroupMembershipKind != null) {
    225             mGroupMembershipView = (GroupMembershipView)mInflater.inflate(
    226                     R.layout.item_group_membership, mFields, false);
    227             mGroupMembershipView.setKind(mGroupMembershipKind);
    228             mGroupMembershipView.setEnabled(isEnabled());
    229         }
    230 
    231         // Create editor sections for each possible data kind
    232         for (DataKind kind : type.getSortedDataKinds()) {
    233             // Skip kind of not editable
    234             if (!kind.editable) continue;
    235 
    236             final String mimeType = kind.mimeType;
    237             if (StructuredName.CONTENT_ITEM_TYPE.equals(mimeType)) {
    238                 // Handle special case editor for structured name
    239                 final ValuesDelta primary = state.getPrimaryEntry(mimeType);
    240                 mName.setValues(
    241                         type.getKindForMimetype(DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME),
    242                         primary, state, false, vig);
    243                 mPhoneticName.setValues(
    244                         type.getKindForMimetype(DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME),
    245                         primary, state, false, vig);
    246             } else if (Photo.CONTENT_ITEM_TYPE.equals(mimeType)) {
    247                 // Handle special case editor for photos
    248                 final ValuesDelta primary = state.getPrimaryEntry(mimeType);
    249                 getPhotoEditor().setValues(kind, primary, state, false, vig);
    250             } else if (GroupMembership.CONTENT_ITEM_TYPE.equals(mimeType)) {
    251                 if (mGroupMembershipView != null) {
    252                     mGroupMembershipView.setState(state);
    253                 }
    254             } else if (Organization.CONTENT_ITEM_TYPE.equals(mimeType)) {
    255                 // Create the organization section
    256                 final KindSectionView section = (KindSectionView) mInflater.inflate(
    257                         R.layout.item_kind_section, mFields, false);
    258                 section.setTitleVisible(false);
    259                 section.setEnabled(isEnabled());
    260                 section.setState(kind, state, false, vig);
    261 
    262                 // If there is organization info for the contact already, display it
    263                 if (!section.isEmpty()) {
    264                     mFields.addView(section);
    265                 } else {
    266                     // Otherwise provide the user with an "add organization" button that shows the
    267                     // EditText fields only when clicked
    268                     final View organizationView = mInflater.inflate(
    269                             R.layout.organization_editor_view_switcher, mFields, false);
    270                     final View addOrganizationButton = organizationView.findViewById(
    271                             R.id.add_organization_button);
    272                     final ViewGroup organizationSectionViewContainer =
    273                             (ViewGroup) organizationView.findViewById(R.id.container);
    274 
    275                     organizationSectionViewContainer.addView(section);
    276 
    277                     // Setup the click listener for the "add organization" button
    278                     addOrganizationButton.setOnClickListener(new OnClickListener() {
    279                         @Override
    280                         public void onClick(View v) {
    281                             // Once the user expands the organization field, the user cannot
    282                             // collapse them again.
    283                             EditorAnimator.getInstance().expandOrganization(addOrganizationButton,
    284                                     organizationSectionViewContainer);
    285                         }
    286                     });
    287 
    288                     mFields.addView(organizationView);
    289                 }
    290             } else {
    291                 // Otherwise use generic section-based editors
    292                 if (kind.fieldList == null) continue;
    293                 final KindSectionView section = (KindSectionView)mInflater.inflate(
    294                         R.layout.item_kind_section, mFields, false);
    295                 section.setEnabled(isEnabled());
    296                 section.setState(kind, state, false, vig);
    297                 mFields.addView(section);
    298             }
    299         }
    300 
    301         if (mGroupMembershipView != null) {
    302             mFields.addView(mGroupMembershipView);
    303         }
    304 
    305         updatePhoneticNameVisibility();
    306 
    307         addToDefaultGroupIfNeeded();
    308 
    309 
    310         final int sectionCount = getSectionViewsWithoutFields().size();
    311         mAddFieldButton.setVisibility(sectionCount > 0 ? View.VISIBLE : View.GONE);
    312         mAddFieldButton.setEnabled(isEnabled());
    313     }
    314 
    315     @Override
    316     public void setGroupMetaData(Cursor groupMetaData) {
    317         mGroupMetaData = groupMetaData;
    318         addToDefaultGroupIfNeeded();
    319         if (mGroupMembershipView != null) {
    320             mGroupMembershipView.setGroupMetaData(groupMetaData);
    321         }
    322     }
    323 
    324     public void setAutoAddToDefaultGroup(boolean flag) {
    325         this.mAutoAddToDefaultGroup = flag;
    326     }
    327 
    328     /**
    329      * If automatic addition to the default group was requested (see
    330      * {@link #setAutoAddToDefaultGroup}, checks if the raw contact is in any
    331      * group and if it is not adds it to the default group (in case of Google
    332      * contacts that's "My Contacts").
    333      */
    334     private void addToDefaultGroupIfNeeded() {
    335         if (!mAutoAddToDefaultGroup || mGroupMetaData == null || mGroupMetaData.isClosed()
    336                 || mState == null) {
    337             return;
    338         }
    339 
    340         boolean hasGroupMembership = false;
    341         ArrayList<ValuesDelta> entries = mState.getMimeEntries(GroupMembership.CONTENT_ITEM_TYPE);
    342         if (entries != null) {
    343             for (ValuesDelta values : entries) {
    344                 Long id = values.getAsLong(GroupMembership.GROUP_ROW_ID);
    345                 if (id != null && id.longValue() != 0) {
    346                     hasGroupMembership = true;
    347                     break;
    348                 }
    349             }
    350         }
    351 
    352         if (!hasGroupMembership) {
    353             long defaultGroupId = getDefaultGroupId();
    354             if (defaultGroupId != -1) {
    355                 ValuesDelta entry = EntityModifier.insertChild(mState, mGroupMembershipKind);
    356                 entry.put(GroupMembership.GROUP_ROW_ID, defaultGroupId);
    357             }
    358         }
    359     }
    360 
    361     /**
    362      * Returns the default group (e.g. "My Contacts") for the current raw contact's
    363      * account.  Returns -1 if there is no such group.
    364      */
    365     private long getDefaultGroupId() {
    366         String accountType = mState.getValues().getAsString(RawContacts.ACCOUNT_TYPE);
    367         String accountName = mState.getValues().getAsString(RawContacts.ACCOUNT_NAME);
    368         String accountDataSet = mState.getValues().getAsString(RawContacts.DATA_SET);
    369         mGroupMetaData.moveToPosition(-1);
    370         while (mGroupMetaData.moveToNext()) {
    371             String name = mGroupMetaData.getString(GroupMetaDataLoader.ACCOUNT_NAME);
    372             String type = mGroupMetaData.getString(GroupMetaDataLoader.ACCOUNT_TYPE);
    373             String dataSet = mGroupMetaData.getString(GroupMetaDataLoader.DATA_SET);
    374             if (name.equals(accountName) && type.equals(accountType)
    375                     && Objects.equal(dataSet, accountDataSet)) {
    376                 long groupId = mGroupMetaData.getLong(GroupMetaDataLoader.GROUP_ID);
    377                 if (!mGroupMetaData.isNull(GroupMetaDataLoader.AUTO_ADD)
    378                             && mGroupMetaData.getInt(GroupMetaDataLoader.AUTO_ADD) != 0) {
    379                     return groupId;
    380                 }
    381             }
    382         }
    383         return -1;
    384     }
    385 
    386     public TextFieldsEditorView getNameEditor() {
    387         return mName;
    388     }
    389 
    390     public TextFieldsEditorView getPhoneticNameEditor() {
    391         return mPhoneticName;
    392     }
    393 
    394     private void updatePhoneticNameVisibility() {
    395         boolean showByDefault =
    396                 getContext().getResources().getBoolean(R.bool.config_editor_include_phonetic_name);
    397 
    398         if (showByDefault || mPhoneticName.hasData() || mPhoneticNameAdded) {
    399             mPhoneticName.setVisibility(View.VISIBLE);
    400         } else {
    401             mPhoneticName.setVisibility(View.GONE);
    402         }
    403     }
    404 
    405     @Override
    406     public long getRawContactId() {
    407         return mRawContactId;
    408     }
    409 
    410     /**
    411      * Return a list of KindSectionViews that have no fields yet...
    412      * these are candidates to have fields added in
    413      * {@link #showAddInformationPopupWindow()}
    414      */
    415     private ArrayList<KindSectionView> getSectionViewsWithoutFields() {
    416         final ArrayList<KindSectionView> fields =
    417                 new ArrayList<KindSectionView>(mFields.getChildCount());
    418         for (int i = 0; i < mFields.getChildCount(); i++) {
    419             View child = mFields.getChildAt(i);
    420             if (child instanceof KindSectionView) {
    421                 final KindSectionView sectionView = (KindSectionView) child;
    422                 // If the section is already visible (has 1 or more editors), then don't offer the
    423                 // option to add this type of field in the popup menu
    424                 if (sectionView.getEditorCount() > 0) {
    425                     continue;
    426                 }
    427                 DataKind kind = sectionView.getKind();
    428                 // not a list and already exists? ignore
    429                 if ((kind.typeOverallMax == 1) && sectionView.getEditorCount() != 0) {
    430                     continue;
    431                 }
    432                 if (DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME.equals(kind.mimeType)) {
    433                     continue;
    434                 }
    435 
    436                 if (DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME.equals(kind.mimeType)
    437                         && mPhoneticName.getVisibility() == View.VISIBLE) {
    438                     continue;
    439                 }
    440 
    441                 fields.add(sectionView);
    442             }
    443         }
    444         return fields;
    445     }
    446 
    447     private void showAddInformationPopupWindow() {
    448         final ArrayList<KindSectionView> fields = getSectionViewsWithoutFields();
    449         final PopupMenu popupMenu = new PopupMenu(getContext(), mAddFieldButton);
    450         final Menu menu = popupMenu.getMenu();
    451         for (int i = 0; i < fields.size(); i++) {
    452             menu.add(Menu.NONE, i, Menu.NONE, fields.get(i).getTitle());
    453         }
    454 
    455         popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
    456             @Override
    457             public boolean onMenuItemClick(MenuItem item) {
    458                 final KindSectionView view = fields.get(item.getItemId());
    459                 if (DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME.equals(view.getKind().mimeType)) {
    460                     mPhoneticNameAdded = true;
    461                     updatePhoneticNameVisibility();
    462                 } else {
    463                     view.addItem();
    464                 }
    465 
    466                 // If this was the last section without an entry, we just added one, and therefore
    467                 // there's no reason to show the button.
    468                 if (fields.size() == 1) {
    469                     mAddFieldButton.setVisibility(View.GONE);
    470                 }
    471 
    472                 return true;
    473             }
    474         });
    475 
    476         popupMenu.show();
    477     }
    478 }
    479