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 android.support.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 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 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 89 protected void startMultiSelectActionMode() { 90 startActionMode(new MultiSelectActionModeCallback(this)); 91 } 92 93 protected void exitMultiSelectState() { 94 mConversationListFragment.showFab(); 95 dismissActionMode(); 96 mConversationListFragment.updateUi(); 97 } 98 99 protected boolean isInConversationListSelectMode() { 100 return getActionModeCallback() instanceof MultiSelectActionModeCallback; 101 } 102 103 @Override 104 public boolean isSelectionMode() { 105 return isInConversationListSelectMode(); 106 } 107 108 @Override 109 public void onActivityResult(final int requestCode, final int resultCode, final Intent data) { 110 } 111 112 @Override 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 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 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 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 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 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 293 public void onCreateConversationClick() { 294 UIIntents.get().launchCreateNewConversationActivity(this, null); 295 } 296 297 298 @Override 299 public boolean isConversationSelected(final String conversationId) { 300 return isInConversationListSelectMode() && 301 ((MultiSelectActionModeCallback) getActionModeCallback()).isSelected( 302 conversationId); 303 } 304 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 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 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