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