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