1 /*
2  * Copyright (C) 2023 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 package android.net.wifi.nl80211;
17 
18 import android.annotation.NonNull;
19 import android.annotation.Nullable;
20 import android.app.AlarmManager;
21 import android.content.BroadcastReceiver;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.IntentFilter;
25 import android.net.ConnectivityManager;
26 import android.net.ConnectivityManager.NetworkCallback;
27 import android.net.Network;
28 import android.net.NetworkCapabilities;
29 import android.net.NetworkRequest;
30 import android.net.wifi.WifiConfiguration;
31 import android.net.wifi.WifiInfo;
32 import android.net.wifi.WifiManager;
33 import android.os.Handler;
34 import android.os.PowerManager;
35 import android.os.SystemClock;
36 import android.util.Log;
37 
38 import com.android.internal.annotations.VisibleForTesting;
39 
40 import java.util.ArrayList;
41 import java.util.HashSet;
42 import java.util.Set;
43 
44 /**
45  * @hide
46  */
47 public class InstantWifi {
48     private static final String INSTANT_WIFI_TAG = "InstantWifi";
49     private static final int OVERRIDED_SCAN_CONNECTION_TIMEOUT_MS = 1000;
50     private static final int WIFI_NETWORK_EXPIRED_MS = 7 * 24 * 60 * 60 * 1000; // a week
51     private static final String NO_CONNECTION_TIMEOUT_ALARM_TAG =
52             INSTANT_WIFI_TAG + " No Connection Timeout";
53 
54     private Context mContext;
55     private AlarmManager mAlarmManager;
56     private Handler mEventHandler;
57     private ConnectivityManager mConnectivityManager;
58     private WifiManager mWifiManager;
59     private PowerManager mPowerManager;
60     private long mLastWifiOnSinceBootMs;
61     private long mLastScreenOnSinceBootMs;
62     private boolean mIsWifiConnected = false;
63     private boolean mScreenOn = false;
64     private boolean mWifiEnabled = false;
65     private boolean mIsNoConnectionAlarmSet = false;
66     private ArrayList<WifiNetwork> mConnectedWifiNetworkList = new ArrayList<>();
67     private AlarmManager.OnAlarmListener mNoConnectionTimeoutCallback =
68             new AlarmManager.OnAlarmListener() {
69                 public void onAlarm() {
70                     Log.i(INSTANT_WIFI_TAG, "Timed out waiting for wifi connection");
71                     mIsNoConnectionAlarmSet = false;
72                     mWifiManager.startScan();
73                 }
74             };
75 
InstantWifi(Context context, AlarmManager alarmManager, Handler eventHandler)76     public InstantWifi(Context context, AlarmManager alarmManager, Handler eventHandler) {
77         mContext = context;
78         mAlarmManager = alarmManager;
79         mEventHandler = eventHandler;
80         mWifiManager = mContext.getSystemService(WifiManager.class);
81         mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
82         mConnectivityManager.registerNetworkCallback(
83                 new NetworkRequest.Builder()
84                 .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
85                 .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
86                 .build(), new WifiNetworkCallback());
87         // System power service was initialized before wifi nl80211 service.
88         mPowerManager = mContext.getSystemService(PowerManager.class);
89         IntentFilter screenEventfilter = new IntentFilter();
90         screenEventfilter.addAction(Intent.ACTION_SCREEN_ON);
91         screenEventfilter.addAction(Intent.ACTION_SCREEN_OFF);
92         mContext.registerReceiver(
93                 new BroadcastReceiver() {
94                 @Override
95                     public void onReceive(Context context, Intent intent) {
96                         String action = intent.getAction();
97                         if (action.equals(Intent.ACTION_SCREEN_ON)) {
98                             if (!mScreenOn) {
99                                 mLastScreenOnSinceBootMs = getMockableElapsedRealtime();
100                             }
101                             mScreenOn = true;
102                         } else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
103                             mScreenOn = false;
104                         }
105                         Log.d(INSTANT_WIFI_TAG, "mScreenOn is changed to " + mScreenOn);
106                     }
107                 }, screenEventfilter, null, mEventHandler);
108         mScreenOn = mPowerManager.isInteractive();
109         mContext.registerReceiver(
110                 new BroadcastReceiver() {
111                     @Override
112                     public void onReceive(Context context, Intent intent) {
113                         int state = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
114                                 WifiManager.WIFI_STATE_UNKNOWN);
115                         mWifiEnabled = state == WifiManager.WIFI_STATE_ENABLED;
116                         if (mWifiEnabled) {
117                             mLastWifiOnSinceBootMs = getMockableElapsedRealtime();
118                         }
119                         Log.d(INSTANT_WIFI_TAG, "mWifiEnabled is changed to " + mWifiEnabled);
120                     }
121                 },
122                 new IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION),
123                 null, mEventHandler);
124     }
125 
126     @VisibleForTesting
getMockableElapsedRealtime()127     protected long getMockableElapsedRealtime() {
128         return SystemClock.elapsedRealtime();
129     }
130 
131     private class WifiNetwork {
132         private final int mNetId;
133         private Set<Integer> mConnectedFrequencies = new HashSet<Integer>();
134         private int[] mLastTwoConnectedFrequencies = new int[2];
135         private long mLastConnectedTimeMillis;
WifiNetwork(int netId)136         WifiNetwork(int netId) {
137             mNetId = netId;
138         }
139 
getNetId()140         public int getNetId() {
141             return mNetId;
142         }
143 
addConnectedFrequency(int channelFrequency)144         public boolean addConnectedFrequency(int channelFrequency) {
145             mLastConnectedTimeMillis = getMockableElapsedRealtime();
146             if (mLastTwoConnectedFrequencies[0] != channelFrequency
147                     && mLastTwoConnectedFrequencies[1] != channelFrequency) {
148                 mLastTwoConnectedFrequencies[0] = mLastTwoConnectedFrequencies[1];
149                 mLastTwoConnectedFrequencies[1] = channelFrequency;
150             }
151             return mConnectedFrequencies.add(channelFrequency);
152         }
153 
getConnectedFrequencies()154         public Set<Integer> getConnectedFrequencies() {
155             return mConnectedFrequencies;
156         }
157 
getLastTwoConnectedFrequencies()158         public int[] getLastTwoConnectedFrequencies() {
159             if ((getMockableElapsedRealtime() - mLastConnectedTimeMillis)
160                     > WIFI_NETWORK_EXPIRED_MS) {
161                 return new int[0];
162             }
163             return mLastTwoConnectedFrequencies;
164         }
165 
getLastConnectedTimeMillis()166         public long getLastConnectedTimeMillis() {
167             return mLastConnectedTimeMillis;
168         }
169     }
170 
171     private class WifiNetworkCallback extends NetworkCallback {
172         @Override
onAvailable(@onNull Network network)173         public void onAvailable(@NonNull Network network) {
174         }
175 
176         @Override
onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities)177         public void onCapabilitiesChanged(Network network,
178                 NetworkCapabilities networkCapabilities) {
179             if (networkCapabilities != null && network != null) {
180                 WifiInfo wifiInfo = (WifiInfo) networkCapabilities.getTransportInfo();
181                 if (wifiInfo == null || mWifiManager == null) {
182                     return;
183                 }
184                 WifiConfiguration config = mWifiManager.getPrivilegedConnectedNetwork();
185                 if (config == null) {
186                     return;
187                 }
188                 final int currentNetworkId = config.networkId;
189                 final int connectecFrequency = wifiInfo.getFrequency();
190                 if (connectecFrequency < 0 || currentNetworkId < 0) {
191                     return;
192                 }
193                 mIsWifiConnected = true;
194                 if (mIsNoConnectionAlarmSet) {
195                     mAlarmManager.cancel(mNoConnectionTimeoutCallback);
196                 }
197                 Log.d(INSTANT_WIFI_TAG, "Receive Wifi is connected, freq =  " + connectecFrequency
198                         + " and currentNetworkId : " + currentNetworkId
199                         + ", wifiinfo = " + wifiInfo);
200                 boolean isExist = false;
201                 for (WifiNetwork wifiNetwork : mConnectedWifiNetworkList) {
202                     if (wifiNetwork.getNetId() == currentNetworkId) {
203                         if (wifiNetwork.addConnectedFrequency(connectecFrequency)) {
204                             Log.d(INSTANT_WIFI_TAG, "Update connected frequency: "
205                                     + connectecFrequency + " to Network currentNetworkId : "
206                                     + currentNetworkId);
207                         }
208                         isExist = true;
209                     }
210                 }
211                 if (!isExist) {
212                     WifiNetwork currentNetwork = new WifiNetwork(currentNetworkId);
213                     currentNetwork.addConnectedFrequency(connectecFrequency);
214                     if (mConnectedWifiNetworkList.size() < 5) {
215                         mConnectedWifiNetworkList.add(currentNetwork);
216                     } else {
217                         ArrayList<WifiNetwork> lastConnectedWifiNetworkList = new ArrayList<>();
218                         WifiNetwork legacyNetwork = mConnectedWifiNetworkList.get(0);
219                         for (WifiNetwork connectedNetwork : mConnectedWifiNetworkList) {
220                             if (connectedNetwork.getNetId() == legacyNetwork.getNetId()) {
221                                 continue;
222                             }
223                             // Keep the used recently network in the last connected list
224                             if (connectedNetwork.getLastConnectedTimeMillis()
225                                     > legacyNetwork.getLastConnectedTimeMillis()) {
226                                 lastConnectedWifiNetworkList.add(connectedNetwork);
227                             } else {
228                                 lastConnectedWifiNetworkList.add(legacyNetwork);
229                                 legacyNetwork = connectedNetwork;
230                             }
231                         }
232                         mConnectedWifiNetworkList = lastConnectedWifiNetworkList;
233                     }
234                 }
235             }
236         }
237 
238         @Override
onLost(@onNull Network network)239         public void onLost(@NonNull Network network) {
240             mIsWifiConnected = false;
241         }
242     }
243 
244     /**
245      * Returns whether or not the scan freqs should be overrided by using predicted channels.
246      */
isUsePredictedScanningChannels()247     public boolean isUsePredictedScanningChannels() {
248         if (mIsWifiConnected || mConnectedWifiNetworkList.size() == 0
249                 || !mWifiManager.isWifiEnabled() || !mPowerManager.isInteractive()) {
250             return false;
251         }
252         if (!mWifiEnabled || !mScreenOn) {
253             Log.d(INSTANT_WIFI_TAG, "WiFi/Screen State mis-match, run instant Wifi anyway!");
254             return true;
255         }
256         return (((getMockableElapsedRealtime() - mLastWifiOnSinceBootMs)
257                         < OVERRIDED_SCAN_CONNECTION_TIMEOUT_MS)
258                 || ((getMockableElapsedRealtime() - mLastScreenOnSinceBootMs)
259                         < OVERRIDED_SCAN_CONNECTION_TIMEOUT_MS));
260     }
261 
262     /**
263      * Overrides the frequenies in SingleScanSetting
264      *
265      * @param settings the SingleScanSettings will be overrided.
266      * @param freqs new frequencies of SingleScanSettings
267      */
268     @Nullable
overrideFreqsForSingleScanSettingsIfNecessary( @ullable SingleScanSettings settings, @Nullable Set<Integer> freqs)269     public void overrideFreqsForSingleScanSettingsIfNecessary(
270             @Nullable SingleScanSettings settings, @Nullable Set<Integer> freqs) {
271         if (!isUsePredictedScanningChannels() || settings == null || freqs == null
272                 || freqs.size() == 0) {
273             return;
274         }
275         if (settings.channelSettings == null) {
276             settings.channelSettings = new ArrayList<>();
277         } else {
278             settings.channelSettings.clear();
279         }
280         for (int freq : freqs) {
281             if (freq > 0) {
282                 ChannelSettings channel = new ChannelSettings();
283                 channel.frequency = freq;
284                 settings.channelSettings.add(channel);
285             }
286         }
287         // Monitor connection after last override scan request.
288         if (mIsNoConnectionAlarmSet) {
289             mAlarmManager.cancel(mNoConnectionTimeoutCallback);
290         }
291         mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
292                 getMockableElapsedRealtime() + OVERRIDED_SCAN_CONNECTION_TIMEOUT_MS,
293                 NO_CONNECTION_TIMEOUT_ALARM_TAG, mNoConnectionTimeoutCallback, mEventHandler);
294         mIsNoConnectionAlarmSet = true;
295     }
296 
297     /**
298      * Returns the predicted scanning chcnnels set.
299      */
300     @NonNull
getPredictedScanningChannels()301     public Set<Integer> getPredictedScanningChannels() {
302         Set<Integer> predictedScanChannels = new HashSet<>();
303         if (!isUsePredictedScanningChannels()) {
304             Log.d(INSTANT_WIFI_TAG, "Drop, size: " + mConnectedWifiNetworkList.size());
305             return predictedScanChannels;
306         }
307         for (WifiNetwork network : mConnectedWifiNetworkList) {
308             for (int connectedFrequency : network.getLastTwoConnectedFrequencies()) {
309                 if (connectedFrequency > 0) {
310                     predictedScanChannels.add(connectedFrequency);
311                     Log.d(INSTANT_WIFI_TAG, "Add channel: " + connectedFrequency
312                             + " to predicted channel");
313                 }
314             }
315         }
316         return predictedScanChannels;
317     }
318 }
319