• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
RawContactEditorView(Context context)88     public RawContactEditorView(Context context) {
89         super(context);
90     }
91 
RawContactEditorView(Context context, AttributeSet attrs)92     public RawContactEditorView(Context context, AttributeSet attrs) {
93         super(context, attrs);
94     }
95 
96     @Override
setEnabled(boolean enabled)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
onFinishInflate()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
onSaveInstanceState()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
onRestoreInstanceState(Parcelable state)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
setState(RawContactDelta state, AccountType type, ViewIdGenerator vig, boolean isProfile)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
setGroupMetaData(Cursor groupMetaData)301     public void setGroupMetaData(Cursor groupMetaData) {
302         mGroupMetaData = groupMetaData;
303         addToDefaultGroupIfNeeded();
304         if (mGroupMembershipView != null) {
305             mGroupMembershipView.setGroupMetaData(groupMetaData);
306         }
307     }
308 
setAutoAddToDefaultGroup(boolean flag)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      */
addToDefaultGroupIfNeeded()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      */
getDefaultGroupId()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 
getNameEditor()373     public StructuredNameEditorView getNameEditor() {
374         return mName;
375     }
376 
getPhoneticNameEditor()377     public TextFieldsEditorView getPhoneticNameEditor() {
378         return mPhoneticName;
379     }
380 
getNickNameEditor()381     public TextFieldsEditorView getNickNameEditor() {
382         return mNickName;
383     }
384 
385     @Override
getRawContactId()386     public long getRawContactId() {
387         return mRawContactId;
388     }
389 }
390