Home | History | Annotate | Download | only in telephony
      1 /*
      2  * Copyright (C) 2008 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.internal.telephony;
     18 
     19 import static com.google.android.mms.pdu.PduHeaders.MESSAGE_TYPE_DELIVERY_IND;
     20 import static com.google.android.mms.pdu.PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND;
     21 import static com.google.android.mms.pdu.PduHeaders.MESSAGE_TYPE_READ_ORIG_IND;
     22 import android.app.Activity;
     23 import android.app.AppOpsManager;
     24 import android.app.BroadcastOptions;
     25 import android.content.BroadcastReceiver;
     26 import android.content.ComponentName;
     27 import android.content.ContentValues;
     28 import android.content.Context;
     29 import android.content.Intent;
     30 import android.content.IntentFilter;
     31 import android.content.ServiceConnection;
     32 import android.database.Cursor;
     33 import android.database.DatabaseUtils;
     34 import android.database.sqlite.SQLiteException;
     35 import android.database.sqlite.SqliteWrapper;
     36 import android.net.Uri;
     37 import android.os.Bundle;
     38 import android.os.IBinder;
     39 import android.os.IDeviceIdleController;
     40 import android.os.RemoteException;
     41 import android.os.ServiceManager;
     42 import android.os.UserHandle;
     43 import android.os.UserManager;
     44 import android.provider.Telephony;
     45 import android.provider.Telephony.Sms.Intents;
     46 import android.telephony.Rlog;
     47 import android.telephony.SmsManager;
     48 import android.telephony.SubscriptionManager;
     49 import android.util.Log;
     50 
     51 import com.android.internal.telephony.uicc.IccUtils;
     52 
     53 import java.util.HashMap;
     54 
     55 import com.google.android.mms.MmsException;
     56 import com.google.android.mms.pdu.DeliveryInd;
     57 import com.google.android.mms.pdu.GenericPdu;
     58 import com.google.android.mms.pdu.NotificationInd;
     59 import com.google.android.mms.pdu.PduHeaders;
     60 import com.google.android.mms.pdu.PduParser;
     61 import com.google.android.mms.pdu.PduPersister;
     62 import com.google.android.mms.pdu.ReadOrigInd;
     63 
     64 /**
     65  * WAP push handler class.
     66  *
     67  * @hide
     68  */
     69 public class WapPushOverSms implements ServiceConnection {
     70     private static final String TAG = "WAP PUSH";
     71     private static final boolean DBG = false;
     72 
     73     private final Context mContext;
     74     private IDeviceIdleController mDeviceIdleController;
     75 
     76     private String mWapPushManagerPackage;
     77 
     78     /** Assigned from ServiceConnection callback on main threaad. */
     79     private volatile IWapPushManager mWapPushManager;
     80 
     81     /** Broadcast receiver that binds to WapPushManager when the user unlocks the phone for the
     82      *  first time after reboot and the credential-encrypted storage is available.
     83      */
     84     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
     85         @Override
     86         public void onReceive(final Context context, Intent intent) {
     87             Rlog.d(TAG, "Received broadcast " + intent.getAction());
     88             if (Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) {
     89                 new BindServiceThread(mContext).start();
     90             }
     91         }
     92     };
     93 
     94     private class BindServiceThread extends Thread {
     95         private final Context context;
     96 
     97         private BindServiceThread(Context context) {
     98             this.context = context;
     99         }
    100 
    101         @Override
    102         public void run() {
    103             bindWapPushManagerService(context);
    104         }
    105     }
    106 
    107     private void bindWapPushManagerService(Context context) {
    108         Intent intent = new Intent(IWapPushManager.class.getName());
    109         ComponentName comp = intent.resolveSystemService(context.getPackageManager(), 0);
    110         intent.setComponent(comp);
    111         if (comp == null || !context.bindService(intent, this, Context.BIND_AUTO_CREATE)) {
    112             Rlog.e(TAG, "bindService() for wappush manager failed");
    113         } else {
    114             synchronized (this) {
    115                 mWapPushManagerPackage = comp.getPackageName();
    116             }
    117             if (DBG) Rlog.v(TAG, "bindService() for wappush manager succeeded");
    118         }
    119     }
    120 
    121     @Override
    122     public void onServiceConnected(ComponentName name, IBinder service) {
    123         mWapPushManager = IWapPushManager.Stub.asInterface(service);
    124         if (DBG) Rlog.v(TAG, "wappush manager connected to " + hashCode());
    125     }
    126 
    127     @Override
    128     public void onServiceDisconnected(ComponentName name) {
    129         mWapPushManager = null;
    130         if (DBG) Rlog.v(TAG, "wappush manager disconnected.");
    131     }
    132 
    133     public WapPushOverSms(Context context) {
    134         mContext = context;
    135         mDeviceIdleController = TelephonyComponentFactory.getInstance().getIDeviceIdleController();
    136 
    137         UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
    138 
    139         if (userManager.isUserUnlocked()) {
    140             bindWapPushManagerService(mContext);
    141         } else {
    142             IntentFilter userFilter = new IntentFilter();
    143             userFilter.addAction(Intent.ACTION_USER_UNLOCKED);
    144             context.registerReceiver(mBroadcastReceiver, userFilter);
    145         }
    146     }
    147 
    148     public void dispose() {
    149         if (mWapPushManager != null) {
    150             if (DBG) Rlog.v(TAG, "dispose: unbind wappush manager");
    151             mContext.unbindService(this);
    152         } else {
    153             Rlog.e(TAG, "dispose: not bound to a wappush manager");
    154         }
    155     }
    156 
    157     /**
    158      * Decodes the wap push pdu. The decoded result is wrapped inside the {@link DecodedResult}
    159      * object. The caller of this method should check {@link DecodedResult#statusCode} for the
    160      * decoding status. It  can have the following values.
    161      *
    162      * Activity.RESULT_OK - the wap push pdu is successfully decoded and should be further processed
    163      * Intents.RESULT_SMS_HANDLED - the wap push pdu should be ignored.
    164      * Intents.RESULT_SMS_GENERIC_ERROR - the pdu is invalid.
    165      */
    166     private DecodedResult decodeWapPdu(byte[] pdu, InboundSmsHandler handler) {
    167         DecodedResult result = new DecodedResult();
    168         if (DBG) Rlog.d(TAG, "Rx: " + IccUtils.bytesToHexString(pdu));
    169 
    170         try {
    171             int index = 0;
    172             int transactionId = pdu[index++] & 0xFF;
    173             int pduType = pdu[index++] & 0xFF;
    174 
    175             // Should we "abort" if no subId for now just no supplying extra param below
    176             int phoneId = handler.getPhone().getPhoneId();
    177 
    178             if ((pduType != WspTypeDecoder.PDU_TYPE_PUSH) &&
    179                     (pduType != WspTypeDecoder.PDU_TYPE_CONFIRMED_PUSH)) {
    180                 index = mContext.getResources().getInteger(
    181                         com.android.internal.R.integer.config_valid_wappush_index);
    182                 if (index != -1) {
    183                     transactionId = pdu[index++] & 0xff;
    184                     pduType = pdu[index++] & 0xff;
    185                     if (DBG)
    186                         Rlog.d(TAG, "index = " + index + " PDU Type = " + pduType +
    187                                 " transactionID = " + transactionId);
    188 
    189                     // recheck wap push pduType
    190                     if ((pduType != WspTypeDecoder.PDU_TYPE_PUSH)
    191                             && (pduType != WspTypeDecoder.PDU_TYPE_CONFIRMED_PUSH)) {
    192                         if (DBG) Rlog.w(TAG, "Received non-PUSH WAP PDU. Type = " + pduType);
    193                         result.statusCode = Intents.RESULT_SMS_HANDLED;
    194                         return result;
    195                     }
    196                 } else {
    197                     if (DBG) Rlog.w(TAG, "Received non-PUSH WAP PDU. Type = " + pduType);
    198                     result.statusCode = Intents.RESULT_SMS_HANDLED;
    199                     return result;
    200                 }
    201             }
    202 
    203             WspTypeDecoder pduDecoder =
    204                     TelephonyComponentFactory.getInstance().makeWspTypeDecoder(pdu);
    205 
    206             /**
    207              * Parse HeaderLen(unsigned integer).
    208              * From wap-230-wsp-20010705-a section 8.1.2
    209              * The maximum size of a uintvar is 32 bits.
    210              * So it will be encoded in no more than 5 octets.
    211              */
    212             if (pduDecoder.decodeUintvarInteger(index) == false) {
    213                 if (DBG) Rlog.w(TAG, "Received PDU. Header Length error.");
    214                 result.statusCode = Intents.RESULT_SMS_GENERIC_ERROR;
    215                 return result;
    216             }
    217             int headerLength = (int) pduDecoder.getValue32();
    218             index += pduDecoder.getDecodedDataLength();
    219 
    220             int headerStartIndex = index;
    221 
    222             /**
    223              * Parse Content-Type.
    224              * From wap-230-wsp-20010705-a section 8.4.2.24
    225              *
    226              * Content-type-value = Constrained-media | Content-general-form
    227              * Content-general-form = Value-length Media-type
    228              * Media-type = (Well-known-media | Extension-Media) *(Parameter)
    229              * Value-length = Short-length | (Length-quote Length)
    230              * Short-length = <Any octet 0-30>   (octet <= WAP_PDU_SHORT_LENGTH_MAX)
    231              * Length-quote = <Octet 31>         (WAP_PDU_LENGTH_QUOTE)
    232              * Length = Uintvar-integer
    233              */
    234             if (pduDecoder.decodeContentType(index) == false) {
    235                 if (DBG) Rlog.w(TAG, "Received PDU. Header Content-Type error.");
    236                 result.statusCode = Intents.RESULT_SMS_GENERIC_ERROR;
    237                 return result;
    238             }
    239 
    240             String mimeType = pduDecoder.getValueString();
    241             long binaryContentType = pduDecoder.getValue32();
    242             index += pduDecoder.getDecodedDataLength();
    243 
    244             byte[] header = new byte[headerLength];
    245             System.arraycopy(pdu, headerStartIndex, header, 0, header.length);
    246 
    247             byte[] intentData;
    248 
    249             if (mimeType != null && mimeType.equals(WspTypeDecoder.CONTENT_TYPE_B_PUSH_CO)) {
    250                 intentData = pdu;
    251             } else {
    252                 int dataIndex = headerStartIndex + headerLength;
    253                 intentData = new byte[pdu.length - dataIndex];
    254                 System.arraycopy(pdu, dataIndex, intentData, 0, intentData.length);
    255             }
    256 
    257             int[] subIds = SubscriptionManager.getSubId(phoneId);
    258             int subId = (subIds != null) && (subIds.length > 0) ? subIds[0]
    259                     : SmsManager.getDefaultSmsSubscriptionId();
    260 
    261             // Continue if PDU parsing fails: the default messaging app may successfully parse the
    262             // same PDU.
    263             GenericPdu parsedPdu = null;
    264             try {
    265                 parsedPdu = new PduParser(intentData, shouldParseContentDisposition(subId)).parse();
    266             } catch (Exception e) {
    267                 Rlog.e(TAG, "Unable to parse PDU: " + e.toString());
    268             }
    269 
    270             if (parsedPdu != null && parsedPdu.getMessageType() == MESSAGE_TYPE_NOTIFICATION_IND) {
    271                 final NotificationInd nInd = (NotificationInd) parsedPdu;
    272                 if (nInd.getFrom() != null
    273                         && BlockChecker.isBlocked(mContext, nInd.getFrom().getString(), null)) {
    274                     result.statusCode = Intents.RESULT_SMS_HANDLED;
    275                     return result;
    276                 }
    277             }
    278 
    279             /**
    280              * Seek for application ID field in WSP header.
    281              * If application ID is found, WapPushManager substitute the message
    282              * processing. Since WapPushManager is optional module, if WapPushManager
    283              * is not found, legacy message processing will be continued.
    284              */
    285             if (pduDecoder.seekXWapApplicationId(index, index + headerLength - 1)) {
    286                 index = (int) pduDecoder.getValue32();
    287                 pduDecoder.decodeXWapApplicationId(index);
    288                 String wapAppId = pduDecoder.getValueString();
    289                 if (wapAppId == null) {
    290                     wapAppId = Integer.toString((int) pduDecoder.getValue32());
    291                 }
    292                 result.wapAppId = wapAppId;
    293                 String contentType = ((mimeType == null) ?
    294                         Long.toString(binaryContentType) : mimeType);
    295                 result.contentType = contentType;
    296                 if (DBG) Rlog.v(TAG, "appid found: " + wapAppId + ":" + contentType);
    297             }
    298 
    299             result.subId = subId;
    300             result.phoneId = phoneId;
    301             result.parsedPdu = parsedPdu;
    302             result.mimeType = mimeType;
    303             result.transactionId = transactionId;
    304             result.pduType = pduType;
    305             result.header = header;
    306             result.intentData = intentData;
    307             result.contentTypeParameters = pduDecoder.getContentParameters();
    308             result.statusCode = Activity.RESULT_OK;
    309         } catch (ArrayIndexOutOfBoundsException aie) {
    310             // 0-byte WAP PDU or other unexpected WAP PDU contents can easily throw this;
    311             // log exception string without stack trace and return false.
    312             Rlog.e(TAG, "ignoring dispatchWapPdu() array index exception: " + aie);
    313             result.statusCode = Intents.RESULT_SMS_GENERIC_ERROR;
    314         }
    315         return result;
    316     }
    317 
    318     /**
    319      * Dispatches inbound messages that are in the WAP PDU format. See
    320      * wap-230-wsp-20010705-a section 8 for details on the WAP PDU format.
    321      *
    322      * @param pdu The WAP PDU, made up of one or more SMS PDUs
    323      * @return a result code from {@link android.provider.Telephony.Sms.Intents}, or
    324      *         {@link Activity#RESULT_OK} if the message has been broadcast
    325      *         to applications
    326      */
    327     public int dispatchWapPdu(byte[] pdu, BroadcastReceiver receiver, InboundSmsHandler handler) {
    328         DecodedResult result = decodeWapPdu(pdu, handler);
    329         if (result.statusCode != Activity.RESULT_OK) {
    330             return result.statusCode;
    331         }
    332 
    333         if (SmsManager.getDefault().getAutoPersisting()) {
    334             // Store the wap push data in telephony
    335             writeInboxMessage(result.subId, result.parsedPdu);
    336         }
    337 
    338         /**
    339          * If the pdu has application ID, WapPushManager substitute the message
    340          * processing. Since WapPushManager is optional module, if WapPushManager
    341          * is not found, legacy message processing will be continued.
    342          */
    343         if (result.wapAppId != null) {
    344             try {
    345                 boolean processFurther = true;
    346                 IWapPushManager wapPushMan = mWapPushManager;
    347 
    348                 if (wapPushMan == null) {
    349                     if (DBG) Rlog.w(TAG, "wap push manager not found!");
    350                 } else {
    351                     synchronized (this) {
    352                         mDeviceIdleController.addPowerSaveTempWhitelistAppForMms(
    353                                 mWapPushManagerPackage, 0, "mms-mgr");
    354                     }
    355 
    356                     Intent intent = new Intent();
    357                     intent.putExtra("transactionId", result.transactionId);
    358                     intent.putExtra("pduType", result.pduType);
    359                     intent.putExtra("header", result.header);
    360                     intent.putExtra("data", result.intentData);
    361                     intent.putExtra("contentTypeParameters", result.contentTypeParameters);
    362                     SubscriptionManager.putPhoneIdAndSubIdExtra(intent, result.phoneId);
    363 
    364                     int procRet = wapPushMan.processMessage(
    365                         result.wapAppId, result.contentType, intent);
    366                     if (DBG) Rlog.v(TAG, "procRet:" + procRet);
    367                     if ((procRet & WapPushManagerParams.MESSAGE_HANDLED) > 0
    368                             && (procRet & WapPushManagerParams.FURTHER_PROCESSING) == 0) {
    369                         processFurther = false;
    370                     }
    371                 }
    372                 if (!processFurther) {
    373                     return Intents.RESULT_SMS_HANDLED;
    374                 }
    375             } catch (RemoteException e) {
    376                 if (DBG) Rlog.w(TAG, "remote func failed...");
    377             }
    378         }
    379         if (DBG) Rlog.v(TAG, "fall back to existing handler");
    380 
    381         if (result.mimeType == null) {
    382             if (DBG) Rlog.w(TAG, "Header Content-Type error.");
    383             return Intents.RESULT_SMS_GENERIC_ERROR;
    384         }
    385 
    386         Intent intent = new Intent(Intents.WAP_PUSH_DELIVER_ACTION);
    387         intent.setType(result.mimeType);
    388         intent.putExtra("transactionId", result.transactionId);
    389         intent.putExtra("pduType", result.pduType);
    390         intent.putExtra("header", result.header);
    391         intent.putExtra("data", result.intentData);
    392         intent.putExtra("contentTypeParameters", result.contentTypeParameters);
    393         SubscriptionManager.putPhoneIdAndSubIdExtra(intent, result.phoneId);
    394 
    395         // Direct the intent to only the default MMS app. If we can't find a default MMS app
    396         // then sent it to all broadcast receivers.
    397         ComponentName componentName = SmsApplication.getDefaultMmsApplication(mContext, true);
    398         Bundle options = null;
    399         if (componentName != null) {
    400             // Deliver MMS message only to this receiver
    401             intent.setComponent(componentName);
    402             if (DBG) Rlog.v(TAG, "Delivering MMS to: " + componentName.getPackageName() +
    403                     " " + componentName.getClassName());
    404             try {
    405                 long duration = mDeviceIdleController.addPowerSaveTempWhitelistAppForMms(
    406                         componentName.getPackageName(), 0, "mms-app");
    407                 BroadcastOptions bopts = BroadcastOptions.makeBasic();
    408                 bopts.setTemporaryAppWhitelistDuration(duration);
    409                 options = bopts.toBundle();
    410             } catch (RemoteException e) {
    411             }
    412         }
    413 
    414         handler.dispatchIntent(intent, getPermissionForType(result.mimeType),
    415                 getAppOpsPermissionForIntent(result.mimeType), options, receiver,
    416                 UserHandle.SYSTEM);
    417         return Activity.RESULT_OK;
    418     }
    419 
    420     /**
    421      * Check whether the pdu is a MMS WAP push pdu that should be dispatched to the SMS app.
    422      */
    423     public boolean isWapPushForMms(byte[] pdu, InboundSmsHandler handler) {
    424         DecodedResult result = decodeWapPdu(pdu, handler);
    425         return result.statusCode == Activity.RESULT_OK
    426             && WspTypeDecoder.CONTENT_TYPE_B_MMS.equals(result.mimeType);
    427     }
    428 
    429     private static boolean shouldParseContentDisposition(int subId) {
    430         return SmsManager
    431                 .getSmsManagerForSubscriptionId(subId)
    432                 .getCarrierConfigValues()
    433                 .getBoolean(SmsManager.MMS_CONFIG_SUPPORT_MMS_CONTENT_DISPOSITION, true);
    434     }
    435 
    436     private void writeInboxMessage(int subId, GenericPdu pdu) {
    437         if (pdu == null) {
    438             Rlog.e(TAG, "Invalid PUSH PDU");
    439         }
    440         final PduPersister persister = PduPersister.getPduPersister(mContext);
    441         final int type = pdu.getMessageType();
    442         try {
    443             switch (type) {
    444                 case MESSAGE_TYPE_DELIVERY_IND:
    445                 case MESSAGE_TYPE_READ_ORIG_IND: {
    446                     final long threadId = getDeliveryOrReadReportThreadId(mContext, pdu);
    447                     if (threadId == -1) {
    448                         // The associated SendReq isn't found, therefore skip
    449                         // processing this PDU.
    450                         Rlog.e(TAG, "Failed to find delivery or read report's thread id");
    451                         break;
    452                     }
    453                     final Uri uri = persister.persist(
    454                             pdu,
    455                             Telephony.Mms.Inbox.CONTENT_URI,
    456                             true/*createThreadId*/,
    457                             true/*groupMmsEnabled*/,
    458                             null/*preOpenedFiles*/);
    459                     if (uri == null) {
    460                         Rlog.e(TAG, "Failed to persist delivery or read report");
    461                         break;
    462                     }
    463                     // Update thread ID for ReadOrigInd & DeliveryInd.
    464                     final ContentValues values = new ContentValues(1);
    465                     values.put(Telephony.Mms.THREAD_ID, threadId);
    466                     if (SqliteWrapper.update(
    467                             mContext,
    468                             mContext.getContentResolver(),
    469                             uri,
    470                             values,
    471                             null/*where*/,
    472                             null/*selectionArgs*/) != 1) {
    473                         Rlog.e(TAG, "Failed to update delivery or read report thread id");
    474                     }
    475                     break;
    476                 }
    477                 case MESSAGE_TYPE_NOTIFICATION_IND: {
    478                     final NotificationInd nInd = (NotificationInd) pdu;
    479 
    480                     Bundle configs = SmsManager.getSmsManagerForSubscriptionId(subId)
    481                             .getCarrierConfigValues();
    482                     if (configs != null && configs.getBoolean(
    483                         SmsManager.MMS_CONFIG_APPEND_TRANSACTION_ID, false)) {
    484                         final byte [] contentLocation = nInd.getContentLocation();
    485                         if ('=' == contentLocation[contentLocation.length - 1]) {
    486                             byte [] transactionId = nInd.getTransactionId();
    487                             byte [] contentLocationWithId = new byte [contentLocation.length
    488                                     + transactionId.length];
    489                             System.arraycopy(contentLocation, 0, contentLocationWithId,
    490                                     0, contentLocation.length);
    491                             System.arraycopy(transactionId, 0, contentLocationWithId,
    492                                     contentLocation.length, transactionId.length);
    493                             nInd.setContentLocation(contentLocationWithId);
    494                         }
    495                     }
    496                     if (!isDuplicateNotification(mContext, nInd)) {
    497                         final Uri uri = persister.persist(
    498                                 pdu,
    499                                 Telephony.Mms.Inbox.CONTENT_URI,
    500                                 true/*createThreadId*/,
    501                                 true/*groupMmsEnabled*/,
    502                                 null/*preOpenedFiles*/);
    503                         if (uri == null) {
    504                             Rlog.e(TAG, "Failed to save MMS WAP push notification ind");
    505                         }
    506                     } else {
    507                         Rlog.d(TAG, "Skip storing duplicate MMS WAP push notification ind: "
    508                                 + new String(nInd.getContentLocation()));
    509                     }
    510                     break;
    511                 }
    512                 default:
    513                     Log.e(TAG, "Received unrecognized WAP Push PDU.");
    514             }
    515         } catch (MmsException e) {
    516             Log.e(TAG, "Failed to save MMS WAP push data: type=" + type, e);
    517         } catch (RuntimeException e) {
    518             Log.e(TAG, "Unexpected RuntimeException in persisting MMS WAP push data", e);
    519         }
    520 
    521     }
    522 
    523     private static final String THREAD_ID_SELECTION =
    524             Telephony.Mms.MESSAGE_ID + "=? AND " + Telephony.Mms.MESSAGE_TYPE + "=?";
    525 
    526     private static long getDeliveryOrReadReportThreadId(Context context, GenericPdu pdu) {
    527         String messageId;
    528         if (pdu instanceof DeliveryInd) {
    529             messageId = new String(((DeliveryInd) pdu).getMessageId());
    530         } else if (pdu instanceof ReadOrigInd) {
    531             messageId = new String(((ReadOrigInd) pdu).getMessageId());
    532         } else {
    533             Rlog.e(TAG, "WAP Push data is neither delivery or read report type: "
    534                     + pdu.getClass().getCanonicalName());
    535             return -1L;
    536         }
    537         Cursor cursor = null;
    538         try {
    539             cursor = SqliteWrapper.query(
    540                     context,
    541                     context.getContentResolver(),
    542                     Telephony.Mms.CONTENT_URI,
    543                     new String[]{ Telephony.Mms.THREAD_ID },
    544                     THREAD_ID_SELECTION,
    545                     new String[]{
    546                             DatabaseUtils.sqlEscapeString(messageId),
    547                             Integer.toString(PduHeaders.MESSAGE_TYPE_SEND_REQ)
    548                     },
    549                     null/*sortOrder*/);
    550             if (cursor != null && cursor.moveToFirst()) {
    551                 return cursor.getLong(0);
    552             }
    553         } catch (SQLiteException e) {
    554             Rlog.e(TAG, "Failed to query delivery or read report thread id", e);
    555         } finally {
    556             if (cursor != null) {
    557                 cursor.close();
    558             }
    559         }
    560         return -1L;
    561     }
    562 
    563     private static final String LOCATION_SELECTION =
    564             Telephony.Mms.MESSAGE_TYPE + "=? AND " + Telephony.Mms.CONTENT_LOCATION + " =?";
    565 
    566     private static boolean isDuplicateNotification(Context context, NotificationInd nInd) {
    567         final byte[] rawLocation = nInd.getContentLocation();
    568         if (rawLocation != null) {
    569             String location = new String(rawLocation);
    570             String[] selectionArgs = new String[] { location };
    571             Cursor cursor = null;
    572             try {
    573                 cursor = SqliteWrapper.query(
    574                         context,
    575                         context.getContentResolver(),
    576                         Telephony.Mms.CONTENT_URI,
    577                         new String[]{Telephony.Mms._ID},
    578                         LOCATION_SELECTION,
    579                         new String[]{
    580                                 Integer.toString(PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND),
    581                                 new String(rawLocation)
    582                         },
    583                         null/*sortOrder*/);
    584                 if (cursor != null && cursor.getCount() > 0) {
    585                     // We already received the same notification before.
    586                     return true;
    587                 }
    588             } catch (SQLiteException e) {
    589                 Rlog.e(TAG, "failed to query existing notification ind", e);
    590             } finally {
    591                 if (cursor != null) {
    592                     cursor.close();
    593                 }
    594             }
    595         }
    596         return false;
    597     }
    598 
    599     public static String getPermissionForType(String mimeType) {
    600         String permission;
    601         if (WspTypeDecoder.CONTENT_TYPE_B_MMS.equals(mimeType)) {
    602             permission = android.Manifest.permission.RECEIVE_MMS;
    603         } else {
    604             permission = android.Manifest.permission.RECEIVE_WAP_PUSH;
    605         }
    606         return permission;
    607     }
    608 
    609     public static int getAppOpsPermissionForIntent(String mimeType) {
    610         int appOp;
    611         if (WspTypeDecoder.CONTENT_TYPE_B_MMS.equals(mimeType)) {
    612             appOp = AppOpsManager.OP_RECEIVE_MMS;
    613         } else {
    614             appOp = AppOpsManager.OP_RECEIVE_WAP_PUSH;
    615         }
    616         return appOp;
    617     }
    618 
    619     /**
    620      * Place holder for decoded Wap pdu data.
    621      */
    622     private final class DecodedResult {
    623         String mimeType;
    624         String contentType;
    625         int transactionId;
    626         int pduType;
    627         int phoneId;
    628         int subId;
    629         byte[] header;
    630         String wapAppId;
    631         byte[] intentData;
    632         HashMap<String, String> contentTypeParameters;
    633         GenericPdu parsedPdu;
    634         int statusCode;
    635     }
    636 }
    637