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.interactions.GroupCreationDialogFragment; 41import com.android.contacts.interactions.GroupCreationDialogFragment.OnGroupCreatedListener; 42import com.android.contacts.model.RawContactModifier; 43import com.android.contacts.model.RawContactDelta; 44import com.android.contacts.model.RawContactDelta.ValuesDelta; 45import com.android.contacts.model.dataitem.DataKind; 46import com.android.internal.util.Objects; 47 48import 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 */ 54public 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 64 public GroupSelectionItem(long groupId, String title, boolean checked) { 65 this.mGroupId = groupId; 66 this.mTitle = title; 67 mChecked = checked; 68 } 69 70 public long getGroupId() { 71 return mGroupId; 72 } 73 74 public boolean isChecked() { 75 return mChecked; 76 } 77 78 public void setChecked(boolean checked) { 79 mChecked = checked; 80 } 81 82 @Override 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 95 public GroupMembershipAdapter(Context context, int textViewResourceId) { 96 super(context, textViewResourceId); 97 } 98 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 105 public int getItemViewType(int position) { 106 return getItemIsCheckable(position) ? 0 : 1; 107 } 108 109 @Override 110 public int getViewTypeCount() { 111 return 2; 112 } 113 114 @Override 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 148 public GroupMembershipView(Context context) { 149 super(context); 150 } 151 152 public GroupMembershipView(Context context, AttributeSet attrs) { 153 super(context, attrs); 154 } 155 156 @Override 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 166 public void setEnabled(boolean enabled) { 167 super.setEnabled(enabled); 168 if (mGroupList != null) { 169 mGroupList.setEnabled(enabled); 170 } 171 } 172 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 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 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 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 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 330 protected void onDetachedFromWindow() { 331 super.onDetachedFromWindow(); 332 if (mPopup != null) { 333 mPopup.dismiss(); 334 mPopup = null; 335 } 336 } 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(); 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 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 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