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