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.common.model.dataitem.DataKind; 41 import com.android.contacts.interactions.GroupCreationDialogFragment; 42 import com.android.contacts.interactions.GroupCreationDialogFragment.OnGroupCreatedListener; 43 import com.android.contacts.common.model.RawContactDelta; 44 import com.android.contacts.common.model.ValuesDelta; 45 import com.android.contacts.common.model.RawContactModifier; 46 import com.android.contacts.util.UiClosables; 47 import com.google.common.base.Objects; 48 49 import 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 */ 55 public 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 entry.setGroupRowId(groupId); 373 } 374 } 375 376 updateView(); 377 } 378 379 private boolean isGroupChecked(long groupId) { 380 int count = mAdapter.getCount(); 381 for (int i = 0; i < count; i++) { 382 GroupSelectionItem item = mAdapter.getItem(i); 383 if (groupId == item.getGroupId()) { 384 return item.isChecked(); 385 } 386 } 387 return false; 388 } 389 390 private boolean hasMembership(long groupId) { 391 if (groupId == mDefaultGroupId && mState.isContactInsert()) { 392 return true; 393 } 394 395 ArrayList<ValuesDelta> entries = mState.getMimeEntries(GroupMembership.CONTENT_ITEM_TYPE); 396 if (entries != null) { 397 for (ValuesDelta values : entries) { 398 if (!values.isDelete()) { 399 Long id = values.getGroupRowId(); 400 if (id != null && id == groupId) { 401 return true; 402 } 403 } 404 } 405 } 406 return false; 407 } 408 409 private void createNewGroup() { 410 UiClosables.closeQuietly(mPopup); 411 mPopup = null; 412 413 GroupCreationDialogFragment.show( 414 ((Activity) getContext()).getFragmentManager(), 415 mAccountType, 416 mAccountName, 417 mDataSet, 418 new OnGroupCreatedListener() { 419 @Override 420 public void onGroupCreated() { 421 mCreatedNewGroup = true; 422 } 423 }); 424 } 425 426 } 427