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