/* * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.wifi.slice; import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL; import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY; import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; import static com.android.settings.wifi.slice.WifiSlice.DEFAULT_EXPANDED_ROW_COUNT; import android.content.Context; import android.content.Intent; import android.net.ConnectivityManager; import android.net.ConnectivityManager.NetworkCallback; import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkRequest; import android.net.Uri; import android.net.wifi.WifiInfo; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.UserHandle; import android.text.TextUtils; import android.util.Log; import androidx.annotation.VisibleForTesting; import com.android.internal.util.Preconditions; import com.android.settings.slices.SliceBackgroundWorker; import com.android.settingslib.wifi.AccessPoint; import com.android.settingslib.wifi.WifiTracker; import java.util.ArrayList; import java.util.List; /** * {@link SliceBackgroundWorker} for Wi-Fi, used by {@link WifiSlice}. */ public class WifiScanWorker extends SliceBackgroundWorker implements WifiTracker.WifiListener { private static final String TAG = "WifiScanWorker"; @VisibleForTesting WifiNetworkCallback mNetworkCallback; private final Context mContext; private final ConnectivityManager mConnectivityManager; private final WifiTracker mWifiTracker; private static String sClickedWifiSsid; public WifiScanWorker(Context context, Uri uri) { super(context, uri); mContext = context; mConnectivityManager = context.getSystemService(ConnectivityManager.class); mWifiTracker = new WifiTracker(mContext, this /* wifiListener */, true /* includeSaved */, true /* includeScans */); } @Override protected void onSlicePinned() { mWifiTracker.onStart(); onAccessPointsChanged(); } @Override protected void onSliceUnpinned() { mWifiTracker.onStop(); unregisterNetworkCallback(); clearClickedWifiOnSliceUnpinned(); } @Override public void close() { mWifiTracker.onDestroy(); } @Override public void onWifiStateChanged(int state) { notifySliceChange(); } @Override public void onConnectedChanged() { } @Override public void onAccessPointsChanged() { // in case state has changed if (!mWifiTracker.getManager().isWifiEnabled()) { updateResults(null); return; } // AccessPoints are sorted by the WifiTracker final List accessPoints = mWifiTracker.getAccessPoints(); final List resultList = new ArrayList<>(); final int apRowCount = getApRowCount(); for (AccessPoint ap : accessPoints) { if (ap.isReachable()) { resultList.add(clone(ap)); if (resultList.size() >= apRowCount) { break; } } } updateResults(resultList); } protected int getApRowCount() { return DEFAULT_EXPANDED_ROW_COUNT; } private AccessPoint clone(AccessPoint accessPoint) { final Bundle savedState = new Bundle(); accessPoint.saveWifiState(savedState); return new AccessPoint(mContext, savedState); } @Override protected boolean areListsTheSame(List a, List b) { if (!a.equals(b)) { return false; } // compare access point states one by one final int listSize = a.size(); for (int i = 0; i < listSize; i++) { if (a.get(i).getDetailedState() != b.get(i).getDetailedState()) { return false; } } return true; } static void saveClickedWifi(AccessPoint accessPoint) { sClickedWifiSsid = accessPoint.getSsidStr(); } static void clearClickedWifi() { sClickedWifiSsid = null; } static boolean isWifiClicked(WifiInfo info) { final String ssid = WifiInfo.sanitizeSsid(info.getSSID()); return !TextUtils.isEmpty(ssid) && TextUtils.equals(ssid, sClickedWifiSsid); } protected void clearClickedWifiOnSliceUnpinned() { clearClickedWifi(); } protected boolean isSessionValid() { return true; } public void registerNetworkCallback(Network wifiNetwork) { if (wifiNetwork == null) { return; } if (mNetworkCallback != null && mNetworkCallback.isSameNetwork(wifiNetwork)) { return; } unregisterNetworkCallback(); mNetworkCallback = new WifiNetworkCallback(wifiNetwork); mConnectivityManager.registerNetworkCallback( new NetworkRequest.Builder() .clearCapabilities() .addTransportType(TRANSPORT_WIFI) .build(), mNetworkCallback, new Handler(Looper.getMainLooper())); } public void unregisterNetworkCallback() { if (mNetworkCallback != null) { try { mConnectivityManager.unregisterNetworkCallback(mNetworkCallback); } catch (RuntimeException e) { Log.e(TAG, "Unregistering CaptivePortalNetworkCallback failed.", e); } mNetworkCallback = null; } } class WifiNetworkCallback extends NetworkCallback { private final Network mNetwork; private boolean mIsCaptivePortal; private boolean mHasPartialConnectivity; private boolean mIsValidated; WifiNetworkCallback(Network network) { mNetwork = Preconditions.checkNotNull(network); } @Override public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) { if (!isSameNetwork(network)) { return; } final boolean prevIsCaptivePortal = mIsCaptivePortal; final boolean prevHasPartialConnectivity = mHasPartialConnectivity; final boolean prevIsValidated = mIsValidated; mIsCaptivePortal = nc.hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL); mHasPartialConnectivity = nc.hasCapability(NET_CAPABILITY_PARTIAL_CONNECTIVITY); mIsValidated = nc.hasCapability(NET_CAPABILITY_VALIDATED); if (prevIsCaptivePortal == mIsCaptivePortal && prevHasPartialConnectivity == mHasPartialConnectivity && prevIsValidated == mIsValidated) { return; } notifySliceChange(); // Automatically start captive portal if (!prevIsCaptivePortal && mIsCaptivePortal && isWifiClicked(mWifiTracker.getManager().getConnectionInfo()) && isSessionValid()) { final Intent intent = new Intent(mContext, ConnectToWifiHandler.class) .putExtra(ConnectivityManager.EXTRA_NETWORK, network) .addFlags(Intent.FLAG_RECEIVER_FOREGROUND); // Sending a broadcast in the system process needs to specify a user mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT); } } /** * Returns true if the supplied network is not null and is the same as the originally * supplied value. */ public boolean isSameNetwork(Network network) { return mNetwork.equals(network); } } }