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  */
16 package com.android.mail;
17 
18 import android.app.IntentService;
19 import android.content.ContentResolver;
20 import android.content.ContentValues;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.net.Uri;
24 import android.os.Parcel;
25 import android.support.v4.app.NotificationManagerCompat;
26 
27 import com.android.mail.analytics.Analytics;
28 import com.android.mail.providers.Message;
29 import com.android.mail.providers.UIProvider;
30 import com.android.mail.utils.LogUtils;
31 import com.android.mail.utils.NotificationActionUtils;
32 import com.android.mail.utils.NotificationActionUtils.NotificationAction;
33 
34 /**
35  * Processes notification action {@link Intent}s that need to run off the main thread.
36  */
37 public class NotificationActionIntentService extends IntentService {
38     private static final String LOG_TAG = "NotifActionIS";
39 
40     // Compose actions
41     public static final String ACTION_REPLY = "com.android.mail.action.notification.REPLY";
42     public static final String ACTION_REPLY_ALL = "com.android.mail.action.notification.REPLY_ALL";
43     public static final String ACTION_FORWARD = "com.android.mail.action.notification.FORWARD";
44     // Toggle actions
45     public static final String ACTION_MARK_READ = "com.android.mail.action.notification.MARK_READ";
46 
47     // Destructive actions - These just display the undo bar
48     public static final String ACTION_ARCHIVE_REMOVE_LABEL =
49             "com.android.mail.action.notification.ARCHIVE";
50     public static final String ACTION_DELETE = "com.android.mail.action.notification.DELETE";
51 
52     /**
53      * This action cancels the undo notification, and does not commit any changes.
54      */
55     public static final String ACTION_UNDO = "com.android.mail.action.notification.UNDO";
56 
57     /**
58      * This action performs the actual destructive action.
59      */
60     public static final String ACTION_DESTRUCT = "com.android.mail.action.notification.DESTRUCT";
61 
62     public static final String EXTRA_NOTIFICATION_ACTION =
63             "com.android.mail.extra.EXTRA_NOTIFICATION_ACTION";
64     public static final String ACTION_UNDO_TIMEOUT =
65             "com.android.mail.action.notification.UNDO_TIMEOUT";
66 
NotificationActionIntentService()67     public NotificationActionIntentService() {
68         super("NotificationActionIntentService");
69     }
70 
logNotificationAction(String intentAction, NotificationAction action)71     private static void logNotificationAction(String intentAction, NotificationAction action) {
72         final String eventAction;
73         final String eventLabel;
74 
75         if (ACTION_ARCHIVE_REMOVE_LABEL.equals(intentAction)) {
76             eventAction = "archive_remove_label";
77             eventLabel = action.getFolder().getTypeDescription();
78         } else if (ACTION_DELETE.equals(intentAction)) {
79             eventAction = "delete";
80             eventLabel = null;
81         } else {
82             eventAction = intentAction;
83             eventLabel = null;
84         }
85 
86         Analytics.getInstance().sendEvent("notification_action", eventAction, eventLabel, 0);
87     }
88 
89     @Override
onHandleIntent(final Intent intent)90     protected void onHandleIntent(final Intent intent) {
91         final Context context = this;
92         final String action = intent.getAction();
93 
94         /*
95          * Grab the alarm from the intent. Since the remote AlarmManagerService fills in the Intent
96          * to add some extra data, it must unparcel the NotificationAction object. It throws a
97          * ClassNotFoundException when unparcelling.
98          * To avoid this, do the marshalling ourselves.
99          */
100         final NotificationAction notificationAction;
101         final byte[] data = intent.getByteArrayExtra(EXTRA_NOTIFICATION_ACTION);
102         if (data != null) {
103             final Parcel in = Parcel.obtain();
104             in.unmarshall(data, 0, data.length);
105             in.setDataPosition(0);
106             notificationAction = NotificationAction.CREATOR.createFromParcel(in,
107                     NotificationAction.class.getClassLoader());
108         } else {
109             LogUtils.wtf(LOG_TAG, "data was null trying to unparcel the NotificationAction");
110             return;
111         }
112 
113         final Message message = notificationAction.getMessage();
114 
115         final ContentResolver contentResolver = getContentResolver();
116 
117         LogUtils.i(LOG_TAG, "Handling %s", action);
118 
119         logNotificationAction(action, notificationAction);
120 
121         if (notificationAction.getSource() == NotificationAction.SOURCE_REMOTE) {
122             // Skip undo if the action is bridged from remote node.  This should be similar to the
123             // logic after the Undo notification expires in a regular flow.
124             LogUtils.d(LOG_TAG, "Canceling %s", notificationAction.getNotificationId());
125             NotificationManagerCompat.from(context).cancel(notificationAction.getNotificationId());
126             NotificationActionUtils.processDestructiveAction(this, notificationAction);
127             NotificationActionUtils.resendNotifications(context, notificationAction.getAccount(),
128                     notificationAction.getFolder());
129             return;
130         }
131 
132         if (ACTION_UNDO.equals(action)) {
133             NotificationActionUtils.cancelUndoTimeout(context, notificationAction);
134             NotificationActionUtils.cancelUndoNotification(context, notificationAction);
135         } else if (ACTION_ARCHIVE_REMOVE_LABEL.equals(action) || ACTION_DELETE.equals(action)) {
136             // All we need to do is switch to an Undo notification
137             NotificationActionUtils.createUndoNotification(context, notificationAction);
138 
139             NotificationActionUtils.registerUndoTimeout(context, notificationAction);
140         } else {
141             if (ACTION_UNDO_TIMEOUT.equals(action) || ACTION_DESTRUCT.equals(action)) {
142                 // Process the action
143                 NotificationActionUtils.cancelUndoTimeout(this, notificationAction);
144                 NotificationActionUtils.processUndoNotification(this, notificationAction);
145             } else if (ACTION_MARK_READ.equals(action)) {
146                 final Uri uri = message.uri;
147 
148                 final ContentValues values = new ContentValues(1);
149                 values.put(UIProvider.MessageColumns.READ, 1);
150 
151                 contentResolver.update(uri, values, null, null);
152             }
153 
154             NotificationActionUtils.resendNotifications(context, notificationAction.getAccount(),
155                     notificationAction.getFolder());
156         }
157     }
158 }
159