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 com.android.contacts.GroupMetaDataLoader; 20 import com.android.contacts.R; 21 import com.android.contacts.interactions.GroupCreationDialogFragment; 22 import com.android.contacts.model.DataKind; 23 import com.android.contacts.model.EntityDelta; 24 import com.android.contacts.model.EntityDelta.ValuesDelta; 25 import com.android.contacts.model.EntityModifier; 26 import com.android.internal.util.Objects; 27 28 import android.app.Activity; 29 import android.content.Context; 30 import android.content.res.ColorStateList; 31 import android.content.res.Resources; 32 import android.database.Cursor; 33 import android.provider.ContactsContract.CommonDataKinds.GroupMembership; 34 import android.provider.ContactsContract.RawContacts; 35 import android.text.TextUtils; 36 import android.util.AttributeSet; 37 import android.view.View; 38 import android.view.View.OnClickListener; 39 import android.widget.AdapterView; 40 import android.widget.AdapterView.OnItemClickListener; 41 import android.widget.ArrayAdapter; 42 import android.widget.LinearLayout; 43 import android.widget.ListPopupWindow; 44 import android.widget.ListView; 45 import android.widget.TextView; 46 47 import java.util.ArrayList; 48 49 /** 50 * An editor for group membership. Displays the current group membership list and 51 * brings up a dialog to change it. 52 */ 53 public class GroupMembershipView extends LinearLayout 54 implements OnClickListener, OnItemClickListener { 55 56 private static final int CREATE_NEW_GROUP_GROUP_ID = 133; 57 58 public static final class GroupSelectionItem { 59 private final long mGroupId; 60 private final String mTitle; 61 private boolean mChecked; 62 63 public GroupSelectionItem(long groupId, String title, boolean checked) { 64 this.mGroupId = groupId; 65 this.mTitle = title; 66 mChecked = checked; 67 } 68 69 public long getGroupId() { 70 return mGroupId; 71 } 72 73 public boolean isChecked() { 74 return mChecked; 75 } 76 77 public void setChecked(boolean checked) { 78 mChecked = checked; 79 } 80 81 @Override 82 public String toString() { 83 return mTitle; 84 } 85 } 86 87 private EntityDelta mState; 88 private Cursor mGroupMetaData; 89 private String mAccountName; 90 private String mAccountType; 91 private String mDataSet; 92 private TextView mGroupList; 93 private ArrayAdapter<GroupSelectionItem> mAdapter; 94 private long mDefaultGroupId; 95 private long mFavoritesGroupId; 96 private ListPopupWindow mPopup; 97 private DataKind mKind; 98 private boolean mDefaultGroupVisibilityKnown; 99 private boolean mDefaultGroupVisible; 100 101 private String mNoGroupString; 102 private int mPrimaryTextColor; 103 private int mSecondaryTextColor; 104 105 public GroupMembershipView(Context context) { 106 super(context); 107 } 108 109 public GroupMembershipView(Context context, AttributeSet attrs) { 110 super(context, attrs); 111 } 112 113 @Override 114 protected void onFinishInflate() { 115 super.onFinishInflate(); 116 Resources resources = mContext.getResources(); 117 mPrimaryTextColor = resources.getColor(R.color.primary_text_color); 118 mSecondaryTextColor = resources.getColor(R.color.secondary_text_color); 119 mNoGroupString = mContext.getString(R.string.group_edit_field_hint_text); 120 } 121 122 @Override 123 public void setEnabled(boolean enabled) { 124 super.setEnabled(enabled); 125 if (mGroupList != null) { 126 mGroupList.setEnabled(enabled); 127 } 128 } 129 130 public void setKind(DataKind kind) { 131 mKind = kind; 132 TextView kindTitle = (TextView) findViewById(R.id.kind_title); 133 kindTitle.setText(getResources().getString(kind.titleRes).toUpperCase()); 134 } 135 136 public void setGroupMetaData(Cursor groupMetaData) { 137 this.mGroupMetaData = groupMetaData; 138 updateView(); 139 } 140 141 public void setState(EntityDelta state) { 142 mState = state; 143 ValuesDelta values = state.getValues(); 144 mAccountType = values.getAsString(RawContacts.ACCOUNT_TYPE); 145 mAccountName = values.getAsString(RawContacts.ACCOUNT_NAME); 146 mDataSet = values.getAsString(RawContacts.DATA_SET); 147 mDefaultGroupVisibilityKnown = false; 148 updateView(); 149 } 150 151 private void updateView() { 152 if (mGroupMetaData == null || mGroupMetaData.isClosed() || mAccountType == null 153 || mAccountName == null) { 154 setVisibility(GONE); 155 return; 156 } 157 158 boolean accountHasGroups = false; 159 mFavoritesGroupId = 0; 160 mDefaultGroupId = 0; 161 162 StringBuilder sb = new StringBuilder(); 163 mGroupMetaData.moveToPosition(-1); 164 while (mGroupMetaData.moveToNext()) { 165 String accountName = mGroupMetaData.getString(GroupMetaDataLoader.ACCOUNT_NAME); 166 String accountType = mGroupMetaData.getString(GroupMetaDataLoader.ACCOUNT_TYPE); 167 String dataSet = mGroupMetaData.getString(GroupMetaDataLoader.DATA_SET); 168 if (accountName.equals(mAccountName) && accountType.equals(mAccountType) 169 && Objects.equal(dataSet, mDataSet)) { 170 long groupId = mGroupMetaData.getLong(GroupMetaDataLoader.GROUP_ID); 171 if (!mGroupMetaData.isNull(GroupMetaDataLoader.FAVORITES) 172 && mGroupMetaData.getInt(GroupMetaDataLoader.FAVORITES) != 0) { 173 mFavoritesGroupId = groupId; 174 } else if (!mGroupMetaData.isNull(GroupMetaDataLoader.AUTO_ADD) 175 && mGroupMetaData.getInt(GroupMetaDataLoader.AUTO_ADD) != 0) { 176 mDefaultGroupId = groupId; 177 } else { 178 accountHasGroups = true; 179 } 180 181 // Exclude favorites from the list - they are handled with special UI (star) 182 // Also exclude the default group. 183 if (groupId != mFavoritesGroupId && groupId != mDefaultGroupId 184 && hasMembership(groupId)) { 185 String title = mGroupMetaData.getString(GroupMetaDataLoader.TITLE); 186 if (!TextUtils.isEmpty(title)) { 187 if (sb.length() != 0) { 188 sb.append(", "); 189 } 190 sb.append(title); 191 } 192 } 193 } 194 } 195 196 if (!accountHasGroups) { 197 setVisibility(GONE); 198 return; 199 } 200 201 if (mGroupList == null) { 202 mGroupList = (TextView) findViewById(R.id.group_list); 203 mGroupList.setOnClickListener(this); 204 } 205 206 mGroupList.setEnabled(isEnabled()); 207 if (sb.length() == 0) { 208 mGroupList.setText(mNoGroupString); 209 mGroupList.setTextColor(mSecondaryTextColor); 210 } else { 211 mGroupList.setText(sb); 212 mGroupList.setTextColor(mPrimaryTextColor); 213 } 214 setVisibility(VISIBLE); 215 216 if (!mDefaultGroupVisibilityKnown) { 217 // Only show the default group (My Contacts) if the contact is NOT in it 218 mDefaultGroupVisible = mDefaultGroupId != 0 && !hasMembership(mDefaultGroupId); 219 mDefaultGroupVisibilityKnown = true; 220 } 221 } 222 223 @Override 224 public void onClick(View v) { 225 if (mPopup != null && mPopup.isShowing()) { 226 mPopup.dismiss(); 227 return; 228 } 229 230 mAdapter = new ArrayAdapter<GroupSelectionItem>( 231 getContext(), R.layout.group_membership_list_item); 232 233 mGroupMetaData.moveToPosition(-1); 234 while (mGroupMetaData.moveToNext()) { 235 String accountName = mGroupMetaData.getString(GroupMetaDataLoader.ACCOUNT_NAME); 236 String accountType = mGroupMetaData.getString(GroupMetaDataLoader.ACCOUNT_TYPE); 237 String dataSet = mGroupMetaData.getString(GroupMetaDataLoader.DATA_SET); 238 if (accountName.equals(mAccountName) && accountType.equals(mAccountType) 239 && Objects.equal(dataSet, mDataSet)) { 240 long groupId = mGroupMetaData.getLong(GroupMetaDataLoader.GROUP_ID); 241 if (groupId != mFavoritesGroupId 242 && (groupId != mDefaultGroupId || mDefaultGroupVisible)) { 243 String title = mGroupMetaData.getString(GroupMetaDataLoader.TITLE); 244 boolean checked = hasMembership(groupId); 245 mAdapter.add(new GroupSelectionItem(groupId, title, checked)); 246 } 247 } 248 } 249 250 mAdapter.add(new GroupSelectionItem(CREATE_NEW_GROUP_GROUP_ID, 251 getContext().getString(R.string.create_group_item_label), false)); 252 253 mPopup = new ListPopupWindow(getContext(), null); 254 mPopup.setAnchorView(mGroupList); 255 mPopup.setAdapter(mAdapter); 256 mPopup.setModal(true); 257 mPopup.show(); 258 259 ListView listView = mPopup.getListView(); 260 listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); 261 listView.setOverScrollMode(OVER_SCROLL_ALWAYS); 262 int count = mAdapter.getCount(); 263 for (int i = 0; i < count; i++) { 264 listView.setItemChecked(i, mAdapter.getItem(i).isChecked()); 265 } 266 267 listView.setOnItemClickListener(this); 268 } 269 270 @Override 271 protected void onDetachedFromWindow() { 272 super.onDetachedFromWindow(); 273 if (mPopup != null) { 274 mPopup.dismiss(); 275 mPopup = null; 276 } 277 } 278 279 @Override 280 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 281 ListView list = (ListView) parent; 282 int count = mAdapter.getCount(); 283 284 if (list.isItemChecked(count - 1)) { 285 list.setItemChecked(count - 1, false); 286 createNewGroup(); 287 return; 288 } 289 290 for (int i = 0; i < count; i++) { 291 mAdapter.getItem(i).setChecked(list.isItemChecked(i)); 292 } 293 294 // First remove the memberships that have been unchecked 295 ArrayList<ValuesDelta> entries = mState.getMimeEntries(GroupMembership.CONTENT_ITEM_TYPE); 296 if (entries != null) { 297 for (ValuesDelta entry : entries) { 298 if (!entry.isDelete()) { 299 Long groupId = entry.getAsLong(GroupMembership.GROUP_ROW_ID); 300 if (groupId != null && groupId != mFavoritesGroupId 301 && (groupId != mDefaultGroupId || mDefaultGroupVisible) 302 && !isGroupChecked(groupId)) { 303 entry.markDeleted(); 304 } 305 } 306 } 307 } 308 309 // Now add the newly selected items 310 for (int i = 0; i < count; i++) { 311 GroupSelectionItem item = mAdapter.getItem(i); 312 long groupId = item.getGroupId(); 313 if (item.isChecked() && !hasMembership(groupId)) { 314 ValuesDelta entry = EntityModifier.insertChild(mState, mKind); 315 entry.put(GroupMembership.GROUP_ROW_ID, groupId); 316 } 317 } 318 319 updateView(); 320 } 321 322 private boolean isGroupChecked(long groupId) { 323 int count = mAdapter.getCount(); 324 for (int i = 0; i < count; i++) { 325 GroupSelectionItem item = mAdapter.getItem(i); 326 if (groupId == item.getGroupId()) { 327 return item.isChecked(); 328 } 329 } 330 return false; 331 } 332 333 private boolean hasMembership(long groupId) { 334 if (groupId == mDefaultGroupId && mState.isContactInsert()) { 335 return true; 336 } 337 338 ArrayList<ValuesDelta> entries = mState.getMimeEntries(GroupMembership.CONTENT_ITEM_TYPE); 339 if (entries != null) { 340 for (ValuesDelta values : entries) { 341 if (!values.isDelete()) { 342 Long id = values.getAsLong(GroupMembership.GROUP_ROW_ID); 343 if (id != null && id == groupId) { 344 return true; 345 } 346 } 347 } 348 } 349 return false; 350 } 351 352 private void createNewGroup() { 353 if (mPopup != null) { 354 mPopup.dismiss(); 355 mPopup = null; 356 } 357 358 GroupCreationDialogFragment.show( 359 ((Activity) getContext()).getFragmentManager(), mAccountType, mAccountName, 360 mDataSet); 361 } 362 } 363