Merge changes I80b93689,Ie767a9b8

* changes:
  Add capport info to WiFi details(WifiDetailPreferenceController2)
  Add capport info to WiFi details
This commit is contained in:
Lucas Lin
2020-02-19 11:20:22 +00:00
committed by Android (Google) Code Review
5 changed files with 379 additions and 21 deletions

View File

@@ -1755,6 +1755,12 @@
<string name="wifi_band_5ghz">5 GHz</string> <string name="wifi_band_5ghz">5 GHz</string>
<!-- Wifi Sign in text for button [CHAR LIMIT = 40]--> <!-- Wifi Sign in text for button [CHAR LIMIT = 40]-->
<string name="wifi_sign_in_button_text">Sign in</string> <string name="wifi_sign_in_button_text">Sign in</string>
<!-- Text for button to go to Wifi venue information webpage when Wifi is a captive portal [CHAR LIMIT=40]-->
<string name="wifi_venue_website_button_text">Open site</string>
<!-- Text shown in wifi settings indicating how much time is left on an internet access point that has a time limit for the session [CHAR LIMIT=40] -->
<string name="wifi_time_remaining"><xliff:g id="Remaining time" example="1 day, 2 hours, 3 minutes">%1$s</xliff:g> left</string>
<!-- Text shown in wifi settings indicating at what time the connection will expire on an internet access point that has a time limit for the session [CHAR LIMIT=40] -->
<string name="wifi_expiry_time">Expires on <xliff:g id="Expiry time" example="2020/01/02 12:34PM">%1$s</xliff:g></string>
<!-- Wifi Sign in CTA for wifi settings when captive portal auth is required [CHAR LIMIT = 50] --> <!-- Wifi Sign in CTA for wifi settings when captive portal auth is required [CHAR LIMIT = 50] -->
<string name="wifi_tap_to_sign_in">Tap here to sign in to network</string> <string name="wifi_tap_to_sign_in">Tap here to sign in to network</string>
<!-- Transmit Link speed on Wifi Status screen [CHAR LIMIT=32] --> <!-- Transmit Link speed on Wifi Status screen [CHAR LIMIT=32] -->

View File

@@ -31,6 +31,7 @@ import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.graphics.drawable.VectorDrawable; import android.graphics.drawable.VectorDrawable;
import android.net.CaptivePortalData;
import android.net.ConnectivityManager; import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback; import android.net.ConnectivityManager.NetworkCallback;
import android.net.LinkAddress; import android.net.LinkAddress;
@@ -41,6 +42,7 @@ import android.net.NetworkInfo;
import android.net.NetworkRequest; import android.net.NetworkRequest;
import android.net.NetworkUtils; import android.net.NetworkUtils;
import android.net.RouteInfo; import android.net.RouteInfo;
import android.net.Uri;
import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiInfo; import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager; import android.net.wifi.WifiManager;
@@ -75,6 +77,7 @@ import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.core.lifecycle.LifecycleObserver; import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnPause; import com.android.settingslib.core.lifecycle.events.OnPause;
import com.android.settingslib.core.lifecycle.events.OnResume; import com.android.settingslib.core.lifecycle.events.OnResume;
import com.android.settingslib.utils.StringUtil;
import com.android.settingslib.widget.ActionButtonsPreference; import com.android.settingslib.widget.ActionButtonsPreference;
import com.android.settingslib.widget.LayoutPreference; import com.android.settingslib.widget.LayoutPreference;
import com.android.settingslib.wifi.AccessPoint; import com.android.settingslib.wifi.AccessPoint;
@@ -86,6 +89,10 @@ import java.net.Inet6Address;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.time.Duration; import java.time.Duration;
import java.time.Instant;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.StringJoiner; import java.util.StringJoiner;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -189,6 +196,7 @@ public class WifiDetailPreferenceController extends AbstractPreferenceController
WifiDataUsageSummaryPreferenceController mSummaryHeaderController; WifiDataUsageSummaryPreferenceController mSummaryHeaderController;
private final IconInjector mIconInjector; private final IconInjector mIconInjector;
private final Clock mClock;
private final IntentFilter mFilter; private final IntentFilter mFilter;
// Passpoint information - cache it in case of losing these information after // Passpoint information - cache it in case of losing these information after
@@ -229,6 +237,8 @@ public class WifiDetailPreferenceController extends AbstractPreferenceController
public void onLinkPropertiesChanged(Network network, LinkProperties lp) { public void onLinkPropertiesChanged(Network network, LinkProperties lp) {
if (network.equals(mNetwork) && !lp.equals(mLinkProperties)) { if (network.equals(mNetwork) && !lp.equals(mLinkProperties)) {
mLinkProperties = lp; mLinkProperties = lp;
refreshEntityHeader();
refreshButtons();
refreshIpLayerInfo(); refreshIpLayerInfo();
} }
} }
@@ -322,7 +332,7 @@ public class WifiDetailPreferenceController extends AbstractPreferenceController
MetricsFeatureProvider metricsFeatureProvider) { MetricsFeatureProvider metricsFeatureProvider) {
return new WifiDetailPreferenceController( return new WifiDetailPreferenceController(
accessPoint, connectivityManager, context, fragment, handler, lifecycle, accessPoint, connectivityManager, context, fragment, handler, lifecycle,
wifiManager, metricsFeatureProvider, new IconInjector(context)); wifiManager, metricsFeatureProvider, new IconInjector(context), new Clock());
} }
@VisibleForTesting @VisibleForTesting
@@ -335,7 +345,8 @@ public class WifiDetailPreferenceController extends AbstractPreferenceController
Lifecycle lifecycle, Lifecycle lifecycle,
WifiManager wifiManager, WifiManager wifiManager,
MetricsFeatureProvider metricsFeatureProvider, MetricsFeatureProvider metricsFeatureProvider,
IconInjector injector) { IconInjector injector,
Clock clock) {
super(context); super(context);
mAccessPoint = accessPoint; mAccessPoint = accessPoint;
@@ -347,6 +358,7 @@ public class WifiDetailPreferenceController extends AbstractPreferenceController
mWifiManager = wifiManager; mWifiManager = wifiManager;
mMetricsFeatureProvider = metricsFeatureProvider; mMetricsFeatureProvider = metricsFeatureProvider;
mIconInjector = injector; mIconInjector = injector;
mClock = clock;
mFilter = new IntentFilter(); mFilter = new IntentFilter();
mFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); mFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
@@ -404,9 +416,6 @@ public class WifiDetailPreferenceController extends AbstractPreferenceController
.setButton1Text(R.string.forget) .setButton1Text(R.string.forget)
.setButton1Icon(R.drawable.ic_settings_delete) .setButton1Icon(R.drawable.ic_settings_delete)
.setButton1OnClickListener(view -> forgetNetwork()) .setButton1OnClickListener(view -> forgetNetwork())
.setButton2Text(R.string.wifi_sign_in_button_text)
.setButton2Icon(R.drawable.ic_settings_sign_in)
.setButton2OnClickListener(view -> signIntoNetwork())
.setButton3Text(R.string.wifi_connect) .setButton3Text(R.string.wifi_connect)
.setButton3Icon(R.drawable.ic_settings_wireless) .setButton3Icon(R.drawable.ic_settings_wireless)
.setButton3OnClickListener(view -> connectNetwork()) .setButton3OnClickListener(view -> connectNetwork())
@@ -414,6 +423,7 @@ public class WifiDetailPreferenceController extends AbstractPreferenceController
.setButton4Text(R.string.share) .setButton4Text(R.string.share)
.setButton4Icon(R.drawable.ic_qrcode_24dp) .setButton4Icon(R.drawable.ic_qrcode_24dp)
.setButton4OnClickListener(view -> shareNetwork()); .setButton4OnClickListener(view -> shareNetwork());
updateCaptivePortalButton();
if (isPasspointConfigurationR1Expired()) { if (isPasspointConfigurationR1Expired()) {
// Hide Connect button. // Hide Connect button.
@@ -439,6 +449,42 @@ public class WifiDetailPreferenceController extends AbstractPreferenceController
mSecurityPref.setSummary(mAccessPoint.getSecurityString(/* concise */ false)); mSecurityPref.setSummary(mAccessPoint.getSecurityString(/* concise */ false));
} }
/**
* Update text, icon and listener of the captive portal button.
* @return True if the button should be shown.
*/
private boolean updateCaptivePortalButton() {
final Uri venueInfoUrl = getCaptivePortalVenueInfoUrl();
if (venueInfoUrl == null) {
mButtonsPref.setButton2Text(R.string.wifi_sign_in_button_text)
.setButton2Icon(R.drawable.ic_settings_sign_in)
.setButton2OnClickListener(view -> signIntoNetwork());
return canSignIntoNetwork();
}
mButtonsPref.setButton2Text(R.string.wifi_venue_website_button_text)
.setButton2Icon(R.drawable.ic_settings_sign_in)
.setButton2OnClickListener(view -> {
final Intent infoIntent = new Intent(Intent.ACTION_VIEW);
infoIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
infoIntent.setData(venueInfoUrl);
mContext.startActivity(infoIntent);
});
return mAccessPoint.isActive();
}
private Uri getCaptivePortalVenueInfoUrl() {
final LinkProperties lp = mLinkProperties;
if (lp == null) {
return null;
}
final CaptivePortalData data = lp.getCaptivePortalData();
if (data == null) {
return null;
}
return data.getVenueInfoUrl();
}
private void setupEntityHeader(PreferenceScreen screen) { private void setupEntityHeader(PreferenceScreen screen) {
LayoutPreference headerPref = screen.findPreference(KEY_HEADER); LayoutPreference headerPref = screen.findPreference(KEY_HEADER);
@@ -464,6 +510,37 @@ public class WifiDetailPreferenceController extends AbstractPreferenceController
mEntityHeaderController.setLabel(mAccessPoint.getTitle()); mEntityHeaderController.setLabel(mAccessPoint.getTitle());
} }
private String getExpiryTimeSummary() {
if (mLinkProperties == null || mLinkProperties.getCaptivePortalData() == null) {
return null;
}
final long expiryTimeMillis = mLinkProperties.getCaptivePortalData().getExpiryTimeMillis();
if (expiryTimeMillis <= 0) {
return null;
}
final ZonedDateTime now = mClock.now();
final ZonedDateTime expiryTime = ZonedDateTime.ofInstant(
Instant.ofEpochMilli(expiryTimeMillis),
now.getZone());
if (now.isAfter(expiryTime)) {
return null;
}
if (now.plusDays(2).isAfter(expiryTime)) {
// Expiration within 2 days: show a duration
return mContext.getString(R.string.wifi_time_remaining, StringUtil.formatElapsedTime(
mContext,
Duration.between(now, expiryTime).getSeconds() * 1000,
false /* withSeconds */));
}
// For more than 2 days, show the expiry date
return mContext.getString(R.string.wifi_expiry_time,
DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT).format(expiryTime));
}
private void refreshEntityHeader() { private void refreshEntityHeader() {
if (usingDataUsageHeader(mContext)) { if (usingDataUsageHeader(mContext)) {
mSummaryHeaderController.updateState(mDataUsageSummaryPref); mSummaryHeaderController.updateState(mDataUsageSummaryPref);
@@ -480,6 +557,7 @@ public class WifiDetailPreferenceController extends AbstractPreferenceController
mEntityHeaderController mEntityHeaderController
.setSummary(summary) .setSummary(summary)
.setSecondSummary(getExpiryTimeSummary())
.setRecyclerView(mFragment.getListView(), mLifecycle) .setRecyclerView(mFragment.getListView(), mLifecycle)
.done(mFragment.getActivity(), true /* rebind */); .done(mFragment.getActivity(), true /* rebind */);
} }
@@ -766,16 +844,16 @@ public class WifiDetailPreferenceController extends AbstractPreferenceController
mIsEphemeral ? R.string.wifi_disconnect_button_text : R.string.forget); mIsEphemeral ? R.string.wifi_disconnect_button_text : R.string.forget);
boolean canForgetNetwork = canForgetNetwork(); boolean canForgetNetwork = canForgetNetwork();
boolean canSignIntoNetwork = canSignIntoNetwork(); boolean showCaptivePortalButton = updateCaptivePortalButton();
boolean canConnectNetwork = canConnectNetwork() && !isPasspointConfigurationR1Expired(); boolean canConnectNetwork = canConnectNetwork() && !isPasspointConfigurationR1Expired();
boolean canShareNetwork = canShareNetwork(); boolean canShareNetwork = canShareNetwork();
mButtonsPref.setButton1Visible(canForgetNetwork); mButtonsPref.setButton1Visible(canForgetNetwork);
mButtonsPref.setButton2Visible(canSignIntoNetwork); mButtonsPref.setButton2Visible(showCaptivePortalButton);
mButtonsPref.setButton3Visible(canConnectNetwork); mButtonsPref.setButton3Visible(canConnectNetwork);
mButtonsPref.setButton4Visible(canShareNetwork); mButtonsPref.setButton4Visible(canShareNetwork);
mButtonsPref.setVisible(canForgetNetwork mButtonsPref.setVisible(canForgetNetwork
|| canSignIntoNetwork || showCaptivePortalButton
|| canConnectNetwork || canConnectNetwork
|| canShareNetwork); || canShareNetwork);
} }
@@ -996,6 +1074,13 @@ public class WifiDetailPreferenceController extends AbstractPreferenceController
} }
} }
@VisibleForTesting
static class Clock {
public ZonedDateTime now() {
return ZonedDateTime.now();
}
}
private boolean usingDataUsageHeader(Context context) { private boolean usingDataUsageHeader(Context context) {
return FeatureFlagUtils.isEnabled(context, FeatureFlags.WIFI_DETAILS_DATAUSAGE_HEADER); return FeatureFlagUtils.isEnabled(context, FeatureFlags.WIFI_DETAILS_DATAUSAGE_HEADER);
} }

View File

@@ -29,6 +29,7 @@ import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.graphics.drawable.VectorDrawable; import android.graphics.drawable.VectorDrawable;
import android.net.CaptivePortalData;
import android.net.ConnectivityManager; import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback; import android.net.ConnectivityManager.NetworkCallback;
import android.net.LinkAddress; import android.net.LinkAddress;
@@ -39,6 +40,7 @@ import android.net.NetworkInfo;
import android.net.NetworkRequest; import android.net.NetworkRequest;
import android.net.NetworkUtils; import android.net.NetworkUtils;
import android.net.RouteInfo; import android.net.RouteInfo;
import android.net.Uri;
import android.net.wifi.WifiInfo; import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager; import android.net.wifi.WifiManager;
import android.os.Handler; import android.os.Handler;
@@ -72,6 +74,7 @@ import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.core.lifecycle.LifecycleObserver; import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnPause; import com.android.settingslib.core.lifecycle.events.OnPause;
import com.android.settingslib.core.lifecycle.events.OnResume; import com.android.settingslib.core.lifecycle.events.OnResume;
import com.android.settingslib.utils.StringUtil;
import com.android.settingslib.widget.ActionButtonsPreference; import com.android.settingslib.widget.ActionButtonsPreference;
import com.android.settingslib.widget.LayoutPreference; import com.android.settingslib.widget.LayoutPreference;
import com.android.wifitrackerlib.WifiEntry; import com.android.wifitrackerlib.WifiEntry;
@@ -90,6 +93,11 @@ import java.net.Inet4Address;
import java.net.Inet6Address; import java.net.Inet6Address;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.time.Duration;
import java.time.Instant;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.StringJoiner; import java.util.StringJoiner;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -173,6 +181,7 @@ public class WifiDetailPreferenceController2 extends AbstractPreferenceControlle
WifiDataUsageSummaryPreferenceController mSummaryHeaderController; WifiDataUsageSummaryPreferenceController mSummaryHeaderController;
private final IconInjector mIconInjector; private final IconInjector mIconInjector;
private final Clock mClock;
private final NetworkRequest mNetworkRequest = new NetworkRequest.Builder() private final NetworkRequest mNetworkRequest = new NetworkRequest.Builder()
.clearCapabilities().addTransportType(TRANSPORT_WIFI).build(); .clearCapabilities().addTransportType(TRANSPORT_WIFI).build();
@@ -183,6 +192,8 @@ public class WifiDetailPreferenceController2 extends AbstractPreferenceControlle
public void onLinkPropertiesChanged(Network network, LinkProperties lp) { public void onLinkPropertiesChanged(Network network, LinkProperties lp) {
if (network.equals(mNetwork) && !lp.equals(mLinkProperties)) { if (network.equals(mNetwork) && !lp.equals(mLinkProperties)) {
mLinkProperties = lp; mLinkProperties = lp;
refreshEntityHeader();
refreshButtons();
refreshIpLayerInfo(); refreshIpLayerInfo();
} }
} }
@@ -253,7 +264,7 @@ public class WifiDetailPreferenceController2 extends AbstractPreferenceControlle
MetricsFeatureProvider metricsFeatureProvider) { MetricsFeatureProvider metricsFeatureProvider) {
return new WifiDetailPreferenceController2( return new WifiDetailPreferenceController2(
wifiEntry, connectivityManager, context, fragment, handler, lifecycle, wifiEntry, connectivityManager, context, fragment, handler, lifecycle,
wifiManager, metricsFeatureProvider, new IconInjector(context)); wifiManager, metricsFeatureProvider, new IconInjector(context), new Clock());
} }
@VisibleForTesting @VisibleForTesting
@@ -266,7 +277,8 @@ public class WifiDetailPreferenceController2 extends AbstractPreferenceControlle
Lifecycle lifecycle, Lifecycle lifecycle,
WifiManager wifiManager, WifiManager wifiManager,
MetricsFeatureProvider metricsFeatureProvider, MetricsFeatureProvider metricsFeatureProvider,
IconInjector injector) { IconInjector injector,
Clock clock) {
super(context); super(context);
mWifiEntry = wifiEntry; mWifiEntry = wifiEntry;
@@ -278,6 +290,7 @@ public class WifiDetailPreferenceController2 extends AbstractPreferenceControlle
mWifiManager = wifiManager; mWifiManager = wifiManager;
mMetricsFeatureProvider = metricsFeatureProvider; mMetricsFeatureProvider = metricsFeatureProvider;
mIconInjector = injector; mIconInjector = injector;
mClock = clock;
mLifecycle = lifecycle; mLifecycle = lifecycle;
lifecycle.addObserver(this); lifecycle.addObserver(this);
@@ -313,6 +326,7 @@ public class WifiDetailPreferenceController2 extends AbstractPreferenceControlle
.setButton4Text(R.string.share) .setButton4Text(R.string.share)
.setButton4Icon(R.drawable.ic_qrcode_24dp) .setButton4Icon(R.drawable.ic_qrcode_24dp)
.setButton4OnClickListener(view -> shareNetwork()); .setButton4OnClickListener(view -> shareNetwork());
updateCaptivePortalButton();
mSignalStrengthPref = screen.findPreference(KEY_SIGNAL_STRENGTH_PREF); mSignalStrengthPref = screen.findPreference(KEY_SIGNAL_STRENGTH_PREF);
mTxLinkSpeedPref = screen.findPreference(KEY_TX_LINK_SPEED); mTxLinkSpeedPref = screen.findPreference(KEY_TX_LINK_SPEED);
@@ -333,6 +347,43 @@ public class WifiDetailPreferenceController2 extends AbstractPreferenceControlle
mSecurityPref.setSummary(mWifiEntry.getSecurityString(false /* concise */)); mSecurityPref.setSummary(mWifiEntry.getSecurityString(false /* concise */));
} }
/**
* Update text, icon and listener of the captive portal button.
* @return True if the button should be shown.
*/
private boolean updateCaptivePortalButton() {
final Uri venueInfoUrl = getCaptivePortalVenueInfoUrl();
if (venueInfoUrl == null) {
mButtonsPref.setButton2Text(R.string.wifi_sign_in_button_text)
.setButton2Icon(R.drawable.ic_settings_sign_in)
.setButton2OnClickListener(view -> signIntoNetwork());
return canSignIntoNetwork();
}
mButtonsPref.setButton2Text(R.string.wifi_venue_website_button_text)
.setButton2Icon(R.drawable.ic_settings_sign_in)
.setButton2OnClickListener(view -> {
final Intent infoIntent = new Intent(Intent.ACTION_VIEW);
infoIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
infoIntent.setData(venueInfoUrl);
mContext.startActivity(infoIntent);
});
// Only show the venue website when the network is connected.
return mWifiEntry.getConnectedState() == WifiEntry.CONNECTED_STATE_CONNECTED;
}
private Uri getCaptivePortalVenueInfoUrl() {
final LinkProperties lp = mLinkProperties;
if (lp == null) {
return null;
}
final CaptivePortalData data = lp.getCaptivePortalData();
if (data == null) {
return null;
}
return data.getVenueInfoUrl();
}
private void setupEntityHeader(PreferenceScreen screen) { private void setupEntityHeader(PreferenceScreen screen) {
LayoutPreference headerPref = screen.findPreference(KEY_HEADER); LayoutPreference headerPref = screen.findPreference(KEY_HEADER);
@@ -359,6 +410,37 @@ public class WifiDetailPreferenceController2 extends AbstractPreferenceControlle
mEntityHeaderController.setLabel(mWifiEntry.getTitle()); mEntityHeaderController.setLabel(mWifiEntry.getTitle());
} }
private String getExpiryTimeSummary() {
if (mLinkProperties == null || mLinkProperties.getCaptivePortalData() == null) {
return null;
}
final long expiryTimeMillis = mLinkProperties.getCaptivePortalData().getExpiryTimeMillis();
if (expiryTimeMillis <= 0) {
return null;
}
final ZonedDateTime now = mClock.now();
final ZonedDateTime expiryTime = ZonedDateTime.ofInstant(
Instant.ofEpochMilli(expiryTimeMillis),
now.getZone());
if (now.isAfter(expiryTime)) {
return null;
}
if (now.plusDays(2).isAfter(expiryTime)) {
// Expiration within 2 days: show a duration
return mContext.getString(R.string.wifi_time_remaining, StringUtil.formatElapsedTime(
mContext,
Duration.between(now, expiryTime).getSeconds() * 1000,
false /* withSeconds */));
}
// For more than 2 days, show the expiry date
return mContext.getString(R.string.wifi_expiry_time,
DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT).format(expiryTime));
}
private void refreshEntityHeader() { private void refreshEntityHeader() {
if (usingDataUsageHeader(mContext)) { if (usingDataUsageHeader(mContext)) {
mSummaryHeaderController.updateState(mDataUsageSummaryPref); mSummaryHeaderController.updateState(mDataUsageSummaryPref);
@@ -367,6 +449,7 @@ public class WifiDetailPreferenceController2 extends AbstractPreferenceControlle
mEntityHeaderController mEntityHeaderController
.setSummary(summary) .setSummary(summary)
.setSecondSummary(getExpiryTimeSummary())
.setRecyclerView(mFragment.getListView(), mLifecycle) .setRecyclerView(mFragment.getListView(), mLifecycle)
.done(mFragment.getActivity(), true /* rebind */); .done(mFragment.getActivity(), true /* rebind */);
} }
@@ -589,13 +672,13 @@ public class WifiDetailPreferenceController2 extends AbstractPreferenceControlle
private void refreshButtons() { private void refreshButtons() {
final boolean canForgetNetwork = mWifiEntry.canForget(); final boolean canForgetNetwork = mWifiEntry.canForget();
final boolean canSignIntoNetwork = canSignIntoNetwork(); final boolean showCaptivePortalButton = updateCaptivePortalButton();
final boolean canConnectDisconnectNetwork = mWifiEntry.canConnect() final boolean canConnectDisconnectNetwork = mWifiEntry.canConnect()
|| mWifiEntry.canDisconnect(); || mWifiEntry.canDisconnect();
final boolean canShareNetwork = canShareNetwork(); final boolean canShareNetwork = canShareNetwork();
mButtonsPref.setButton1Visible(canForgetNetwork); mButtonsPref.setButton1Visible(canForgetNetwork);
mButtonsPref.setButton2Visible(canSignIntoNetwork); mButtonsPref.setButton2Visible(showCaptivePortalButton);
// If it's expired and connected, shows Disconnect button for users to disconnect it. // If it's expired and connected, shows Disconnect button for users to disconnect it.
// If it's expired and not connected, hides the button and users are not able to connect it. // If it's expired and not connected, hides the button and users are not able to connect it.
// //
@@ -612,7 +695,7 @@ public class WifiDetailPreferenceController2 extends AbstractPreferenceControlle
mButtonsPref.setButton3Icon(getConnectDisconnectButtonIconResource()); mButtonsPref.setButton3Icon(getConnectDisconnectButtonIconResource());
mButtonsPref.setButton4Visible(canShareNetwork); mButtonsPref.setButton4Visible(canShareNetwork);
mButtonsPref.setVisible(canForgetNetwork mButtonsPref.setVisible(canForgetNetwork
|| canSignIntoNetwork || showCaptivePortalButton
|| canConnectDisconnectNetwork || canConnectDisconnectNetwork
|| canShareNetwork); || canShareNetwork);
} }
@@ -839,6 +922,13 @@ public class WifiDetailPreferenceController2 extends AbstractPreferenceControlle
} }
} }
@VisibleForTesting
static class Clock {
public ZonedDateTime now() {
return ZonedDateTime.now();
}
}
private boolean usingDataUsageHeader(Context context) { private boolean usingDataUsageHeader(Context context) {
return FeatureFlagUtils.isEnabled(context, FeatureFlags.WIFI_DETAILS_DATAUSAGE_HEADER); return FeatureFlagUtils.isEnabled(context, FeatureFlags.WIFI_DETAILS_DATAUSAGE_HEADER);
} }

View File

@@ -20,7 +20,6 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doNothing;
@@ -41,6 +40,7 @@ import android.content.res.Resources;
import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.net.CaptivePortalData;
import android.net.ConnectivityManager; import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback; import android.net.ConnectivityManager.NetworkCallback;
import android.net.IpPrefix; import android.net.IpPrefix;
@@ -52,6 +52,7 @@ import android.net.NetworkCapabilities;
import android.net.NetworkInfo; import android.net.NetworkInfo;
import android.net.NetworkRequest; import android.net.NetworkRequest;
import android.net.RouteInfo; import android.net.RouteInfo;
import android.net.Uri;
import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiInfo; import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager; import android.net.wifi.WifiManager;
@@ -75,6 +76,7 @@ import com.android.settings.testutils.shadow.ShadowEntityHeaderController;
import com.android.settings.widget.EntityHeaderController; import com.android.settings.widget.EntityHeaderController;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import com.android.settingslib.core.lifecycle.Lifecycle; import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.utils.StringUtil;
import com.android.settingslib.widget.ActionButtonsPreference; import com.android.settingslib.widget.ActionButtonsPreference;
import com.android.settingslib.widget.LayoutPreference; import com.android.settingslib.widget.LayoutPreference;
import com.android.settingslib.wifi.AccessPoint; import com.android.settingslib.wifi.AccessPoint;
@@ -99,6 +101,10 @@ import org.robolectric.shadows.ShadowToast;
import java.net.Inet4Address; import java.net.Inet4Address;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -146,6 +152,8 @@ public class WifiDetailPreferenceControllerTest {
@Mock @Mock
private WifiDetailPreferenceController.IconInjector mockIconInjector; private WifiDetailPreferenceController.IconInjector mockIconInjector;
@Mock @Mock
private WifiDetailPreferenceController.Clock mMockClock;
@Mock
private MacAddress mockMacAddress; private MacAddress mockMacAddress;
@Mock(answer = Answers.RETURNS_DEEP_STUBS) @Mock(answer = Answers.RETURNS_DEEP_STUBS)
@@ -286,7 +294,10 @@ public class WifiDetailPreferenceControllerTest {
// builder pattern // builder pattern
when(mockHeaderController.setRecyclerView(mockFragment.getListView(), mLifecycle)) when(mockHeaderController.setRecyclerView(mockFragment.getListView(), mLifecycle))
.thenReturn(mockHeaderController); .thenReturn(mockHeaderController);
when(mockHeaderController.setSummary(anyString())).thenReturn(mockHeaderController); when(mockHeaderController.setSummary(nullable(String.class)))
.thenReturn(mockHeaderController);
when(mockHeaderController.setSecondSummary(nullable(String.class)))
.thenReturn(mockHeaderController);
when(mockIconInjector.getIcon(anyInt())).thenReturn(new ColorDrawable()); when(mockIconInjector.getIcon(anyInt())).thenReturn(new ColorDrawable());
setupMockedPreferenceScreen(); setupMockedPreferenceScreen();
@@ -338,7 +349,8 @@ public class WifiDetailPreferenceControllerTest {
mLifecycle, mLifecycle,
mockWifiManager, mockWifiManager,
mockMetricsFeatureProvider, mockMetricsFeatureProvider,
mockIconInjector); mockIconInjector,
mMockClock);
} }
private void setupMockedPreferenceScreen() { private void setupMockedPreferenceScreen() {
@@ -525,6 +537,54 @@ public class WifiDetailPreferenceControllerTest {
verify(mockHeaderController).setSummary(summary); verify(mockHeaderController).setSummary(summary);
} }
private void doShouldShowRemainingTimeTest(ZonedDateTime now, long timeRemainingMs) {
when(mMockClock.now()).thenReturn(now);
setUpForConnectedNetwork();
displayAndResume();
final CaptivePortalData data = new CaptivePortalData.Builder()
.setExpiryTime(now.toInstant().getEpochSecond() * 1000 + timeRemainingMs)
.build();
final LinkProperties lp = new LinkProperties();
lp.setCaptivePortalData(data);
updateLinkProperties(lp);
}
@Test
public void entityHeader_shouldShowShortRemainingTime() {
// Expires in 1h, 2min, 15sec
final long timeRemainingMs = (3600 + 2 * 60 + 15) * 1000;
final ZonedDateTime fakeNow = ZonedDateTime.of(2020, 1, 2, 3, 4, 5, 6,
ZoneId.of("Europe/London"));
doShouldShowRemainingTimeTest(fakeNow, timeRemainingMs);
final String expectedSummary = mContext.getString(R.string.wifi_time_remaining,
StringUtil.formatElapsedTime(mContext, timeRemainingMs, false /* withSeconds */));
final InOrder inOrder = inOrder(mockHeaderController);
inOrder.verify(mockHeaderController).setSecondSummary(expectedSummary);
updateLinkProperties(new LinkProperties());
inOrder.verify(mockHeaderController).setSecondSummary((String) null);
}
@Test
public void entityHeader_shouldShowExpiryDate() {
// Expires in 49h, 2min, 15sec
final long timeRemainingMs = (49 * 3600 + 2 * 60 + 15) * 1000;
final ZonedDateTime fakeNow = ZonedDateTime.of(2020, 1, 2, 3, 4, 5, 6,
ZoneId.of("Europe/London"));
doShouldShowRemainingTimeTest(fakeNow, timeRemainingMs);
final String expectedSummary = mContext.getString(
R.string.wifi_expiry_time,
DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT).format(
fakeNow.plusNanos(timeRemainingMs * 1_000_000)));
final InOrder inOrder = inOrder(mockHeaderController);
inOrder.verify(mockHeaderController).setSecondSummary(expectedSummary);
updateLinkProperties(new LinkProperties());
inOrder.verify(mockHeaderController).setSecondSummary((String) null);
}
@Test @Test
public void entityHeader_shouldConvertSavedAsDisconnected() { public void entityHeader_shouldConvertSavedAsDisconnected() {
setUpForDisconnectedNetwork(); setUpForDisconnectedNetwork();
@@ -1272,6 +1332,8 @@ public class WifiDetailPreferenceControllerTest {
nc.addCapability(NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL); nc.addCapability(NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL);
updateNetworkCapabilities(nc); updateNetworkCapabilities(nc);
inOrder.verify(mockButtonsPref).setButton2Text(R.string.wifi_sign_in_button_text);
inOrder.verify(mockButtonsPref).setButton2Visible(true); inOrder.verify(mockButtonsPref).setButton2Visible(true);
nc.removeCapability(NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL); nc.removeCapability(NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL);
@@ -1279,6 +1341,31 @@ public class WifiDetailPreferenceControllerTest {
inOrder.verify(mockButtonsPref).setButton2Visible(false); inOrder.verify(mockButtonsPref).setButton2Visible(false);
} }
@Test
public void captivePortal_shouldShowVenueInfoButton() {
setUpForConnectedNetwork();
InOrder inOrder = inOrder(mockButtonsPref);
displayAndResume();
inOrder.verify(mockButtonsPref).setButton2Visible(false);
LinkProperties lp = new LinkProperties();
final CaptivePortalData data = new CaptivePortalData.Builder()
.setVenueInfoUrl(Uri.parse("https://example.com/info"))
.build();
lp.setCaptivePortalData(data);
updateLinkProperties(lp);
inOrder.verify(mockButtonsPref).setButton2Text(R.string.wifi_venue_website_button_text);
inOrder.verify(mockButtonsPref).setButton2Visible(true);
lp.setCaptivePortalData(null);
updateLinkProperties(lp);
inOrder.verify(mockButtonsPref).setButton2Visible(false);
}
@Test @Test
public void testSignInButton_shouldStartCaptivePortalApp() { public void testSignInButton_shouldStartCaptivePortalApp() {
setUpForConnectedNetwork(); setUpForConnectedNetwork();
@@ -1286,7 +1373,8 @@ public class WifiDetailPreferenceControllerTest {
displayAndResume(); displayAndResume();
ArgumentCaptor<OnClickListener> captor = ArgumentCaptor.forClass(OnClickListener.class); ArgumentCaptor<OnClickListener> captor = ArgumentCaptor.forClass(OnClickListener.class);
verify(mockButtonsPref).setButton2OnClickListener(captor.capture()); verify(mockButtonsPref, atLeastOnce()).setButton2OnClickListener(captor.capture());
// getValue() returns the last captured value
captor.getValue().onClick(null); captor.getValue().onClick(null);
verify(mockConnectivityManager).startCaptivePortalApp(mockNetwork); verify(mockConnectivityManager).startCaptivePortalApp(mockNetwork);
verify(mockMetricsFeatureProvider) verify(mockMetricsFeatureProvider)

View File

@@ -20,8 +20,8 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
@@ -40,6 +40,7 @@ import android.content.res.Resources;
import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.net.CaptivePortalData;
import android.net.ConnectivityManager; import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback; import android.net.ConnectivityManager.NetworkCallback;
import android.net.IpPrefix; import android.net.IpPrefix;
@@ -51,6 +52,7 @@ import android.net.NetworkCapabilities;
import android.net.NetworkInfo; import android.net.NetworkInfo;
import android.net.NetworkRequest; import android.net.NetworkRequest;
import android.net.RouteInfo; import android.net.RouteInfo;
import android.net.Uri;
import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiInfo; import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager; import android.net.wifi.WifiManager;
@@ -74,6 +76,7 @@ import com.android.settings.testutils.shadow.ShadowEntityHeaderController;
import com.android.settings.widget.EntityHeaderController; import com.android.settings.widget.EntityHeaderController;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import com.android.settingslib.core.lifecycle.Lifecycle; import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.utils.StringUtil;
import com.android.settingslib.widget.ActionButtonsPreference; import com.android.settingslib.widget.ActionButtonsPreference;
import com.android.settingslib.widget.LayoutPreference; import com.android.settingslib.widget.LayoutPreference;
import com.android.wifitrackerlib.NetworkDetailsTracker; import com.android.wifitrackerlib.NetworkDetailsTracker;
@@ -98,6 +101,10 @@ import org.robolectric.shadows.ShadowToast;
import java.net.Inet4Address; import java.net.Inet4Address;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.Arrays; import java.util.Arrays;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -148,6 +155,8 @@ public class WifiDetailPreferenceController2Test {
@Mock @Mock
private WifiDetailPreferenceController2.IconInjector mMockIconInjector; private WifiDetailPreferenceController2.IconInjector mMockIconInjector;
@Mock @Mock
private WifiDetailPreferenceController2.Clock mMockClock;
@Mock
private MacAddress mMockMacAddress; private MacAddress mMockMacAddress;
@Mock(answer = Answers.RETURNS_DEEP_STUBS) @Mock(answer = Answers.RETURNS_DEEP_STUBS)
@@ -288,7 +297,10 @@ public class WifiDetailPreferenceController2Test {
// builder pattern // builder pattern
when(mMockHeaderController.setRecyclerView(mMockFragment.getListView(), mLifecycle)) when(mMockHeaderController.setRecyclerView(mMockFragment.getListView(), mLifecycle))
.thenReturn(mMockHeaderController); .thenReturn(mMockHeaderController);
when(mMockHeaderController.setSummary(anyString())).thenReturn(mMockHeaderController); when(mMockHeaderController.setSummary(nullable(String.class)))
.thenReturn(mMockHeaderController);
when(mMockHeaderController.setSecondSummary(nullable(String.class)))
.thenReturn(mMockHeaderController);
when(mMockIconInjector.getIcon(anyInt())).thenReturn(new ColorDrawable()); when(mMockIconInjector.getIcon(anyInt())).thenReturn(new ColorDrawable());
setupMockedPreferenceScreen(); setupMockedPreferenceScreen();
@@ -326,7 +338,8 @@ public class WifiDetailPreferenceController2Test {
mLifecycle, mLifecycle,
mMockWifiManager, mMockWifiManager,
mMockMetricsFeatureProvider, mMockMetricsFeatureProvider,
mMockIconInjector)); mMockIconInjector,
mMockClock));
} }
private void setupMockedPreferenceScreen() { private void setupMockedPreferenceScreen() {
@@ -514,6 +527,54 @@ public class WifiDetailPreferenceController2Test {
verify(mMockHeaderController).setSummary(summary); verify(mMockHeaderController).setSummary(summary);
} }
private void doShouldShowRemainingTimeTest(ZonedDateTime now, long timeRemainingMs) {
when(mMockClock.now()).thenReturn(now);
setUpForConnectedNetwork();
displayAndResume();
final CaptivePortalData data = new CaptivePortalData.Builder()
.setExpiryTime(now.toInstant().getEpochSecond() * 1000 + timeRemainingMs)
.build();
final LinkProperties lp = new LinkProperties();
lp.setCaptivePortalData(data);
updateLinkProperties(lp);
}
@Test
public void entityHeader_shouldShowShortRemainingTime() {
// Expires in 1h, 2min, 15sec
final long timeRemainingMs = (3600 + 2 * 60 + 15) * 1000;
final ZonedDateTime fakeNow = ZonedDateTime.of(2020, 1, 2, 3, 4, 5, 6,
ZoneId.of("Europe/London"));
doShouldShowRemainingTimeTest(fakeNow, timeRemainingMs);
final String expectedSummary = mContext.getString(R.string.wifi_time_remaining,
StringUtil.formatElapsedTime(mContext, timeRemainingMs, false /* withSeconds */));
final InOrder inOrder = inOrder(mMockHeaderController);
inOrder.verify(mMockHeaderController).setSecondSummary(expectedSummary);
updateLinkProperties(new LinkProperties());
inOrder.verify(mMockHeaderController).setSecondSummary((String) null);
}
@Test
public void entityHeader_shouldShowExpiryDate() {
// Expires in 49h, 2min, 15sec
final long timeRemainingMs = (49 * 3600 + 2 * 60 + 15) * 1000;
final ZonedDateTime fakeNow = ZonedDateTime.of(2020, 1, 2, 3, 4, 5, 6,
ZoneId.of("Europe/London"));
doShouldShowRemainingTimeTest(fakeNow, timeRemainingMs);
final String expectedSummary = mContext.getString(
R.string.wifi_expiry_time,
DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT).format(
fakeNow.plusNanos(timeRemainingMs * 1_000_000)));
final InOrder inOrder = inOrder(mMockHeaderController);
inOrder.verify(mMockHeaderController).setSecondSummary(expectedSummary);
updateLinkProperties(new LinkProperties());
inOrder.verify(mMockHeaderController).setSecondSummary((String) null);
}
@Test @Test
public void entityHeader_shouldConvertSavedAsDisconnected() { public void entityHeader_shouldConvertSavedAsDisconnected() {
setUpForDisconnectedNetwork(); setUpForDisconnectedNetwork();
@@ -1260,6 +1321,8 @@ public class WifiDetailPreferenceController2Test {
nc.addCapability(NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL); nc.addCapability(NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL);
updateNetworkCapabilities(nc); updateNetworkCapabilities(nc);
inOrder.verify(mMockButtonsPref).setButton2Text(R.string.wifi_sign_in_button_text);
inOrder.verify(mMockButtonsPref).setButton2Visible(true); inOrder.verify(mMockButtonsPref).setButton2Visible(true);
nc.removeCapability(NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL); nc.removeCapability(NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL);
@@ -1267,6 +1330,31 @@ public class WifiDetailPreferenceController2Test {
inOrder.verify(mMockButtonsPref).setButton2Visible(false); inOrder.verify(mMockButtonsPref).setButton2Visible(false);
} }
@Test
public void captivePortal_shouldShowVenueInfoButton() {
setUpForConnectedNetwork();
InOrder inOrder = inOrder(mMockButtonsPref);
displayAndResume();
inOrder.verify(mMockButtonsPref).setButton2Visible(false);
LinkProperties lp = new LinkProperties();
final CaptivePortalData data = new CaptivePortalData.Builder()
.setVenueInfoUrl(Uri.parse("https://example.com/info"))
.build();
lp.setCaptivePortalData(data);
updateLinkProperties(lp);
inOrder.verify(mMockButtonsPref).setButton2Text(R.string.wifi_venue_website_button_text);
inOrder.verify(mMockButtonsPref).setButton2Visible(true);
lp.setCaptivePortalData(null);
updateLinkProperties(lp);
inOrder.verify(mMockButtonsPref).setButton2Visible(false);
}
@Test @Test
public void testSignInButton_shouldStartCaptivePortalApp() { public void testSignInButton_shouldStartCaptivePortalApp() {
setUpForConnectedNetwork(); setUpForConnectedNetwork();
@@ -1274,7 +1362,8 @@ public class WifiDetailPreferenceController2Test {
displayAndResume(); displayAndResume();
ArgumentCaptor<OnClickListener> captor = ArgumentCaptor.forClass(OnClickListener.class); ArgumentCaptor<OnClickListener> captor = ArgumentCaptor.forClass(OnClickListener.class);
verify(mMockButtonsPref).setButton2OnClickListener(captor.capture()); verify(mMockButtonsPref, atLeastOnce()).setButton2OnClickListener(captor.capture());
// getValue() returns the last captured value
captor.getValue().onClick(null); captor.getValue().onClick(null);
verify(mMockConnectivityManager).startCaptivePortalApp(mMockNetwork); verify(mMockConnectivityManager).startCaptivePortalApp(mMockNetwork);
verify(mMockMetricsFeatureProvider) verify(mMockMetricsFeatureProvider)