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