• 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 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 
RetrieveTransaction(Context context, int serviceId, TransactionSettings connectionSettings, String uri)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 
getContentLocation(Context context, Uri uri)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
process()122     public void process() {
123         new Thread(this).start();
124     }
125 
run()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 
isDuplicateMessage(Context context, RetrieveConf rc)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 
sendAcknowledgeInd(RetrieveConf rc)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 
updateContentLocation(Context context, Uri uri, String contentLocation, boolean locked)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
getType()254     public int getType() {
255         return RETRIEVE_TRANSACTION;
256     }
257 }
258