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.google.android.mms.ContentType; 26 import com.google.android.mms.MmsException; 27 import com.google.android.mms.pdu.DeliveryInd; 28 import com.google.android.mms.pdu.GenericPdu; 29 import com.google.android.mms.pdu.NotificationInd; 30 import com.google.android.mms.pdu.PduHeaders; 31 import com.google.android.mms.pdu.PduParser; 32 import com.google.android.mms.pdu.PduPersister; 33 import com.google.android.mms.pdu.ReadOrigInd; 34 import com.google.android.mms.util.SqliteWrapper; 35 36 import android.content.BroadcastReceiver; 37 import android.content.ContentResolver; 38 import android.content.ContentValues; 39 import android.content.Context; 40 import android.content.Intent; 41 import android.database.Cursor; 42 import android.database.DatabaseUtils; 43 import android.net.Uri; 44 import android.os.AsyncTask; 45 import android.os.PowerManager; 46 import android.provider.Telephony.Mms; 47 import android.provider.Telephony.Mms.Inbox; 48 import android.util.Config; 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 = DEBUG ? Config.LOGD : Config.LOGV; 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 if (!isDuplicateNotification(mContext, nInd)) { 106 Uri uri = p.persist(pdu, Inbox.CONTENT_URI); 107 // Start service to finish the notification transaction. 108 Intent svc = new Intent(mContext, TransactionService.class); 109 svc.putExtra(TransactionBundle.URI, uri.toString()); 110 svc.putExtra(TransactionBundle.TRANSACTION_TYPE, 111 Transaction.NOTIFICATION_TRANSACTION); 112 mContext.startService(svc); 113 } else if (LOCAL_LOGV) { 114 Log.v(TAG, "Skip downloading duplicate message: " 115 + new String(nInd.getContentLocation())); 116 } 117 break; 118 } 119 default: 120 Log.e(TAG, "Received unrecognized PDU."); 121 } 122 } catch (MmsException e) { 123 Log.e(TAG, "Failed to save the data from PUSH: type=" + type, e); 124 } catch (RuntimeException e) { 125 Log.e(TAG, "Unexpected RuntimeException.", e); 126 } 127 128 if (LOCAL_LOGV) { 129 Log.v(TAG, "PUSH Intent processed."); 130 } 131 132 return null; 133 } 134 } 135 136 @Override onReceive(Context context, Intent intent)137 public void onReceive(Context context, Intent intent) { 138 if (intent.getAction().equals(WAP_PUSH_RECEIVED_ACTION) 139 && ContentType.MMS_MESSAGE.equals(intent.getType())) { 140 if (LOCAL_LOGV) { 141 Log.v(TAG, "Received PUSH Intent: " + intent); 142 } 143 144 // Hold a wake lock for 5 seconds, enough to give any 145 // services we start time to take their own wake locks. 146 PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE); 147 PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, 148 "MMS PushReceiver"); 149 wl.acquire(5000); 150 new ReceivePushTask(context).execute(intent); 151 } 152 } 153 findThreadId(Context context, GenericPdu pdu, int type)154 private static long findThreadId(Context context, GenericPdu pdu, int type) { 155 String messageId; 156 157 if (type == MESSAGE_TYPE_DELIVERY_IND) { 158 messageId = new String(((DeliveryInd) pdu).getMessageId()); 159 } else { 160 messageId = new String(((ReadOrigInd) pdu).getMessageId()); 161 } 162 163 StringBuilder sb = new StringBuilder('('); 164 sb.append(Mms.MESSAGE_ID); 165 sb.append('='); 166 sb.append(DatabaseUtils.sqlEscapeString(messageId)); 167 sb.append(" AND "); 168 sb.append(Mms.MESSAGE_TYPE); 169 sb.append('='); 170 sb.append(PduHeaders.MESSAGE_TYPE_SEND_REQ); 171 // TODO ContentResolver.query() appends closing ')' to the selection argument 172 // sb.append(')'); 173 174 Cursor cursor = SqliteWrapper.query(context, context.getContentResolver(), 175 Mms.CONTENT_URI, new String[] { Mms.THREAD_ID }, 176 sb.toString(), null, null); 177 if (cursor != null) { 178 try { 179 if ((cursor.getCount() == 1) && cursor.moveToFirst()) { 180 return cursor.getLong(0); 181 } 182 } finally { 183 cursor.close(); 184 } 185 } 186 187 return -1; 188 } 189 isDuplicateNotification( Context context, NotificationInd nInd)190 private static boolean isDuplicateNotification( 191 Context context, NotificationInd nInd) { 192 byte[] rawLocation = nInd.getContentLocation(); 193 if (rawLocation != null) { 194 String location = new String(rawLocation); 195 String selection = Mms.CONTENT_LOCATION + " = ?"; 196 String[] selectionArgs = new String[] { location }; 197 Cursor cursor = SqliteWrapper.query( 198 context, context.getContentResolver(), 199 Mms.CONTENT_URI, new String[] { Mms._ID }, 200 selection, selectionArgs, null); 201 if (cursor != null) { 202 try { 203 if (cursor.getCount() > 0) { 204 // We already received the same notification before. 205 return true; 206 } 207 } finally { 208 cursor.close(); 209 } 210 } 211 } 212 return false; 213 } 214 } 215