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