Enable launching captive portal directly

Whenever a user has connected to a captive portal network, split the
ConnectedAccessPointPreference to allow directly signing into the
captive portal or modifying the network's settings. When in any other
network state, use the old behavior of a single tappable preference that
takes the user to settings.

Bug: 63929546
Bug: 68031656
Test: make RunSettingsRoboTests
Test: manual by connecting to Captive Portal and normal WiFi networks.

Change-Id: I444202a12138d90c94bda94945c121c8c0810536
(cherry picked from commit 7577624db7)
This commit is contained in:
Adam Newman
2018-03-08 16:59:34 -08:00
parent 767743d64d
commit c6b4f3d92c
7 changed files with 186 additions and 22 deletions

View File

@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2011 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.
-->
<!-- Settings button with optional ripple background through toggling visiblity. -->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/settings_button_no_background"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:scaleType="center"
android:src="@drawable/ic_settings"
android:contentDescription="@string/settings_button" />
<!-- Additional overdraw background to stop parent's material ripple -->
<FrameLayout
android:id="@+id/settings_button"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="invisible"
android:background="?android:attr/colorBackground">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:paddingStart="?android:attr/listPreferredItemPaddingEnd"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:background="?android:attr/selectableItemBackground"
android:scaleType="center"
android:src="@drawable/ic_settings"
android:contentDescription="@string/settings_button" />
</FrameLayout>
</FrameLayout>

View File

@@ -1731,6 +1731,8 @@
<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>
<!-- 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>
<!-- Link speed on Wifi Status screen --> <!-- Link speed on Wifi Status screen -->
<string name="link_speed">%1$d Mbps</string> <string name="link_speed">%1$d Mbps</string>

View File

@@ -31,12 +31,31 @@ import com.android.settingslib.wifi.AccessPointPreference;
public class ConnectedAccessPointPreference extends AccessPointPreference implements public class ConnectedAccessPointPreference extends AccessPointPreference implements
View.OnClickListener { View.OnClickListener {
private final CaptivePortalStatus mCaptivePortalStatus;
private OnGearClickListener mOnGearClickListener; private OnGearClickListener mOnGearClickListener;
private boolean mCaptivePortalNetwork;
public ConnectedAccessPointPreference(AccessPoint accessPoint, Context context, public ConnectedAccessPointPreference(AccessPoint accessPoint, Context context,
UserBadgeCache cache, @DrawableRes int iconResId, boolean forSavedNetworks) { UserBadgeCache cache, @DrawableRes int iconResId, boolean forSavedNetworks,
CaptivePortalStatus captivePortalStatus) {
super(accessPoint, context, cache, iconResId, forSavedNetworks); super(accessPoint, context, cache, iconResId, forSavedNetworks);
setWidgetLayoutResource(R.layout.preference_widget_gear_no_bg); mCaptivePortalStatus = captivePortalStatus;
}
@Override
protected int getWidgetLayoutResourceId() {
return R.layout.preference_widget_gear_optional_background;
}
@Override
public void refresh() {
super.refresh();
mCaptivePortalNetwork = mCaptivePortalStatus.isCaptivePortalNetwork();
setShowDivider(mCaptivePortalNetwork);
if (mCaptivePortalNetwork) {
setSummary(R.string.wifi_tap_to_sign_in);
}
} }
public void setOnGearClickListener(OnGearClickListener l) { public void setOnGearClickListener(OnGearClickListener l) {
@@ -44,6 +63,18 @@ public class ConnectedAccessPointPreference extends AccessPointPreference implem
notifyChanged(); notifyChanged();
} }
@Override
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
final View gear = holder.findViewById(R.id.settings_button);
gear.setOnClickListener(this);
final View gearNoBg = holder.findViewById(R.id.settings_button_no_background);
gearNoBg.setVisibility(mCaptivePortalNetwork ? View.INVISIBLE : View.VISIBLE);
gear.setVisibility(mCaptivePortalNetwork ? View.VISIBLE : View.INVISIBLE);
}
@Override @Override
public void onClick(View v) { public void onClick(View v) {
if (v.getId() == R.id.settings_button) { if (v.getId() == R.id.settings_button) {
@@ -56,4 +87,8 @@ public class ConnectedAccessPointPreference extends AccessPointPreference implem
public interface OnGearClickListener { public interface OnGearClickListener {
void onGearClick(ConnectedAccessPointPreference p); void onGearClick(ConnectedAccessPointPreference p);
} }
public interface CaptivePortalStatus {
boolean isCaptivePortalNetwork();
}
} }

View File

@@ -26,6 +26,8 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.res.Resources; import android.content.res.Resources;
import android.net.ConnectivityManager; import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo; import android.net.NetworkInfo;
import android.net.NetworkInfo.State; import android.net.NetworkInfo.State;
import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiConfiguration;
@@ -60,6 +62,7 @@ import com.android.settings.search.SearchIndexableRaw;
import com.android.settings.widget.SummaryUpdater.OnSummaryChangeListener; import com.android.settings.widget.SummaryUpdater.OnSummaryChangeListener;
import com.android.settings.widget.SwitchBarController; import com.android.settings.widget.SwitchBarController;
import com.android.settings.wifi.details.WifiNetworkDetailsFragment; import com.android.settings.wifi.details.WifiNetworkDetailsFragment;
import com.android.settings.wrapper.ConnectivityManagerWrapper;
import com.android.settings.wrapper.WifiManagerWrapper; import com.android.settings.wrapper.WifiManagerWrapper;
import com.android.settingslib.RestrictedLockUtils; import com.android.settingslib.RestrictedLockUtils;
import com.android.settingslib.wifi.AccessPoint; import com.android.settingslib.wifi.AccessPoint;
@@ -117,6 +120,7 @@ public class WifiSettings extends RestrictedSettingsFragment
}; };
protected WifiManager mWifiManager; protected WifiManager mWifiManager;
private ConnectivityManager mConnectivityManager;
private WifiManager.ActionListener mConnectListener; private WifiManager.ActionListener mConnectListener;
private WifiManager.ActionListener mSaveListener; private WifiManager.ActionListener mSaveListener;
private WifiManager.ActionListener mForgetListener; private WifiManager.ActionListener mForgetListener;
@@ -238,6 +242,11 @@ public class WifiSettings extends RestrictedSettingsFragment
getActivity(), this, getLifecycle(), true, true); getActivity(), this, getLifecycle(), true, true);
mWifiManager = mWifiTracker.getManager(); mWifiManager = mWifiTracker.getManager();
final Activity activity = getActivity();
if (activity != null) {
mConnectivityManager = getActivity().getSystemService(ConnectivityManager.class);
}
mConnectListener = new WifiManager.ActionListener() { mConnectListener = new WifiManager.ActionListener() {
@Override @Override
public void onSuccess() { public void onSuccess() {
@@ -777,9 +786,11 @@ public class WifiSettings extends RestrictedSettingsFragment
@NonNull @NonNull
private ConnectedAccessPointPreference createConnectedAccessPointPreference( private ConnectedAccessPointPreference createConnectedAccessPointPreference(
AccessPoint accessPoint) { AccessPoint accessPoint,
ConnectedAccessPointPreference.CaptivePortalStatus captivePortalStatus) {
return new ConnectedAccessPointPreference(accessPoint, getPrefContext(), mUserBadgeCache, return new ConnectedAccessPointPreference(accessPoint, getPrefContext(), mUserBadgeCache,
R.drawable.ic_wifi_signal_0, false /* forSavedNetworks */); R.drawable.ic_wifi_signal_0, false /* forSavedNetworks */,
captivePortalStatus);
} }
/** /**
@@ -828,22 +839,31 @@ public class WifiSettings extends RestrictedSettingsFragment
* {@link #mConnectedAccessPointPreferenceCategory}. * {@link #mConnectedAccessPointPreferenceCategory}.
*/ */
private void addConnectedAccessPointPreference(AccessPoint connectedAp) { private void addConnectedAccessPointPreference(AccessPoint connectedAp) {
final ConnectedAccessPointPreference pref = createConnectedAccessPointPreference( final ConnectedAccessPointPreference pref =
connectedAp); createConnectedAccessPointPreference(
connectedAp, this::isConnectedToCaptivePortalNetwork);
// Launch details page on click. // Launch details page or captive portal on click.
pref.setOnPreferenceClickListener(preference -> { pref.setOnPreferenceClickListener(
preference -> {
pref.getAccessPoint().saveWifiState(pref.getExtras()); pref.getAccessPoint().saveWifiState(pref.getExtras());
Network network = getConnectedWifiNetwork();
new SubSettingLauncher(getContext()) if (isConnectedToCaptivePortalNetwork(network)) {
.setTitle(pref.getTitle()) ConnectivityManagerWrapper connectivityManagerWrapper =
.setDestination(WifiNetworkDetailsFragment.class.getName()) new ConnectivityManagerWrapper(mConnectivityManager);
.setArguments(pref.getExtras()) connectivityManagerWrapper.startCaptivePortalApp(network);
.setSourceMetricsCategory(getMetricsCategory()) } else {
.launch(); launchNetworkDetailsFragment(pref);
}
return true; return true;
}); });
pref.setOnGearClickListener(
preference -> {
pref.getAccessPoint().saveWifiState(pref.getExtras());
launchNetworkDetailsFragment(pref);
});
pref.refresh(); pref.refresh();
mConnectedAccessPointPreferenceCategory.addPreference(pref); mConnectedAccessPointPreferenceCategory.addPreference(pref);
@@ -854,6 +874,43 @@ public class WifiSettings extends RestrictedSettingsFragment
} }
} }
private void launchNetworkDetailsFragment(ConnectedAccessPointPreference pref) {
new SubSettingLauncher(getContext())
.setTitle(pref.getTitle())
.setDestination(WifiNetworkDetailsFragment.class.getName())
.setArguments(pref.getExtras())
.setSourceMetricsCategory(getMetricsCategory())
.launch();
}
private boolean isConnectedToCaptivePortalNetwork() {
return isConnectedToCaptivePortalNetwork(getConnectedWifiNetwork());
}
private boolean isConnectedToCaptivePortalNetwork(Network network) {
if (mConnectivityManager == null || network == null) {
return false;
}
return WifiUtils.canSignIntoNetwork(mConnectivityManager.getNetworkCapabilities(network));
}
private Network getConnectedWifiNetwork() {
if (mConnectivityManager != null) {
Network networks[] = mConnectivityManager.getAllNetworks();
if (networks != null) {
for (Network network : networks) {
NetworkCapabilities capabilities =
mConnectivityManager.getNetworkCapabilities(network);
if (capabilities != null
&& capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
return network;
}
}
}
}
return null;
}
/** Removes all preferences and hide the {@link #mConnectedAccessPointPreferenceCategory}. */ /** Removes all preferences and hide the {@link #mConnectedAccessPointPreferenceCategory}. */
private void removeConnectedAccessPointPreference() { private void removeConnectedAccessPointPreference() {
mConnectedAccessPointPreferenceCategory.removeAll(); mConnectedAccessPointPreferenceCategory.removeAll();

View File

@@ -16,11 +16,11 @@
package com.android.settings.wifi; package com.android.settings.wifi;
import android.app.admin.DevicePolicyManager;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.net.NetworkCapabilities;
import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiConfiguration;
import android.provider.Settings; import android.provider.Settings;
import android.text.TextUtils; import android.text.TextUtils;
@@ -101,4 +101,10 @@ public class WifiUtils {
Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 0) != 0; Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 0) != 0;
return isLockdownFeatureEnabled; return isLockdownFeatureEnabled;
} }
/** Returns true if the provided NetworkCapabilities indicate a captive portal network. */
public static boolean canSignIntoNetwork(NetworkCapabilities capabilities) {
return (capabilities != null
&& capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL));
}
} }

View File

@@ -511,8 +511,7 @@ public class WifiDetailPreferenceController extends AbstractPreferenceController
* Returns whether the user can sign into the network represented by this preference. * Returns whether the user can sign into the network represented by this preference.
*/ */
private boolean canSignIntoNetwork() { private boolean canSignIntoNetwork() {
return mNetworkCapabilities != null && mNetworkCapabilities.hasCapability( return WifiUtils.canSignIntoNetwork(mNetworkCapabilities);
NET_CAPABILITY_CAPTIVE_PORTAL);
} }
/** /**

View File

@@ -32,6 +32,7 @@ import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import org.robolectric.RuntimeEnvironment; import org.robolectric.RuntimeEnvironment;
@@ -44,6 +45,8 @@ public class ConnectedAccessPointPreferenceTest {
private View mView; private View mView;
@Mock @Mock
private ConnectedAccessPointPreference.OnGearClickListener mOnGearClickListener; private ConnectedAccessPointPreference.OnGearClickListener mOnGearClickListener;
@Mock
private ConnectedAccessPointPreference.CaptivePortalStatus mCaptivePortalStatus;
private Context mContext; private Context mContext;
private ConnectedAccessPointPreference mConnectedAccessPointPreference; private ConnectedAccessPointPreference mConnectedAccessPointPreference;
@@ -53,7 +56,7 @@ public class ConnectedAccessPointPreferenceTest {
mContext = RuntimeEnvironment.application; mContext = RuntimeEnvironment.application;
mConnectedAccessPointPreference = new ConnectedAccessPointPreference(mAccessPoint, mContext, mConnectedAccessPointPreference = new ConnectedAccessPointPreference(mAccessPoint, mContext,
null, 0 /* iconResId */, false /* forSavedNetworks */); null, 0 /* iconResId */, false /* forSavedNetworks */, mCaptivePortalStatus);
mConnectedAccessPointPreference.setOnGearClickListener(mOnGearClickListener); mConnectedAccessPointPreference.setOnGearClickListener(mOnGearClickListener);
} }
@@ -73,9 +76,23 @@ public class ConnectedAccessPointPreferenceTest {
verify(mOnGearClickListener, never()).onGearClick(mConnectedAccessPointPreference); verify(mOnGearClickListener, never()).onGearClick(mConnectedAccessPointPreference);
} }
@Test
public void testCaptivePortalStatus_isCaptivePortal_dividerDrawn() {
Mockito.when(mCaptivePortalStatus.isCaptivePortalNetwork()).thenReturn(true);
mConnectedAccessPointPreference.refresh();
assertThat(mConnectedAccessPointPreference.shouldShowDivider()).isTrue();
}
@Test
public void testCaptivePortalStatus_isNotCaptivePortal_dividerNotDrawn() {
Mockito.when(mCaptivePortalStatus.isCaptivePortalNetwork()).thenReturn(false);
mConnectedAccessPointPreference.refresh();
assertThat(mConnectedAccessPointPreference.shouldShowDivider()).isFalse();
}
@Test @Test
public void testWidgetLayoutPreference() { public void testWidgetLayoutPreference() {
assertThat(mConnectedAccessPointPreference.getWidgetLayoutResource()) assertThat(mConnectedAccessPointPreference.getWidgetLayoutResource())
.isEqualTo(R.layout.preference_widget_gear_no_bg); .isEqualTo(R.layout.preference_widget_gear_optional_background);
} }
} }