[Wi-Fi] Apply WifiTrackerLib objects in Wi-Fi Slice

This change uses WifiTrackerLib's WifiPickerTracker & WifiEntry
to replace SettingLib's WifiTracker & AccessPoint.

This change includes

1. WifiScanWorker has the callbacks similar to a lifecycle component
   but it's not a lifecycle component. Let WifiScanWorker implements
   LifecycleOwner and provides #getLifecycle() for WifiPickerTracker.

2. Remove captive portal related code because WifiEntry#connect will
   handle captive portal login if it's necessary.

3. Create WifiSliceItem to wrap WifiEntry because WifiEntry is an
   abstract object and it does not provide copy constructor.
   Without copy construcor, Wi-Fi Slice may show unexpected information
   when a WifiEntry is updated.

4. Use WifiTrackerLib's NetworkDetailsTracker & WifiEntry in
   WifiDialogActivity because it gets a WifiEntry key from Wi-Fi Slice.
   NetworkDetailsTracker can get the WifiEntry of th key.

Bug: 155613549
Bug: 152571756
Test: make RunSettingsRoboTests ROBOTEST_FILTER=com.android.settings.wifi.slice
      make RunSettingsRoboTests ROBOTEST_FILTER=com.android.settings.wifi
Change-Id: I0718f4647cea007a9b701922f3121a388dd43918
This commit is contained in:
Arc Wang
2020-05-28 21:35:58 +08:00
parent ff5fcdc965
commit 7b1aded2a6
17 changed files with 587 additions and 928 deletions

View File

@@ -16,243 +16,187 @@
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.NetworkScoreManager;
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.net.wifi.WifiManager;
import android.os.HandlerThread;
import android.os.Process;
import android.os.SimpleClock;
import android.os.SystemClock;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LifecycleRegistry;
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 com.android.settingslib.utils.ThreadUtils;
import com.android.wifitrackerlib.WifiEntry;
import com.android.wifitrackerlib.WifiEntry.WifiEntryCallback;
import com.android.wifitrackerlib.WifiPickerTracker;
import java.time.Clock;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.List;
/**
* {@link SliceBackgroundWorker} for Wi-Fi, used by {@link WifiSlice}.
*/
public class WifiScanWorker extends SliceBackgroundWorker<AccessPoint> implements
WifiTracker.WifiListener {
public class WifiScanWorker extends SliceBackgroundWorker<WifiSliceItem> implements
WifiPickerTracker.WifiPickerTrackerCallback, LifecycleOwner, WifiEntryCallback {
private static final String TAG = "WifiScanWorker";
// Max age of tracked WifiEntries.
private static final long MAX_SCAN_AGE_MILLIS = 15_000;
// Interval between initiating WifiPickerTracker scans.
private static final long SCAN_INTERVAL_MILLIS = 10_000;
@VisibleForTesting
WifiNetworkCallback mNetworkCallback;
private final Context mContext;
private final ConnectivityManager mConnectivityManager;
private final WifiTracker mWifiTracker;
private static String sClickedWifiSsid;
final LifecycleRegistry mLifecycleRegistry;
@VisibleForTesting
WifiPickerTracker mWifiPickerTracker;
// Worker thread used for WifiPickerTracker work
private final HandlerThread mWorkerThread;
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 */);
mLifecycleRegistry = new LifecycleRegistry(this);
mWorkerThread = new HandlerThread(TAG, Process.THREAD_PRIORITY_BACKGROUND);
mWorkerThread.start();
final Clock elapsedRealtimeClock = new SimpleClock(ZoneOffset.UTC) {
@Override
public long millis() {
return SystemClock.elapsedRealtime();
}
};
mWifiPickerTracker = new WifiPickerTracker(getLifecycle(), context,
context.getSystemService(WifiManager.class),
context.getSystemService(ConnectivityManager.class),
context.getSystemService(NetworkScoreManager.class),
ThreadUtils.getUiThreadHandler(),
mWorkerThread.getThreadHandler(),
elapsedRealtimeClock,
MAX_SCAN_AGE_MILLIS,
SCAN_INTERVAL_MILLIS,
this);
mLifecycleRegistry.markState(Lifecycle.State.INITIALIZED);
mLifecycleRegistry.markState(Lifecycle.State.CREATED);
}
@Override
protected void onSlicePinned() {
mWifiTracker.onStart();
onAccessPointsChanged();
mLifecycleRegistry.markState(Lifecycle.State.STARTED);
mLifecycleRegistry.markState(Lifecycle.State.RESUMED);
updateResults();
}
@Override
protected void onSliceUnpinned() {
mWifiTracker.onStop();
unregisterNetworkCallback();
clearClickedWifiOnSliceUnpinned();
mLifecycleRegistry.markState(Lifecycle.State.STARTED);
mLifecycleRegistry.markState(Lifecycle.State.CREATED);
}
@Override
public void close() {
mWifiTracker.onDestroy();
mLifecycleRegistry.markState(Lifecycle.State.DESTROYED);
mWorkerThread.quit();
}
@Override
public void onWifiStateChanged(int state) {
public Lifecycle getLifecycle() {
return mLifecycleRegistry;
}
/** Called when the state of Wifi has changed. */
@Override
public void onWifiStateChanged() {
updateResults();
}
/**
* Update the results when data changes
*/
@Override
public void onWifiEntriesChanged() {
updateResults();
}
/**
* Indicates the state of the WifiEntry has changed and clients may retrieve updates through
* the WifiEntry getter methods.
*/
@Override
public void onUpdated() {
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<AccessPoint> accessPoints = mWifiTracker.getAccessPoints();
final List<AccessPoint> 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
public void onNumSavedSubscriptionsChanged() {
// Do nothing.
}
@Override
protected boolean areListsTheSame(List<AccessPoint> a, List<AccessPoint> b) {
if (!a.equals(b)) {
return false;
}
public void onNumSavedNetworksChanged() {
// Do nothing.
}
// 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;
/**
* To get the WifiEntry of key.
*/
public WifiEntry getWifiEntry(String key) {
// Get specified WifiEntry.
WifiEntry keyWifiEntry = null;
final WifiEntry connectedWifiEntry = mWifiPickerTracker.getConnectedWifiEntry();
if (connectedWifiEntry != null && TextUtils.equals(key, connectedWifiEntry.getKey())) {
keyWifiEntry = connectedWifiEntry;
} else {
for (WifiEntry wifiEntry : mWifiPickerTracker.getWifiEntries()) {
if (TextUtils.equals(key, wifiEntry.getKey())) {
keyWifiEntry = wifiEntry;
break;
}
}
}
return true;
return keyWifiEntry;
}
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) {
@VisibleForTesting
void updateResults() {
if (mWifiPickerTracker.getWifiState() != WifiManager.WIFI_STATE_ENABLED
|| mLifecycleRegistry.getCurrentState() != Lifecycle.State.RESUMED) {
super.updateResults(null);
return;
}
if (mNetworkCallback != null && mNetworkCallback.isSameNetwork(wifiNetwork)) {
return;
final List<WifiSliceItem> resultList = new ArrayList<>();
final WifiEntry connectedWifiEntry = mWifiPickerTracker.getConnectedWifiEntry();
if (connectedWifiEntry != null) {
connectedWifiEntry.setListener(this);
resultList.add(new WifiSliceItem(getContext(), connectedWifiEntry));
}
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);
for (WifiEntry wifiEntry : mWifiPickerTracker.getWifiEntries()) {
if (resultList.size() >= getApRowCount()) {
break;
}
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);
if (wifiEntry.getLevel() != WifiEntry.WIFI_LEVEL_UNREACHABLE) {
wifiEntry.setListener(this);
resultList.add(new WifiSliceItem(getContext(), wifiEntry));
}
}
/**
* 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);
}
super.updateResults(resultList);
}
}