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