diff --git a/res/values/strings.xml b/res/values/strings.xml index 00cdf40bd6f..b49557ec291 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -2195,8 +2195,6 @@ Wi\u2011Fi turned on Connected to %1$s - - Connecting to %1$s Connecting\u2026 diff --git a/src/com/android/settings/homepage/contextualcards/ContextualCardsFragment.java b/src/com/android/settings/homepage/contextualcards/ContextualCardsFragment.java index 3b75ebf420e..9f0023aa15c 100644 --- a/src/com/android/settings/homepage/contextualcards/ContextualCardsFragment.java +++ b/src/com/android/settings/homepage/contextualcards/ContextualCardsFragment.java @@ -40,6 +40,7 @@ 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 { @@ -79,6 +80,7 @@ public class ContextualCardsFragment extends InstrumentedFragment implements super.onStart(); registerScreenOffReceiver(); registerKeyEventReceiver(); + ContextualWifiScanWorker.newVisibleUiSession(); mContextualCardManager.loadContextualCards(LoaderManager.getInstance(this), sRestartLoaderNeeded); sRestartLoaderNeeded = false; diff --git a/src/com/android/settings/wifi/AddNetworkFragment.java b/src/com/android/settings/wifi/AddNetworkFragment.java index 31dc21fea29..7e17e380e79 100644 --- a/src/com/android/settings/wifi/AddNetworkFragment.java +++ b/src/com/android/settings/wifi/AddNetworkFragment.java @@ -32,7 +32,6 @@ 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; /** @@ -117,7 +116,7 @@ public class AddNetworkFragment extends InstrumentedFragment implements WifiConf } final WifiConfiguration config = data.getParcelableExtra( - WifiDppQrCodeScannerFragment.KEY_WIFI_CONFIGURATION); + WifiDialogActivity.KEY_WIFI_CONFIGURATION); successfullyFinish(config); } } diff --git a/src/com/android/settings/wifi/WifiDialogActivity.java b/src/com/android/settings/wifi/WifiDialogActivity.java index b25d13f7520..77827867630 100644 --- a/src/com/android/settings/wifi/WifiDialogActivity.java +++ b/src/com/android/settings/wifi/WifiDialogActivity.java @@ -16,56 +16,51 @@ package com.android.settings.wifi; +import android.app.Activity; import android.content.DialogInterface; import android.content.Intent; -import android.net.ConnectivityManager; -import android.net.NetworkScoreManager; +import android.net.NetworkInfo; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiManager; +import android.net.wifi.WifiManager.ActionListener; import android.os.Bundle; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.Looper; -import android.os.Process; -import android.os.SimpleClock; -import android.os.SystemClock; +import android.util.Log; + +import androidx.annotation.VisibleForTesting; import com.android.settings.R; import com.android.settings.SetupWizardUtils; import com.android.settings.wifi.dpp.WifiDppUtils; -import com.android.settingslib.core.lifecycle.ObservableActivity; -import com.android.wifitrackerlib.NetworkDetailsTracker; -import com.android.wifitrackerlib.WifiEntry; +import com.android.settingslib.wifi.AccessPoint; import com.google.android.setupcompat.util.WizardManagerHelper; -import java.time.Clock; -import java.time.ZoneOffset; - -/** - * The activity shows a CONNECT_MODE Wi-fi editor dialog. - */ -public class WifiDialogActivity extends ObservableActivity implements - WifiDialog2.WifiDialog2Listener, DialogInterface.OnDismissListener { +public class WifiDialogActivity extends Activity implements WifiDialog.WifiDialogListener, + DialogInterface.OnDismissListener { private static final String TAG = "WifiDialogActivity"; - public static final String KEY_CHOSEN_WIFIENTRY_KEY = "key_chosen_wifientry_key"; + public static final String KEY_ACCESS_POINT_STATE = "access_point_state"; + + /** + * Boolean extra indicating whether this activity should connect to an access point on the + * caller's behalf. If this is set to false, the caller should check + * {@link #KEY_WIFI_CONFIGURATION} in the result data and save that using + * {@link WifiManager#connect(WifiConfiguration, ActionListener)}. Default is true. + */ + @VisibleForTesting + static final String KEY_CONNECT_FOR_CALLER = "connect_for_caller"; + + public static final String KEY_WIFI_CONFIGURATION = "wifi_configuration"; private static final int RESULT_CONNECTED = RESULT_FIRST_USER; private static final int RESULT_FORGET = RESULT_FIRST_USER + 1; private static final int REQUEST_CODE_WIFI_DPP_ENROLLEE_QR_CODE_SCANNER = 0; - // Max age of tracked WifiEntries. - private static final long MAX_SCAN_AGE_MILLIS = 15_000; - // Interval between initiating NetworkDetailsTracker scans. - private static final long SCAN_INTERVAL_MILLIS = 10_000; + private WifiDialog mDialog; - private WifiDialog2 mDialog; private Intent mIntent; - private NetworkDetailsTracker mNetworkDetailsTracker; - private HandlerThread mWorkerThread; @Override protected void onCreate(Bundle savedInstanceState) { @@ -76,43 +71,18 @@ public class WifiDialogActivity extends ObservableActivity implements super.onCreate(savedInstanceState); - mWorkerThread = new HandlerThread( - TAG + "{" + Integer.toHexString(System.identityHashCode(this)) + "}", - Process.THREAD_PRIORITY_BACKGROUND); - mWorkerThread.start(); - final Clock elapsedRealtimeClock = new SimpleClock(ZoneOffset.UTC) { - @Override - public long millis() { - return SystemClock.elapsedRealtime(); - } - }; - mNetworkDetailsTracker = NetworkDetailsTracker.createNetworkDetailsTracker( - getLifecycle(), - this, - getSystemService(WifiManager.class), - getSystemService(ConnectivityManager.class), - getSystemService(NetworkScoreManager.class), - new Handler(Looper.getMainLooper()), - mWorkerThread.getThreadHandler(), - elapsedRealtimeClock, - MAX_SCAN_AGE_MILLIS, - SCAN_INTERVAL_MILLIS, - mIntent.getStringExtra(KEY_CHOSEN_WIFIENTRY_KEY)); - } - - @Override - protected void onStart() { - super.onStart(); - if (mDialog != null) { - return; + final Bundle accessPointState = mIntent.getBundleExtra(KEY_ACCESS_POINT_STATE); + AccessPoint accessPoint = null; + if (accessPointState != null) { + accessPoint = new AccessPoint(this, accessPointState); } if (WizardManagerHelper.isAnySetupWizard(getIntent())) { - mDialog = WifiDialog2.createModal(this, this, mNetworkDetailsTracker.getWifiEntry(), - WifiConfigUiBase2.MODE_CONNECT, R.style.SuwAlertDialogThemeCompat_Light); + mDialog = WifiDialog.createModal(this, this, accessPoint, + WifiConfigUiBase.MODE_CONNECT, R.style.SuwAlertDialogThemeCompat_Light); } else { - mDialog = WifiDialog2.createModal(this, this, mNetworkDetailsTracker.getWifiEntry(), - WifiConfigUiBase2.MODE_CONNECT); + mDialog = WifiDialog.createModal( + this, this, accessPoint, WifiConfigUiBase.MODE_CONNECT); } mDialog.show(); mDialog.setOnDismissListener(this); @@ -120,44 +90,82 @@ public class WifiDialogActivity extends ObservableActivity implements @Override public void finish() { - overridePendingTransition(0, 0); - super.finish(); + overridePendingTransition(0, 0); } @Override public void onDestroy() { + super.onDestroy(); if (mDialog != null && mDialog.isShowing()) { mDialog.dismiss(); mDialog = null; } - mWorkerThread.quit(); - - super.onDestroy(); } @Override - public void onForget(WifiDialog2 dialog) { - final WifiEntry wifiEntry = dialog.getController().getWifiEntry(); - if (wifiEntry != null && wifiEntry.canForget()) { - wifiEntry.forget(null /* callback */); + public void onForget(WifiDialog dialog) { + final WifiManager wifiManager = getSystemService(WifiManager.class); + final AccessPoint accessPoint = dialog.getController().getAccessPoint(); + if (accessPoint != null) { + if (!accessPoint.isSaved()) { + if (accessPoint.getNetworkInfo() != null && + accessPoint.getNetworkInfo().getState() != NetworkInfo.State.DISCONNECTED) { + // Network is active but has no network ID - must be ephemeral. + wifiManager.disableEphemeralNetwork( + AccessPoint.convertToQuotedString(accessPoint.getSsidStr())); + } else { + // Should not happen, but a monkey seems to trigger it + Log.e(TAG, "Failed to forget invalid network " + accessPoint.getConfig()); + } + } else { + wifiManager.forget(accessPoint.getConfig().networkId, null /* listener */); + } } + Intent resultData = new Intent(); + if (accessPoint != null) { + Bundle accessPointState = new Bundle(); + accessPoint.saveWifiState(accessPointState); + resultData.putExtra(KEY_ACCESS_POINT_STATE, accessPointState); + } setResult(RESULT_FORGET); finish(); } @Override - public void onSubmit(WifiDialog2 dialog) { - final WifiEntry wifiEntry = dialog.getController().getWifiEntry(); + public void onSubmit(WifiDialog dialog) { final WifiConfiguration config = dialog.getController().getConfig(); - if (config == null && wifiEntry != null && wifiEntry.canConnect()) { - wifiEntry.connect(null /* callback */); - } else { - getSystemService(WifiManager.class).connect(config, null /* listener */); + final AccessPoint accessPoint = dialog.getController().getAccessPoint(); + final WifiManager wifiManager = getSystemService(WifiManager.class); + + if (getIntent().getBooleanExtra(KEY_CONNECT_FOR_CALLER, true)) { + if (config == null) { + if (accessPoint != null && accessPoint.isSaved()) { + wifiManager.connect(accessPoint.getConfig(), null /* listener */); + } + } else { + wifiManager.save(config, null /* listener */); + if (accessPoint != null) { + // accessPoint is null for "Add network" + NetworkInfo networkInfo = accessPoint.getNetworkInfo(); + if (networkInfo == null || !networkInfo.isConnected()) { + wifiManager.connect(config, null /* listener */); + } + } + } } - setResult(RESULT_CONNECTED); + Intent resultData = new Intent(); + if (accessPoint != null) { + Bundle accessPointState = new Bundle(); + accessPoint.saveWifiState(accessPointState); + resultData.putExtra(KEY_ACCESS_POINT_STATE, accessPointState); + } + if (config != null) { + resultData.putExtra(KEY_WIFI_CONFIGURATION, config); + } + setResult(RESULT_CONNECTED, resultData); finish(); } @@ -168,7 +176,7 @@ public class WifiDialogActivity extends ObservableActivity implements } @Override - public void onScan(WifiDialog2 dialog, String ssid) { + public void onScan(WifiDialog dialog, String ssid) { Intent intent = WifiDppUtils.getEnrolleeQrCodeScannerIntent(ssid); WizardManagerHelper.copyWizardManagerExtras(mIntent, intent); diff --git a/src/com/android/settings/wifi/dpp/WifiDppQrCodeScannerFragment.java b/src/com/android/settings/wifi/dpp/WifiDppQrCodeScannerFragment.java index cee3ccdeb08..accef12364f 100644 --- a/src/com/android/settings/wifi/dpp/WifiDppQrCodeScannerFragment.java +++ b/src/com/android/settings/wifi/dpp/WifiDppQrCodeScannerFragment.java @@ -57,6 +57,7 @@ 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; @@ -90,7 +91,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"; - public static final String KEY_WIFI_CONFIGURATION = "key_wifi_configuration"; + private static final String KEY_WIFI_CONFIGURATION = "key_wifi_configuration"; private static final int ARG_RESTART_CAMERA = 1; @@ -688,7 +689,8 @@ public class WifiDppQrCodeScannerFragment extends WifiDppQrCodeBaseFragment impl @Override public void onSuccess() { final Intent resultIntent = new Intent(); - resultIntent.putExtra(KEY_WIFI_CONFIGURATION, mEnrolleeWifiConfiguration); + resultIntent.putExtra(WifiDialogActivity.KEY_WIFI_CONFIGURATION, + mEnrolleeWifiConfiguration); final Activity hostActivity = getActivity(); hostActivity.setResult(Activity.RESULT_OK, resultIntent); diff --git a/src/com/android/settings/wifi/slice/ConnectToWifiHandler.java b/src/com/android/settings/wifi/slice/ConnectToWifiHandler.java index 779a57e40f3..5c92d81da22 100644 --- a/src/com/android/settings/wifi/slice/ConnectToWifiHandler.java +++ b/src/com/android/settings/wifi/slice/ConnectToWifiHandler.java @@ -19,74 +19,61 @@ package com.android.settings.wifi.slice; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; -import android.text.TextUtils; -import android.widget.Toast; +import android.net.ConnectivityManager; +import android.net.Network; +import android.net.wifi.WifiManager; +import android.os.Bundle; import androidx.annotation.VisibleForTesting; -import com.android.settings.R; -import com.android.settings.slices.SliceBackgroundWorker; +import com.android.settings.wifi.WifiConnectListener; import com.android.settings.wifi.WifiDialogActivity; -import com.android.wifitrackerlib.WifiEntry; -import com.android.wifitrackerlib.WifiEntry.ConnectCallback; +import com.android.settings.wifi.WifiUtils; +import com.android.settingslib.wifi.AccessPoint; /** * 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 String key = intent.getStringExtra(KEY_CHOSEN_WIFIENTRY_KEY); - if (TextUtils.isEmpty(key)) { - 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)); } - 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 - WifiScanWorker getWifiScanWorker(Intent intent) { - return SliceBackgroundWorker.getInstance(intent.getParcelableExtra(KEY_WIFI_SLICE_URI)); - } + void connect(Context context, AccessPoint accessPoint) { + ContextualWifiScanWorker.saveSession(); + WifiScanWorker.saveClickedWifi(accessPoint); - @VisibleForTesting - static class WifiEntryConnectCallback implements WifiEntry.ConnectCallback { - final Context mContext; - final WifiEntry mWifiEntry; + final WifiConnectListener connectListener = new WifiConnectListener(context); + switch (WifiUtils.getConnectingType(accessPoint)) { + case WifiUtils.CONNECT_TYPE_OSU_PROVISION: + accessPoint.startOsuProvisioning(connectListener); + break; - WifiEntryConnectCallback(Context context, WifiEntry connectWifiEntry) { - mContext = context; - mWifiEntry = connectWifiEntry; - } + case WifiUtils.CONNECT_TYPE_OPEN_NETWORK: + accessPoint.generateOpenNetworkConfig(); - @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(); - } + case WifiUtils.CONNECT_TYPE_SAVED_NETWORK: + final WifiManager wifiManager = context.getSystemService(WifiManager.class); + wifiManager.connect(accessPoint.getConfig(), connectListener); + break; } } } diff --git a/src/com/android/settings/wifi/slice/ContextualWifiScanWorker.java b/src/com/android/settings/wifi/slice/ContextualWifiScanWorker.java index aa73a179d39..616c92fb33a 100644 --- a/src/com/android/settings/wifi/slice/ContextualWifiScanWorker.java +++ b/src/com/android/settings/wifi/slice/ContextualWifiScanWorker.java @@ -18,6 +18,7 @@ package com.android.settings.wifi.slice; import android.content.Context; import android.net.Uri; +import android.os.SystemClock; import com.android.settings.slices.SliceBackgroundWorker; @@ -26,12 +27,44 @@ 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(); } -} +} \ No newline at end of file diff --git a/src/com/android/settings/wifi/slice/ContextualWifiSlice.java b/src/com/android/settings/wifi/slice/ContextualWifiSlice.java index 4806573bf1e..ea9a7452202 100644 --- a/src/com/android/settings/wifi/slice/ContextualWifiSlice.java +++ b/src/com/android/settings/wifi/slice/ContextualWifiSlice.java @@ -18,8 +18,10 @@ 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; @@ -35,7 +37,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.wifitrackerlib.WifiEntry; +import com.android.settingslib.wifi.AccessPoint; /** * {@link CustomSliceable} for Wi-Fi, used by contextual homepage. @@ -50,12 +52,8 @@ 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 @@ -86,17 +84,16 @@ public class ContextualWifiSlice extends WifiSlice { } @Override - protected ListBuilder.RowBuilder getHeaderRow(boolean isWifiEnabled, - WifiSliceItem wifiSliceItem) { - final ListBuilder.RowBuilder builder = super.getHeaderRow(isWifiEnabled, wifiSliceItem); - builder.setTitleItem(getHeaderIcon(isWifiEnabled, wifiSliceItem), ListBuilder.ICON_IMAGE); + protected ListBuilder.RowBuilder getHeaderRow(boolean isWifiEnabled, AccessPoint accessPoint) { + final ListBuilder.RowBuilder builder = super.getHeaderRow(isWifiEnabled, accessPoint); + builder.setTitleItem(getHeaderIcon(isWifiEnabled, accessPoint), ListBuilder.ICON_IMAGE); if (sApRowCollapsed) { - builder.setSubtitle(getHeaderSubtitle(wifiSliceItem)); + builder.setSubtitle(getSubtitle(accessPoint)); } return builder; } - private IconCompat getHeaderIcon(boolean isWifiEnabled, WifiSliceItem wifiSliceItem) { + private IconCompat getHeaderIcon(boolean isWifiEnabled, AccessPoint accessPoint) { final Drawable drawable; final int tint; if (!isWifiEnabled) { @@ -106,8 +103,7 @@ public class ContextualWifiSlice extends WifiSlice { } else { // get icon of medium signal strength drawable = mContext.getDrawable(com.android.settingslib.Utils.getWifiIconResource(2)); - if (wifiSliceItem != null - && wifiSliceItem.getConnectedState() == WifiEntry.CONNECTED_STATE_CONNECTED) { + if (isNetworkConnected(accessPoint)) { tint = Utils.getColorAccentDefaultColor(mContext); } else { tint = Utils.getColorAttrDefaultColor(mContext, android.R.attr.colorControlNormal); @@ -117,16 +113,49 @@ public class ContextualWifiSlice extends WifiSlice { return Utils.createIconWithDrawable(drawable); } - private CharSequence getHeaderSubtitle(WifiSliceItem wifiSliceItem) { - if (wifiSliceItem == null - || wifiSliceItem.getConnectedState() == WifiEntry.CONNECTED_STATE_DISCONNECTED) { + 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) { 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 NetworkInfo networkInfo = accessPoint.getNetworkInfo(); + if (networkInfo == null) { + return mContext.getText(R.string.disconnected); } - return mContext.getString(R.string.wifi_connected_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()); } private boolean hasWorkingNetwork() { diff --git a/src/com/android/settings/wifi/slice/WifiScanWorker.java b/src/com/android/settings/wifi/slice/WifiScanWorker.java index 631faac18ef..9d0f8210624 100644 --- a/src/com/android/settings/wifi/slice/WifiScanWorker.java +++ b/src/com/android/settings/wifi/slice/WifiScanWorker.java @@ -16,187 +16,243 @@ 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.NetworkScoreManager; +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.WifiManager; -import android.os.HandlerThread; -import android.os.Process; -import android.os.SimpleClock; -import android.os.SystemClock; +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 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.utils.ThreadUtils; -import com.android.wifitrackerlib.WifiEntry; -import com.android.wifitrackerlib.WifiEntry.WifiEntryCallback; -import com.android.wifitrackerlib.WifiPickerTracker; +import com.android.settingslib.wifi.AccessPoint; +import com.android.settingslib.wifi.WifiTracker; -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 implements - WifiPickerTracker.WifiPickerTrackerCallback, LifecycleOwner, WifiEntryCallback { +public class WifiScanWorker extends SliceBackgroundWorker implements + WifiTracker.WifiListener { 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; - @VisibleForTesting - final LifecycleRegistry mLifecycleRegistry; - @VisibleForTesting - WifiPickerTracker mWifiPickerTracker; - // Worker thread used for WifiPickerTracker work - private final HandlerThread mWorkerThread; + 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); - - 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); + mContext = context; + mConnectivityManager = context.getSystemService(ConnectivityManager.class); + mWifiTracker = new WifiTracker(mContext, this /* wifiListener */, + true /* includeSaved */, true /* includeScans */); } @Override protected void onSlicePinned() { - mLifecycleRegistry.markState(Lifecycle.State.STARTED); - mLifecycleRegistry.markState(Lifecycle.State.RESUMED); - updateResults(); + mWifiTracker.onStart(); + onAccessPointsChanged(); } @Override protected void onSliceUnpinned() { - mLifecycleRegistry.markState(Lifecycle.State.STARTED); - mLifecycleRegistry.markState(Lifecycle.State.CREATED); + mWifiTracker.onStop(); + unregisterNetworkCallback(); + clearClickedWifiOnSliceUnpinned(); } @Override public void close() { - mLifecycleRegistry.markState(Lifecycle.State.DESTROYED); - mWorkerThread.quit(); + mWifiTracker.onDestroy(); } @Override - 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() { + 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; } - @Override - public void onNumSavedSubscriptionsChanged() { - // Do nothing. + private AccessPoint clone(AccessPoint accessPoint) { + final Bundle savedState = new Bundle(); + accessPoint.saveWifiState(savedState); + return new AccessPoint(mContext, savedState); } @Override - public void onNumSavedNetworksChanged() { - // Do nothing. - } + protected boolean areListsTheSame(List a, List b) { + if (!a.equals(b)) { + 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; - } + // 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 keyWifiEntry; + return true; } - @VisibleForTesting - void updateResults() { - if (mWifiPickerTracker.getWifiState() != WifiManager.WIFI_STATE_ENABLED - || mLifecycleRegistry.getCurrentState() != Lifecycle.State.RESUMED) { - super.updateResults(null); + 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; } - final List resultList = new ArrayList<>(); - final WifiEntry connectedWifiEntry = mWifiPickerTracker.getConnectedWifiEntry(); - if (connectedWifiEntry != null) { - connectedWifiEntry.setListener(this); - resultList.add(new WifiSliceItem(getContext(), connectedWifiEntry)); + if (mNetworkCallback != null && mNetworkCallback.isSameNetwork(wifiNetwork)) { + return; } - for (WifiEntry wifiEntry : mWifiPickerTracker.getWifiEntries()) { - if (resultList.size() >= getApRowCount()) { - break; + + 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); } - if (wifiEntry.getLevel() != WifiEntry.WIFI_LEVEL_UNREACHABLE) { - wifiEntry.setListener(this); - resultList.add(new WifiSliceItem(getContext(), wifiEntry)); + 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); } } - super.updateResults(resultList); + + /** + * 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); + } } } diff --git a/src/com/android/settings/wifi/slice/WifiSlice.java b/src/com/android/settings/wifi/slice/WifiSlice.java index 324d68c870e..a489b05e2e4 100644 --- a/src/com/android/settings/wifi/slice/WifiSlice.java +++ b/src/com/android/settings/wifi/slice/WifiSlice.java @@ -29,6 +29,9 @@ 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; @@ -49,8 +52,9 @@ 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.details2.WifiNetworkDetailsFragment2; -import com.android.wifitrackerlib.WifiEntry; +import com.android.settings.wifi.WifiUtils; +import com.android.settings.wifi.details.WifiNetworkDetailsFragment; +import com.android.settingslib.wifi.AccessPoint; import java.util.Arrays; import java.util.List; @@ -67,10 +71,12 @@ 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 @@ -81,16 +87,17 @@ public class WifiSlice implements CustomSliceable { @Override public Slice getSlice() { final boolean isWifiEnabled = isWifiEnabled(); - ListBuilder listBuilder = getListBuilder(isWifiEnabled, null /* wifiSliceItem */); + ListBuilder listBuilder = getListBuilder(isWifiEnabled, null /* accessPoint */); if (!isWifiEnabled) { + WifiScanWorker.clearClickedWifi(); return listBuilder.build(); } final WifiScanWorker worker = SliceBackgroundWorker.getInstance(getUri()); - final List apList = worker != null ? worker.getResults() : null; + final List apList = worker != null ? worker.getResults() : null; final int apCount = apList == null ? 0 : apList.size(); - final boolean isFirstApActive = apCount > 0 - && apList.get(0).getConnectedState() != WifiEntry.CONNECTED_STATE_DISCONNECTED; + final boolean isFirstApActive = apCount > 0 && apList.get(0).isActive(); + handleNetworkCallback(worker, isFirstApActive); if (isFirstApActive) { // refresh header subtext @@ -105,7 +112,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(getWifiSliceItemRow(apList.get(i))); + listBuilder.addRow(getAccessPointRow(apList.get(i))); } else if (i == apCount) { listBuilder.addRow(getLoadingRow(placeholder)); } else { @@ -117,12 +124,22 @@ 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, - WifiSliceItem wifiSliceItem) { + protected ListBuilder.RowBuilder getHeaderRow(boolean isWifiEnabled, AccessPoint accessPoint) { final IconCompat icon = IconCompat.createWithResource(mContext, R.drawable.ic_settings_wireless); final String title = mContext.getString(R.string.wifi_settings); @@ -135,90 +152,115 @@ public class WifiSlice implements CustomSliceable { .setPrimaryAction(primarySliceAction); } - private ListBuilder getListBuilder(boolean isWifiEnabled, WifiSliceItem wifiSliceItem) { + private ListBuilder getListBuilder(boolean isWifiEnabled, AccessPoint accessPoint) { 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, wifiSliceItem)) + .addRow(getHeaderRow(isWifiEnabled, accessPoint)) .addAction(toggleSliceAction); return builder; } - private ListBuilder.RowBuilder getWifiSliceItemRow(WifiSliceItem wifiSliceItem) { - final CharSequence title = wifiSliceItem.getTitle(); - final IconCompat levelIcon = getWifiSliceItemLevelIcon(wifiSliceItem); + 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); final ListBuilder.RowBuilder rowBuilder = new ListBuilder.RowBuilder() .setTitleItem(levelIcon, ListBuilder.ICON_IMAGE) .setTitle(title) - .setSubtitle(wifiSliceItem.getSummary()) - .setContentDescription(wifiSliceItem.getContentDescription()) - .setPrimaryAction(getWifiEntryAction(wifiSliceItem, levelIcon, title)); + .setSubtitle(summary) + .setPrimaryAction(getAccessPointAction(accessPoint, isCaptivePortal, levelIcon, + title)); - final IconCompat endIcon = getEndIcon(wifiSliceItem); - if (endIcon != null) { - rowBuilder.addEndItem(endIcon, ListBuilder.ICON_IMAGE); + if (isCaptivePortal) { + rowBuilder.addEndItem(getCaptivePortalEndAction(accessPoint, title)); + } else { + final IconCompat endIcon = getEndIcon(accessPoint); + if (endIcon != null) { + rowBuilder.addEndItem(endIcon, ListBuilder.ICON_IMAGE); + } } return rowBuilder; } - private IconCompat getWifiSliceItemLevelIcon(WifiSliceItem wifiSliceItem) { + 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) { final @ColorInt int tint; - 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, + 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 { + tint = Utils.getColorAttrDefaultColor(mContext, android.R.attr.colorControlNormal); } final Drawable drawable = mContext.getDrawable( - com.android.settingslib.Utils.getWifiIconResource(wifiSliceItem.getLevel())); + com.android.settingslib.Utils.getWifiIconResource(accessPoint.getLevel())); drawable.setTint(tint); return Utils.createIconWithDrawable(drawable); } - private IconCompat getEndIcon(WifiSliceItem wifiSliceItem) { - if (wifiSliceItem.getConnectedState() != WifiEntry.CONNECTED_STATE_DISCONNECTED) { + private IconCompat getEndIcon(AccessPoint accessPoint) { + if (accessPoint.isActive()) { return null; - } - - if (wifiSliceItem.getSecurity() != WifiEntry.SECURITY_NONE) { + } else if (accessPoint.getSecurity() != AccessPoint.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 getWifiEntryAction(WifiSliceItem wifiSliceItem, IconCompat icon, - CharSequence title) { - final int requestCode = wifiSliceItem.getKey().hashCode(); + private SliceAction getCaptivePortalEndAction(AccessPoint accessPoint, CharSequence title) { + return getAccessPointAction(accessPoint, false /* isCaptivePortal */, + IconCompat.createWithResource(mContext, R.drawable.ic_settings_accent), title); + } - if (wifiSliceItem.getConnectedState() != WifiEntry.CONNECTED_STATE_DISCONNECTED) { - final Bundle bundle = new Bundle(); - bundle.putString(WifiNetworkDetailsFragment2.KEY_CHOSEN_WIFIENTRY_KEY, - wifiSliceItem.getKey()); + 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()) { final Intent intent = new SubSettingLauncher(mContext) .setTitleRes(R.string.pref_title_network_details) - .setDestination(WifiNetworkDetailsFragment2.class.getName()) - .setArguments(bundle) + .setDestination(WifiNetworkDetailsFragment.class.getName()) + .setArguments(extras) .setSourceMetricsCategory(SettingsEnums.WIFI) .toIntent(); return getActivityAction(requestCode, intent, icon, title); - } - - if (wifiSliceItem.shouldEditBeforeConnect()) { + } 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 { final Intent intent = new Intent(mContext, WifiDialogActivity.class) - .putExtra(WifiDialogActivity.KEY_CHOSEN_WIFIENTRY_KEY, wifiSliceItem.getKey()); + .putExtra(WifiDialogActivity.KEY_ACCESS_POINT_STATE, extras); 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, @@ -249,6 +291,12 @@ 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}. diff --git a/src/com/android/settings/wifi/slice/WifiSliceItem.java b/src/com/android/settings/wifi/slice/WifiSliceItem.java deleted file mode 100644 index 7a0f0d73a20..00000000000 --- a/src/com/android/settings/wifi/slice/WifiSliceItem.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * 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)); - } -} diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowWifiManager.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowWifiManager.java index ea57bf7e89d..80db53e7be1 100644 --- a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowWifiManager.java +++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowWifiManager.java @@ -18,7 +18,6 @@ package com.android.settings.testutils.shadow; import static org.robolectric.RuntimeEnvironment.application; -import android.net.wifi.ScanResult; import android.net.wifi.SoftApConfiguration; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiManager; @@ -82,11 +81,6 @@ public class ShadowWifiManager extends org.robolectric.shadows.ShadowWifiManager return false; } - @Implementation - protected List getScanResults() { - return new ArrayList(); - } - public static ShadowWifiManager get() { return Shadow.extract(application.getSystemService(WifiManager.class)); } diff --git a/tests/robotests/src/com/android/settings/wifi/WifiDialogActivityTest.java b/tests/robotests/src/com/android/settings/wifi/WifiDialogActivityTest.java index 12f7c67b4bb..41d1bbe4f38 100644 --- a/tests/robotests/src/com/android/settings/wifi/WifiDialogActivityTest.java +++ b/tests/robotests/src/com/android/settings/wifi/WifiDialogActivityTest.java @@ -50,7 +50,7 @@ public class WifiDialogActivityTest { private static final String AP1_SSID = "\"ap1\""; @Mock - private WifiConfigController2 mController; + private WifiConfigController mController; @Before public void setUp() { @@ -63,12 +63,8 @@ public class WifiDialogActivityTest { @Test public void onSubmit_shouldConnectToNetwork() { - WifiDialogActivity activity = - Robolectric.buildActivity(WifiDialogActivity.class, - new Intent().putExtra(WifiDialogActivity.KEY_CHOSEN_WIFIENTRY_KEY, - "StandardWifiEntry:OpenNetwork,0")) - .setup().get(); - WifiDialog2 dialog = (WifiDialog2) ShadowAlertDialogCompat.getLatestAlertDialog(); + WifiDialogActivity activity = Robolectric.setupActivity(WifiDialogActivity.class); + WifiDialog dialog = (WifiDialog) ShadowAlertDialogCompat.getLatestAlertDialog(); assertThat(dialog).isNotNull(); ReflectionHelpers.setField(dialog, "mController", mController); @@ -78,18 +74,35 @@ public class WifiDialogActivityTest { assertThat(ShadowWifiManager.get().savedWifiConfig.SSID).isEqualTo(AP1_SSID); } + @Test + public void onSubmit_whenConnectForCallerIsFalse_shouldNotConnectToNetwork() { + WifiDialogActivity activity = + Robolectric.buildActivity( + WifiDialogActivity.class, + new Intent().putExtra(WifiDialogActivity.KEY_CONNECT_FOR_CALLER, false)) + .setup().get(); + WifiDialog dialog = (WifiDialog) ShadowAlertDialogCompat.getLatestAlertDialog(); + + assertThat(dialog).isNotNull(); + + ReflectionHelpers.setField(dialog, "mController", mController); + + activity.onSubmit(dialog); + + assertThat(ShadowWifiManager.get().savedWifiConfig).isNull(); + } + @Test public void onSubmit_whenLaunchInSetupFlow_shouldBeLightThemeForWifiDialog() { WifiDialogActivity activity = Robolectric.buildActivity( WifiDialogActivity.class, new Intent() + .putExtra(WifiDialogActivity.KEY_CONNECT_FOR_CALLER, false) .putExtra(WizardManagerHelper.EXTRA_IS_FIRST_RUN, true) - .putExtra(WizardManagerHelper.EXTRA_IS_SETUP_FLOW, true) - .putExtra(WifiDialogActivity.KEY_CHOSEN_WIFIENTRY_KEY, - "StandardWifiEntry:OpenNetwork,0")) + .putExtra(WizardManagerHelper.EXTRA_IS_SETUP_FLOW, true)) .setup().get(); - WifiDialog2 dialog = (WifiDialog2) ShadowAlertDialogCompat.getLatestAlertDialog(); + WifiDialog dialog = (WifiDialog) ShadowAlertDialogCompat.getLatestAlertDialog(); assertThat(dialog).isNotNull(); diff --git a/tests/robotests/src/com/android/settings/wifi/slice/ConnectToWifiHandlerTest.java b/tests/robotests/src/com/android/settings/wifi/slice/ConnectToWifiHandlerTest.java index 829ec8fc4dd..ac1384f8756 100644 --- a/tests/robotests/src/com/android/settings/wifi/slice/ConnectToWifiHandlerTest.java +++ b/tests/robotests/src/com/android/settings/wifi/slice/ConnectToWifiHandlerTest.java @@ -26,69 +26,95 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; -import android.content.Intent; +import android.net.wifi.WifiConfiguration; +import android.net.wifi.WifiConfiguration.NetworkSelectionStatus; +import android.net.wifi.WifiManager; -import com.android.settings.wifi.WifiDialogActivity; -import com.android.wifitrackerlib.WifiEntry; -import com.android.wifitrackerlib.WifiEntry.ConnectCallback; +import com.android.settings.testutils.shadow.ShadowWifiManager; +import com.android.settingslib.wifi.AccessPoint; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; @RunWith(RobolectricTestRunner.class) +@Config(shadows = ShadowWifiManager.class) public class ConnectToWifiHandlerTest { private static final String AP_SSID = "\"ap\""; private Context mContext; private ConnectToWifiHandler mHandler; + private WifiConfiguration mWifiConfig; @Mock - private WifiScanWorker mWifiScanWorker; + private AccessPoint mAccessPoint; @Before public void setUp() { MockitoAnnotations.initMocks(this); - mContext = spy(RuntimeEnvironment.application); - mHandler = spy(new ConnectToWifiHandler()); - doReturn(mWifiScanWorker).when(mHandler).getWifiScanWorker(any()); + mContext = RuntimeEnvironment.application; + mHandler = new ConnectToWifiHandler(); + mWifiConfig = spy(new WifiConfiguration()); + mWifiConfig.SSID = AP_SSID; + doReturn(mWifiConfig).when(mAccessPoint).getConfig(); } @Test - public void onReceive_nonNullKeyAndUri_shouldConnectWifintry() { - final Intent intent = new Intent(); - final String key = "key"; - intent.putExtra(ConnectToWifiHandler.KEY_CHOSEN_WIFIENTRY_KEY, key); - intent.putExtra(ConnectToWifiHandler.KEY_WIFI_SLICE_URI, - com.android.settings.slices.CustomSliceRegistry.WIFI_SLICE_URI); - final WifiEntry wifiEntry = mock(WifiEntry.class); - when(mWifiScanWorker.getWifiEntry(key)).thenReturn(wifiEntry); + public void connect_shouldConnectToUnsavedOpenNetwork() { + when(mAccessPoint.isSaved()).thenReturn(false); + when(mAccessPoint.getSecurity()).thenReturn(AccessPoint.SECURITY_NONE); - mHandler.onReceive(mContext, intent); + mHandler.connect(mContext, mAccessPoint); - verify(wifiEntry).connect(any()); + assertThat(ShadowWifiManager.get().savedWifiConfig.SSID).isEqualTo(AP_SSID); } @Test - public void onConnectResult_failNoConfig_shouldStartActivity() { - final String key = "key"; - final WifiEntry wifiEntry = mock(WifiEntry.class); - when(wifiEntry.getKey()).thenReturn(key); - final ConnectToWifiHandler.WifiEntryConnectCallback callback = - spy(new ConnectToWifiHandler.WifiEntryConnectCallback(mContext, wifiEntry)); + public void connect_shouldStartOsuProvisioning() { + when(mAccessPoint.isSaved()).thenReturn(false); + when(mAccessPoint.isOsuProvider()).thenReturn(true); - callback.onConnectResult(ConnectCallback.CONNECT_STATUS_FAILURE_NO_CONFIG); + mHandler.connect(mContext, mAccessPoint); - final ArgumentCaptor argument = ArgumentCaptor.forClass(Intent.class); - verify(mContext).startActivity(argument.capture()); - assertThat(argument.getValue().getStringExtra(WifiDialogActivity.KEY_CHOSEN_WIFIENTRY_KEY)) - .isEqualTo(key); - assertThat(argument.getValue().getFlags() & Intent.FLAG_ACTIVITY_NEW_TASK) - .isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK); + verify(mAccessPoint).startOsuProvisioning(any(WifiManager.ActionListener.class)); + } + + + @Test + public void connect_shouldConnectWithPasspointProvider() { + when(mAccessPoint.isSaved()).thenReturn(false); + when(mAccessPoint.isPasspoint()).thenReturn(true); + + mHandler.connect(mContext, mAccessPoint); + + assertThat(ShadowWifiManager.get().savedWifiConfig.SSID).isEqualTo(AP_SSID); + } + + @Test + public void connect_shouldConnectToSavedSecuredNetwork() { + when(mAccessPoint.isSaved()).thenReturn(true); + when(mAccessPoint.getSecurity()).thenReturn(AccessPoint.SECURITY_PSK); + final NetworkSelectionStatus status = mock(NetworkSelectionStatus.class); + when(status.hasEverConnected()).thenReturn(true); + when(mWifiConfig.getNetworkSelectionStatus()).thenReturn(status); + + mHandler.connect(mContext, mAccessPoint); + + assertThat(ShadowWifiManager.get().savedWifiConfig.SSID).isEqualTo(AP_SSID); + } + + @Test + public void connect_shouldNotConnectToUnsavedSecuredNetwork() { + when(mAccessPoint.isSaved()).thenReturn(false); + when(mAccessPoint.getSecurity()).thenReturn(AccessPoint.SECURITY_PSK); + + mHandler.connect(mContext, mAccessPoint); + + assertThat(ShadowWifiManager.get().savedWifiConfig).isNull(); } } diff --git a/tests/robotests/src/com/android/settings/wifi/slice/ContextualWifiScanWorkerTest.java b/tests/robotests/src/com/android/settings/wifi/slice/ContextualWifiScanWorkerTest.java new file mode 100644 index 00000000000..0e525207caa --- /dev/null +++ b/tests/robotests/src/com/android/settings/wifi/slice/ContextualWifiScanWorkerTest.java @@ -0,0 +1,105 @@ +/* + * 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 com.android.settings.slices.CustomSliceRegistry.CONTEXTUAL_WIFI_SLICE_URI; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import android.content.Context; +import android.content.Intent; +import android.net.ConnectivityManager; +import android.net.ConnectivityManager.NetworkCallback; +import android.net.Network; +import android.net.wifi.WifiManager; +import android.os.UserHandle; + +import com.android.settings.testutils.shadow.ShadowWifiManager; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +@RunWith(RobolectricTestRunner.class) +@Config(shadows = { + ShadowWifiManager.class, + WifiScanWorkerTest.ShadowWifiTracker.class, +}) +public class ContextualWifiScanWorkerTest { + + private Context mContext; + private WifiManager mWifiManager; + private ConnectivityManager mConnectivityManager; + private ContextualWifiScanWorker mWifiScanWorker; + private ConnectToWifiHandler mConnectToWifiHandler; + + @Before + public void setUp() { + mContext = spy(RuntimeEnvironment.application); + mWifiManager = mContext.getSystemService(WifiManager.class); + mWifiManager.setWifiEnabled(true); + + mConnectivityManager = mContext.getSystemService(ConnectivityManager.class); + mWifiScanWorker = new ContextualWifiScanWorker(mContext, CONTEXTUAL_WIFI_SLICE_URI); + mConnectToWifiHandler = new ConnectToWifiHandler(); + } + + @After + public void tearDown() { + mWifiScanWorker.clearClickedWifi(); + } + + @Test + public void NetworkCallback_onCapabilitiesChanged_sliceIsUnpinned_shouldSendBroadcast() { + final Intent intent = WifiScanWorkerTest.getIntentWithAccessPoint("ap1"); + WifiScanWorkerTest.setConnectionInfoSSID("ap1"); + final Network network = mConnectivityManager.getActiveNetwork(); + mWifiScanWorker.registerNetworkCallback(network); + final NetworkCallback callback = mWifiScanWorker.mNetworkCallback; + + mWifiScanWorker.onSlicePinned(); + mConnectToWifiHandler.onReceive(mContext, intent); + mWifiScanWorker.onSliceUnpinned(); + callback.onCapabilitiesChanged(network, + WifiSliceTest.makeCaptivePortalNetworkCapabilities()); + + verify(mContext).sendBroadcastAsUser(any(Intent.class), eq(UserHandle.CURRENT)); + } + + @Test + public void NetworkCallback_onCapabilitiesChanged_newSession_shouldNotSendBroadcast() { + final Intent intent = WifiScanWorkerTest.getIntentWithAccessPoint("ap1"); + WifiScanWorkerTest.setConnectionInfoSSID("ap1"); + final Network network = mConnectivityManager.getActiveNetwork(); + mWifiScanWorker.registerNetworkCallback(network); + + mConnectToWifiHandler.onReceive(mContext, intent); + ContextualWifiScanWorker.newVisibleUiSession(); + mWifiScanWorker.mNetworkCallback.onCapabilitiesChanged(network, + WifiSliceTest.makeCaptivePortalNetworkCapabilities()); + + verify(mContext, never()).sendBroadcastAsUser(any(Intent.class), eq(UserHandle.CURRENT)); + } +} diff --git a/tests/robotests/src/com/android/settings/wifi/slice/WifiScanWorkerTest.java b/tests/robotests/src/com/android/settings/wifi/slice/WifiScanWorkerTest.java index 395048c3c64..33195438fe0 100644 --- a/tests/robotests/src/com/android/settings/wifi/slice/WifiScanWorkerTest.java +++ b/tests/robotests/src/com/android/settings/wifi/slice/WifiScanWorkerTest.java @@ -17,89 +17,216 @@ package com.android.settings.wifi.slice; import static com.android.settings.slices.CustomSliceRegistry.WIFI_SLICE_URI; +import static com.android.settings.wifi.WifiDialogActivity.KEY_ACCESS_POINT_STATE; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import androidx.lifecycle.Lifecycle; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.net.ConnectivityManager; +import android.net.ConnectivityManager.NetworkCallback; +import android.net.Network; +import android.net.NetworkInfo; +import android.net.NetworkInfo.DetailedState; +import android.net.wifi.WifiInfo; +import android.net.wifi.WifiManager; +import android.os.Bundle; +import android.os.UserHandle; -import com.android.wifitrackerlib.WifiEntry; -import com.android.wifitrackerlib.WifiPickerTracker; +import com.android.settings.slices.ShadowSliceBackgroundWorker; +import com.android.settings.testutils.shadow.ShadowWifiManager; +import com.android.settingslib.wifi.AccessPoint; +import com.android.settingslib.wifi.WifiTracker; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; +import org.robolectric.shadows.ShadowNetworkInfo; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; @RunWith(RobolectricTestRunner.class) +@Config(shadows = {ShadowSliceBackgroundWorker.class, ShadowWifiManager.class, + WifiScanWorkerTest.ShadowWifiTracker.class}) public class WifiScanWorkerTest { + private Context mContext; + private ContentResolver mResolver; + private WifiManager mWifiManager; + private ConnectivityManager mConnectivityManager; private WifiScanWorker mWifiScanWorker; - @Mock - WifiPickerTracker mWifiPickerTracker; + private ConnectToWifiHandler mConnectToWifiHandler; @Before public void setUp() { - MockitoAnnotations.initMocks(this); + mContext = spy(RuntimeEnvironment.application); + mResolver = mock(ContentResolver.class); + doReturn(mResolver).when(mContext).getContentResolver(); + mWifiManager = mContext.getSystemService(WifiManager.class); + mWifiManager.setWifiEnabled(true); - mWifiScanWorker = new WifiScanWorker(RuntimeEnvironment.application, WIFI_SLICE_URI); - mWifiScanWorker.mWifiPickerTracker = mWifiPickerTracker; + mConnectivityManager = mContext.getSystemService(ConnectivityManager.class); + mWifiScanWorker = new WifiScanWorker(mContext, WIFI_SLICE_URI); + mConnectToWifiHandler = new ConnectToWifiHandler(); + } + + @After + public void tearDown() { + mWifiScanWorker.clearClickedWifi(); } @Test - public void onConstructor_shouldBeInCreatedState() { - assertThat(mWifiScanWorker.getLifecycle().getCurrentState()) - .isEqualTo(Lifecycle.State.CREATED); + public void onWifiStateChanged_shouldNotifyChange() { + mWifiScanWorker.onWifiStateChanged(WifiManager.WIFI_STATE_DISABLED); + + verify(mResolver).notifyChange(WIFI_SLICE_URI, null); } @Test - public void onSlicePinned_shouldBeInResumedState() { + public void AccessPointList_sameState_shouldBeTheSame() { + final AccessPoint ap1 = createAccessPoint(DetailedState.CONNECTED); + final AccessPoint ap2 = createAccessPoint(DetailedState.CONNECTED); + + assertThat(mWifiScanWorker.areListsTheSame(Arrays.asList(ap1), Arrays.asList(ap2))) + .isTrue(); + } + + @Test + public void AccessPointList_differentState_shouldBeDifferent() { + final AccessPoint ap1 = createAccessPoint(DetailedState.CONNECTING); + final AccessPoint ap2 = createAccessPoint(DetailedState.CONNECTED); + + assertThat(mWifiScanWorker.areListsTheSame(Arrays.asList(ap1), Arrays.asList(ap2))) + .isFalse(); + } + + @Test + public void AccessPointList_differentListLength_shouldBeDifferent() { + final AccessPoint ap1 = createAccessPoint(DetailedState.CONNECTED); + final AccessPoint ap2 = createAccessPoint(DetailedState.CONNECTED); + final List list = new ArrayList<>(); + list.add(ap1); + list.add(ap2); + + assertThat(mWifiScanWorker.areListsTheSame(list, Arrays.asList(ap1))).isFalse(); + } + + @Test + public void NetworkCallback_onCapabilitiesChanged_shouldNotifyChange() { + final Network network = mConnectivityManager.getActiveNetwork(); + mWifiScanWorker.registerNetworkCallback(network); + + mWifiScanWorker.mNetworkCallback.onCapabilitiesChanged(network, + WifiSliceTest.makeCaptivePortalNetworkCapabilities()); + + verify(mResolver).notifyChange(WIFI_SLICE_URI, null); + } + + @Test + public void NetworkCallback_onCapabilitiesChanged_isClickedWifi_shouldSendBroadcast() { + final Intent intent = getIntentWithAccessPoint("ap1"); + setConnectionInfoSSID("ap1"); + final Network network = mConnectivityManager.getActiveNetwork(); + mWifiScanWorker.registerNetworkCallback(network); + + mConnectToWifiHandler.onReceive(mContext, intent); + mWifiScanWorker.mNetworkCallback.onCapabilitiesChanged(network, + WifiSliceTest.makeCaptivePortalNetworkCapabilities()); + + verify(mContext).sendBroadcastAsUser(any(Intent.class), eq(UserHandle.CURRENT)); + } + + @Test + public void NetworkCallback_onCapabilitiesChanged_isNotClickedWifi_shouldNotSendBroadcast() { + final Intent intent = getIntentWithAccessPoint("ap1"); + setConnectionInfoSSID("ap2"); + final Network network = mConnectivityManager.getActiveNetwork(); + mWifiScanWorker.registerNetworkCallback(network); + + mConnectToWifiHandler.onReceive(mContext, intent); + mWifiScanWorker.mNetworkCallback.onCapabilitiesChanged(network, + WifiSliceTest.makeCaptivePortalNetworkCapabilities()); + + verify(mContext, never()).sendBroadcastAsUser(any(Intent.class), eq(UserHandle.CURRENT)); + } + + @Test + public void NetworkCallback_onCapabilitiesChanged_neverClickWifi_shouldNotSendBroadcast() { + setConnectionInfoSSID("ap1"); + final Network network = mConnectivityManager.getActiveNetwork(); + mWifiScanWorker.registerNetworkCallback(network); + + mWifiScanWorker.mNetworkCallback.onCapabilitiesChanged(network, + WifiSliceTest.makeCaptivePortalNetworkCapabilities()); + + verify(mContext, never()).sendBroadcastAsUser(any(Intent.class), eq(UserHandle.CURRENT)); + } + + @Test + public void NetworkCallback_onCapabilitiesChanged_sliceIsUnpinned_shouldNotSendBroadcast() { + final Intent intent = getIntentWithAccessPoint("ap1"); + setConnectionInfoSSID("ap1"); + final Network network = mConnectivityManager.getActiveNetwork(); + mWifiScanWorker.registerNetworkCallback(network); + final NetworkCallback callback = mWifiScanWorker.mNetworkCallback; + mWifiScanWorker.onSlicePinned(); - - assertThat(mWifiScanWorker.getLifecycle().getCurrentState()) - .isEqualTo(Lifecycle.State.RESUMED); - } - - @Test - public void onSliceUnpinned_shouldBeInCreatedState() { + mConnectToWifiHandler.onReceive(mContext, intent); mWifiScanWorker.onSliceUnpinned(); + callback.onCapabilitiesChanged(network, + WifiSliceTest.makeCaptivePortalNetworkCapabilities()); - assertThat(mWifiScanWorker.getLifecycle().getCurrentState()) - .isEqualTo(Lifecycle.State.CREATED); + verify(mContext, never()).sendBroadcastAsUser(any(Intent.class), eq(UserHandle.CURRENT)); } - @Test - public void close_shouldBeInDestroyedState() { - mWifiScanWorker.close(); - - assertThat(mWifiScanWorker.getLifecycle().getCurrentState()) - .isEqualTo(Lifecycle.State.DESTROYED); + static Intent getIntentWithAccessPoint(String ssid) { + final Bundle savedState = new Bundle(); + savedState.putString("key_ssid", ssid); + return new Intent().putExtra(KEY_ACCESS_POINT_STATE, savedState); } - @Test - public void getWifiEntry_connectedWifiKey_shouldGetConnectedWifi() { - final String key = "key"; - final WifiEntry connectedWifiEntry = mock(WifiEntry.class); - when(connectedWifiEntry.getKey()).thenReturn(key); - when(mWifiPickerTracker.getConnectedWifiEntry()).thenReturn(connectedWifiEntry); - - assertThat(mWifiScanWorker.getWifiEntry(key)).isEqualTo(connectedWifiEntry); + static void setConnectionInfoSSID(String ssid) { + final WifiInfo wifiInfo = mock(WifiInfo.class); + when(wifiInfo.getSSID()).thenReturn(ssid); + ShadowWifiManager.get().setConnectionInfo(wifiInfo); } - @Test - public void getWifiEntry_reachableWifiKey_shouldGetReachableWifi() { - final String key = "key"; - final WifiEntry reachableWifiEntry = mock(WifiEntry.class); - when(reachableWifiEntry.getKey()).thenReturn(key); - when(mWifiPickerTracker.getWifiEntries()).thenReturn(Arrays.asList(reachableWifiEntry)); + private AccessPoint createAccessPoint(String ssid, DetailedState detailedState) { + final NetworkInfo info = ShadowNetworkInfo.newInstance(detailedState, 1 /* type */, + 0 /*subType */, true /* isAvailable */, true /* isConnected */); + final Bundle savedState = new Bundle(); + savedState.putString("key_ssid", ssid); + savedState.putParcelable("key_networkinfo", info); + return new AccessPoint(mContext, savedState); + } - assertThat(mWifiScanWorker.getWifiEntry(key)).isEqualTo(reachableWifiEntry); + private AccessPoint createAccessPoint(DetailedState detailedState) { + return createAccessPoint("ap", detailedState); + } + + @Implements(WifiTracker.class) + public static class ShadowWifiTracker { + @Implementation + public void onStart() { + // do nothing + } } } diff --git a/tests/robotests/src/com/android/settings/wifi/slice/WifiSliceTest.java b/tests/robotests/src/com/android/settings/wifi/slice/WifiSliceTest.java index 5431540028d..ad74a326c83 100644 --- a/tests/robotests/src/com/android/settings/wifi/slice/WifiSliceTest.java +++ b/tests/robotests/src/com/android/settings/wifi/slice/WifiSliceTest.java @@ -23,14 +23,17 @@ import static com.android.settings.wifi.slice.WifiSlice.DEFAULT_EXPANDED_ROW_COU import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.when; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; +import android.net.ConnectivityManager; +import android.net.NetworkCapabilities; +import android.net.NetworkInfo; import android.net.Uri; import android.net.wifi.WifiManager; @@ -46,8 +49,7 @@ import androidx.slice.widget.SliceLiveData; import com.android.settings.R; import com.android.settings.slices.SliceBackgroundWorker; import com.android.settings.testutils.SliceTester; -import com.android.wifitrackerlib.WifiEntry; -import com.android.wifitrackerlib.WifiEntry.ConnectedState; +import com.android.settingslib.wifi.AccessPoint; import org.junit.Before; import org.junit.Test; @@ -72,6 +74,7 @@ public class WifiSliceTest { private Context mContext; private ContentResolver mResolver; private WifiManager mWifiManager; + private ConnectivityManager mConnectivityManager; private WifiSlice mWifiSlice; @Before @@ -85,6 +88,9 @@ public class WifiSliceTest { SliceProvider.setSpecs(SliceLiveData.SUPPORTED_SPECS); mWifiManager.setWifiEnabled(true); + mConnectivityManager = spy(mContext.getSystemService(ConnectivityManager.class)); + doReturn(mConnectivityManager).when(mContext).getSystemService(ConnectivityManager.class); + mWifiSlice = new WifiSlice(mContext); } @@ -132,42 +138,33 @@ public class WifiSliceTest { mContext.getString(R.string.wifi_empty_list_wifi_on)); } - private WifiSliceItem createWifiSliceItem(String title, @ConnectedState int connectedState) { - final WifiEntry wifiEntry = mock(WifiEntry.class); - when(wifiEntry.getTitle()).thenReturn(title); - when(wifiEntry.getKey()).thenReturn("key"); - when(wifiEntry.getConnectedState()).thenReturn(connectedState); - when(wifiEntry.getLevel()).thenReturn(WifiEntry.WIFI_LEVEL_MAX); - return new WifiSliceItem(mContext, wifiEntry); + private AccessPoint createAccessPoint(String name, boolean active, boolean reachable) { + final AccessPoint accessPoint = mock(AccessPoint.class); + doReturn(name).when(accessPoint).getTitle(); + doReturn(active).when(accessPoint).isActive(); + doReturn(reachable).when(accessPoint).isReachable(); + if (active) { + final NetworkInfo networkInfo = mock(NetworkInfo.class); + doReturn(networkInfo).when(accessPoint).getNetworkInfo(); + doReturn(NetworkInfo.State.CONNECTED).when(networkInfo).getState(); + } + return accessPoint; } - private void setWorkerResults(WifiSliceItem... wifiSliceItems) { - final ArrayList results = new ArrayList<>(); - for (WifiSliceItem wifiSliceItem : wifiSliceItems) { - results.add(wifiSliceItem); + private void setWorkerResults(AccessPoint... accessPoints) { + final ArrayList results = new ArrayList<>(); + for (AccessPoint ap : accessPoints) { + results.add(ap); } final SliceBackgroundWorker worker = SliceBackgroundWorker.getInstance(mWifiSlice.getUri()); doReturn(results).when(worker).getResults(); } @Test - public void getWifiSlice_oneConnectedAp_shouldReturnLoadingRow() { - setWorkerResults(createWifiSliceItem(AP1_NAME, WifiEntry.CONNECTED_STATE_CONNECTED)); - - final Slice wifiSlice = mWifiSlice.getSlice(); - final List sliceItems = wifiSlice.getItems(); - - SliceTester.assertAnySliceItemContainsTitle(sliceItems, AP1_NAME); - // Has scanning text - SliceTester.assertAnySliceItemContainsSubtitle(sliceItems, - mContext.getString(R.string.wifi_empty_list_wifi_on)); - } - - @Test - public void getWifiSlice_oneConnectedApAndOneDisconnectedAp_shouldReturnLoadingRow() { + public void getWifiSlice_noReachableAp_shouldReturnLoadingRow() { setWorkerResults( - createWifiSliceItem(AP1_NAME, WifiEntry.CONNECTED_STATE_CONNECTED), - createWifiSliceItem(AP2_NAME, WifiEntry.CONNECTED_STATE_DISCONNECTED)); + createAccessPoint(AP1_NAME, false, false), + createAccessPoint(AP2_NAME, false, false)); final Slice wifiSlice = mWifiSlice.getSlice(); final List sliceItems = wifiSlice.getItems(); @@ -180,8 +177,8 @@ public class WifiSliceTest { } @Test - public void getWifiSlice_oneDisconnectedAp_shouldReturnLoadingRow() { - setWorkerResults(createWifiSliceItem(AP1_NAME, WifiEntry.CONNECTED_STATE_DISCONNECTED)); + public void getWifiSlice_oneActiveAp_shouldReturnLoadingRow() { + setWorkerResults(createAccessPoint(AP1_NAME, true, true)); final Slice wifiSlice = mWifiSlice.getSlice(); final List sliceItems = wifiSlice.getItems(); @@ -193,11 +190,40 @@ public class WifiSliceTest { } @Test - public void getWifiSlice_apReachExpandedCount_shouldNotReturnLoadingRow() { + public void getWifiSlice_oneActiveApAndOneUnreachableAp_shouldReturnLoadingRow() { setWorkerResults( - createWifiSliceItem(AP1_NAME, WifiEntry.CONNECTED_STATE_DISCONNECTED), - createWifiSliceItem(AP2_NAME, WifiEntry.CONNECTED_STATE_DISCONNECTED), - createWifiSliceItem(AP3_NAME, WifiEntry.CONNECTED_STATE_DISCONNECTED)); + createAccessPoint(AP1_NAME, true, true), + createAccessPoint(AP2_NAME, false, false)); + + final Slice wifiSlice = mWifiSlice.getSlice(); + final List sliceItems = wifiSlice.getItems(); + + SliceTester.assertAnySliceItemContainsTitle(sliceItems, AP1_NAME); + SliceTester.assertAnySliceItemContainsTitle(sliceItems, AP2_NAME); + // Has scanning text + SliceTester.assertAnySliceItemContainsSubtitle(sliceItems, + mContext.getString(R.string.wifi_empty_list_wifi_on)); + } + + @Test + public void getWifiSlice_oneReachableAp_shouldReturnLoadingRow() { + setWorkerResults(createAccessPoint(AP1_NAME, false, true)); + + final Slice wifiSlice = mWifiSlice.getSlice(); + final List sliceItems = wifiSlice.getItems(); + + SliceTester.assertAnySliceItemContainsTitle(sliceItems, AP1_NAME); + // Has scanning text + SliceTester.assertAnySliceItemContainsSubtitle(sliceItems, + mContext.getString(R.string.wifi_empty_list_wifi_on)); + } + + @Test + public void getWifiSlice_allReachableAps_shouldNotReturnLoadingRow() { + setWorkerResults( + createAccessPoint(AP1_NAME, false, true), + createAccessPoint(AP2_NAME, false, true), + createAccessPoint(AP3_NAME, false, true)); final Slice wifiSlice = mWifiSlice.getSlice(); final List sliceItems = wifiSlice.getItems(); @@ -210,6 +236,29 @@ public class WifiSliceTest { mContext.getString(R.string.wifi_empty_list_wifi_on)); } + @Test + public void getWifiSlice_isCaptivePortal_shouldHaveCaptivePortalItems() { + setWorkerResults(createAccessPoint(AP1_NAME, true, true)); + doReturn(makeCaptivePortalNetworkCapabilities()).when(mConnectivityManager) + .getNetworkCapabilities(any()); + final IconCompat expectedIcon = IconCompat.createWithResource(mContext, + R.drawable.ic_settings_accent); + + final Slice wifiSlice = mWifiSlice.getSlice(); + final List sliceItems = wifiSlice.getItems(); + + SliceTester.assertAnySliceItemContainsTitle(sliceItems, AP1_NAME); + SliceTester.assertAnySliceItemContainsIcon(sliceItems, expectedIcon); + } + + static NetworkCapabilities makeCaptivePortalNetworkCapabilities() { + final NetworkCapabilities nc = new NetworkCapabilities(); + nc.clearAll(); + nc.addTransportType(NetworkCapabilities.TRANSPORT_WIFI); + nc.addCapability(NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL); + return nc; + } + @Test public void handleUriChange_updatesWifi() { final Intent intent = mWifiSlice.getIntent();