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 28 import java.io.IOException; 29 30 import android.content.ContentValues; 31 import android.content.Context; 32 import android.database.sqlite.SqliteWrapper; 33 import android.net.Uri; 34 import android.provider.Telephony.Mms; 35 import android.provider.Telephony.Threads; 36 import android.provider.Telephony.Mms.Inbox; 37 import android.telephony.TelephonyManager; 38 import android.util.Log; 39 40 import com.android.mms.MmsApp; 41 import com.android.mms.MmsConfig; 42 import com.android.mms.ui.MessagingPreferenceActivity; 43 import com.android.mms.util.DownloadManager; 44 import com.android.mms.util.Recycler; 45 import com.android.mms.widget.MmsWidgetProvider; 46 import com.google.android.mms.MmsException; 47 import com.google.android.mms.pdu.GenericPdu; 48 import com.google.android.mms.pdu.NotificationInd; 49 import com.google.android.mms.pdu.NotifyRespInd; 50 import com.google.android.mms.pdu.PduComposer; 51 import com.google.android.mms.pdu.PduHeaders; 52 import com.google.android.mms.pdu.PduParser; 53 import com.google.android.mms.pdu.PduPersister; 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 = "NotificationTransaction"; 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 mId = new String(mNotificationInd.getTransactionId()); 96 mContentLocation = new String(mNotificationInd.getContentLocation()); 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(ind.getTransactionId()); 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(retrieveConfData).parse(); 178 if ((pdu == null) || (pdu.getMessageType() != MESSAGE_TYPE_RETRIEVE_CONF)) { 179 Log.e(TAG, "Invalid M-RETRIEVE.CONF PDU. " + 180 (pdu != null ? "message type: " + pdu.getMessageType() : "null pdu")); 181 mTransactionState.setState(FAILED); 182 status = STATUS_UNRECOGNIZED; 183 } else { 184 // Save the received PDU (must be a M-RETRIEVE.CONF). 185 PduPersister p = PduPersister.getPduPersister(mContext); 186 Uri uri = p.persist(pdu, Inbox.CONTENT_URI, true, 187 MessagingPreferenceActivity.getIsGroupMmsEnabled(mContext), null); 188 189 // Use local time instead of PDU time 190 ContentValues values = new ContentValues(1); 191 values.put(Mms.DATE, System.currentTimeMillis() / 1000L); 192 SqliteWrapper.update(mContext, mContext.getContentResolver(), 193 uri, values, null, null); 194 195 // We have successfully downloaded the new MM. Delete the 196 // M-NotifyResp.ind from Inbox. 197 SqliteWrapper.delete(mContext, mContext.getContentResolver(), 198 mUri, null, null); 199 Log.v(TAG, "NotificationTransaction received new mms message: " + uri); 200 // Delete obsolete threads 201 SqliteWrapper.delete(mContext, mContext.getContentResolver(), 202 Threads.OBSOLETE_THREADS_URI, null, null); 203 204 // Notify observers with newly received MM. 205 mUri = uri; 206 status = STATUS_RETRIEVED; 207 } 208 } 209 210 if (LOCAL_LOGV) { 211 Log.v(TAG, "status=0x" + Integer.toHexString(status)); 212 } 213 214 // Check the status and update the result state of this Transaction. 215 switch (status) { 216 case STATUS_RETRIEVED: 217 mTransactionState.setState(SUCCESS); 218 break; 219 case STATUS_DEFERRED: 220 // STATUS_DEFERRED, may be a failed immediate retrieval. 221 if (mTransactionState.getState() == INITIALIZED) { 222 mTransactionState.setState(SUCCESS); 223 } 224 break; 225 } 226 227 sendNotifyRespInd(status); 228 229 // Make sure this thread isn't over the limits in message count. 230 Recycler.getMmsRecycler().deleteOldMessagesInSameThreadAsMessage(mContext, mUri); 231 MmsWidgetProvider.notifyDatasetChanged(mContext); 232 } catch (Throwable t) { 233 Log.e(TAG, Log.getStackTraceString(t)); 234 } finally { 235 mTransactionState.setContentUri(mUri); 236 if (!autoDownload) { 237 // Always mark the transaction successful for deferred 238 // download since any error here doesn't make sense. 239 mTransactionState.setState(SUCCESS); 240 } 241 if (mTransactionState.getState() != SUCCESS) { 242 mTransactionState.setState(FAILED); 243 Log.e(TAG, "NotificationTransaction failed."); 244 } 245 notifyObservers(); 246 } 247 } 248 249 private void sendNotifyRespInd(int status) throws MmsException, IOException { 250 // Create the M-NotifyResp.ind 251 NotifyRespInd notifyRespInd = new NotifyRespInd( 252 PduHeaders.CURRENT_MMS_VERSION, 253 mNotificationInd.getTransactionId(), 254 status); 255 256 // Pack M-NotifyResp.ind and send it 257 if(MmsConfig.getNotifyWapMMSC()) { 258 sendPdu(new PduComposer(mContext, notifyRespInd).make(), mContentLocation); 259 } else { 260 sendPdu(new PduComposer(mContext, notifyRespInd).make()); 261 } 262 } 263 264 @Override 265 public int getType() { 266 return NOTIFICATION_TRANSACTION; 267 } 268 } 269