1// Copyright 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5package org.chromium.net;
6
7import android.content.BroadcastReceiver;
8import android.content.Context;
9import android.content.Intent;
10import android.content.IntentFilter;
11import android.net.ConnectivityManager;
12import android.net.wifi.WifiInfo;
13import android.net.wifi.WifiManager;
14import android.telephony.TelephonyManager;
15import android.util.Log;
16
17import org.chromium.base.ApplicationState;
18import org.chromium.base.ApplicationStatus;
19
20/**
21 * Used by the NetworkChangeNotifier to listens to platform changes in connectivity.
22 * Note that use of this class requires that the app have the platform
23 * ACCESS_NETWORK_STATE permission.
24 */
25public class NetworkChangeNotifierAutoDetect extends BroadcastReceiver
26        implements ApplicationStatus.ApplicationStateListener {
27
28    /** Queries the ConnectivityManager for information about the current connection. */
29    static class ConnectivityManagerDelegate {
30        private final ConnectivityManager mConnectivityManager;
31
32        ConnectivityManagerDelegate(Context context) {
33            mConnectivityManager =
34                    (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
35        }
36
37        // For testing.
38        ConnectivityManagerDelegate() {
39            // All the methods below should be overridden.
40            mConnectivityManager = null;
41        }
42
43        boolean activeNetworkExists() {
44            return mConnectivityManager.getActiveNetworkInfo() != null;
45        }
46
47        boolean isConnected() {
48            return mConnectivityManager.getActiveNetworkInfo().isConnected();
49        }
50
51        int getNetworkType() {
52            return mConnectivityManager.getActiveNetworkInfo().getType();
53        }
54
55        int getNetworkSubtype() {
56            return mConnectivityManager.getActiveNetworkInfo().getSubtype();
57        }
58    }
59
60    /** Queries the WifiManager for SSID of the current Wifi connection. */
61    static class WifiManagerDelegate {
62        private final WifiManager mWifiManager;
63
64        WifiManagerDelegate(Context context) {
65            mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
66        }
67
68        // For testing.
69        WifiManagerDelegate() {
70            // All the methods below should be overridden.
71            mWifiManager = null;
72        }
73
74        String getWifiSSID() {
75            WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
76            if (wifiInfo == null)
77                return "";
78            String ssid = wifiInfo.getSSID();
79            return ssid == null ? "" : ssid;
80        }
81    }
82
83    private static final String TAG = "NetworkChangeNotifierAutoDetect";
84
85    private final NetworkConnectivityIntentFilter mIntentFilter =
86            new NetworkConnectivityIntentFilter();
87
88    private final Observer mObserver;
89
90    private final Context mContext;
91    private ConnectivityManagerDelegate mConnectivityManagerDelegate;
92    private WifiManagerDelegate mWifiManagerDelegate;
93    private boolean mRegistered;
94    private int mConnectionType;
95    private String mWifiSSID;
96
97    /**
98     * Observer notified on the UI thread whenever a new connection type was detected.
99     */
100    public static interface Observer {
101        public void onConnectionTypeChanged(int newConnectionType);
102    }
103
104    public NetworkChangeNotifierAutoDetect(Observer observer, Context context) {
105        mObserver = observer;
106        mContext = context.getApplicationContext();
107        mConnectivityManagerDelegate = new ConnectivityManagerDelegate(context);
108        mWifiManagerDelegate = new WifiManagerDelegate(context);
109        mConnectionType = getCurrentConnectionType();
110        mWifiSSID = getCurrentWifiSSID();
111        ApplicationStatus.registerApplicationStateListener(this);
112    }
113
114    /**
115     * Allows overriding the ConnectivityManagerDelegate for tests.
116     */
117    void setConnectivityManagerDelegateForTests(ConnectivityManagerDelegate delegate) {
118        mConnectivityManagerDelegate = delegate;
119    }
120
121    /**
122     * Allows overriding the WifiManagerDelegate for tests.
123     */
124    void setWifiManagerDelegateForTests(WifiManagerDelegate delegate) {
125        mWifiManagerDelegate = delegate;
126    }
127
128    public void destroy() {
129        unregisterReceiver();
130    }
131
132    /**
133     * Register a BroadcastReceiver in the given context.
134     */
135    private void registerReceiver() {
136        if (!mRegistered) {
137            mRegistered = true;
138            mContext.registerReceiver(this, mIntentFilter);
139        }
140    }
141
142    /**
143     * Unregister the BroadcastReceiver in the given context.
144     */
145    private void unregisterReceiver() {
146        if (mRegistered) {
147            mRegistered = false;
148            mContext.unregisterReceiver(this);
149        }
150    }
151
152    public int getCurrentConnectionType() {
153        // Track exactly what type of connection we have.
154        if (!mConnectivityManagerDelegate.activeNetworkExists() ||
155                !mConnectivityManagerDelegate.isConnected()) {
156            return NetworkChangeNotifier.CONNECTION_NONE;
157        }
158
159        switch (mConnectivityManagerDelegate.getNetworkType()) {
160            case ConnectivityManager.TYPE_ETHERNET:
161                return NetworkChangeNotifier.CONNECTION_ETHERNET;
162            case ConnectivityManager.TYPE_WIFI:
163                return NetworkChangeNotifier.CONNECTION_WIFI;
164            case ConnectivityManager.TYPE_WIMAX:
165                return NetworkChangeNotifier.CONNECTION_4G;
166            case ConnectivityManager.TYPE_BLUETOOTH:
167                return NetworkChangeNotifier.CONNECTION_BLUETOOTH;
168            case ConnectivityManager.TYPE_MOBILE:
169                // Use information from TelephonyManager to classify the connection.
170                switch (mConnectivityManagerDelegate.getNetworkSubtype()) {
171                    case TelephonyManager.NETWORK_TYPE_GPRS:
172                    case TelephonyManager.NETWORK_TYPE_EDGE:
173                    case TelephonyManager.NETWORK_TYPE_CDMA:
174                    case TelephonyManager.NETWORK_TYPE_1xRTT:
175                    case TelephonyManager.NETWORK_TYPE_IDEN:
176                        return NetworkChangeNotifier.CONNECTION_2G;
177                    case TelephonyManager.NETWORK_TYPE_UMTS:
178                    case TelephonyManager.NETWORK_TYPE_EVDO_0:
179                    case TelephonyManager.NETWORK_TYPE_EVDO_A:
180                    case TelephonyManager.NETWORK_TYPE_HSDPA:
181                    case TelephonyManager.NETWORK_TYPE_HSUPA:
182                    case TelephonyManager.NETWORK_TYPE_HSPA:
183                    case TelephonyManager.NETWORK_TYPE_EVDO_B:
184                    case TelephonyManager.NETWORK_TYPE_EHRPD:
185                    case TelephonyManager.NETWORK_TYPE_HSPAP:
186                        return NetworkChangeNotifier.CONNECTION_3G;
187                    case TelephonyManager.NETWORK_TYPE_LTE:
188                        return NetworkChangeNotifier.CONNECTION_4G;
189                    default:
190                        return NetworkChangeNotifier.CONNECTION_UNKNOWN;
191                }
192            default:
193                return NetworkChangeNotifier.CONNECTION_UNKNOWN;
194        }
195    }
196
197    private String getCurrentWifiSSID() {
198        if (getCurrentConnectionType() != NetworkChangeNotifier.CONNECTION_WIFI)
199            return "";
200        return mWifiManagerDelegate.getWifiSSID();
201    }
202
203    // BroadcastReceiver
204    @Override
205    public void onReceive(Context context, Intent intent) {
206        connectionTypeChanged();
207    }
208
209    // ApplicationStatus.ApplicationStateListener
210    @Override
211    public void onApplicationStateChange(int newState) {
212        if (newState == ApplicationState.HAS_RUNNING_ACTIVITIES) {
213            connectionTypeChanged();
214            registerReceiver();
215        } else if (newState == ApplicationState.HAS_PAUSED_ACTIVITIES) {
216            unregisterReceiver();
217        }
218    }
219
220    private void connectionTypeChanged() {
221        int newConnectionType = getCurrentConnectionType();
222        String newWifiSSID = getCurrentWifiSSID();
223        if (newConnectionType == mConnectionType && newWifiSSID.equals(mWifiSSID))
224            return;
225
226        mConnectionType = newConnectionType;
227        mWifiSSID = newWifiSSID;
228        Log.d(TAG, "Network connectivity changed, type is: " + mConnectionType);
229        mObserver.onConnectionTypeChanged(newConnectionType);
230    }
231
232    private static class NetworkConnectivityIntentFilter extends IntentFilter {
233        NetworkConnectivityIntentFilter() {
234            addAction(ConnectivityManager.CONNECTIVITY_ACTION);
235        }
236    }
237}
238