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 for (AccessPoint ap : accessPoints) { 115 if (ap.isReachable()) { 116 resultList.add(clone(ap)); 117 if (resultList.size() >= DEFAULT_EXPANDED_ROW_COUNT) { 118 break; 119 } 120 } 121 } 122 updateResults(resultList); 123 } 124 clone(AccessPoint accessPoint)125 private AccessPoint clone(AccessPoint accessPoint) { 126 final Bundle savedState = new Bundle(); 127 accessPoint.saveWifiState(savedState); 128 return new AccessPoint(mContext, savedState); 129 } 130 131 @Override areListsTheSame(List<AccessPoint> a, List<AccessPoint> b)132 protected boolean areListsTheSame(List<AccessPoint> a, List<AccessPoint> b) { 133 if (!a.equals(b)) { 134 return false; 135 } 136 137 // compare access point states one by one 138 final int listSize = a.size(); 139 for (int i = 0; i < listSize; i++) { 140 if (a.get(i).getDetailedState() != b.get(i).getDetailedState()) { 141 return false; 142 } 143 } 144 return true; 145 } 146 saveClickedWifi(AccessPoint accessPoint)147 static void saveClickedWifi(AccessPoint accessPoint) { 148 sClickedWifiSsid = accessPoint.getSsidStr(); 149 } 150 clearClickedWifi()151 static void clearClickedWifi() { 152 sClickedWifiSsid = null; 153 } 154 isWifiClicked(WifiInfo info)155 static boolean isWifiClicked(WifiInfo info) { 156 final String ssid = WifiInfo.removeDoubleQuotes(info.getSSID()); 157 return !TextUtils.isEmpty(ssid) && TextUtils.equals(ssid, sClickedWifiSsid); 158 } 159 clearClickedWifiOnSliceUnpinned()160 protected void clearClickedWifiOnSliceUnpinned() { 161 clearClickedWifi(); 162 } 163 isSessionValid()164 protected boolean isSessionValid() { 165 return true; 166 } 167 registerNetworkCallback(Network wifiNetwork)168 public void registerNetworkCallback(Network wifiNetwork) { 169 if (wifiNetwork == null) { 170 return; 171 } 172 173 if (mNetworkCallback != null && mNetworkCallback.isSameNetwork(wifiNetwork)) { 174 return; 175 } 176 177 unregisterNetworkCallback(); 178 179 mNetworkCallback = new WifiNetworkCallback(wifiNetwork); 180 mConnectivityManager.registerNetworkCallback( 181 new NetworkRequest.Builder() 182 .clearCapabilities() 183 .addTransportType(TRANSPORT_WIFI) 184 .build(), 185 mNetworkCallback, 186 new Handler(Looper.getMainLooper())); 187 } 188 unregisterNetworkCallback()189 public void unregisterNetworkCallback() { 190 if (mNetworkCallback != null) { 191 try { 192 mConnectivityManager.unregisterNetworkCallback(mNetworkCallback); 193 } catch (RuntimeException e) { 194 Log.e(TAG, "Unregistering CaptivePortalNetworkCallback failed.", e); 195 } 196 mNetworkCallback = null; 197 } 198 } 199 200 class WifiNetworkCallback extends NetworkCallback { 201 202 private final Network mNetwork; 203 private boolean mIsCaptivePortal; 204 private boolean mHasPartialConnectivity; 205 private boolean mIsValidated; 206 WifiNetworkCallback(Network network)207 WifiNetworkCallback(Network network) { 208 mNetwork = Preconditions.checkNotNull(network); 209 } 210 211 @Override onCapabilitiesChanged(Network network, NetworkCapabilities nc)212 public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) { 213 if (!isSameNetwork(network)) { 214 return; 215 } 216 217 final boolean prevIsCaptivePortal = mIsCaptivePortal; 218 final boolean prevHasPartialConnectivity = mHasPartialConnectivity; 219 final boolean prevIsValidated = mIsValidated; 220 221 mIsCaptivePortal = nc.hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL); 222 mHasPartialConnectivity = nc.hasCapability(NET_CAPABILITY_PARTIAL_CONNECTIVITY); 223 mIsValidated = nc.hasCapability(NET_CAPABILITY_VALIDATED); 224 225 if (prevIsCaptivePortal == mIsCaptivePortal 226 && prevHasPartialConnectivity == mHasPartialConnectivity 227 && prevIsValidated == mIsValidated) { 228 return; 229 } 230 231 notifySliceChange(); 232 233 // Automatically start captive portal 234 if (!prevIsCaptivePortal && mIsCaptivePortal 235 && isWifiClicked(mWifiTracker.getManager().getConnectionInfo()) 236 && isSessionValid()) { 237 final Intent intent = new Intent(mContext, ConnectToWifiHandler.class) 238 .putExtra(ConnectivityManager.EXTRA_NETWORK, network) 239 .addFlags(Intent.FLAG_RECEIVER_FOREGROUND); 240 // Sending a broadcast in the system process needs to specify a user 241 mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT); 242 } 243 } 244 245 /** 246 * Returns true if the supplied network is not null and is the same as the originally 247 * supplied value. 248 */ isSameNetwork(Network network)249 public boolean isSameNetwork(Network network) { 250 return mNetwork.equals(network); 251 } 252 } 253 } 254