/*
* 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.ui.MessageUtils;
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.telephony.TelephonyManager;
import android.text.format.DateFormat;
import android.util.Config;
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 = DEBUG ? Config.LOGD : Config.LOGV;
private final Context mContext;
private final ContentResolver mContentResolver;
private int mPhoneId;
RetryScheduler(Context context, int phoneId) {
mContext = context;
mContentResolver = context.getContentResolver();
mPhoneId = phoneId;
}
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);
return (mConnMgr.getNetworkInfo(ConnectivityManager.getMmsTypeByPhoneId(mPhoneId)).
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 {
// when the mms is disconnected by others, the retry should be set
//if (isConnected()) {
setRetryAlarm(mContext, mPhoneId);
//}
}
}
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 || msgType == PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF);
boolean retry = true;
int respStatus = getResponseStatus(msgId);
if (respStatus == PduHeaders.RESPONSE_STATUS_ERROR_SENDING_ADDRESS_UNRESOLVED
|| respStatus == PduHeaders.RESPONSE_STATUS_ERROR_SERVICE_DENIED) {
DownloadManager.getInstance().showErrorCodeToast(R.string.invalid_destination);
retry = false;
}
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, mPhoneId);
}
} 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, mPhoneId);
} 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;
}
public static void setRetryAlarm(Context context, int phoneId) {
Log.d(TAG, "enter setRetryAlarm");
Cursor cursor = PduPersister.getPduPersister(context).getPendingMessages(
Long.MAX_VALUE, phoneId);
Log.d(TAG, "cursor=" + cursor);
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));
// edit by Hao Zhang
// ignore retryAt==0 which should not be alarmed, it may cause
// items which behind retryAt==0 items will not be alarmed
while (retryAt == 0 && cursor.moveToNext()) {
retryAt = cursor.getLong(cursor.getColumnIndexOrThrow(
PendingMessages.DUE_TIME));
}
if (retryAt == 0) {
Log.d(TAG, "All due time is zero, no need to retry");
} else {
//cienet edit yuanman 2011-6-15:
Intent service = null;
if (TelephonyManager.getPhoneCount() > 1 || MessageUtils.isMSMS) {
service = new Intent(TransactionService.ACTION_ONALARM, null, context,
TransactionServiceHelper.getTransactionServiceClass(phoneId));
} else {
service = new Intent(TransactionService.ACTION_ONALARM, null, context,
TransactionService.class);
}
//cienet end yuanman.
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();
}
}
}
}