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