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 com.android.mms.MmsApp; 29 import com.android.mms.MmsConfig; 30 import com.android.mms.util.DownloadManager; 31 import com.android.mms.util.Recycler; 32 import com.google.android.mms.MmsException; 33 import com.google.android.mms.pdu.GenericPdu; 34 import com.google.android.mms.pdu.NotificationInd; 35 import com.google.android.mms.pdu.NotifyRespInd; 36 import com.google.android.mms.pdu.PduComposer; 37 import com.google.android.mms.pdu.PduHeaders; 38 import com.google.android.mms.pdu.PduParser; 39 import com.google.android.mms.pdu.PduPersister; 40 import android.database.sqlite.SqliteWrapper; 41 42 import android.content.ContentValues; 43 import android.content.Context; 44 import android.database.Cursor; 45 import android.net.Uri; 46 import android.provider.Telephony.Mms; 47 import android.provider.Telephony.Mms.Inbox; 48 import android.telephony.TelephonyManager; 49 import android.util.Log; 50 51 import java.io.IOException; 52 53 /** 54 * The NotificationTransaction is responsible for handling multimedia 55 * message notifications (M-Notification.ind). It: 56 * 57 * <ul> 58 * <li>Composes the notification response (M-NotifyResp.ind). 59 * <li>Sends the notification response to the MMSC server. 60 * <li>Stores the notification indication. 61 * <li>Notifies the TransactionService about succesful completion. 62 * </ul> 63 * 64 * NOTE: This MMS client handles all notifications with a <b>deferred 65 * retrieval</b> response. The transaction service, upon succesful 66 * completion of this transaction, will trigger a retrieve transaction 67 * in case the client is in immediate retrieve mode. 68 */ 69 public class NotificationTransaction extends Transaction implements Runnable { 70 private static final String TAG = "NotificationTransaction"; 71 private static final boolean DEBUG = false; 72 private static final boolean LOCAL_LOGV = false; 73 74 private Uri mUri; 75 private NotificationInd mNotificationInd; 76 private String mContentLocation; 77 78 public NotificationTransaction( 79 Context context, int serviceId, 80 TransactionSettings connectionSettings, String uriString) { 81 super(context, serviceId, connectionSettings); 82 83 mUri = Uri.parse(uriString); 84 85 try { 86 mNotificationInd = (NotificationInd) 87 PduPersister.getPduPersister(context).load(mUri); 88 } catch (MmsException e) { 89 Log.e(TAG, "Failed to load NotificationInd from: " + uriString, e); 90 throw new IllegalArgumentException(); 91 } 92 93 mId = new String(mNotificationInd.getTransactionId()); 94 mContentLocation = new String(mNotificationInd.getContentLocation()); 95 96 // Attach the transaction to the instance of RetryScheduler. 97 attach(RetryScheduler.getInstance(context)); 98 } 99 100 /** 101 * This constructor is only used for test purposes. 102 */ 103 public NotificationTransaction( 104 Context context, int serviceId, 105 TransactionSettings connectionSettings, NotificationInd ind) { 106 super(context, serviceId, connectionSettings); 107 108 try { 109 mUri = PduPersister.getPduPersister(context).persist( 110 ind, Inbox.CONTENT_URI); 111 } catch (MmsException e) { 112 Log.e(TAG, "Failed to save NotificationInd in constructor.", e); 113 throw new IllegalArgumentException(); 114 } 115 116 mNotificationInd = ind; 117 mId = new String(ind.getTransactionId()); 118 } 119 120 /* 121 * (non-Javadoc) 122 * @see com.google.android.mms.pdu.Transaction#process() 123 */ 124 @Override 125 public void process() { 126 new Thread(this).start(); 127 } 128 129 public void run() { 130 DownloadManager downloadManager = DownloadManager.getInstance(); 131 boolean autoDownload = downloadManager.isAuto(); 132 boolean dataSuspended = (MmsApp.getApplication().getTelephonyManager().getDataState() == 133 TelephonyManager.DATA_SUSPENDED); 134 try { 135 if (LOCAL_LOGV) { 136 Log.v(TAG, "Notification transaction launched: " + this); 137 } 138 139 // By default, we set status to STATUS_DEFERRED because we 140 // should response MMSC with STATUS_DEFERRED when we cannot 141 // download a MM immediately. 142 int status = STATUS_DEFERRED; 143 // Don't try to download when data is suspended, as it will fail, so defer download 144 if (!autoDownload || dataSuspended) { 145 downloadManager.markState(mUri, DownloadManager.STATE_UNSTARTED); 146 sendNotifyRespInd(status); 147 return; 148 } 149 150 downloadManager.markState(mUri, DownloadManager.STATE_DOWNLOADING); 151 152 if (LOCAL_LOGV) { 153 Log.v(TAG, "Content-Location: " + mContentLocation); 154 } 155 156 byte[] retrieveConfData = null; 157 // We should catch exceptions here to response MMSC 158 // with STATUS_DEFERRED. 159 try { 160 retrieveConfData = getPdu(mContentLocation); 161 } catch (IOException e) { 162 mTransactionState.setState(FAILED); 163 } 164 165 if (retrieveConfData != null) { 166 GenericPdu pdu = new PduParser(retrieveConfData).parse(); 167 if ((pdu == null) || (pdu.getMessageType() != MESSAGE_TYPE_RETRIEVE_CONF)) { 168 Log.e(TAG, "Invalid M-RETRIEVE.CONF PDU. " + 169 (pdu != null ? "message type: " + pdu.getMessageType() : "null pdu")); 170 mTransactionState.setState(FAILED); 171 status = STATUS_UNRECOGNIZED; 172 } else { 173 // Save the received PDU (must be a M-RETRIEVE.CONF). 174 PduPersister p = PduPersister.getPduPersister(mContext); 175 Uri uri = p.persist(pdu, Inbox.CONTENT_URI); 176 177 // Use local time instead of PDU time 178 ContentValues values = new ContentValues(1); 179 values.put(Mms.DATE, System.currentTimeMillis() / 1000L); 180 SqliteWrapper.update(mContext, mContext.getContentResolver(), 181 uri, values, null, null); 182 183 // We have successfully downloaded the new MM. Delete the 184 // M-NotifyResp.ind from Inbox. 185 SqliteWrapper.delete(mContext, mContext.getContentResolver(), 186 mUri, null, null); 187 // Notify observers with newly received MM. 188 mUri = uri; 189 status = STATUS_RETRIEVED; 190 } 191 } 192 193 if (LOCAL_LOGV) { 194 Log.v(TAG, "status=0x" + Integer.toHexString(status)); 195 } 196 197 // Check the status and update the result state of this Transaction. 198 switch (status) { 199 case STATUS_RETRIEVED: 200 mTransactionState.setState(SUCCESS); 201 break; 202 case STATUS_DEFERRED: 203 // STATUS_DEFERRED, may be a failed immediate retrieval. 204 if (mTransactionState.getState() == INITIALIZED) { 205 mTransactionState.setState(SUCCESS); 206 } 207 break; 208 } 209 210 sendNotifyRespInd(status); 211 212 // Make sure this thread isn't over the limits in message count. 213 Recycler.getMmsRecycler().deleteOldMessagesInSameThreadAsMessage(mContext, mUri); 214 } catch (Throwable t) { 215 Log.e(TAG, Log.getStackTraceString(t)); 216 } finally { 217 mTransactionState.setContentUri(mUri); 218 if (!autoDownload || dataSuspended) { 219 // Always mark the transaction successful for deferred 220 // download since any error here doesn't make sense. 221 mTransactionState.setState(SUCCESS); 222 } 223 if (mTransactionState.getState() != SUCCESS) { 224 mTransactionState.setState(FAILED); 225 Log.e(TAG, "NotificationTransaction failed."); 226 } 227 notifyObservers(); 228 } 229 } 230 231 private void sendNotifyRespInd(int status) throws MmsException, IOException { 232 // Create the M-NotifyResp.ind 233 NotifyRespInd notifyRespInd = new NotifyRespInd( 234 PduHeaders.CURRENT_MMS_VERSION, 235 mNotificationInd.getTransactionId(), 236 status); 237 238 // Pack M-NotifyResp.ind and send it 239 if(MmsConfig.getNotifyWapMMSC()) { 240 sendPdu(new PduComposer(mContext, notifyRespInd).make(), mContentLocation); 241 } else { 242 sendPdu(new PduComposer(mContext, notifyRespInd).make()); 243 } 244 } 245 246 @Override 247 public int getType() { 248 return NOTIFICATION_TRANSACTION; 249 } 250 } 251