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