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.Config; 44 import android.util.Log; 45 46 public class RetryScheduler implements Observer { 47 private static final String TAG = "RetryScheduler"; 48 private static final boolean DEBUG = false; 49 private static final boolean LOCAL_LOGV = DEBUG ? Config.LOGD : Config.LOGV; 50 51 private final Context mContext; 52 private final ContentResolver mContentResolver; 53 54 private RetryScheduler(Context context) { 55 mContext = context; 56 mContentResolver = context.getContentResolver(); 57 } 58 59 private static RetryScheduler sInstance; 60 public static RetryScheduler getInstance(Context context) { 61 if (sInstance == null) { 62 sInstance = new RetryScheduler(context); 63 } 64 return sInstance; 65 } 66 67 private boolean isConnected() { 68 ConnectivityManager mConnMgr = (ConnectivityManager) 69 mContext.getSystemService(Context.CONNECTIVITY_SERVICE); 70 return (mConnMgr.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_MMS). 71 isConnected()); 72 } 73 74 public void update(Observable observable) { 75 try { 76 Transaction t = (Transaction) observable; 77 78 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 79 Log.v(TAG, "[RetryScheduler] update " + observable); 80 } 81 82 // We are only supposed to handle M-Notification.ind, M-Send.req 83 // and M-ReadRec.ind. 84 if ((t instanceof NotificationTransaction) 85 || (t instanceof RetrieveTransaction) 86 || (t instanceof ReadRecTransaction) 87 || (t instanceof SendTransaction)) { 88 try { 89 TransactionState state = t.getState(); 90 if (state.getState() == TransactionState.FAILED) { 91 Uri uri = state.getContentUri(); 92 if (uri != null) { 93 scheduleRetry(uri); 94 } 95 } 96 } finally { 97 t.detach(this); 98 } 99 } 100 } finally { 101 if (isConnected()) { 102 setRetryAlarm(mContext); 103 } 104 } 105 } 106 107 private void scheduleRetry(Uri uri) { 108 long msgId = ContentUris.parseId(uri); 109 110 Uri.Builder uriBuilder = PendingMessages.CONTENT_URI.buildUpon(); 111 uriBuilder.appendQueryParameter("protocol", "mms"); 112 uriBuilder.appendQueryParameter("message", String.valueOf(msgId)); 113 114 Cursor cursor = SqliteWrapper.query(mContext, mContentResolver, 115 uriBuilder.build(), null, null, null, null); 116 117 if (cursor != null) { 118 try { 119 if ((cursor.getCount() == 1) && cursor.moveToFirst()) { 120 int msgType = cursor.getInt(cursor.getColumnIndexOrThrow( 121 PendingMessages.MSG_TYPE)); 122 123 int retryIndex = cursor.getInt(cursor.getColumnIndexOrThrow( 124 PendingMessages.RETRY_INDEX)) + 1; // Count this time. 125 126 // TODO Should exactly understand what was happened. 127 int errorType = MmsSms.ERR_TYPE_GENERIC; 128 129 DefaultRetryScheme scheme = new DefaultRetryScheme(mContext, retryIndex); 130 131 ContentValues values = new ContentValues(4); 132 long current = System.currentTimeMillis(); 133 boolean isRetryDownloading = 134 (msgType == PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND); 135 boolean retry = true; 136 int respStatus = getResponseStatus(msgId); 137 if (respStatus == PduHeaders.RESPONSE_STATUS_ERROR_SENDING_ADDRESS_UNRESOLVED) { 138 DownloadManager.getInstance().showErrorCodeToast(R.string.invalid_destination); 139 retry = false; 140 } 141 142 if ((retryIndex < scheme.getRetryLimit()) && retry) { 143 long retryAt = current + scheme.getWaitingInterval(); 144 145 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 146 Log.v(TAG, "scheduleRetry: retry for " + uri + " is scheduled at " 147 + (retryAt - System.currentTimeMillis()) + "ms from now"); 148 } 149 150 values.put(PendingMessages.DUE_TIME, retryAt); 151 152 if (isRetryDownloading) { 153 // Downloading process is transiently failed. 154 DownloadManager.getInstance().markState( 155 uri, DownloadManager.STATE_TRANSIENT_FAILURE); 156 } 157 } else { 158 errorType = MmsSms.ERR_TYPE_GENERIC_PERMANENT; 159 if (isRetryDownloading) { 160 Cursor c = SqliteWrapper.query(mContext, mContext.getContentResolver(), uri, 161 new String[] { Mms.THREAD_ID }, null, null, null); 162 163 long threadId = -1; 164 if (c != null) { 165 try { 166 if (c.moveToFirst()) { 167 threadId = c.getLong(0); 168 } 169 } finally { 170 c.close(); 171 } 172 } 173 174 if (threadId != -1) { 175 // Downloading process is permanently failed. 176 MessagingNotification.notifyDownloadFailed(mContext, threadId); 177 } 178 179 DownloadManager.getInstance().markState( 180 uri, DownloadManager.STATE_PERMANENT_FAILURE); 181 } else { 182 // Mark the failed message as unread. 183 ContentValues readValues = new ContentValues(1); 184 readValues.put(Mms.READ, 0); 185 SqliteWrapper.update(mContext, mContext.getContentResolver(), 186 uri, readValues, null, null); 187 MessagingNotification.notifySendFailed(mContext, true); 188 } 189 } 190 191 values.put(PendingMessages.ERROR_TYPE, errorType); 192 values.put(PendingMessages.RETRY_INDEX, retryIndex); 193 values.put(PendingMessages.LAST_TRY, current); 194 195 int columnIndex = cursor.getColumnIndexOrThrow( 196 PendingMessages._ID); 197 long id = cursor.getLong(columnIndex); 198 SqliteWrapper.update(mContext, mContentResolver, 199 PendingMessages.CONTENT_URI, 200 values, PendingMessages._ID + "=" + id, null); 201 } else if (LOCAL_LOGV) { 202 Log.v(TAG, "Cannot found correct pending status for: " + msgId); 203 } 204 } finally { 205 cursor.close(); 206 } 207 } 208 } 209 210 private int getResponseStatus(long msgID) { 211 int respStatus = 0; 212 Cursor cursor = SqliteWrapper.query(mContext, mContentResolver, 213 Mms.Outbox.CONTENT_URI, null, Mms._ID + "=" + msgID, null, null); 214 try { 215 if (cursor.moveToFirst()) { 216 respStatus = cursor.getInt(cursor.getColumnIndexOrThrow(Mms.RESPONSE_STATUS)); 217 } 218 } finally { 219 cursor.close(); 220 } 221 if (respStatus != 0) { 222 Log.e(TAG, "Response status is: " + respStatus); 223 } 224 return respStatus; 225 } 226 227 public static void setRetryAlarm(Context context) { 228 Cursor cursor = PduPersister.getPduPersister(context).getPendingMessages( 229 Long.MAX_VALUE); 230 if (cursor != null) { 231 try { 232 if (cursor.moveToFirst()) { 233 // The result of getPendingMessages() is order by due time. 234 long retryAt = cursor.getLong(cursor.getColumnIndexOrThrow( 235 PendingMessages.DUE_TIME)); 236 237 Intent service = new Intent(TransactionService.ACTION_ONALARM, 238 null, context, TransactionService.class); 239 PendingIntent operation = PendingIntent.getService( 240 context, 0, service, PendingIntent.FLAG_ONE_SHOT); 241 AlarmManager am = (AlarmManager) context.getSystemService( 242 Context.ALARM_SERVICE); 243 am.set(AlarmManager.RTC, retryAt, operation); 244 245 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 246 Log.v(TAG, "Next retry is scheduled at" 247 + (retryAt - System.currentTimeMillis()) + "ms from now"); 248 } 249 } 250 } finally { 251 cursor.close(); 252 } 253 } 254 } 255 } 256