• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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