Home | History | Annotate | Download | only in editor
      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.interactions.GroupCreationDialogFragment;
     41 import com.android.contacts.interactions.GroupCreationDialogFragment.OnGroupCreatedListener;
     42 import com.android.contacts.model.RawContactModifier;
     43 import com.android.contacts.model.RawContactDelta;
     44 import com.android.contacts.model.RawContactDelta.ValuesDelta;
     45 import com.android.contacts.model.dataitem.DataKind;
     46 import com.android.internal.util.Objects;
     47 
     48 import 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  */
     54 public 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