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