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