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