• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2010 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.app.Activity;
20 import android.app.FragmentManager;
21 import android.content.Context;
22 import android.content.res.Resources;
23 import android.database.Cursor;
24 import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
25 import android.text.TextUtils;
26 import android.util.AttributeSet;
27 import android.view.View;
28 import android.view.View.OnClickListener;
29 import android.view.ViewGroup;
30 import android.widget.AdapterView;
31 import android.widget.AdapterView.OnItemClickListener;
32 import android.widget.ArrayAdapter;
33 import android.widget.CheckedTextView;
34 import android.widget.ImageView;
35 import android.widget.LinearLayout;
36 import android.widget.ListPopupWindow;
37 import android.widget.ListView;
38 import android.widget.TextView;
39 
40 import com.android.contacts.GroupMetaDataLoader;
41 import com.android.contacts.R;
42 import com.android.contacts.group.GroupNameEditDialogFragment;
43 import com.android.contacts.model.RawContactDelta;
44 import com.android.contacts.model.RawContactModifier;
45 import com.android.contacts.model.ValuesDelta;
46 import com.android.contacts.model.account.AccountWithDataSet;
47 import com.android.contacts.model.dataitem.DataKind;
48 import com.android.contacts.util.UiClosables;
49 
50 import com.google.common.base.Objects;
51 
52 import java.util.ArrayList;
53 
54 /**
55  * An editor for group membership.  Displays the current group membership list and
56  * brings up a dialog to change it.
57  */
58 public class GroupMembershipView extends LinearLayout
59         implements OnClickListener, OnItemClickListener {
60 
61     public static final String TAG_CREATE_GROUP_FRAGMENT = "createGroupDialog";
62 
63     private static final int CREATE_NEW_GROUP_GROUP_ID = 133;
64 
65     public static final class GroupSelectionItem {
66         private final long mGroupId;
67         private final String mTitle;
68         private boolean mChecked;
69 
GroupSelectionItem(long groupId, String title, boolean checked)70         public GroupSelectionItem(long groupId, String title, boolean checked) {
71             this.mGroupId = groupId;
72             this.mTitle = title;
73             mChecked = checked;
74         }
75 
getGroupId()76         public long getGroupId() {
77             return mGroupId;
78         }
79 
isChecked()80         public boolean isChecked() {
81             return mChecked;
82         }
83 
setChecked(boolean checked)84         public void setChecked(boolean checked) {
85             mChecked = checked;
86         }
87 
88         @Override
toString()89         public String toString() {
90             return mTitle;
91         }
92     }
93 
94     /**
95      * Extends the array adapter to show checkmarks on all but the last list item for
96      * the group membership popup.  Note that this is highly specific to the fact that the
97      * group_membership_list_item.xml is a CheckedTextView object.
98      */
99     private class GroupMembershipAdapter<T> extends ArrayAdapter<T> {
100 
101         // The position of the group with the largest group ID
102         private int mNewestGroupPosition;
103 
GroupMembershipAdapter(Context context, int textViewResourceId)104         public GroupMembershipAdapter(Context context, int textViewResourceId) {
105             super(context, textViewResourceId);
106         }
107 
getItemIsCheckable(int position)108         public boolean getItemIsCheckable(int position) {
109             // Item is checkable if it is NOT the last one in the list
110             return position != getCount()-1;
111         }
112 
113         @Override
getItemViewType(int position)114         public int getItemViewType(int position) {
115             return getItemIsCheckable(position) ? 0 : 1;
116         }
117 
118         @Override
getViewTypeCount()119         public int getViewTypeCount() {
120             return 2;
121         }
122 
123         @Override
getView(int position, View convertView, ViewGroup parent)124         public View getView(int position, View convertView, ViewGroup parent) {
125             final View itemView = super.getView(position, convertView, parent);
126             if (itemView == null) {
127                 return null;
128             }
129 
130             // Hide the checkable drawable.  This assumes that the item views
131             // are CheckedTextView objects
132             final CheckedTextView checkedTextView = (CheckedTextView)itemView;
133             if (!getItemIsCheckable(position)) {
134                 checkedTextView.setCheckMarkDrawable(null);
135             }
136             checkedTextView.setTextColor(mPrimaryTextColor);
137 
138             return checkedTextView;
139         }
140 
getNewestGroupPosition()141         public int getNewestGroupPosition() {
142             return mNewestGroupPosition;
143         }
144 
setNewestGroupPosition(int newestGroupPosition)145         public void setNewestGroupPosition(int newestGroupPosition) {
146             mNewestGroupPosition = newestGroupPosition;
147         }
148 
149     }
150 
151     private RawContactDelta mState;
152     private Cursor mGroupMetaData;
153     private boolean mAccountHasGroups;
154     private String mAccountName;
155     private String mAccountType;
156     private String mDataSet;
157     private TextView mGroupList;
158     private GroupMembershipAdapter<GroupSelectionItem> mAdapter;
159     private long mDefaultGroupId;
160     private long mFavoritesGroupId;
161     private ListPopupWindow mPopup;
162     private DataKind mKind;
163     private boolean mDefaultGroupVisibilityKnown;
164     private boolean mDefaultGroupVisible;
165     private boolean mCreatedNewGroup;
166     private GroupNameEditDialogFragment mGroupNameEditDialogFragment;
167     private GroupNameEditDialogFragment.Listener mListener =
168             new GroupNameEditDialogFragment.Listener() {
169                 @Override
170                 public void onGroupNameEditCancelled() {
171                 }
172 
173                 @Override
174                 public void onGroupNameEditCompleted(String name) {
175                     mCreatedNewGroup = true;
176                 }
177             };
178 
179     private String mNoGroupString;
180     private int mPrimaryTextColor;
181     private int mHintTextColor;
182 
GroupMembershipView(Context context)183     public GroupMembershipView(Context context) {
184         super(context);
185     }
186 
GroupMembershipView(Context context, AttributeSet attrs)187     public GroupMembershipView(Context context, AttributeSet attrs) {
188         super(context, attrs);
189     }
190 
191     @Override
onFinishInflate()192     protected void onFinishInflate() {
193         super.onFinishInflate();
194         Resources resources = getContext().getResources();
195         mPrimaryTextColor = resources.getColor(R.color.primary_text_color);
196         mHintTextColor = resources.getColor(R.color.editor_disabled_text_color);
197         mNoGroupString = getContext().getString(R.string.group_edit_field_hint_text);
198         setFocusable(true);
199         setFocusableInTouchMode(true);
200     }
201 
setGroupNameEditDialogFragment()202     private void setGroupNameEditDialogFragment() {
203         final FragmentManager fragmentManager = ((Activity) getContext()).getFragmentManager();
204         mGroupNameEditDialogFragment = (GroupNameEditDialogFragment)
205                 fragmentManager.findFragmentByTag(TAG_CREATE_GROUP_FRAGMENT);
206         if (mGroupNameEditDialogFragment != null) {
207             mGroupNameEditDialogFragment.setListener(mListener);
208         }
209     }
210 
211     @Override
setEnabled(boolean enabled)212     public void setEnabled(boolean enabled) {
213         super.setEnabled(enabled);
214         if (mGroupList != null) {
215             mGroupList.setEnabled(enabled);
216         }
217     }
218 
setKind(DataKind kind)219     public void setKind(DataKind kind) {
220         mKind = kind;
221         final ImageView imageView = (ImageView) findViewById(R.id.kind_icon);
222         imageView.setContentDescription(getResources().getString(kind.titleRes));
223     }
224 
setGroupMetaData(Cursor groupMetaData)225     public void setGroupMetaData(Cursor groupMetaData) {
226         this.mGroupMetaData = groupMetaData;
227         updateView();
228         // Open up the list of groups if a new group was just created.
229         if (mCreatedNewGroup) {
230             mCreatedNewGroup = false;
231             onClick(this); // This causes the popup to open.
232             if (mPopup != null) {
233                 // Ensure that the newly created group is checked.
234                 final int position = mAdapter.getNewestGroupPosition();
235                 ListView listView = mPopup.getListView();
236                 if (listView != null && !listView.isItemChecked(position)) {
237                     // Newly created group is not checked, so check it.
238                     listView.setItemChecked(position, true);
239                     onItemClick(listView, null, position, listView.getItemIdAtPosition(position));
240                 }
241             }
242         }
243     }
244 
245     /** Whether {@link #setGroupMetaData} has been invoked yet. */
wasGroupMetaDataBound()246     public boolean wasGroupMetaDataBound() {
247         return mGroupMetaData != null;
248     }
249 
250     /**
251      * Return true if the account has groups to edit group membership for contacts
252      * belong to the account.
253      */
accountHasGroups()254     public boolean accountHasGroups() {
255         return mAccountHasGroups;
256     }
257 
setState(RawContactDelta state)258     public void setState(RawContactDelta state) {
259         mState = state;
260         mAccountType = mState.getAccountType();
261         mAccountName = mState.getAccountName();
262         mDataSet = mState.getDataSet();
263         mDefaultGroupVisibilityKnown = false;
264         mCreatedNewGroup = false;
265         updateView();
266         setGroupNameEditDialogFragment();
267     }
268 
updateView()269     private void updateView() {
270         if (mGroupMetaData == null || mGroupMetaData.isClosed()) {
271             setVisibility(GONE);
272             return;
273         }
274 
275         mFavoritesGroupId = 0;
276         mDefaultGroupId = 0;
277 
278         StringBuilder sb = new StringBuilder();
279         mGroupMetaData.moveToPosition(-1);
280         while (mGroupMetaData.moveToNext()) {
281             String accountName = mGroupMetaData.getString(GroupMetaDataLoader.ACCOUNT_NAME);
282             String accountType = mGroupMetaData.getString(GroupMetaDataLoader.ACCOUNT_TYPE);
283             String dataSet = mGroupMetaData.getString(GroupMetaDataLoader.DATA_SET);
284             if (TextUtils.equals(accountName, mAccountName)
285                     && TextUtils.equals(accountType, mAccountType)
286                     && TextUtils.equals(dataSet, mDataSet)) {
287                 long groupId = mGroupMetaData.getLong(GroupMetaDataLoader.GROUP_ID);
288                 if (!mGroupMetaData.isNull(GroupMetaDataLoader.FAVORITES)
289                         && mGroupMetaData.getInt(GroupMetaDataLoader.FAVORITES) != 0) {
290                     mFavoritesGroupId = groupId;
291                 } else if (!mGroupMetaData.isNull(GroupMetaDataLoader.AUTO_ADD)
292                             && mGroupMetaData.getInt(GroupMetaDataLoader.AUTO_ADD) != 0) {
293                     mDefaultGroupId = groupId;
294                 } else {
295                     mAccountHasGroups = true;
296                 }
297 
298                 // Exclude favorites from the list - they are handled with special UI (star)
299                 // Also exclude the default group.
300                 if (groupId != mFavoritesGroupId && groupId != mDefaultGroupId
301                         && hasMembership(groupId)) {
302                     String title = mGroupMetaData.getString(GroupMetaDataLoader.TITLE);
303                     if (!TextUtils.isEmpty(title)) {
304                         if (sb.length() != 0) {
305                             sb.append(", ");
306                         }
307                         sb.append(title);
308                     }
309                 }
310             }
311         }
312 
313         if (!mAccountHasGroups) {
314             setVisibility(GONE);
315             return;
316         }
317 
318         if (mGroupList == null) {
319             mGroupList = (TextView) findViewById(R.id.group_list);
320             mGroupList.setOnClickListener(this);
321         }
322 
323         mGroupList.setEnabled(isEnabled());
324         if (sb.length() == 0) {
325             mGroupList.setText(mNoGroupString);
326             mGroupList.setTextColor(mHintTextColor);
327         } else {
328             mGroupList.setText(sb);
329             mGroupList.setTextColor(mPrimaryTextColor);
330         }
331         setVisibility(VISIBLE);
332 
333         if (!mDefaultGroupVisibilityKnown) {
334             // Only show the default group (My Contacts) if the contact is NOT in it
335             mDefaultGroupVisible = mDefaultGroupId != 0 && !hasMembership(mDefaultGroupId);
336             mDefaultGroupVisibilityKnown = true;
337         }
338     }
339 
340     @Override
onClick(View v)341     public void onClick(View v) {
342         if (UiClosables.closeQuietly(mPopup)) {
343             mPopup = null;
344             return;
345         }
346 
347         requestFocus();
348         mAdapter = new GroupMembershipAdapter<GroupSelectionItem>(
349                 getContext(), R.layout.group_membership_list_item);
350 
351         long newestGroupId = -1;
352 
353         mGroupMetaData.moveToPosition(-1);
354         while (mGroupMetaData.moveToNext()) {
355             String accountName = mGroupMetaData.getString(GroupMetaDataLoader.ACCOUNT_NAME);
356             String accountType = mGroupMetaData.getString(GroupMetaDataLoader.ACCOUNT_TYPE);
357             String dataSet = mGroupMetaData.getString(GroupMetaDataLoader.DATA_SET);
358             if (TextUtils.equals(accountName, mAccountName)
359                     && TextUtils.equals(accountType, mAccountType)
360                     && TextUtils.equals(dataSet, mDataSet)) {
361                 long groupId = mGroupMetaData.getLong(GroupMetaDataLoader.GROUP_ID);
362                 if (groupId != mFavoritesGroupId
363                         && (groupId != mDefaultGroupId || mDefaultGroupVisible)) {
364                     if (groupId > newestGroupId) {
365                         newestGroupId = groupId;
366                         mAdapter.setNewestGroupPosition(mAdapter.getCount());
367                     }
368                     String title = mGroupMetaData.getString(GroupMetaDataLoader.TITLE);
369                     boolean checked = hasMembership(groupId);
370                     mAdapter.add(new GroupSelectionItem(groupId, title, checked));
371                 }
372             }
373         }
374 
375         mAdapter.add(new GroupSelectionItem(CREATE_NEW_GROUP_GROUP_ID,
376                 getContext().getString(R.string.create_group_item_label), false));
377 
378         mPopup = new ListPopupWindow(getContext(), null);
379         mPopup.setAnchorView(mGroupList);
380         mPopup.setAdapter(mAdapter);
381         mPopup.setModal(true);
382         mPopup.setInputMethodMode(ListPopupWindow.INPUT_METHOD_NOT_NEEDED);
383         mPopup.show();
384 
385         ListView listView = mPopup.getListView();
386         listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
387         listView.setOverScrollMode(OVER_SCROLL_ALWAYS);
388         int count = mAdapter.getCount();
389         for (int i = 0; i < count; i++) {
390             listView.setItemChecked(i, mAdapter.getItem(i).isChecked());
391         }
392 
393         listView.setOnItemClickListener(this);
394     }
395 
396     @Override
onDetachedFromWindow()397     protected void onDetachedFromWindow() {
398         super.onDetachedFromWindow();
399         UiClosables.closeQuietly(mPopup);
400         mPopup = null;
401     }
402 
403     @Override
onItemClick(AdapterView<?> parent, View view, int position, long id)404     public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
405         ListView list = (ListView) parent;
406         int count = mAdapter.getCount();
407 
408         if (list.isItemChecked(count - 1)) {
409             list.setItemChecked(count - 1, false);
410             createNewGroup();
411             return;
412         }
413 
414         for (int i = 0; i < count; i++) {
415             mAdapter.getItem(i).setChecked(list.isItemChecked(i));
416         }
417 
418         // First remove the memberships that have been unchecked
419         ArrayList<ValuesDelta> entries = mState.getMimeEntries(GroupMembership.CONTENT_ITEM_TYPE);
420         if (entries != null) {
421             for (ValuesDelta entry : entries) {
422                 if (!entry.isDelete()) {
423                     Long groupId = entry.getGroupRowId();
424                     if (groupId != null && groupId != mFavoritesGroupId
425                             && (groupId != mDefaultGroupId || mDefaultGroupVisible)
426                             && !isGroupChecked(groupId)) {
427                         entry.markDeleted();
428                     }
429                 }
430             }
431         }
432 
433         // Now add the newly selected items
434         for (int i = 0; i < count; i++) {
435             GroupSelectionItem item = mAdapter.getItem(i);
436             long groupId = item.getGroupId();
437             if (item.isChecked() && !hasMembership(groupId)) {
438                 ValuesDelta entry = RawContactModifier.insertChild(mState, mKind);
439                 if (entry != null) {
440                     entry.setGroupRowId(groupId);
441                 }
442             }
443         }
444 
445         updateView();
446     }
447 
isGroupChecked(long groupId)448     private boolean isGroupChecked(long groupId) {
449         int count = mAdapter.getCount();
450         for (int i = 0; i < count; i++) {
451             GroupSelectionItem item = mAdapter.getItem(i);
452             if (groupId == item.getGroupId()) {
453                 return item.isChecked();
454             }
455         }
456         return false;
457     }
458 
hasMembership(long groupId)459     private boolean hasMembership(long groupId) {
460         if (groupId == mDefaultGroupId && mState.isContactInsert()) {
461             return true;
462         }
463 
464         ArrayList<ValuesDelta> entries = mState.getMimeEntries(GroupMembership.CONTENT_ITEM_TYPE);
465         if (entries != null) {
466             for (ValuesDelta values : entries) {
467                 if (!values.isDelete()) {
468                     Long id = values.getGroupRowId();
469                     if (id != null && id == groupId) {
470                         return true;
471                     }
472                 }
473             }
474         }
475         return false;
476     }
477 
createNewGroup()478     private void createNewGroup() {
479         UiClosables.closeQuietly(mPopup);
480         mPopup = null;
481         mGroupNameEditDialogFragment =
482                     GroupNameEditDialogFragment.newInstanceForCreation(
483                             new AccountWithDataSet(mAccountName, mAccountType, mDataSet), null);
484         mGroupNameEditDialogFragment.setListener(mListener);
485         mGroupNameEditDialogFragment.show(
486                 ((Activity) getContext()).getFragmentManager(),
487                 TAG_CREATE_GROUP_FRAGMENT);
488     }
489 }
490