1 /* 2 * Copyright (C) 2019 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.wifi.slice; 18 19 import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL; 20 import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY; 21 import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; 22 import static android.net.NetworkCapabilities.TRANSPORT_WIFI; 23 24 import static com.android.settings.wifi.slice.WifiSlice.DEFAULT_EXPANDED_ROW_COUNT; 25 26 import android.content.Context; 27 import android.content.Intent; 28 import android.net.ConnectivityManager; 29 import android.net.ConnectivityManager.NetworkCallback; 30 import android.net.Network; 31 import android.net.NetworkCapabilities; 32 import android.net.NetworkRequest; 33 import android.net.Uri; 34 import android.net.wifi.WifiInfo; 35 import android.os.Bundle; 36 import android.os.Handler; 37 import android.os.Looper; 38 import android.os.UserHandle; 39 import android.text.TextUtils; 40 import android.util.Log; 41 42 import androidx.annotation.VisibleForTesting; 43 44 import com.android.internal.util.Preconditions; 45 import com.android.settings.slices.SliceBackgroundWorker; 46 import com.android.settingslib.wifi.AccessPoint; 47 import com.android.settingslib.wifi.WifiTracker; 48 49 import java.util.ArrayList; 50 import java.util.List; 51 52 /** 53 * {@link SliceBackgroundWorker} for Wi-Fi, used by {@link WifiSlice}. 54 */ 55 public class WifiScanWorker extends SliceBackgroundWorker<AccessPoint> implements 56 WifiTracker.WifiListener { 57 58 private static final String TAG = "WifiScanWorker"; 59 60 @VisibleForTesting 61 WifiNetworkCallback mNetworkCallback; 62 63 private final Context mContext; 64 private final ConnectivityManager mConnectivityManager; 65 private final WifiTracker mWifiTracker; 66 67 private static String sClickedWifiSsid; 68 WifiScanWorker(Context context, Uri uri)69 public WifiScanWorker(Context context, Uri uri) { 70 super(context, uri); 71 mContext = context; 72 mConnectivityManager = context.getSystemService(ConnectivityManager.class); 73 mWifiTracker = new WifiTracker(mContext, this /* wifiListener */, 74 true /* includeSaved */, true /* includeScans */); 75 } 76 77 @Override onSlicePinned()78 protected void onSlicePinned() { 79 mWifiTracker.onStart(); 80 onAccessPointsChanged(); 81 } 82 83 @Override onSliceUnpinned()84 protected void onSliceUnpinned() { 85 mWifiTracker.onStop(); 86 unregisterNetworkCallback(); 87 clearClickedWifiOnSliceUnpinned(); 88 } 89 90 @Override close()91 public void close() { 92 mWifiTracker.onDestroy(); 93 } 94 95 @Override onWifiStateChanged(int state)96 public void onWifiStateChanged(int state) { 97 notifySliceChange(); 98 } 99 100 @Override onConnectedChanged()101 public void onConnectedChanged() { 102 } 103 104 @Override onAccessPointsChanged()105 public void onAccessPointsChanged() { 106 // in case state has changed 107 if (!mWifiTracker.getManager().isWifiEnabled()) { 108 updateResults(null); 109 return; 110 } 111 // AccessPoints are sorted by the WifiTracker 112 final List<AccessPoint> accessPoints = mWifiTracker.getAccessPoints(); 113 final List<AccessPoint> resultList = new ArrayList<>(); 114 final int apRowCount = getApRowCount(); 115 for (AccessPoint ap : accessPoints) { 116 if (ap.isReachable()) { 117 resultList.add(clone(ap)); 118 if (resultList.size() >= apRowCount) { 119 break; 120 } 121 } 122 } 123 updateResults(resultList); 124 } 125 getApRowCount()126 protected int getApRowCount() { 127 return DEFAULT_EXPANDED_ROW_COUNT; 128 } 129 clone(AccessPoint accessPoint)130 private AccessPoint clone(AccessPoint accessPoint) { 131 final Bundle savedState = new Bundle(); 132 accessPoint.saveWifiState(savedState); 133 return new AccessPoint(mContext, savedState); 134 } 135 136 @Override areListsTheSame(List<AccessPoint> a, List<AccessPoint> b)137 protected boolean areListsTheSame(List<AccessPoint> a, List<AccessPoint> b) { 138 if (!a.equals(b)) { 139 return false; 140 } 141 142 // compare access point states one by one 143 final int listSize = a.size(); 144 for (int i = 0; i < listSize; i++) { 145 if (a.get(i).getDetailedState() != b.get(i).getDetailedState()) { 146 return false; 147 } 148 } 149 return true; 150 } 151 saveClickedWifi(AccessPoint accessPoint)152 static void saveClickedWifi(AccessPoint accessPoint) { 153 sClickedWifiSsid = accessPoint.getSsidStr(); 154 } 155 clearClickedWifi()156 static void clearClickedWifi() { 157 sClickedWifiSsid = null; 158 } 159 isWifiClicked(WifiInfo info)160 static boolean isWifiClicked(WifiInfo info) { 161 final String ssid = WifiInfo.sanitizeSsid(info.getSSID()); 162 return !TextUtils.isEmpty(ssid) && TextUtils.equals(ssid, sClickedWifiSsid); 163 } 164 clearClickedWifiOnSliceUnpinned()165 protected void clearClickedWifiOnSliceUnpinned() { 166 clearClickedWifi(); 167 } 168 isSessionValid()169 protected boolean isSessionValid() { 170 return true; 171 } 172 registerNetworkCallback(Network wifiNetwork)173 public void registerNetworkCallback(Network wifiNetwork) { 174 if (wifiNetwork == null) { 175 return; 176 } 177 178 if (mNetworkCallback != null && mNetworkCallback.isSameNetwork(wifiNetwork)) { 179 return; 180 } 181 182 unregisterNetworkCallback(); 183 184 mNetworkCallback = new WifiNetworkCallback(wifiNetwork); 185 mConnectivityManager.registerNetworkCallback( 186 new NetworkRequest.Builder() 187 .clearCapabilities() 188 .addTransportType(TRANSPORT_WIFI) 189 .build(), 190 mNetworkCallback, 191 new Handler(Looper.getMainLooper())); 192 } 193 unregisterNetworkCallback()194 public void unregisterNetworkCallback() { 195 if (mNetworkCallback != null) { 196 try { 197 mConnectivityManager.unregisterNetworkCallback(mNetworkCallback); 198 } catch (RuntimeException e) { 199 Log.e(TAG, "Unregistering CaptivePortalNetworkCallback failed.", e); 200 } 201 mNetworkCallback = null; 202 } 203 } 204 205 class WifiNetworkCallback extends NetworkCallback { 206 207 private final Network mNetwork; 208 private boolean mIsCaptivePortal; 209 private boolean mHasPartialConnectivity; 210 private boolean mIsValidated; 211 WifiNetworkCallback(Network network)212 WifiNetworkCallback(Network network) { 213 mNetwork = Preconditions.checkNotNull(network); 214 } 215 216 @Override onCapabilitiesChanged(Network network, NetworkCapabilities nc)217 public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) { 218 if (!isSameNetwork(network)) { 219 return; 220 } 221 222 final boolean prevIsCaptivePortal = mIsCaptivePortal; 223 final boolean prevHasPartialConnectivity = mHasPartialConnectivity; 224 final boolean prevIsValidated = mIsValidated; 225 226 mIsCaptivePortal = nc.hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL); 227 mHasPartialConnectivity = nc.hasCapability(NET_CAPABILITY_PARTIAL_CONNECTIVITY); 228 mIsValidated = nc.hasCapability(NET_CAPABILITY_VALIDATED); 229 230 if (prevIsCaptivePortal == mIsCaptivePortal 231 && prevHasPartialConnectivity == mHasPartialConnectivity 232 && prevIsValidated == mIsValidated) { 233 return; 234 } 235 236 notifySliceChange(); 237 238 // Automatically start captive portal 239 if (!prevIsCaptivePortal && mIsCaptivePortal 240 && isWifiClicked(mWifiTracker.getManager().getConnectionInfo()) 241 && isSessionValid()) { 242 final Intent intent = new Intent(mContext, ConnectToWifiHandler.class) 243 .putExtra(ConnectivityManager.EXTRA_NETWORK, network) 244 .addFlags(Intent.FLAG_RECEIVER_FOREGROUND); 245 // Sending a broadcast in the system process needs to specify a user 246 mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT); 247 } 248 } 249 250 /** 251 * Returns true if the supplied network is not null and is the same as the originally 252 * supplied value. 253 */ isSameNetwork(Network network)254 public boolean isSameNetwork(Network network) { 255 return mNetwork.equals(network); 256 } 257 } 258 } 259