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 18package com.android.mms.transaction; 19 20import static com.android.mms.transaction.TransactionState.FAILED; 21import static com.android.mms.transaction.TransactionState.INITIALIZED; 22import static com.android.mms.transaction.TransactionState.SUCCESS; 23import static com.google.android.mms.pdu.PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF; 24import static com.google.android.mms.pdu.PduHeaders.STATUS_DEFERRED; 25import static com.google.android.mms.pdu.PduHeaders.STATUS_RETRIEVED; 26import static com.google.android.mms.pdu.PduHeaders.STATUS_UNRECOGNIZED; 27 28import java.io.IOException; 29 30import android.content.ContentValues; 31import android.content.Context; 32import android.database.sqlite.SqliteWrapper; 33import android.net.Uri; 34import android.provider.Telephony.Mms; 35import android.provider.Telephony.Threads; 36import android.provider.Telephony.Mms.Inbox; 37import android.telephony.TelephonyManager; 38import android.util.Log; 39 40import com.android.mms.LogTag; 41import com.android.mms.MmsApp; 42import com.android.mms.MmsConfig; 43import com.android.mms.ui.MessagingPreferenceActivity; 44import com.android.mms.util.DownloadManager; 45import com.android.mms.util.Recycler; 46import com.android.mms.widget.MmsWidgetProvider; 47import com.google.android.mms.MmsException; 48import com.google.android.mms.pdu.GenericPdu; 49import com.google.android.mms.pdu.NotificationInd; 50import com.google.android.mms.pdu.NotifyRespInd; 51import com.google.android.mms.pdu.PduComposer; 52import com.google.android.mms.pdu.PduHeaders; 53import com.google.android.mms.pdu.PduParser; 54import com.google.android.mms.pdu.PduPersister; 55 56/** 57 * The NotificationTransaction is responsible for handling multimedia 58 * message notifications (M-Notification.ind). It: 59 * 60 * <ul> 61 * <li>Composes the notification response (M-NotifyResp.ind). 62 * <li>Sends the notification response to the MMSC server. 63 * <li>Stores the notification indication. 64 * <li>Notifies the TransactionService about succesful completion. 65 * </ul> 66 * 67 * NOTE: This MMS client handles all notifications with a <b>deferred 68 * retrieval</b> response. The transaction service, upon succesful 69 * completion of this transaction, will trigger a retrieve transaction 70 * in case the client is in immediate retrieve mode. 71 */ 72public class NotificationTransaction extends Transaction implements Runnable { 73 private static final String TAG = LogTag.TAG; 74 private static final boolean DEBUG = false; 75 private static final boolean LOCAL_LOGV = false; 76 77 private Uri mUri; 78 private NotificationInd mNotificationInd; 79 private String mContentLocation; 80 81 public NotificationTransaction( 82 Context context, int serviceId, 83 TransactionSettings connectionSettings, String uriString) { 84 super(context, serviceId, connectionSettings); 85 86 mUri = Uri.parse(uriString); 87 88 try { 89 mNotificationInd = (NotificationInd) 90 PduPersister.getPduPersister(context).load(mUri); 91 } catch (MmsException e) { 92 Log.e(TAG, "Failed to load NotificationInd from: " + uriString, e); 93 throw new IllegalArgumentException(); 94 } 95 96 mContentLocation = new String(mNotificationInd.getContentLocation()); 97 mId = mContentLocation; 98 99 // Attach the transaction to the instance of RetryScheduler. 100 attach(RetryScheduler.getInstance(context)); 101 } 102 103 /** 104 * This constructor is only used for test purposes. 105 */ 106 public NotificationTransaction( 107 Context context, int serviceId, 108 TransactionSettings connectionSettings, NotificationInd ind) { 109 super(context, serviceId, connectionSettings); 110 111 try { 112 // Save the pdu. If we can start downloading the real pdu immediately, don't allow 113 // persist() to create a thread for the notificationInd because it causes UI jank. 114 mUri = PduPersister.getPduPersister(context).persist( 115 ind, Inbox.CONTENT_URI, !allowAutoDownload(), 116 MessagingPreferenceActivity.getIsGroupMmsEnabled(context), null); 117 } catch (MmsException e) { 118 Log.e(TAG, "Failed to save NotificationInd in constructor.", e); 119 throw new IllegalArgumentException(); 120 } 121 122 mNotificationInd = ind; 123 mId = new String(mNotificationInd.getContentLocation()); 124 } 125 126 /* 127 * (non-Javadoc) 128 * @see com.google.android.mms.pdu.Transaction#process() 129 */ 130 @Override 131 public void process() { 132 new Thread(this, "NotificationTransaction").start(); 133 } 134 135 public static boolean allowAutoDownload() { 136 DownloadManager downloadManager = DownloadManager.getInstance(); 137 boolean autoDownload = downloadManager.isAuto(); 138 boolean dataSuspended = (MmsApp.getApplication().getTelephonyManager().getDataState() == 139 TelephonyManager.DATA_SUSPENDED); 140 return autoDownload && !dataSuspended; 141 } 142 143 public void run() { 144 DownloadManager downloadManager = DownloadManager.getInstance(); 145 boolean autoDownload = allowAutoDownload(); 146 try { 147 if (LOCAL_LOGV) { 148 Log.v(TAG, "Notification transaction launched: " + this); 149 } 150 151 // By default, we set status to STATUS_DEFERRED because we 152 // should response MMSC with STATUS_DEFERRED when we cannot 153 // download a MM immediately. 154 int status = STATUS_DEFERRED; 155 // Don't try to download when data is suspended, as it will fail, so defer download 156 if (!autoDownload) { 157 downloadManager.markState(mUri, DownloadManager.STATE_UNSTARTED); 158 sendNotifyRespInd(status); 159 return; 160 } 161 162 downloadManager.markState(mUri, DownloadManager.STATE_DOWNLOADING); 163 164 if (LOCAL_LOGV) { 165 Log.v(TAG, "Content-Location: " + mContentLocation); 166 } 167 168 byte[] retrieveConfData = null; 169 // We should catch exceptions here to response MMSC 170 // with STATUS_DEFERRED. 171 try { 172 retrieveConfData = getPdu(mContentLocation); 173 } catch (IOException e) { 174 mTransactionState.setState(FAILED); 175 } 176 177 if (retrieveConfData != null) { 178 GenericPdu pdu = new PduParser(retrieveConfData).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