RetryScheduler.java revision d64419030e1fec1e751695dab3bd7236e2fb0214
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
18package com.android.mms.transaction;
19
20import android.app.AlarmManager;
21import android.app.PendingIntent;
22import android.content.ContentResolver;
23import android.content.ContentUris;
24import android.content.ContentValues;
25import android.content.Context;
26import android.content.Intent;
27import android.database.Cursor;
28import android.database.sqlite.SqliteWrapper;
29import android.net.ConnectivityManager;
30import android.net.NetworkInfo;
31import android.net.Uri;
32import android.provider.Telephony.Mms;
33import android.provider.Telephony.MmsSms;
34import android.provider.Telephony.MmsSms.PendingMessages;
35import android.util.Log;
36
37import com.android.mms.LogTag;
38import com.android.mms.R;
39import com.android.mms.util.DownloadManager;
40import com.google.android.mms.pdu.PduHeaders;
41import com.google.android.mms.pdu.PduPersister;
42
43public 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