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