1 /* 2 * Copyright (C) 2007-2008 Esmertec AG. 3 * Copyright (C) 2007-2008 The Android Open Source Project 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package com.android.mms.transaction; 19 20 import static com.android.mms.transaction.TransactionState.FAILED; 21 import static com.android.mms.transaction.TransactionState.INITIALIZED; 22 import static com.android.mms.transaction.TransactionState.SUCCESS; 23 import static com.google.android.mms.pdu.PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF; 24 import static com.google.android.mms.pdu.PduHeaders.STATUS_DEFERRED; 25 import static com.google.android.mms.pdu.PduHeaders.STATUS_RETRIEVED; 26 import static com.google.android.mms.pdu.PduHeaders.STATUS_UNRECOGNIZED; 27 import android.content.ContentValues; 28 import android.content.Context; 29 import android.database.sqlite.SqliteWrapper; 30 import android.net.Uri; 31 import android.provider.Telephony.Mms; 32 import android.provider.Telephony.Mms.Inbox; 33 import android.provider.Telephony.Threads; 34 import android.telephony.TelephonyManager; 35 import android.util.Log; 36 37 import com.android.mms.LogTag; 38 import com.android.mms.MmsApp; 39 import com.android.mms.MmsConfig; 40 import com.android.mms.ui.MessagingPreferenceActivity; 41 import com.android.mms.util.DownloadManager; 42 import com.android.mms.util.Recycler; 43 import com.android.mms.widget.MmsWidgetProvider; 44 import com.google.android.mms.MmsException; 45 import com.google.android.mms.pdu.GenericPdu; 46 import com.google.android.mms.pdu.NotificationInd; 47 import com.google.android.mms.pdu.NotifyRespInd; 48 import com.google.android.mms.pdu.PduComposer; 49 import com.google.android.mms.pdu.PduHeaders; 50 import com.google.android.mms.pdu.PduParser; 51 import com.google.android.mms.pdu.PduPersister; 52 53 import java.io.IOException; 54 55 /** 56 * The NotificationTransaction is responsible for handling multimedia 57 * message notifications (M-Notification.ind). It: 58 * 59 * <ul> 60 * <li>Composes the notification response (M-NotifyResp.ind). 61 * <li>Sends the notification response to the MMSC server. 62 * <li>Stores the notification indication. 63 * <li>Notifies the TransactionService about succesful completion. 64 * </ul> 65 * 66 * NOTE: This MMS client handles all notifications with a <b>deferred 67 * retrieval</b> response. The transaction service, upon succesful 68 * completion of this transaction, will trigger a retrieve transaction 69 * in case the client is in immediate retrieve mode. 70 */ 71 public class NotificationTransaction extends Transaction implements Runnable { 72 private static final String TAG = LogTag.TAG; 73 private static final boolean DEBUG = false; 74 private static final boolean LOCAL_LOGV = false; 75 76 private Uri mUri; 77 private NotificationInd mNotificationInd; 78 private String mContentLocation; 79 80 public NotificationTransaction( 81 Context context, int serviceId, 82 TransactionSettings connectionSettings, String uriString) { 83 super(context, serviceId, connectionSettings); 84 85 mUri = Uri.parse(uriString); 86 87 try { 88 mNotificationInd = (NotificationInd) 89 PduPersister.getPduPersister(context).load(mUri); 90 } catch (MmsException e) { 91 Log.e(TAG, "Failed to load NotificationInd from: " + uriString, e); 92 throw new IllegalArgumentException(); 93 } 94 95 mContentLocation = new String(mNotificationInd.getContentLocation()); 96 mId = mContentLocation; 97 98 // Attach the transaction to the instance of RetryScheduler. 99 attach(RetryScheduler.getInstance(context)); 100 } 101 102 /** 103 * This constructor is only used for test purposes. 104 */ 105 public NotificationTransaction( 106 Context context, int serviceId, 107 TransactionSettings connectionSettings, NotificationInd ind) { 108 super(context, serviceId, connectionSettings); 109 110 try { 111 // Save the pdu. If we can start downloading the real pdu immediately, don't allow 112 // persist() to create a thread for the notificationInd because it causes UI jank. 113 mUri = PduPersister.getPduPersister(context).persist( 114 ind, Inbox.CONTENT_URI, !allowAutoDownload(), 115 MessagingPreferenceActivity.getIsGroupMmsEnabled(context), null); 116 } catch (MmsException e) { 117 Log.e(TAG, "Failed to save NotificationInd in constructor.", e); 118 throw new IllegalArgumentException(); 119 } 120 121 mNotificationInd = ind; 122 mId = new String(mNotificationInd.getContentLocation()); 123 } 124 125 /* 126 * (non-Javadoc) 127 * @see com.google.android.mms.pdu.Transaction#process() 128 */ 129 @Override 130 public void process() { 131 new Thread(this, "NotificationTransaction").start(); 132 } 133 134 public static boolean allowAutoDownload() { 135 DownloadManager downloadManager = DownloadManager.getInstance(); 136 boolean autoDownload = downloadManager.isAuto(); 137 boolean dataSuspended = (MmsApp.getApplication().getTelephonyManager().getDataState() == 138 TelephonyManager.DATA_SUSPENDED); 139 return autoDownload && !dataSuspended; 140 } 141 142 public void run() { 143 DownloadManager downloadManager = DownloadManager.getInstance(); 144 boolean autoDownload = allowAutoDownload(); 145 try { 146 if (LOCAL_LOGV) { 147 Log.v(TAG, "Notification transaction launched: " + this); 148 } 149 150 // By default, we set status to STATUS_DEFERRED because we 151 // should response MMSC with STATUS_DEFERRED when we cannot 152 // download a MM immediately. 153 int status = STATUS_DEFERRED; 154 // Don't try to download when data is suspended, as it will fail, so defer download 155 if (!autoDownload) { 156 downloadManager.markState(mUri, DownloadManager.STATE_UNSTARTED); 157 sendNotifyRespInd(status); 158 return; 159 } 160 161 downloadManager.markState(mUri, DownloadManager.STATE_DOWNLOADING); 162 163 if (LOCAL_LOGV) { 164 Log.v(TAG, "Content-Location: " + mContentLocation); 165 } 166 167 byte[] retrieveConfData = null; 168 // We should catch exceptions here to response MMSC 169 // with STATUS_DEFERRED. 170 try { 171 retrieveConfData = getPdu(mContentLocation); 172 } catch (IOException e) { 173 mTransactionState.setState(FAILED); 174 } 175 176 if (retrieveConfData != null) { 177 GenericPdu pdu = new PduParser( 178 retrieveConfData, PduParserUtil.shouldParseContentDisposition()).parse(); 179 if ((pdu == null) || (pdu.getMessageType() != MESSAGE_TYPE_RETRIEVE_CONF)) { 180 Log.e(TAG, "Invalid M-RETRIEVE.CONF PDU. " + 181 (pdu != null ? "message type: " + pdu.getMessageType() : "null pdu")); 182 mTransactionState.setState(FAILED); 183 status = STATUS_UNRECOGNIZED; 184 } else { 185 // Save the received PDU (must be a M-RETRIEVE.CONF). 186 PduPersister p = PduPersister.getPduPersister(mContext); 187 Uri uri = p.persist(pdu, Inbox.CONTENT_URI, true, 188 MessagingPreferenceActivity.getIsGroupMmsEnabled(mContext), null); 189 190 // Use local time instead of PDU time 191 ContentValues values = new ContentValues(1); 192 values.put(Mms.DATE, System.currentTimeMillis() / 1000L); 193 SqliteWrapper.update(mContext, mContext.getContentResolver(), 194 uri, values, null, null); 195 196 // We have successfully downloaded the new MM. Delete the 197 // M-NotifyResp.ind from Inbox. 198 SqliteWrapper.delete(mContext, mContext.getContentResolver(), 199 mUri, null, null); 200 Log.v(TAG, "NotificationTransaction received new mms message: " + uri); 201 // Delete obsolete threads 202 SqliteWrapper.delete(mContext, mContext.getContentResolver(), 203 Threads.OBSOLETE_THREADS_URI, null, null); 204 205 // Notify observers with newly received MM. 206 mUri = uri; 207 status = STATUS_RETRIEVED; 208 } 209 } 210 211 if (LOCAL_LOGV) { 212 Log.v(TAG, "status=0x" + Integer.toHexString(status)); 213 } 214 215 // Check the status and update the result state of this Transaction. 216 switch (status) { 217 case STATUS_RETRIEVED: 218 mTransactionState.setState(SUCCESS); 219 break; 220 case STATUS_DEFERRED: 221 // STATUS_DEFERRED, may be a failed immediate retrieval. 222 if (mTransactionState.getState() == INITIALIZED) { 223 mTransactionState.setState(SUCCESS); 224 } 225 break; 226 } 227 228 sendNotifyRespInd(status); 229 230 // Make sure this thread isn't over the limits in message count. 231 Recycler.getMmsRecycler().deleteOldMessagesInSameThreadAsMessage(mContext, mUri); 232 MmsWidgetProvider.notifyDatasetChanged(mContext); 233 } catch (Throwable t) { 234 Log.e(TAG, Log.getStackTraceString(t)); 235 } finally { 236 mTransactionState.setContentUri(mUri); 237 if (!autoDownload) { 238 // Always mark the transaction successful for deferred 239 // download since any error here doesn't make sense. 240 mTransactionState.setState(SUCCESS); 241 } 242 if (mTransactionState.getState() != SUCCESS) { 243 mTransactionState.setState(FAILED); 244 Log.e(TAG, "NotificationTransaction failed."); 245 } 246 notifyObservers(); 247 } 248 } 249 250 private void sendNotifyRespInd(int status) throws MmsException, IOException { 251 // Create the M-NotifyResp.ind 252 NotifyRespInd notifyRespInd = new NotifyRespInd( 253 PduHeaders.CURRENT_MMS_VERSION, 254 mNotificationInd.getTransactionId(), 255 status); 256 257 // Pack M-NotifyResp.ind and send it 258 if(MmsConfig.getNotifyWapMMSC()) { 259 sendPdu(new PduComposer(mContext, notifyRespInd).make(), mContentLocation); 260 } else { 261 sendPdu(new PduComposer(mContext, notifyRespInd).make()); 262 } 263 } 264 265 @Override 266 public int getType() { 267 return NOTIFICATION_TRANSACTION; 268 } 269 } 270