Home | History | Annotate | Download | only in transaction
      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 
     18 package com.android.mms.transaction;
     19 
     20 import static com.android.mms.transaction.TransactionState.FAILED;
     21 import static com.android.mms.transaction.TransactionState.INITIALIZED;
     22 import static com.android.mms.transaction.TransactionState.SUCCESS;
     23 import static com.google.android.mms.pdu.PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF;
     24 import static com.google.android.mms.pdu.PduHeaders.STATUS_DEFERRED;
     25 import static com.google.android.mms.pdu.PduHeaders.STATUS_RETRIEVED;
     26 import static com.google.android.mms.pdu.PduHeaders.STATUS_UNRECOGNIZED;
     27 
     28 import com.android.mms.MmsApp;
     29 import com.android.mms.MmsConfig;
     30 import com.android.mms.util.DownloadManager;
     31 import com.android.mms.util.Recycler;
     32 import com.google.android.mms.MmsException;
     33 import com.google.android.mms.pdu.GenericPdu;
     34 import com.google.android.mms.pdu.NotificationInd;
     35 import com.google.android.mms.pdu.NotifyRespInd;
     36 import com.google.android.mms.pdu.PduComposer;
     37 import com.google.android.mms.pdu.PduHeaders;
     38 import com.google.android.mms.pdu.PduParser;
     39 import com.google.android.mms.pdu.PduPersister;
     40 import android.database.sqlite.SqliteWrapper;
     41 
     42 import android.content.ContentValues;
     43 import android.content.Context;
     44 import android.database.Cursor;
     45 import android.net.Uri;
     46 import android.provider.Telephony.Mms;
     47 import android.provider.Telephony.Mms.Inbox;
     48 import android.telephony.TelephonyManager;
     49 import android.util.Log;
     50 
     51 import java.io.IOException;
     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  */
     69 public 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).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         } catch (Throwable t) {
    215             Log.e(TAG, Log.getStackTraceString(t));
    216         } finally {
    217             mTransactionState.setContentUri(mUri);
    218             if (!autoDownload || dataSuspended) {
    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         if(MmsConfig.getNotifyWapMMSC()) {
    240             sendPdu(new PduComposer(mContext, notifyRespInd).make(), mContentLocation);
    241         } else {
    242             sendPdu(new PduComposer(mContext, notifyRespInd).make());
    243         }
    244     }
    245 
    246     @Override
    247     public int getType() {
    248         return NOTIFICATION_TRANSACTION;
    249     }
    250 }
    251