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