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