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