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>
<!-- Wifi Sign in text for button [CHAR LIMIT = 40]-->
<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 -->
<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
View.OnClickListener {
private final CaptivePortalStatus mCaptivePortalStatus;
private OnGearClickListener mOnGearClickListener;
private boolean mCaptivePortalNetwork;
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);
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) {
@@ -44,6 +63,18 @@ public class ConnectedAccessPointPreference extends AccessPointPreference implem
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
public void onClick(View v) {
if (v.getId() == R.id.settings_button) {
@@ -56,4 +87,8 @@ public class ConnectedAccessPointPreference extends AccessPointPreference implem
public interface OnGearClickListener {
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.res.Resources;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.net.NetworkInfo.State;
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.SwitchBarController;
import com.android.settings.wifi.details.WifiNetworkDetailsFragment;
import com.android.settings.wrapper.ConnectivityManagerWrapper;
import com.android.settings.wrapper.WifiManagerWrapper;
import com.android.settingslib.RestrictedLockUtils;
import com.android.settingslib.wifi.AccessPoint;
@@ -117,6 +120,7 @@ public class WifiSettings extends RestrictedSettingsFragment
};
protected WifiManager mWifiManager;
private ConnectivityManager mConnectivityManager;
private WifiManager.ActionListener mConnectListener;
private WifiManager.ActionListener mSaveListener;
private WifiManager.ActionListener mForgetListener;
@@ -238,6 +242,11 @@ public class WifiSettings extends RestrictedSettingsFragment
getActivity(), this, getLifecycle(), true, true);
mWifiManager = mWifiTracker.getManager();
final Activity activity = getActivity();
if (activity != null) {
mConnectivityManager = getActivity().getSystemService(ConnectivityManager.class);
}
mConnectListener = new WifiManager.ActionListener() {
@Override
public void onSuccess() {
@@ -777,9 +786,11 @@ public class WifiSettings extends RestrictedSettingsFragment
@NonNull
private ConnectedAccessPointPreference createConnectedAccessPointPreference(
AccessPoint accessPoint) {
AccessPoint accessPoint,
ConnectedAccessPointPreference.CaptivePortalStatus captivePortalStatus) {
return new ConnectedAccessPointPreference(accessPoint, getPrefContext(), mUserBadgeCache,
R.drawable.ic_wifi_signal_0, false /* forSavedNetworks */);
R.drawable.ic_wifi_signal_0, false /* forSavedNetworks */,
captivePortalStatus);
}
/**
@@ -828,21 +839,30 @@ public class WifiSettings extends RestrictedSettingsFragment
* {@link #mConnectedAccessPointPreferenceCategory}.
*/
private void addConnectedAccessPointPreference(AccessPoint connectedAp) {
final ConnectedAccessPointPreference pref = createConnectedAccessPointPreference(
connectedAp);
final ConnectedAccessPointPreference pref =
createConnectedAccessPointPreference(
connectedAp, this::isConnectedToCaptivePortalNetwork);
// Launch details page on click.
pref.setOnPreferenceClickListener(preference -> {
pref.getAccessPoint().saveWifiState(pref.getExtras());
// Launch details page or captive portal on click.
pref.setOnPreferenceClickListener(
preference -> {
pref.getAccessPoint().saveWifiState(pref.getExtras());
Network network = getConnectedWifiNetwork();
if (isConnectedToCaptivePortalNetwork(network)) {
ConnectivityManagerWrapper connectivityManagerWrapper =
new ConnectivityManagerWrapper(mConnectivityManager);
connectivityManagerWrapper.startCaptivePortalApp(network);
} else {
launchNetworkDetailsFragment(pref);
}
return true;
});
new SubSettingLauncher(getContext())
.setTitle(pref.getTitle())
.setDestination(WifiNetworkDetailsFragment.class.getName())
.setArguments(pref.getExtras())
.setSourceMetricsCategory(getMetricsCategory())
.launch();
return true;
});
pref.setOnGearClickListener(
preference -> {
pref.getAccessPoint().saveWifiState(pref.getExtras());
launchNetworkDetailsFragment(pref);
});
pref.refresh();
@@ -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}. */
private void removeConnectedAccessPointPreference() {
mConnectedAccessPointPreferenceCategory.removeAll();

View File

@@ -16,11 +16,11 @@
package com.android.settings.wifi;
import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.PackageManager;
import android.net.NetworkCapabilities;
import android.net.wifi.WifiConfiguration;
import android.provider.Settings;
import android.text.TextUtils;
@@ -101,4 +101,10 @@ public class WifiUtils {
Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 0) != 0;
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.
*/
private boolean canSignIntoNetwork() {
return mNetworkCapabilities != null && mNetworkCapabilities.hasCapability(
NET_CAPABILITY_CAPTIVE_PORTAL);
return WifiUtils.canSignIntoNetwork(mNetworkCapabilities);
}
/**

View File

@@ -32,6 +32,7 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.robolectric.RuntimeEnvironment;
@@ -44,6 +45,8 @@ public class ConnectedAccessPointPreferenceTest {
private View mView;
@Mock
private ConnectedAccessPointPreference.OnGearClickListener mOnGearClickListener;
@Mock
private ConnectedAccessPointPreference.CaptivePortalStatus mCaptivePortalStatus;
private Context mContext;
private ConnectedAccessPointPreference mConnectedAccessPointPreference;
@@ -53,7 +56,7 @@ public class ConnectedAccessPointPreferenceTest {
mContext = RuntimeEnvironment.application;
mConnectedAccessPointPreference = new ConnectedAccessPointPreference(mAccessPoint, mContext,
null, 0 /* iconResId */, false /* forSavedNetworks */);
null, 0 /* iconResId */, false /* forSavedNetworks */, mCaptivePortalStatus);
mConnectedAccessPointPreference.setOnGearClickListener(mOnGearClickListener);
}
@@ -73,9 +76,23 @@ public class ConnectedAccessPointPreferenceTest {
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
public void testWidgetLayoutPreference() {
assertThat(mConnectedAccessPointPreference.getWidgetLayoutResource())
.isEqualTo(R.layout.preference_widget_gear_no_bg);
.isEqualTo(R.layout.preference_widget_gear_optional_background);
}
}