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