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