1/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.email;
18
19import android.content.BroadcastReceiver;
20import android.content.ContentResolver;
21import android.content.Context;
22import android.content.Intent;
23import android.content.IntentFilter;
24import android.net.ConnectivityManager;
25import android.net.NetworkInfo;
26import android.net.NetworkInfo.State;
27import android.os.Bundle;
28import android.os.PowerManager;
29import android.os.PowerManager.WakeLock;
30
31import com.android.mail.utils.LogUtils;
32
33/**
34 * Encapsulates functionality of ConnectivityManager for use in the Email application.  In
35 * particular, this class provides callbacks for connectivity lost, connectivity restored, and
36 * background setting changed, as well as providing a method that waits for connectivity
37 * to be available without holding a wake lock
38 *
39 * To use, EmailConnectivityManager mgr = new EmailConnectivityManager(context, "Name");
40 * When done, mgr.unregister() to unregister the internal receiver
41 *
42 * TODO: Use this class in ExchangeService
43 */
44public class EmailConnectivityManager extends BroadcastReceiver {
45    private static final String TAG = "EmailConnectivityMgr";
46
47    // Loop time while waiting (stopgap in case we don't get a broadcast)
48    private static final int CONNECTIVITY_WAIT_TIME = 10*60*1000;
49
50    // Sentinel value for "no active network"
51    public static final int NO_ACTIVE_NETWORK = -1;
52
53    // The name of this manager (used for logging)
54    private final String mName;
55    // The monitor lock we use while waiting for connectivity
56    private final Object mLock = new Object();
57    // The instantiator's context
58    private final Context mContext;
59    // The wake lock used while running (so we don't fall asleep during execution/callbacks)
60    private final WakeLock mWakeLock;
61    private final android.net.ConnectivityManager mConnectivityManager;
62
63    // Set when we abort waitForConnectivity() via stopWait
64    private boolean mStop = false;
65    // The thread waiting for connectivity
66    private Thread mWaitThread;
67    // Whether or not we're registered with the system connectivity manager
68    private boolean mRegistered = true;
69
70    public EmailConnectivityManager(Context context, String name)  {
71        mContext = context;
72        mName = name;
73        mConnectivityManager =
74            (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
75        PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
76        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, name);
77        mContext.registerReceiver(this, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
78    }
79
80    public boolean isAutoSyncAllowed() {
81        return ContentResolver.getMasterSyncAutomatically();
82    }
83
84    public void stopWait() {
85        mStop = true;
86        Thread thread= mWaitThread;
87        if (thread != null) {
88            thread.interrupt();
89        }
90    }
91
92    /**
93     * Called when network connectivity has been restored; this method should be overridden by
94     * subclasses as necessary. NOTE: CALLED ON UI THREAD
95     * @param networkType as defined by ConnectivityManager
96     */
97    public void onConnectivityRestored(int networkType) {
98    }
99
100    /**
101     * Called when network connectivity has been lost; this method should be overridden by
102     * subclasses as necessary. NOTE: CALLED ON UI THREAD
103     * @param networkType as defined by ConnectivityManager
104     */
105    public void onConnectivityLost(int networkType) {
106    }
107
108    public void unregister() {
109        try {
110            mContext.unregisterReceiver(this);
111        } catch (RuntimeException e) {
112            // Don't crash if we didn't register
113        } finally {
114            mRegistered = false;
115        }
116    }
117
118    @Override
119    public void onReceive(Context context, Intent intent) {
120        if (intent.getAction().equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
121            Bundle extras = intent.getExtras();
122            if (extras != null) {
123                NetworkInfo networkInfo =
124                    (NetworkInfo)extras.get(ConnectivityManager.EXTRA_NETWORK_INFO);
125                if (networkInfo == null) return;
126                State state = networkInfo.getState();
127                if (state == State.CONNECTED) {
128                    synchronized (mLock) {
129                        mLock.notifyAll();
130                    }
131                    onConnectivityRestored(networkInfo.getType());
132                } else if (state == State.DISCONNECTED) {
133                    onConnectivityLost(networkInfo.getType());
134                }
135            }
136        }
137    }
138
139    /**
140     * Request current connectivity status
141     * @return whether there is connectivity at this time
142     */
143    public boolean hasConnectivity() {
144        NetworkInfo info = mConnectivityManager.getActiveNetworkInfo();
145        return (info != null);
146    }
147
148    /**
149     * Get the type of the currently active data network
150     * @return the type of the active network (or NO_ACTIVE_NETWORK)
151     */
152    public int getActiveNetworkType() {
153        return getActiveNetworkType(mConnectivityManager);
154    }
155
156    static public int getActiveNetworkType(Context context) {
157        ConnectivityManager cm =
158            (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
159        return getActiveNetworkType(cm);
160    }
161
162    static public int getActiveNetworkType(ConnectivityManager cm) {
163        NetworkInfo info = cm.getActiveNetworkInfo();
164        if (info == null) return NO_ACTIVE_NETWORK;
165        return info.getType();
166    }
167
168    public void waitForConnectivity() {
169        // If we're unregistered, throw an exception
170        if (!mRegistered) {
171            throw new IllegalStateException("ConnectivityManager not registered");
172        }
173        boolean waiting = false;
174        mWaitThread = Thread.currentThread();
175        // Acquire the wait lock while we work
176        mWakeLock.acquire();
177        try {
178            while (!mStop) {
179                NetworkInfo info = mConnectivityManager.getActiveNetworkInfo();
180                if (info != null) {
181                    // We're done if there's an active network
182                    if (waiting) {
183                        if (DebugUtils.DEBUG) {
184                            LogUtils.d(TAG, mName + ": Connectivity wait ended");
185                        }
186                    }
187                    return;
188                } else {
189                    if (!waiting) {
190                        if (DebugUtils.DEBUG) {
191                            LogUtils.d(TAG, mName + ": Connectivity waiting...");
192                        }
193                        waiting = true;
194                    }
195                    // Wait until a network is connected (or 10 mins), but let the device sleep
196                    synchronized (mLock) {
197                        // Don't hold a lock during our wait
198                        mWakeLock.release();
199                        try {
200                            mLock.wait(CONNECTIVITY_WAIT_TIME);
201                        } catch (InterruptedException e) {
202                            // This is fine; we just go around the loop again
203                        }
204                        // Get the lock back and check again for connectivity
205                        mWakeLock.acquire();
206                    }
207                }
208            }
209        } finally {
210            // Make sure we always release the wait lock
211            if (mWakeLock.isHeld()) {
212                mWakeLock.release();
213            }
214            mWaitThread = null;
215        }
216    }
217}
218