1 /*
2  * Copyright (C) 2015 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 package com.android.messaging.ui.conversationlist;
17 
18 import androidx.collection.ArrayMap;
19 import android.text.TextUtils;
20 import android.view.ActionMode;
21 import android.view.ActionMode.Callback;
22 import android.view.Menu;
23 import android.view.MenuItem;
24 
25 import com.android.messaging.R;
26 import com.android.messaging.datamodel.data.ConversationListData;
27 import com.android.messaging.datamodel.data.ConversationListItemData;
28 import com.android.messaging.util.Assert;
29 
30 import java.util.Collection;
31 import java.util.HashSet;
32 
33 public class MultiSelectActionModeCallback implements Callback {
34     private HashSet<String> mBlockedSet;
35 
36     public interface Listener {
onActionBarDelete(Collection<SelectedConversation> conversations)37         void onActionBarDelete(Collection<SelectedConversation> conversations);
onActionBarArchive(Iterable<SelectedConversation> conversations, boolean isToArchive)38         void onActionBarArchive(Iterable<SelectedConversation> conversations,
39                 boolean isToArchive);
onActionBarNotification(Iterable<SelectedConversation> conversations, boolean isNotificationOn)40         void onActionBarNotification(Iterable<SelectedConversation> conversations,
41                 boolean isNotificationOn);
onActionBarAddContact(final SelectedConversation conversation)42         void onActionBarAddContact(final SelectedConversation conversation);
onActionBarBlock(final SelectedConversation conversation)43         void onActionBarBlock(final SelectedConversation conversation);
onActionBarHome()44         void onActionBarHome();
45     }
46 
47     static class SelectedConversation {
48         public final String conversationId;
49         public final long timestamp;
50         public final String icon;
51         public final String otherParticipantNormalizedDestination;
52         public final CharSequence participantLookupKey;
53         public final boolean isGroup;
54         public final boolean isArchived;
55         public final boolean notificationEnabled;
SelectedConversation(ConversationListItemData data)56         public SelectedConversation(ConversationListItemData data) {
57             conversationId = data.getConversationId();
58             timestamp = data.getTimestamp();
59             icon = data.getIcon();
60             otherParticipantNormalizedDestination = data.getOtherParticipantNormalizedDestination();
61             participantLookupKey = data.getParticipantLookupKey();
62             isGroup = data.getIsGroup();
63             isArchived = data.getIsArchived();
64             notificationEnabled = data.getNotificationEnabled();
65         }
66     }
67 
68     private final ArrayMap<String, SelectedConversation> mSelectedConversations;
69 
70     private Listener mListener;
71     private MenuItem mArchiveMenuItem;
72     private MenuItem mUnarchiveMenuItem;
73     private MenuItem mAddContactMenuItem;
74     private MenuItem mBlockMenuItem;
75     private MenuItem mNotificationOnMenuItem;
76     private MenuItem mNotificationOffMenuItem;
77     private boolean mHasInflated;
78 
MultiSelectActionModeCallback(final Listener listener)79     public MultiSelectActionModeCallback(final Listener listener) {
80         mListener = listener;
81         mSelectedConversations = new ArrayMap<>();
82 
83     }
84 
85     @Override
onCreateActionMode(ActionMode actionMode, Menu menu)86     public boolean onCreateActionMode(ActionMode actionMode, Menu menu) {
87         actionMode.getMenuInflater().inflate(R.menu.conversation_list_fragment_select_menu, menu);
88         mArchiveMenuItem = menu.findItem(R.id.action_archive);
89         mUnarchiveMenuItem = menu.findItem(R.id.action_unarchive);
90         mAddContactMenuItem = menu.findItem(R.id.action_add_contact);
91         mBlockMenuItem = menu.findItem(R.id.action_block);
92         mNotificationOffMenuItem = menu.findItem(R.id.action_notification_off);
93         mNotificationOnMenuItem = menu.findItem(R.id.action_notification_on);
94         mHasInflated = true;
95         updateActionIconsVisiblity();
96         return true;
97     }
98 
99     @Override
onPrepareActionMode(ActionMode actionMode, Menu menu)100     public boolean onPrepareActionMode(ActionMode actionMode, Menu menu) {
101         return true;
102     }
103 
104     @Override
onActionItemClicked(ActionMode actionMode, MenuItem menuItem)105     public boolean onActionItemClicked(ActionMode actionMode, MenuItem menuItem) {
106         switch(menuItem.getItemId()) {
107             case R.id.action_delete:
108                 mListener.onActionBarDelete(mSelectedConversations.values());
109                 return true;
110             case R.id.action_archive:
111                 mListener.onActionBarArchive(mSelectedConversations.values(), true);
112                 return true;
113             case R.id.action_unarchive:
114                 mListener.onActionBarArchive(mSelectedConversations.values(), false);
115                 return true;
116             case R.id.action_notification_off:
117                 mListener.onActionBarNotification(mSelectedConversations.values(), false);
118                 return true;
119             case R.id.action_notification_on:
120                 mListener.onActionBarNotification(mSelectedConversations.values(), true);
121                 return true;
122             case R.id.action_add_contact:
123                 Assert.isTrue(mSelectedConversations.size() == 1);
124                 mListener.onActionBarAddContact(mSelectedConversations.valueAt(0));
125                 return true;
126             case R.id.action_block:
127                 Assert.isTrue(mSelectedConversations.size() == 1);
128                 mListener.onActionBarBlock(mSelectedConversations.valueAt(0));
129                 return true;
130             case android.R.id.home:
131                 mListener.onActionBarHome();
132                 return true;
133             default:
134                 return false;
135         }
136     }
137 
138     @Override
onDestroyActionMode(ActionMode actionMode)139     public void onDestroyActionMode(ActionMode actionMode) {
140         mListener = null;
141         mSelectedConversations.clear();
142         mHasInflated = false;
143     }
144 
toggleSelect(final ConversationListData listData, final ConversationListItemData conversationListItemData)145     public void toggleSelect(final ConversationListData listData,
146                              final ConversationListItemData conversationListItemData) {
147         Assert.notNull(conversationListItemData);
148         mBlockedSet = listData.getBlockedParticipants();
149         final String id = conversationListItemData.getConversationId();
150         if (mSelectedConversations.containsKey(id)) {
151             mSelectedConversations.remove(id);
152         } else {
153             mSelectedConversations.put(id, new SelectedConversation(conversationListItemData));
154         }
155 
156         if (mSelectedConversations.isEmpty()) {
157             mListener.onActionBarHome();
158         } else {
159             updateActionIconsVisiblity();
160         }
161     }
162 
isSelected(final String selectedId)163     public boolean isSelected(final String selectedId) {
164         return mSelectedConversations.containsKey(selectedId);
165     }
166 
updateActionIconsVisiblity()167     private void updateActionIconsVisiblity() {
168         if (!mHasInflated) {
169             return;
170         }
171 
172         if (mSelectedConversations.size() == 1) {
173             final SelectedConversation conversation = mSelectedConversations.valueAt(0);
174             // The look up key is a key given to us by contacts app, so if we have a look up key,
175             // we know that the participant is already in contacts.
176             final boolean isInContacts = !TextUtils.isEmpty(conversation.participantLookupKey);
177             mAddContactMenuItem.setVisible(!conversation.isGroup && !isInContacts);
178             // ParticipantNormalizedDestination is always null for group conversations.
179             final String otherParticipant = conversation.otherParticipantNormalizedDestination;
180             mBlockMenuItem.setVisible(otherParticipant != null
181                     && !mBlockedSet.contains(otherParticipant));
182         } else {
183             mBlockMenuItem.setVisible(false);
184             mAddContactMenuItem.setVisible(false);
185         }
186 
187         boolean hasCurrentlyArchived = false;
188         boolean hasCurrentlyUnarchived = false;
189         boolean hasCurrentlyOnNotification = false;
190         boolean hasCurrentlyOffNotification = false;
191         final Iterable<SelectedConversation> conversations = mSelectedConversations.values();
192         for (final SelectedConversation conversation : conversations) {
193             if (conversation.notificationEnabled) {
194                 hasCurrentlyOnNotification = true;
195             } else {
196                 hasCurrentlyOffNotification = true;
197             }
198 
199             if (conversation.isArchived) {
200                 hasCurrentlyArchived = true;
201             } else {
202                 hasCurrentlyUnarchived = true;
203             }
204 
205             // If we found at least one of each example we don't need to keep looping.
206             if (hasCurrentlyOffNotification && hasCurrentlyOnNotification &&
207                     hasCurrentlyArchived && hasCurrentlyUnarchived) {
208                 break;
209             }
210         }
211         // If we have notification off conversations we show on button, if we have notification on
212         // conversation we show off button. We can show both if we have a mixture.
213         mNotificationOffMenuItem.setVisible(hasCurrentlyOnNotification);
214         mNotificationOnMenuItem.setVisible(hasCurrentlyOffNotification);
215 
216         mArchiveMenuItem.setVisible(hasCurrentlyUnarchived);
217         mUnarchiveMenuItem.setVisible(hasCurrentlyArchived);
218     }
219 }
220