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                     if (!isRetryDownloading) {
    138                         // Send Transaction case
    139                         switch (respStatus) {
    140                             case PduHeaders.RESPONSE_STATUS_ERROR_SENDING_ADDRESS_UNRESOLVED:
    141                                 errorString = R.string.invalid_destination;
    142                                 break;
    143                             case PduHeaders.RESPONSE_STATUS_ERROR_SERVICE_DENIED:
    144                             case PduHeaders.RESPONSE_STATUS_ERROR_PERMANENT_SERVICE_DENIED:
    145                                 errorString = R.string.service_not_activated;
    146                                 break;
    147                             case PduHeaders.RESPONSE_STATUS_ERROR_NETWORK_PROBLEM:
    148                                 errorString = R.string.service_network_problem;
    149                                 break;
    150                             case PduHeaders.RESPONSE_STATUS_ERROR_TRANSIENT_MESSAGE_NOT_FOUND:
    151                             case PduHeaders.RESPONSE_STATUS_ERROR_PERMANENT_MESSAGE_NOT_FOUND:
    152                                 errorString = R.string.service_message_not_found;
    153                                 break;
    154                         }
    155                         if (errorString != 0) {
    156                             DownloadManager.getInstance().showErrorCodeToast(errorString);
    157                             retry = false;
    158                         }
    159                     } else {
    160                         // apply R880 IOT issue (Conformance 11.6 Retrieve Invalid Message)
    161                         // Notification Transaction case
    162                         respStatus = getRetrieveStatus(msgId);
    163                         if (respStatus ==
    164                                 PduHeaders.RESPONSE_STATUS_ERROR_PERMANENT_MESSAGE_NOT_FOUND) {
    165                             DownloadManager.getInstance().showErrorCodeToast(
    166                                     R.string.service_message_not_found);
    167                             SqliteWrapper.delete(mContext, mContext.getContentResolver(), uri,
    168                                     null, null);
    169                             retry = false;
    170                             return;
    171                         }
    172                     }
    173                     if ((retryIndex < scheme.getRetryLimit()) && retry) {
    174                         long retryAt = current + scheme.getWaitingInterval();
    175 
    176                         if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
    177                             Log.v(TAG, "scheduleRetry: retry for " + uri + " is scheduled at "
    178                                     + (retryAt - System.currentTimeMillis()) + "ms from now");
    179                         }
    180 
    181                         values.put(PendingMessages.DUE_TIME, retryAt);
    182 
    183                         if (isRetryDownloading) {
    184                             // Downloading process is transiently failed.
    185                             DownloadManager.getInstance().markState(
    186                                     uri, DownloadManager.STATE_TRANSIENT_FAILURE);
    187                         }
    188                     } else {
    189                         errorType = MmsSms.ERR_TYPE_GENERIC_PERMANENT;
    190                         if (isRetryDownloading) {
    191                             Cursor c = SqliteWrapper.query(mContext, mContext.getContentResolver(), uri,
    192                                     new String[] { Mms.THREAD_ID }, null, null, null);
    193 
    194                             long threadId = -1;
    195                             if (c != null) {
    196                                 try {
    197                                     if (c.moveToFirst()) {
    198                                         threadId = c.getLong(0);
    199                                     }
    200                                 } finally {
    201                                     c.close();
    202                                 }
    203                             }
    204 
    205                             if (threadId != -1) {
    206                                 // Downloading process is permanently failed.
    207                                 MessagingNotification.notifyDownloadFailed(mContext, threadId);
    208                             }
    209 
    210                             DownloadManager.getInstance().markState(
    211                                     uri, DownloadManager.STATE_PERMANENT_FAILURE);
    212                         } else {
    213                             // Mark the failed message as unread.
    214                             ContentValues readValues = new ContentValues(1);
    215                             readValues.put(Mms.READ, 0);
    216                             SqliteWrapper.update(mContext, mContext.getContentResolver(),
    217                                     uri, readValues, null, null);
    218                             MessagingNotification.notifySendFailed(mContext, true);
    219                         }
    220                     }
    221 
    222                     values.put(PendingMessages.ERROR_TYPE,  errorType);
    223                     values.put(PendingMessages.RETRY_INDEX, retryIndex);
    224                     values.put(PendingMessages.LAST_TRY,    current);
    225 
    226                     int columnIndex = cursor.getColumnIndexOrThrow(
    227                             PendingMessages._ID);
    228                     long id = cursor.getLong(columnIndex);
    229                     SqliteWrapper.update(mContext, mContentResolver,
    230                             PendingMessages.CONTENT_URI,
    231                             values, PendingMessages._ID + "=" + id, null);
    232                 } else if (LOCAL_LOGV) {
    233                     Log.v(TAG, "Cannot found correct pending status for: " + msgId);
    234                 }
    235             } finally {
    236                 cursor.close();
    237             }
    238         }
    239     }
    240 
    241     private int getResponseStatus(long msgID) {
    242         int respStatus = 0;
    243         Cursor cursor = SqliteWrapper.query(mContext, mContentResolver,
    244                 Mms.Outbox.CONTENT_URI, null, Mms._ID + "=" + msgID, null, null);
    245         try {
    246             if (cursor.moveToFirst()) {
    247                 respStatus = cursor.getInt(cursor.getColumnIndexOrThrow(Mms.RESPONSE_STATUS));
    248             }
    249         } finally {
    250             cursor.close();
    251         }
    252         if (respStatus != 0) {
    253             Log.e(TAG, "Response status is: " + respStatus);
    254         }
    255         return respStatus;
    256     }
    257 
    258     // apply R880 IOT issue (Conformance 11.6 Retrieve Invalid Message)
    259     private int getRetrieveStatus(long msgID) {
    260         int retrieveStatus = 0;
    261         Cursor cursor = SqliteWrapper.query(mContext, mContentResolver,
    262                 Mms.Inbox.CONTENT_URI, null, Mms._ID + "=" + msgID, null, null);
    263         try {
    264             if (cursor.moveToFirst()) {
    265                 retrieveStatus = cursor.getInt(cursor.getColumnIndexOrThrow(
    266                             Mms.RESPONSE_STATUS));
    267             }
    268         } finally {
    269             cursor.close();
    270         }
    271         if (retrieveStatus != 0) {
    272             if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
    273                 Log.v(TAG, "Retrieve status is: " + retrieveStatus);
    274             }
    275         }
    276         return retrieveStatus;
    277     }
    278 
    279     public static void setRetryAlarm(Context context) {
    280         Cursor cursor = PduPersister.getPduPersister(context).getPendingMessages(
    281                 Long.MAX_VALUE);
    282         if (cursor != null) {
    283             try {
    284                 if (cursor.moveToFirst()) {
    285                     // The result of getPendingMessages() is order by due time.
    286                     long retryAt = cursor.getLong(cursor.getColumnIndexOrThrow(
    287                             PendingMessages.DUE_TIME));
    288 
    289                     Intent service = new Intent(TransactionService.ACTION_ONALARM,
    290                                         null, context, TransactionService.class);
    291                     PendingIntent operation = PendingIntent.getService(
    292                             context, 0, service, PendingIntent.FLAG_ONE_SHOT);
    293                     AlarmManager am = (AlarmManager) context.getSystemService(
    294                             Context.ALARM_SERVICE);
    295                     am.set(AlarmManager.RTC, retryAt, operation);
    296 
    297                     if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
    298                         Log.v(TAG, "Next retry is scheduled at"
    299                                 + (retryAt - System.currentTimeMillis()) + "ms from now");
    300                     }
    301                 }
    302             } finally {
    303                 cursor.close();
    304             }
    305         }
    306     }
    307 }
    308