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;
17
18import android.app.IntentService;
19import android.content.ContentResolver;
20import android.content.ContentValues;
21import android.content.Context;
22import android.content.Intent;
23import android.net.Uri;
24import android.os.Parcel;
25import android.support.v4.app.NotificationManagerCompat;
26
27import com.android.mail.analytics.Analytics;
28import com.android.mail.providers.Message;
29import com.android.mail.providers.UIProvider;
30import com.android.mail.utils.LogUtils;
31import com.android.mail.utils.NotificationActionUtils;
32import com.android.mail.utils.NotificationActionUtils.NotificationAction;
33
34/**
35 * Processes notification action {@link Intent}s that need to run off the main thread.
36 */
37public 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
67    public NotificationActionIntentService() {
68        super("NotificationActionIntentService");
69    }
70
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
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