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