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
17package com.android.mms.service;
18
19import android.app.Activity;
20import android.app.ActivityManager;
21import android.app.AppOpsManager;
22import android.app.PendingIntent;
23import android.content.ContentValues;
24import android.content.Context;
25import android.content.Intent;
26import android.content.pm.UserInfo;
27import android.database.sqlite.SQLiteException;
28import android.net.Uri;
29import android.os.Binder;
30import android.os.Bundle;
31import android.os.RemoteException;
32import android.os.UserHandle;
33import android.os.UserManager;
34import android.provider.Telephony;
35import android.service.carrier.CarrierMessagingService;
36import android.service.carrier.ICarrierMessagingService;
37import android.telephony.CarrierMessagingServiceManager;
38import android.telephony.SmsManager;
39import android.text.TextUtils;
40
41import com.android.mms.service.exception.MmsHttpException;
42import com.google.android.mms.MmsException;
43import com.google.android.mms.pdu.GenericPdu;
44import com.google.android.mms.pdu.PduHeaders;
45import com.google.android.mms.pdu.PduParser;
46import com.google.android.mms.pdu.PduPersister;
47import com.google.android.mms.pdu.RetrieveConf;
48import com.google.android.mms.util.SqliteWrapper;
49
50/**
51 * Request to download an MMS
52 */
53public 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