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     @Override
onActivityResult(final int requestCode, final int resultCode, final Intent data)109     public void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
110     }
111 
112     @Override
onActionBarDelete(final Collection<SelectedConversation> conversations)113     public void onActionBarDelete(final Collection<SelectedConversation> conversations) {
114         if (!PhoneUtils.getDefault().isDefaultSmsApp()) {
115             // TODO: figure out a good way to combine this with the implementation in
116             // ConversationFragment doing similar things
117             final Activity activity = this;
118             UiUtils.showSnackBarWithCustomAction(this,
119                     getWindow().getDecorView().getRootView(),
120                     getString(R.string.requires_default_sms_app),
121                     SnackBar.Action.createCustomAction(new Runnable() {
122                             @Override
123                             public void run() {
124                                 final Intent intent =
125                                         UIIntents.get().getChangeDefaultSmsAppIntent(activity);
126                                 startActivityForResult(intent, REQUEST_SET_DEFAULT_SMS_APP);
127                             }
128                         },
129                         getString(R.string.requires_default_sms_change_button)),
130                     null /* interactions */,
131                     null /* placement */);
132             return;
133         }
134 
135         new AlertDialog.Builder(this)
136                 .setTitle(getResources().getQuantityString(
137                         R.plurals.delete_conversations_confirmation_dialog_title,
138                         conversations.size()))
139                 .setPositiveButton(R.string.delete_conversation_confirmation_button,
140                         new DialogInterface.OnClickListener() {
141                             @Override
142                             public void onClick(final DialogInterface dialog,
143                                     final int button) {
144                                 for (final SelectedConversation conversation : conversations) {
145                                     DeleteConversationAction.deleteConversation(
146                                             conversation.conversationId,
147                                             conversation.timestamp);
148                                 }
149                                 exitMultiSelectState();
150                             }
151                 })
152                 .setNegativeButton(R.string.delete_conversation_decline_button, null)
153                 .show();
154     }
155 
156     @Override
onActionBarArchive(final Iterable<SelectedConversation> conversations, final boolean isToArchive)157     public void onActionBarArchive(final Iterable<SelectedConversation> conversations,
158             final boolean isToArchive) {
159         final ArrayList<String> conversationIds = new ArrayList<String>();
160         for (final SelectedConversation conversation : conversations) {
161             final String conversationId = conversation.conversationId;
162             conversationIds.add(conversationId);
163             if (isToArchive) {
164                 UpdateConversationArchiveStatusAction.archiveConversation(conversationId);
165             } else {
166                 UpdateConversationArchiveStatusAction.unarchiveConversation(conversationId);
167             }
168         }
169 
170         final Runnable undoRunnable = new Runnable() {
171             @Override
172             public void run() {
173                 for (final String conversationId : conversationIds) {
174                     if (isToArchive) {
175                         UpdateConversationArchiveStatusAction.unarchiveConversation(conversationId);
176                     } else {
177                         UpdateConversationArchiveStatusAction.archiveConversation(conversationId);
178                     }
179                 }
180             }
181         };
182 
183         final int textId =
184                 isToArchive ? R.string.archived_toast_message : R.string.unarchived_toast_message;
185         final String message = getResources().getString(textId, conversationIds.size());
186         UiUtils.showSnackBar(this, findViewById(android.R.id.list), message, undoRunnable,
187                 SnackBar.Action.SNACK_BAR_UNDO,
188                 mConversationListFragment.getSnackBarInteractions());
189         exitMultiSelectState();
190     }
191 
192     @Override
onActionBarNotification(final Iterable<SelectedConversation> conversations, final boolean isNotificationOn)193     public void onActionBarNotification(final Iterable<SelectedConversation> conversations,
194             final boolean isNotificationOn) {
195         for (final SelectedConversation conversation : conversations) {
196             UpdateConversationOptionsAction.enableConversationNotifications(
197                     conversation.conversationId, isNotificationOn);
198         }
199 
200         final int textId = isNotificationOn ?
201                 R.string.notification_on_toast_message : R.string.notification_off_toast_message;
202         final String message = getResources().getString(textId, 1);
203         UiUtils.showSnackBar(this, findViewById(android.R.id.list), message,
204             null /* undoRunnable */,
205             SnackBar.Action.SNACK_BAR_UNDO, mConversationListFragment.getSnackBarInteractions());
206         exitMultiSelectState();
207     }
208 
209     @Override
onActionBarAddContact(final SelectedConversation conversation)210     public void onActionBarAddContact(final SelectedConversation conversation) {
211         final Uri avatarUri;
212         if (conversation.icon != null) {
213             avatarUri = Uri.parse(conversation.icon);
214         } else {
215             avatarUri = null;
216         }
217         final AddContactsConfirmationDialog dialog = new AddContactsConfirmationDialog(
218                 this, avatarUri, conversation.otherParticipantNormalizedDestination);
219         dialog.show();
220         exitMultiSelectState();
221     }
222 
223     @Override
onActionBarBlock(final SelectedConversation conversation)224     public void onActionBarBlock(final SelectedConversation conversation) {
225         final Resources res = getResources();
226         new AlertDialog.Builder(this)
227                 .setTitle(res.getString(R.string.block_confirmation_title,
228                         conversation.otherParticipantNormalizedDestination))
229                 .setMessage(res.getString(R.string.block_confirmation_message))
230                 .setNegativeButton(android.R.string.cancel, null)
231                 .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
232                     @Override
233                     public void onClick(final DialogInterface arg0, final int arg1) {
234                         final Context context = AbstractConversationListActivity.this;
235                         final View listView = findViewById(android.R.id.list);
236                         final List<SnackBarInteraction> interactions =
237                                 mConversationListFragment.getSnackBarInteractions();
238                         final UpdateDestinationBlockedAction.UpdateDestinationBlockedActionListener
239                                 undoListener =
240                                         new UpdateDestinationBlockedActionSnackBar(
241                                                 context, listView, null /* undoRunnable */,
242                                                 interactions);
243                         final Runnable undoRunnable = new Runnable() {
244                             @Override
245                             public void run() {
246                                 UpdateDestinationBlockedAction.updateDestinationBlocked(
247                                         conversation.otherParticipantNormalizedDestination, false,
248                                         conversation.conversationId,
249                                         undoListener);
250                             }
251                         };
252                         final UpdateDestinationBlockedAction.UpdateDestinationBlockedActionListener
253                               listener = new UpdateDestinationBlockedActionSnackBar(
254                                       context, listView, undoRunnable, interactions);
255                         UpdateDestinationBlockedAction.updateDestinationBlocked(
256                                 conversation.otherParticipantNormalizedDestination, true,
257                                 conversation.conversationId,
258                                 listener);
259                         exitMultiSelectState();
260                     }
261                 })
262                 .create()
263                 .show();
264     }
265 
266     @Override
onConversationClick(final ConversationListData listData, final ConversationListItemData conversationListItemData, final boolean isLongClick, final ConversationListItemView conversationView)267     public void onConversationClick(final ConversationListData listData,
268                                     final ConversationListItemData conversationListItemData,
269                                     final boolean isLongClick,
270                                     final ConversationListItemView conversationView) {
271         if (isLongClick && !isInConversationListSelectMode()) {
272             startMultiSelectActionMode();
273         }
274 
275         if (isInConversationListSelectMode()) {
276             final MultiSelectActionModeCallback multiSelectActionMode =
277                     (MultiSelectActionModeCallback) getActionModeCallback();
278             multiSelectActionMode.toggleSelect(listData, conversationListItemData);
279             mConversationListFragment.updateUi();
280         } else {
281             final String conversationId = conversationListItemData.getConversationId();
282             Bundle sceneTransitionAnimationOptions = null;
283             boolean hasCustomTransitions = false;
284 
285             UIIntents.get().launchConversationActivity(
286                     this, conversationId, null,
287                     sceneTransitionAnimationOptions,
288                     hasCustomTransitions);
289         }
290     }
291 
292     @Override
onCreateConversationClick()293     public void onCreateConversationClick() {
294         UIIntents.get().launchCreateNewConversationActivity(this, null);
295     }
296 
297 
298     @Override
isConversationSelected(final String conversationId)299     public boolean isConversationSelected(final String conversationId) {
300         return isInConversationListSelectMode() &&
301                 ((MultiSelectActionModeCallback) getActionModeCallback()).isSelected(
302                         conversationId);
303     }
304 
onActionBarDebug()305     public void onActionBarDebug() {
306         DebugUtils.showDebugOptions(this);
307     }
308 
309     private static class UpdateDestinationBlockedActionSnackBar
310             implements UpdateDestinationBlockedAction.UpdateDestinationBlockedActionListener {
311         private final Context mContext;
312         private final View mParentView;
313         private final Runnable mUndoRunnable;
314         private final List<SnackBarInteraction> mInteractions;
315 
UpdateDestinationBlockedActionSnackBar(final Context context, @NonNull final View parentView, @Nullable final Runnable undoRunnable, @Nullable List<SnackBarInteraction> interactions)316         UpdateDestinationBlockedActionSnackBar(final Context context,
317                 @NonNull final View parentView, @Nullable final Runnable undoRunnable,
318                 @Nullable List<SnackBarInteraction> interactions) {
319             mContext = context;
320             mParentView = parentView;
321             mUndoRunnable = undoRunnable;
322             mInteractions = interactions;
323         }
324 
325         @Override
onUpdateDestinationBlockedAction( final UpdateDestinationBlockedAction action, final boolean success, final boolean block, final String destination)326         public void onUpdateDestinationBlockedAction(
327             final UpdateDestinationBlockedAction action,
328             final boolean success, final boolean block,
329             final String destination) {
330             if (success) {
331                 final int messageId = block ? R.string.blocked_toast_message
332                         : R.string.unblocked_toast_message;
333                 final String message = mContext.getResources().getString(messageId, 1);
334                 UiUtils.showSnackBar(mContext, mParentView, message, mUndoRunnable,
335                         SnackBar.Action.SNACK_BAR_UNDO, mInteractions);
336             }
337         }
338     }
339 }
340