• 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 static android.provider.Telephony.Sms.Intents.WAP_PUSH_RECEIVED_ACTION;
21 import static com.google.android.mms.pdu.PduHeaders.MESSAGE_TYPE_DELIVERY_IND;
22 import static com.google.android.mms.pdu.PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND;
23 import static com.google.android.mms.pdu.PduHeaders.MESSAGE_TYPE_READ_ORIG_IND;
24 
25 import com.android.mms.MmsConfig;
26 import com.google.android.mms.ContentType;
27 import com.google.android.mms.MmsException;
28 import com.google.android.mms.pdu.DeliveryInd;
29 import com.google.android.mms.pdu.GenericPdu;
30 import com.google.android.mms.pdu.NotificationInd;
31 import com.google.android.mms.pdu.PduHeaders;
32 import com.google.android.mms.pdu.PduParser;
33 import com.google.android.mms.pdu.PduPersister;
34 import com.google.android.mms.pdu.ReadOrigInd;
35 import android.database.sqlite.SqliteWrapper;
36 
37 import android.content.BroadcastReceiver;
38 import android.content.ContentResolver;
39 import android.content.ContentValues;
40 import android.content.Context;
41 import android.content.Intent;
42 import android.database.Cursor;
43 import android.database.DatabaseUtils;
44 import android.net.Uri;
45 import android.os.AsyncTask;
46 import android.os.PowerManager;
47 import android.provider.Telephony.Mms;
48 import android.provider.Telephony.Mms.Inbox;
49 import android.util.Log;
50 
51 /**
52  * Receives Intent.WAP_PUSH_RECEIVED_ACTION intents and starts the
53  * TransactionService by passing the push-data to it.
54  */
55 public class PushReceiver extends BroadcastReceiver {
56     private static final String TAG = "PushReceiver";
57     private static final boolean DEBUG = false;
58     private static final boolean LOCAL_LOGV = false;
59 
60     private class ReceivePushTask extends AsyncTask<Intent,Void,Void> {
61         private Context mContext;
ReceivePushTask(Context context)62         public ReceivePushTask(Context context) {
63             mContext = context;
64         }
65 
66         @Override
doInBackground(Intent... intents)67         protected Void doInBackground(Intent... intents) {
68             Intent intent = intents[0];
69 
70             // Get raw PDU push-data from the message and parse it
71             byte[] pushData = intent.getByteArrayExtra("data");
72             PduParser parser = new PduParser(pushData);
73             GenericPdu pdu = parser.parse();
74 
75             if (null == pdu) {
76                 Log.e(TAG, "Invalid PUSH data");
77                 return null;
78             }
79 
80             PduPersister p = PduPersister.getPduPersister(mContext);
81             ContentResolver cr = mContext.getContentResolver();
82             int type = pdu.getMessageType();
83             long threadId = -1;
84 
85             try {
86                 switch (type) {
87                     case MESSAGE_TYPE_DELIVERY_IND:
88                     case MESSAGE_TYPE_READ_ORIG_IND: {
89                         threadId = findThreadId(mContext, pdu, type);
90                         if (threadId == -1) {
91                             // The associated SendReq isn't found, therefore skip
92                             // processing this PDU.
93                             break;
94                         }
95 
96                         Uri uri = p.persist(pdu, Inbox.CONTENT_URI);
97                         // Update thread ID for ReadOrigInd & DeliveryInd.
98                         ContentValues values = new ContentValues(1);
99                         values.put(Mms.THREAD_ID, threadId);
100                         SqliteWrapper.update(mContext, cr, uri, values, null, null);
101                         break;
102                     }
103                     case MESSAGE_TYPE_NOTIFICATION_IND: {
104                         NotificationInd nInd = (NotificationInd) pdu;
105 
106                         if (MmsConfig.getTransIdEnabled()) {
107                             byte [] contentLocation = nInd.getContentLocation();
108                             if ('=' == contentLocation[contentLocation.length - 1]) {
109                                 byte [] transactionId = nInd.getTransactionId();
110                                 byte [] contentLocationWithId = new byte [contentLocation.length
111                                                                           + transactionId.length];
112                                 System.arraycopy(contentLocation, 0, contentLocationWithId,
113                                         0, contentLocation.length);
114                                 System.arraycopy(transactionId, 0, contentLocationWithId,
115                                         contentLocation.length, transactionId.length);
116                                 nInd.setContentLocation(contentLocationWithId);
117                             }
118                         }
119 
120                         if (!isDuplicateNotification(mContext, nInd)) {
121                             Uri uri = p.persist(pdu, Inbox.CONTENT_URI);
122                             // Start service to finish the notification transaction.
123                             Intent svc = new Intent(mContext, TransactionService.class);
124                             svc.putExtra(TransactionBundle.URI, uri.toString());
125                             svc.putExtra(TransactionBundle.TRANSACTION_TYPE,
126                                     Transaction.NOTIFICATION_TRANSACTION);
127                             mContext.startService(svc);
128                         } else if (LOCAL_LOGV) {
129                             Log.v(TAG, "Skip downloading duplicate message: "
130                                     + new String(nInd.getContentLocation()));
131                         }
132                         break;
133                     }
134                     default:
135                         Log.e(TAG, "Received unrecognized PDU.");
136                 }
137             } catch (MmsException e) {
138                 Log.e(TAG, "Failed to save the data from PUSH: type=" + type, e);
139             } catch (RuntimeException e) {
140                 Log.e(TAG, "Unexpected RuntimeException.", e);
141             }
142 
143             if (LOCAL_LOGV) {
144                 Log.v(TAG, "PUSH Intent processed.");
145             }
146 
147             return null;
148         }
149     }
150 
151     @Override
onReceive(Context context, Intent intent)152     public void onReceive(Context context, Intent intent) {
153         if (intent.getAction().equals(WAP_PUSH_RECEIVED_ACTION)
154                 && ContentType.MMS_MESSAGE.equals(intent.getType())) {
155             if (LOCAL_LOGV) {
156                 Log.v(TAG, "Received PUSH Intent: " + intent);
157             }
158 
159             // Hold a wake lock for 5 seconds, enough to give any
160             // services we start time to take their own wake locks.
161             PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
162             PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
163                                             "MMS PushReceiver");
164             wl.acquire(5000);
165             new ReceivePushTask(context).execute(intent);
166         }
167     }
168 
findThreadId(Context context, GenericPdu pdu, int type)169     private static long findThreadId(Context context, GenericPdu pdu, int type) {
170         String messageId;
171 
172         if (type == MESSAGE_TYPE_DELIVERY_IND) {
173             messageId = new String(((DeliveryInd) pdu).getMessageId());
174         } else {
175             messageId = new String(((ReadOrigInd) pdu).getMessageId());
176         }
177 
178         StringBuilder sb = new StringBuilder('(');
179         sb.append(Mms.MESSAGE_ID);
180         sb.append('=');
181         sb.append(DatabaseUtils.sqlEscapeString(messageId));
182         sb.append(" AND ");
183         sb.append(Mms.MESSAGE_TYPE);
184         sb.append('=');
185         sb.append(PduHeaders.MESSAGE_TYPE_SEND_REQ);
186         // TODO ContentResolver.query() appends closing ')' to the selection argument
187         // sb.append(')');
188 
189         Cursor cursor = SqliteWrapper.query(context, context.getContentResolver(),
190                             Mms.CONTENT_URI, new String[] { Mms.THREAD_ID },
191                             sb.toString(), null, null);
192         if (cursor != null) {
193             try {
194                 if ((cursor.getCount() == 1) && cursor.moveToFirst()) {
195                     return cursor.getLong(0);
196                 }
197             } finally {
198                 cursor.close();
199             }
200         }
201 
202         return -1;
203     }
204 
isDuplicateNotification( Context context, NotificationInd nInd)205     private static boolean isDuplicateNotification(
206             Context context, NotificationInd nInd) {
207         byte[] rawLocation = nInd.getContentLocation();
208         if (rawLocation != null) {
209             String location = new String(rawLocation);
210             String selection = Mms.CONTENT_LOCATION + " = ?";
211             String[] selectionArgs = new String[] { location };
212             Cursor cursor = SqliteWrapper.query(
213                     context, context.getContentResolver(),
214                     Mms.CONTENT_URI, new String[] { Mms._ID },
215                     selection, selectionArgs, null);
216             if (cursor != null) {
217                 try {
218                     if (cursor.getCount() > 0) {
219                         // We already received the same notification before.
220                         return true;
221                     }
222                 } finally {
223                     cursor.close();
224                 }
225             }
226         }
227         return false;
228     }
229 }
230