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

Commit 46aa5866 authored by Matthew Fritze's avatar Matthew Fritze
Browse files

Add Wifi Slice

Add a custom Wifi Slice to the Settings Slice Provider.
It needs a custom Slice because of the complicated listener logic
in the MasterSwitchPreferenceController, which makes it hard to
work-in synchronous set/get logic.

The one-off Slice requires extra changes, including:
- Including it in getDescendants
- Handling changes to wifi by the framework

This is the first change that uses SettingsLib's broadcast relay,
which allows settings to (un)register IntentFilters to a Uri,
allowing Settings Slices affected by the framework (quicksettings,
connectivity related, volume, etc) to be updated without action
on the Slice.

Fixes: 70622039
Fixes: 67997332
Test: robotests
Change-Id: Ia76738dd6baacd5522d52df2c61ebad86a600282
Merged-In: Ibfe4736beecb833e3f6bb871b2eb5228a5fd3a34
parent 24bbc4bf
Loading
Loading
Loading
Loading
+56 −75
Original line number Diff line number Diff line
@@ -18,41 +18,39 @@ package com.android.settings.slices;

import static android.Manifest.permission.READ_SEARCH_INDEXABLES;

import static com.android.settings.wifi.calling.WifiCallingSliceHelper.PATH_WIFI_CALLING;

import android.app.PendingIntent;
import android.app.slice.SliceManager;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Icon;
import android.content.IntentFilter;
import android.content.Context;
import android.net.Uri;
import android.net.wifi.WifiManager;
import android.provider.Settings;
import android.provider.SettingsSlicesContract;
import android.support.annotation.VisibleForTesting;
import android.support.v4.graphics.drawable.IconCompat;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;

import androidx.slice.Slice;
import androidx.slice.SliceProvider;
import androidx.slice.builders.ListBuilder;
import androidx.slice.builders.SliceAction;

import com.android.settings.R;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.wifi.WifiSliceBuilder;
import com.android.settings.wifi.calling.WifiCallingSliceHelper;
import com.android.settingslib.SliceBroadcastRelay;
import com.android.settingslib.utils.ThreadUtils;

import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;

import androidx.slice.Slice;
import androidx.slice.SliceProvider;

/**
 * A {@link SliceProvider} for Settings to enabled inline results in system apps.
 *
@@ -84,10 +82,6 @@ public class SettingsSliceProvider extends SliceProvider {
     */
    public static final String SLICE_AUTHORITY = "com.android.settings.slices";

    public static final String PATH_WIFI = "wifi";
    public static final String ACTION_WIFI_CHANGED =
            "com.android.settings.slice.action.WIFI_CHANGED";

    /**
     * Action passed for changes to Toggle Slices.
     */
@@ -121,6 +115,8 @@ public class SettingsSliceProvider extends SliceProvider {
    @VisibleForTesting
    Map<Uri, SliceData> mSliceDataCache;

    final Set<Uri> mRegisteredUris = new ArraySet<>();

    public SettingsSliceProvider() {
        super(READ_SEARCH_INDEXABLES);
    }
@@ -146,28 +142,37 @@ public class SettingsSliceProvider extends SliceProvider {

    @Override
    public void onSlicePinned(Uri sliceUri) {
        if (WifiSliceBuilder.WIFI_URI.equals(sliceUri)) {
            registerIntentToUri(WifiSliceBuilder.INTENT_FILTER , sliceUri);
            // TODO (b/) Register IntentFilters for database entries.
            mRegisteredUris.add(sliceUri);
            return;
        }

        // Start warming the slice, we expect someone will want it soon.
        loadSliceInBackground(sliceUri);
    }

    @Override
    public void onSliceUnpinned(Uri sliceUri) {
        if (mRegisteredUris.contains(sliceUri)) {
            SliceBroadcastRelay.unregisterReceivers(getContext(), sliceUri);
            mRegisteredUris.remove(sliceUri);
        }
        mSliceDataCache.remove(sliceUri);
    }

    @Override
    public Slice onBindSlice(Uri sliceUri) {
        String path = sliceUri.getPath();
        // If adding a new Slice, do not directly match Slice URIs.
        // Use {@link SlicesDatabaseAccessor}.
        switch (path) {
            case "/" + PATH_WIFI:
                return createWifiSlice(sliceUri);
            case "/" + PATH_WIFI_CALLING:
        if (WifiCallingSliceHelper.WIFI_CALLING_URI.equals(sliceUri)) {
            return FeatureFactory.getFactory(getContext())
                    .getSlicesFeatureProvider()
                    .getNewWifiCallingSliceHelper(getContext())
                    .createWifiCallingSlice(sliceUri);
        } else if (WifiSliceBuilder.WIFI_URI.equals(sliceUri)) {
            return WifiSliceBuilder.getSlice(getContext());
        }

        SliceData cachedSliceData = mSliceWeakDataCache.get(sliceUri);
@@ -223,11 +228,12 @@ public class SettingsSliceProvider extends SliceProvider {
                    true /* isPlatformSlice */);
            final List<String> oemKeys = mSlicesDatabaseAccessor.getSliceKeys(
                    false /* isPlatformSlice */);
            final List<Uri> allUris = buildUrisFromKeys(platformKeys,
                    SettingsSlicesContract.AUTHORITY);
            allUris.addAll(buildUrisFromKeys(oemKeys, SettingsSliceProvider.SLICE_AUTHORITY));
            descendants.addAll(buildUrisFromKeys(platformKeys, SettingsSlicesContract.AUTHORITY));
            descendants.addAll(buildUrisFromKeys(oemKeys, SettingsSliceProvider.SLICE_AUTHORITY));
            descendants.addAll(getSpecialCaseUris(true /* isPlatformSlice */));
            descendants.addAll(getSpecialCaseUris(false /* isPlatformSlice */));

            return allUris;
            return descendants;
        }

        // Path is anything but empty, "action", or "intent". Return empty list.
@@ -242,7 +248,9 @@ public class SettingsSliceProvider extends SliceProvider {
        // Can assume authority belongs to the provider. Return all Uris for the authority.
        final boolean isPlatformUri = TextUtils.equals(authority, SettingsSlicesContract.AUTHORITY);
        final List<String> keys = mSlicesDatabaseAccessor.getSliceKeys(isPlatformUri);
        return buildUrisFromKeys(keys, authority);
        descendants.addAll(buildUrisFromKeys(keys, authority));
        descendants.addAll(getSpecialCaseUris(isPlatformUri));
        return descendants;
    }

    private List<Uri> buildUrisFromKeys(List<String> keys, String authority) {
@@ -295,55 +303,28 @@ public class SettingsSliceProvider extends SliceProvider {
        return new Slice.Builder(uri).build();
    }

    // TODO (b/70622039) remove this when the proper wifi slice is enabled.
    private Slice createWifiSlice(Uri sliceUri) {
        // Get wifi state
        WifiManager wifiManager = (WifiManager) getContext().getSystemService(Context.WIFI_SERVICE);
        int wifiState = wifiManager.getWifiState();
        boolean wifiEnabled = false;
        String state;
        switch (wifiState) {
            case WifiManager.WIFI_STATE_DISABLED:
            case WifiManager.WIFI_STATE_DISABLING:
                state = getContext().getString(R.string.disconnected);
                break;
            case WifiManager.WIFI_STATE_ENABLED:
            case WifiManager.WIFI_STATE_ENABLING:
                state = wifiManager.getConnectionInfo().getSSID();
                wifiEnabled = true;
                break;
            case WifiManager.WIFI_STATE_UNKNOWN:
            default:
                state = ""; // just don't show anything?
                break;
    private List<Uri> getSpecialCaseUris(boolean isPlatformUri) {
        if (isPlatformUri) {
            return getSpecialCasePlatformUris();
        }
        return getSpecialCaseOemUris();
    }

        boolean finalWifiEnabled = wifiEnabled;
        return new ListBuilder(getContext(), sliceUri)
                .setColor(R.color.material_blue_500)
                .addRow(b -> b
                        .setTitle(getContext().getString(R.string.wifi_settings))
                        .setTitleItem(Icon.createWithResource(getContext(), R.drawable.wifi_signal))
                        .setSubtitle(state)
                        .addEndItem(new SliceAction(getBroadcastIntent(ACTION_WIFI_CHANGED),
                                null, finalWifiEnabled))
                        .setPrimaryAction(
                                new SliceAction(getIntent(Settings.ACTION_WIFI_SETTINGS),
                                        (IconCompat) null, null)))
                .build();
    private List<Uri> getSpecialCasePlatformUris() {
        return Arrays.asList(WifiSliceBuilder.WIFI_URI);
    }

    private PendingIntent getIntent(String action) {
        Intent intent = new Intent(action);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        PendingIntent pi = PendingIntent.getActivity(getContext(), 0, intent, 0);
        return pi;
    private List<Uri> getSpecialCaseOemUris() {
        return new ArrayList<>();
    }

    private PendingIntent getBroadcastIntent(String action) {
        Intent intent = new Intent(action);
        intent.setClass(getContext(), SliceBroadcastReceiver.class);
        return PendingIntent.getBroadcast(getContext(), 0, intent,
                PendingIntent.FLAG_CANCEL_CURRENT);
    @VisibleForTesting
    /**
     * Registers an IntentFilter in SysUI to notify changes to {@param sliceUri} when broadcasts to
     * {@param intentFilter} happen.
     */
    void registerIntentToUri(IntentFilter intentFilter, Uri sliceUri) {
        SliceBroadcastRelay.registerReceiver(getContext(), sliceUri, SliceBroadcastReceiver.class,
                intentFilter);
    }
}
+14 −18
Original line number Diff line number Diff line
@@ -18,18 +18,16 @@ package com.android.settings.slices;

import static com.android.settings.slices.SettingsSliceProvider.ACTION_SLIDER_CHANGED;
import static com.android.settings.slices.SettingsSliceProvider.ACTION_TOGGLE_CHANGED;
import static com.android.settings.slices.SettingsSliceProvider.ACTION_WIFI_CHANGED;
import static com.android.settings.slices.SettingsSliceProvider.EXTRA_SLICE_KEY;
import static com.android.settings.slices.SettingsSliceProvider.EXTRA_SLICE_PLATFORM_DEFINED;
import static com.android.settings.wifi.calling.WifiCallingSliceHelper.ACTION_WIFI_CALLING_CHANGED;
import static com.android.settings.wifi.WifiSliceBuilder.ACTION_WIFI_SLICE_CHANGED;

import android.app.slice.Slice;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.net.wifi.WifiManager;
import android.os.Handler;
import android.provider.SettingsSlicesContract;
import android.text.TextUtils;
import android.util.Log;
@@ -40,6 +38,8 @@ import com.android.settings.core.BasePreferenceController;
import com.android.settings.core.SliderPreferenceController;
import com.android.settings.core.TogglePreferenceController;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.wifi.WifiSliceBuilder;
import com.android.settingslib.SliceBroadcastRelay;

/**
 * Responds to actions performed on slices and notifies slices of updates in state changes.
@@ -67,18 +67,8 @@ public class SliceBroadcastReceiver extends BroadcastReceiver {
                final int newPosition = intent.getIntExtra(Slice.EXTRA_RANGE_VALUE, -1);
                handleSliderAction(context, key, newPosition, isPlatformSlice);
                break;
            case ACTION_WIFI_CHANGED:
                WifiManager wm = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
                boolean newState = intent.getBooleanExtra(Slice.EXTRA_TOGGLE_STATE,
                        wm.isWifiEnabled());
                wm.setWifiEnabled(newState);
                // Wait a bit for wifi to update (TODO: is there a better way to do this?)
                Handler h = new Handler();
                h.postDelayed(() -> {
                    Uri uri = SliceBuilderUtils.getUri(SettingsSliceProvider.PATH_WIFI,
                            false /* isPlatformSlice */);
                    context.getContentResolver().notifyChange(uri, null);
                }, 1000);
            case ACTION_WIFI_SLICE_CHANGED:
                WifiSliceBuilder.handleUriChange(context, intent);
                break;
            case ACTION_WIFI_CALLING_CHANGED:
                FeatureFactory.getFactory(context)
@@ -86,6 +76,12 @@ public class SliceBroadcastReceiver extends BroadcastReceiver {
                        .getNewWifiCallingSliceHelper(context)
                        .handleWifiCallingChanged(intent);
                break;
            default:
                final String uriString = intent.getStringExtra(SliceBroadcastRelay.EXTRA_URI);
                if (!TextUtils.isEmpty(uriString)) {
                    final Uri uri = Uri.parse(uriString);
                    context.getContentResolver().notifyChange(uri, null /* observer */);
                }
        }
    }

+6 −2
Original line number Diff line number Diff line
@@ -39,7 +39,6 @@ import android.util.Pair;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settings.R;
import com.android.settings.SettingsActivity;
import com.android.settings.SubSettings;
import com.android.settings.Utils;
import com.android.settings.core.BasePreferenceController;
@@ -47,6 +46,7 @@ import com.android.settings.core.SliderPreferenceController;
import com.android.settings.core.TogglePreferenceController;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.search.DatabaseIndexingUtils;
import com.android.settingslib.SliceBroadcastRelay;
import com.android.settingslib.core.AbstractPreferenceController;

import android.support.v4.graphics.drawable.IconCompat;
@@ -54,6 +54,7 @@ import android.support.v4.graphics.drawable.IconCompat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

import androidx.slice.Slice;
import androidx.slice.builders.ListBuilder;
@@ -345,7 +346,10 @@ public class SliceBuilderUtils {
        final String keywordString = data.getKeywords();
        if (keywordString != null) {
            final String[] keywordArray = keywordString.split(",");
            keywords.addAll(Arrays.asList(keywordArray));
            final List<String> strippedKeywords = Arrays.stream(keywordArray)
                    .map(s -> s = s.trim())
                    .collect(Collectors.toList());
            keywords.addAll(strippedKeywords);
        }

        return keywords;
+178 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 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.
 */

package com.android.settings.wifi;

import static android.provider.SettingsSlicesContract.KEY_WIFI;

import android.annotation.ColorInt;
import android.app.PendingIntent;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiSsid;
import android.provider.SettingsSlicesContract;

import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settings.R;
import com.android.settings.SubSettings;
import com.android.settings.Utils;
import com.android.settings.search.DatabaseIndexingUtils;
import com.android.settings.slices.SliceBroadcastReceiver;
import com.android.settings.slices.SliceBuilderUtils;

import androidx.slice.Slice;
import androidx.slice.builders.ListBuilder;
import androidx.slice.builders.SliceAction;

import android.support.v4.graphics.drawable.IconCompat;
import android.text.TextUtils;

/**
 * Utility class to build a Wifi Slice, and handle all associated actions.
 */
public class WifiSliceBuilder {

    /**
     * Backing Uri for the Wifi Slice.
     */
    public static final Uri WIFI_URI = new Uri.Builder()
            .scheme(ContentResolver.SCHEME_CONTENT)
            .authority(SettingsSlicesContract.AUTHORITY)
            .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION)
            .appendPath(KEY_WIFI)
            .build();

    /**
     * Action notifying a change on the Wifi Slice.
     */
    public static final String ACTION_WIFI_SLICE_CHANGED =
            "com.android.settings.wifi.action.WIFI_CHANGED";

    public static final IntentFilter INTENT_FILTER = new IntentFilter();

    static {
        INTENT_FILTER.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
        INTENT_FILTER.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
    }

    private WifiSliceBuilder() {
    }

    /**
     * Return a Wifi Slice bound to {@link #WIFI_URI}.
     * <p>
     * Note that you should register a listener with {@link #registerIntentFilter(Context, Uri)}
     * to get changes from Wifi.
     */
    public static Slice getSlice(Context context) {
        final boolean isWifiEnabled = isWifiEnabled(context);
        final IconCompat icon = IconCompat.createWithResource(context,
                R.drawable.ic_settings_wireless);
        final String title = context.getString(R.string.wifi_settings);
        final CharSequence summary = getSummary(context);
        @ColorInt final int color = Utils.getColorAccent(context);
        final PendingIntent toggleAction = getBroadcastIntent(context);
        final PendingIntent primaryAction = getPrimaryAction(context);
        final SliceAction primarySliceAction = new SliceAction(primaryAction, icon, title);
        final SliceAction toggleSliceAction = new SliceAction(toggleAction, null /* actionTitle */,
                isWifiEnabled);

        return new ListBuilder(context, WIFI_URI, ListBuilder.INFINITY)
                .setAccentColor(color)
                .addRow(b -> b
                        .setTitle(title)
                        .setSubtitle(summary)
                        .addEndItem(toggleSliceAction)
                        .setPrimaryAction(primarySliceAction))
                .build();
    }

    /**
     * Update the current wifi status to the boolean value keyed by
     * {@link android.app.slice.Slice#EXTRA_TOGGLE_STATE} on {@param intent}.
     */
    public static void handleUriChange(Context context, Intent intent) {
        final WifiManager wifiManager = context.getSystemService(WifiManager.class);
        final boolean newState = intent.getBooleanExtra(android.app.slice.Slice.EXTRA_TOGGLE_STATE,
                wifiManager.isWifiEnabled());
        wifiManager.setWifiEnabled(newState);
        // Do not notifyChange on Uri. The service takes longer to update the current value than it
        // does for the Slice to check the current value again. Let {@link SliceBroadcastRelay}
        // handle it.
    }

    private static boolean isWifiEnabled(Context context) {
        final WifiManager wifiManager = context.getSystemService(WifiManager.class);

        switch (wifiManager.getWifiState()) {
            case WifiManager.WIFI_STATE_ENABLED:
            case WifiManager.WIFI_STATE_ENABLING:
                return true;
            case WifiManager.WIFI_STATE_DISABLED:
            case WifiManager.WIFI_STATE_DISABLING:
            case WifiManager.WIFI_STATE_UNKNOWN:
            default:
                return false;
        }
    }

    private static CharSequence getSummary(Context context) {
        final WifiManager wifiManager = context.getSystemService(WifiManager.class);

        switch (wifiManager.getWifiState()) {
            case WifiManager.WIFI_STATE_ENABLED:
                final String ssid = WifiInfo.removeDoubleQuotes(wifiManager.getConnectionInfo()
                        .getSSID());
                if (TextUtils.equals(ssid, WifiSsid.NONE)) {
                    return context.getText(R.string.disconnected);
                }
                return ssid;
            case WifiManager.WIFI_STATE_ENABLING:
                return context.getText(R.string.disconnected);
            case WifiManager.WIFI_STATE_DISABLED:
            case WifiManager.WIFI_STATE_DISABLING:
                return context.getText(R.string.switch_off_text);
            case WifiManager.WIFI_STATE_UNKNOWN:
            default:
                return "";
        }
    }

    private static PendingIntent getPrimaryAction(Context context) {
        final String screenTitle = context.getText(R.string.wifi_settings).toString();
        final Uri contentUri = new Uri.Builder().appendPath(KEY_WIFI).build();
        final Intent intent = DatabaseIndexingUtils.buildSearchResultPageIntent(context,
                WifiSettings.class.getName(), KEY_WIFI, screenTitle,
                MetricsEvent.DIALOG_WIFI_AP_EDIT);
        intent.setClassName(context.getPackageName(), SubSettings.class.getName());
        intent.setData(contentUri);

        return PendingIntent.getActivity(context, 0 /* requestCode */,
                intent, 0 /* flags */);
    }

    private static PendingIntent getBroadcastIntent(Context context) {
        final Intent intent = new Intent(ACTION_WIFI_SLICE_CHANGED);
        intent.setClass(context, SliceBroadcastReceiver.class);
        return PendingIntent.getBroadcast(context, 0 /* requestCode */, intent,
                PendingIntent.FLAG_CANCEL_CURRENT);
    }
}
+12 −6
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import static android.app.slice.Slice.EXTRA_TOGGLE_STATE;

import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
@@ -38,6 +39,7 @@ import androidx.slice.builders.SliceAction;
import com.android.ims.ImsManager;
import com.android.internal.annotations.VisibleForTesting;
import com.android.settings.R;
import com.android.settings.slices.SettingsSliceProvider;
import com.android.settings.slices.SliceBroadcastReceiver;
import com.android.settings.slices.SliceBuilderUtils;

@@ -77,14 +79,18 @@ public class WifiCallingSliceHelper {
            "android.settings.WIFI_CALLING_SETTINGS";

    /**
     * Timeout for querying wifi calling setting from ims manager.
     * Full {@link Uri} for the Wifi Calling Slice.
     */
    private static final int TIMEOUT_MILLIS = 2000;
    public static final Uri WIFI_CALLING_URI = new Uri.Builder()
            .scheme(ContentResolver.SCHEME_CONTENT)
            .authority(SettingsSliceProvider.SLICE_AUTHORITY)
            .appendPath(PATH_WIFI_CALLING)
            .build();

    /**
     * Time for which data contained in the slice can remain fresh.
     * Timeout for querying wifi calling setting from ims manager.
     */
    private static final int SLICE_TTL_MILLIS = 60000;
    private static final int TIMEOUT_MILLIS = 2000;

    protected SubscriptionManager mSubscriptionManager;
    private final Context mContext;
@@ -182,7 +188,7 @@ public class WifiCallingSliceHelper {

        final IconCompat icon = IconCompat.createWithResource(mContext, R.drawable.wifi_signal);
        final String title = mContext.getString(R.string.wifi_calling_settings_title);
        return new ListBuilder(mContext, sliceUri, SLICE_TTL_MILLIS)
        return new ListBuilder(mContext, sliceUri, ListBuilder.INFINITY)
                .setColor(R.color.material_blue_500)
                .addRow(b -> b
                        .setTitle(title)
@@ -260,7 +266,7 @@ public class WifiCallingSliceHelper {
    private Slice getNonActionableWifiCallingSlice(String title, String subtitle, Uri sliceUri,
            PendingIntent primaryActionIntent) {
        final IconCompat icon = IconCompat.createWithResource(mContext, R.drawable.wifi_signal);
        return new ListBuilder(mContext, sliceUri, SLICE_TTL_MILLIS)
        return new ListBuilder(mContext, sliceUri, ListBuilder.INFINITY)
                .setColor(R.color.material_blue_500)
                .addRow(b -> b
                        .setTitle(title)
Loading