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