/** * Copyright (C) 2011 Whisper Systems * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.thoughtcrime.securesms.service; import android.content.Context; import android.content.Intent; import android.telephony.TelephonyManager; import android.util.Log; import android.util.Pair; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.crypto.DecryptingQueue; import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.MmsDatabase; import org.thoughtcrime.securesms.database.model.NotificationMmsMessageRecord; import org.thoughtcrime.securesms.mms.ApnUnavailableException; import org.thoughtcrime.securesms.mms.MmsDownloadHelper; import org.thoughtcrime.securesms.mms.MmsSendHelper; import org.thoughtcrime.securesms.notifications.MessageNotifier; import org.thoughtcrime.securesms.protocol.WirePrefix; import java.io.IOException; import java.util.LinkedList; import java.util.List; import ws.com.google.android.mms.InvalidHeaderValueException; import ws.com.google.android.mms.MmsException; import ws.com.google.android.mms.pdu.NotifyRespInd; import ws.com.google.android.mms.pdu.PduComposer; import ws.com.google.android.mms.pdu.PduHeaders; import ws.com.google.android.mms.pdu.RetrieveConf; public class MmsDownloader extends MmscProcessor { private final LinkedList pendingMessages = new LinkedList(); private final SendReceiveService.ToastHandler toastHandler; public MmsDownloader(Context context, SendReceiveService.ToastHandler toastHandler) { super(context); this.toastHandler = toastHandler; } public void process(MasterSecret masterSecret, Intent intent) { if (intent.getAction().equals(SendReceiveService.DOWNLOAD_MMS_ACTION)) { boolean isCdma = ((TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE)).getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA; DownloadItem item = new DownloadItem(masterSecret, !isCdma, false, intent.getLongExtra("message_id", -1), intent.getLongExtra("thread_id", -1), intent.getBooleanExtra("automatic", false), intent.getStringExtra("content_location"), intent.getByteArrayExtra("transaction_id")); handleDownloadMmsAction(item); } else if (intent.getAction().equals(SendReceiveService.DOWNLOAD_MMS_CONNECTIVITY_ACTION)) { handleConnectivityChange(); } else if (intent.getAction().equals(SendReceiveService.DOWNLOAD_MMS_PENDING_APN_ACTION)) { handleMmsPendingApnDownloads(masterSecret); } } private void handleDownloadMmsAction(DownloadItem item) { if (!isConnectivityPossible()) { Log.w("MmsDownloader", "No MMS connectivity available!"); handleDownloadError(item, MmsDatabase.Status.DOWNLOAD_NO_CONNECTIVITY, context.getString(R.string.MmsDownloader_no_connectivity_available_for_mms_download_try_again_later)); return; } DatabaseFactory.getMmsDatabase(context).markDownloadState(item.getMessageId(), MmsDatabase.Status.DOWNLOAD_CONNECTING); if (item.useMmsRadioMode()) downloadMmsWithRadioChange(item); else downloadMms(item); } private void handleMmsPendingApnDownloads(MasterSecret masterSecret) { if (!MmsDownloadHelper.isMmsConnectionParametersAvailable(context, null, false)) return; MmsDatabase mmsDatabase = DatabaseFactory.getMmsDatabase(context); MmsDatabase.Reader stalledMmsReader = mmsDatabase.getNotificationsWithDownloadState(masterSecret, MmsDatabase.Status.DOWNLOAD_APN_UNAVAILABLE); while (stalledMmsReader.getNext() != null) { NotificationMmsMessageRecord stalledMmsRecord = (NotificationMmsMessageRecord) stalledMmsReader.getCurrent(); Intent intent = new Intent(SendReceiveService.DOWNLOAD_MMS_ACTION, null, context, SendReceiveService.class); intent.putExtra("content_location", new String(stalledMmsRecord.getContentLocation())); intent.putExtra("message_id", stalledMmsRecord.getId()); intent.putExtra("transaction_id", stalledMmsRecord.getTransactionId()); intent.putExtra("thread_id", stalledMmsRecord.getThreadId()); intent.putExtra("automatic", true); context.startService(intent); } stalledMmsReader.close(); } private void downloadMmsWithRadioChange(DownloadItem item) { Log.w("MmsDownloader", "Handling MMS download with radio change..."); pendingMessages.add(item); issueConnectivityRequest(); } private void downloadMms(DownloadItem item) { Log.w("MmsDownloadService", "Handling actual MMS download..."); MmsDatabase mmsDatabase = DatabaseFactory.getMmsDatabase(context); try { RetrieveConf retrieved = MmsDownloadHelper.retrieveMms(context, item.getContentLocation(), getApnInformation(), item.useMmsRadioMode(), item.proxyRequestIfPossible()); for (int i=0;i messageAndThreadId; if (retrieved.getSubject() != null && WirePrefix.isEncryptedMmsSubject(retrieved.getSubject().getString())) { messageAndThreadId = mmsDatabase.insertSecureMessageInbox(item.getMasterSecret(), retrieved, item.getContentLocation(), item.getThreadId()); if (item.getMasterSecret() != null) DecryptingQueue.scheduleDecryption(context, item.getMasterSecret(), messageAndThreadId.first, messageAndThreadId.second, retrieved); } else { messageAndThreadId = mmsDatabase.insertMessageInbox(item.getMasterSecret(), retrieved, item.getContentLocation(), item.getThreadId()); } mmsDatabase.delete(item.getMessageId()); MessageNotifier.updateNotification(context, item.getMasterSecret(), messageAndThreadId.second); } private void sendRetrievedAcknowledgement(DownloadItem item) { try { NotifyRespInd notifyResponse = new NotifyRespInd(PduHeaders.CURRENT_MMS_VERSION, item.getTransactionId(), PduHeaders.STATUS_RETRIEVED); MmsSendHelper.sendNotificationReceived(context, new PduComposer(context, notifyResponse).make(), getApnInformation(), item.useMmsRadioMode(), item.proxyRequestIfPossible()); } catch (InvalidHeaderValueException e) { Log.w("MmsDownloader", e); } catch (IOException e) { Log.w("MmsDownloader", e); } } protected void handleConnectivityChange() { LinkedList downloadItems = (LinkedList)pendingMessages.clone(); if (isConnected()) { pendingMessages.clear(); for (DownloadItem item : downloadItems) { downloadMms(item); } if (pendingMessages.isEmpty()) finishConnectivity(); } else if (!isConnected() && (!isConnectivityPossible() || isConnectivityFailure())) { pendingMessages.clear(); handleDownloadError(downloadItems, MmsDatabase.Status.DOWNLOAD_NO_CONNECTIVITY, context.getString(R.string.MmsDownloader_no_connectivity_available_for_mms_download_try_again_later)); finishConnectivity(); } } private void handleDownloadError(List items, int downloadStatus, String error) { for (DownloadItem item : items) { handleDownloadError(item, downloadStatus, error); } } private void handleDownloadError(DownloadItem item, int downloadStatus, String error) { MmsDatabase db = DatabaseFactory.getMmsDatabase(context); db.markDownloadState(item.getMessageId(), downloadStatus); if (item.isAutomatic()) { db.markIncomingNotificationReceived(item.getThreadId()); MessageNotifier.updateNotification(context, item.getMasterSecret(), item.getThreadId()); } toastHandler.makeToast(error); } private void scheduleDownloadWithRadioMode(DownloadItem item) { item.mmsRadioMode = true; handleDownloadMmsAction(item); } private void scheduleDownloadWithRadioModeAndProxy(DownloadItem item) { item.mmsRadioMode = true; item.proxyIfPossible = true; handleDownloadMmsAction(item); } private static class DownloadItem { private final MasterSecret masterSecret; private boolean mmsRadioMode; private boolean proxyIfPossible; private long threadId; private long messageId; private byte[] transactionId; private String contentLocation; private boolean automatic; public DownloadItem(MasterSecret masterSecret, boolean mmsRadioMode, boolean proxyIfPossible, long messageId, long threadId, boolean automatic, String contentLocation, byte[] transactionId) { this.masterSecret = masterSecret; this.mmsRadioMode = mmsRadioMode; this.proxyIfPossible = proxyIfPossible; this.threadId = threadId; this.messageId = messageId; this.contentLocation = contentLocation; this.transactionId = transactionId; this.automatic = automatic; } public long getThreadId() { return threadId; } public long getMessageId() { return messageId; } public String getContentLocation() { return contentLocation; } public byte[] getTransactionId() { return transactionId; } public MasterSecret getMasterSecret() { return masterSecret; } public boolean proxyRequestIfPossible() { return proxyIfPossible; } public boolean useMmsRadioMode() { return mmsRadioMode; } public boolean isAutomatic() { return automatic; } } @Override protected String getConnectivityAction() { return SendReceiveService.DOWNLOAD_MMS_CONNECTIVITY_ACTION; } }