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