/* * Copyright (C) 2008 Esmertec AG. * Copyright (C) 2008 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.mms.transaction; import com.android.mms.R; import com.android.mms.LogTag; import com.android.mms.util.DownloadManager; import com.google.android.mms.pdu.PduHeaders; import com.google.android.mms.pdu.PduPersister; import android.database.sqlite.SqliteWrapper; import android.app.AlarmManager; import android.app.PendingIntent; import android.content.ContentResolver; import android.content.ContentUris; import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.net.Uri; import android.provider.Telephony.Mms; import android.provider.Telephony.MmsSms; import android.provider.Telephony.Sms; import android.provider.Telephony.MmsSms.PendingMessages; import android.text.format.DateFormat; import android.util.Log; public class RetryScheduler implements Observer { private static final String TAG = "RetryScheduler"; private static final boolean DEBUG = false; private static final boolean LOCAL_LOGV = false; private final Context mContext; private final ContentResolver mContentResolver; private RetryScheduler(Context context) { mContext = context; mContentResolver = context.getContentResolver(); } private static RetryScheduler sInstance; public static RetryScheduler getInstance(Context context) { if (sInstance == null) { sInstance = new RetryScheduler(context); } return sInstance; } private boolean isConnected() { ConnectivityManager mConnMgr = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo ni = mConnMgr.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_MMS); return (ni == null ? false : ni.isConnected()); } public void update(Observable observable) { try { Transaction t = (Transaction) observable; if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { Log.v(TAG, "[RetryScheduler] update " + observable); } // We are only supposed to handle M-Notification.ind, M-Send.req // and M-ReadRec.ind. if ((t instanceof NotificationTransaction) || (t instanceof RetrieveTransaction) || (t instanceof ReadRecTransaction) || (t instanceof SendTransaction)) { try { TransactionState state = t.getState(); if (state.getState() == TransactionState.FAILED) { Uri uri = state.getContentUri(); if (uri != null) { scheduleRetry(uri); } } } finally { t.detach(this); } } } finally { if (isConnected()) { setRetryAlarm(mContext); } } } private void scheduleRetry(Uri uri) { long msgId = ContentUris.parseId(uri); Uri.Builder uriBuilder = PendingMessages.CONTENT_URI.buildUpon(); uriBuilder.appendQueryParameter("protocol", "mms"); uriBuilder.appendQueryParameter("message", String.valueOf(msgId)); Cursor cursor = SqliteWrapper.query(mContext, mContentResolver, uriBuilder.build(), null, null, null, null); if (cursor != null) { try { if ((cursor.getCount() == 1) && cursor.moveToFirst()) { int msgType = cursor.getInt(cursor.getColumnIndexOrThrow( PendingMessages.MSG_TYPE)); int retryIndex = cursor.getInt(cursor.getColumnIndexOrThrow( PendingMessages.RETRY_INDEX)) + 1; // Count this time. // TODO Should exactly understand what was happened. int errorType = MmsSms.ERR_TYPE_GENERIC; DefaultRetryScheme scheme = new DefaultRetryScheme(mContext, retryIndex); ContentValues values = new ContentValues(4); long current = System.currentTimeMillis(); boolean isRetryDownloading = (msgType == PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND); boolean retry = true; int respStatus = getResponseStatus(msgId); int errorString = 0; if (!isRetryDownloading) { // Send Transaction case switch (respStatus) { case PduHeaders.RESPONSE_STATUS_ERROR_SENDING_ADDRESS_UNRESOLVED: errorString = R.string.invalid_destination; break; case PduHeaders.RESPONSE_STATUS_ERROR_SERVICE_DENIED: case PduHeaders.RESPONSE_STATUS_ERROR_PERMANENT_SERVICE_DENIED: errorString = R.string.service_not_activated; break; case PduHeaders.RESPONSE_STATUS_ERROR_NETWORK_PROBLEM: errorString = R.string.service_network_problem; break; case PduHeaders.RESPONSE_STATUS_ERROR_TRANSIENT_MESSAGE_NOT_FOUND: case PduHeaders.RESPONSE_STATUS_ERROR_PERMANENT_MESSAGE_NOT_FOUND: errorString = R.string.service_message_not_found; break; } if (errorString != 0) { DownloadManager.getInstance().showErrorCodeToast(errorString); retry = false; } } else { // apply R880 IOT issue (Conformance 11.6 Retrieve Invalid Message) // Notification Transaction case respStatus = getRetrieveStatus(msgId); if (respStatus == PduHeaders.RESPONSE_STATUS_ERROR_PERMANENT_MESSAGE_NOT_FOUND) { DownloadManager.getInstance().showErrorCodeToast( R.string.service_message_not_found); SqliteWrapper.delete(mContext, mContext.getContentResolver(), uri, null, null); retry = false; return; } } if ((retryIndex < scheme.getRetryLimit()) && retry) { long retryAt = current + scheme.getWaitingInterval(); if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { Log.v(TAG, "scheduleRetry: retry for " + uri + " is scheduled at " + (retryAt - System.currentTimeMillis()) + "ms from now"); } values.put(PendingMessages.DUE_TIME, retryAt); if (isRetryDownloading) { // Downloading process is transiently failed. DownloadManager.getInstance().markState( uri, DownloadManager.STATE_TRANSIENT_FAILURE); } } else { errorType = MmsSms.ERR_TYPE_GENERIC_PERMANENT; if (isRetryDownloading) { Cursor c = SqliteWrapper.query(mContext, mContext.getContentResolver(), uri, new String[] { Mms.THREAD_ID }, null, null, null); long threadId = -1; if (c != null) { try { if (c.moveToFirst()) { threadId = c.getLong(0); } } finally { c.close(); } } if (threadId != -1) { // Downloading process is permanently failed. MessagingNotification.notifyDownloadFailed(mContext, threadId); } DownloadManager.getInstance().markState( uri, DownloadManager.STATE_PERMANENT_FAILURE); } else { // Mark the failed message as unread. ContentValues readValues = new ContentValues(1); readValues.put(Mms.READ, 0); SqliteWrapper.update(mContext, mContext.getContentResolver(), uri, readValues, null, null); MessagingNotification.notifySendFailed(mContext, true); } } values.put(PendingMessages.ERROR_TYPE, errorType); values.put(PendingMessages.RETRY_INDEX, retryIndex); values.put(PendingMessages.LAST_TRY, current); int columnIndex = cursor.getColumnIndexOrThrow( PendingMessages._ID); long id = cursor.getLong(columnIndex); SqliteWrapper.update(mContext, mContentResolver, PendingMessages.CONTENT_URI, values, PendingMessages._ID + "=" + id, null); } else if (LOCAL_LOGV) { Log.v(TAG, "Cannot found correct pending status for: " + msgId); } } finally { cursor.close(); } } } private int getResponseStatus(long msgID) { int respStatus = 0; Cursor cursor = SqliteWrapper.query(mContext, mContentResolver, Mms.Outbox.CONTENT_URI, null, Mms._ID + "=" + msgID, null, null); try { if (cursor.moveToFirst()) { respStatus = cursor.getInt(cursor.getColumnIndexOrThrow(Mms.RESPONSE_STATUS)); } } finally { cursor.close(); } if (respStatus != 0) { Log.e(TAG, "Response status is: " + respStatus); } return respStatus; } // apply R880 IOT issue (Conformance 11.6 Retrieve Invalid Message) private int getRetrieveStatus(long msgID) { int retrieveStatus = 0; Cursor cursor = SqliteWrapper.query(mContext, mContentResolver, Mms.Inbox.CONTENT_URI, null, Mms._ID + "=" + msgID, null, null); try { if (cursor.moveToFirst()) { retrieveStatus = cursor.getInt(cursor.getColumnIndexOrThrow( Mms.RESPONSE_STATUS)); } } finally { cursor.close(); } if (retrieveStatus != 0) { if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { Log.v(TAG, "Retrieve status is: " + retrieveStatus); } } return retrieveStatus; } public static void setRetryAlarm(Context context) { Cursor cursor = PduPersister.getPduPersister(context).getPendingMessages( Long.MAX_VALUE); if (cursor != null) { try { if (cursor.moveToFirst()) { // The result of getPendingMessages() is order by due time. long retryAt = cursor.getLong(cursor.getColumnIndexOrThrow( PendingMessages.DUE_TIME)); Intent service = new Intent(TransactionService.ACTION_ONALARM, null, context, TransactionService.class); PendingIntent operation = PendingIntent.getService( context, 0, service, PendingIntent.FLAG_ONE_SHOT); AlarmManager am = (AlarmManager) context.getSystemService( Context.ALARM_SERVICE); am.set(AlarmManager.RTC, retryAt, operation); if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { Log.v(TAG, "Next retry is scheduled at" + (retryAt - System.currentTimeMillis()) + "ms from now"); } } } finally { cursor.close(); } } } }