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 GroupSelectionItem(long groupId, String title, boolean checked)65 public GroupSelectionItem(long groupId, String title, boolean checked) { 66 this.mGroupId = groupId; 67 this.mTitle = title; 68 mChecked = checked; 69 } 70 getGroupId()71 public long getGroupId() { 72 return mGroupId; 73 } 74 isChecked()75 public boolean isChecked() { 76 return mChecked; 77 } 78 setChecked(boolean checked)79 public void setChecked(boolean checked) { 80 mChecked = checked; 81 } 82 83 @Override toString()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 GroupMembershipAdapter(Context context, int textViewResourceId)96 public GroupMembershipAdapter(Context context, int textViewResourceId) { 97 super(context, textViewResourceId); 98 } 99 getItemIsCheckable(int position)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 getItemViewType(int position)106 public int getItemViewType(int position) { 107 return getItemIsCheckable(position) ? 0 : 1; 108 } 109 110 @Override getViewTypeCount()111 public int getViewTypeCount() { 112 return 2; 113 } 114 115 @Override getView(int position, View convertView, ViewGroup parent)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 GroupMembershipView(Context context)149 public GroupMembershipView(Context context) { 150 super(context); 151 } 152 GroupMembershipView(Context context, AttributeSet attrs)153 public GroupMembershipView(Context context, AttributeSet attrs) { 154 super(context, attrs); 155 } 156 157 @Override onFinishInflate()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 setEnabled(boolean enabled)167 public void setEnabled(boolean enabled) { 168 super.setEnabled(enabled); 169 if (mGroupList != null) { 170 mGroupList.setEnabled(enabled); 171 } 172 } 173 setKind(DataKind kind)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 setGroupMetaData(Cursor groupMetaData)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 != null && !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 setState(RawContactDelta state)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 updateView()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 onClick(View v)283 public void onClick(View v) { 284 if (UiClosables.closeQuietly(mPopup)) { 285 mPopup = null; 286 return; 287 } 288 289 mAdapter = new GroupMembershipAdapter<GroupSelectionItem>( 290 getContext(), R.layout.group_membership_list_item); 291 292 mGroupMetaData.moveToPosition(-1); 293 while (mGroupMetaData.moveToNext()) { 294 String accountName = mGroupMetaData.getString(GroupMetaDataLoader.ACCOUNT_NAME); 295 String accountType = mGroupMetaData.getString(GroupMetaDataLoader.ACCOUNT_TYPE); 296 String dataSet = mGroupMetaData.getString(GroupMetaDataLoader.DATA_SET); 297 if (accountName.equals(mAccountName) && accountType.equals(mAccountType) 298 && Objects.equal(dataSet, mDataSet)) { 299 long groupId = mGroupMetaData.getLong(GroupMetaDataLoader.GROUP_ID); 300 if (groupId != mFavoritesGroupId 301 && (groupId != mDefaultGroupId || mDefaultGroupVisible)) { 302 String title = mGroupMetaData.getString(GroupMetaDataLoader.TITLE); 303 boolean checked = hasMembership(groupId); 304 mAdapter.add(new GroupSelectionItem(groupId, title, checked)); 305 } 306 } 307 } 308 309 mAdapter.add(new GroupSelectionItem(CREATE_NEW_GROUP_GROUP_ID, 310 getContext().getString(R.string.create_group_item_label), false)); 311 312 mPopup = new ListPopupWindow(getContext(), null); 313 mPopup.setAnchorView(mGroupList); 314 mPopup.setAdapter(mAdapter); 315 mPopup.setModal(true); 316 mPopup.setInputMethodMode(ListPopupWindow.INPUT_METHOD_NOT_NEEDED); 317 mPopup.show(); 318 319 ListView listView = mPopup.getListView(); 320 listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); 321 listView.setOverScrollMode(OVER_SCROLL_ALWAYS); 322 int count = mAdapter.getCount(); 323 for (int i = 0; i < count; i++) { 324 listView.setItemChecked(i, mAdapter.getItem(i).isChecked()); 325 } 326 327 listView.setOnItemClickListener(this); 328 } 329 330 @Override onDetachedFromWindow()331 protected void onDetachedFromWindow() { 332 super.onDetachedFromWindow(); 333 UiClosables.closeQuietly(mPopup); 334 mPopup = null; 335 } 336 337 @Override onItemClick(AdapterView<?> parent, View view, int position, long id)338 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 339 ListView list = (ListView) parent; 340 int count = mAdapter.getCount(); 341 342 if (list.isItemChecked(count - 1)) { 343 list.setItemChecked(count - 1, false); 344 createNewGroup(); 345 return; 346 } 347 348 for (int i = 0; i < count; i++) { 349 mAdapter.getItem(i).setChecked(list.isItemChecked(i)); 350 } 351 352 // First remove the memberships that have been unchecked 353 ArrayList<ValuesDelta> entries = mState.getMimeEntries(GroupMembership.CONTENT_ITEM_TYPE); 354 if (entries != null) { 355 for (ValuesDelta entry : entries) { 356 if (!entry.isDelete()) { 357 Long groupId = entry.getGroupRowId(); 358 if (groupId != null && groupId != mFavoritesGroupId 359 && (groupId != mDefaultGroupId || mDefaultGroupVisible) 360 && !isGroupChecked(groupId)) { 361 entry.markDeleted(); 362 } 363 } 364 } 365 } 366 367 // Now add the newly selected items 368 for (int i = 0; i < count; i++) { 369 GroupSelectionItem item = mAdapter.getItem(i); 370 long groupId = item.getGroupId(); 371 if (item.isChecked() && !hasMembership(groupId)) { 372 ValuesDelta entry = RawContactModifier.insertChild(mState, mKind); 373 if (entry != null) { 374 entry.setGroupRowId(groupId); 375 } 376 } 377 } 378 379 updateView(); 380 } 381 isGroupChecked(long groupId)382 private boolean isGroupChecked(long groupId) { 383 int count = mAdapter.getCount(); 384 for (int i = 0; i < count; i++) { 385 GroupSelectionItem item = mAdapter.getItem(i); 386 if (groupId == item.getGroupId()) { 387 return item.isChecked(); 388 } 389 } 390 return false; 391 } 392 hasMembership(long groupId)393 private boolean hasMembership(long groupId) { 394 if (groupId == mDefaultGroupId && mState.isContactInsert()) { 395 return true; 396 } 397 398 ArrayList<ValuesDelta> entries = mState.getMimeEntries(GroupMembership.CONTENT_ITEM_TYPE); 399 if (entries != null) { 400 for (ValuesDelta values : entries) { 401 if (!values.isDelete()) { 402 Long id = values.getGroupRowId(); 403 if (id != null && id == groupId) { 404 return true; 405 } 406 } 407 } 408 } 409 return false; 410 } 411 createNewGroup()412 private void createNewGroup() { 413 UiClosables.closeQuietly(mPopup); 414 mPopup = null; 415 416 GroupCreationDialogFragment.show( 417 ((Activity) getContext()).getFragmentManager(), 418 mAccountType, 419 mAccountName, 420 mDataSet, 421 new OnGroupCreatedListener() { 422 @Override 423 public void onGroupCreated() { 424 mCreatedNewGroup = true; 425 } 426 }); 427 } 428 429 } 430