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