Home | History | Annotate | Download | only in transaction
      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