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