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