NotificationTransaction.java revision d64419030e1fec1e751695dab3bd7236e2fb0214
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.Mms.Inbox;
36import android.telephony.TelephonyManager;
37import android.util.Log;
38
39import com.android.mms.MmsApp;
40import com.android.mms.MmsConfig;
41import com.android.mms.util.DownloadManager;
42import com.android.mms.util.Recycler;
43import com.android.mms.widget.MmsWidgetProvider;
44import com.google.android.mms.MmsException;
45import com.google.android.mms.pdu.GenericPdu;
46import com.google.android.mms.pdu.NotificationInd;
47import com.google.android.mms.pdu.NotifyRespInd;
48import com.google.android.mms.pdu.PduComposer;
49import com.google.android.mms.pdu.PduHeaders;
50import com.google.android.mms.pdu.PduParser;
51import com.google.android.mms.pdu.PduPersister;
52
53/**
54 * The NotificationTransaction is responsible for handling multimedia
55 * message notifications (M-Notification.ind).  It:
56 *
57 * <ul>
58 * <li>Composes the notification response (M-NotifyResp.ind).
59 * <li>Sends the notification response to the MMSC server.
60 * <li>Stores the notification indication.
61 * <li>Notifies the TransactionService about succesful completion.
62 * </ul>
63 *
64 * NOTE: This MMS client handles all notifications with a <b>deferred
65 * retrieval</b> response.  The transaction service, upon succesful
66 * completion of this transaction, will trigger a retrieve transaction
67 * in case the client is in immediate retrieve mode.
68 */
69public class NotificationTransaction extends Transaction implements Runnable {
70    private static final String TAG = "NotificationTransaction";
71    private static final boolean DEBUG = false;
72    private static final boolean LOCAL_LOGV = false;
73
74    private Uri mUri;
75    private NotificationInd mNotificationInd;
76    private String mContentLocation;
77
78    public NotificationTransaction(
79            Context context, int serviceId,
80            TransactionSettings connectionSettings, String uriString) {
81        super(context, serviceId, connectionSettings);
82
83        mUri = Uri.parse(uriString);
84
85        try {
86            mNotificationInd = (NotificationInd)
87                    PduPersister.getPduPersister(context).load(mUri);
88        } catch (MmsException e) {
89            Log.e(TAG, "Failed to load NotificationInd from: " + uriString, e);
90            throw new IllegalArgumentException();
91        }
92
93        mId = new String(mNotificationInd.getTransactionId());
94        mContentLocation = new String(mNotificationInd.getContentLocation());
95
96        // Attach the transaction to the instance of RetryScheduler.
97        attach(RetryScheduler.getInstance(context));
98    }
99
100    /**
101     * This constructor is only used for test purposes.
102     */
103    public NotificationTransaction(
104            Context context, int serviceId,
105            TransactionSettings connectionSettings, NotificationInd ind) {
106        super(context, serviceId, connectionSettings);
107
108        try {
109            mUri = PduPersister.getPduPersister(context).persist(
110                        ind, Inbox.CONTENT_URI);
111        } catch (MmsException e) {
112            Log.e(TAG, "Failed to save NotificationInd in constructor.", e);
113            throw new IllegalArgumentException();
114        }
115
116        mNotificationInd = ind;
117        mId = new String(ind.getTransactionId());
118    }
119
120    /*
121     * (non-Javadoc)
122     * @see com.google.android.mms.pdu.Transaction#process()
123     */
124    @Override
125    public void process() {
126        new Thread(this, "NotificationTransaction").start();
127    }
128
129    public void run() {
130        DownloadManager downloadManager = DownloadManager.getInstance();
131        boolean autoDownload = downloadManager.isAuto();
132        boolean dataSuspended = (MmsApp.getApplication().getTelephonyManager().getDataState() ==
133                TelephonyManager.DATA_SUSPENDED);
134        try {
135            if (LOCAL_LOGV) {
136                Log.v(TAG, "Notification transaction launched: " + this);
137            }
138
139            // By default, we set status to STATUS_DEFERRED because we
140            // should response MMSC with STATUS_DEFERRED when we cannot
141            // download a MM immediately.
142            int status = STATUS_DEFERRED;
143            // Don't try to download when data is suspended, as it will fail, so defer download
144            if (!autoDownload || dataSuspended) {
145                downloadManager.markState(mUri, DownloadManager.STATE_UNSTARTED);
146                sendNotifyRespInd(status);
147                return;
148            }
149
150            downloadManager.markState(mUri, DownloadManager.STATE_DOWNLOADING);
151
152            if (LOCAL_LOGV) {
153                Log.v(TAG, "Content-Location: " + mContentLocation);
154            }
155
156            byte[] retrieveConfData = null;
157            // We should catch exceptions here to response MMSC
158            // with STATUS_DEFERRED.
159            try {
160                retrieveConfData = getPdu(mContentLocation);
161            } catch (IOException e) {
162                mTransactionState.setState(FAILED);
163            }
164
165            if (retrieveConfData != null) {
166                GenericPdu pdu = new PduParser(retrieveConfData).parse();
167                if ((pdu == null) || (pdu.getMessageType() != MESSAGE_TYPE_RETRIEVE_CONF)) {
168                    Log.e(TAG, "Invalid M-RETRIEVE.CONF PDU. " +
169                            (pdu != null ? "message type: " + pdu.getMessageType() : "null pdu"));
170                    mTransactionState.setState(FAILED);
171                    status = STATUS_UNRECOGNIZED;
172                } else {
173                    // Save the received PDU (must be a M-RETRIEVE.CONF).
174                    PduPersister p = PduPersister.getPduPersister(mContext);
175                    Uri uri = p.persist(pdu, Inbox.CONTENT_URI);
176
177                    // Use local time instead of PDU time
178                    ContentValues values = new ContentValues(1);
179                    values.put(Mms.DATE, System.currentTimeMillis() / 1000L);
180                    SqliteWrapper.update(mContext, mContext.getContentResolver(),
181                            uri, values, null, null);
182
183                    // We have successfully downloaded the new MM. Delete the
184                    // M-NotifyResp.ind from Inbox.
185                    SqliteWrapper.delete(mContext, mContext.getContentResolver(),
186                                         mUri, null, null);
187                    // Notify observers with newly received MM.
188                    mUri = uri;
189                    status = STATUS_RETRIEVED;
190                }
191            }
192
193            if (LOCAL_LOGV) {
194                Log.v(TAG, "status=0x" + Integer.toHexString(status));
195            }
196
197            // Check the status and update the result state of this Transaction.
198            switch (status) {
199                case STATUS_RETRIEVED:
200                    mTransactionState.setState(SUCCESS);
201                    break;
202                case STATUS_DEFERRED:
203                    // STATUS_DEFERRED, may be a failed immediate retrieval.
204                    if (mTransactionState.getState() == INITIALIZED) {
205                        mTransactionState.setState(SUCCESS);
206                    }
207                    break;
208            }
209
210            sendNotifyRespInd(status);
211
212            // Make sure this thread isn't over the limits in message count.
213            Recycler.getMmsRecycler().deleteOldMessagesInSameThreadAsMessage(mContext, mUri);
214            MmsWidgetProvider.notifyDatasetChanged(mContext);
215        } catch (Throwable t) {
216            Log.e(TAG, Log.getStackTraceString(t));
217        } finally {
218            mTransactionState.setContentUri(mUri);
219            if (!autoDownload || dataSuspended) {
220                // Always mark the transaction successful for deferred
221                // download since any error here doesn't make sense.
222                mTransactionState.setState(SUCCESS);
223            }
224            if (mTransactionState.getState() != SUCCESS) {
225                mTransactionState.setState(FAILED);
226                Log.e(TAG, "NotificationTransaction failed.");
227            }
228            notifyObservers();
229        }
230    }
231
232    private void sendNotifyRespInd(int status) throws MmsException, IOException {
233        // Create the M-NotifyResp.ind
234        NotifyRespInd notifyRespInd = new NotifyRespInd(
235                PduHeaders.CURRENT_MMS_VERSION,
236                mNotificationInd.getTransactionId(),
237                status);
238
239        // Pack M-NotifyResp.ind and send it
240        if(MmsConfig.getNotifyWapMMSC()) {
241            sendPdu(new PduComposer(mContext, notifyRespInd).make(), mContentLocation);
242        } else {
243            sendPdu(new PduComposer(mContext, notifyRespInd).make());
244        }
245    }
246
247    @Override
248    public int getType() {
249        return NOTIFICATION_TRANSACTION;
250    }
251}
252