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