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