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