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 android.content.Context;
     20 import android.database.Cursor;
     21 import android.os.Bundle;
     22 import android.os.Parcelable;
     23 import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
     24 import android.provider.ContactsContract.CommonDataKinds.Nickname;
     25 import android.provider.ContactsContract.CommonDataKinds.Photo;
     26 import android.provider.ContactsContract.CommonDataKinds.StructuredName;
     27 import android.provider.ContactsContract.Contacts;
     28 import android.provider.ContactsContract.Data;
     29 import android.text.TextUtils;
     30 import android.util.AttributeSet;
     31 import android.util.Pair;
     32 import android.view.LayoutInflater;
     33 import android.view.View;
     34 import android.view.ViewGroup;
     35 import android.widget.TextView;
     36 
     37 import com.android.contacts.GroupMetaDataLoader;
     38 import com.android.contacts.R;
     39 import com.android.contacts.common.model.account.AccountType;
     40 import com.android.contacts.common.model.account.AccountType.EditType;
     41 import com.android.contacts.common.model.dataitem.DataKind;
     42 import com.android.contacts.common.model.RawContactDelta;
     43 import com.android.contacts.common.model.ValuesDelta;
     44 import com.android.contacts.common.model.RawContactModifier;
     45 
     46 import com.google.common.base.Objects;
     47 
     48 import java.util.ArrayList;
     49 
     50 /**
     51  * Custom view that provides all the editor interaction for a specific
     52  * {@link Contacts} represented through an {@link RawContactDelta}. Callers can
     53  * reuse this view and quickly rebuild its contents through
     54  * {@link #setState(RawContactDelta, AccountType, ViewIdGenerator)}.
     55  * <p>
     56  * Internal updates are performed against {@link ValuesDelta} so that the
     57  * source {@link RawContact} can be swapped out. Any state-based changes, such as
     58  * adding {@link Data} rows or changing {@link EditType}, are performed through
     59  * {@link RawContactModifier} to ensure that {@link AccountType} are enforced.
     60  */
     61 public class RawContactEditorView extends BaseRawContactEditorView {
     62     private static final String KEY_SUPER_INSTANCE_STATE = "superInstanceState";
     63 
     64     private LayoutInflater mInflater;
     65 
     66     private StructuredNameEditorView mName;
     67     private PhoneticNameEditorView mPhoneticName;
     68     private TextFieldsEditorView mNickName;
     69 
     70     private GroupMembershipView mGroupMembershipView;
     71 
     72     private ViewGroup mFields;
     73 
     74     private View mAccountSelector;
     75     private TextView mAccountSelectorTypeTextView;
     76     private TextView mAccountSelectorNameTextView;
     77 
     78     private View mAccountHeader;
     79     private TextView mAccountHeaderTypeTextView;
     80     private TextView mAccountHeaderNameTextView;
     81 
     82     private long mRawContactId = -1;
     83     private boolean mAutoAddToDefaultGroup = true;
     84     private Cursor mGroupMetaData;
     85     private DataKind mGroupMembershipKind;
     86     private RawContactDelta mState;
     87 
     88     public RawContactEditorView(Context context) {
     89         super(context);
     90     }
     91 
     92     public RawContactEditorView(Context context, AttributeSet attrs) {
     93         super(context, attrs);
     94     }
     95 
     96     @Override
     97     public void setEnabled(boolean enabled) {
     98         super.setEnabled(enabled);
     99 
    100         View view = getPhotoEditor();
    101         if (view != null) {
    102             view.setEnabled(enabled);
    103         }
    104 
    105         if (mName != null) {
    106             mName.setEnabled(enabled);
    107         }
    108 
    109         if (mPhoneticName != null) {
    110             mPhoneticName.setEnabled(enabled);
    111         }
    112 
    113         if (mFields != null) {
    114             int count = mFields.getChildCount();
    115             for (int i = 0; i < count; i++) {
    116                 mFields.getChildAt(i).setEnabled(enabled);
    117             }
    118         }
    119 
    120         if (mGroupMembershipView != null) {
    121             mGroupMembershipView.setEnabled(enabled);
    122         }
    123     }
    124 
    125     @Override
    126     protected void onFinishInflate() {
    127         super.onFinishInflate();
    128 
    129         mInflater = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    130 
    131         mName = (StructuredNameEditorView)findViewById(R.id.edit_name);
    132         mName.setDeletable(false);
    133 
    134         mPhoneticName = (PhoneticNameEditorView)findViewById(R.id.edit_phonetic_name);
    135         mPhoneticName.setDeletable(false);
    136 
    137         mNickName = (TextFieldsEditorView)findViewById(R.id.edit_nick_name);
    138 
    139         mFields = (ViewGroup)findViewById(R.id.sect_fields);
    140 
    141         mAccountHeader = findViewById(R.id.account_header_container);
    142         mAccountHeaderTypeTextView = (TextView) findViewById(R.id.account_type);
    143         mAccountHeaderNameTextView = (TextView) findViewById(R.id.account_name);
    144 
    145         mAccountSelector = findViewById(R.id.account_selector_container);
    146         mAccountSelectorTypeTextView = (TextView) findViewById(R.id.account_type_selector);
    147         mAccountSelectorNameTextView = (TextView) findViewById(R.id.account_name_selector);
    148     }
    149 
    150     @Override
    151     protected Parcelable onSaveInstanceState() {
    152         Bundle bundle = new Bundle();
    153         // super implementation of onSaveInstanceState returns null
    154         bundle.putParcelable(KEY_SUPER_INSTANCE_STATE, super.onSaveInstanceState());
    155         return bundle;
    156     }
    157 
    158     @Override
    159     protected void onRestoreInstanceState(Parcelable state) {
    160         if (state instanceof Bundle) {
    161             Bundle bundle = (Bundle) state;
    162             super.onRestoreInstanceState(bundle.getParcelable(KEY_SUPER_INSTANCE_STATE));
    163             return;
    164         }
    165         super.onRestoreInstanceState(state);
    166     }
    167 
    168     /**
    169      * Set the internal state for this view, given a current
    170      * {@link RawContactDelta} state and the {@link AccountType} that
    171      * apply to that state.
    172      */
    173     @Override
    174     public void setState(RawContactDelta state, AccountType type, ViewIdGenerator vig,
    175             boolean isProfile) {
    176 
    177         mState = state;
    178 
    179         // Remove any existing sections
    180         mFields.removeAllViews();
    181 
    182         // Bail if invalid state or account type
    183         if (state == null || type == null) return;
    184 
    185         setId(vig.getId(state, null, null, ViewIdGenerator.NO_VIEW_INDEX));
    186 
    187         // Make sure we have a StructuredName
    188         RawContactModifier.ensureKindExists(state, type, StructuredName.CONTENT_ITEM_TYPE);
    189 
    190         mRawContactId = state.getRawContactId();
    191 
    192         // Fill in the account info
    193         final Pair<String,String> accountInfo = EditorUiUtils.getAccountInfo(getContext(),
    194                 isProfile, state.getAccountName(), type);
    195         if (accountInfo == null) {
    196             // Hide this view so the other text view will be centered vertically
    197             mAccountHeaderNameTextView.setVisibility(View.GONE);
    198         } else {
    199             if (accountInfo.first == null) {
    200                 mAccountHeaderNameTextView.setVisibility(View.GONE);
    201             } else {
    202                 mAccountHeaderNameTextView.setVisibility(View.VISIBLE);
    203                 mAccountHeaderNameTextView.setText(accountInfo.first);
    204             }
    205             mAccountHeaderTypeTextView.setText(accountInfo.second);
    206         }
    207         updateAccountHeaderContentDescription();
    208 
    209         // The account selector and header are both used to display the same information.
    210         mAccountSelectorTypeTextView.setText(mAccountHeaderTypeTextView.getText());
    211         mAccountSelectorTypeTextView.setVisibility(mAccountHeaderTypeTextView.getVisibility());
    212         mAccountSelectorNameTextView.setText(mAccountHeaderNameTextView.getText());
    213         mAccountSelectorNameTextView.setVisibility(mAccountHeaderNameTextView.getVisibility());
    214         // Showing the account header at the same time as the account selector drop down is
    215         // confusing. They should be mutually exclusive.
    216         mAccountHeader.setVisibility(mAccountSelector.getVisibility() == View.GONE
    217                 ? View.VISIBLE : View.GONE);
    218 
    219         // Show photo editor when supported
    220         RawContactModifier.ensureKindExists(state, type, Photo.CONTENT_ITEM_TYPE);
    221         setHasPhotoEditor((type.getKindForMimetype(Photo.CONTENT_ITEM_TYPE) != null));
    222         getPhotoEditor().setEnabled(isEnabled());
    223         mName.setEnabled(isEnabled());
    224 
    225         mPhoneticName.setEnabled(isEnabled());
    226 
    227         // Show and hide the appropriate views
    228         mFields.setVisibility(View.VISIBLE);
    229         mName.setVisibility(View.VISIBLE);
    230         mPhoneticName.setVisibility(View.VISIBLE);
    231 
    232         mGroupMembershipKind = type.getKindForMimetype(GroupMembership.CONTENT_ITEM_TYPE);
    233         if (mGroupMembershipKind != null) {
    234             mGroupMembershipView = (GroupMembershipView)mInflater.inflate(
    235                     R.layout.item_group_membership, mFields, false);
    236             mGroupMembershipView.setKind(mGroupMembershipKind);
    237             mGroupMembershipView.setEnabled(isEnabled());
    238         }
    239 
    240         // Create editor sections for each possible data kind
    241         for (DataKind kind : type.getSortedDataKinds()) {
    242             // Skip kind of not editable
    243             if (!kind.editable) continue;
    244 
    245             final String mimeType = kind.mimeType;
    246             if (StructuredName.CONTENT_ITEM_TYPE.equals(mimeType)) {
    247                 // Handle special case editor for structured name
    248                 final ValuesDelta primary = state.getPrimaryEntry(mimeType);
    249                 mName.setValues(
    250                         type.getKindForMimetype(DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME),
    251                         primary, state, false, vig);
    252                 mPhoneticName.setValues(
    253                         type.getKindForMimetype(DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME),
    254                         primary, state, false, vig);
    255                 // It is useful to use Nickname outside of a KindSectionView so that we can treat it
    256                 // as a part of StructuredName's fake KindSectionView, even though it uses a
    257                 // different CP2 mime-type. We do a bit of extra work below to make this possible.
    258                 final DataKind nickNameKind = type.getKindForMimetype(Nickname.CONTENT_ITEM_TYPE);
    259                 if (nickNameKind != null) {
    260                     ValuesDelta primaryNickNameEntry = state.getPrimaryEntry(nickNameKind.mimeType);
    261                     if (primaryNickNameEntry == null) {
    262                         primaryNickNameEntry = RawContactModifier.insertChild(state, nickNameKind);
    263                     }
    264                     mNickName.setValues(nickNameKind, primaryNickNameEntry, state, false, vig);
    265                     mNickName.setDeletable(false);
    266                 } else {
    267                     mPhoneticName.setPadding(0, 0, 0, (int) getResources().getDimension(
    268                             R.dimen.editor_padding_between_editor_views));
    269                     mNickName.setVisibility(View.GONE);
    270                 }
    271             } else if (Photo.CONTENT_ITEM_TYPE.equals(mimeType)) {
    272                 // Handle special case editor for photos
    273                 final ValuesDelta primary = state.getPrimaryEntry(mimeType);
    274                 getPhotoEditor().setValues(kind, primary, state, false, vig);
    275             } else if (GroupMembership.CONTENT_ITEM_TYPE.equals(mimeType)) {
    276                 if (mGroupMembershipView != null) {
    277                     mGroupMembershipView.setState(state);
    278                     mFields.addView(mGroupMembershipView);
    279                 }
    280             } else if (DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME.equals(mimeType)
    281                     || DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME.equals(mimeType)
    282                     || Nickname.CONTENT_ITEM_TYPE.equals(mimeType)) {
    283                 // Don't create fields for each of these mime-types. They are handled specially.
    284                 continue;
    285             } else {
    286                 // Otherwise use generic section-based editors
    287                 if (kind.fieldList == null) continue;
    288                 final KindSectionView section = (KindSectionView)mInflater.inflate(
    289                         R.layout.item_kind_section, mFields, false);
    290                 section.setShowOneEmptyEditor(true);
    291                 section.setEnabled(isEnabled());
    292                 section.setState(kind, state, /* readOnly =*/ false, vig);
    293                 mFields.addView(section);
    294             }
    295         }
    296 
    297         addToDefaultGroupIfNeeded();
    298     }
    299 
    300     @Override
    301     public void setGroupMetaData(Cursor groupMetaData) {
    302         mGroupMetaData = groupMetaData;
    303         addToDefaultGroupIfNeeded();
    304         if (mGroupMembershipView != null) {
    305             mGroupMembershipView.setGroupMetaData(groupMetaData);
    306         }
    307     }
    308 
    309     public void setAutoAddToDefaultGroup(boolean flag) {
    310         this.mAutoAddToDefaultGroup = flag;
    311     }
    312 
    313     /**
    314      * If automatic addition to the default group was requested (see
    315      * {@link #setAutoAddToDefaultGroup}, checks if the raw contact is in any
    316      * group and if it is not adds it to the default group (in case of Google
    317      * contacts that's "My Contacts").
    318      */
    319     private void addToDefaultGroupIfNeeded() {
    320         if (!mAutoAddToDefaultGroup || mGroupMetaData == null || mGroupMetaData.isClosed()
    321                 || mState == null) {
    322             return;
    323         }
    324 
    325         boolean hasGroupMembership = false;
    326         ArrayList<ValuesDelta> entries = mState.getMimeEntries(GroupMembership.CONTENT_ITEM_TYPE);
    327         if (entries != null) {
    328             for (ValuesDelta values : entries) {
    329                 Long id = values.getGroupRowId();
    330                 if (id != null && id.longValue() != 0) {
    331                     hasGroupMembership = true;
    332                     break;
    333                 }
    334             }
    335         }
    336 
    337         if (!hasGroupMembership) {
    338             long defaultGroupId = getDefaultGroupId();
    339             if (defaultGroupId != -1) {
    340                 ValuesDelta entry = RawContactModifier.insertChild(mState, mGroupMembershipKind);
    341                 if (entry != null) {
    342                     entry.setGroupRowId(defaultGroupId);
    343                 }
    344             }
    345         }
    346     }
    347 
    348     /**
    349      * Returns the default group (e.g. "My Contacts") for the current raw contact's
    350      * account.  Returns -1 if there is no such group.
    351      */
    352     private long getDefaultGroupId() {
    353         String accountType = mState.getAccountType();
    354         String accountName = mState.getAccountName();
    355         String accountDataSet = mState.getDataSet();
    356         mGroupMetaData.moveToPosition(-1);
    357         while (mGroupMetaData.moveToNext()) {
    358             String name = mGroupMetaData.getString(GroupMetaDataLoader.ACCOUNT_NAME);
    359             String type = mGroupMetaData.getString(GroupMetaDataLoader.ACCOUNT_TYPE);
    360             String dataSet = mGroupMetaData.getString(GroupMetaDataLoader.DATA_SET);
    361             if (name.equals(accountName) && type.equals(accountType)
    362                     && Objects.equal(dataSet, accountDataSet)) {
    363                 long groupId = mGroupMetaData.getLong(GroupMetaDataLoader.GROUP_ID);
    364                 if (!mGroupMetaData.isNull(GroupMetaDataLoader.AUTO_ADD)
    365                             && mGroupMetaData.getInt(GroupMetaDataLoader.AUTO_ADD) != 0) {
    366                     return groupId;
    367                 }
    368             }
    369         }
    370         return -1;
    371     }
    372 
    373     public StructuredNameEditorView getNameEditor() {
    374         return mName;
    375     }
    376 
    377     public TextFieldsEditorView getPhoneticNameEditor() {
    378         return mPhoneticName;
    379     }
    380 
    381     public TextFieldsEditorView getNickNameEditor() {
    382         return mNickName;
    383     }
    384 
    385     @Override
    386     public long getRawContactId() {
    387         return mRawContactId;
    388     }
    389 }
    390