• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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