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