[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.

Bug: 155613549
Bug: 152571756
Test: make RunSettingsRoboTests ROBOTEST_FILTER=com.android.settings.wifi.slice
      make RunSettingsRoboTests ROBOTEST_FILTER=com.android.settings.wifi
Change-Id: I2d66ea4905daca3244ec4cf8f2935cda817480b1
This commit is contained in:
Arc Wang
2020-07-20 15:26:04 +08:00
parent e57ead9a75
commit 0496d2c142
15 changed files with 493 additions and 813 deletions

View File

@@ -40,7 +40,6 @@ import com.android.settings.core.InstrumentedFragment;
import com.android.settings.homepage.contextualcards.slices.BluetoothUpdateWorker;
import com.android.settings.homepage.contextualcards.slices.SwipeDismissalDelegate;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.wifi.slice.ContextualWifiScanWorker;
public class ContextualCardsFragment extends InstrumentedFragment implements
FocusRecyclerView.FocusListener {
@@ -80,7 +79,6 @@ public class ContextualCardsFragment extends InstrumentedFragment implements
super.onStart();
registerScreenOffReceiver();
registerKeyEventReceiver();
ContextualWifiScanWorker.newVisibleUiSession();
mContextualCardManager.loadContextualCards(LoaderManager.getInstance(this),
sRestartLoaderNeeded);
sRestartLoaderNeeded = false;

View File

@@ -32,6 +32,7 @@ import androidx.annotation.VisibleForTesting;
import com.android.settings.R;
import com.android.settings.core.InstrumentedFragment;
import com.android.settings.wifi.dpp.WifiDppQrCodeScannerFragment;
import com.android.settings.wifi.dpp.WifiDppUtils;
/**
@@ -116,7 +117,7 @@ public class AddNetworkFragment extends InstrumentedFragment implements WifiConf
}
final WifiConfiguration config = data.getParcelableExtra(
WifiDialogActivity.KEY_WIFI_CONFIGURATION);
WifiDppQrCodeScannerFragment.KEY_WIFI_CONFIGURATION);
successfullyFinish(config);
}
}

View File

@@ -57,7 +57,6 @@ import androidx.annotation.VisibleForTesting;
import androidx.lifecycle.ViewModelProviders;
import com.android.settings.R;
import com.android.settings.wifi.WifiDialogActivity;
import com.android.settings.wifi.qrcode.QrCamera;
import com.android.settings.wifi.qrcode.QrDecorateView;
import com.android.wifitrackerlib.WifiEntry;
@@ -91,7 +90,7 @@ public class WifiDppQrCodeScannerFragment extends WifiDppQrCodeBaseFragment impl
// Key for Bundle usage
private static final String KEY_IS_CONFIGURATOR_MODE = "key_is_configurator_mode";
private static final String KEY_LATEST_ERROR_CODE = "key_latest_error_code";
private static final String KEY_WIFI_CONFIGURATION = "key_wifi_configuration";
public static final String KEY_WIFI_CONFIGURATION = "key_wifi_configuration";
private static final int ARG_RESTART_CAMERA = 1;
@@ -689,8 +688,7 @@ public class WifiDppQrCodeScannerFragment extends WifiDppQrCodeBaseFragment impl
@Override
public void onSuccess() {
final Intent resultIntent = new Intent();
resultIntent.putExtra(WifiDialogActivity.KEY_WIFI_CONFIGURATION,
mEnrolleeWifiConfiguration);
resultIntent.putExtra(KEY_WIFI_CONFIGURATION, mEnrolleeWifiConfiguration);
final Activity hostActivity = getActivity();
hostActivity.setResult(Activity.RESULT_OK, resultIntent);

View File

@@ -19,61 +19,74 @@ package com.android.settings.wifi.slice;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.text.TextUtils;
import android.widget.Toast;
import androidx.annotation.VisibleForTesting;
import com.android.settings.wifi.WifiConnectListener;
import com.android.settings.R;
import com.android.settings.slices.SliceBackgroundWorker;
import com.android.settings.wifi.WifiDialogActivity;
import com.android.settings.wifi.WifiUtils;
import com.android.settingslib.wifi.AccessPoint;
import com.android.wifitrackerlib.WifiEntry;
import com.android.wifitrackerlib.WifiEntry.ConnectCallback;
/**
* This receiver helps connect to Wi-Fi network
*/
public class ConnectToWifiHandler extends BroadcastReceiver {
static final String KEY_CHOSEN_WIFIENTRY_KEY = "key_chosen_wifientry_key";
static final String KEY_WIFI_SLICE_URI = "key_wifi_slice_uri";
@Override
public void onReceive(Context context, Intent intent) {
if (context == null || intent == null) {
return;
}
final Network network = intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK);
final Bundle accessPointState = intent.getBundleExtra(
WifiDialogActivity.KEY_ACCESS_POINT_STATE);
if (network != null) {
WifiScanWorker.clearClickedWifi();
final ConnectivityManager cm = context.getSystemService(ConnectivityManager.class);
// start captive portal app to sign in to network
cm.startCaptivePortalApp(network);
} else if (accessPointState != null) {
connect(context, new AccessPoint(context, accessPointState));
final String key = intent.getStringExtra(KEY_CHOSEN_WIFIENTRY_KEY);
if (TextUtils.isEmpty(key)) {
return;
}
if (intent.getParcelableExtra(KEY_WIFI_SLICE_URI) == null) {
return;
}
final WifiScanWorker worker = getWifiScanWorker(intent);
if (worker == null) {
return;
}
final WifiEntry wifiEntry = worker.getWifiEntry(key);
if (wifiEntry == null) {
return;
}
wifiEntry.connect(new WifiEntryConnectCallback(context, wifiEntry));
}
@VisibleForTesting
void connect(Context context, AccessPoint accessPoint) {
ContextualWifiScanWorker.saveSession();
WifiScanWorker.saveClickedWifi(accessPoint);
WifiScanWorker getWifiScanWorker(Intent intent) {
return SliceBackgroundWorker.getInstance(intent.getParcelableExtra(KEY_WIFI_SLICE_URI));
}
final WifiConnectListener connectListener = new WifiConnectListener(context);
switch (WifiUtils.getConnectingType(accessPoint)) {
case WifiUtils.CONNECT_TYPE_OSU_PROVISION:
accessPoint.startOsuProvisioning(connectListener);
break;
@VisibleForTesting
static class WifiEntryConnectCallback implements WifiEntry.ConnectCallback {
final Context mContext;
final WifiEntry mWifiEntry;
case WifiUtils.CONNECT_TYPE_OPEN_NETWORK:
accessPoint.generateOpenNetworkConfig();
WifiEntryConnectCallback(Context context, WifiEntry connectWifiEntry) {
mContext = context;
mWifiEntry = connectWifiEntry;
}
case WifiUtils.CONNECT_TYPE_SAVED_NETWORK:
final WifiManager wifiManager = context.getSystemService(WifiManager.class);
wifiManager.connect(accessPoint.getConfig(), connectListener);
break;
@Override
public void onConnectResult(@ConnectStatus int status) {
if (status == ConnectCallback.CONNECT_STATUS_FAILURE_NO_CONFIG) {
final Intent intent = new Intent(mContext, WifiDialogActivity.class)
.putExtra(WifiDialogActivity.KEY_CHOSEN_WIFIENTRY_KEY, mWifiEntry.getKey());
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent);
} else if (status == CONNECT_STATUS_FAILURE_UNKNOWN) {
Toast.makeText(mContext, R.string.wifi_failed_connect_message,
Toast.LENGTH_SHORT).show();
}
}
}
}

View File

@@ -18,7 +18,6 @@ package com.android.settings.wifi.slice;
import android.content.Context;
import android.net.Uri;
import android.os.SystemClock;
import com.android.settings.slices.SliceBackgroundWorker;
@@ -27,44 +26,12 @@ import com.android.settings.slices.SliceBackgroundWorker;
*/
public class ContextualWifiScanWorker extends WifiScanWorker {
private static long sVisibleUiSessionToken;
private static long sActiveSession;
public ContextualWifiScanWorker(Context context, Uri uri) {
super(context, uri);
}
/**
* Starts a new visible UI session for the purpose of automatically starting captive portal.
*
* A visible UI session is defined as a duration of time when a UI screen is visible to user.
* Going to a sub-page and coming out breaks the continuation, leaving the page and coming back
* breaks it too.
*/
public static void newVisibleUiSession() {
sVisibleUiSessionToken = SystemClock.elapsedRealtime();
}
static void saveSession() {
sActiveSession = sVisibleUiSessionToken;
}
@Override
protected void clearClickedWifiOnSliceUnpinned() {
// Do nothing for contextual Wi-Fi slice
}
@Override
protected boolean isSessionValid() {
if (sVisibleUiSessionToken != sActiveSession) {
clearClickedWifi();
return false;
}
return true;
}
@Override
protected int getApRowCount() {
return ContextualWifiSlice.getApRowCount();
}
}
}

View File

@@ -18,10 +18,8 @@ package com.android.settings.wifi.slice;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.net.ConnectivityManager;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.net.NetworkInfo.DetailedState;
import android.net.NetworkInfo.State;
import android.net.Uri;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
@@ -37,7 +35,7 @@ import com.android.settings.Utils;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.slices.CustomSliceRegistry;
import com.android.settings.slices.CustomSliceable;
import com.android.settingslib.wifi.AccessPoint;
import com.android.wifitrackerlib.WifiEntry;
/**
* {@link CustomSliceable} for Wi-Fi, used by contextual homepage.
@@ -52,8 +50,12 @@ public class ContextualWifiSlice extends WifiSlice {
@VisibleForTesting
static boolean sApRowCollapsed;
private final ConnectivityManager mConnectivityManager;
public ContextualWifiSlice(Context context) {
super(context);
mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
}
@Override
@@ -84,16 +86,17 @@ public class ContextualWifiSlice extends WifiSlice {
}
@Override
protected ListBuilder.RowBuilder getHeaderRow(boolean isWifiEnabled, AccessPoint accessPoint) {
final ListBuilder.RowBuilder builder = super.getHeaderRow(isWifiEnabled, accessPoint);
builder.setTitleItem(getHeaderIcon(isWifiEnabled, accessPoint), ListBuilder.ICON_IMAGE);
protected ListBuilder.RowBuilder getHeaderRow(boolean isWifiEnabled,
WifiSliceItem wifiSliceItem) {
final ListBuilder.RowBuilder builder = super.getHeaderRow(isWifiEnabled, wifiSliceItem);
builder.setTitleItem(getHeaderIcon(isWifiEnabled, wifiSliceItem), ListBuilder.ICON_IMAGE);
if (sApRowCollapsed) {
builder.setSubtitle(getSubtitle(accessPoint));
builder.setSubtitle(getHeaderSubtitle(wifiSliceItem));
}
return builder;
}
private IconCompat getHeaderIcon(boolean isWifiEnabled, AccessPoint accessPoint) {
private IconCompat getHeaderIcon(boolean isWifiEnabled, WifiSliceItem wifiSliceItem) {
final Drawable drawable;
final int tint;
if (!isWifiEnabled) {
@@ -103,7 +106,8 @@ public class ContextualWifiSlice extends WifiSlice {
} else {
// get icon of medium signal strength
drawable = mContext.getDrawable(com.android.settingslib.Utils.getWifiIconResource(2));
if (isNetworkConnected(accessPoint)) {
if (wifiSliceItem != null
&& wifiSliceItem.getConnectedState() == WifiEntry.CONNECTED_STATE_CONNECTED) {
tint = Utils.getColorAccentDefaultColor(mContext);
} else {
tint = Utils.getColorAttrDefaultColor(mContext, android.R.attr.colorControlNormal);
@@ -113,49 +117,16 @@ public class ContextualWifiSlice extends WifiSlice {
return Utils.createIconWithDrawable(drawable);
}
private boolean isNetworkConnected(AccessPoint accessPoint) {
if (accessPoint == null) {
return false;
}
final NetworkInfo networkInfo = accessPoint.getNetworkInfo();
if (networkInfo == null) {
return false;
}
return networkInfo.getState() == State.CONNECTED;
}
private CharSequence getSubtitle(AccessPoint accessPoint) {
if (isCaptivePortal()) {
final int id = mContext.getResources()
.getIdentifier("network_available_sign_in", "string", "android");
return mContext.getText(id);
}
if (accessPoint == null) {
private CharSequence getHeaderSubtitle(WifiSliceItem wifiSliceItem) {
if (wifiSliceItem == null
|| wifiSliceItem.getConnectedState() == WifiEntry.CONNECTED_STATE_DISCONNECTED) {
return mContext.getText(R.string.disconnected);
}
final NetworkInfo networkInfo = accessPoint.getNetworkInfo();
if (networkInfo == null) {
return mContext.getText(R.string.disconnected);
if (wifiSliceItem.getConnectedState() == WifiEntry.CONNECTED_STATE_CONNECTING) {
return mContext.getString(R.string.wifi_connecting_to_message,
wifiSliceItem.getTitle());
}
final State state = networkInfo.getState();
DetailedState detailedState;
if (state == State.CONNECTING) {
detailedState = DetailedState.CONNECTING;
} else if (state == State.CONNECTED) {
detailedState = DetailedState.CONNECTED;
} else {
detailedState = networkInfo.getDetailedState();
}
final String[] formats = mContext.getResources().getStringArray(
R.array.wifi_status_with_ssid);
final int index = detailedState.ordinal();
return String.format(formats[index], accessPoint.getTitle());
return mContext.getString(R.string.wifi_connected_to_message, wifiSliceItem.getTitle());
}
private boolean hasWorkingNetwork() {

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() {
notifySliceChange();
}
/**
* Update the results when data changes
*/
@Override
public void onConnectedChanged() {
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 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);
public void onUpdated() {
updateResults();
}
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);
}
}

View File

@@ -29,9 +29,6 @@ import android.content.Intent;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.net.ConnectivityManager;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.net.Uri;
import android.net.wifi.WifiManager;
import android.os.Bundle;
@@ -52,9 +49,8 @@ import com.android.settings.slices.SliceBackgroundWorker;
import com.android.settings.slices.SliceBuilderUtils;
import com.android.settings.wifi.WifiDialogActivity;
import com.android.settings.wifi.WifiSettings;
import com.android.settings.wifi.WifiUtils;
import com.android.settings.wifi.details.WifiNetworkDetailsFragment;
import com.android.settingslib.wifi.AccessPoint;
import com.android.settings.wifi.details2.WifiNetworkDetailsFragment2;
import com.android.wifitrackerlib.WifiEntry;
import java.util.Arrays;
import java.util.List;
@@ -71,12 +67,10 @@ public class WifiSlice implements CustomSliceable {
protected final Context mContext;
protected final WifiManager mWifiManager;
protected final ConnectivityManager mConnectivityManager;
public WifiSlice(Context context) {
mContext = context;
mWifiManager = mContext.getSystemService(WifiManager.class);
mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
}
@Override
@@ -87,17 +81,16 @@ public class WifiSlice implements CustomSliceable {
@Override
public Slice getSlice() {
final boolean isWifiEnabled = isWifiEnabled();
ListBuilder listBuilder = getListBuilder(isWifiEnabled, null /* accessPoint */);
ListBuilder listBuilder = getListBuilder(isWifiEnabled, null /* wifiSliceItem */);
if (!isWifiEnabled) {
WifiScanWorker.clearClickedWifi();
return listBuilder.build();
}
final WifiScanWorker worker = SliceBackgroundWorker.getInstance(getUri());
final List<AccessPoint> apList = worker != null ? worker.getResults() : null;
final List<WifiSliceItem> apList = worker != null ? worker.getResults() : null;
final int apCount = apList == null ? 0 : apList.size();
final boolean isFirstApActive = apCount > 0 && apList.get(0).isActive();
handleNetworkCallback(worker, isFirstApActive);
final boolean isFirstApActive = apCount > 0
&& apList.get(0).getConnectedState() != WifiEntry.CONNECTED_STATE_DISCONNECTED;
if (isFirstApActive) {
// refresh header subtext
@@ -112,7 +105,7 @@ public class WifiSlice implements CustomSliceable {
final CharSequence placeholder = mContext.getText(R.string.summary_placeholder);
for (int i = 0; i < DEFAULT_EXPANDED_ROW_COUNT; i++) {
if (i < apCount) {
listBuilder.addRow(getAccessPointRow(apList.get(i)));
listBuilder.addRow(getWifiSliceItemRow(apList.get(i)));
} else if (i == apCount) {
listBuilder.addRow(getLoadingRow(placeholder));
} else {
@@ -124,22 +117,12 @@ public class WifiSlice implements CustomSliceable {
return listBuilder.build();
}
private void handleNetworkCallback(WifiScanWorker worker, boolean isFirstApActive) {
if (worker == null) {
return;
}
if (isFirstApActive) {
worker.registerNetworkCallback(mWifiManager.getCurrentNetwork());
} else {
worker.unregisterNetworkCallback();
}
}
protected boolean isApRowCollapsed() {
return false;
}
protected ListBuilder.RowBuilder getHeaderRow(boolean isWifiEnabled, AccessPoint accessPoint) {
protected ListBuilder.RowBuilder getHeaderRow(boolean isWifiEnabled,
WifiSliceItem wifiSliceItem) {
final IconCompat icon = IconCompat.createWithResource(mContext,
R.drawable.ic_settings_wireless);
final String title = mContext.getString(R.string.wifi_settings);
@@ -152,115 +135,90 @@ public class WifiSlice implements CustomSliceable {
.setPrimaryAction(primarySliceAction);
}
private ListBuilder getListBuilder(boolean isWifiEnabled, AccessPoint accessPoint) {
private ListBuilder getListBuilder(boolean isWifiEnabled, WifiSliceItem wifiSliceItem) {
final PendingIntent toggleAction = getBroadcastIntent(mContext);
final SliceAction toggleSliceAction = SliceAction.createToggle(toggleAction,
null /* actionTitle */, isWifiEnabled);
final ListBuilder builder = new ListBuilder(mContext, getUri(), ListBuilder.INFINITY)
.setAccentColor(COLOR_NOT_TINTED)
.setKeywords(getKeywords())
.addRow(getHeaderRow(isWifiEnabled, accessPoint))
.addRow(getHeaderRow(isWifiEnabled, wifiSliceItem))
.addAction(toggleSliceAction);
return builder;
}
private ListBuilder.RowBuilder getAccessPointRow(AccessPoint accessPoint) {
final boolean isCaptivePortal = accessPoint.isActive() && isCaptivePortal();
final CharSequence title = accessPoint.getTitle();
final CharSequence summary = getAccessPointSummary(accessPoint, isCaptivePortal);
final IconCompat levelIcon = getAccessPointLevelIcon(accessPoint);
private ListBuilder.RowBuilder getWifiSliceItemRow(WifiSliceItem wifiSliceItem) {
final CharSequence title = wifiSliceItem.getTitle();
final IconCompat levelIcon = getWifiSliceItemLevelIcon(wifiSliceItem);
final ListBuilder.RowBuilder rowBuilder = new ListBuilder.RowBuilder()
.setTitleItem(levelIcon, ListBuilder.ICON_IMAGE)
.setTitle(title)
.setSubtitle(summary)
.setPrimaryAction(getAccessPointAction(accessPoint, isCaptivePortal, levelIcon,
title));
.setSubtitle(wifiSliceItem.getSummary())
.setContentDescription(wifiSliceItem.getContentDescription())
.setPrimaryAction(getWifiEntryAction(wifiSliceItem, levelIcon, title));
if (isCaptivePortal) {
rowBuilder.addEndItem(getCaptivePortalEndAction(accessPoint, title));
} else {
final IconCompat endIcon = getEndIcon(accessPoint);
if (endIcon != null) {
rowBuilder.addEndItem(endIcon, ListBuilder.ICON_IMAGE);
}
final IconCompat endIcon = getEndIcon(wifiSliceItem);
if (endIcon != null) {
rowBuilder.addEndItem(endIcon, ListBuilder.ICON_IMAGE);
}
return rowBuilder;
}
private CharSequence getAccessPointSummary(AccessPoint accessPoint, boolean isCaptivePortal) {
if (isCaptivePortal) {
return mContext.getText(R.string.wifi_tap_to_sign_in);
}
final CharSequence summary = accessPoint.getSettingsSummary();
return TextUtils.isEmpty(summary) ? mContext.getText(R.string.disconnected) : summary;
}
private IconCompat getAccessPointLevelIcon(AccessPoint accessPoint) {
private IconCompat getWifiSliceItemLevelIcon(WifiSliceItem wifiSliceItem) {
final @ColorInt int tint;
if (accessPoint.isActive()) {
final NetworkInfo.State state = accessPoint.getNetworkInfo().getState();
if (state == NetworkInfo.State.CONNECTED) {
tint = Utils.getColorAccentDefaultColor(mContext);
} else { // connecting
tint = Utils.getDisabled(mContext, Utils.getColorAttrDefaultColor(mContext,
android.R.attr.colorControlNormal));
}
} else {
if (wifiSliceItem.getConnectedState() == WifiEntry.CONNECTED_STATE_CONNECTED) {
tint = Utils.getColorAccentDefaultColor(mContext);
} else if (wifiSliceItem.getConnectedState() == WifiEntry.CONNECTED_STATE_DISCONNECTED) {
tint = Utils.getColorAttrDefaultColor(mContext, android.R.attr.colorControlNormal);
} else {
tint = Utils.getDisabled(mContext, Utils.getColorAttrDefaultColor(mContext,
android.R.attr.colorControlNormal));
}
final Drawable drawable = mContext.getDrawable(
com.android.settingslib.Utils.getWifiIconResource(accessPoint.getLevel()));
com.android.settingslib.Utils.getWifiIconResource(wifiSliceItem.getLevel()));
drawable.setTint(tint);
return Utils.createIconWithDrawable(drawable);
}
private IconCompat getEndIcon(AccessPoint accessPoint) {
if (accessPoint.isActive()) {
private IconCompat getEndIcon(WifiSliceItem wifiSliceItem) {
if (wifiSliceItem.getConnectedState() != WifiEntry.CONNECTED_STATE_DISCONNECTED) {
return null;
} else if (accessPoint.getSecurity() != AccessPoint.SECURITY_NONE) {
}
if (wifiSliceItem.getSecurity() != WifiEntry.SECURITY_NONE) {
return IconCompat.createWithResource(mContext, R.drawable.ic_friction_lock_closed);
} else if (accessPoint.isMetered()) {
return IconCompat.createWithResource(mContext, R.drawable.ic_friction_money);
}
return null;
}
private SliceAction getCaptivePortalEndAction(AccessPoint accessPoint, CharSequence title) {
return getAccessPointAction(accessPoint, false /* isCaptivePortal */,
IconCompat.createWithResource(mContext, R.drawable.ic_settings_accent), title);
}
private SliceAction getWifiEntryAction(WifiSliceItem wifiSliceItem, IconCompat icon,
CharSequence title) {
final int requestCode = wifiSliceItem.getKey().hashCode();
private SliceAction getAccessPointAction(AccessPoint accessPoint, boolean isCaptivePortal,
IconCompat icon, CharSequence title) {
final int requestCode = accessPoint.hashCode();
if (isCaptivePortal) {
final Intent intent = new Intent(mContext, ConnectToWifiHandler.class)
.putExtra(ConnectivityManager.EXTRA_NETWORK, mWifiManager.getCurrentNetwork());
return getBroadcastAction(requestCode, intent, icon, title);
}
final Bundle extras = new Bundle();
accessPoint.saveWifiState(extras);
if (accessPoint.isActive()) {
if (wifiSliceItem.getConnectedState() != WifiEntry.CONNECTED_STATE_DISCONNECTED) {
final Bundle bundle = new Bundle();
bundle.putString(WifiNetworkDetailsFragment2.KEY_CHOSEN_WIFIENTRY_KEY,
wifiSliceItem.getKey());
final Intent intent = new SubSettingLauncher(mContext)
.setTitleRes(R.string.pref_title_network_details)
.setDestination(WifiNetworkDetailsFragment.class.getName())
.setArguments(extras)
.setDestination(WifiNetworkDetailsFragment2.class.getName())
.setArguments(bundle)
.setSourceMetricsCategory(SettingsEnums.WIFI)
.toIntent();
return getActivityAction(requestCode, intent, icon, title);
} else if (WifiUtils.getConnectingType(accessPoint) != WifiUtils.CONNECT_TYPE_OTHERS) {
final Intent intent = new Intent(mContext, ConnectToWifiHandler.class)
.putExtra(WifiDialogActivity.KEY_ACCESS_POINT_STATE, extras);
return getBroadcastAction(requestCode, intent, icon, title);
} else {
}
if (wifiSliceItem.shouldEditBeforeConnect()) {
final Intent intent = new Intent(mContext, WifiDialogActivity.class)
.putExtra(WifiDialogActivity.KEY_ACCESS_POINT_STATE, extras);
.putExtra(WifiDialogActivity.KEY_CHOSEN_WIFIENTRY_KEY, wifiSliceItem.getKey());
return getActivityAction(requestCode, intent, icon, title);
}
final Intent intent = new Intent(mContext, ConnectToWifiHandler.class)
.putExtra(ConnectToWifiHandler.KEY_CHOSEN_WIFIENTRY_KEY, wifiSliceItem.getKey())
.putExtra(ConnectToWifiHandler.KEY_WIFI_SLICE_URI, getUri());
return getBroadcastAction(requestCode, intent, icon, title);
}
private SliceAction getActivityAction(int requestCode, Intent intent, IconCompat icon,
@@ -291,12 +249,6 @@ public class WifiSlice implements CustomSliceable {
.setSubtitle(title);
}
protected boolean isCaptivePortal() {
final NetworkCapabilities nc = mConnectivityManager.getNetworkCapabilities(
mWifiManager.getCurrentNetwork());
return WifiUtils.canSignIntoNetwork(nc);
}
/**
* Update the current wifi status to the boolean value keyed by
* {@link android.app.slice.Slice#EXTRA_TOGGLE_STATE} on {@param intent}.

View File

@@ -0,0 +1,135 @@
/*
* Copyright (C) 2020 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 android.content.Context;
import android.text.TextUtils;
import com.android.settingslib.R;
import com.android.wifitrackerlib.WifiEntry;
/**
* The data set which is needed by a Wi-Fi Slice, it collects necessary data from {@link WifiEntry}
* and provides similar getter methods for corresponding data.
*/
public class WifiSliceItem {
private final Context mContext;
private final String mKey;
private final String mTitle;
private final int mSecurity;
private final int mConnectedState;
private final int mLevel;
private final boolean mShouldEditBeforeConnect;
private final String mSummary;
// These values must be kept within [WifiEntry.WIFI_LEVEL_MIN, WifiEntry.WIFI_LEVEL_MAX]
private static final int[] WIFI_CONNECTION_STRENGTH = {
R.string.accessibility_no_wifi,
R.string.accessibility_wifi_one_bar,
R.string.accessibility_wifi_two_bars,
R.string.accessibility_wifi_three_bars,
R.string.accessibility_wifi_signal_full
};
public WifiSliceItem(Context context, WifiEntry wifiEntry) {
mContext = context;
mKey = wifiEntry.getKey();
mTitle = wifiEntry.getTitle();
mSecurity = wifiEntry.getSecurity();
mConnectedState = wifiEntry.getConnectedState();
mLevel = wifiEntry.getLevel();
mShouldEditBeforeConnect = wifiEntry.shouldEditBeforeConnect();
mSummary = wifiEntry.getSummary(false /* concise */);
}
@Override
public boolean equals(Object other) {
if (!(other instanceof WifiSliceItem)) {
return false;
}
final WifiSliceItem otherItem = (WifiSliceItem) other;
if (!TextUtils.equals(getKey(), otherItem.getKey())) {
return false;
}
if (getConnectedState() != otherItem.getConnectedState()) {
return false;
}
if (getLevel() != otherItem.getLevel()) {
return false;
}
if (!TextUtils.equals(getSummary(), otherItem.getSummary())) {
return false;
}
return true;
}
public String getKey() {
return mKey;
}
public String getTitle() {
return mTitle;
}
public int getSecurity() {
return mSecurity;
}
public int getConnectedState() {
return mConnectedState;
}
public int getLevel() {
return mLevel;
}
/**
* In Wi-Fi picker, when users click a saved network, it will connect to the Wi-Fi network.
* However, for some special cases, Wi-Fi picker should show Wi-Fi editor UI for users to edit
* security or password before connecting. Or users will always get connection fail results.
*/
public boolean shouldEditBeforeConnect() {
return mShouldEditBeforeConnect;
}
/**
* Returns a 'NOT' concise summary, this is different from WifiEntry#getSummary().
*/
public String getSummary() {
return mSummary;
}
/**
* This method has similar code as WifiEntryPreference#buildContentDescription().
* TODO(b/154191825): Adds WifiEntry#getContentDescription() to replace the duplicate code.
*/
public CharSequence getContentDescription() {
CharSequence contentDescription = mTitle;
if (!TextUtils.isEmpty(mSummary)) {
contentDescription = TextUtils.concat(contentDescription, ",", mSummary);
}
if (mLevel >= 0 && mLevel < WIFI_CONNECTION_STRENGTH.length) {
contentDescription = TextUtils.concat(contentDescription, ",",
mContext.getString(WIFI_CONNECTION_STRENGTH[mLevel]));
}
return TextUtils.concat(contentDescription, ",", mSecurity == WifiEntry.SECURITY_NONE
? mContext.getString(R.string.accessibility_wifi_security_type_none)
: mContext.getString(R.string.accessibility_wifi_security_type_secured));
}
}