1package com.android.email.provider;
2
3import com.android.mail.providers.UIProvider;
4import com.android.mail.utils.LogTag;
5import com.android.mail.utils.LogUtils;
6import com.android.mail.utils.StorageLowState;
7
8import android.content.Context;
9import android.net.ConnectivityManager;
10import android.net.NetworkInfo;
11import android.os.Handler;
12import android.text.format.DateUtils;
13
14import java.util.HashMap;
15import 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 */
24public 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
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
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
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
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
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
99        RemoveRefreshStatusRunnable(long mailboxId, Callback callback) {
100            mMailboxId = mailboxId;
101            mCallback = callback;
102        }
103
104        @Override
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 {
156        void onRefreshCompleted(long mailboxId, int result);
157        void onTimeout(long mailboxId);
158    }
159}
160