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.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