Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit fbf3d757 authored by Jason Chiu's avatar Jason Chiu
Browse files

Implement new design of Wi-Fi card

- always show Wi-Fi card
- collapse the card in the new UI session when connecting to a stable
  network
- hide toggle, show a level icon and subtext in the new collapsed mode
- show loading row when the AP list is not full

Test: robotest
Fixes: 147473096
Change-Id: I893064ef04d40d8e7cb8e62c1e72a2cb5e97f6ac
parent 07416f4b
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -62,4 +62,9 @@ public class ContextualWifiScanWorker extends WifiScanWorker {
        }
        return true;
    }

    @Override
    protected int getApRowCount() {
        return ContextualWifiSlice.getApRowCount();
    }
}
 No newline at end of file
+81 −11
Original line number Diff line number Diff line
@@ -17,30 +17,42 @@
package com.android.settings.wifi.slice;

import android.content.Context;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.drawable.Drawable;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.net.NetworkInfo.DetailedState;
import android.net.NetworkInfo.State;
import android.net.Uri;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.text.TextUtils;
import android.util.Log;

import androidx.annotation.VisibleForTesting;
import androidx.core.graphics.drawable.IconCompat;
import androidx.slice.Slice;
import androidx.slice.builders.ListBuilder;

import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.slices.CustomSliceRegistry;
import com.android.settings.slices.CustomSliceable;
import com.android.settingslib.wifi.AccessPoint;

/**
 * {@link CustomSliceable} for Wi-Fi, used by contextual homepage.
 */
public class ContextualWifiSlice extends WifiSlice {

    private static final String TAG = "ContextualWifiSlice";
    @VisibleForTesting
    static final int COLLAPSED_ROW_COUNT = 0;

    @VisibleForTesting
    static long sActiveUiSession = -1000;
    @VisibleForTesting
    static boolean sPreviouslyDisplayed;
    static boolean sToggleNeeded = true;

    public ContextualWifiSlice(Context context) {
        super(context);
@@ -57,17 +69,75 @@ public class ContextualWifiSlice extends WifiSlice {
                .getSlicesFeatureProvider().getUiSessionToken();
        if (currentUiSession != sActiveUiSession) {
            sActiveUiSession = currentUiSession;
            sPreviouslyDisplayed = false;
            sToggleNeeded = !hasWorkingNetwork();
        } else if (!mWifiManager.isWifiEnabled()) {
            sToggleNeeded = true;
        }
        if (!sPreviouslyDisplayed && hasWorkingNetwork()) {
            Log.d(TAG, "Wifi is connected, no point showing any suggestion.");
            return null;
        return super.getSlice();
    }
        // Set sPreviouslyDisplayed to true - we will show *something* on the screen. So we should
        // keep showing this card to keep UI stable, even if wifi connects to a network later.
        sPreviouslyDisplayed = true;

        return super.getSlice();
    static int getApRowCount() {
        return sToggleNeeded ? DEFAULT_EXPANDED_ROW_COUNT : COLLAPSED_ROW_COUNT;
    }

    @Override
    protected boolean isToggleNeeded() {
        return sToggleNeeded;
    }

    @Override
    protected ListBuilder.RowBuilder getHeaderRow(AccessPoint accessPoint) {
        final ListBuilder.RowBuilder builder = super.getHeaderRow(accessPoint);
        if (!sToggleNeeded) {
            builder.setTitleItem(getLevelIcon(accessPoint), ListBuilder.ICON_IMAGE)
                    .setSubtitle(getSubtitle(accessPoint));
        }
        return builder;
    }

    private IconCompat getLevelIcon(AccessPoint accessPoint) {
        if (accessPoint != null) {
            return getAccessPointLevelIcon(accessPoint);
        }

        final Drawable drawable = mContext.getDrawable(
                com.android.settingslib.Utils.getWifiIconResource(0));
        final int color = Utils.getColorAttrDefaultColor(mContext,
                android.R.attr.colorControlNormal);
        drawable.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN));
        return Utils.createIconWithDrawable(drawable);
    }

    private CharSequence getSubtitle(AccessPoint accessPoint) {
        if (isCaptivePortal()) {
            final int id = mContext.getResources()
                    .getIdentifier("network_available_sign_in", "string", "android");
            return mContext.getText(id);
        }

        if (accessPoint == null) {
            return mContext.getText(R.string.disconnected);
        }

        final NetworkInfo networkInfo = accessPoint.getNetworkInfo();
        if (networkInfo == null) {
            return mContext.getText(R.string.disconnected);
        }

        final State state = networkInfo.getState();
        DetailedState detailedState;
        if (state == State.CONNECTING) {
            detailedState = DetailedState.CONNECTING;
        } else if (state == State.CONNECTED) {
            detailedState = DetailedState.CONNECTED;
        } else {
            detailedState = networkInfo.getDetailedState();
        }

        final String[] formats = mContext.getResources().getStringArray(
                R.array.wifi_status_with_ssid);
        final int index = detailedState.ordinal();
        return String.format(formats[index], accessPoint.getTitle());
    }

    private boolean hasWorkingNetwork() {
+6 −1
Original line number Diff line number Diff line
@@ -111,10 +111,11 @@ public class WifiScanWorker extends SliceBackgroundWorker<AccessPoint> implement
        // AccessPoints are sorted by the WifiTracker
        final List<AccessPoint> accessPoints = mWifiTracker.getAccessPoints();
        final List<AccessPoint> resultList = new ArrayList<>();
        final int apRowCount = getApRowCount();
        for (AccessPoint ap : accessPoints) {
            if (ap.isReachable()) {
                resultList.add(clone(ap));
                if (resultList.size() >= DEFAULT_EXPANDED_ROW_COUNT) {
                if (resultList.size() >= apRowCount) {
                    break;
                }
            }
@@ -122,6 +123,10 @@ public class WifiScanWorker extends SliceBackgroundWorker<AccessPoint> implement
        updateResults(resultList);
    }

    protected int getApRowCount() {
        return DEFAULT_EXPANDED_ROW_COUNT;
    }

    private AccessPoint clone(AccessPoint accessPoint) {
        final Bundle savedState = new Bundle();
        accessPoint.saveWifiState(savedState);
+35 −39
Original line number Diff line number Diff line
@@ -95,7 +95,7 @@ public class WifiSlice implements CustomSliceable {
        mContext.getTheme().applyStyle(R.style.Theme_Settings_Home, true /* force */);

        final boolean isWifiEnabled = isWifiEnabled();
        ListBuilder listBuilder = getHeaderRow(isWifiEnabled);
        ListBuilder listBuilder = getListBuilder(isWifiEnabled, null /* accessPoint */);
        if (!isWifiEnabled) {
            WifiScanWorker.clearClickedWifi();
            return listBuilder.build();
@@ -107,17 +107,12 @@ public class WifiSlice implements CustomSliceable {
        final boolean isFirstApActive = apCount > 0 && apList.get(0).isActive();
        handleNetworkCallback(worker, isFirstApActive);

        // Need a loading text when results are not ready or out of date.
        boolean needLoadingRow = true;
        // Skip checking the existence of the first access point if it's active
        int index = isFirstApActive ? 1 : 0;
        // This loop checks the existence of reachable APs to determine the validity of the current
        // AP list.
        for (; index < apCount; index++) {
            if (apList.get(index).isReachable()) {
                needLoadingRow = false;
                break;
        if (!isToggleNeeded()) {
            if (isFirstApActive) {
                // refresh header subtext
                listBuilder = getListBuilder(true /* isWifiEnabled */, apList.get(0));
            }
            return listBuilder.build();
        }

        // Add AP rows
@@ -125,9 +120,8 @@ public class WifiSlice implements CustomSliceable {
        for (int i = 0; i < DEFAULT_EXPANDED_ROW_COUNT; i++) {
            if (i < apCount) {
                listBuilder.addRow(getAccessPointRow(apList.get(i)));
            } else if (needLoadingRow) {
            } else if (i == apCount) {
                listBuilder.addRow(getLoadingRow(placeholder));
                needLoadingRow = false;
            } else {
                listBuilder.addRow(new ListBuilder.RowBuilder()
                        .setTitle(placeholder)
@@ -148,24 +142,34 @@ public class WifiSlice implements CustomSliceable {
        }
    }

    private ListBuilder getHeaderRow(boolean isWifiEnabled) {
    protected boolean isToggleNeeded() {
        return true;
    }

    protected ListBuilder.RowBuilder getHeaderRow(AccessPoint accessPoint) {
        final IconCompat icon = IconCompat.createWithResource(mContext,
                R.drawable.ic_settings_wireless);
        final String title = mContext.getString(R.string.wifi_settings);
        final PendingIntent toggleAction = getBroadcastIntent(mContext);
        final PendingIntent primaryAction = getPrimaryAction();
        final SliceAction primarySliceAction = SliceAction.createDeeplink(primaryAction, icon,
                ListBuilder.ICON_IMAGE, title);
        final SliceAction toggleSliceAction = SliceAction.createToggle(toggleAction,
                null /* actionTitle */, isWifiEnabled);

        return new ListBuilder(mContext, getUri(), ListBuilder.INFINITY)
        return new ListBuilder.RowBuilder()
                .setTitle(title)
                .setPrimaryAction(primarySliceAction);
    }

    private ListBuilder getListBuilder(boolean isWifiEnabled, AccessPoint accessPoint) {
        final ListBuilder builder = new ListBuilder(mContext, getUri(), ListBuilder.INFINITY)
                .setAccentColor(COLOR_NOT_TINTED)
                .setKeywords(getKeywords())
                .addRow(new ListBuilder.RowBuilder()
                        .setTitle(title)
                        .addEndItem(toggleSliceAction)
                        .setPrimaryAction(primarySliceAction));
                .addRow(getHeaderRow(accessPoint));
        if (isToggleNeeded()) {
            final PendingIntent toggleAction = getBroadcastIntent(mContext);
            builder.addAction(SliceAction.createToggle(toggleAction, null /* actionTitle */,
                    isWifiEnabled));
        }
        return builder;
    }

    private ListBuilder.RowBuilder getAccessPointRow(AccessPoint accessPoint) {
@@ -200,7 +204,7 @@ public class WifiSlice implements CustomSliceable {
        return TextUtils.isEmpty(summary) ? mContext.getText(R.string.disconnected) : summary;
    }

    private IconCompat getAccessPointLevelIcon(AccessPoint accessPoint) {
    protected IconCompat getAccessPointLevelIcon(AccessPoint accessPoint) {
        final Drawable d = mContext.getDrawable(
                com.android.settingslib.Utils.getWifiIconResource(accessPoint.getLevel()));

@@ -250,24 +254,16 @@ public class WifiSlice implements CustomSliceable {
        accessPoint.saveWifiState(extras);

        if (accessPoint.isActive()) {
            Intent intent;
            if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_WIFITRACKER2)) {
                intent = new SubSettingLauncher(mContext)
            final SubSettingLauncher launcher = new SubSettingLauncher(mContext)
                    .setTitleRes(R.string.pref_title_network_details)
                        .setDestination(WifiNetworkDetailsFragment2.class.getName())
                    .setArguments(extras)
                        .setSourceMetricsCategory(SettingsEnums.WIFI)
                        .toIntent();
                return getActivityAction(requestCode, intent, icon, title);
                    .setSourceMetricsCategory(SettingsEnums.WIFI);
            if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_WIFITRACKER2)) {
                launcher.setDestination(WifiNetworkDetailsFragment2.class.getName());
            } else {
                intent = new SubSettingLauncher(mContext)
                        .setTitleRes(R.string.pref_title_network_details)
                        .setDestination(WifiNetworkDetailsFragment.class.getName())
                        .setArguments(extras)
                        .setSourceMetricsCategory(SettingsEnums.WIFI)
                        .toIntent();
                return getActivityAction(requestCode, intent, icon, title);
                launcher.setDestination(WifiNetworkDetailsFragment.class.getName());
            }
            return getActivityAction(requestCode, launcher.toIntent(), icon, title);
        } else if (WifiUtils.getConnectingType(accessPoint) != WifiUtils.CONNECT_TYPE_OTHERS) {
            final Intent intent = new Intent(mContext, ConnectToWifiHandler.class)
                    .putExtra(WifiDialogActivity.KEY_ACCESS_POINT_STATE, extras);
@@ -307,7 +303,7 @@ public class WifiSlice implements CustomSliceable {
                .setSubtitle(title);
    }

    private boolean isCaptivePortal() {
    protected boolean isCaptivePortal() {
        final NetworkCapabilities nc = mConnectivityManager.getNetworkCapabilities(
                mWifiManager.getCurrentNetwork());
        return WifiUtils.canSignIntoNetwork(nc);
+69 −32
Original line number Diff line number Diff line
@@ -16,6 +16,9 @@

package com.android.settings.wifi.slice;

import static com.android.settings.wifi.slice.ContextualWifiSlice.COLLAPSED_ROW_COUNT;
import static com.android.settings.wifi.slice.WifiSlice.DEFAULT_EXPANDED_ROW_COUNT;

import static com.google.common.truth.Truth.assertThat;

import static org.mockito.Mockito.doReturn;
@@ -75,75 +78,87 @@ public class ContextualWifiSliceTest {
        mWifiManager.setWifiEnabled(true);

        mWifiSlice = new ContextualWifiSlice(mContext);
        mWifiSlice.sPreviouslyDisplayed = false;
    }

    @Test
    public void getWifiSlice_hasActiveConnection_shouldReturnNull() {
        mWifiSlice.sPreviouslyDisplayed = false;
    public void getWifiSlice_newSession_hasActiveConnection_shouldCollapseSlice() {
        mWifiSlice.sActiveUiSession = ~mFeatureFactory.slicesFeatureProvider.getUiSessionToken();
        connectToWifi(makeValidatedNetworkCapabilities());

        final Slice wifiSlice = mWifiSlice.getSlice();

        assertThat(wifiSlice).isNull();
        assertTitleAndIcon(wifiSlice);
        assertNoToggle(wifiSlice);
        assertThat(ContextualWifiSlice.getApRowCount()).isEqualTo(COLLAPSED_ROW_COUNT);
    }

    @Test
    public void getWifiSlice_newSession_hasActiveConnection_shouldReturnNull() {
        // Session: use a non-active value
        // previous displayed: yes
        mWifiSlice.sPreviouslyDisplayed = true;
    public void getWifiSlice_newSession_noConnection_shouldExpandSlice() {
        mWifiSlice.sActiveUiSession = ~mFeatureFactory.slicesFeatureProvider.getUiSessionToken();
        connectToWifi(makeValidatedNetworkCapabilities());

        final Slice wifiSlice = mWifiSlice.getSlice();

        assertThat(wifiSlice).isNull();
        assertTitleAndIcon(wifiSlice);
        assertToggle(wifiSlice);
        assertThat(ContextualWifiSlice.getApRowCount()).isEqualTo(DEFAULT_EXPANDED_ROW_COUNT);
    }

    @Test
    public void getWifiSlice_previousDisplayed_hasActiveConnection_shouldHaveTitleAndToggle() {
    public void getWifiSlice_previousExpanded_hasActiveConnection_shouldExpandSlice() {
        mWifiSlice.sActiveUiSession = mFeatureFactory.slicesFeatureProvider.getUiSessionToken();
        mWifiSlice.sPreviouslyDisplayed = true;
        mWifiSlice.sToggleNeeded = true;
        connectToWifi(makeValidatedNetworkCapabilities());

        final Slice wifiSlice = mWifiSlice.getSlice();

        final SliceMetadata metadata = SliceMetadata.from(mContext, wifiSlice);
        assertThat(metadata.getTitle()).isEqualTo(mContext.getString(R.string.wifi_settings));
        assertTitleAndIcon(wifiSlice);
        assertToggle(wifiSlice);
        assertThat(ContextualWifiSlice.getApRowCount()).isEqualTo(DEFAULT_EXPANDED_ROW_COUNT);
    }

        final List<SliceAction> toggles = metadata.getToggles();
        assertThat(toggles).hasSize(1);
    @Test
    public void getWifiSlice_previousExpanded_disableWifi_shouldHaveToggle() {
        mWifiSlice.sActiveUiSession = mFeatureFactory.slicesFeatureProvider.getUiSessionToken();
        mWifiSlice.sToggleNeeded = true;
        connectToWifi(makeValidatedNetworkCapabilities());

        final SliceAction primaryAction = metadata.getPrimaryAction();
        final IconCompat expectedToggleIcon = IconCompat.createWithResource(mContext,
                R.drawable.ic_settings_wireless);
        assertThat(primaryAction.getIcon().toString()).isEqualTo(expectedToggleIcon.toString());
        mWifiManager.setWifiEnabled(false);
        final Slice wifiSlice = mWifiSlice.getSlice();

        assertTitleAndIcon(wifiSlice);
        assertToggle(wifiSlice);
    }

    @Test
    public void getWifiSlice_isCaptivePortal_shouldHaveTitleAndToggle() {
        mWifiSlice.sPreviouslyDisplayed = false;
        connectToWifi(WifiSliceTest.makeCaptivePortalNetworkCapabilities());
    public void getWifiSlice_previousCollapsed_disableWifi_shouldHaveToggle() {
        mWifiSlice.sActiveUiSession = mFeatureFactory.slicesFeatureProvider.getUiSessionToken();
        mWifiSlice.sToggleNeeded = false;
        connectToWifi(makeValidatedNetworkCapabilities());

        mWifiManager.setWifiEnabled(false);
        final Slice wifiSlice = mWifiSlice.getSlice();

        final SliceMetadata metadata = SliceMetadata.from(mContext, wifiSlice);
        assertThat(metadata.getTitle()).isEqualTo(mContext.getString(R.string.wifi_settings));
        assertTitleAndIcon(wifiSlice);
        assertToggle(wifiSlice);
    }

        final List<SliceAction> toggles = metadata.getToggles();
        assertThat(toggles).hasSize(1);
    @Test
    public void getWifiSlice_previousCollapsed_connectionLoss_shouldCollapseSlice() {
        mWifiSlice.sActiveUiSession = mFeatureFactory.slicesFeatureProvider.getUiSessionToken();
        mWifiSlice.sToggleNeeded = false;
        connectToWifi(makeValidatedNetworkCapabilities());

        final SliceAction primaryAction = metadata.getPrimaryAction();
        final IconCompat expectedToggleIcon = IconCompat.createWithResource(mContext,
                R.drawable.ic_settings_wireless);
        assertThat(primaryAction.getIcon().toString()).isEqualTo(expectedToggleIcon.toString());
        mWifiManager.disconnect();
        final Slice wifiSlice = mWifiSlice.getSlice();

        assertTitleAndIcon(wifiSlice);
        assertNoToggle(wifiSlice);
        assertThat(ContextualWifiSlice.getApRowCount()).isEqualTo(COLLAPSED_ROW_COUNT);
    }

    @Test
    public void getWifiSlice_contextualWifiSlice_shouldReturnContextualWifiSliceUri() {
        mWifiSlice.sActiveUiSession = mFeatureFactory.slicesFeatureProvider.getUiSessionToken();
        mWifiSlice.sPreviouslyDisplayed = true;

        final Slice wifiSlice = mWifiSlice.getSlice();

@@ -165,4 +180,26 @@ public class ContextualWifiSliceTest {
        nc.addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED);
        return nc;
    }

    private void assertTitleAndIcon(Slice slice) {
        final SliceMetadata metadata = SliceMetadata.from(mContext, slice);
        assertThat(metadata.getTitle()).isEqualTo(mContext.getString(R.string.wifi_settings));

        final SliceAction primaryAction = metadata.getPrimaryAction();
        final IconCompat expectedToggleIcon = IconCompat.createWithResource(mContext,
                R.drawable.ic_settings_wireless);
        assertThat(primaryAction.getIcon().toString()).isEqualTo(expectedToggleIcon.toString());
    }

    private void assertToggle(Slice slice) {
        final SliceMetadata metadata = SliceMetadata.from(mContext, slice);
        final List<SliceAction> toggles = metadata.getToggles();
        assertThat(toggles).hasSize(1);
    }

    private void assertNoToggle(Slice slice) {
        final SliceMetadata metadata = SliceMetadata.from(mContext, slice);
        final List<SliceAction> toggles = metadata.getToggles();
        assertThat(toggles).isEmpty();
    }
}
Loading