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 com.android.mms.MmsConfig;
     21 import com.android.mms.ui.MessageUtils;
     22 import com.android.mms.util.DownloadManager;
     23 import com.android.mms.util.Recycler;
     24 import com.google.android.mms.MmsException;
     25 import com.google.android.mms.pdu.AcknowledgeInd;
     26 import com.google.android.mms.pdu.PduComposer;
     27 import com.google.android.mms.pdu.PduHeaders;
     28 import com.google.android.mms.pdu.PduParser;
     29 import com.google.android.mms.pdu.PduPersister;
     30 import com.google.android.mms.pdu.RetrieveConf;
     31 import com.google.android.mms.pdu.EncodedStringValue;
     32 import android.database.sqlite.SqliteWrapper;
     33 
     34 import android.content.ContentValues;
     35 import android.content.Context;
     36 import android.database.Cursor;
     37 import android.net.Uri;
     38 import android.provider.Telephony.Mms;
     39 import android.provider.Telephony.Mms.Inbox;
     40 import android.util.Log;
     41 
     42 import java.io.IOException;
     43 
     44 /**
     45  * The RetrieveTransaction is responsible for retrieving multimedia
     46  * messages (M-Retrieve.conf) from the MMSC server.  It:
     47  *
     48  * <ul>
     49  * <li>Sends a GET request to the MMSC server.
     50  * <li>Retrieves the binary M-Retrieve.conf data and parses it.
     51  * <li>Persists the retrieve multimedia message.
     52  * <li>Determines whether an acknowledgement is required.
     53  * <li>Creates appropriate M-Acknowledge.ind and sends it to MMSC server.
     54  * <li>Notifies the TransactionService about succesful completion.
     55  * </ul>
     56  */
     57 public class RetrieveTransaction extends Transaction implements Runnable {
     58     private static final String TAG = "RetrieveTransaction";
     59     private static final boolean DEBUG = false;
     60     private static final boolean LOCAL_LOGV = false;
     61 
     62     private final Uri mUri;
     63     private final String mContentLocation;
     64     private boolean mLocked;
     65 
     66     static final String[] PROJECTION = new String[] {
     67         Mms.CONTENT_LOCATION,
     68         Mms.LOCKED
     69     };
     70 
     71     // The indexes of the columns which must be consistent with above PROJECTION.
     72     static final int COLUMN_CONTENT_LOCATION      = 0;
     73     static final int COLUMN_LOCKED                = 1;
     74 
     75     public RetrieveTransaction(Context context, int serviceId,
     76             TransactionSettings connectionSettings, String uri)
     77             throws MmsException {
     78         super(context, serviceId, connectionSettings);
     79 
     80         if (uri.startsWith("content://")) {
     81             mUri = Uri.parse(uri); // The Uri of the M-Notification.ind
     82             mId = mContentLocation = getContentLocation(context, mUri);
     83             if (LOCAL_LOGV) {
     84                 Log.v(TAG, "X-Mms-Content-Location: " + mContentLocation);
     85             }
     86         } else {
     87             throw new IllegalArgumentException(
     88                     "Initializing from X-Mms-Content-Location is abandoned!");
     89         }
     90 
     91         // Attach the transaction to the instance of RetryScheduler.
     92         attach(RetryScheduler.getInstance(context));
     93     }
     94 
     95     private String getContentLocation(Context context, Uri uri)
     96             throws MmsException {
     97         Cursor cursor = SqliteWrapper.query(context, context.getContentResolver(),
     98                             uri, PROJECTION, null, null, null);
     99         mLocked = false;
    100 
    101         if (cursor != null) {
    102             try {
    103                 if ((cursor.getCount() == 1) && cursor.moveToFirst()) {
    104                     // Get the locked flag from the M-Notification.ind so it can be transferred
    105                     // to the real message after the download.
    106                     mLocked = cursor.getInt(COLUMN_LOCKED) == 1;
    107                     return cursor.getString(COLUMN_CONTENT_LOCATION);
    108                 }
    109             } finally {
    110                 cursor.close();
    111             }
    112         }
    113 
    114         throw new MmsException("Cannot get X-Mms-Content-Location from: " + uri);
    115     }
    116 
    117     /*
    118      * (non-Javadoc)
    119      * @see com.android.mms.transaction.Transaction#process()
    120      */
    121     @Override
    122     public void process() {
    123         new Thread(this).start();
    124     }
    125 
    126     public void run() {
    127         try {
    128             // Change the downloading state of the M-Notification.ind.
    129             DownloadManager.getInstance().markState(
    130                     mUri, DownloadManager.STATE_DOWNLOADING);
    131 
    132             // Send GET request to MMSC and retrieve the response data.
    133             byte[] resp = getPdu(mContentLocation);
    134 
    135             // Parse M-Retrieve.conf
    136             RetrieveConf retrieveConf = (RetrieveConf) new PduParser(resp).parse();
    137             if (null == retrieveConf) {
    138                 throw new MmsException("Invalid M-Retrieve.conf PDU.");
    139             }
    140 
    141             Uri msgUri = null;
    142             if (isDuplicateMessage(mContext, retrieveConf)) {
    143                 // Mark this transaction as failed to prevent duplicate
    144                 // notification to user.
    145                 mTransactionState.setState(TransactionState.FAILED);
    146                 mTransactionState.setContentUri(mUri);
    147             } else {
    148                 // Store M-Retrieve.conf into Inbox
    149                 PduPersister persister = PduPersister.getPduPersister(mContext);
    150                 msgUri = persister.persist(retrieveConf, Inbox.CONTENT_URI);
    151 
    152                 // Use local time instead of PDU time
    153                 ContentValues values = new ContentValues(1);
    154                 values.put(Mms.DATE, System.currentTimeMillis() / 1000L);
    155                 SqliteWrapper.update(mContext, mContext.getContentResolver(),
    156                         msgUri, values, null, null);
    157 
    158                 // The M-Retrieve.conf has been successfully downloaded.
    159                 mTransactionState.setState(TransactionState.SUCCESS);
    160                 mTransactionState.setContentUri(msgUri);
    161                 // Remember the location the message was downloaded from.
    162                 // Since it's not critical, it won't fail the transaction.
    163                 // Copy over the locked flag from the M-Notification.ind in case
    164                 // the user locked the message before activating the download.
    165                 updateContentLocation(mContext, msgUri, mContentLocation, mLocked);
    166             }
    167 
    168             // Delete the corresponding M-Notification.ind.
    169             SqliteWrapper.delete(mContext, mContext.getContentResolver(),
    170                                  mUri, null, null);
    171 
    172             if (msgUri != null) {
    173                 // Have to delete messages over limit *after* the delete above. Otherwise,
    174                 // it would be counted as part of the total.
    175                 Recycler.getMmsRecycler().deleteOldMessagesInSameThreadAsMessage(mContext, msgUri);
    176             }
    177 
    178             // Send ACK to the Proxy-Relay to indicate we have fetched the
    179             // MM successfully.
    180             // Don't mark the transaction as failed if we failed to send it.
    181             sendAcknowledgeInd(retrieveConf);
    182         } catch (Throwable t) {
    183             Log.e(TAG, Log.getStackTraceString(t));
    184         } finally {
    185             if (mTransactionState.getState() != TransactionState.SUCCESS) {
    186                 mTransactionState.setState(TransactionState.FAILED);
    187                 mTransactionState.setContentUri(mUri);
    188                 Log.e(TAG, "Retrieval failed.");
    189             }
    190             notifyObservers();
    191         }
    192     }
    193 
    194     private static boolean isDuplicateMessage(Context context, RetrieveConf rc) {
    195         byte[] rawMessageId = rc.getMessageId();
    196         if (rawMessageId != null) {
    197             String messageId = new String(rawMessageId);
    198             String selection = "(" + Mms.MESSAGE_ID + " = ? AND "
    199                                    + Mms.MESSAGE_TYPE + " = ?)";
    200             String[] selectionArgs = new String[] { messageId,
    201                     String.valueOf(PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF) };
    202             Cursor cursor = SqliteWrapper.query(
    203                     context, context.getContentResolver(),
    204                     Mms.CONTENT_URI, new String[] { Mms._ID },
    205                     selection, selectionArgs, null);
    206             if (cursor != null) {
    207                 try {
    208                     if (cursor.getCount() > 0) {
    209                         // We already received the same message before.
    210                         return true;
    211                     }
    212                 } finally {
    213                     cursor.close();
    214                 }
    215             }
    216         }
    217         return false;
    218     }
    219 
    220     private void sendAcknowledgeInd(RetrieveConf rc) throws MmsException, IOException {
    221         // Send M-Acknowledge.ind to MMSC if required.
    222         // If the Transaction-ID isn't set in the M-Retrieve.conf, it means
    223         // the MMS proxy-relay doesn't require an ACK.
    224         byte[] tranId = rc.getTransactionId();
    225         if (tranId != null) {
    226             // Create M-Acknowledge.ind
    227             AcknowledgeInd acknowledgeInd = new AcknowledgeInd(
    228                     PduHeaders.CURRENT_MMS_VERSION, tranId);
    229 
    230             // insert the 'from' address per spec
    231             String lineNumber = MessageUtils.getLocalNumber();
    232             acknowledgeInd.setFrom(new EncodedStringValue(lineNumber));
    233 
    234             // Pack M-Acknowledge.ind and send it
    235             if(MmsConfig.getNotifyWapMMSC()) {
    236                 sendPdu(new PduComposer(mContext, acknowledgeInd).make(), mContentLocation);
    237             } else {
    238                 sendPdu(new PduComposer(mContext, acknowledgeInd).make());
    239             }
    240         }
    241     }
    242 
    243     private static void updateContentLocation(Context context, Uri uri,
    244                                               String contentLocation,
    245                                               boolean locked) {
    246         ContentValues values = new ContentValues(2);
    247         values.put(Mms.CONTENT_LOCATION, contentLocation);
    248         values.put(Mms.LOCKED, locked);     // preserve the state of the M-Notification.ind lock.
    249         SqliteWrapper.update(context, context.getContentResolver(),
    250                              uri, values, null, null);
    251     }
    252 
    253     @Override
    254     public int getType() {
    255         return RETRIEVE_TRANSACTION;
    256     }
    257 }
    258