NotificationActionUtils.java revision 143eb6ca9acb2d41e56efa5b3ceb2d82dfedf755
1/* 2 * Copyright (C) 2012 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 */ 16package com.android.mail.utils; 17 18import android.app.AlarmManager; 19import android.app.Notification; 20import android.app.NotificationManager; 21import android.app.PendingIntent; 22import android.content.ContentResolver; 23import android.content.ContentValues; 24import android.content.Context; 25import android.content.Intent; 26import android.net.Uri; 27import android.os.Parcel; 28import android.os.Parcelable; 29import android.os.SystemClock; 30import android.support.v4.app.NotificationCompat; 31import android.support.v4.app.TaskStackBuilder; 32import android.support.v4.util.SparseArrayCompat; 33import android.text.TextUtils; 34import android.widget.RemoteViews; 35 36import com.android.mail.MailIntentService; 37import com.android.mail.NotificationActionIntentService; 38import com.android.mail.R; 39import com.android.mail.compose.ComposeActivity; 40import com.android.mail.providers.Account; 41import com.android.mail.providers.Conversation; 42import com.android.mail.providers.Folder; 43import com.android.mail.providers.Message; 44import com.android.mail.providers.UIProvider; 45import com.android.mail.providers.UIProvider.ConversationOperations; 46import com.android.mail.providers.UIProvider.FolderType; 47import com.google.common.collect.ImmutableMap; 48 49import java.util.ArrayList; 50import java.util.HashSet; 51import java.util.List; 52import java.util.Map; 53import java.util.Set; 54 55public class NotificationActionUtils { 56 private static long sUndoTimeoutMillis = -1; 57 58 /** 59 * If an {@link NotificationAction} exists here for a given notification key, then we should 60 * display this undo notification rather than an email notification. 61 */ 62 public static final SparseArrayCompat<NotificationAction> sUndoNotifications = 63 new SparseArrayCompat<NotificationAction>(); 64 65 /** 66 * If an undo notification is displayed, its timestamp 67 * ({@link android.app.Notification.Builder#setWhen(long)}) is stored here so we can use it for 68 * the original notification if the action is undone. 69 */ 70 public static final SparseLongArray sNotificationTimestamps = new SparseLongArray(); 71 72 public enum NotificationActionType { 73 ARCHIVE_REMOVE_LABEL("archive", R.drawable.ic_menu_archive_holo_dark, 74 R.drawable.ic_menu_remove_label_holo_dark, R.string.notification_action_archive, 75 R.string.notification_action_remove_label, new ActionToggler() { 76 @Override 77 public boolean shouldDisplayPrimary(final Folder folder, 78 final Conversation conversation, final Message message) { 79 return folder == null || folder.type == FolderType.INBOX; 80 } 81 }), 82 DELETE("delete", R.drawable.ic_menu_delete_holo_dark, R.string.notification_action_delete), 83 MARK_READ("mark_read", R.drawable.ic_menu_mark_read_holo_dark, 84 R.string.notification_action_mark_read), 85 REPLY("reply", R.drawable.ic_reply_holo_dark, R.string.notification_action_reply), 86 REPLY_ALL("reply_all", R.drawable.ic_reply_all_holo_dark, 87 R.string.notification_action_reply_all), 88 FORWARD("forward", R.drawable.ic_forward_holo_dark, R.string.notification_action_forward); 89 90 private final String mPersistedValue; 91 92 private final int mActionIcon; 93 private final int mActionIcon2; 94 95 private final int mDisplayString; 96 private final int mDisplayString2; 97 98 private final ActionToggler mActionToggler; 99 100 private static final Map<String, NotificationActionType> sPersistedMapping; 101 102 private interface ActionToggler { 103 /** 104 * Determines if we should display the primary or secondary text/icon. 105 * 106 * @return <code>true</code> to display primary, <code>false</code> to display secondary 107 */ 108 boolean shouldDisplayPrimary(Folder folder, Conversation conversation, Message message); 109 } 110 111 static { 112 final NotificationActionType[] values = values(); 113 final ImmutableMap.Builder<String, NotificationActionType> mapBuilder = 114 new ImmutableMap.Builder<String, NotificationActionType>(); 115 116 for (int i = 0; i < values.length; i++) { 117 mapBuilder.put(values[i].getPersistedValue(), values[i]); 118 } 119 120 sPersistedMapping = mapBuilder.build(); 121 } 122 123 private NotificationActionType(final String persistedValue, final int actionIcon, 124 final int displayString) { 125 mPersistedValue = persistedValue; 126 mActionIcon = actionIcon; 127 mActionIcon2 = -1; 128 mDisplayString = displayString; 129 mDisplayString2 = -1; 130 mActionToggler = null; 131 } 132 133 private NotificationActionType(final String persistedValue, final int actionIcon, 134 final int actionIcon2, final int displayString, final int displayString2, 135 final ActionToggler actionToggler) { 136 mPersistedValue = persistedValue; 137 mActionIcon = actionIcon; 138 mActionIcon2 = actionIcon2; 139 mDisplayString = displayString; 140 mDisplayString2 = displayString2; 141 mActionToggler = actionToggler; 142 } 143 144 public static NotificationActionType getActionType(final String persistedValue) { 145 return sPersistedMapping.get(persistedValue); 146 } 147 148 public String getPersistedValue() { 149 return mPersistedValue; 150 } 151 152 public int getActionIconResId(final Folder folder, final Conversation conversation, 153 final Message message) { 154 if (mActionToggler == null || mActionToggler.shouldDisplayPrimary(folder, conversation, 155 message)) { 156 return mActionIcon; 157 } 158 159 return mActionIcon2; 160 } 161 162 public int getDisplayStringResId(final Folder folder, final Conversation conversation, 163 final Message message) { 164 if (mActionToggler == null || mActionToggler.shouldDisplayPrimary(folder, conversation, 165 message)) { 166 return mDisplayString; 167 } 168 169 return mDisplayString2; 170 } 171 } 172 173 /** 174 * Adds the appropriate notification actions to the specified 175 * {@link android.app.Notification.Builder} 176 * 177 * @param notificationIntent The {@link Intent} used when the notification is clicked 178 * @param when The value passed into {@link android.app.Notification.Builder#setWhen(long)}. 179 * This is used for maintaining notification ordering with the undo bar 180 * @param notificationActionsString A comma-delimited {@link String} of the actions to display 181 */ 182 public static void addNotificationActions(final Context context, 183 final Intent notificationIntent, final Notification.Builder notification, 184 final Account account, final Conversation conversation, final Message message, 185 final Folder folder, final int notificationId, final long when, 186 final String notificationActionsString) { 187 final String[] notificationActionsValueArray = 188 TextUtils.isEmpty(notificationActionsString) ? new String[0] 189 : notificationActionsString.split(","); 190 final List<NotificationActionType> notificationActions = 191 new ArrayList<NotificationActionType>(notificationActionsValueArray.length); 192 for (int i = 0; i < notificationActionsValueArray.length; i++) { 193 notificationActions.add( 194 NotificationActionType.getActionType(notificationActionsValueArray[i])); 195 } 196 197 sortNotificationActions(folder, notificationActions); 198 199 for (final NotificationActionType notificationAction : notificationActions) { 200 notification.addAction(notificationAction.getActionIconResId( 201 folder, conversation, message), context.getString(notificationAction 202 .getDisplayStringResId(folder, conversation, message)), 203 getNotificationActionPendingIntent(context, account, conversation, message, 204 folder, notificationIntent, notificationAction, notificationId, when)); 205 } 206 } 207 208 /** 209 * Sorts the notification actions into the appropriate order, based on current label 210 * 211 * @param folder The {@link Folder} being notified 212 * @param notificationActions The actions to sort 213 */ 214 private static void sortNotificationActions( 215 final Folder folder, final List<NotificationActionType> notificationActions) { 216 final Set<NotificationActionType> tempActions = 217 new HashSet<NotificationActionType>(notificationActions); 218 notificationActions.clear(); 219 220 if (folder.type == FolderType.INBOX) { 221 // Inbox 222 /* 223 * Action 1: Archive, Delete, Mute, Mark read, Add star, Mark important, Reply, Reply 224 * all, Forward 225 */ 226 /* 227 * Action 2: Reply, Reply all, Forward, Mark important, Add star, Mark read, Mute, 228 * Delete, Archive 229 */ 230 if (tempActions.contains(NotificationActionType.ARCHIVE_REMOVE_LABEL)) { 231 notificationActions.add(NotificationActionType.ARCHIVE_REMOVE_LABEL); 232 } 233 if (tempActions.contains(NotificationActionType.DELETE)) { 234 notificationActions.add(NotificationActionType.DELETE); 235 } 236 if (tempActions.contains(NotificationActionType.MARK_READ)) { 237 notificationActions.add(NotificationActionType.MARK_READ); 238 } 239 if (tempActions.contains(NotificationActionType.REPLY)) { 240 notificationActions.add(NotificationActionType.REPLY); 241 } 242 if (tempActions.contains(NotificationActionType.REPLY_ALL)) { 243 notificationActions.add(NotificationActionType.REPLY_ALL); 244 } 245 if (tempActions.contains(NotificationActionType.FORWARD)) { 246 notificationActions.add(NotificationActionType.FORWARD); 247 } 248 } else if (folder.isProviderFolder()) { 249 // Gmail system labels 250 /* 251 * Action 1: Delete, Mute, Mark read, Add star, Mark important, Reply, Reply all, 252 * Forward 253 */ 254 /* 255 * Action 2: Reply, Reply all, Forward, Mark important, Add star, Mark read, Mute, 256 * Delete 257 */ 258 if (tempActions.contains(NotificationActionType.DELETE)) { 259 notificationActions.add(NotificationActionType.DELETE); 260 } 261 if (tempActions.contains(NotificationActionType.MARK_READ)) { 262 notificationActions.add(NotificationActionType.MARK_READ); 263 } 264 if (tempActions.contains(NotificationActionType.REPLY)) { 265 notificationActions.add(NotificationActionType.REPLY); 266 } 267 if (tempActions.contains(NotificationActionType.REPLY_ALL)) { 268 notificationActions.add(NotificationActionType.REPLY_ALL); 269 } 270 if (tempActions.contains(NotificationActionType.FORWARD)) { 271 notificationActions.add(NotificationActionType.FORWARD); 272 } 273 } else { 274 // Gmail user created labels 275 /* 276 * Action 1: Remove label, Delete, Mark read, Add star, Mark important, Reply, Reply 277 * all, Forward 278 */ 279 /* 280 * Action 2: Reply, Reply all, Forward, Mark important, Add star, Mark read, Delete 281 */ 282 if (tempActions.contains(NotificationActionType.ARCHIVE_REMOVE_LABEL)) { 283 notificationActions.add(NotificationActionType.ARCHIVE_REMOVE_LABEL); 284 } 285 if (tempActions.contains(NotificationActionType.DELETE)) { 286 notificationActions.add(NotificationActionType.DELETE); 287 } 288 if (tempActions.contains(NotificationActionType.MARK_READ)) { 289 notificationActions.add(NotificationActionType.MARK_READ); 290 } 291 if (tempActions.contains(NotificationActionType.REPLY)) { 292 notificationActions.add(NotificationActionType.REPLY); 293 } 294 if (tempActions.contains(NotificationActionType.REPLY_ALL)) { 295 notificationActions.add(NotificationActionType.REPLY_ALL); 296 } 297 if (tempActions.contains(NotificationActionType.FORWARD)) { 298 notificationActions.add(NotificationActionType.FORWARD); 299 } 300 } 301 } 302 303 /** 304 * Creates a {@link PendingIntent} for the specified notification action. 305 */ 306 private static PendingIntent getNotificationActionPendingIntent(final Context context, 307 final Account account, final Conversation conversation, final Message message, 308 final Folder folder, final Intent notificationIntent, 309 final NotificationActionType action, final int notificationId, final long when) { 310 final Uri messageUri = message.uri; 311 312 final NotificationAction notificationAction = new NotificationAction(action, account, 313 conversation, message, folder, conversation.id, message.serverId, message.id, when); 314 315 switch (action) { 316 case REPLY: { 317 // Build a task stack that forces the conversation view on the stack before the 318 // reply activity. 319 final TaskStackBuilder taskStackBuilder = TaskStackBuilder.create(context); 320 321 final Intent replyIntent = createReplyIntent(context, account, messageUri, false); 322 replyIntent.putExtra(ComposeActivity.EXTRA_NOTIFICATION_FOLDER, folder); 323 // To make sure that the reply intents one notification don't clobber over 324 // intents for other notification, force a data uri on the intent 325 final Uri notificationUri = 326 Uri.parse("gmailfrom://gmail-ls/account/" + "reply/" + notificationId); 327 replyIntent.setData(notificationUri); 328 329 taskStackBuilder.addNextIntent(notificationIntent).addNextIntent(replyIntent); 330 331 return taskStackBuilder.getPendingIntent( 332 notificationId, PendingIntent.FLAG_UPDATE_CURRENT); 333 } case REPLY_ALL: { 334 // Build a task stack that forces the conversation view on the stack before the 335 // reply activity. 336 final TaskStackBuilder taskStackBuilder = TaskStackBuilder.create(context); 337 338 final Intent replyIntent = createReplyIntent(context, account, messageUri, true); 339 replyIntent.putExtra(ComposeActivity.EXTRA_NOTIFICATION_FOLDER, folder); 340 // To make sure that the reply intents one notification don't clobber over 341 // intents for other notification, force a data uri on the intent 342 final Uri notificationUri = 343 Uri.parse("gmailfrom://gmail-ls/account/" + "replyall/" + notificationId); 344 replyIntent.setData(notificationUri); 345 346 taskStackBuilder.addNextIntent(notificationIntent).addNextIntent(replyIntent); 347 348 return taskStackBuilder.getPendingIntent( 349 notificationId, PendingIntent.FLAG_UPDATE_CURRENT); 350 } case FORWARD: { 351 // Build a task stack that forces the conversation view on the stack before the 352 // reply activity. 353 final TaskStackBuilder taskStackBuilder = TaskStackBuilder.create(context); 354 355 final Intent replyIntent = createForwardIntent(context, account, messageUri); 356 replyIntent.putExtra(ComposeActivity.EXTRA_NOTIFICATION_FOLDER, folder); 357 // To make sure that the reply intents one notification don't clobber over 358 // intents for other notification, force a data uri on the intent 359 final Uri notificationUri = 360 Uri.parse("gmailfrom://gmail-ls/account/" + "forward/" + notificationId); 361 replyIntent.setData(notificationUri); 362 363 taskStackBuilder.addNextIntent(notificationIntent).addNextIntent(replyIntent); 364 365 return taskStackBuilder.getPendingIntent( 366 notificationId, PendingIntent.FLAG_UPDATE_CURRENT); 367 } case ARCHIVE_REMOVE_LABEL: { 368 final String intentAction = 369 NotificationActionIntentService.ACTION_ARCHIVE_REMOVE_LABEL; 370 371 final Intent intent = new Intent(intentAction); 372 intent.putExtra(NotificationActionIntentService.EXTRA_NOTIFICATION_ACTION, 373 notificationAction); 374 375 return PendingIntent.getService( 376 context, notificationId, intent, PendingIntent.FLAG_UPDATE_CURRENT); 377 } case DELETE: { 378 final String intentAction = NotificationActionIntentService.ACTION_DELETE; 379 380 final Intent intent = new Intent(intentAction); 381 intent.putExtra(NotificationActionIntentService.EXTRA_NOTIFICATION_ACTION, 382 notificationAction); 383 384 return PendingIntent.getService( 385 context, notificationId, intent, PendingIntent.FLAG_UPDATE_CURRENT); 386 } case MARK_READ: { 387 final String intentAction = NotificationActionIntentService.ACTION_MARK_READ; 388 389 final Intent intent = new Intent(intentAction); 390 intent.putExtra(NotificationActionIntentService.EXTRA_NOTIFICATION_ACTION, 391 notificationAction); 392 393 return PendingIntent.getService( 394 context, notificationId, intent, PendingIntent.FLAG_UPDATE_CURRENT); 395 } 396 } 397 398 throw new IllegalArgumentException("Invalid NotificationActionType"); 399 } 400 401 /** 402 * @return an intent which, if launched, will reply to the conversation 403 */ 404 public static Intent createReplyIntent(final Context context, final Account account, 405 final Uri messageUri, final boolean isReplyAll) { 406 final Intent intent = ComposeActivity.createReplyIntent(context, account, messageUri, 407 isReplyAll); 408 return intent; 409 } 410 411 /** 412 * @return an intent which, if launched, will forward the conversation 413 */ 414 public static Intent createForwardIntent( 415 final Context context, final Account account, final Uri messageUri) { 416 final Intent intent = ComposeActivity.createForwardIntent(context, account, messageUri); 417 return intent; 418 } 419 420 public static class NotificationAction implements Parcelable { 421 private final NotificationActionType mNotificationActionType; 422 private final Account mAccount; 423 private final Conversation mConversation; 424 private final Message mMessage; 425 private final Folder mFolder; 426 private final long mConversationId; 427 private final String mMessageId; 428 private final long mLocalMessageId; 429 private final long mWhen; 430 431 public NotificationAction(final NotificationActionType notificationActionType, 432 final Account account, final Conversation conversation, final Message message, 433 final Folder folder, final long conversationId, final String messageId, 434 final long localMessageId, final long when) { 435 mNotificationActionType = notificationActionType; 436 mAccount = account; 437 mConversation = conversation; 438 mMessage = message; 439 mFolder = folder; 440 mConversationId = conversationId; 441 mMessageId = messageId; 442 mLocalMessageId = localMessageId; 443 mWhen = when; 444 } 445 446 public NotificationActionType getNotificationActionType() { 447 return mNotificationActionType; 448 } 449 450 public Account getAccount() { 451 return mAccount; 452 } 453 454 public Conversation getConversation() { 455 return mConversation; 456 } 457 458 public Message getMessage() { 459 return mMessage; 460 } 461 462 public Folder getFolder() { 463 return mFolder; 464 } 465 466 public long getConversationId() { 467 return mConversationId; 468 } 469 470 public String getMessageId() { 471 return mMessageId; 472 } 473 474 public long getLocalMessageId() { 475 return mLocalMessageId; 476 } 477 478 public long getWhen() { 479 return mWhen; 480 } 481 482 public int getActionTextResId() { 483 switch (mNotificationActionType) { 484 case ARCHIVE_REMOVE_LABEL: 485 if (mFolder.type == FolderType.INBOX) { 486 return R.string.notification_action_undo_archive; 487 } else { 488 return R.string.notification_action_undo_remove_label; 489 } 490 case DELETE: 491 return R.string.notification_action_undo_delete; 492 default: 493 throw new IllegalStateException( 494 "There is no action text for this NotificationActionType."); 495 } 496 } 497 498 @Override 499 public int describeContents() { 500 return 0; 501 } 502 503 @Override 504 public void writeToParcel(final Parcel out, final int flags) { 505 out.writeInt(mNotificationActionType.ordinal()); 506 out.writeParcelable(mAccount, 0); 507 out.writeParcelable(mConversation, 0); 508 out.writeParcelable(mMessage, 0); 509 out.writeParcelable(mFolder, 0); 510 out.writeLong(mConversationId); 511 out.writeString(mMessageId); 512 out.writeLong(mLocalMessageId); 513 out.writeLong(mWhen); 514 } 515 516 public static final Parcelable.ClassLoaderCreator<NotificationAction> CREATOR = 517 new Parcelable.ClassLoaderCreator<NotificationAction>() { 518 @Override 519 public NotificationAction createFromParcel(final Parcel in) { 520 return new NotificationAction(in, null); 521 } 522 523 @Override 524 public NotificationAction[] newArray(final int size) { 525 return new NotificationAction[size]; 526 } 527 528 @Override 529 public NotificationAction createFromParcel( 530 final Parcel in, final ClassLoader loader) { 531 return new NotificationAction(in, loader); 532 } 533 }; 534 535 private NotificationAction(final Parcel in, final ClassLoader loader) { 536 mNotificationActionType = NotificationActionType.values()[in.readInt()]; 537 mAccount = in.readParcelable(loader); 538 mConversation = in.readParcelable(loader); 539 mMessage = in.readParcelable(loader); 540 mFolder = in.readParcelable(loader); 541 mConversationId = in.readLong(); 542 mMessageId = in.readString(); 543 mLocalMessageId = in.readLong(); 544 mWhen = in.readLong(); 545 } 546 } 547 548 public static Notification createUndoNotification(final Context context, 549 final NotificationAction notificationAction, final int notificationId) { 550 final NotificationCompat.Builder builder = new NotificationCompat.Builder(context); 551 552 builder.setSmallIcon(R.drawable.stat_notify_email); 553 builder.setWhen(notificationAction.getWhen()); 554 555 final RemoteViews undoView = 556 new RemoteViews(context.getPackageName(), R.layout.undo_notification); 557 undoView.setTextViewText( 558 R.id.description_text, context.getString(notificationAction.getActionTextResId())); 559 560 final Intent clickIntent = new Intent(NotificationActionIntentService.ACTION_UNDO); 561 clickIntent.putExtra(NotificationActionIntentService.EXTRA_NOTIFICATION_ACTION, 562 notificationAction); 563 final PendingIntent clickPendingIntent = PendingIntent.getService(context, notificationId, 564 clickIntent, PendingIntent.FLAG_CANCEL_CURRENT); 565 566 undoView.setOnClickPendingIntent(R.id.status_bar_latest_event_content, clickPendingIntent); 567 568 builder.setContent(undoView); 569 570 // When the notification is cleared, we perform the destructive action 571 final Intent deleteIntent = new Intent(NotificationActionIntentService.ACTION_DESTRUCT); 572 deleteIntent.putExtra(NotificationActionIntentService.EXTRA_NOTIFICATION_ACTION, 573 notificationAction); 574 final PendingIntent deletePendingIntent = PendingIntent.getService(context, 575 notificationId, deleteIntent, PendingIntent.FLAG_CANCEL_CURRENT); 576 builder.setDeleteIntent(deletePendingIntent); 577 578 final Notification notification = builder.build(); 579 580 return notification; 581 } 582 583 /** 584 * Registers a timeout for the undo notification such that when it expires, the undo bar will 585 * disappear, and the action will be performed. 586 */ 587 public static void registerUndoTimeout( 588 final Context context, final NotificationAction notificationAction) { 589 if (sUndoTimeoutMillis == -1) { 590 sUndoTimeoutMillis = 591 context.getResources().getInteger(R.integer.undo_notification_timeout); 592 } 593 594 final AlarmManager alarmManager = 595 (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); 596 597 final long triggerAtMills = SystemClock.elapsedRealtime() + sUndoTimeoutMillis; 598 599 final PendingIntent pendingIntent = 600 createUndoTimeoutPendingIntent(context, notificationAction); 601 602 alarmManager.set(AlarmManager.ELAPSED_REALTIME, triggerAtMills, pendingIntent); 603 } 604 605 /** 606 * Cancels the undo timeout for a notification action. This should be called if the undo 607 * notification is clicked (to prevent the action from being performed anyway) or cleared (since 608 * we have already performed the action). 609 */ 610 public static void cancelUndoTimeout( 611 final Context context, final NotificationAction notificationAction) { 612 final AlarmManager alarmManager = 613 (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); 614 615 final PendingIntent pendingIntent = 616 createUndoTimeoutPendingIntent(context, notificationAction); 617 618 alarmManager.cancel(pendingIntent); 619 } 620 621 /** 622 * Creates a {@link PendingIntent} to be used for creating and canceling the undo timeout 623 * alarm. 624 */ 625 private static PendingIntent createUndoTimeoutPendingIntent( 626 final Context context, final NotificationAction notificationAction) { 627 final Intent intent = new Intent(NotificationActionIntentService.ACTION_UNDO_TIMEOUT); 628 intent.putExtra( 629 NotificationActionIntentService.EXTRA_NOTIFICATION_ACTION, notificationAction); 630 631 final int requestCode = notificationAction.getAccount().hashCode() 632 ^ notificationAction.getFolder().hashCode(); 633 final PendingIntent pendingIntent = 634 PendingIntent.getService(context, requestCode, intent, 0); 635 636 return pendingIntent; 637 } 638 639 /** 640 * Processes the specified destructive action (archive, delete, mute) on the message. 641 */ 642 public static void processDestructiveAction( 643 final Context context, final NotificationAction notificationAction) { 644 final NotificationActionType destructAction = 645 notificationAction.getNotificationActionType(); 646 final Conversation conversation = notificationAction.getConversation(); 647 final Folder folder = notificationAction.getFolder(); 648 649 final ContentResolver contentResolver = context.getContentResolver(); 650 final Uri uri = conversation.uri.buildUpon().appendQueryParameter( 651 UIProvider.FORCE_UI_NOTIFICATIONS_QUERY_PARAMETER, Boolean.TRUE.toString()).build(); 652 653 switch (destructAction) { 654 case ARCHIVE_REMOVE_LABEL: { 655 if (folder.type == FolderType.INBOX) { 656 // Inbox, so archive 657 final ContentValues values = new ContentValues(1); 658 values.put(UIProvider.ConversationOperations.OPERATION_KEY, 659 UIProvider.ConversationOperations.ARCHIVE); 660 661 contentResolver.update(uri, values, null, null); 662 } else { 663 // Not inbox, so remove label 664 final ContentValues values = new ContentValues(1); 665 666 final String removeFolderUri = 667 folder.uri.buildUpon().appendPath(Boolean.FALSE.toString()).toString(); 668 values.put(ConversationOperations.FOLDERS_UPDATED, removeFolderUri); 669 670 contentResolver.update(uri, values, null, null); 671 } 672 673 markSeen(context, notificationAction.mAccount.toString(), folder); 674 break; 675 } 676 case DELETE: { 677 contentResolver.delete(uri, null, null); 678 markSeen(context, notificationAction.mAccount.toString(), folder); 679 break; 680 } 681 default: 682 throw new IllegalArgumentException( 683 "The specified NotificationActionType is not a destructive action."); 684 } 685 } 686 687 private static void markSeen(final Context context, final String account, final Folder folder) { 688 final Intent intent = new Intent(MailIntentService.ACTION_MARK_SEEN); 689 intent.putExtra(MailIntentService.ACCOUNT_EXTRA, account); 690 intent.putExtra(MailIntentService.FOLDER_EXTRA, folder); 691 692 context.startService(intent); 693 } 694 695 /** 696 * Creates and displays an Undo notification for the specified {@link NotificationAction}. 697 */ 698 public static void createUndoNotification(final Context context, 699 final NotificationAction notificationAction) { 700 final int notificationId = getNotificationId( 701 notificationAction.getAccount().name, notificationAction.getFolder()); 702 703 final Notification notification = 704 createUndoNotification(context, notificationAction, notificationId); 705 706 final NotificationManager notificationManager = 707 (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); 708 notificationManager.notify(notificationId, notification); 709 710 sUndoNotifications.put(notificationId, notificationAction); 711 sNotificationTimestamps.put(notificationId, notificationAction.getWhen()); 712 } 713 714 public static void cancelUndoNotification(final Context context, 715 final NotificationAction notificationAction) { 716 final int notificationId = getNotificationId( 717 notificationAction.getAccount().name, notificationAction.getFolder()); 718 removeUndoNotification(context, notificationId, false); 719 resendNotifications(context); 720 } 721 722 /** 723 * If an undo notification is left alone for a long enough time, it will disappear, this method 724 * will be called, and the action will be finalized. 725 */ 726 public static void processUndoNotification(final Context context, 727 final NotificationAction notificationAction) { 728 final int notificationId = getNotificationId( 729 notificationAction.getAccount().name, notificationAction.getFolder()); 730 removeUndoNotification(context, notificationId, true); 731 sNotificationTimestamps.delete(notificationId); 732 processDestructiveAction(context, notificationAction); 733 734 resendNotifications(context); 735 } 736 737 /** 738 * Removes the undo notification. 739 * 740 * @param removeNow <code>true</code> to remove it from the drawer right away, 741 * <code>false</code> to just remove the reference to it 742 */ 743 private static void removeUndoNotification( 744 final Context context, final int notificationId, final boolean removeNow) { 745 sUndoNotifications.delete(notificationId); 746 747 if (removeNow) { 748 final NotificationManager notificationManager = 749 (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); 750 notificationManager.cancel(notificationId); 751 } 752 } 753 754 public static int getNotificationId(final String account, final Folder folder) { 755 // TODO(skennedy): When notifications are fully in UnifiedEmail, remove this method and use 756 // the one in Utils 757 // 1 == Gmail.NOTIFICATION_ID 758 return 1 ^ account.hashCode() ^ folder.hashCode(); 759 } 760 761 /** 762 * Broadcasts an {@link Intent} to inform the app to resend its notifications. 763 */ 764 public static void resendNotifications(final Context context) { 765 final Intent intent = new Intent(MailIntentService.ACTION_RESEND_NOTIFICATIONS); 766 intent.setPackage(context.getPackageName()); // Make sure we only deliver this to ourself 767 context.startService(intent); 768 } 769} 770