1 /*
2  * Copyright (C) 2021 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 
17 package com.android.settings.network;
18 
19 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
20 import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
21 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
22 
23 import static androidx.lifecycle.Lifecycle.Event.ON_DESTROY;
24 import static androidx.lifecycle.Lifecycle.Event.ON_PAUSE;
25 import static androidx.lifecycle.Lifecycle.Event.ON_RESUME;
26 
27 import android.content.BroadcastReceiver;
28 import android.content.Context;
29 import android.content.Intent;
30 import android.content.IntentFilter;
31 import android.net.ConnectivityManager;
32 import android.net.ConnectivityManager.NetworkCallback;
33 import android.net.Network;
34 import android.net.NetworkCapabilities;
35 import android.net.wifi.WifiInfo;
36 import android.net.wifi.WifiManager;
37 import android.util.Log;
38 
39 import androidx.annotation.NonNull;
40 import androidx.annotation.VisibleForTesting;
41 import androidx.lifecycle.Lifecycle;
42 import androidx.lifecycle.LifecycleObserver;
43 import androidx.lifecycle.OnLifecycleEvent;
44 
45 import com.android.settings.AirplaneModeEnabler;
46 
47 import java.lang.annotation.Retention;
48 import java.lang.annotation.RetentionPolicy;
49 import java.util.HashMap;
50 import java.util.Map;
51 
52 /**
53  * Helper class to update the internet type for connected network preference
54  */
55 public class InternetUpdater implements AirplaneModeEnabler.OnAirplaneModeChangedListener,
56         LifecycleObserver {
57 
58     private static final String TAG = "InternetUpdater";
59 
60     private InternetChangeListener mListener;
61 
62     /** Interface that handles the internet updater callback */
63     public interface InternetChangeListener {
64         /**
65          * Called when internet type is changed.
66          *
67          * @param internetType the internet type
68          */
onInternetTypeChanged(@nternetType int internetType)69         default void onInternetTypeChanged(@InternetType int internetType) {};
70 
71         /**
72          * Called when airplane mode state is changed.
73          */
onAirplaneModeChanged(boolean isAirplaneModeOn)74         default void onAirplaneModeChanged(boolean isAirplaneModeOn) {};
75 
76         /**
77          * Called when Wi-Fi enabled is changed.
78          */
onWifiEnabledChanged(boolean enabled)79         default void onWifiEnabledChanged(boolean enabled) {};
80     }
81 
82     /**
83      * Indicates the internet is off when airplane mode is on.
84      */
85     public static final int INTERNET_OFF = 0;
86 
87     /**
88      * Indicates this internet is not connected (includes no networks connected) or network(s)
89      * available.
90      *
91      * Examples include:
92      * <p>When airplane mode is turned off, and some networks (Wi-Fi, Mobile-data) are turned on,
93      * but no network can access the Internet.
94      *
95      * <p>When the airplane mode is turned on, and the WiFi is also turned on, but the WiFi is not
96      * connected or cannot access the Internet.
97      */
98     public static final int INTERNET_NETWORKS_AVAILABLE = 1;
99 
100     /**
101      * Indicates this internet uses a Wi-Fi network type.
102      */
103     public static final int INTERNET_WIFI = 2;
104 
105     /**
106      * Indicates this internet uses a Cellular network type.
107      */
108     public static final int INTERNET_CELLULAR = 3;
109 
110     /**
111      * Indicates this internet uses a Ethernet network type.
112      */
113     public static final int INTERNET_ETHERNET = 4;
114 
115     @Retention(RetentionPolicy.SOURCE)
116     @android.annotation.IntDef(prefix = { "INTERNET_" }, value = {
117             INTERNET_OFF,
118             INTERNET_NETWORKS_AVAILABLE,
119             INTERNET_WIFI,
120             INTERNET_CELLULAR,
121             INTERNET_ETHERNET,
122     })
123     public @interface InternetType { }
124     private @InternetType int mInternetType;
125 
126     private final Context mContext;
127     private final ConnectivityManager mConnectivityManager;
128     private final WifiManager mWifiManager;
129     private final IntentFilter mWifiStateFilter;
130     @VisibleForTesting
131     AirplaneModeEnabler mAirplaneModeEnabler;
132 
133     @VisibleForTesting
134     boolean mInternetAvailable;
135     @VisibleForTesting
136     int mTransport;
137     private static Map<Integer, Integer> sTransportMap = new HashMap<>();
138     static {
sTransportMap.put(TRANSPORT_WIFI, INTERNET_WIFI)139         sTransportMap.put(TRANSPORT_WIFI, INTERNET_WIFI);
sTransportMap.put(TRANSPORT_CELLULAR, INTERNET_CELLULAR)140         sTransportMap.put(TRANSPORT_CELLULAR, INTERNET_CELLULAR);
sTransportMap.put(TRANSPORT_ETHERNET, INTERNET_ETHERNET)141         sTransportMap.put(TRANSPORT_ETHERNET, INTERNET_ETHERNET);
142     }
143 
144     private NetworkCallback mNetworkCallback = new NetworkCallback() {
145         public void onCapabilitiesChanged(@NonNull Network network,
146                 @NonNull NetworkCapabilities networkCapabilities) {
147             updateInternetAvailable(networkCapabilities);
148         }
149 
150         @Override
151         public void onLost(@NonNull Network network) {
152             mInternetAvailable = false;
153             updateInternetType();
154         }
155     };
156 
157     private final BroadcastReceiver mWifiStateReceiver = new BroadcastReceiver() {
158         @Override
159         public void onReceive(Context context, Intent intent) {
160             fetchActiveNetwork();
161             if (mListener != null) {
162                 mListener.onWifiEnabledChanged(mWifiManager.isWifiEnabled());
163             }
164         }
165     };
166 
InternetUpdater(Context context, Lifecycle lifecycle, InternetChangeListener listener)167     public InternetUpdater(Context context, Lifecycle lifecycle, InternetChangeListener listener) {
168         mContext = context;
169         mAirplaneModeEnabler = new AirplaneModeEnabler(mContext, this);
170         mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
171         mWifiManager = mContext.getSystemService(WifiManager.class);
172         mWifiStateFilter = new IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION);
173         mListener = listener;
174         fetchActiveNetwork();
175         if (lifecycle != null) {
176             lifecycle.addObserver(this);
177         }
178     }
179 
180     /** @OnLifecycleEvent(ON_RESUME) */
181     @OnLifecycleEvent(ON_RESUME)
onResume()182     public void onResume() {
183         mAirplaneModeEnabler.start();
184         mConnectivityManager.registerDefaultNetworkCallback(mNetworkCallback);
185         mContext.registerReceiver(mWifiStateReceiver, mWifiStateFilter,
186                 Context.RECEIVER_EXPORTED_UNAUDITED);
187     }
188 
189     /** @OnLifecycleEvent(ON_PAUSE) */
190     @OnLifecycleEvent(ON_PAUSE)
onPause()191     public void onPause() {
192         mAirplaneModeEnabler.stop();
193         mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
194         mContext.unregisterReceiver(mWifiStateReceiver);
195     }
196 
197     /** @OnLifecycleEvent(ON_DESTROY) */
198     @OnLifecycleEvent(ON_DESTROY)
onDestroy()199     public void onDestroy() {
200         mAirplaneModeEnabler.close();
201     }
202 
203     @Override
onAirplaneModeChanged(boolean isAirplaneModeOn)204     public void onAirplaneModeChanged(boolean isAirplaneModeOn) {
205         fetchActiveNetwork();
206         if (mListener != null) {
207             mListener.onAirplaneModeChanged(isAirplaneModeOn);
208         }
209     }
210 
fetchActiveNetwork()211     private void fetchActiveNetwork() {
212         Network activeNetwork = mConnectivityManager.getActiveNetwork();
213         if (activeNetwork == null) {
214             mInternetAvailable = false;
215             updateInternetType();
216             return;
217         }
218 
219         NetworkCapabilities activeNetworkCapabilities =
220                 mConnectivityManager.getNetworkCapabilities(activeNetwork);
221         if (activeNetworkCapabilities == null) {
222             mInternetAvailable = false;
223             updateInternetType();
224             return;
225         }
226 
227         updateInternetAvailable(activeNetworkCapabilities);
228     }
229 
230     @VisibleForTesting
updateInternetAvailable(@onNull NetworkCapabilities capabilities)231     void updateInternetAvailable(@NonNull NetworkCapabilities capabilities) {
232         boolean internetAvailable = false;
233         if (capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
234                 && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)) {
235             for (int transport : capabilities.getTransportTypes()) {
236                 if (sTransportMap.containsKey(transport)) {
237                     mTransport = transport;
238                     internetAvailable = true;
239                     Log.i(TAG, "Detect an internet available network with transport type: "
240                             + mTransport);
241                     break;
242                 }
243             }
244         }
245         mInternetAvailable = internetAvailable;
246         updateInternetType();
247     }
248 
249     @VisibleForTesting
updateInternetType()250     void updateInternetType() {
251         @InternetType int internetType = INTERNET_NETWORKS_AVAILABLE;
252         if (mInternetAvailable) {
253             internetType = sTransportMap.get(mTransport);
254             if (internetType == INTERNET_WIFI && isCarrierWifiActive()) {
255                 internetType = INTERNET_CELLULAR;
256             }
257         } else if (mAirplaneModeEnabler.isAirplaneModeOn()
258                 && mWifiManager.getWifiState() != WifiManager.WIFI_STATE_ENABLED) {
259             internetType = INTERNET_OFF;
260         }
261         mInternetType = internetType;
262 
263         if (mListener != null) {
264             mListener.onInternetTypeChanged(mInternetType);
265         }
266     }
267 
isCarrierWifiActive()268     protected boolean isCarrierWifiActive() {
269         final WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
270         if (wifiInfo == null || !wifiInfo.isCarrierMerged()) {
271             return false;
272         }
273         Log.i(TAG, "Detect a merged carrier Wi-Fi connected.");
274         return true;
275     }
276 
277     /**
278      * Get the internet type.
279      */
getInternetType()280     public @InternetType int getInternetType() {
281         return mInternetType;
282     }
283 
284     /**
285      * Return ture when the airplane mode is on.
286      */
isAirplaneModeOn()287     public boolean isAirplaneModeOn() {
288         return mAirplaneModeEnabler.isAirplaneModeOn();
289     }
290 
291     /**
292      * Return ture when the Wi-Fi is enabled.
293      */
isWifiEnabled()294     public boolean isWifiEnabled() {
295         return mWifiManager.isWifiEnabled();
296     }
297 }
298