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