diff --git a/src/com/android/email/EmailConnectivityManager.java b/src/com/android/email/EmailConnectivityManager.java new file mode 100644 index 000000000..ca24374b8 --- /dev/null +++ b/src/com/android/email/EmailConnectivityManager.java @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.email; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.net.NetworkInfo.State; +import android.os.Bundle; +import android.os.PowerManager; +import android.os.PowerManager.WakeLock; +import android.util.Log; + +/** + * Encapsulates functionality of ConnectivityManager for use in the Email application. In + * particular, this class provides callbacks for connectivity lost, connectivity restored, and + * background setting changed, as well as providing a method that waits for connectivity + * to be available without holding a wake lock + * + * To use, EmailConnectivityManager mgr = new EmailConnectivityManager(context, "Name"); + * When done, mgr.unregister() to unregister the internal receiver + * + * TODO: Use this class in ExchangeService + */ +public class EmailConnectivityManager extends BroadcastReceiver { + private static final String TAG = "EmailConnectivityManager"; + + // Loop time while waiting (stopgap in case we don't get a broadcast) + private static final int CONNECTIVITY_WAIT_TIME = 10*60*1000; + + // The name of this manager (used for logging) + private final String mName; + // The monitor lock we use while waiting for connectivity + private final Object mLock = new Object(); + // The instantiator's context + private final Context mContext; + // The wake lock used while running (so we don't fall asleep during execution/callbacks) + private final WakeLock mWakeLock; + private final android.net.ConnectivityManager mConnectivityManager; + + // Set when we abort waitForConnectivity() via stopWait + private boolean mStop = false; + // The thread waiting for connectivity + private Thread mWaitThread; + // Whether or not we're registered with the system connectivity manager + private boolean mRegistered = true; + + public EmailConnectivityManager(Context context, String name) { + mContext = context; + mName = name; + mConnectivityManager = + (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE); + PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE); + mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, name); + mContext.registerReceiver(this, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); + } + + public boolean isBackgroundDataAllowed() { + return mConnectivityManager.getBackgroundDataSetting(); + } + + public void stopWait() { + mStop = true; + Thread thread= mWaitThread; + if (thread != null) { + thread.interrupt(); + } + } + + /** + * Called when network connectivity has been restored; this method should be overridden by + * subclasses as necessary. NOTE: CALLED ON UI THREAD + * @param networkType as defined by ConnectivityManager + */ + public void onConnectivityRestored(int networkType) { + } + + /** + * Called when network connectivity has been lost; this method should be overridden by + * subclasses as necessary. NOTE: CALLED ON UI THREAD + * @param networkType as defined by ConnectivityManager + */ + public void onConnectivityLost(int networkType) { + } + + /** + * Called when the user changes the state of the "Background Data" setting; this method should + * be overridden by subclasses as necessary. NOTE: CALLED ON UI THREAD + * @param state the new state of the "Background Data" setting + */ + public void onBackgroundDataChanged(boolean state) { + } + + public void unregister() { + try { + mContext.unregisterReceiver(this); + } catch (RuntimeException e) { + // Don't crash if we didn't register + } finally { + mRegistered = false; + } + } + + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction().equals(ConnectivityManager.CONNECTIVITY_ACTION)) { + Bundle extras = intent.getExtras(); + if (extras != null) { + NetworkInfo networkInfo = + (NetworkInfo)extras.get(ConnectivityManager.EXTRA_NETWORK_INFO); + if (networkInfo == null) return; + State state = networkInfo.getState(); + if (state == State.CONNECTED) { + synchronized (mLock) { + mLock.notifyAll(); + } + onConnectivityRestored(networkInfo.getType()); + } else if (state == State.DISCONNECTED) { + onConnectivityLost(networkInfo.getType()); + } + } + } else if (intent.getAction().equals( + ConnectivityManager.ACTION_BACKGROUND_DATA_SETTING_CHANGED)) { + onBackgroundDataChanged(isBackgroundDataAllowed()); + } + } + + public void waitForConnectivity() { + // If we're unregistered, throw an exception + if (!mRegistered) { + throw new IllegalStateException("ConnectivityManager not registered"); + } + boolean waiting = false; + mWaitThread = Thread.currentThread(); + // Acquire the wait lock while we work + mWakeLock.acquire(); + try { + while (!mStop) { + NetworkInfo info = mConnectivityManager.getActiveNetworkInfo(); + if (info != null) { + // We're done if there's an active network + if (waiting) { + if (Email.DEBUG) { + Log.d(TAG, mName + ": Connectivity wait ended"); + } + } + return; + } else { + if (!waiting) { + if (Email.DEBUG) { + Log.d(TAG, mName + ": Connectivity waiting..."); + } + waiting = true; + } + // Wait until a network is connected (or 10 mins), but let the device sleep + synchronized (mLock) { + // Don't hold a lock during our wait + mWakeLock.release(); + try { + mLock.wait(CONNECTIVITY_WAIT_TIME); + } catch (InterruptedException e) { + // This is fine; we just go around the loop again + } + // Get the lock back and check again for connectivity + mWakeLock.acquire(); + } + } + } + } finally { + // Make sure we always release the wait lock + if (mWakeLock.isHeld()) { + mWakeLock.release(); + } + mWaitThread = null; + } + } +} diff --git a/src/com/android/email/service/AttachmentDownloadService.java b/src/com/android/email/service/AttachmentDownloadService.java index d78fadfd1..ab318159e 100644 --- a/src/com/android/email/service/AttachmentDownloadService.java +++ b/src/com/android/email/service/AttachmentDownloadService.java @@ -19,9 +19,9 @@ package com.android.email.service; import com.android.email.AttachmentInfo; import com.android.email.Controller.ControllerService; import com.android.email.Email; +import com.android.email.EmailConnectivityManager; import com.android.email.ExchangeUtils.NullEmailService; import com.android.email.NotificationController; -import com.android.email.Preferences; import com.android.email.Utility; import com.android.email.provider.AttachmentProvider; import com.android.email.provider.EmailContent; @@ -91,7 +91,8 @@ public class AttachmentDownloadService extends Service implements Runnable { /*package*/ static AttachmentDownloadService sRunningService = null; /*package*/ Context mContext; - private final Preferences mPreferences; + private EmailConnectivityManager mConnectivityManager; + /*package*/ final DownloadSet mDownloadSet = new DownloadSet(new DownloadComparator()); private final HashMap> mAccountServiceMap = @@ -305,6 +306,10 @@ public class AttachmentDownloadService extends Service implements Runnable { if (Email.DEBUG) { Log.d(TAG, "== Checking attachment queue, " + mDownloadSet.size() + " entries"); } + + // Don't run unless/until we have connectivity + mConnectivityManager.waitForConnectivity(); + Iterator iterator = mDownloadSet.descendingIterator(); // First, start up any required downloads, in priority order while (iterator.hasNext() && @@ -324,6 +329,8 @@ public class AttachmentDownloadService extends Service implements Runnable { } } + // Don't prefetch if background downloading is disallowed + if (!mConnectivityManager.isBackgroundDataAllowed()) return; // Then, try opportunistic download of appropriate attachments int backgroundDownloads = MAX_SIMULTANEOUS_DOWNLOADS - mDownloadsInProgress.size(); // Always leave one slot for user requested download @@ -582,10 +589,6 @@ public class AttachmentDownloadService extends Service implements Runnable { } } - public AttachmentDownloadService() { - mPreferences = Preferences.getPreferences(this); - } - /** * Calculate the download priority of an Attachment. A priority of zero means that the * attachment is not marked for download. @@ -884,6 +887,7 @@ public class AttachmentDownloadService extends Service implements Runnable { public void onCreate() { // Start up our service thread new Thread(this, "AttachmentDownloadService").start(); + mConnectivityManager = new EmailConnectivityManager(this, TAG); } @Override public IBinder onBind(Intent intent) { @@ -898,6 +902,7 @@ public class AttachmentDownloadService extends Service implements Runnable { kick(); } sRunningService = null; + mConnectivityManager.unregister(); } @Override