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