1 /* 2 * Copyright (C) 2008 Esmertec AG. 3 * Copyright (C) 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 com.android.mms.R; 21 import com.android.mms.LogTag; 22 import com.android.mms.util.DownloadManager; 23 import com.google.android.mms.pdu.PduHeaders; 24 import com.google.android.mms.pdu.PduPersister; 25 import android.database.sqlite.SqliteWrapper; 26 27 import android.app.AlarmManager; 28 import android.app.PendingIntent; 29 import android.content.ContentResolver; 30 import android.content.ContentUris; 31 import android.content.ContentValues; 32 import android.content.Context; 33 import android.content.Intent; 34 import android.database.Cursor; 35 import android.net.ConnectivityManager; 36 import android.net.NetworkInfo; 37 import android.net.Uri; 38 import android.provider.Telephony.Mms; 39 import android.provider.Telephony.MmsSms; 40 import android.provider.Telephony.Sms; 41 import android.provider.Telephony.MmsSms.PendingMessages; 42 import android.text.format.DateFormat; 43 import android.util.Log; 44 45 public class RetryScheduler implements Observer { 46 private static final String TAG = "RetryScheduler"; 47 private static final boolean DEBUG = false; 48 private static final boolean LOCAL_LOGV = false; 49 50 private final Context mContext; 51 private final ContentResolver mContentResolver; 52 RetryScheduler(Context context)53 private RetryScheduler(Context context) { 54 mContext = context; 55 mContentResolver = context.getContentResolver(); 56 } 57 58 private static RetryScheduler sInstance; getInstance(Context context)59 public static RetryScheduler getInstance(Context context) { 60 if (sInstance == null) { 61 sInstance = new RetryScheduler(context); 62 } 63 return sInstance; 64 } 65 isConnected()66 private boolean isConnected() { 67 ConnectivityManager mConnMgr = (ConnectivityManager) 68 mContext.getSystemService(Context.CONNECTIVITY_SERVICE); 69 NetworkInfo ni = mConnMgr.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_MMS); 70 return (ni == null ? false : ni.isConnected()); 71 } 72 update(Observable observable)73 public void update(Observable observable) { 74 try { 75 Transaction t = (Transaction) observable; 76 77 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 78 Log.v(TAG, "[RetryScheduler] update " + observable); 79 } 80 81 // We are only supposed to handle M-Notification.ind, M-Send.req 82 // and M-ReadRec.ind. 83 if ((t instanceof NotificationTransaction) 84 || (t instanceof RetrieveTransaction) 85 || (t instanceof ReadRecTransaction) 86 || (t instanceof SendTransaction)) { 87 try { 88 TransactionState state = t.getState(); 89 if (state.getState() == TransactionState.FAILED) { 90 Uri uri = state.getContentUri(); 91 if (uri != null) { 92 scheduleRetry(uri); 93 } 94 } 95 } finally { 96 t.detach(this); 97 } 98 } 99 } finally { 100 if (isConnected()) { 101 setRetryAlarm(mContext); 102 } 103 } 104 } 105 scheduleRetry(Uri uri)106 private void scheduleRetry(Uri uri) { 107 long msgId = ContentUris.parseId(uri); 108 109 Uri.Builder uriBuilder = PendingMessages.CONTENT_URI.buildUpon(); 110 uriBuilder.appendQueryParameter("protocol", "mms"); 111 uriBuilder.appendQueryParameter("message", String.valueOf(msgId)); 112 113 Cursor cursor = SqliteWrapper.query(mContext, mContentResolver, 114 uriBuilder.build(), null, null, null, null); 115 116 if (cursor != null) { 117 try { 118 if ((cursor.getCount() == 1) && cursor.moveToFirst()) { 119 int msgType = cursor.getInt(cursor.getColumnIndexOrThrow( 120 PendingMessages.MSG_TYPE)); 121 122 int retryIndex = cursor.getInt(cursor.getColumnIndexOrThrow( 123 PendingMessages.RETRY_INDEX)) + 1; // Count this time. 124 125 // TODO Should exactly understand what was happened. 126 int errorType = MmsSms.ERR_TYPE_GENERIC; 127 128 DefaultRetryScheme scheme = new DefaultRetryScheme(mContext, retryIndex); 129 130 ContentValues values = new ContentValues(4); 131 long current = System.currentTimeMillis(); 132 boolean isRetryDownloading = 133 (msgType == PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND); 134 boolean retry = true; 135 int respStatus = getResponseStatus(msgId); 136 int errorString = 0; 137 if (!isRetryDownloading) { 138 // Send Transaction case 139 switch (respStatus) { 140 case PduHeaders.RESPONSE_STATUS_ERROR_SENDING_ADDRESS_UNRESOLVED: 141 errorString = R.string.invalid_destination; 142 break; 143 case PduHeaders.RESPONSE_STATUS_ERROR_SERVICE_DENIED: 144 case PduHeaders.RESPONSE_STATUS_ERROR_PERMANENT_SERVICE_DENIED: 145 errorString = R.string.service_not_activated; 146 break; 147 case PduHeaders.RESPONSE_STATUS_ERROR_NETWORK_PROBLEM: 148 errorString = R.string.service_network_problem; 149 break; 150 case PduHeaders.RESPONSE_STATUS_ERROR_TRANSIENT_MESSAGE_NOT_FOUND: 151 case PduHeaders.RESPONSE_STATUS_ERROR_PERMANENT_MESSAGE_NOT_FOUND: 152 errorString = R.string.service_message_not_found; 153 break; 154 } 155 if (errorString != 0) { 156 DownloadManager.getInstance().showErrorCodeToast(errorString); 157 retry = false; 158 } 159 } else { 160 // apply R880 IOT issue (Conformance 11.6 Retrieve Invalid Message) 161 // Notification Transaction case 162 respStatus = getRetrieveStatus(msgId); 163 if (respStatus == 164 PduHeaders.RESPONSE_STATUS_ERROR_PERMANENT_MESSAGE_NOT_FOUND) { 165 DownloadManager.getInstance().showErrorCodeToast( 166 R.string.service_message_not_found); 167 SqliteWrapper.delete(mContext, mContext.getContentResolver(), uri, 168 null, null); 169 retry = false; 170 return; 171 } 172 } 173 if ((retryIndex < scheme.getRetryLimit()) && retry) { 174 long retryAt = current + scheme.getWaitingInterval(); 175 176 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 177 Log.v(TAG, "scheduleRetry: retry for " + uri + " is scheduled at " 178 + (retryAt - System.currentTimeMillis()) + "ms from now"); 179 } 180 181 values.put(PendingMessages.DUE_TIME, retryAt); 182 183 if (isRetryDownloading) { 184 // Downloading process is transiently failed. 185 DownloadManager.getInstance().markState( 186 uri, DownloadManager.STATE_TRANSIENT_FAILURE); 187 } 188 } else { 189 errorType = MmsSms.ERR_TYPE_GENERIC_PERMANENT; 190 if (isRetryDownloading) { 191 Cursor c = SqliteWrapper.query(mContext, mContext.getContentResolver(), uri, 192 new String[] { Mms.THREAD_ID }, null, null, null); 193 194 long threadId = -1; 195 if (c != null) { 196 try { 197 if (c.moveToFirst()) { 198 threadId = c.getLong(0); 199 } 200 } finally { 201 c.close(); 202 } 203 } 204 205 if (threadId != -1) { 206 // Downloading process is permanently failed. 207 MessagingNotification.notifyDownloadFailed(mContext, threadId); 208 } 209 210 DownloadManager.getInstance().markState( 211 uri, DownloadManager.STATE_PERMANENT_FAILURE); 212 } else { 213 // Mark the failed message as unread. 214 ContentValues readValues = new ContentValues(1); 215 readValues.put(Mms.READ, 0); 216 SqliteWrapper.update(mContext, mContext.getContentResolver(), 217 uri, readValues, null, null); 218 MessagingNotification.notifySendFailed(mContext, true); 219 } 220 } 221 222 values.put(PendingMessages.ERROR_TYPE, errorType); 223 values.put(PendingMessages.RETRY_INDEX, retryIndex); 224 values.put(PendingMessages.LAST_TRY, current); 225 226 int columnIndex = cursor.getColumnIndexOrThrow( 227 PendingMessages._ID); 228 long id = cursor.getLong(columnIndex); 229 SqliteWrapper.update(mContext, mContentResolver, 230 PendingMessages.CONTENT_URI, 231 values, PendingMessages._ID + "=" + id, null); 232 } else if (LOCAL_LOGV) { 233 Log.v(TAG, "Cannot found correct pending status for: " + msgId); 234 } 235 } finally { 236 cursor.close(); 237 } 238 } 239 } 240 getResponseStatus(long msgID)241 private int getResponseStatus(long msgID) { 242 int respStatus = 0; 243 Cursor cursor = SqliteWrapper.query(mContext, mContentResolver, 244 Mms.Outbox.CONTENT_URI, null, Mms._ID + "=" + msgID, null, null); 245 try { 246 if (cursor.moveToFirst()) { 247 respStatus = cursor.getInt(cursor.getColumnIndexOrThrow(Mms.RESPONSE_STATUS)); 248 } 249 } finally { 250 cursor.close(); 251 } 252 if (respStatus != 0) { 253 Log.e(TAG, "Response status is: " + respStatus); 254 } 255 return respStatus; 256 } 257 258 // apply R880 IOT issue (Conformance 11.6 Retrieve Invalid Message) getRetrieveStatus(long msgID)259 private int getRetrieveStatus(long msgID) { 260 int retrieveStatus = 0; 261 Cursor cursor = SqliteWrapper.query(mContext, mContentResolver, 262 Mms.Inbox.CONTENT_URI, null, Mms._ID + "=" + msgID, null, null); 263 try { 264 if (cursor.moveToFirst()) { 265 retrieveStatus = cursor.getInt(cursor.getColumnIndexOrThrow( 266 Mms.RESPONSE_STATUS)); 267 } 268 } finally { 269 cursor.close(); 270 } 271 if (retrieveStatus != 0) { 272 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 273 Log.v(TAG, "Retrieve status is: " + retrieveStatus); 274 } 275 } 276 return retrieveStatus; 277 } 278 setRetryAlarm(Context context)279 public static void setRetryAlarm(Context context) { 280 Cursor cursor = PduPersister.getPduPersister(context).getPendingMessages( 281 Long.MAX_VALUE); 282 if (cursor != null) { 283 try { 284 if (cursor.moveToFirst()) { 285 // The result of getPendingMessages() is order by due time. 286 long retryAt = cursor.getLong(cursor.getColumnIndexOrThrow( 287 PendingMessages.DUE_TIME)); 288 289 Intent service = new Intent(TransactionService.ACTION_ONALARM, 290 null, context, TransactionService.class); 291 PendingIntent operation = PendingIntent.getService( 292 context, 0, service, PendingIntent.FLAG_ONE_SHOT); 293 AlarmManager am = (AlarmManager) context.getSystemService( 294 Context.ALARM_SERVICE); 295 am.set(AlarmManager.RTC, retryAt, operation); 296 297 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 298 Log.v(TAG, "Next retry is scheduled at" 299 + (retryAt - System.currentTimeMillis()) + "ms from now"); 300 } 301 } 302 } finally { 303 cursor.close(); 304 } 305 } 306 } 307 } 308