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