/* * Copyright (C) 2015 Jacob Klinker * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.mms.service_alt; import android.app.PendingIntent; import android.content.ContentResolver; import android.content.ContentUris; import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.database.sqlite.SQLiteException; import android.net.Uri; import android.os.Binder; import android.os.Build; import android.os.Bundle; import android.provider.Telephony; import android.text.TextUtils; import android.util.Log; import com.android.mms.service_alt.exception.MmsHttpException; import com.google.android.mms.MmsException; import com.google.android.mms.pdu_alt.GenericPdu; import com.google.android.mms.pdu_alt.PduHeaders; import com.google.android.mms.pdu_alt.PduParser; import com.google.android.mms.pdu_alt.PduPersister; import com.google.android.mms.pdu_alt.RetrieveConf; import com.google.android.mms.util_alt.SqliteWrapper; import com.klinker.android.send_message.BroadcastUtils; import com.klinker.android.send_message.Transaction; /** * Request to download an MMS */ public class DownloadRequest extends MmsRequest { private static final String TAG = "DownloadRequest"; private static final String LOCATION_SELECTION = Telephony.Mms.MESSAGE_TYPE + "=? AND " + Telephony.Mms.CONTENT_LOCATION + " =?"; static final String[] PROJECTION = new String[] { Telephony.Mms.CONTENT_LOCATION }; // The indexes of the columns which must be consistent with above PROJECTION. static final int COLUMN_CONTENT_LOCATION = 0; private final String mLocationUrl; private final PendingIntent mDownloadedIntent; private final Uri mContentUri; public DownloadRequest(RequestManager manager, int subId, String locationUrl, Uri contentUri, PendingIntent downloadedIntent, String creator, Bundle configOverrides, Context context) throws MmsException { super(manager, subId, creator, configOverrides); if (locationUrl == null) { mLocationUrl = getContentLocation(context, contentUri); } else { mLocationUrl = locationUrl; } mDownloadedIntent = downloadedIntent; mContentUri = contentUri; } @Override protected byte[] doHttp(Context context, MmsNetworkManager netMgr, ApnSettings apn) throws MmsHttpException { final MmsHttpClient mmsHttpClient = netMgr.getOrCreateHttpClient(); if (mmsHttpClient == null) { Log.e(TAG, "MMS network is not ready!"); throw new MmsHttpException(0/*statusCode*/, "MMS network is not ready"); } return mmsHttpClient.execute( mLocationUrl, null/*pud*/, MmsHttpClient.METHOD_GET, apn.isProxySet(), apn.getProxyAddress(), apn.getProxyPort(), mMmsConfig); } @Override protected PendingIntent getPendingIntent() { return mDownloadedIntent; } @Override protected int getQueueType() { return 1; } @Override protected Uri persistIfRequired(Context context, int result, byte[] response) { if (!mRequestManager.getAutoPersistingPref()) { notifyOfDownload(context); return null; } return persist(context, response, mMmsConfig, mLocationUrl, mSubId, mCreator); } public static Uri persist(Context context, byte[] response, MmsConfig.Overridden mmsConfig, String locationUrl, int subId, String creator) { // Let any mms apps running as secondary user know that a new mms has been downloaded. notifyOfDownload(context); Log.d(TAG, "DownloadRequest.persistIfRequired"); if (response == null || response.length < 1) { Log.e(TAG, "DownloadRequest.persistIfRequired: empty response"); // Update the retrieve status of the NotificationInd final ContentValues values = new ContentValues(1); values.put(Telephony.Mms.RETRIEVE_STATUS, PduHeaders.RETRIEVE_STATUS_ERROR_END); SqliteWrapper.update( context, context.getContentResolver(), Telephony.Mms.CONTENT_URI, values, LOCATION_SELECTION, new String[]{ Integer.toString(PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND), locationUrl }); return null; } final long identity = Binder.clearCallingIdentity(); try { final GenericPdu pdu = (new PduParser(response, mmsConfig.getSupportMmsContentDisposition())).parse(); if (pdu == null || !(pdu instanceof RetrieveConf)) { Log.e(TAG, "DownloadRequest.persistIfRequired: invalid parsed PDU"); // Update the error type of the NotificationInd setErrorType(context, locationUrl, Telephony.MmsSms.ERR_TYPE_MMS_PROTO_PERMANENT); return null; } final RetrieveConf retrieveConf = (RetrieveConf) pdu; final int status = retrieveConf.getRetrieveStatus(); // if (status != PduHeaders.RETRIEVE_STATUS_OK) { // Log.e(TAG, "DownloadRequest.persistIfRequired: retrieve failed " // + status); // // Update the retrieve status of the NotificationInd // final ContentValues values = new ContentValues(1); // values.put(Telephony.Mms.RETRIEVE_STATUS, status); // SqliteWrapper.update( // context, // context.getContentResolver(), // Telephony.Mms.CONTENT_URI, // values, // LOCATION_SELECTION, // new String[]{ // Integer.toString(PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND), // mLocationUrl // }); // return null; // } // Store the downloaded message final PduPersister persister = PduPersister.getPduPersister(context); final Uri messageUri = persister.persist( pdu, Telephony.Mms.Inbox.CONTENT_URI, true/*createThreadId*/, true/*groupMmsEnabled*/, null/*preOpenedFiles*/); if (messageUri == null) { Log.e(TAG, "DownloadRequest.persistIfRequired: can not persist message"); return null; } // Update some of the properties of the message final ContentValues values = new ContentValues(); values.put(Telephony.Mms.DATE, System.currentTimeMillis() / 1000L); values.put(Telephony.Mms.READ, 0); values.put(Telephony.Mms.SEEN, 0); if (!TextUtils.isEmpty(creator)) { values.put(Telephony.Mms.CREATOR, creator); } if (SubscriptionIdChecker.getInstance(context).canUseSubscriptionId()) { values.put(Telephony.Mms.SUBSCRIPTION_ID, subId); } if (SqliteWrapper.update( context, context.getContentResolver(), messageUri, values, null/*where*/, null/*selectionArg*/) != 1) { Log.e(TAG, "DownloadRequest.persistIfRequired: can not update message"); } // Delete the corresponding NotificationInd SqliteWrapper.delete(context, context.getContentResolver(), Telephony.Mms.CONTENT_URI, LOCATION_SELECTION, new String[]{ Integer.toString(PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND), locationUrl }); return messageUri; } catch (MmsException e) { Log.e(TAG, "DownloadRequest.persistIfRequired: can not persist message", e); } catch (SQLiteException e) { Log.e(TAG, "DownloadRequest.persistIfRequired: can not update message", e); } catch (RuntimeException e) { Log.e(TAG, "DownloadRequest.persistIfRequired: can not parse response", e); } finally { Binder.restoreCallingIdentity(identity); } return null; } private static void notifyOfDownload(Context context) { BroadcastUtils.sendExplicitBroadcast(context, new Intent(), Transaction.NOTIFY_OF_MMS); // TODO, not sure what this is doing... sending a broadcast that // the download has finished from a specific user account I believe. // final Intent intent = new Intent("android.provider.Telephony.MMS_DOWNLOADED"); // intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT); // // // Get a list of currently started users. // int[] users = null; // try { // users = ActivityManagerNative.getDefault().getRunningUserIds(); // } catch (RemoteException re) { // } // if (users == null) { // users = new int[] {UserHandle.ALL.getIdentifier()}; // } // final UserManager userManager = // (UserManager) context.getSystemService(Context.USER_SERVICE); // // // Deliver the broadcast only to those running users that are permitted // // by user policy. // for (int i = users.length - 1; i >= 0; i--) { // UserHandle targetUser = new UserHandle(users[i]); // if (users[i] != UserHandle.USER_OWNER) { // // Is the user not allowed to use SMS? // if (userManager.hasUserRestriction(UserManager.DISALLOW_SMS, targetUser)) { // continue; // } // // Skip unknown users and managed profiles as well // UserInfo info = userManager.getUserInfo(users[i]); // if (info == null || info.isManagedProfile()) { // continue; // } // } // context.sendOrderedBroadcastAsUser(intent, targetUser, // android.Manifest.permission.RECEIVE_MMS, // 18, // null, // null, Activity.RESULT_OK, null, null); // } } /** * Transfer the received response to the caller (for download requests write to content uri) * * @param fillIn the intent that will be returned to the caller * @param response the pdu to transfer */ @Override protected boolean transferResponse(Intent fillIn, final byte[] response) { return mRequestManager.writePduToContentUri(mContentUri, response); } @Override protected boolean prepareForHttpRequest() { return true; } /** * Try downloading via the carrier app. * * @param context The context * @param carrierMessagingServicePackage The carrier messaging service handling the download */ public void tryDownloadingByCarrierApp(Context context, String carrierMessagingServicePackage) { // final CarrierDownloadManager carrierDownloadManger = new CarrierDownloadManager(); // final CarrierDownloadCompleteCallback downloadCallback = // new CarrierDownloadCompleteCallback(context, carrierDownloadManger); // carrierDownloadManger.downloadMms(context, carrierMessagingServicePackage, // downloadCallback); } @Override protected void revokeUriPermission(Context context) { context.revokeUriPermission(mContentUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION); } private String getContentLocation(Context context, Uri uri) throws MmsException { Cursor cursor = android.database.sqlite.SqliteWrapper.query(context, context.getContentResolver(), uri, PROJECTION, null, null, null); if (cursor != null) { try { if ((cursor.getCount() == 1) && cursor.moveToFirst()) { String location = cursor.getString(COLUMN_CONTENT_LOCATION); cursor.close(); return location; } } finally { cursor.close(); } } throw new MmsException("Cannot get X-Mms-Content-Location from: " + uri); } private static Long getId(Context context, String location) { String selection = Telephony.Mms.CONTENT_LOCATION + " = ?"; String[] selectionArgs = new String[] { location }; Cursor c = android.database.sqlite.SqliteWrapper.query( context, context.getContentResolver(), Telephony.Mms.CONTENT_URI, new String[] { Telephony.Mms._ID }, selection, selectionArgs, null); if (c != null) { try { if (c.moveToFirst()) { return c.getLong(c.getColumnIndex(Telephony.Mms._ID)); } } finally { c.close(); } } return null; } private static void setErrorType(Context context, String locationUrl, int errorType) { Long msgId = getId(context, locationUrl); if (msgId == null) { return; } Uri.Builder uriBuilder = Telephony.MmsSms.PendingMessages.CONTENT_URI.buildUpon(); uriBuilder.appendQueryParameter("protocol", "mms"); uriBuilder.appendQueryParameter("message", String.valueOf(msgId)); Cursor cursor = android.database.sqlite.SqliteWrapper.query(context, context.getContentResolver(), uriBuilder.build(), null, null, null, null); if (cursor == null) { return; } try { if ((cursor.getCount() == 1) && cursor.moveToFirst()) { ContentValues values = new ContentValues(); values.put(Telephony.MmsSms.PendingMessages.ERROR_TYPE, errorType); int columnIndex = cursor.getColumnIndexOrThrow( Telephony.MmsSms.PendingMessages._ID); long id = cursor.getLong(columnIndex); android.database.sqlite.SqliteWrapper.update(context, context.getContentResolver(), Telephony.MmsSms.PendingMessages.CONTENT_URI, values, Telephony.MmsSms.PendingMessages._ID + "=" + id, null); } } finally { cursor.close(); } } }