/* Copyright © 2013-2014, Silent Circle, LLC. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Any redistribution, use, or modification is done solely for personal benefit and not for any commercial purpose or for monetary gain * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name Silent Circle nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SILENT CIRCLE, LLC BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.silentcircle.contacts.editor; import android.annotation.TargetApi; import android.app.AlertDialog; import android.app.Dialog; import android.content.Context; import android.content.res.Resources; import android.database.Cursor; import android.os.Build; import android.os.Bundle; import android.support.v4.app.DialogFragment; import android.support.v4.app.FragmentManager; import android.text.TextUtils; import android.util.AttributeSet; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.ArrayAdapter; import android.widget.CheckedTextView; import android.widget.LinearLayout; import android.widget.ListPopupWindow; import android.widget.ListView; import android.widget.TextView; import com.actionbarsherlock.app.SherlockFragmentActivity; import com.silentcircle.contacts.GroupMetaDataLoader; import com.silentcircle.contacts.interactions.GroupCreationDialogFragment; import com.silentcircle.contacts.model.RawContactDelta; import com.silentcircle.contacts.model.dataitem.DataKind; import com.silentcircle.contacts.R; import com.silentcircle.silentcontacts.ScContactsContract.CommonDataKinds.GroupMembership; import com.silentcircle.contacts.model.RawContactModifier; //import com.android.internal.util.Objects; import java.util.ArrayList; /** * An editor for group membership. Displays the current group membership list and * brings up a dialog to change it. */ public class GroupMembershipView extends LinearLayout implements OnClickListener, OnItemClickListener { private static final String TAG = "GroupMembershipView"; private static final int CREATE_NEW_GROUP_GROUP_ID = 133; public static final class GroupSelectionItem { private final long mGroupId; private final String mTitle; private boolean mChecked; public GroupSelectionItem(long groupId, String title, boolean checked) { this.mGroupId = groupId; this.mTitle = title; mChecked = checked; } public long getGroupId() { return mGroupId; } public boolean isChecked() { return mChecked; } public void setChecked(boolean checked) { mChecked = checked; } @Override public String toString() { return mTitle; } } /** * Extends the array adapter to show checkmarks on all but the last list item for * the group membership popup. Note that this is highly specific to the fact that the * group_membership_list_item.xml is a CheckedTextView object. */ private class GroupMembershipAdapter<T> extends ArrayAdapter<T> { public GroupMembershipAdapter(Context context, int textViewResourceId) { super(context, textViewResourceId); } public boolean getItemIsCheckable(int position) { // Item is checkable if it is NOT the last one in the list return position != getCount()-1; } @Override public int getItemViewType(int position) { return getItemIsCheckable(position) ? 0 : 1; } @Override public int getViewTypeCount() { return 2; } @Override public View getView(int position, View convertView, ViewGroup parent) { final View itemView = super.getView(position, convertView, parent); // Hide the checkable drawable. This assumes that the item views // are CheckedTextView objects final CheckedTextView checkedTextView = (CheckedTextView)itemView; if (!getItemIsCheckable(position)) { checkedTextView.setCheckMarkDrawable(null); } return checkedTextView; } } private RawContactDelta mState; private Cursor mGroupMetaData; // private String mAccountName; // private String mAccountType; private String mDataSet; private TextView mGroupList; private GroupMembershipAdapter<GroupSelectionItem> mAdapter; private long mDefaultGroupId; private long mFavoritesGroupId; private ListPopupWindow mPopup; private DataKind mKind; private boolean mDefaultGroupVisibilityKnown; private boolean mDefaultGroupVisible; private boolean mCreatedNewGroup; private String mNoGroupString; private int mPrimaryTextColor; private int mSecondaryTextColor; private Context mContext; private ContactEditorFragment parent; public GroupMembershipView(Context context) { super(context); mContext = context; } public GroupMembershipView(Context context, AttributeSet attrs) { super(context, attrs); mContext = context; } @Override protected void onFinishInflate() { super.onFinishInflate(); Resources resources = mContext.getResources(); mPrimaryTextColor = resources.getColor(R.color.primary_text_color); mSecondaryTextColor = resources.getColor(R.color.secondary_text_color); mNoGroupString = mContext.getString(R.string.group_edit_field_hint_text); } @Override public void setEnabled(boolean enabled) { super.setEnabled(enabled); if (mGroupList != null) { mGroupList.setEnabled(enabled); } } public void setParent(ContactEditorFragment p) { parent = p; } public void setKind(DataKind kind) { mKind = kind; TextView kindTitle = (TextView) findViewById(R.id.kind_title); kindTitle.setText(getResources().getString(kind.titleRes).toUpperCase()); } @TargetApi(Build.VERSION_CODES.HONEYCOMB) public void setGroupMetaData(Cursor groupMetaData) { this.mGroupMetaData = groupMetaData; updateView(); // Open up the list of groups if a new group was just created. if (mCreatedNewGroup) { mCreatedNewGroup = false; onClick(this); // This causes the popup to open. if (mPopup != null || dialogPopup != null) { // Ensure that the newly created group is checked. int position = mAdapter.getCount() - 2; ListView listView = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) ? mPopup.getListView() : GroupCheckPopup.modeList; if (!listView.isItemChecked(position)) { // Newly created group is not checked, so check it. listView.setItemChecked(position, true); onItemClick(listView, null, position, listView.getItemIdAtPosition(position)); } } } } public void setState(RawContactDelta state) { mState = state; mDefaultGroupVisibilityKnown = false; mCreatedNewGroup = false; updateView(); } private void updateView() { if (mGroupMetaData == null || mGroupMetaData.isClosed()) { setVisibility(GONE); return; } boolean accountHasGroups = false; mFavoritesGroupId = 0; mDefaultGroupId = 0; StringBuilder sb = new StringBuilder(); mGroupMetaData.moveToPosition(-1); while (mGroupMetaData.moveToNext()) { long groupId = mGroupMetaData.getLong(GroupMetaDataLoader.GROUP_ID); if (!mGroupMetaData.isNull(GroupMetaDataLoader.FAVORITES) && mGroupMetaData.getInt(GroupMetaDataLoader.FAVORITES) != 0) { mFavoritesGroupId = groupId; } else if (!mGroupMetaData.isNull(GroupMetaDataLoader.AUTO_ADD) && mGroupMetaData.getInt(GroupMetaDataLoader.AUTO_ADD) != 0) { mDefaultGroupId = groupId; } else { accountHasGroups = true; } // Exclude favorites from the list - they are handled with special UI (star) // Also exclude the default group. if (groupId != mFavoritesGroupId && groupId != mDefaultGroupId && hasMembership(groupId)) { String title = mGroupMetaData.getString(GroupMetaDataLoader.TITLE); if (!TextUtils.isEmpty(title)) { if (sb.length() != 0) { sb.append(", "); } sb.append(title); } } } if (!accountHasGroups) { setVisibility(GONE); return; } if (mGroupList == null) { mGroupList = (TextView)findViewById(R.id.group_list); mGroupList.setOnClickListener(this); } mGroupList.setEnabled(isEnabled()); if (sb.length() == 0) { mGroupList.setText(mNoGroupString); mGroupList.setTextColor(mSecondaryTextColor); } else { mGroupList.setText(sb); mGroupList.setTextColor(mPrimaryTextColor); } setVisibility(VISIBLE); if (!mDefaultGroupVisibilityKnown) { // Only show the default group (My Contacts) if the contact is NOT in it mDefaultGroupVisible = mDefaultGroupId != 0 && !hasMembership(mDefaultGroupId); mDefaultGroupVisibilityKnown = true; } } @TargetApi(Build.VERSION_CODES.HONEYCOMB) @Override public void onClick(View v) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { if (mPopup != null && mPopup.isShowing()) { mPopup.dismiss(); return; } } else { groupCheckDialogDismiss(); } mAdapter = new GroupMembershipAdapter<GroupSelectionItem>(getContext(), R.layout.group_membership_list_item); mGroupMetaData.moveToPosition(-1); while (mGroupMetaData.moveToNext()) { long groupId = mGroupMetaData.getLong(GroupMetaDataLoader.GROUP_ID); if (groupId != mFavoritesGroupId && (groupId != mDefaultGroupId || mDefaultGroupVisible)) { String title = mGroupMetaData.getString(GroupMetaDataLoader.TITLE); boolean checked = hasMembership(groupId); mAdapter.add(new GroupSelectionItem(groupId, title, checked)); } } mAdapter.add(new GroupSelectionItem(CREATE_NEW_GROUP_GROUP_ID, getContext().getString(R.string.create_group_item_label), false)); ListView listView; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { mPopup = new ListPopupWindow(getContext(), null); mPopup.setAnchorView(mGroupList); mPopup.setAdapter(mAdapter); mPopup.setModal(true); mPopup.setInputMethodMode(ListPopupWindow.INPUT_METHOD_NOT_NEEDED); mPopup.show(); listView = mPopup.getListView(); } else { GroupCheckPopup.modeList = listView = new ListView(parent.getActivity()); listView.setAdapter(mAdapter); } listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); listView.setOverScrollMode(OVER_SCROLL_ALWAYS); int count = mAdapter.getCount(); for (int i = 0; i < count; i++) { listView.setItemChecked(i, mAdapter.getItem(i).isChecked()); } if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { groupCheckDialog(mAdapter); } listView.setOnItemClickListener(this); } @TargetApi(Build.VERSION_CODES.HONEYCOMB) @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { if (mPopup != null) { mPopup.dismiss(); mPopup = null; } } else { groupCheckDialogDismiss(); } } @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { ListView list = (ListView) parent; int count = mAdapter.getCount(); if (list.isItemChecked(count - 1)) { list.setItemChecked(count - 1, false); createNewGroup(); return; } for (int i = 0; i < count; i++) { mAdapter.getItem(i).setChecked(list.isItemChecked(i)); } // First remove the memberships that have been unchecked ArrayList<RawContactDelta.ValuesDelta> entries = mState.getMimeEntries(GroupMembership.CONTENT_ITEM_TYPE); if (entries != null) { for (RawContactDelta.ValuesDelta entry : entries) { if (!entry.isDelete()) { Long groupId = entry.getGroupRowId(); if (groupId != null && groupId != mFavoritesGroupId && (groupId != mDefaultGroupId || mDefaultGroupVisible) && !isGroupChecked(groupId)) { entry.markDeleted(); } } } } // Now add the newly selected items for (int i = 0; i < count; i++) { GroupSelectionItem item = mAdapter.getItem(i); long groupId = item.getGroupId(); if (item.isChecked() && !hasMembership(groupId)) { RawContactDelta.ValuesDelta entry = RawContactModifier.insertChild(mState, mKind); entry.setGroupRowId(groupId); } } updateView(); } private boolean isGroupChecked(long groupId) { int count = mAdapter.getCount(); for (int i = 0; i < count; i++) { GroupSelectionItem item = mAdapter.getItem(i); if (groupId == item.getGroupId()) { return item.isChecked(); } } return false; } private boolean hasMembership(long groupId) { if (groupId == mDefaultGroupId && mState.isContactInsert()) { return true; } ArrayList<RawContactDelta.ValuesDelta> entries = mState.getMimeEntries(GroupMembership.CONTENT_ITEM_TYPE); if (entries != null) { for (RawContactDelta.ValuesDelta values : entries) { if (!values.isDelete()) { Long id = values.getGroupRowId(); if (id != null && id == groupId) { Log.d(TAG, "has a membership, id: " + groupId); return true; } } } } return false; } @TargetApi(Build.VERSION_CODES.HONEYCOMB) private void createNewGroup() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { if (mPopup != null) { mPopup.dismiss(); mPopup = null; } } else { groupCheckDialogDismiss(); } GroupCreationDialogFragment.show( ((SherlockFragmentActivity) getContext()).getSupportFragmentManager(), new GroupCreationDialogFragment.OnGroupCreatedListener() { @Override public void onGroupCreated() { mCreatedNewGroup = true; } }); } private GroupCheckPopup dialogPopup; private void groupCheckDialogDismiss() { if (dialogPopup != null && dialogPopup.isVisible()) { dialogPopup.dismiss(); dialogPopup = null; } } private void groupCheckDialog(GroupMembershipAdapter<GroupSelectionItem> a) { if (parent == null) return; groupCheckDialogDismiss(); dialogPopup = GroupCheckPopup.newInstance(); FragmentManager fragmentManager = parent.getFragmentManager(); dialogPopup.show(fragmentManager, "SilentContactsGroupCheck"); } public static class GroupCheckPopup extends DialogFragment { public static ListView modeList; public static GroupCheckPopup newInstance() { GroupCheckPopup f = new GroupCheckPopup(); return f; } @Override public Dialog onCreateDialog(Bundle savedInstanceState) { // Use the Builder class for convenient dialog construction AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.setView(modeList); return builder.create(); } } }