blob: 5752a47967954d0a9d4ccd242692afb3e5c6fb3c [file] [log] [blame]
Dmitri Plotnikove843f912010-09-16 15:21:48 -07001/*
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
Dmitri Plotnikov18ffaa22010-12-03 14:28:00 -080017package com.android.contacts.editor;
Dmitri Plotnikove843f912010-09-16 15:21:48 -070018
Dmitri Plotnikov1ac58b62010-11-19 16:12:09 -080019import android.app.Activity;
Dmitri Plotnikove843f912010-09-16 15:21:48 -070020import android.content.Context;
Katherine Kuanfd709032011-08-02 14:18:16 -070021import android.content.res.Resources;
Dmitri Plotnikove843f912010-09-16 15:21:48 -070022import android.database.Cursor;
23import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
Makoto Onuki93a00252012-01-11 16:48:40 -080024import android.text.TextUtils;
Dmitri Plotnikove843f912010-09-16 15:21:48 -070025import android.util.AttributeSet;
26import android.view.View;
27import android.view.View.OnClickListener;
Maurice Chub03e6a22012-05-04 12:46:25 -070028import android.view.ViewGroup;
Dmitri Plotnikove843f912010-09-16 15:21:48 -070029import android.widget.AdapterView;
30import android.widget.AdapterView.OnItemClickListener;
31import android.widget.ArrayAdapter;
Maurice Chub03e6a22012-05-04 12:46:25 -070032import android.widget.CheckedTextView;
Brian Attwell043fba62014-10-30 11:11:56 -070033import android.widget.ImageView;
Dmitri Plotnikove843f912010-09-16 15:21:48 -070034import android.widget.LinearLayout;
35import android.widget.ListPopupWindow;
36import android.widget.ListView;
37import android.widget.TextView;
38
Chiao Chenge0b2f1e2012-06-12 13:07:56 -070039import com.android.contacts.GroupMetaDataLoader;
40import com.android.contacts.R;
Chiao Chengba09d4c2012-11-14 15:52:32 -080041import com.android.contacts.common.model.dataitem.DataKind;
Chiao Chenge0b2f1e2012-06-12 13:07:56 -070042import com.android.contacts.interactions.GroupCreationDialogFragment;
43import com.android.contacts.interactions.GroupCreationDialogFragment.OnGroupCreatedListener;
Yorke Leecd321f62013-10-28 15:20:15 -070044import com.android.contacts.common.model.RawContactDelta;
Chiao Cheng738ff862012-11-30 12:06:03 -080045import com.android.contacts.common.model.ValuesDelta;
Yorke Leecd321f62013-10-28 15:20:15 -070046import com.android.contacts.common.model.RawContactModifier;
Chiao Cheng619ac162012-12-20 14:29:03 -080047import com.android.contacts.util.UiClosables;
Chiao Chengba09d4c2012-11-14 15:52:32 -080048import com.google.common.base.Objects;
Chiao Chenge0b2f1e2012-06-12 13:07:56 -070049
Dmitri Plotnikove843f912010-09-16 15:21:48 -070050import java.util.ArrayList;
51
52/**
53 * An editor for group membership. Displays the current group membership list and
54 * brings up a dialog to change it.
55 */
56public class GroupMembershipView extends LinearLayout
57 implements OnClickListener, OnItemClickListener {
58
Dmitri Plotnikov1ac58b62010-11-19 16:12:09 -080059 private static final int CREATE_NEW_GROUP_GROUP_ID = 133;
60
Dmitri Plotnikove843f912010-09-16 15:21:48 -070061 public static final class GroupSelectionItem {
62 private final long mGroupId;
63 private final String mTitle;
64 private boolean mChecked;
65
66 public GroupSelectionItem(long groupId, String title, boolean checked) {
67 this.mGroupId = groupId;
68 this.mTitle = title;
69 mChecked = checked;
70 }
71
72 public long getGroupId() {
73 return mGroupId;
74 }
75
76 public boolean isChecked() {
77 return mChecked;
78 }
79
80 public void setChecked(boolean checked) {
81 mChecked = checked;
82 }
83
84 @Override
85 public String toString() {
86 return mTitle;
87 }
88 }
89
Maurice Chub03e6a22012-05-04 12:46:25 -070090 /**
91 * Extends the array adapter to show checkmarks on all but the last list item for
92 * the group membership popup. Note that this is highly specific to the fact that the
93 * group_membership_list_item.xml is a CheckedTextView object.
94 */
95 private class GroupMembershipAdapter<T> extends ArrayAdapter<T> {
96
97 public GroupMembershipAdapter(Context context, int textViewResourceId) {
98 super(context, textViewResourceId);
99 }
100
101 public boolean getItemIsCheckable(int position) {
102 // Item is checkable if it is NOT the last one in the list
103 return position != getCount()-1;
104 }
105
106 @Override
107 public int getItemViewType(int position) {
108 return getItemIsCheckable(position) ? 0 : 1;
109 }
110
111 @Override
112 public int getViewTypeCount() {
113 return 2;
114 }
115
116 @Override
117 public View getView(int position, View convertView, ViewGroup parent) {
118 final View itemView = super.getView(position, convertView, parent);
119
120 // Hide the checkable drawable. This assumes that the item views
121 // are CheckedTextView objects
122 final CheckedTextView checkedTextView = (CheckedTextView)itemView;
123 if (!getItemIsCheckable(position)) {
124 checkedTextView.setCheckMarkDrawable(null);
125 }
126
127 return checkedTextView;
128 }
129 }
130
Maurice Chu851222a2012-06-21 11:43:08 -0700131 private RawContactDelta mState;
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700132 private Cursor mGroupMetaData;
133 private String mAccountName;
134 private String mAccountType;
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700135 private String mDataSet;
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700136 private TextView mGroupList;
Maurice Chub03e6a22012-05-04 12:46:25 -0700137 private GroupMembershipAdapter<GroupSelectionItem> mAdapter;
Dmitri Plotnikov025838f2010-09-22 17:52:42 -0700138 private long mDefaultGroupId;
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700139 private long mFavoritesGroupId;
140 private ListPopupWindow mPopup;
141 private DataKind mKind;
Dmitri Plotnikovad9659e2010-10-06 16:46:39 -0700142 private boolean mDefaultGroupVisibilityKnown;
143 private boolean mDefaultGroupVisible;
Maurice Chub03e6a22012-05-04 12:46:25 -0700144 private boolean mCreatedNewGroup;
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700145
Katherine Kuanfd709032011-08-02 14:18:16 -0700146 private String mNoGroupString;
147 private int mPrimaryTextColor;
148 private int mSecondaryTextColor;
149
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700150 public GroupMembershipView(Context context) {
151 super(context);
152 }
153
154 public GroupMembershipView(Context context, AttributeSet attrs) {
155 super(context, attrs);
156 }
157
Dmitri Plotnikov02bb1252010-12-06 09:39:05 -0800158 @Override
Katherine Kuanfd709032011-08-02 14:18:16 -0700159 protected void onFinishInflate() {
160 super.onFinishInflate();
161 Resources resources = mContext.getResources();
162 mPrimaryTextColor = resources.getColor(R.color.primary_text_color);
163 mSecondaryTextColor = resources.getColor(R.color.secondary_text_color);
Katherine Kuan95743142011-08-10 17:56:11 -0700164 mNoGroupString = mContext.getString(R.string.group_edit_field_hint_text);
Katherine Kuanfd709032011-08-02 14:18:16 -0700165 }
166
167 @Override
Dmitri Plotnikov02bb1252010-12-06 09:39:05 -0800168 public void setEnabled(boolean enabled) {
169 super.setEnabled(enabled);
170 if (mGroupList != null) {
171 mGroupList.setEnabled(enabled);
172 }
173 }
174
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700175 public void setKind(DataKind kind) {
176 mKind = kind;
Brian Attwell043fba62014-10-30 11:11:56 -0700177 final ImageView imageView = (ImageView) findViewById(R.id.kind_icon);
178 imageView.setContentDescription(getResources().getString(kind.titleRes));
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700179 }
180
181 public void setGroupMetaData(Cursor groupMetaData) {
182 this.mGroupMetaData = groupMetaData;
183 updateView();
Maurice Chub03e6a22012-05-04 12:46:25 -0700184 // Open up the list of groups if a new group was just created.
185 if (mCreatedNewGroup) {
186 mCreatedNewGroup = false;
187 onClick(this); // This causes the popup to open.
188 if (mPopup != null) {
189 // Ensure that the newly created group is checked.
190 int position = mAdapter.getCount() - 2;
191 ListView listView = mPopup.getListView();
Jay Shrauner9813b712014-02-21 13:09:09 -0800192 if (listView != null && !listView.isItemChecked(position)) {
Maurice Chub03e6a22012-05-04 12:46:25 -0700193 // Newly created group is not checked, so check it.
194 listView.setItemChecked(position, true);
195 onItemClick(listView, null, position, listView.getItemIdAtPosition(position));
196 }
197 }
198 }
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700199 }
200
Maurice Chu851222a2012-06-21 11:43:08 -0700201 public void setState(RawContactDelta state) {
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700202 mState = state;
Maurice Chu851222a2012-06-21 11:43:08 -0700203 mAccountType = mState.getAccountType();
204 mAccountName = mState.getAccountName();
205 mDataSet = mState.getDataSet();
Dmitri Plotnikovad9659e2010-10-06 16:46:39 -0700206 mDefaultGroupVisibilityKnown = false;
Maurice Chub03e6a22012-05-04 12:46:25 -0700207 mCreatedNewGroup = false;
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700208 updateView();
209 }
210
211 private void updateView() {
Dmitri Plotnikov08d23342010-09-16 15:43:33 -0700212 if (mGroupMetaData == null || mGroupMetaData.isClosed() || mAccountType == null
213 || mAccountName == null) {
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700214 setVisibility(GONE);
215 return;
216 }
217
218 boolean accountHasGroups = false;
Dmitri Plotnikov025838f2010-09-22 17:52:42 -0700219 mFavoritesGroupId = 0;
220 mDefaultGroupId = 0;
221
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700222 StringBuilder sb = new StringBuilder();
223 mGroupMetaData.moveToPosition(-1);
224 while (mGroupMetaData.moveToNext()) {
225 String accountName = mGroupMetaData.getString(GroupMetaDataLoader.ACCOUNT_NAME);
226 String accountType = mGroupMetaData.getString(GroupMetaDataLoader.ACCOUNT_TYPE);
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700227 String dataSet = mGroupMetaData.getString(GroupMetaDataLoader.DATA_SET);
228 if (accountName.equals(mAccountName) && accountType.equals(mAccountType)
229 && Objects.equal(dataSet, mDataSet)) {
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700230 long groupId = mGroupMetaData.getLong(GroupMetaDataLoader.GROUP_ID);
231 if (!mGroupMetaData.isNull(GroupMetaDataLoader.FAVORITES)
232 && mGroupMetaData.getInt(GroupMetaDataLoader.FAVORITES) != 0) {
233 mFavoritesGroupId = groupId;
Dmitri Plotnikov025838f2010-09-22 17:52:42 -0700234 } else if (!mGroupMetaData.isNull(GroupMetaDataLoader.AUTO_ADD)
235 && mGroupMetaData.getInt(GroupMetaDataLoader.AUTO_ADD) != 0) {
236 mDefaultGroupId = groupId;
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700237 } else {
238 accountHasGroups = true;
239 }
240
241 // Exclude favorites from the list - they are handled with special UI (star)
Dmitri Plotnikov5d11ccd2010-09-26 17:16:12 -0700242 // Also exclude the default group.
243 if (groupId != mFavoritesGroupId && groupId != mDefaultGroupId
244 && hasMembership(groupId)) {
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700245 String title = mGroupMetaData.getString(GroupMetaDataLoader.TITLE);
Makoto Onuki93a00252012-01-11 16:48:40 -0800246 if (!TextUtils.isEmpty(title)) {
247 if (sb.length() != 0) {
248 sb.append(", ");
249 }
250 sb.append(title);
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700251 }
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700252 }
253 }
254 }
255
256 if (!accountHasGroups) {
257 setVisibility(GONE);
258 return;
259 }
260
261 if (mGroupList == null) {
262 mGroupList = (TextView) findViewById(R.id.group_list);
Daniel Lehmann82b49b82010-11-05 15:17:58 -0700263 mGroupList.setOnClickListener(this);
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700264 }
265
Dmitri Plotnikov02bb1252010-12-06 09:39:05 -0800266 mGroupList.setEnabled(isEnabled());
Dmitri Plotnikov135b44c2011-01-19 11:05:04 -0800267 if (sb.length() == 0) {
Katherine Kuanfd709032011-08-02 14:18:16 -0700268 mGroupList.setText(mNoGroupString);
269 mGroupList.setTextColor(mSecondaryTextColor);
Dmitri Plotnikov135b44c2011-01-19 11:05:04 -0800270 } else {
271 mGroupList.setText(sb);
Katherine Kuanfd709032011-08-02 14:18:16 -0700272 mGroupList.setTextColor(mPrimaryTextColor);
Dmitri Plotnikov135b44c2011-01-19 11:05:04 -0800273 }
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700274 setVisibility(VISIBLE);
Dmitri Plotnikovad9659e2010-10-06 16:46:39 -0700275
276 if (!mDefaultGroupVisibilityKnown) {
277 // Only show the default group (My Contacts) if the contact is NOT in it
278 mDefaultGroupVisible = mDefaultGroupId != 0 && !hasMembership(mDefaultGroupId);
279 mDefaultGroupVisibilityKnown = true;
280 }
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700281 }
282
283 @Override
284 public void onClick(View v) {
Chiao Cheng619ac162012-12-20 14:29:03 -0800285 if (UiClosables.closeQuietly(mPopup)) {
Jay Shrauner9813b712014-02-21 13:09:09 -0800286 mPopup = null;
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700287 return;
288 }
289
Maurice Chub03e6a22012-05-04 12:46:25 -0700290 mAdapter = new GroupMembershipAdapter<GroupSelectionItem>(
Dmitri Plotnikov2b95e622011-01-24 16:52:59 -0800291 getContext(), R.layout.group_membership_list_item);
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700292
293 mGroupMetaData.moveToPosition(-1);
294 while (mGroupMetaData.moveToNext()) {
295 String accountName = mGroupMetaData.getString(GroupMetaDataLoader.ACCOUNT_NAME);
296 String accountType = mGroupMetaData.getString(GroupMetaDataLoader.ACCOUNT_TYPE);
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700297 String dataSet = mGroupMetaData.getString(GroupMetaDataLoader.DATA_SET);
298 if (accountName.equals(mAccountName) && accountType.equals(mAccountType)
299 && Objects.equal(dataSet, mDataSet)) {
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700300 long groupId = mGroupMetaData.getLong(GroupMetaDataLoader.GROUP_ID);
Dmitri Plotnikovad9659e2010-10-06 16:46:39 -0700301 if (groupId != mFavoritesGroupId
302 && (groupId != mDefaultGroupId || mDefaultGroupVisible)) {
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700303 String title = mGroupMetaData.getString(GroupMetaDataLoader.TITLE);
304 boolean checked = hasMembership(groupId);
305 mAdapter.add(new GroupSelectionItem(groupId, title, checked));
306 }
307 }
308 }
309
Dmitri Plotnikov1ac58b62010-11-19 16:12:09 -0800310 mAdapter.add(new GroupSelectionItem(CREATE_NEW_GROUP_GROUP_ID,
311 getContext().getString(R.string.create_group_item_label), false));
312
Dmitri Plotnikovb93d6262010-09-26 18:24:40 -0700313 mPopup = new ListPopupWindow(getContext(), null);
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700314 mPopup.setAnchorView(mGroupList);
315 mPopup.setAdapter(mAdapter);
Dmitri Plotnikov5d11ccd2010-09-26 17:16:12 -0700316 mPopup.setModal(true);
Daniel Lehmann6d172802012-04-25 18:17:20 -0700317 mPopup.setInputMethodMode(ListPopupWindow.INPUT_METHOD_NOT_NEEDED);
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700318 mPopup.show();
319
320 ListView listView = mPopup.getListView();
321 listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
Katherine Kuand85b9862011-09-25 19:37:38 -0700322 listView.setOverScrollMode(OVER_SCROLL_ALWAYS);
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700323 int count = mAdapter.getCount();
324 for (int i = 0; i < count; i++) {
325 listView.setItemChecked(i, mAdapter.getItem(i).isChecked());
326 }
327
328 listView.setOnItemClickListener(this);
329 }
330
331 @Override
332 protected void onDetachedFromWindow() {
333 super.onDetachedFromWindow();
Chiao Cheng619ac162012-12-20 14:29:03 -0800334 UiClosables.closeQuietly(mPopup);
335 mPopup = null;
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700336 }
337
338 @Override
339 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
340 ListView list = (ListView) parent;
341 int count = mAdapter.getCount();
Dmitri Plotnikov1ac58b62010-11-19 16:12:09 -0800342
343 if (list.isItemChecked(count - 1)) {
344 list.setItemChecked(count - 1, false);
345 createNewGroup();
346 return;
347 }
348
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700349 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) {
Dmitri Plotnikov025838f2010-09-22 17:52:42 -0700357 if (!entry.isDelete()) {
Maurice Chu851222a2012-06-21 11:43:08 -0700358 Long groupId = entry.getGroupRowId();
Dmitri Plotnikov025838f2010-09-22 17:52:42 -0700359 if (groupId != null && groupId != mFavoritesGroupId
Dmitri Plotnikova61e0fb2010-11-02 18:38:42 -0700360 && (groupId != mDefaultGroupId || mDefaultGroupVisible)
Dmitri Plotnikov025838f2010-09-22 17:52:42 -0700361 && !isGroupChecked(groupId)) {
362 entry.markDeleted();
363 }
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700364 }
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)) {
Maurice Chu851222a2012-06-21 11:43:08 -0700373 ValuesDelta entry = RawContactModifier.insertChild(mState, mKind);
Jay Shraunerf54c9f22014-01-10 12:41:55 -0800374 if (entry != null) {
375 entry.setGroupRowId(groupId);
376 }
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700377 }
378 }
379
380 updateView();
381 }
382
383 private boolean isGroupChecked(long groupId) {
384 int count = mAdapter.getCount();
385 for (int i = 0; i < count; i++) {
386 GroupSelectionItem item = mAdapter.getItem(i);
387 if (groupId == item.getGroupId()) {
388 return item.isChecked();
389 }
390 }
391 return false;
392 }
393
394 private boolean hasMembership(long groupId) {
Dmitri Plotnikov025838f2010-09-22 17:52:42 -0700395 if (groupId == mDefaultGroupId && mState.isContactInsert()) {
396 return true;
397 }
398
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700399 ArrayList<ValuesDelta> entries = mState.getMimeEntries(GroupMembership.CONTENT_ITEM_TYPE);
400 if (entries != null) {
401 for (ValuesDelta values : entries) {
Dmitri Plotnikov025838f2010-09-22 17:52:42 -0700402 if (!values.isDelete()) {
Maurice Chu851222a2012-06-21 11:43:08 -0700403 Long id = values.getGroupRowId();
Dmitri Plotnikov025838f2010-09-22 17:52:42 -0700404 if (id != null && id == groupId) {
405 return true;
406 }
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700407 }
408 }
409 }
410 return false;
411 }
Dmitri Plotnikov1ac58b62010-11-19 16:12:09 -0800412
413 private void createNewGroup() {
Chiao Cheng619ac162012-12-20 14:29:03 -0800414 UiClosables.closeQuietly(mPopup);
415 mPopup = null;
Dmitri Plotnikov1ac58b62010-11-19 16:12:09 -0800416
417 GroupCreationDialogFragment.show(
Maurice Chub03e6a22012-05-04 12:46:25 -0700418 ((Activity) getContext()).getFragmentManager(),
419 mAccountType,
420 mAccountName,
421 mDataSet,
422 new OnGroupCreatedListener() {
423 @Override
424 public void onGroupCreated() {
425 mCreatedNewGroup = true;
426 }
427 });
Dmitri Plotnikov1ac58b62010-11-19 16:12:09 -0800428 }
Maurice Chub03e6a22012-05-04 12:46:25 -0700429
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700430}