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 android.app.AlarmManager;
     21 import android.app.PendingIntent;
     22 import android.content.ContentResolver;
     23 import android.content.ContentUris;
     24 import android.content.ContentValues;
     25 import android.content.Context;
     26 import android.content.Intent;
     27 import android.database.Cursor;
     28 import android.database.sqlite.SqliteWrapper;
     29 import android.net.ConnectivityManager;
     30 import android.net.NetworkInfo;
     31 import android.net.Uri;
     32 import android.provider.Telephony.Mms;
     33 import android.provider.Telephony.MmsSms;
     34 import android.provider.Telephony.MmsSms.PendingMessages;
     35 import android.util.Log;
     36 
     37 import com.android.mms.LogTag;
     38 import com.android.mms.R;
     39 import com.android.mms.util.DownloadManager;
     40 import com.google.android.mms.pdu.PduHeaders;
     41 import com.google.android.mms.pdu.PduPersister;
     42 
     43 public class RetryScheduler implements Observer {
     44     private static final String TAG = "RetryScheduler";
     45     private static final boolean DEBUG = false;
     46     private static final boolean LOCAL_LOGV = false;
     47 
     48     private final Context mContext;
     49     private final ContentResolver mContentResolver;
     50 
     51     private RetryScheduler(Context context) {
     52         mContext = context;
     53         mContentResolver = context.getContentResolver();
     54     }
     55 
     56     private static RetryScheduler sInstance;
     57     public static RetryScheduler getInstance(Context context) {
     58         if (sInstance == null) {
     59             sInstance = new RetryScheduler(context);
     60         }
     61         return sInstance;
     62     }
     63 
     64     private boolean isConnected() {
     65         ConnectivityManager mConnMgr = (ConnectivityManager)
     66                 mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
     67         NetworkInfo ni = mConnMgr.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_MMS);
     68         return (ni == null ? false : ni.isConnected());
     69     }
     70 
     71     public void update(Observable observable) {
     72         try {
     73             Transaction t = (Transaction) observable;
     74 
     75             if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
     76                 Log.v(TAG, "[RetryScheduler] update " + observable);
     77             }
     78 
     79             // We are only supposed to handle M-Notification.ind, M-Send.req
     80             // and M-ReadRec.ind.
     81             if ((t instanceof NotificationTransaction)
     82                     || (t instanceof RetrieveTransaction)
     83                     || (t instanceof ReadRecTransaction)
     84                     || (t instanceof SendTransaction)) {
     85                 try {
     86                     TransactionState state = t.getState();
     87                     if (state.getState() == TransactionState.FAILED) {
     88                         Uri uri = state.getContentUri();
     89                         if (uri != null) {
     90                             scheduleRetry(uri);
     91                         }
     92                     }
     93                 } finally {
     94                     t.detach(this);
     95                 }
     96             }
     97         } finally {
     98             if (isConnected()) {
     99                 setRetryAlarm(mContext);
    100             }
    101         }
    102     }
    103 
    104     private void scheduleRetry(Uri uri) {
    105         long msgId = ContentUris.parseId(uri);
    106 
    107         Uri.Builder uriBuilder = PendingMessages.CONTENT_URI.buildUpon();
    108         uriBuilder.appendQueryParameter("protocol", "mms");
    109         uriBuilder.appendQueryParameter("message", String.valueOf(msgId));
    110 
    111         Cursor cursor = SqliteWrapper.query(mContext, mContentResolver,
    112                 uriBuilder.build(), null, null, null, null);
    113 
    114         if (cursor != null) {
    115             try {
    116                 if ((cursor.getCount() == 1) && cursor.moveToFirst()) {
    117                     int msgType = cursor.getInt(cursor.getColumnIndexOrThrow(
    118                             PendingMessages.MSG_TYPE));
    119 
    120                     int retryIndex = cursor.getInt(cursor.getColumnIndexOrThrow(
    121                             PendingMessages.RETRY_INDEX)) + 1; // Count this time.
    122 
    123                     // TODO Should exactly understand what was happened.
    124                     int errorType = MmsSms.ERR_TYPE_GENERIC;
    125 
    126                     DefaultRetryScheme scheme = new DefaultRetryScheme(mContext, retryIndex);
    127 
    128                     ContentValues values = new ContentValues(4);
    129                     long current = System.currentTimeMillis();
    130                     boolean isRetryDownloading =
    131                             (msgType == PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND);
    132                     boolean retry = true;
    133                     int respStatus = getResponseStatus(msgId);
    134                     int errorString = 0;
    135                     if (!isRetryDownloading) {
    136                         // Send Transaction case
    137                         switch (respStatus) {
    138                             case PduHeaders.RESPONSE_STATUS_ERROR_SENDING_ADDRESS_UNRESOLVED:
    139                                 errorString = R.string.invalid_destination;
    140                                 break;
    141                             case PduHeaders.RESPONSE_STATUS_ERROR_SERVICE_DENIED:
    142                             case PduHeaders.RESPONSE_STATUS_ERROR_PERMANENT_SERVICE_DENIED:
    143                                 errorString = R.string.service_not_activated;
    144                                 break;
    145                             case PduHeaders.RESPONSE_STATUS_ERROR_NETWORK_PROBLEM:
    146                                 errorString = R.string.service_network_problem;
    147                                 break;
    148                             case PduHeaders.RESPONSE_STATUS_ERROR_TRANSIENT_MESSAGE_NOT_FOUND:
    149                             case PduHeaders.RESPONSE_STATUS_ERROR_PERMANENT_MESSAGE_NOT_FOUND:
    150                                 errorString = R.string.service_message_not_found;
    151                                 break;
    152                         }
    153                         if (errorString != 0) {
    154                             DownloadManager.getInstance().showErrorCodeToast(errorString);
    155                             retry = false;
    156                         }
    157                     } else {
    158                         // apply R880 IOT issue (Conformance 11.6 Retrieve Invalid Message)
    159                         // Notification Transaction case
    160                         respStatus = getRetrieveStatus(msgId);
    161                         if (respStatus ==
    162                                 PduHeaders.RESPONSE_STATUS_ERROR_PERMANENT_MESSAGE_NOT_FOUND) {
    163                             DownloadManager.getInstance().showErrorCodeToast(
    164                                     R.string.service_message_not_found);
    165                             SqliteWrapper.delete(mContext, mContext.getContentResolver(), uri,
    166                                     null, null);
    167                             retry = false;
    168                             return;
    169                         }
    170                     }
    171                     if ((retryIndex < scheme.getRetryLimit()) && retry) {
    172                         long retryAt = current + scheme.getWaitingInterval();
    173 
    174                         if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
    175                             Log.v(TAG, "scheduleRetry: retry for " + uri + " is scheduled at "
    176                                     + (retryAt - System.currentTimeMillis()) + "ms from now");
    177                         }
    178 
    179                         values.put(PendingMessages.DUE_TIME, retryAt);
    180 
    181                         if (isRetryDownloading) {
    182                             // Downloading process is transiently failed.
    183                             DownloadManager.getInstance().markState(
    184                                     uri, DownloadManager.STATE_TRANSIENT_FAILURE);
    185                         }
    186                     } else {
    187                         errorType = MmsSms.ERR_TYPE_GENERIC_PERMANENT;
    188                         if (isRetryDownloading) {
    189                             Cursor c = SqliteWrapper.query(mContext, mContext.getContentResolver(), uri,
    190                                     new String[] { Mms.THREAD_ID }, null, null, null);
    191 
    192                             long threadId = -1;
    193                             if (c != null) {
    194                                 try {
    195                                     if (c.moveToFirst()) {
    196                                         threadId = c.getLong(0);
    197                                     }
    198                                 } finally {
    199                                     c.close();
    200                                 }
    201                             }
    202 
    203                             if (threadId != -1) {
    204                                 // Downloading process is permanently failed.
    205                                 MessagingNotification.notifyDownloadFailed(mContext, threadId);
    206                             }
    207 
    208                             DownloadManager.getInstance().markState(
    209                                     uri, DownloadManager.STATE_PERMANENT_FAILURE);
    210                         } else {
    211                             // Mark the failed message as unread.
    212                             ContentValues readValues = new ContentValues(1);
    213                             readValues.put(Mms.READ, 0);
    214                             SqliteWrapper.update(mContext, mContext.getContentResolver(),
    215                                     uri, readValues, null, null);
    216                             MessagingNotification.notifySendFailed(mContext, true);
    217                         }
    218                     }
    219 
    220                     values.put(PendingMessages.ERROR_TYPE,  errorType);
    221                     values.put(PendingMessages.RETRY_INDEX, retryIndex);
    222                     values.put(PendingMessages.LAST_TRY,    current);
    223 
    224                     int columnIndex = cursor.getColumnIndexOrThrow(
    225                             PendingMessages._ID);
    226                     long id = cursor.getLong(columnIndex);
    227                     SqliteWrapper.update(mContext, mContentResolver,
    228                             PendingMessages.CONTENT_URI,
    229                             values, PendingMessages._ID + "=" + id, null);
    230                 } else if (LOCAL_LOGV) {
    231                     Log.v(TAG, "Cannot found correct pending status for: " + msgId);
    232                 }
    233             } finally {
    234                 cursor.close();
    235             }
    236         }
    237     }
    238 
    239     private int getResponseStatus(long msgID) {
    240         int respStatus = 0;
    241         Cursor cursor = SqliteWrapper.query(mContext, mContentResolver,
    242                 Mms.Outbox.CONTENT_URI, null, Mms._ID + "=" + msgID, null, null);
    243         try {
    244             if (cursor.moveToFirst()) {
    245                 respStatus = cursor.getInt(cursor.getColumnIndexOrThrow(Mms.RESPONSE_STATUS));
    246             }
    247         } finally {
    248             cursor.close();
    249         }
    250         if (respStatus != 0) {
    251             Log.e(TAG, "Response status is: " + respStatus);
    252         }
    253         return respStatus;
    254     }
    255 
    256     // apply R880 IOT issue (Conformance 11.6 Retrieve Invalid Message)
    257     private int getRetrieveStatus(long msgID) {
    258         int retrieveStatus = 0;
    259         Cursor cursor = SqliteWrapper.query(mContext, mContentResolver,
    260                 Mms.Inbox.CONTENT_URI, null, Mms._ID + "=" + msgID, null, null);
    261         try {
    262             if (cursor.moveToFirst()) {
    263                 retrieveStatus = cursor.getInt(cursor.getColumnIndexOrThrow(
    264                             Mms.RESPONSE_STATUS));
    265             }
    266         } finally {
    267             cursor.close();
    268         }
    269         if (retrieveStatus != 0) {
    270             if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
    271                 Log.v(TAG, "Retrieve status is: " + retrieveStatus);
    272             }
    273         }
    274         return retrieveStatus;
    275     }
    276 
    277     public static void setRetryAlarm(Context context) {
    278         Cursor cursor = PduPersister.getPduPersister(context).getPendingMessages(
    279                 Long.MAX_VALUE);
    280         if (cursor != null) {
    281             try {
    282                 if (cursor.moveToFirst()) {
    283                     // The result of getPendingMessages() is order by due time.
    284                     long retryAt = cursor.getLong(cursor.getColumnIndexOrThrow(
    285                             PendingMessages.DUE_TIME));
    286 
    287                     Intent service = new Intent(TransactionService.ACTION_ONALARM,
    288                                         null, context, TransactionService.class);
    289                     PendingIntent operation = PendingIntent.getService(
    290                             context, 0, service, PendingIntent.FLAG_ONE_SHOT);
    291                     AlarmManager am = (AlarmManager) context.getSystemService(
    292                             Context.ALARM_SERVICE);
    293                     am.set(AlarmManager.RTC, retryAt, operation);
    294 
    295                     if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
    296                         Log.v(TAG, "Next retry is scheduled at"
    297                                 + (retryAt - System.currentTimeMillis()) + "ms from now");
    298                     }
    299                 }
    300             } finally {
    301                 cursor.close();
    302             }
    303         }
    304     }
    305 }
    306