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 53 private RetryScheduler(Context context) { 54 mContext = context; 55 mContentResolver = context.getContentResolver(); 56 } 57 58 private static RetryScheduler sInstance; 59 public static RetryScheduler getInstance(Context context) { 60 if (sInstance == null) { 61 sInstance = new RetryScheduler(context); 62 } 63 return sInstance; 64 } 65 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 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 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 switch (respStatus) { 138 case PduHeaders.RESPONSE_STATUS_ERROR_SENDING_ADDRESS_UNRESOLVED: 139 errorString = R.string.invalid_destination; 140 break; 141 142 case PduHeaders.RESPONSE_STATUS_ERROR_SERVICE_DENIED: 143 case PduHeaders.RESPONSE_STATUS_ERROR_PERMANENT_SERVICE_DENIED: 144 errorString = R.string.service_not_activated; 145 break; 146 147 case PduHeaders.RESPONSE_STATUS_ERROR_NETWORK_PROBLEM: 148 errorString = R.string.service_network_problem; 149 break; 150 151 case PduHeaders.RESPONSE_STATUS_ERROR_TRANSIENT_MESSAGE_NOT_FOUND: 152 case PduHeaders.RESPONSE_STATUS_ERROR_PERMANENT_MESSAGE_NOT_FOUND: 153 errorString = R.string.service_message_not_found; 154 break; 155 } 156 if (errorString != 0) { 157 DownloadManager.getInstance().showErrorCodeToast(errorString); 158 retry = false; 159 } 160 161 if ((retryIndex < scheme.getRetryLimit()) && retry) { 162 long retryAt = current + scheme.getWaitingInterval(); 163 164 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 165 Log.v(TAG, "scheduleRetry: retry for " + uri + " is scheduled at " 166 + (retryAt - System.currentTimeMillis()) + "ms from now"); 167 } 168 169 values.put(PendingMessages.DUE_TIME, retryAt); 170 171 if (isRetryDownloading) { 172 // Downloading process is transiently failed. 173 DownloadManager.getInstance().markState( 174 uri, DownloadManager.STATE_TRANSIENT_FAILURE); 175 } 176 } else { 177 errorType = MmsSms.ERR_TYPE_GENERIC_PERMANENT; 178 if (isRetryDownloading) { 179 Cursor c = SqliteWrapper.query(mContext, mContext.getContentResolver(), uri, 180 new String[] { Mms.THREAD_ID }, null, null, null); 181 182 long threadId = -1; 183 if (c != null) { 184 try { 185 if (c.moveToFirst()) { 186 threadId = c.getLong(0); 187 } 188 } finally { 189 c.close(); 190 } 191 } 192 193 if (threadId != -1) { 194 // Downloading process is permanently failed. 195 MessagingNotification.notifyDownloadFailed(mContext, threadId); 196 } 197 198 DownloadManager.getInstance().markState( 199 uri, DownloadManager.STATE_PERMANENT_FAILURE); 200 } else { 201 // Mark the failed message as unread. 202 ContentValues readValues = new ContentValues(1); 203 readValues.put(Mms.READ, 0); 204 SqliteWrapper.update(mContext, mContext.getContentResolver(), 205 uri, readValues, null, null); 206 MessagingNotification.notifySendFailed(mContext, true); 207 } 208 } 209 210 values.put(PendingMessages.ERROR_TYPE, errorType); 211 values.put(PendingMessages.RETRY_INDEX, retryIndex); 212 values.put(PendingMessages.LAST_TRY, current); 213 214 int columnIndex = cursor.getColumnIndexOrThrow( 215 PendingMessages._ID); 216 long id = cursor.getLong(columnIndex); 217 SqliteWrapper.update(mContext, mContentResolver, 218 PendingMessages.CONTENT_URI, 219 values, PendingMessages._ID + "=" + id, null); 220 } else if (LOCAL_LOGV) { 221 Log.v(TAG, "Cannot found correct pending status for: " + msgId); 222 } 223 } finally { 224 cursor.close(); 225 } 226 } 227 } 228 229 private int getResponseStatus(long msgID) { 230 int respStatus = 0; 231 Cursor cursor = SqliteWrapper.query(mContext, mContentResolver, 232 Mms.Outbox.CONTENT_URI, null, Mms._ID + "=" + msgID, null, null); 233 try { 234 if (cursor.moveToFirst()) { 235 respStatus = cursor.getInt(cursor.getColumnIndexOrThrow(Mms.RESPONSE_STATUS)); 236 } 237 } finally { 238 cursor.close(); 239 } 240 if (respStatus != 0) { 241 Log.e(TAG, "Response status is: " + respStatus); 242 } 243 return respStatus; 244 } 245 246 public static void setRetryAlarm(Context context) { 247 Cursor cursor = PduPersister.getPduPersister(context).getPendingMessages( 248 Long.MAX_VALUE); 249 if (cursor != null) { 250 try { 251 if (cursor.moveToFirst()) { 252 // The result of getPendingMessages() is order by due time. 253 long retryAt = cursor.getLong(cursor.getColumnIndexOrThrow( 254 PendingMessages.DUE_TIME)); 255 256 Intent service = new Intent(TransactionService.ACTION_ONALARM, 257 null, context, TransactionService.class); 258 PendingIntent operation = PendingIntent.getService( 259 context, 0, service, PendingIntent.FLAG_ONE_SHOT); 260 AlarmManager am = (AlarmManager) context.getSystemService( 261 Context.ALARM_SERVICE); 262 am.set(AlarmManager.RTC, retryAt, operation); 263 264 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 265 Log.v(TAG, "Next retry is scheduled at" 266 + (retryAt - System.currentTimeMillis()) + "ms from now"); 267 } 268 } 269 } finally { 270 cursor.close(); 271 } 272 } 273 } 274 } 275