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