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