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