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