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