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