NotificationTransaction.java revision 99f719d1cb0c6ed68373d73427a816c4e56eb5ba
1/*
2 * Copyright (C) 2007-2008 Esmertec AG.
3 * Copyright (C) 2007-2008 The Android Open Source Project
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 *      http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18package com.android.mms.transaction;
19
20import static com.android.mms.transaction.TransactionState.FAILED;
21import static com.android.mms.transaction.TransactionState.INITIALIZED;
22import static com.android.mms.transaction.TransactionState.SUCCESS;
23import static com.google.android.mms.pdu.PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF;
24import static com.google.android.mms.pdu.PduHeaders.STATUS_DEFERRED;
25import static com.google.android.mms.pdu.PduHeaders.STATUS_RETRIEVED;
26import static com.google.android.mms.pdu.PduHeaders.STATUS_UNRECOGNIZED;
27
28import java.io.IOException;
29
30import android.content.ContentValues;
31import android.content.Context;
32import android.database.sqlite.SqliteWrapper;
33import android.net.Uri;
34import android.provider.Telephony.Mms;
35import android.provider.Telephony.Threads;
36import android.provider.Telephony.Mms.Inbox;
37import android.telephony.TelephonyManager;
38import android.util.Log;
39
40import com.android.mms.MmsApp;
41import com.android.mms.MmsConfig;
42import com.android.mms.util.DownloadManager;
43import com.android.mms.util.Recycler;
44import com.android.mms.widget.MmsWidgetProvider;
45import com.google.android.mms.MmsException;
46import com.google.android.mms.pdu.GenericPdu;
47import com.google.android.mms.pdu.NotificationInd;
48import com.google.android.mms.pdu.NotifyRespInd;
49import com.google.android.mms.pdu.PduComposer;
50import com.google.android.mms.pdu.PduHeaders;
51import com.google.android.mms.pdu.PduParser;
52import com.google.android.mms.pdu.PduPersister;
53
54/**
55 * The NotificationTransaction is responsible for handling multimedia
56 * message notifications (M-Notification.ind).  It:
57 *
58 * <ul>
59 * <li>Composes the notification response (M-NotifyResp.ind).
60 * <li>Sends the notification response to the MMSC server.
61 * <li>Stores the notification indication.
62 * <li>Notifies the TransactionService about succesful completion.
63 * </ul>
64 *
65 * NOTE: This MMS client handles all notifications with a <b>deferred
66 * retrieval</b> response.  The transaction service, upon succesful
67 * completion of this transaction, will trigger a retrieve transaction
68 * in case the client is in immediate retrieve mode.
69 */
70public class NotificationTransaction extends Transaction implements Runnable {
71    private static final String TAG = "NotificationTransaction";
72    private static final boolean DEBUG = false;
73    private static final boolean LOCAL_LOGV = false;
74
75    private Uri mUri;
76    private NotificationInd mNotificationInd;
77    private String mContentLocation;
78
79    public NotificationTransaction(
80            Context context, int serviceId,
81            TransactionSettings connectionSettings, String uriString) {
82        super(context, serviceId, connectionSettings);
83
84        mUri = Uri.parse(uriString);
85
86        try {
87            mNotificationInd = (NotificationInd)
88                    PduPersister.getPduPersister(context).load(mUri);
89        } catch (MmsException e) {
90            Log.e(TAG, "Failed to load NotificationInd from: " + uriString, e);
91            throw new IllegalArgumentException();
92        }
93
94        mId = new String(mNotificationInd.getTransactionId());
95        mContentLocation = new String(mNotificationInd.getContentLocation());
96
97        // Attach the transaction to the instance of RetryScheduler.
98        attach(RetryScheduler.getInstance(context));
99    }
100
101    /**
102     * This constructor is only used for test purposes.
103     */
104    public NotificationTransaction(
105            Context context, int serviceId,
106            TransactionSettings connectionSettings, NotificationInd ind) {
107        super(context, serviceId, connectionSettings);
108
109        try {
110            // Save the pdu. If we can start downloading the real pdu immediately, don't allow
111            // persist() to create a thread for the notificationInd because it causes UI jank.
112            mUri = PduPersister.getPduPersister(context).persist(
113                        ind, Inbox.CONTENT_URI, !allowAutoDownload());
114        } catch (MmsException e) {
115            Log.e(TAG, "Failed to save NotificationInd in constructor.", e);
116            throw new IllegalArgumentException();
117        }
118
119        mNotificationInd = ind;
120        mId = new String(ind.getTransactionId());
121    }
122
123    /*
124     * (non-Javadoc)
125     * @see com.google.android.mms.pdu.Transaction#process()
126     */
127    @Override
128    public void process() {
129        new Thread(this, "NotificationTransaction").start();
130    }
131
132    public static boolean allowAutoDownload() {
133        DownloadManager downloadManager = DownloadManager.getInstance();
134        boolean autoDownload = downloadManager.isAuto();
135        boolean dataSuspended = (MmsApp.getApplication().getTelephonyManager().getDataState() ==
136                TelephonyManager.DATA_SUSPENDED);
137        return autoDownload && !dataSuspended;
138    }
139
140    public void run() {
141        DownloadManager downloadManager = DownloadManager.getInstance();
142        boolean autoDownload = allowAutoDownload();
143        try {
144            if (LOCAL_LOGV) {
145                Log.v(TAG, "Notification transaction launched: " + this);
146            }
147
148            // By default, we set status to STATUS_DEFERRED because we
149            // should response MMSC with STATUS_DEFERRED when we cannot
150            // download a MM immediately.
151            int status = STATUS_DEFERRED;
152            // Don't try to download when data is suspended, as it will fail, so defer download
153            if (!autoDownload) {
154                downloadManager.markState(mUri, DownloadManager.STATE_UNSTARTED);
155                sendNotifyRespInd(status);
156                return;
157            }
158
159            downloadManager.markState(mUri, DownloadManager.STATE_DOWNLOADING);
160
161            if (LOCAL_LOGV) {
162                Log.v(TAG, "Content-Location: " + mContentLocation);
163            }
164
165            byte[] retrieveConfData = null;
166            // We should catch exceptions here to response MMSC
167            // with STATUS_DEFERRED.
168            try {
169                retrieveConfData = getPdu(mContentLocation);
170            } catch (IOException e) {
171                mTransactionState.setState(FAILED);
172            }
173
174            if (retrieveConfData != null) {
175                GenericPdu pdu = new PduParser(retrieveConfData).parse();
176                if ((pdu == null) || (pdu.getMessageType() != MESSAGE_TYPE_RETRIEVE_CONF)) {
177                    Log.e(TAG, "Invalid M-RETRIEVE.CONF PDU. " +
178                            (pdu != null ? "message type: " + pdu.getMessageType() : "null pdu"));
179                    mTransactionState.setState(FAILED);
180                    status = STATUS_UNRECOGNIZED;
181                } else {
182                    // Save the received PDU (must be a M-RETRIEVE.CONF).
183                    PduPersister p = PduPersister.getPduPersister(mContext);
184                    Uri uri = p.persist(pdu, Inbox.CONTENT_URI);
185
186                    // Use local time instead of PDU time
187                    ContentValues values = new ContentValues(1);
188                    values.put(Mms.DATE, System.currentTimeMillis() / 1000L);
189                    SqliteWrapper.update(mContext, mContext.getContentResolver(),
190                            uri, values, null, null);
191
192                    // We have successfully downloaded the new MM. Delete the
193                    // M-NotifyResp.ind from Inbox.
194                    SqliteWrapper.delete(mContext, mContext.getContentResolver(),
195                                         mUri, null, null);
196                    if (true) {
197                        Log.v(TAG, "NotificationTransaction deleting obsolete threads");
198                    }
199                    // Delete obsolete threads
200                    SqliteWrapper.delete(mContext, mContext.getContentResolver(),
201                            Threads.OBSOLETE_THREADS_URI, null, null);
202
203                    // Notify observers with newly received MM.
204                    mUri = uri;
205                    status = STATUS_RETRIEVED;
206                }
207            }
208
209            if (LOCAL_LOGV) {
210                Log.v(TAG, "status=0x" + Integer.toHexString(status));
211            }
212
213            // Check the status and update the result state of this Transaction.
214            switch (status) {
215                case STATUS_RETRIEVED:
216                    mTransactionState.setState(SUCCESS);
217                    break;
218                case STATUS_DEFERRED:
219                    // STATUS_DEFERRED, may be a failed immediate retrieval.
220                    if (mTransactionState.getState() == INITIALIZED) {
221                        mTransactionState.setState(SUCCESS);
222                    }
223                    break;
224            }
225
226            sendNotifyRespInd(status);
227
228            // Make sure this thread isn't over the limits in message count.
229            Recycler.getMmsRecycler().deleteOldMessagesInSameThreadAsMessage(mContext, mUri);
230            MmsWidgetProvider.notifyDatasetChanged(mContext);
231        } catch (Throwable t) {
232            Log.e(TAG, Log.getStackTraceString(t));
233        } finally {
234            mTransactionState.setContentUri(mUri);
235            if (!autoDownload) {
236                // Always mark the transaction successful for deferred
237                // download since any error here doesn't make sense.
238                mTransactionState.setState(SUCCESS);
239            }
240            if (mTransactionState.getState() != SUCCESS) {
241                mTransactionState.setState(FAILED);
242                Log.e(TAG, "NotificationTransaction failed.");
243            }
244            notifyObservers();
245        }
246    }
247
248    private void sendNotifyRespInd(int status) throws MmsException, IOException {
249        // Create the M-NotifyResp.ind
250        NotifyRespInd notifyRespInd = new NotifyRespInd(
251                PduHeaders.CURRENT_MMS_VERSION,
252                mNotificationInd.getTransactionId(),
253                status);
254
255        // Pack M-NotifyResp.ind and send it
256        if(MmsConfig.getNotifyWapMMSC()) {
257            sendPdu(new PduComposer(mContext, notifyRespInd).make(), mContentLocation);
258        } else {
259            sendPdu(new PduComposer(mContext, notifyRespInd).make());
260        }
261    }
262
263    @Override
264    public int getType() {
265        return NOTIFICATION_TRANSACTION;
266    }
267}
268