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 android.app.Activity;
19 import android.app.AlertDialog;
20 import android.app.Fragment;
21 import android.content.Context;
22 import android.content.DialogInterface;
23 import android.content.Intent;
24 import android.content.res.Resources;
25 import android.net.Uri;
26 import android.os.Bundle;
27 import androidx.annotation.NonNull;
28 import android.view.View;
29 
30 import com.android.messaging.R;
31 import com.android.messaging.datamodel.action.DeleteConversationAction;
32 import com.android.messaging.datamodel.action.UpdateConversationArchiveStatusAction;
33 import com.android.messaging.datamodel.action.UpdateConversationOptionsAction;
34 import com.android.messaging.datamodel.action.UpdateDestinationBlockedAction;
35 import com.android.messaging.datamodel.data.ConversationListData;
36 import com.android.messaging.datamodel.data.ConversationListItemData;
37 import com.android.messaging.ui.BugleActionBarActivity;
38 import com.android.messaging.ui.SnackBar;
39 import com.android.messaging.ui.SnackBarInteraction;
40 import com.android.messaging.ui.UIIntents;
41 import com.android.messaging.ui.contact.AddContactsConfirmationDialog;
42 import com.android.messaging.ui.conversationlist.ConversationListFragment.ConversationListFragmentHost;
43 import com.android.messaging.ui.conversationlist.MultiSelectActionModeCallback.SelectedConversation;
44 import com.android.messaging.util.BugleGservices;
45 import com.android.messaging.util.BugleGservicesKeys;
46 import com.android.messaging.util.DebugUtils;
47 import com.android.messaging.util.PhoneUtils;
48 import com.android.messaging.util.Trace;
49 import com.android.messaging.util.UiUtils;
50 
51 import java.util.ArrayList;
52 import java.util.Collection;
53 import java.util.List;
54 
55 import javax.annotation.Nullable;
56 
57 /**
58  * Base class for many Conversation List activities. This will handle the common actions of multi
59  * select and common launching of intents.
60  */
61 public abstract class AbstractConversationListActivity  extends BugleActionBarActivity
62     implements ConversationListFragmentHost, MultiSelectActionModeCallback.Listener {
63 
64     private static final int REQUEST_SET_DEFAULT_SMS_APP = 1;
65 
66     protected ConversationListFragment mConversationListFragment;
67 
68     @Override
onAttachFragment(final Fragment fragment)69     public void onAttachFragment(final Fragment fragment) {
70         Trace.beginSection("AbstractConversationListActivity.onAttachFragment");
71         // Fragment could be debug dialog
72         if (fragment instanceof ConversationListFragment) {
73             mConversationListFragment = (ConversationListFragment) fragment;
74             mConversationListFragment.setHost(this);
75         }
76         Trace.endSection();
77     }
78 
79     @Override
onBackPressed()80     public void onBackPressed() {
81         // If action mode is active dismiss it
82         if (getActionMode() != null) {
83             dismissActionMode();
84             return;
85         }
86         super.onBackPressed();
87     }
88 
startMultiSelectActionMode()89     protected void startMultiSelectActionMode() {
90         startActionMode(new MultiSelectActionModeCallback(this));
91     }
92 
exitMultiSelectState()93     protected void exitMultiSelectState() {
94         mConversationListFragment.showFab();
95         dismissActionMode();
96         mConversationListFragment.updateUi();
97     }
98 
isInConversationListSelectMode()99     protected boolean isInConversationListSelectMode() {
100         return getActionModeCallback() instanceof MultiSelectActionModeCallback;
101     }
102 
103     @Override
isSelectionMode()104     public boolean isSelectionMode() {
105         return isInConversationListSelectMode();
106     }
107 
108     @SuppressWarnings("MissingSuperCall") // TODO: fix me
109     @Override
onActivityResult(final int requestCode, final int resultCode, final Intent data)110     public void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
111     }
112 
113     @Override
onActionBarDelete(final Collection<SelectedConversation> conversations)114     public void onActionBarDelete(final Collection<SelectedConversation> conversations) {
115         if (!PhoneUtils.getDefault().isDefaultSmsApp()) {
116             // TODO: figure out a good way to combine this with the implementation in
117             // ConversationFragment doing similar things
118             final Activity activity = this;
119             UiUtils.showSnackBarWithCustomAction(this,
120                     getWindow().getDecorView().getRootView(),
121                     getString(R.string.requires_default_sms_app),
122                     SnackBar.Action.createCustomAction(new Runnable() {
123                             @Override
124                             public void run() {
125                                 final Intent intent =
126                                         UIIntents.get().getChangeDefaultSmsAppIntent(activity);
127                                 startActivityForResult(intent, REQUEST_SET_DEFAULT_SMS_APP);
128                             }
129                         },
130                         getString(R.string.requires_default_sms_change_button)),
131                     null /* interactions */,
132                     null /* placement */);
133             return;
134         }
135 
136         new AlertDialog.Builder(this)
137                 .setTitle(getResources().getQuantityString(
138                         R.plurals.delete_conversations_confirmation_dialog_title,
139                         conversations.size()))
140                 .setPositiveButton(R.string.delete_conversation_confirmation_button,
141                         new DialogInterface.OnClickListener() {
142                             @Override
143                             public void onClick(final DialogInterface dialog,
144                                     final int button) {
145                                 for (final SelectedConversation conversation : conversations) {
146                                     DeleteConversationAction.deleteConversation(
147                                             conversation.conversationId,
148                                             conversation.timestamp);
149                                 }
150                                 exitMultiSelectState();
151                             }
152                 })
153                 .setNegativeButton(R.string.delete_conversation_decline_button, null)
154                 .show();
155     }
156 
157     @Override
onActionBarArchive(final Iterable<SelectedConversation> conversations, final boolean isToArchive)158     public void onActionBarArchive(final Iterable<SelectedConversation> conversations,
159             final boolean isToArchive) {
160         final ArrayList<String> conversationIds = new ArrayList<String>();
161         for (final SelectedConversation conversation : conversations) {
162             final String conversationId = conversation.conversationId;
163             conversationIds.add(conversationId);
164             if (isToArchive) {
165                 UpdateConversationArchiveStatusAction.archiveConversation(conversationId);
166             } else {
167                 UpdateConversationArchiveStatusAction.unarchiveConversation(conversationId);
168             }
169         }
170 
171         final Runnable undoRunnable = new Runnable() {
172             @Override
173             public void run() {
174                 for (final String conversationId : conversationIds) {
175                     if (isToArchive) {
176                         UpdateConversationArchiveStatusAction.unarchiveConversation(conversationId);
177                     } else {
178                         UpdateConversationArchiveStatusAction.archiveConversation(conversationId);
179                     }
180                 }
181             }
182         };
183 
184         final int textId =
185                 isToArchive ? R.string.archived_toast_message : R.string.unarchived_toast_message;
186         final String message = getResources().getString(textId, conversationIds.size());
187         UiUtils.showSnackBar(this, findViewById(android.R.id.list), message, undoRunnable,
188                 SnackBar.Action.SNACK_BAR_UNDO,
189                 mConversationListFragment.getSnackBarInteractions());
190         exitMultiSelectState();
191     }
192 
193     @Override
onActionBarNotification(final Iterable<SelectedConversation> conversations, final boolean isNotificationOn)194     public void onActionBarNotification(final Iterable<SelectedConversation> conversations,
195             final boolean isNotificationOn) {
196         for (final SelectedConversation conversation : conversations) {
197             UpdateConversationOptionsAction.enableConversationNotifications(
198                     conversation.conversationId, isNotificationOn);
199         }
200 
201         final int textId = isNotificationOn ?
202                 R.string.notification_on_toast_message : R.string.notification_off_toast_message;
203         final String message = getResources().getString(textId, 1);
204         UiUtils.showSnackBar(this, findViewById(android.R.id.list), message,
205             null /* undoRunnable */,
206             SnackBar.Action.SNACK_BAR_UNDO, mConversationListFragment.getSnackBarInteractions());
207         exitMultiSelectState();
208     }
209 
210     @Override
onActionBarAddContact(final SelectedConversation conversation)211     public void onActionBarAddContact(final SelectedConversation conversation) {
212         final Uri avatarUri;
213         if (conversation.icon != null) {
214             avatarUri = Uri.parse(conversation.icon);
215         } else {
216             avatarUri = null;
217         }
218         final AddContactsConfirmationDialog dialog = new AddContactsConfirmationDialog(
219                 this, avatarUri, conversation.otherParticipantNormalizedDestination);
220         dialog.show();
221         exitMultiSelectState();
222     }
223 
224     @Override
onActionBarBlock(final SelectedConversation conversation)225     public void onActionBarBlock(final SelectedConversation conversation) {
226         final Resources res = getResources();
227         new AlertDialog.Builder(this)
228                 .setTitle(res.getString(R.string.block_confirmation_title,
229                         conversation.otherParticipantNormalizedDestination))
230                 .setMessage(res.getString(R.string.block_confirmation_message))
231                 .setNegativeButton(android.R.string.cancel, null)
232                 .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
233                     @Override
234                     public void onClick(final DialogInterface arg0, final int arg1) {
235                         final Context context = AbstractConversationListActivity.this;
236                         final View listView = findViewById(android.R.id.list);
237                         final List<SnackBarInteraction> interactions =
238                                 mConversationListFragment.getSnackBarInteractions();
239                         final UpdateDestinationBlockedAction.UpdateDestinationBlockedActionListener
240                                 undoListener =
241                                         new UpdateDestinationBlockedActionSnackBar(
242                                                 context, listView, null /* undoRunnable */,
243                                                 interactions);
244                         final Runnable undoRunnable = new Runnable() {
245                             @Override
246                             public void run() {
247                                 UpdateDestinationBlockedAction.updateDestinationBlocked(
248                                         conversation.otherParticipantNormalizedDestination, false,
249                                         conversation.conversationId,
250                                         undoListener);
251                             }
252                         };
253                         final UpdateDestinationBlockedAction.UpdateDestinationBlockedActionListener
254                               listener = new UpdateDestinationBlockedActionSnackBar(
255                                       context, listView, undoRunnable, interactions);
256                         UpdateDestinationBlockedAction.updateDestinationBlocked(
257                                 conversation.otherParticipantNormalizedDestination, true,
258                                 conversation.conversationId,
259                                 listener);
260                         exitMultiSelectState();
261                     }
262                 })
263                 .create()
264                 .show();
265     }
266 
267     @Override
onConversationClick(final ConversationListData listData, final ConversationListItemData conversationListItemData, final boolean isLongClick, final ConversationListItemView conversationView)268     public void onConversationClick(final ConversationListData listData,
269                                     final ConversationListItemData conversationListItemData,
270                                     final boolean isLongClick,
271                                     final ConversationListItemView conversationView) {
272         if (isLongClick && !isInConversationListSelectMode()) {
273             startMultiSelectActionMode();
274         }
275 
276         if (isInConversationListSelectMode()) {
277             final MultiSelectActionModeCallback multiSelectActionMode =
278                     (MultiSelectActionModeCallback) getActionModeCallback();
279             multiSelectActionMode.toggleSelect(listData, conversationListItemData);
280             mConversationListFragment.updateUi();
281         } else {
282             final String conversationId = conversationListItemData.getConversationId();
283             Bundle sceneTransitionAnimationOptions = null;
284             boolean hasCustomTransitions = false;
285 
286             UIIntents.get().launchConversationActivity(
287                     this, conversationId, null,
288                     sceneTransitionAnimationOptions,
289                     hasCustomTransitions);
290         }
291     }
292 
293     @Override
onCreateConversationClick()294     public void onCreateConversationClick() {
295         UIIntents.get().launchCreateNewConversationActivity(this, null);
296     }
297 
298 
299     @Override
isConversationSelected(final String conversationId)300     public boolean isConversationSelected(final String conversationId) {
301         return isInConversationListSelectMode() &&
302                 ((MultiSelectActionModeCallback) getActionModeCallback()).isSelected(
303                         conversationId);
304     }
305 
onActionBarDebug()306     public void onActionBarDebug() {
307         DebugUtils.showDebugOptions(this);
308     }
309 
310     private static class UpdateDestinationBlockedActionSnackBar
311             implements UpdateDestinationBlockedAction.UpdateDestinationBlockedActionListener {
312         private final Context mContext;
313         private final View mParentView;
314         private final Runnable mUndoRunnable;
315         private final List<SnackBarInteraction> mInteractions;
316 
UpdateDestinationBlockedActionSnackBar(final Context context, @NonNull final View parentView, @Nullable final Runnable undoRunnable, @Nullable List<SnackBarInteraction> interactions)317         UpdateDestinationBlockedActionSnackBar(final Context context,
318                 @NonNull final View parentView, @Nullable final Runnable undoRunnable,
319                 @Nullable List<SnackBarInteraction> interactions) {
320             mContext = context;
321             mParentView = parentView;
322             mUndoRunnable = undoRunnable;
323             mInteractions = interactions;
324         }
325 
326         @Override
onUpdateDestinationBlockedAction( final UpdateDestinationBlockedAction action, final boolean success, final boolean block, final String destination)327         public void onUpdateDestinationBlockedAction(
328             final UpdateDestinationBlockedAction action,
329             final boolean success, final boolean block,
330             final String destination) {
331             if (success) {
332                 final int messageId = block ? R.string.blocked_toast_message
333                         : R.string.unblocked_toast_message;
334                 final String message = mContext.getResources().getString(messageId, 1);
335                 UiUtils.showSnackBar(mContext, mParentView, message, mUndoRunnable,
336                         SnackBar.Action.SNACK_BAR_UNDO, mInteractions);
337             }
338         }
339     }
340 }
341