1 package com.android.email.provider;
2 
3 import com.android.mail.providers.UIProvider;
4 import com.android.mail.utils.LogTag;
5 import com.android.mail.utils.LogUtils;
6 import com.android.mail.utils.StorageLowState;
7 
8 import android.content.Context;
9 import android.net.ConnectivityManager;
10 import android.net.NetworkInfo;
11 import android.os.Handler;
12 import android.text.format.DateUtils;
13 
14 import java.util.HashMap;
15 import java.util.Map;
16 
17 /**
18  * This class implements a singleton that monitors a mailbox refresh activated by the user.
19  * The refresh requests a sync but sometimes the sync doesn't happen till much later. This class
20  * checks if a sync has been started for a specific mailbox. It checks for no network connectivity
21  * and low storage conditions which prevent a sync and notifies the the caller using a callback.
22  * If no sync is started after a certain timeout, it gives up and notifies the caller.
23  */
24 public class RefreshStatusMonitor {
25     private static final String TAG = LogTag.getLogTag();
26 
27     private static final int REMOVE_REFRESH_STATUS_DELAY_MS = 250;
28     public static final long REMOVE_REFRESH_TIMEOUT_MS = DateUtils.MINUTE_IN_MILLIS;
29     private static final int MAX_RETRY =
30             (int) (REMOVE_REFRESH_TIMEOUT_MS / REMOVE_REFRESH_STATUS_DELAY_MS);
31 
32     private static RefreshStatusMonitor sInstance = null;
33     private final Handler mHandler;
34     private boolean mIsStorageLow = false;
35     private final Map<Long, Boolean> mMailboxSync = new HashMap<Long, Boolean>();
36 
37     private final Context mContext;
38 
getInstance(Context context)39     public static RefreshStatusMonitor getInstance(Context context) {
40         synchronized (RefreshStatusMonitor.class) {
41             if (sInstance == null) {
42                 sInstance = new RefreshStatusMonitor(context.getApplicationContext());
43             }
44         }
45         return sInstance;
46     }
47 
RefreshStatusMonitor(Context context)48     private RefreshStatusMonitor(Context context) {
49         mContext = context;
50         mHandler = new Handler(mContext.getMainLooper());
51         StorageLowState.registerHandler(new StorageLowState
52                     .LowStorageHandler() {
53                 @Override
54                 public void onStorageLow() {
55                     mIsStorageLow = true;
56                 }
57 
58                 @Override
59                 public void onStorageOk() {
60                     mIsStorageLow = false;
61                 }
62             });
63     }
64 
monitorRefreshStatus(long mailboxId, Callback callback)65     public void monitorRefreshStatus(long mailboxId, Callback callback) {
66         synchronized (mMailboxSync) {
67             if (!mMailboxSync.containsKey(mailboxId))
68                 mMailboxSync.put(mailboxId, false);
69                 mHandler.postDelayed(
70                         new RemoveRefreshStatusRunnable(mailboxId, callback),
71                         REMOVE_REFRESH_STATUS_DELAY_MS);
72         }
73     }
74 
setSyncStarted(long mailboxId)75     public void setSyncStarted(long mailboxId) {
76         synchronized (mMailboxSync) {
77             // only if we're tracking this mailbox
78             if (mMailboxSync.containsKey(mailboxId)) {
79                 LogUtils.d(TAG, "RefreshStatusMonitor: setSyncStarted: mailboxId=%d", mailboxId);
80                 mMailboxSync.put(mailboxId, true);
81             }
82         }
83     }
84 
isConnected()85     private boolean isConnected() {
86         final ConnectivityManager connectivityManager =
87                 ((ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE));
88         final NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
89         return (networkInfo != null) && networkInfo.isConnected();
90     }
91 
92     private class RemoveRefreshStatusRunnable implements Runnable {
93         private final long mMailboxId;
94         private final Callback mCallback;
95 
96         private int mNumRetries = 0;
97 
98 
RemoveRefreshStatusRunnable(long mailboxId, Callback callback)99         RemoveRefreshStatusRunnable(long mailboxId, Callback callback) {
100             mMailboxId = mailboxId;
101             mCallback = callback;
102         }
103 
104         @Override
run()105         public void run() {
106             synchronized (mMailboxSync) {
107                 final Boolean isSyncRunning = mMailboxSync.get(mMailboxId);
108                 if (Boolean.FALSE.equals(isSyncRunning)) {
109                     if (mIsStorageLow) {
110                         LogUtils.d(TAG, "RefreshStatusMonitor: mailboxId=%d LOW STORAGE",
111                                 mMailboxId);
112                         // The device storage is low and sync will never succeed.
113                         mCallback.onRefreshCompleted(
114                                 mMailboxId, UIProvider.LastSyncResult.STORAGE_ERROR);
115                         mMailboxSync.remove(mMailboxId);
116                     } else if (!isConnected()) {
117                         LogUtils.d(TAG, "RefreshStatusMonitor: mailboxId=%d NOT CONNECTED",
118                                 mMailboxId);
119                         // The device is not connected to the Internet. A sync will never succeed.
120                         mCallback.onRefreshCompleted(
121                                 mMailboxId, UIProvider.LastSyncResult.CONNECTION_ERROR);
122                         mMailboxSync.remove(mMailboxId);
123                     } else {
124                         // The device is connected to the Internet. It might take a short while for
125                         // the sync manager to initiate our sync, so let's post this runnable again
126                         // and hope that we have started syncing by then.
127                         mNumRetries++;
128                         LogUtils.d(TAG, "RefreshStatusMonitor: mailboxId=%d Retry %d",
129                                 mMailboxId, mNumRetries);
130                         if (mNumRetries > MAX_RETRY) {
131                             LogUtils.d(TAG, "RefreshStatusMonitor: mailboxId=%d TIMEOUT",
132                                     mMailboxId);
133                             // Hide the sync status bar if it's been a while since sync was
134                             // requested and still hasn't started.
135                             mMailboxSync.remove(mMailboxId);
136                             mCallback.onTimeout(mMailboxId);
137                             // TODO: Displaying a user friendly message in addition.
138                         } else {
139                             mHandler.postDelayed(this, REMOVE_REFRESH_STATUS_DELAY_MS);
140                         }
141                     }
142                 } else {
143                     // Some sync is currently in progress. We're done
144                     LogUtils.d(TAG, "RefreshStatusMonitor: mailboxId=%d SYNC DETECTED", mMailboxId);
145                     // it's not quite a success yet, the sync just started but we need to clear the
146                     // error so the retry bar goes away.
147                     mCallback.onRefreshCompleted(
148                             mMailboxId, UIProvider.LastSyncResult.SUCCESS);
149                     mMailboxSync.remove(mMailboxId);
150                 }
151             }
152         }
153     }
154 
155     public interface Callback {
onRefreshCompleted(long mailboxId, int result)156         void onRefreshCompleted(long mailboxId, int result);
onTimeout(long mailboxId)157         void onTimeout(long mailboxId);
158     }
159 }
160