• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
RetryScheduler(Context context)53     private RetryScheduler(Context context) {
54         mContext = context;
55         mContentResolver = context.getContentResolver();
56     }
57 
58     private static RetryScheduler sInstance;
getInstance(Context context)59     public static RetryScheduler getInstance(Context context) {
60         if (sInstance == null) {
61             sInstance = new RetryScheduler(context);
62         }
63         return sInstance;
64     }
65 
isConnected()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 
update(Observable observable)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 
scheduleRetry(Uri uri)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 
getResponseStatus(long msgID)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)
getRetrieveStatus(long msgID)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 
setRetryAlarm(Context context)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