Add capport info to WiFi details
Session expiration time and venue webpage can be obtained through the captive portal API. Test: make RunSettingsRoboTests \ ROBOTEST_FILTER=WifiDetailPreferenceControllerTest Bug: 139269711 Change-Id: Ie767a9b8eb17de2c1b70928a8f3cdf4cf2a1dbd1
This commit is contained in:
@@ -1755,6 +1755,12 @@
|
||||
<string name="wifi_band_5ghz">5 GHz</string>
|
||||
<!-- Wifi Sign in text for button [CHAR LIMIT = 40]-->
|
||||
<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] -->
|
||||
<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] -->
|
||||
|
@@ -31,6 +31,7 @@ import android.graphics.Bitmap;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.VectorDrawable;
|
||||
import android.net.CaptivePortalData;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.ConnectivityManager.NetworkCallback;
|
||||
import android.net.LinkAddress;
|
||||
@@ -41,6 +42,7 @@ import android.net.NetworkInfo;
|
||||
import android.net.NetworkRequest;
|
||||
import android.net.NetworkUtils;
|
||||
import android.net.RouteInfo;
|
||||
import android.net.Uri;
|
||||
import android.net.wifi.WifiConfiguration;
|
||||
import android.net.wifi.WifiInfo;
|
||||
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.events.OnPause;
|
||||
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.LayoutPreference;
|
||||
import com.android.settingslib.wifi.AccessPoint;
|
||||
@@ -86,6 +89,10 @@ import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
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.stream.Collectors;
|
||||
|
||||
@@ -189,6 +196,7 @@ public class WifiDetailPreferenceController extends AbstractPreferenceController
|
||||
WifiDataUsageSummaryPreferenceController mSummaryHeaderController;
|
||||
|
||||
private final IconInjector mIconInjector;
|
||||
private final Clock mClock;
|
||||
private final IntentFilter mFilter;
|
||||
|
||||
// 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) {
|
||||
if (network.equals(mNetwork) && !lp.equals(mLinkProperties)) {
|
||||
mLinkProperties = lp;
|
||||
refreshEntityHeader();
|
||||
refreshButtons();
|
||||
refreshIpLayerInfo();
|
||||
}
|
||||
}
|
||||
@@ -322,7 +332,7 @@ public class WifiDetailPreferenceController extends AbstractPreferenceController
|
||||
MetricsFeatureProvider metricsFeatureProvider) {
|
||||
return new WifiDetailPreferenceController(
|
||||
accessPoint, connectivityManager, context, fragment, handler, lifecycle,
|
||||
wifiManager, metricsFeatureProvider, new IconInjector(context));
|
||||
wifiManager, metricsFeatureProvider, new IconInjector(context), new Clock());
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
@@ -335,7 +345,8 @@ public class WifiDetailPreferenceController extends AbstractPreferenceController
|
||||
Lifecycle lifecycle,
|
||||
WifiManager wifiManager,
|
||||
MetricsFeatureProvider metricsFeatureProvider,
|
||||
IconInjector injector) {
|
||||
IconInjector injector,
|
||||
Clock clock) {
|
||||
super(context);
|
||||
|
||||
mAccessPoint = accessPoint;
|
||||
@@ -347,6 +358,7 @@ public class WifiDetailPreferenceController extends AbstractPreferenceController
|
||||
mWifiManager = wifiManager;
|
||||
mMetricsFeatureProvider = metricsFeatureProvider;
|
||||
mIconInjector = injector;
|
||||
mClock = clock;
|
||||
|
||||
mFilter = new IntentFilter();
|
||||
mFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
|
||||
@@ -404,9 +416,6 @@ public class WifiDetailPreferenceController extends AbstractPreferenceController
|
||||
.setButton1Text(R.string.forget)
|
||||
.setButton1Icon(R.drawable.ic_settings_delete)
|
||||
.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)
|
||||
.setButton3Icon(R.drawable.ic_settings_wireless)
|
||||
.setButton3OnClickListener(view -> connectNetwork())
|
||||
@@ -414,6 +423,7 @@ public class WifiDetailPreferenceController extends AbstractPreferenceController
|
||||
.setButton4Text(R.string.share)
|
||||
.setButton4Icon(R.drawable.ic_qrcode_24dp)
|
||||
.setButton4OnClickListener(view -> shareNetwork());
|
||||
updateCaptivePortalButton();
|
||||
|
||||
if (isPasspointConfigurationR1Expired()) {
|
||||
// Hide Connect button.
|
||||
@@ -439,6 +449,42 @@ public class WifiDetailPreferenceController extends AbstractPreferenceController
|
||||
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) {
|
||||
LayoutPreference headerPref = screen.findPreference(KEY_HEADER);
|
||||
|
||||
@@ -464,6 +510,37 @@ public class WifiDetailPreferenceController extends AbstractPreferenceController
|
||||
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() {
|
||||
if (usingDataUsageHeader(mContext)) {
|
||||
mSummaryHeaderController.updateState(mDataUsageSummaryPref);
|
||||
@@ -480,6 +557,7 @@ public class WifiDetailPreferenceController extends AbstractPreferenceController
|
||||
|
||||
mEntityHeaderController
|
||||
.setSummary(summary)
|
||||
.setSecondSummary(getExpiryTimeSummary())
|
||||
.setRecyclerView(mFragment.getListView(), mLifecycle)
|
||||
.done(mFragment.getActivity(), true /* rebind */);
|
||||
}
|
||||
@@ -766,16 +844,16 @@ public class WifiDetailPreferenceController extends AbstractPreferenceController
|
||||
mIsEphemeral ? R.string.wifi_disconnect_button_text : R.string.forget);
|
||||
|
||||
boolean canForgetNetwork = canForgetNetwork();
|
||||
boolean canSignIntoNetwork = canSignIntoNetwork();
|
||||
boolean showCaptivePortalButton = updateCaptivePortalButton();
|
||||
boolean canConnectNetwork = canConnectNetwork() && !isPasspointConfigurationR1Expired();
|
||||
boolean canShareNetwork = canShareNetwork();
|
||||
|
||||
mButtonsPref.setButton1Visible(canForgetNetwork);
|
||||
mButtonsPref.setButton2Visible(canSignIntoNetwork);
|
||||
mButtonsPref.setButton2Visible(showCaptivePortalButton);
|
||||
mButtonsPref.setButton3Visible(canConnectNetwork);
|
||||
mButtonsPref.setButton4Visible(canShareNetwork);
|
||||
mButtonsPref.setVisible(canForgetNetwork
|
||||
|| canSignIntoNetwork
|
||||
|| showCaptivePortalButton
|
||||
|| canConnectNetwork
|
||||
|| 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) {
|
||||
return FeatureFlagUtils.isEnabled(context, FeatureFlags.WIFI_DETAILS_DATAUSAGE_HEADER);
|
||||
}
|
||||
|
@@ -20,7 +20,6 @@ import static com.google.common.truth.Truth.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.nullable;
|
||||
import static org.mockito.Mockito.atLeastOnce;
|
||||
import static org.mockito.Mockito.doNothing;
|
||||
@@ -41,6 +40,7 @@ import android.content.res.Resources;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.CaptivePortalData;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.ConnectivityManager.NetworkCallback;
|
||||
import android.net.IpPrefix;
|
||||
@@ -52,6 +52,7 @@ import android.net.NetworkCapabilities;
|
||||
import android.net.NetworkInfo;
|
||||
import android.net.NetworkRequest;
|
||||
import android.net.RouteInfo;
|
||||
import android.net.Uri;
|
||||
import android.net.wifi.WifiConfiguration;
|
||||
import android.net.wifi.WifiInfo;
|
||||
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.settingslib.core.instrumentation.MetricsFeatureProvider;
|
||||
import com.android.settingslib.core.lifecycle.Lifecycle;
|
||||
import com.android.settingslib.utils.StringUtil;
|
||||
import com.android.settingslib.widget.ActionButtonsPreference;
|
||||
import com.android.settingslib.widget.LayoutPreference;
|
||||
import com.android.settingslib.wifi.AccessPoint;
|
||||
@@ -99,6 +101,10 @@ import org.robolectric.shadows.ShadowToast;
|
||||
import java.net.Inet4Address;
|
||||
import java.net.InetAddress;
|
||||
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.Arrays;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -146,6 +152,8 @@ public class WifiDetailPreferenceControllerTest {
|
||||
@Mock
|
||||
private WifiDetailPreferenceController.IconInjector mockIconInjector;
|
||||
@Mock
|
||||
private WifiDetailPreferenceController.Clock mMockClock;
|
||||
@Mock
|
||||
private MacAddress mockMacAddress;
|
||||
|
||||
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
|
||||
@@ -286,7 +294,10 @@ public class WifiDetailPreferenceControllerTest {
|
||||
// builder pattern
|
||||
when(mockHeaderController.setRecyclerView(mockFragment.getListView(), mLifecycle))
|
||||
.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());
|
||||
|
||||
setupMockedPreferenceScreen();
|
||||
@@ -338,7 +349,8 @@ public class WifiDetailPreferenceControllerTest {
|
||||
mLifecycle,
|
||||
mockWifiManager,
|
||||
mockMetricsFeatureProvider,
|
||||
mockIconInjector);
|
||||
mockIconInjector,
|
||||
mMockClock);
|
||||
}
|
||||
|
||||
private void setupMockedPreferenceScreen() {
|
||||
@@ -525,6 +537,54 @@ public class WifiDetailPreferenceControllerTest {
|
||||
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
|
||||
public void entityHeader_shouldConvertSavedAsDisconnected() {
|
||||
setUpForDisconnectedNetwork();
|
||||
@@ -1272,6 +1332,8 @@ public class WifiDetailPreferenceControllerTest {
|
||||
|
||||
nc.addCapability(NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL);
|
||||
updateNetworkCapabilities(nc);
|
||||
|
||||
inOrder.verify(mockButtonsPref).setButton2Text(R.string.wifi_sign_in_button_text);
|
||||
inOrder.verify(mockButtonsPref).setButton2Visible(true);
|
||||
|
||||
nc.removeCapability(NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL);
|
||||
@@ -1279,6 +1341,31 @@ public class WifiDetailPreferenceControllerTest {
|
||||
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
|
||||
public void testSignInButton_shouldStartCaptivePortalApp() {
|
||||
setUpForConnectedNetwork();
|
||||
@@ -1286,7 +1373,8 @@ public class WifiDetailPreferenceControllerTest {
|
||||
displayAndResume();
|
||||
|
||||
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);
|
||||
verify(mockConnectivityManager).startCaptivePortalApp(mockNetwork);
|
||||
verify(mockMetricsFeatureProvider)
|
||||
|
Reference in New Issue
Block a user