1 /* 2 * Copyright (C) 2014 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.mms.service; 18 19 import static com.google.android.mms.pdu.PduHeaders.MESSAGE_TYPE; 20 import static com.google.android.mms.pdu.PduHeaders.MESSAGE_TYPE_SEND_REQ; 21 22 import android.annotation.Nullable; 23 import android.app.PendingIntent; 24 import android.app.Service; 25 import android.content.ContentResolver; 26 import android.content.ContentUris; 27 import android.content.ContentValues; 28 import android.content.Context; 29 import android.content.Intent; 30 import android.content.SharedPreferences; 31 import android.database.sqlite.SQLiteException; 32 import android.net.Uri; 33 import android.os.Binder; 34 import android.os.Bundle; 35 import android.os.IBinder; 36 import android.os.ParcelFileDescriptor; 37 import android.os.Process; 38 import android.os.RemoteException; 39 import android.os.UserHandle; 40 import android.provider.Settings; 41 import android.provider.Telephony; 42 import android.security.NetworkSecurityPolicy; 43 import android.service.carrier.CarrierMessagingService; 44 import android.telephony.SmsManager; 45 import android.telephony.SubscriptionManager; 46 import android.telephony.TelephonyManager; 47 import android.telephony.data.ApnSetting; 48 import android.text.TextUtils; 49 import android.util.SparseArray; 50 51 import com.android.internal.telephony.IMms; 52 53 import com.google.android.mms.MmsException; 54 import com.google.android.mms.pdu.DeliveryInd; 55 import com.google.android.mms.pdu.GenericPdu; 56 import com.google.android.mms.pdu.NotificationInd; 57 import com.google.android.mms.pdu.PduParser; 58 import com.google.android.mms.pdu.PduPersister; 59 import com.google.android.mms.pdu.ReadOrigInd; 60 import com.google.android.mms.pdu.RetrieveConf; 61 import com.google.android.mms.pdu.SendReq; 62 import com.google.android.mms.util.SqliteWrapper; 63 64 import java.io.IOException; 65 import java.util.ArrayDeque; 66 import java.util.Arrays; 67 import java.util.List; 68 import java.util.Queue; 69 import java.util.concurrent.Callable; 70 import java.util.concurrent.ExecutorService; 71 import java.util.concurrent.Executors; 72 import java.util.concurrent.Future; 73 import java.util.concurrent.TimeUnit; 74 75 /** 76 * System service to process MMS API requests 77 */ 78 public class MmsService extends Service implements MmsRequest.RequestManager { 79 public static final int QUEUE_INDEX_SEND = 0; 80 public static final int QUEUE_INDEX_DOWNLOAD = 1; 81 82 private static final String SHARED_PREFERENCES_NAME = "mmspref"; 83 private static final String PREF_AUTO_PERSISTING = "autopersisting"; 84 85 // Maximum time to spend waiting to read data from a content provider before failing with error. 86 private static final int TASK_TIMEOUT_MS = 30 * 1000; 87 // Maximum size of MMS service supports - used on occassions when MMS messages are processed 88 // in a carrier independent manner (for example for imports and drafts) and the carrier 89 // specific size limit should not be used (as it could be lower on some carriers). 90 private static final int MAX_MMS_FILE_SIZE = 8 * 1024 * 1024; 91 92 // The default number of threads allowed to run MMS requests in each queue 93 public static final int THREAD_POOL_SIZE = 4; 94 95 /** Represents the received SMS message for importing. */ 96 public static final int SMS_TYPE_INCOMING = 0; 97 /** Represents the sent SMS message for importing. */ 98 public static final int SMS_TYPE_OUTGOING = 1; 99 /** Message status property: whether the message has been seen. */ 100 public static final String MESSAGE_STATUS_SEEN = "seen"; 101 /** Message status property: whether the message has been read. */ 102 public static final String MESSAGE_STATUS_READ = "read"; 103 104 // Pending requests that are waiting for the SIM to be available 105 // If a different SIM is currently used by previous requests, the following 106 // requests will stay in this queue until that SIM finishes its current requests in 107 // RequestQueue. 108 // Requests are not reordered. So, e.g. if current SIM is SIM1, a request for SIM2 will be 109 // blocked in the queue. And a later request for SIM1 will be appended to the queue, ordered 110 // after the request for SIM2, instead of being put into the running queue. 111 // TODO: persist this in case MmsService crashes 112 private final Queue<MmsRequest> mPendingSimRequestQueue = new ArrayDeque<>(); 113 114 // Thread pool for transferring PDU with MMS apps 115 private final ExecutorService mPduTransferExecutor = Executors.newCachedThreadPool(); 116 117 // A cache of MmsNetworkManager for SIMs 118 private final SparseArray<MmsNetworkManager> mNetworkManagerCache = new SparseArray<>(); 119 120 // The default TelephonyManager and a cache of TelephonyManagers for individual subscriptions 121 private TelephonyManager mDefaultTelephonyManager; 122 private final SparseArray<TelephonyManager> mTelephonyManagerCache = new SparseArray<>(); 123 124 // The current SIM ID for the running requests. Only one SIM can send/download MMS at a time. 125 private int mCurrentSubId; 126 // The current running MmsRequest count. 127 private int mRunningRequestCount; 128 129 // Running request queues, one thread pool per queue 130 // 0: send queue 131 // 1: download queue 132 private final ExecutorService[] mRunningRequestExecutors = new ExecutorService[2]; 133 getNetworkManager(int subId)134 private MmsNetworkManager getNetworkManager(int subId) { 135 synchronized (mNetworkManagerCache) { 136 MmsNetworkManager manager = mNetworkManagerCache.get(subId); 137 if (manager == null) { 138 manager = new MmsNetworkManager(this, subId); 139 mNetworkManagerCache.put(subId, manager); 140 } 141 return manager; 142 } 143 } 144 getTelephonyManager(int subId)145 private TelephonyManager getTelephonyManager(int subId) { 146 synchronized (mTelephonyManagerCache) { 147 if (mDefaultTelephonyManager == null) { 148 mDefaultTelephonyManager = (TelephonyManager) this.getSystemService( 149 Context.TELEPHONY_SERVICE); 150 } 151 152 TelephonyManager subSpecificTelephonyManager = mTelephonyManagerCache.get(subId); 153 if (subSpecificTelephonyManager == null) { 154 subSpecificTelephonyManager = mDefaultTelephonyManager.createForSubscriptionId( 155 subId); 156 mTelephonyManagerCache.put(subId, subSpecificTelephonyManager); 157 } 158 return subSpecificTelephonyManager; 159 } 160 } 161 enforceSystemUid()162 private void enforceSystemUid() { 163 if (Binder.getCallingUid() != Process.SYSTEM_UID) { 164 throw new SecurityException("Only system can call this service"); 165 } 166 } 167 168 @Nullable getCarrierMessagingServicePackageIfExists(int subId)169 private String getCarrierMessagingServicePackageIfExists(int subId) { 170 Intent intent = new Intent(CarrierMessagingService.SERVICE_INTERFACE); 171 TelephonyManager telephonyManager = getTelephonyManager(subId); 172 List<String> carrierPackages = telephonyManager.getCarrierPackageNamesForIntent(intent); 173 174 if (carrierPackages == null || carrierPackages.size() != 1) { 175 return null; 176 } else { 177 return carrierPackages.get(0); 178 } 179 } 180 181 private IMms.Stub mStub = new IMms.Stub() { 182 @Override 183 public void sendMessage(int subId, String callingPkg, Uri contentUri, 184 String locationUrl, Bundle configOverrides, PendingIntent sentIntent, 185 long messageId) { 186 LogUtil.d("sendMessage messageId: " + messageId); 187 enforceSystemUid(); 188 189 // Make sure the subId is correct 190 if (!SubscriptionManager.isValidSubscriptionId(subId)) { 191 LogUtil.e("Invalid subId " + subId); 192 sendErrorInPendingIntent(sentIntent); 193 return; 194 } 195 if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) { 196 subId = SubscriptionManager.getDefaultSmsSubscriptionId(); 197 } 198 199 // Make sure the subId is active 200 if (!isActiveSubId(subId)) { 201 sendErrorInPendingIntent(sentIntent); 202 return; 203 } 204 205 final SendRequest request = new SendRequest(MmsService.this, subId, contentUri, 206 locationUrl, sentIntent, callingPkg, configOverrides, MmsService.this, 207 messageId); 208 209 final String carrierMessagingServicePackage = 210 getCarrierMessagingServicePackageIfExists(subId); 211 212 if (carrierMessagingServicePackage != null) { 213 LogUtil.d(request.toString(), "sending message by carrier app"); 214 request.trySendingByCarrierApp(MmsService.this, carrierMessagingServicePackage); 215 return; 216 } 217 218 // Make sure subId has MMS data. We intentionally do this after attempting to send via a 219 // carrier messaging service as the carrier messaging service may want to handle this in 220 // a different way and may not be restricted by whether data is enabled for an APN on a 221 // given subscription. 222 if (!getTelephonyManager(subId).isDataEnabledForApn(ApnSetting.TYPE_MMS)) { 223 // ENABLE_MMS_DATA_REQUEST_REASON_OUTGOING_MMS is set for only SendReq case, since 224 // AcknowledgeInd and NotifyRespInd are parts of downloading sequence. 225 // TODO: Should consider ReadRecInd(Read Report)? 226 sendSettingsIntentForFailedMms(!isRawPduSendReq(contentUri), subId); 227 sendErrorInPendingIntent(sentIntent); 228 return; 229 } 230 231 addSimRequest(request); 232 } 233 234 @Override 235 public void downloadMessage(int subId, String callingPkg, String locationUrl, 236 Uri contentUri, Bundle configOverrides, 237 PendingIntent downloadedIntent, long messageId) { 238 // If the subId is no longer active it could be caused by an MVNO using multiple 239 // subIds, so we should try to download anyway. 240 // TODO: Fail fast when downloading will fail (i.e. SIM swapped) 241 LogUtil.d("downloadMessage: " + MmsHttpClient.redactUrlForNonVerbose(locationUrl) + 242 ", messageId: " + messageId); 243 244 enforceSystemUid(); 245 246 // Make sure the subId is correct 247 if (!SubscriptionManager.isValidSubscriptionId(subId)) { 248 LogUtil.e("Invalid subId " + subId); 249 sendErrorInPendingIntent(downloadedIntent); 250 return; 251 } 252 if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) { 253 subId = SubscriptionManager.getDefaultSmsSubscriptionId(); 254 } 255 256 final DownloadRequest request = new DownloadRequest(MmsService.this, subId, locationUrl, 257 contentUri, downloadedIntent, callingPkg, configOverrides, MmsService.this, 258 messageId); 259 260 final String carrierMessagingServicePackage = 261 getCarrierMessagingServicePackageIfExists(subId); 262 263 if (carrierMessagingServicePackage != null) { 264 LogUtil.d(request.toString(), "downloading message by carrier app"); 265 request.tryDownloadingByCarrierApp(MmsService.this, carrierMessagingServicePackage); 266 return; 267 } 268 269 // Make sure subId has MMS data 270 if (!getTelephonyManager(subId).isDataEnabledForApn(ApnSetting.TYPE_MMS)) { 271 sendSettingsIntentForFailedMms(/*isIncoming=*/ true, subId); 272 sendErrorInPendingIntent(downloadedIntent); 273 return; 274 } 275 276 addSimRequest(request); 277 } 278 279 @Override 280 public Uri importTextMessage(String callingPkg, String address, int type, String text, 281 long timestampMillis, boolean seen, boolean read) { 282 LogUtil.d("importTextMessage"); 283 enforceSystemUid(); 284 return importSms(address, type, text, timestampMillis, seen, read, callingPkg); 285 } 286 287 @Override 288 public Uri importMultimediaMessage(String callingPkg, Uri contentUri, 289 String messageId, long timestampSecs, boolean seen, boolean read) { 290 LogUtil.d("importMultimediaMessage"); 291 enforceSystemUid(); 292 return importMms(contentUri, messageId, timestampSecs, seen, read, callingPkg); 293 } 294 295 @Override 296 public boolean deleteStoredMessage(String callingPkg, Uri messageUri) 297 throws RemoteException { 298 LogUtil.d("deleteStoredMessage " + messageUri); 299 enforceSystemUid(); 300 if (!isSmsMmsContentUri(messageUri)) { 301 LogUtil.e("deleteStoredMessage: invalid message URI: " + messageUri.toString()); 302 return false; 303 } 304 // Clear the calling identity and query the database using the phone user id 305 // Otherwise the AppOps check in TelephonyProvider would complain about mismatch 306 // between the calling uid and the package uid 307 final long identity = Binder.clearCallingIdentity(); 308 try { 309 if (getContentResolver().delete( 310 messageUri, null/*where*/, null/*selectionArgs*/) != 1) { 311 LogUtil.e("deleteStoredMessage: failed to delete"); 312 return false; 313 } 314 } catch (SQLiteException e) { 315 LogUtil.e("deleteStoredMessage: failed to delete", e); 316 } finally { 317 Binder.restoreCallingIdentity(identity); 318 } 319 return true; 320 } 321 322 @Override 323 public boolean deleteStoredConversation(String callingPkg, long conversationId) 324 throws RemoteException { 325 LogUtil.d("deleteStoredConversation " + conversationId); 326 enforceSystemUid(); 327 if (conversationId == -1) { 328 LogUtil.e("deleteStoredConversation: invalid thread id"); 329 return false; 330 } 331 final Uri uri = ContentUris.withAppendedId( 332 Telephony.Threads.CONTENT_URI, conversationId); 333 // Clear the calling identity and query the database using the phone user id 334 // Otherwise the AppOps check in TelephonyProvider would complain about mismatch 335 // between the calling uid and the package uid 336 final long identity = Binder.clearCallingIdentity(); 337 try { 338 if (getContentResolver().delete(uri, null, null) != 1) { 339 LogUtil.e("deleteStoredConversation: failed to delete"); 340 return false; 341 } 342 } catch (SQLiteException e) { 343 LogUtil.e("deleteStoredConversation: failed to delete", e); 344 } finally { 345 Binder.restoreCallingIdentity(identity); 346 } 347 return true; 348 } 349 350 @Override 351 public boolean updateStoredMessageStatus(String callingPkg, Uri messageUri, 352 ContentValues statusValues) throws RemoteException { 353 LogUtil.d("updateStoredMessageStatus " + messageUri); 354 enforceSystemUid(); 355 return updateMessageStatus(messageUri, statusValues); 356 } 357 358 @Override 359 public boolean archiveStoredConversation(String callingPkg, long conversationId, 360 boolean archived) throws RemoteException { 361 LogUtil.d("archiveStoredConversation " + conversationId + " " + archived); 362 if (conversationId == -1) { 363 LogUtil.e("archiveStoredConversation: invalid thread id"); 364 return false; 365 } 366 return archiveConversation(conversationId, archived); 367 } 368 369 @Override 370 public Uri addTextMessageDraft(String callingPkg, String address, String text) 371 throws RemoteException { 372 LogUtil.d("addTextMessageDraft"); 373 enforceSystemUid(); 374 return addSmsDraft(address, text, callingPkg); 375 } 376 377 @Override 378 public Uri addMultimediaMessageDraft(String callingPkg, Uri contentUri) 379 throws RemoteException { 380 LogUtil.d("addMultimediaMessageDraft"); 381 enforceSystemUid(); 382 return addMmsDraft(contentUri, callingPkg); 383 } 384 385 @Override 386 public void sendStoredMessage(int subId, String callingPkg, Uri messageUri, 387 Bundle configOverrides, PendingIntent sentIntent) throws RemoteException { 388 throw new UnsupportedOperationException(); 389 } 390 391 @Override 392 public void setAutoPersisting(String callingPkg, boolean enabled) throws RemoteException { 393 LogUtil.d("setAutoPersisting " + enabled); 394 enforceSystemUid(); 395 final SharedPreferences preferences = getSharedPreferences( 396 SHARED_PREFERENCES_NAME, MODE_PRIVATE); 397 final SharedPreferences.Editor editor = preferences.edit(); 398 editor.putBoolean(PREF_AUTO_PERSISTING, enabled); 399 editor.apply(); 400 } 401 402 @Override 403 public boolean getAutoPersisting() throws RemoteException { 404 LogUtil.d("getAutoPersisting"); 405 return getAutoPersistingPref(); 406 } 407 408 /* 409 * @return true if the subId is active. 410 */ 411 private boolean isActiveSubId(int subId) { 412 return ((SubscriptionManager) getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE)) 413 .isActiveSubscriptionId(subId); 414 } 415 416 /* 417 * Calls the pending intent with <code>MMS_ERROR_NO_DATA_NETWORK</code>. 418 */ 419 private void sendErrorInPendingIntent(@Nullable PendingIntent intent) { 420 if (intent != null) { 421 try { 422 intent.send(SmsManager.MMS_ERROR_NO_DATA_NETWORK); 423 } catch (PendingIntent.CanceledException ex) { 424 } 425 } 426 } 427 428 private boolean isRawPduSendReq(Uri contentUri) { 429 // X-Mms-Message-Type is at the beginning of the message headers always. 1st byte is 430 // MMS-filed-name and 2nd byte is MMS-value for X-Mms-Message-Type field. 431 // See OMA-TS-MMS_ENC-V1_3-20110913-A, 7. Binary Encoding of ProtocolData Units 432 byte[] pduData = new byte[2]; 433 int bytesRead = readPduBytesFromContentUri(contentUri, pduData); 434 435 // Return true for MESSAGE_TYPE_SEND_REQ only. Otherwise false even wrong PDU case. 436 if (bytesRead == 2 437 && (pduData[0] & 0xFF) == MESSAGE_TYPE 438 && (pduData[1] & 0xFF) == MESSAGE_TYPE_SEND_REQ) { 439 return true; 440 } 441 return false; 442 } 443 }; 444 445 @Override addSimRequest(MmsRequest request)446 public void addSimRequest(MmsRequest request) { 447 if (request == null) { 448 LogUtil.e("Add running or pending: empty request"); 449 return; 450 } 451 LogUtil.d("Current running=" + mRunningRequestCount + ", " 452 + "current subId=" + mCurrentSubId + ", " 453 + "pending=" + mPendingSimRequestQueue.size()); 454 synchronized (this) { 455 if (mPendingSimRequestQueue.size() > 0 || 456 (mRunningRequestCount > 0 && request.getSubId() != mCurrentSubId)) { 457 LogUtil.d("Add request to pending queue." 458 + " Request subId=" + request.getSubId() + "," 459 + " current subId=" + mCurrentSubId); 460 mPendingSimRequestQueue.add(request); 461 if (mRunningRequestCount <= 0) { 462 LogUtil.e("Nothing's running but queue's not empty"); 463 // Nothing is running but we are accumulating on pending queue. 464 // This should not happen. But just in case... 465 movePendingSimRequestsToRunningSynchronized(); 466 } 467 } else { 468 addToRunningRequestQueueSynchronized(request); 469 } 470 } 471 } 472 sendSettingsIntentForFailedMms(boolean isIncoming, int subId)473 private void sendSettingsIntentForFailedMms(boolean isIncoming, int subId) { 474 LogUtil.w("Subscription with id: " + subId 475 + " cannot " + (isIncoming ? "download" : "send") 476 + " MMS, data connection is not available"); 477 Intent intent = new Intent(Settings.ACTION_ENABLE_MMS_DATA_REQUEST); 478 479 intent.putExtra(Settings.EXTRA_ENABLE_MMS_DATA_REQUEST_REASON, 480 isIncoming ? Settings.ENABLE_MMS_DATA_REQUEST_REASON_INCOMING_MMS 481 : Settings.ENABLE_MMS_DATA_REQUEST_REASON_OUTGOING_MMS); 482 483 intent.putExtra(Settings.EXTRA_SUB_ID, subId); 484 485 this.sendBroadcastAsUser(intent, UserHandle.SYSTEM, 486 android.Manifest.permission.NETWORK_SETTINGS); 487 } 488 addToRunningRequestQueueSynchronized(final MmsRequest request)489 private void addToRunningRequestQueueSynchronized(final MmsRequest request) { 490 LogUtil.d("Add request to running queue for subId " + request.getSubId()); 491 // Update current state of running requests 492 final int queue = request.getQueueType(); 493 if (queue < 0 || queue >= mRunningRequestExecutors.length) { 494 LogUtil.e("Invalid request queue index for running request"); 495 return; 496 } 497 mRunningRequestCount++; 498 mCurrentSubId = request.getSubId(); 499 // Send to the corresponding request queue for execution 500 mRunningRequestExecutors[queue].execute(new Runnable() { 501 @Override 502 public void run() { 503 try { 504 request.execute(MmsService.this, getNetworkManager(request.getSubId())); 505 } finally { 506 synchronized (MmsService.this) { 507 mRunningRequestCount--; 508 if (mRunningRequestCount <= 0) { 509 movePendingSimRequestsToRunningSynchronized(); 510 } 511 } 512 } 513 } 514 }); 515 } 516 movePendingSimRequestsToRunningSynchronized()517 private void movePendingSimRequestsToRunningSynchronized() { 518 LogUtil.d("Schedule requests pending on SIM"); 519 mCurrentSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; 520 while (mPendingSimRequestQueue.size() > 0) { 521 final MmsRequest request = mPendingSimRequestQueue.peek(); 522 if (request != null) { 523 if (!SubscriptionManager.isValidSubscriptionId(mCurrentSubId) 524 || mCurrentSubId == request.getSubId()) { 525 // First or subsequent requests with same SIM ID 526 mPendingSimRequestQueue.remove(); 527 addToRunningRequestQueueSynchronized(request); 528 } else { 529 // Stop if we see a different SIM ID 530 break; 531 } 532 } else { 533 LogUtil.e("Schedule pending: found empty request"); 534 mPendingSimRequestQueue.remove(); 535 } 536 } 537 } 538 539 @Override onBind(Intent intent)540 public IBinder onBind(Intent intent) { 541 return mStub; 542 } 543 asBinder()544 public final IBinder asBinder() { 545 return mStub; 546 } 547 548 @Override onCreate()549 public void onCreate() { 550 super.onCreate(); 551 LogUtil.d("onCreate"); 552 // Load mms_config 553 MmsConfigManager.getInstance().init(this); 554 555 NetworkSecurityPolicy.getInstance().setCleartextTrafficPermitted(true); 556 557 // Initialize running request state 558 for (int i = 0; i < mRunningRequestExecutors.length; i++) { 559 mRunningRequestExecutors[i] = Executors.newFixedThreadPool(THREAD_POOL_SIZE); 560 } 561 synchronized (this) { 562 mCurrentSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; 563 mRunningRequestCount = 0; 564 } 565 } 566 567 @Override onDestroy()568 public void onDestroy() { 569 super.onDestroy(); 570 LogUtil.d("onDestroy"); 571 for (ExecutorService executor : mRunningRequestExecutors) { 572 executor.shutdown(); 573 } 574 } 575 importSms(String address, int type, String text, long timestampMillis, boolean seen, boolean read, String creator)576 private Uri importSms(String address, int type, String text, long timestampMillis, 577 boolean seen, boolean read, String creator) { 578 Uri insertUri = null; 579 switch (type) { 580 case SMS_TYPE_INCOMING: 581 insertUri = Telephony.Sms.Inbox.CONTENT_URI; 582 583 break; 584 case SMS_TYPE_OUTGOING: 585 insertUri = Telephony.Sms.Sent.CONTENT_URI; 586 break; 587 } 588 if (insertUri == null) { 589 LogUtil.e("importTextMessage: invalid message type for importing: " + type); 590 return null; 591 } 592 final ContentValues values = new ContentValues(6); 593 values.put(Telephony.Sms.ADDRESS, address); 594 values.put(Telephony.Sms.DATE, timestampMillis); 595 values.put(Telephony.Sms.SEEN, seen ? 1 : 0); 596 values.put(Telephony.Sms.READ, read ? 1 : 0); 597 values.put(Telephony.Sms.BODY, text); 598 if (!TextUtils.isEmpty(creator)) { 599 values.put(Telephony.Mms.CREATOR, creator); 600 } 601 // Clear the calling identity and query the database using the phone user id 602 // Otherwise the AppOps check in TelephonyProvider would complain about mismatch 603 // between the calling uid and the package uid 604 final long identity = Binder.clearCallingIdentity(); 605 try { 606 return getContentResolver().insert(insertUri, values); 607 } catch (SQLiteException e) { 608 LogUtil.e("importTextMessage: failed to persist imported text message", e); 609 } finally { 610 Binder.restoreCallingIdentity(identity); 611 } 612 return null; 613 } 614 importMms(Uri contentUri, String messageId, long timestampSecs, boolean seen, boolean read, String creator)615 private Uri importMms(Uri contentUri, String messageId, long timestampSecs, 616 boolean seen, boolean read, String creator) { 617 byte[] pduData = readPduFromContentUri(contentUri, MAX_MMS_FILE_SIZE); 618 if (pduData == null || pduData.length < 1) { 619 LogUtil.e("importMessage: empty PDU"); 620 return null; 621 } 622 // Clear the calling identity and query the database using the phone user id 623 // Otherwise the AppOps check in TelephonyProvider would complain about mismatch 624 // between the calling uid and the package uid 625 final long identity = Binder.clearCallingIdentity(); 626 try { 627 final GenericPdu pdu = parsePduForAnyCarrier(pduData); 628 if (pdu == null) { 629 LogUtil.e("importMessage: can't parse input PDU"); 630 return null; 631 } 632 Uri insertUri = null; 633 if (pdu instanceof SendReq) { 634 insertUri = Telephony.Mms.Sent.CONTENT_URI; 635 } else if (pdu instanceof RetrieveConf || 636 pdu instanceof NotificationInd || 637 pdu instanceof DeliveryInd || 638 pdu instanceof ReadOrigInd) { 639 insertUri = Telephony.Mms.Inbox.CONTENT_URI; 640 } 641 if (insertUri == null) { 642 LogUtil.e("importMessage; invalid MMS type: " + pdu.getClass().getCanonicalName()); 643 return null; 644 } 645 final PduPersister persister = PduPersister.getPduPersister(this); 646 final Uri uri = persister.persist( 647 pdu, 648 insertUri, 649 true/*createThreadId*/, 650 true/*groupMmsEnabled*/, 651 null/*preOpenedFiles*/); 652 if (uri == null) { 653 LogUtil.e("importMessage: failed to persist message"); 654 return null; 655 } 656 final ContentValues values = new ContentValues(5); 657 if (!TextUtils.isEmpty(messageId)) { 658 values.put(Telephony.Mms.MESSAGE_ID, messageId); 659 } 660 if (timestampSecs != -1) { 661 values.put(Telephony.Mms.DATE, timestampSecs); 662 } 663 values.put(Telephony.Mms.READ, seen ? 1 : 0); 664 values.put(Telephony.Mms.SEEN, read ? 1 : 0); 665 if (!TextUtils.isEmpty(creator)) { 666 values.put(Telephony.Mms.CREATOR, creator); 667 } 668 if (SqliteWrapper.update(this, getContentResolver(), uri, values, 669 null/*where*/, null/*selectionArg*/) != 1) { 670 LogUtil.e("importMessage: failed to update message"); 671 } 672 return uri; 673 } catch (RuntimeException e) { 674 LogUtil.e("importMessage: failed to parse input PDU", e); 675 } catch (MmsException e) { 676 LogUtil.e("importMessage: failed to persist message", e); 677 } finally { 678 Binder.restoreCallingIdentity(identity); 679 } 680 return null; 681 } 682 isSmsMmsContentUri(Uri uri)683 private static boolean isSmsMmsContentUri(Uri uri) { 684 final String uriString = uri.toString(); 685 if (!uriString.startsWith("content://sms/") && !uriString.startsWith("content://mms/")) { 686 return false; 687 } 688 if (ContentUris.parseId(uri) == -1) { 689 return false; 690 } 691 return true; 692 } 693 updateMessageStatus(Uri messageUri, ContentValues statusValues)694 private boolean updateMessageStatus(Uri messageUri, ContentValues statusValues) { 695 if (!isSmsMmsContentUri(messageUri)) { 696 LogUtil.e("updateMessageStatus: invalid messageUri: " + messageUri.toString()); 697 return false; 698 } 699 if (statusValues == null) { 700 LogUtil.w("updateMessageStatus: empty values to update"); 701 return false; 702 } 703 final ContentValues values = new ContentValues(); 704 if (statusValues.containsKey(MESSAGE_STATUS_READ)) { 705 final Integer val = statusValues.getAsInteger(MESSAGE_STATUS_READ); 706 if (val != null) { 707 // MMS uses the same column name 708 values.put(Telephony.Sms.READ, val); 709 } 710 } else if (statusValues.containsKey(MESSAGE_STATUS_SEEN)) { 711 final Integer val = statusValues.getAsInteger(MESSAGE_STATUS_SEEN); 712 if (val != null) { 713 // MMS uses the same column name 714 values.put(Telephony.Sms.SEEN, val); 715 } 716 } 717 if (values.size() < 1) { 718 LogUtil.w("updateMessageStatus: no value to update"); 719 return false; 720 } 721 // Clear the calling identity and query the database using the phone user id 722 // Otherwise the AppOps check in TelephonyProvider would complain about mismatch 723 // between the calling uid and the package uid 724 final long identity = Binder.clearCallingIdentity(); 725 try { 726 if (getContentResolver().update( 727 messageUri, values, null/*where*/, null/*selectionArgs*/) != 1) { 728 LogUtil.e("updateMessageStatus: failed to update database"); 729 return false; 730 } 731 return true; 732 } catch (SQLiteException e) { 733 LogUtil.e("updateMessageStatus: failed to update database", e); 734 } finally { 735 Binder.restoreCallingIdentity(identity); 736 } 737 return false; 738 } 739 740 private static final String ARCHIVE_CONVERSATION_SELECTION = Telephony.Threads._ID + "=?"; 741 archiveConversation(long conversationId, boolean archived)742 private boolean archiveConversation(long conversationId, boolean archived) { 743 final ContentValues values = new ContentValues(1); 744 values.put(Telephony.Threads.ARCHIVED, archived ? 1 : 0); 745 // Clear the calling identity and query the database using the phone user id 746 // Otherwise the AppOps check in TelephonyProvider would complain about mismatch 747 // between the calling uid and the package uid 748 final long identity = Binder.clearCallingIdentity(); 749 try { 750 if (getContentResolver().update( 751 Telephony.Threads.CONTENT_URI, 752 values, 753 ARCHIVE_CONVERSATION_SELECTION, 754 new String[]{Long.toString(conversationId)}) != 1) { 755 LogUtil.e("archiveConversation: failed to update database"); 756 return false; 757 } 758 return true; 759 } catch (SQLiteException e) { 760 LogUtil.e("archiveConversation: failed to update database", e); 761 } finally { 762 Binder.restoreCallingIdentity(identity); 763 } 764 return false; 765 } 766 addSmsDraft(String address, String text, String creator)767 private Uri addSmsDraft(String address, String text, String creator) { 768 final ContentValues values = new ContentValues(5); 769 values.put(Telephony.Sms.ADDRESS, address); 770 values.put(Telephony.Sms.BODY, text); 771 values.put(Telephony.Sms.READ, 1); 772 values.put(Telephony.Sms.SEEN, 1); 773 if (!TextUtils.isEmpty(creator)) { 774 values.put(Telephony.Mms.CREATOR, creator); 775 } 776 // Clear the calling identity and query the database using the phone user id 777 // Otherwise the AppOps check in TelephonyProvider would complain about mismatch 778 // between the calling uid and the package uid 779 final long identity = Binder.clearCallingIdentity(); 780 try { 781 return getContentResolver().insert(Telephony.Sms.Draft.CONTENT_URI, values); 782 } catch (SQLiteException e) { 783 LogUtil.e("addSmsDraft: failed to store draft message", e); 784 } finally { 785 Binder.restoreCallingIdentity(identity); 786 } 787 return null; 788 } 789 addMmsDraft(Uri contentUri, String creator)790 private Uri addMmsDraft(Uri contentUri, String creator) { 791 byte[] pduData = readPduFromContentUri(contentUri, MAX_MMS_FILE_SIZE); 792 if (pduData == null || pduData.length < 1) { 793 LogUtil.e("addMmsDraft: empty PDU"); 794 return null; 795 } 796 // Clear the calling identity and query the database using the phone user id 797 // Otherwise the AppOps check in TelephonyProvider would complain about mismatch 798 // between the calling uid and the package uid 799 final long identity = Binder.clearCallingIdentity(); 800 try { 801 final GenericPdu pdu = parsePduForAnyCarrier(pduData); 802 if (pdu == null) { 803 LogUtil.e("addMmsDraft: can't parse input PDU"); 804 return null; 805 } 806 if (!(pdu instanceof SendReq)) { 807 LogUtil.e("addMmsDraft; invalid MMS type: " + pdu.getClass().getCanonicalName()); 808 return null; 809 } 810 final PduPersister persister = PduPersister.getPduPersister(this); 811 final Uri uri = persister.persist( 812 pdu, 813 Telephony.Mms.Draft.CONTENT_URI, 814 true/*createThreadId*/, 815 true/*groupMmsEnabled*/, 816 null/*preOpenedFiles*/); 817 if (uri == null) { 818 LogUtil.e("addMmsDraft: failed to persist message"); 819 return null; 820 } 821 final ContentValues values = new ContentValues(3); 822 values.put(Telephony.Mms.READ, 1); 823 values.put(Telephony.Mms.SEEN, 1); 824 if (!TextUtils.isEmpty(creator)) { 825 values.put(Telephony.Mms.CREATOR, creator); 826 } 827 if (SqliteWrapper.update(this, getContentResolver(), uri, values, 828 null/*where*/, null/*selectionArg*/) != 1) { 829 LogUtil.e("addMmsDraft: failed to update message"); 830 } 831 return uri; 832 } catch (RuntimeException e) { 833 LogUtil.e("addMmsDraft: failed to parse input PDU", e); 834 } catch (MmsException e) { 835 LogUtil.e("addMmsDraft: failed to persist message", e); 836 } finally { 837 Binder.restoreCallingIdentity(identity); 838 } 839 return null; 840 } 841 842 /** 843 * Try parsing a PDU without knowing the carrier. This is useful for importing 844 * MMS or storing draft when carrier info is not available 845 * 846 * @param data The PDU data 847 * @return Parsed PDU, null if failed to parse 848 */ parsePduForAnyCarrier(final byte[] data)849 private static GenericPdu parsePduForAnyCarrier(final byte[] data) { 850 GenericPdu pdu = null; 851 try { 852 pdu = (new PduParser(data, true/*parseContentDisposition*/)).parse(); 853 } catch (RuntimeException e) { 854 LogUtil.w("parsePduForAnyCarrier: Failed to parse PDU with content disposition", e); 855 } 856 if (pdu == null) { 857 try { 858 pdu = (new PduParser(data, false/*parseContentDisposition*/)).parse(); 859 } catch (RuntimeException e) { 860 LogUtil.w("parsePduForAnyCarrier: Failed to parse PDU without content disposition", 861 e); 862 } 863 } 864 return pdu; 865 } 866 867 @Override getAutoPersistingPref()868 public boolean getAutoPersistingPref() { 869 final SharedPreferences preferences = getSharedPreferences( 870 SHARED_PREFERENCES_NAME, MODE_PRIVATE); 871 return preferences.getBoolean(PREF_AUTO_PERSISTING, false); 872 } 873 874 /** 875 * Read pdu from content provider uri. 876 * 877 * @param contentUri content provider uri from which to read. 878 * @param maxSize maximum number of bytes to read. 879 * @return pdu bytes if succeeded else null. 880 */ readPduFromContentUri(final Uri contentUri, final int maxSize)881 public byte[] readPduFromContentUri(final Uri contentUri, final int maxSize) { 882 // Request one extra byte to make sure file not bigger than maxSize 883 byte[] pduData = new byte[maxSize + 1]; 884 int bytesRead = readPduBytesFromContentUri(contentUri, pduData); 885 if (bytesRead <= 0) { 886 return null; 887 } 888 if (bytesRead > maxSize) { 889 LogUtil.e("PDU read is too large"); 890 return null; 891 } 892 return Arrays.copyOf(pduData, bytesRead); 893 } 894 895 /** 896 * Read up to length of the pduData array from content provider uri. 897 * 898 * @param contentUri content provider uri from which to read. 899 * @param pduData the buffer into which the data is read. 900 * @return the total number of bytes read into the pduData. 901 */ readPduBytesFromContentUri(final Uri contentUri, byte[] pduData)902 public int readPduBytesFromContentUri(final Uri contentUri, byte[] pduData) { 903 if (contentUri == null) { 904 LogUtil.e("Uri is null"); 905 return 0; 906 } 907 Callable<Integer> copyPduToArray = new Callable<Integer>() { 908 public Integer call() { 909 ParcelFileDescriptor.AutoCloseInputStream inStream = null; 910 try { 911 ContentResolver cr = MmsService.this.getContentResolver(); 912 ParcelFileDescriptor pduFd = cr.openFileDescriptor(contentUri, "r"); 913 inStream = new ParcelFileDescriptor.AutoCloseInputStream(pduFd); 914 int bytesRead = inStream.read(pduData, 0, pduData.length); 915 if (bytesRead <= 0) { 916 LogUtil.e("Empty PDU or at end of the file"); 917 } 918 return bytesRead; 919 } catch (IOException ex) { 920 LogUtil.e("IO exception reading PDU", ex); 921 return 0; 922 } finally { 923 if (inStream != null) { 924 try { 925 inStream.close(); 926 } catch (IOException ex) { 927 } 928 } 929 } 930 } 931 }; 932 933 final Future<Integer> pendingResult = mPduTransferExecutor.submit(copyPduToArray); 934 try { 935 return pendingResult.get(TASK_TIMEOUT_MS, TimeUnit.MILLISECONDS); 936 } catch (Exception e) { 937 // Typically a timeout occurred - cancel task 938 pendingResult.cancel(true); 939 } 940 return 0; 941 } 942 943 /** 944 * Write pdu bytes to content provider uri 945 * 946 * @param contentUri content provider uri to which bytes should be written 947 * @param pdu Bytes to write 948 * @return true if all bytes successfully written else false 949 */ writePduToContentUri(final Uri contentUri, final byte[] pdu)950 public boolean writePduToContentUri(final Uri contentUri, final byte[] pdu) { 951 if (contentUri == null || pdu == null) { 952 return false; 953 } 954 final Callable<Boolean> copyDownloadedPduToOutput = new Callable<Boolean>() { 955 public Boolean call() { 956 ParcelFileDescriptor.AutoCloseOutputStream outStream = null; 957 try { 958 ContentResolver cr = MmsService.this.getContentResolver(); 959 ParcelFileDescriptor pduFd = cr.openFileDescriptor(contentUri, "w"); 960 outStream = new ParcelFileDescriptor.AutoCloseOutputStream(pduFd); 961 outStream.write(pdu); 962 return Boolean.TRUE; 963 } catch (IOException ex) { 964 LogUtil.e("IO exception writing PDU", ex); 965 return Boolean.FALSE; 966 } finally { 967 if (outStream != null) { 968 try { 969 outStream.close(); 970 } catch (IOException ex) { 971 } 972 } 973 } 974 } 975 }; 976 977 final Future<Boolean> pendingResult = 978 mPduTransferExecutor.submit(copyDownloadedPduToOutput); 979 try { 980 return pendingResult.get(TASK_TIMEOUT_MS, TimeUnit.MILLISECONDS); 981 } catch (Exception e) { 982 // Typically a timeout occurred - cancel task 983 pendingResult.cancel(true); 984 } 985 return false; 986 } 987 } 988