• 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 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 
RawContactEditorView(Context context)87     public RawContactEditorView(Context context) {
88         super(context);
89     }
90 
RawContactEditorView(Context context, AttributeSet attrs)91     public RawContactEditorView(Context context, AttributeSet attrs) {
92         super(context, attrs);
93     }
94 
95     @Override
setEnabled(boolean enabled)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
onFinishInflate()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
setState(EntityDelta state, AccountType type, ViewIdGenerator vig, boolean isProfile)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                             addOrganizationButton.setVisibility(View.GONE);
284                             organizationSectionViewContainer.setVisibility(View.VISIBLE);
285                             organizationSectionViewContainer.requestFocus();
286                         }
287                     });
288 
289                     mFields.addView(organizationView);
290                 }
291             } else {
292                 // Otherwise use generic section-based editors
293                 if (kind.fieldList == null) continue;
294                 final KindSectionView section = (KindSectionView)mInflater.inflate(
295                         R.layout.item_kind_section, mFields, false);
296                 section.setEnabled(isEnabled());
297                 section.setState(kind, state, false, vig);
298                 mFields.addView(section);
299             }
300         }
301 
302         if (mGroupMembershipView != null) {
303             mFields.addView(mGroupMembershipView);
304         }
305 
306         updatePhoneticNameVisibility();
307 
308         addToDefaultGroupIfNeeded();
309 
310         mAddFieldButton.setEnabled(isEnabled());
311     }
312 
313     @Override
setGroupMetaData(Cursor groupMetaData)314     public void setGroupMetaData(Cursor groupMetaData) {
315         mGroupMetaData = groupMetaData;
316         addToDefaultGroupIfNeeded();
317         if (mGroupMembershipView != null) {
318             mGroupMembershipView.setGroupMetaData(groupMetaData);
319         }
320     }
321 
setAutoAddToDefaultGroup(boolean flag)322     public void setAutoAddToDefaultGroup(boolean flag) {
323         this.mAutoAddToDefaultGroup = flag;
324     }
325 
326     /**
327      * If automatic addition to the default group was requested (see
328      * {@link #setAutoAddToDefaultGroup}, checks if the raw contact is in any
329      * group and if it is not adds it to the default group (in case of Google
330      * contacts that's "My Contacts").
331      */
addToDefaultGroupIfNeeded()332     private void addToDefaultGroupIfNeeded() {
333         if (!mAutoAddToDefaultGroup || mGroupMetaData == null || mGroupMetaData.isClosed()
334                 || mState == null) {
335             return;
336         }
337 
338         boolean hasGroupMembership = false;
339         ArrayList<ValuesDelta> entries = mState.getMimeEntries(GroupMembership.CONTENT_ITEM_TYPE);
340         if (entries != null) {
341             for (ValuesDelta values : entries) {
342                 Long id = values.getAsLong(GroupMembership.GROUP_ROW_ID);
343                 if (id != null && id.longValue() != 0) {
344                     hasGroupMembership = true;
345                     break;
346                 }
347             }
348         }
349 
350         if (!hasGroupMembership) {
351             long defaultGroupId = getDefaultGroupId();
352             if (defaultGroupId != -1) {
353                 ValuesDelta entry = EntityModifier.insertChild(mState, mGroupMembershipKind);
354                 entry.put(GroupMembership.GROUP_ROW_ID, defaultGroupId);
355             }
356         }
357     }
358 
359     /**
360      * Returns the default group (e.g. "My Contacts") for the current raw contact's
361      * account.  Returns -1 if there is no such group.
362      */
getDefaultGroupId()363     private long getDefaultGroupId() {
364         String accountType = mState.getValues().getAsString(RawContacts.ACCOUNT_TYPE);
365         String accountName = mState.getValues().getAsString(RawContacts.ACCOUNT_NAME);
366         String accountDataSet = mState.getValues().getAsString(RawContacts.DATA_SET);
367         mGroupMetaData.moveToPosition(-1);
368         while (mGroupMetaData.moveToNext()) {
369             String name = mGroupMetaData.getString(GroupMetaDataLoader.ACCOUNT_NAME);
370             String type = mGroupMetaData.getString(GroupMetaDataLoader.ACCOUNT_TYPE);
371             String dataSet = mGroupMetaData.getString(GroupMetaDataLoader.DATA_SET);
372             if (name.equals(accountName) && type.equals(accountType)
373                     && Objects.equal(dataSet, accountDataSet)) {
374                 long groupId = mGroupMetaData.getLong(GroupMetaDataLoader.GROUP_ID);
375                 if (!mGroupMetaData.isNull(GroupMetaDataLoader.AUTO_ADD)
376                             && mGroupMetaData.getInt(GroupMetaDataLoader.AUTO_ADD) != 0) {
377                     return groupId;
378                 }
379             }
380         }
381         return -1;
382     }
383 
getNameEditor()384     public TextFieldsEditorView getNameEditor() {
385         return mName;
386     }
387 
getPhoneticNameEditor()388     public TextFieldsEditorView getPhoneticNameEditor() {
389         return mPhoneticName;
390     }
391 
updatePhoneticNameVisibility()392     private void updatePhoneticNameVisibility() {
393         boolean showByDefault =
394                 getContext().getResources().getBoolean(R.bool.config_editor_include_phonetic_name);
395 
396         if (showByDefault || mPhoneticName.hasData() || mPhoneticNameAdded) {
397             mPhoneticName.setVisibility(View.VISIBLE);
398         } else {
399             mPhoneticName.setVisibility(View.GONE);
400         }
401     }
402 
403     @Override
getRawContactId()404     public long getRawContactId() {
405         return mRawContactId;
406     }
407 
showAddInformationPopupWindow()408     private void showAddInformationPopupWindow() {
409         final ArrayList<KindSectionView> fields =
410                 new ArrayList<KindSectionView>(mFields.getChildCount());
411 
412         final PopupMenu popupMenu = new PopupMenu(getContext(), mAddFieldButton);
413         final Menu menu = popupMenu.getMenu();
414         for (int i = 0; i < mFields.getChildCount(); i++) {
415             View child = mFields.getChildAt(i);
416             if (child instanceof KindSectionView) {
417                 final KindSectionView sectionView = (KindSectionView) child;
418                 // If the section is already visible (has 1 or more editors), then don't offer the
419                 // option to add this type of field in the popup menu
420                 if (sectionView.getEditorCount() > 0) {
421                     continue;
422                 }
423                 DataKind kind = sectionView.getKind();
424                 // not a list and already exists? ignore
425                 if ((kind.typeOverallMax == 1) && sectionView.getEditorCount() != 0) {
426                     continue;
427                 }
428                 if (DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME.equals(kind.mimeType)) {
429                     continue;
430                 }
431 
432                 if (DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME.equals(kind.mimeType)
433                         && mPhoneticName.getVisibility() == View.VISIBLE) {
434                     continue;
435                 }
436 
437                 menu.add(Menu.NONE, fields.size(), Menu.NONE, sectionView.getTitle());
438                 fields.add(sectionView);
439             }
440         }
441 
442         popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
443             @Override
444             public boolean onMenuItemClick(MenuItem item) {
445                 final KindSectionView view = fields.get(item.getItemId());
446                 if (DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME.equals(view.getKind().mimeType)) {
447                     mPhoneticNameAdded = true;
448                     updatePhoneticNameVisibility();
449                 } else {
450                     view.addItem();
451                 }
452                 return true;
453             }
454         });
455 
456         popupMenu.show();
457     }
458 }
459