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