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.app.Activity;
     20 import android.app.ActivityManager;
     21 import android.app.AppOpsManager;
     22 import android.app.PendingIntent;
     23 import android.content.ContentValues;
     24 import android.content.Context;
     25 import android.content.Intent;
     26 import android.content.pm.UserInfo;
     27 import android.database.sqlite.SQLiteException;
     28 import android.net.Uri;
     29 import android.os.Binder;
     30 import android.os.Bundle;
     31 import android.os.RemoteException;
     32 import android.os.UserHandle;
     33 import android.os.UserManager;
     34 import android.provider.Telephony;
     35 import android.service.carrier.CarrierMessagingService;
     36 import android.service.carrier.ICarrierMessagingService;
     37 import android.telephony.CarrierMessagingServiceManager;
     38 import android.telephony.SmsManager;
     39 import android.text.TextUtils;
     40 
     41 import com.android.mms.service.exception.MmsHttpException;
     42 import com.google.android.mms.MmsException;
     43 import com.google.android.mms.pdu.GenericPdu;
     44 import com.google.android.mms.pdu.PduHeaders;
     45 import com.google.android.mms.pdu.PduParser;
     46 import com.google.android.mms.pdu.PduPersister;
     47 import com.google.android.mms.pdu.RetrieveConf;
     48 import com.google.android.mms.util.SqliteWrapper;
     49 
     50 /**
     51  * Request to download an MMS
     52  */
     53 public class DownloadRequest extends MmsRequest {
     54     private static final String LOCATION_SELECTION =
     55             Telephony.Mms.MESSAGE_TYPE + "=? AND " + Telephony.Mms.CONTENT_LOCATION + " =?";
     56 
     57     private final String mLocationUrl;
     58     private final PendingIntent mDownloadedIntent;
     59     private final Uri mContentUri;
     60 
     61     public DownloadRequest(RequestManager manager, int subId, String locationUrl,
     62             Uri contentUri, PendingIntent downloadedIntent, String creator,
     63             Bundle configOverrides, Context context) {
     64         super(manager, subId, creator, configOverrides, context);
     65         mLocationUrl = locationUrl;
     66         mDownloadedIntent = downloadedIntent;
     67         mContentUri = contentUri;
     68     }
     69 
     70     @Override
     71     protected byte[] doHttp(Context context, MmsNetworkManager netMgr, ApnSettings apn)
     72             throws MmsHttpException {
     73         final String requestId = getRequestId();
     74         final MmsHttpClient mmsHttpClient = netMgr.getOrCreateHttpClient();
     75         if (mmsHttpClient == null) {
     76             LogUtil.e(requestId, "MMS network is not ready!");
     77             throw new MmsHttpException(0/*statusCode*/, "MMS network is not ready");
     78         }
     79         return mmsHttpClient.execute(
     80                 mLocationUrl,
     81                 null/*pud*/,
     82                 MmsHttpClient.METHOD_GET,
     83                 apn.isProxySet(),
     84                 apn.getProxyAddress(),
     85                 apn.getProxyPort(),
     86                 mMmsConfig,
     87                 mSubId,
     88                 requestId);
     89     }
     90 
     91     @Override
     92     protected PendingIntent getPendingIntent() {
     93         return mDownloadedIntent;
     94     }
     95 
     96     @Override
     97     protected int getQueueType() {
     98         return MmsService.QUEUE_INDEX_DOWNLOAD;
     99     }
    100 
    101     @Override
    102     protected Uri persistIfRequired(Context context, int result, byte[] response) {
    103         final String requestId = getRequestId();
    104         // Let any mms apps running as secondary user know that a new mms has been downloaded.
    105         notifyOfDownload(context);
    106 
    107         if (!mRequestManager.getAutoPersistingPref()) {
    108             return null;
    109         }
    110         LogUtil.d(requestId, "persistIfRequired");
    111         if (response == null || response.length < 1) {
    112             LogUtil.e(requestId, "persistIfRequired: empty response");
    113             return null;
    114         }
    115         final long identity = Binder.clearCallingIdentity();
    116         try {
    117             final boolean supportMmsContentDisposition =
    118                     mMmsConfig.getBoolean(SmsManager.MMS_CONFIG_SUPPORT_MMS_CONTENT_DISPOSITION);
    119             final GenericPdu pdu = (new PduParser(response, supportMmsContentDisposition)).parse();
    120             if (pdu == null || !(pdu instanceof RetrieveConf)) {
    121                 LogUtil.e(requestId, "persistIfRequired: invalid parsed PDU");
    122                 return null;
    123             }
    124             final RetrieveConf retrieveConf = (RetrieveConf) pdu;
    125             final int status = retrieveConf.getRetrieveStatus();
    126             if (status != PduHeaders.RETRIEVE_STATUS_OK) {
    127                 LogUtil.e(requestId, "persistIfRequired: retrieve failed " + status);
    128                 // Update the retrieve status of the NotificationInd
    129                 final ContentValues values = new ContentValues(1);
    130                 values.put(Telephony.Mms.RETRIEVE_STATUS, status);
    131                 SqliteWrapper.update(
    132                         context,
    133                         context.getContentResolver(),
    134                         Telephony.Mms.CONTENT_URI,
    135                         values,
    136                         LOCATION_SELECTION,
    137                         new String[] {
    138                                 Integer.toString(PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND),
    139                                 mLocationUrl
    140                         });
    141                 return null;
    142             }
    143             // Store the downloaded message
    144             final PduPersister persister = PduPersister.getPduPersister(context);
    145             final Uri messageUri = persister.persist(
    146                     pdu,
    147                     Telephony.Mms.Inbox.CONTENT_URI,
    148                     true/*createThreadId*/,
    149                     true/*groupMmsEnabled*/,
    150                     null/*preOpenedFiles*/);
    151             if (messageUri == null) {
    152                 LogUtil.e(requestId, "persistIfRequired: can not persist message");
    153                 return null;
    154             }
    155             // Update some of the properties of the message
    156             final ContentValues values = new ContentValues();
    157             values.put(Telephony.Mms.DATE, System.currentTimeMillis() / 1000L);
    158             values.put(Telephony.Mms.READ, 0);
    159             values.put(Telephony.Mms.SEEN, 0);
    160             if (!TextUtils.isEmpty(mCreator)) {
    161                 values.put(Telephony.Mms.CREATOR, mCreator);
    162             }
    163             values.put(Telephony.Mms.SUBSCRIPTION_ID, mSubId);
    164             if (SqliteWrapper.update(
    165                     context,
    166                     context.getContentResolver(),
    167                     messageUri,
    168                     values,
    169                     null/*where*/,
    170                     null/*selectionArg*/) != 1) {
    171                 LogUtil.e(requestId, "persistIfRequired: can not update message");
    172             }
    173             // Delete the corresponding NotificationInd
    174             SqliteWrapper.delete(context,
    175                     context.getContentResolver(),
    176                     Telephony.Mms.CONTENT_URI,
    177                     LOCATION_SELECTION,
    178                     new String[]{
    179                             Integer.toString(PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND),
    180                             mLocationUrl
    181                     });
    182 
    183             return messageUri;
    184         } catch (MmsException e) {
    185             LogUtil.e(requestId, "persistIfRequired: can not persist message", e);
    186         } catch (SQLiteException e) {
    187             LogUtil.e(requestId, "persistIfRequired: can not update message", e);
    188         } catch (RuntimeException e) {
    189             LogUtil.e(requestId, "persistIfRequired: can not parse response", e);
    190         } finally {
    191             Binder.restoreCallingIdentity(identity);
    192         }
    193         return null;
    194     }
    195 
    196     private void notifyOfDownload(Context context) {
    197         final Intent intent = new Intent(Telephony.Sms.Intents.MMS_DOWNLOADED_ACTION);
    198         intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT);
    199 
    200         // Get a list of currently started users.
    201         int[] users = null;
    202         try {
    203             users = ActivityManager.getService().getRunningUserIds();
    204         } catch (RemoteException re) {
    205         }
    206         if (users == null) {
    207             users = new int[] {UserHandle.ALL.getIdentifier()};
    208         }
    209         final UserManager userManager =
    210                 (UserManager) context.getSystemService(Context.USER_SERVICE);
    211 
    212         // Deliver the broadcast only to those running users that are permitted
    213         // by user policy.
    214         for (int i = users.length - 1; i >= 0; i--) {
    215             UserHandle targetUser = new UserHandle(users[i]);
    216             if (users[i] != UserHandle.USER_SYSTEM) {
    217                 // Is the user not allowed to use SMS?
    218                 if (userManager.hasUserRestriction(UserManager.DISALLOW_SMS, targetUser)) {
    219                     continue;
    220                 }
    221                 // Skip unknown users and managed profiles as well
    222                 UserInfo info = userManager.getUserInfo(users[i]);
    223                 if (info == null || info.isManagedProfile()) {
    224                     continue;
    225                 }
    226             }
    227             context.sendOrderedBroadcastAsUser(intent, targetUser,
    228                     android.Manifest.permission.RECEIVE_MMS,
    229                     AppOpsManager.OP_RECEIVE_MMS,
    230                     null,
    231                     null, Activity.RESULT_OK, null, null);
    232         }
    233     }
    234 
    235     /**
    236      * Transfer the received response to the caller (for download requests write to content uri)
    237      *
    238      * @param fillIn the intent that will be returned to the caller
    239      * @param response the pdu to transfer
    240      */
    241     @Override
    242     protected boolean transferResponse(Intent fillIn, final byte[] response) {
    243         return mRequestManager.writePduToContentUri(mContentUri, response);
    244     }
    245 
    246     @Override
    247     protected boolean prepareForHttpRequest() {
    248         return true;
    249     }
    250 
    251     /**
    252      * Try downloading via the carrier app.
    253      *
    254      * @param context The context
    255      * @param carrierMessagingServicePackage The carrier messaging service handling the download
    256      */
    257     public void tryDownloadingByCarrierApp(Context context, String carrierMessagingServicePackage) {
    258         final CarrierDownloadManager carrierDownloadManger = new CarrierDownloadManager();
    259         final CarrierDownloadCompleteCallback downloadCallback =
    260                 new CarrierDownloadCompleteCallback(context, carrierDownloadManger);
    261         carrierDownloadManger.downloadMms(context, carrierMessagingServicePackage,
    262                 downloadCallback);
    263     }
    264 
    265     @Override
    266     protected void revokeUriPermission(Context context) {
    267         context.revokeUriPermission(mContentUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
    268     }
    269 
    270     /**
    271      * Downloads the MMS through through the carrier app.
    272      */
    273     private final class CarrierDownloadManager extends CarrierMessagingServiceManager {
    274         // Initialized in downloadMms
    275         private volatile CarrierDownloadCompleteCallback mCarrierDownloadCallback;
    276 
    277         void downloadMms(Context context, String carrierMessagingServicePackage,
    278                 CarrierDownloadCompleteCallback carrierDownloadCallback) {
    279             mCarrierDownloadCallback = carrierDownloadCallback;
    280             if (bindToCarrierMessagingService(context, carrierMessagingServicePackage)) {
    281                 LogUtil.v("bindService() for carrier messaging service succeeded");
    282             } else {
    283                 LogUtil.e("bindService() for carrier messaging service failed");
    284                 carrierDownloadCallback.onDownloadMmsComplete(
    285                         CarrierMessagingService.DOWNLOAD_STATUS_RETRY_ON_CARRIER_NETWORK);
    286             }
    287         }
    288 
    289         @Override
    290         protected void onServiceReady(ICarrierMessagingService carrierMessagingService) {
    291             try {
    292                 carrierMessagingService.downloadMms(mContentUri, mSubId, Uri.parse(mLocationUrl),
    293                         mCarrierDownloadCallback);
    294             } catch (RemoteException e) {
    295                 LogUtil.e("Exception downloading MMS using the carrier messaging service: " + e, e);
    296                 mCarrierDownloadCallback.onDownloadMmsComplete(
    297                         CarrierMessagingService.DOWNLOAD_STATUS_RETRY_ON_CARRIER_NETWORK);
    298             }
    299         }
    300     }
    301 
    302     /**
    303      * A callback which notifies carrier messaging app send result. Once the result is ready, the
    304      * carrier messaging service connection is disposed.
    305      */
    306     private final class CarrierDownloadCompleteCallback extends
    307             MmsRequest.CarrierMmsActionCallback {
    308         private final Context mContext;
    309         private final CarrierDownloadManager mCarrierDownloadManager;
    310 
    311         public CarrierDownloadCompleteCallback(Context context,
    312                 CarrierDownloadManager carrierDownloadManager) {
    313             mContext = context;
    314             mCarrierDownloadManager = carrierDownloadManager;
    315         }
    316 
    317         @Override
    318         public void onSendMmsComplete(int result, byte[] sendConfPdu) {
    319             LogUtil.e("Unexpected onSendMmsComplete call with result: " + result);
    320         }
    321 
    322         @Override
    323         public void onDownloadMmsComplete(int result) {
    324             LogUtil.d("Carrier app result for download: " + result);
    325             mCarrierDownloadManager.disposeConnection(mContext);
    326 
    327             if (!maybeFallbackToRegularDelivery(result)) {
    328                 processResult(mContext, toSmsManagerResult(result), null/* response */,
    329                         0/* httpStatusCode */);
    330             }
    331         }
    332     }
    333 }
    334