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.android.mms.widget.MmsWidgetProvider;
     33 import com.google.android.mms.MmsException;
     34 import com.google.android.mms.pdu.GenericPdu;
     35 import com.google.android.mms.pdu.NotificationInd;
     36 import com.google.android.mms.pdu.NotifyRespInd;
     37 import com.google.android.mms.pdu.PduComposer;
     38 import com.google.android.mms.pdu.PduHeaders;
     39 import com.google.android.mms.pdu.PduParser;
     40 import com.google.android.mms.pdu.PduPersister;
     41 import android.database.sqlite.SqliteWrapper;
     42 
     43 import android.content.ContentValues;
     44 import android.content.Context;
     45 import android.database.Cursor;
     46 import android.net.Uri;
     47 import android.provider.Telephony.Mms;
     48 import android.provider.Telephony.Mms.Inbox;
     49 import android.telephony.TelephonyManager;
     50 import android.util.Log;
     51 
     52 import java.io.IOException;
     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  */
     70 public 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             mUri = PduPersister.getPduPersister(context).persist(
    111                         ind, Inbox.CONTENT_URI);
    112         } catch (MmsException e) {
    113             Log.e(TAG, "Failed to save NotificationInd in constructor.", e);
    114             throw new IllegalArgumentException();
    115         }
    116 
    117         mNotificationInd = ind;
    118         mId = new String(ind.getTransactionId());
    119     }
    120 
    121     /*
    122      * (non-Javadoc)
    123      * @see com.google.android.mms.pdu.Transaction#process()
    124      */
    125     @Override
    126     public void process() {
    127         new Thread(this, "NotificationTransaction").start();
    128     }
    129 
    130     public void run() {
    131         DownloadManager downloadManager = DownloadManager.getInstance();
    132         boolean autoDownload = downloadManager.isAuto();
    133         boolean dataSuspended = (MmsApp.getApplication().getTelephonyManager().getDataState() ==
    134                 TelephonyManager.DATA_SUSPENDED);
    135         try {
    136             if (LOCAL_LOGV) {
    137                 Log.v(TAG, "Notification transaction launched: " + this);
    138             }
    139 
    140             // By default, we set status to STATUS_DEFERRED because we
    141             // should response MMSC with STATUS_DEFERRED when we cannot
    142             // download a MM immediately.
    143             int status = STATUS_DEFERRED;
    144             // Don't try to download when data is suspended, as it will fail, so defer download
    145             if (!autoDownload || dataSuspended) {
    146                 downloadManager.markState(mUri, DownloadManager.STATE_UNSTARTED);
    147                 sendNotifyRespInd(status);
    148                 return;
    149             }
    150 
    151             downloadManager.markState(mUri, DownloadManager.STATE_DOWNLOADING);
    152 
    153             if (LOCAL_LOGV) {
    154                 Log.v(TAG, "Content-Location: " + mContentLocation);
    155             }
    156 
    157             byte[] retrieveConfData = null;
    158             // We should catch exceptions here to response MMSC
    159             // with STATUS_DEFERRED.
    160             try {
    161                 retrieveConfData = getPdu(mContentLocation);
    162             } catch (IOException e) {
    163                 mTransactionState.setState(FAILED);
    164             }
    165 
    166             if (retrieveConfData != null) {
    167                 GenericPdu pdu = new PduParser(retrieveConfData).parse();
    168                 if ((pdu == null) || (pdu.getMessageType() != MESSAGE_TYPE_RETRIEVE_CONF)) {
    169                     Log.e(TAG, "Invalid M-RETRIEVE.CONF PDU. " +
    170                             (pdu != null ? "message type: " + pdu.getMessageType() : "null pdu"));
    171                     mTransactionState.setState(FAILED);
    172                     status = STATUS_UNRECOGNIZED;
    173                 } else {
    174                     // Save the received PDU (must be a M-RETRIEVE.CONF).
    175                     PduPersister p = PduPersister.getPduPersister(mContext);
    176                     Uri uri = p.persist(pdu, Inbox.CONTENT_URI);
    177 
    178                     // Use local time instead of PDU time
    179                     ContentValues values = new ContentValues(1);
    180                     values.put(Mms.DATE, System.currentTimeMillis() / 1000L);
    181                     SqliteWrapper.update(mContext, mContext.getContentResolver(),
    182                             uri, values, null, null);
    183 
    184                     // We have successfully downloaded the new MM. Delete the
    185                     // M-NotifyResp.ind from Inbox.
    186                     SqliteWrapper.delete(mContext, mContext.getContentResolver(),
    187                                          mUri, null, null);
    188                     // Notify observers with newly received MM.
    189                     mUri = uri;
    190                     status = STATUS_RETRIEVED;
    191                 }
    192             }
    193 
    194             if (LOCAL_LOGV) {
    195                 Log.v(TAG, "status=0x" + Integer.toHexString(status));
    196             }
    197 
    198             // Check the status and update the result state of this Transaction.
    199             switch (status) {
    200                 case STATUS_RETRIEVED:
    201                     mTransactionState.setState(SUCCESS);
    202                     break;
    203                 case STATUS_DEFERRED:
    204                     // STATUS_DEFERRED, may be a failed immediate retrieval.
    205                     if (mTransactionState.getState() == INITIALIZED) {
    206                         mTransactionState.setState(SUCCESS);
    207                     }
    208                     break;
    209             }
    210 
    211             sendNotifyRespInd(status);
    212 
    213             // Make sure this thread isn't over the limits in message count.
    214             Recycler.getMmsRecycler().deleteOldMessagesInSameThreadAsMessage(mContext, mUri);
    215             MmsWidgetProvider.notifyDatasetChanged(mContext);
    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