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

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

Slice background worker with Wi-Fi Slice

Test: manual

Change-Id: Ic4fdc5713f511ff80f03728c99c68fda3d0cab02
parent 4ae062b7
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -89,6 +89,16 @@ public interface CustomSliceable {
        return null;
    }

    /**
     * Settings Slices which can represent component lists that are updatable by the
     * {@link SliceBackgroundWorker} returned here.
     *
     * @return a {@link SliceBackgroundWorker} for fetching the list of results in the background.
     */
    default SliceBackgroundWorker getBackgroundWorker() {
        return null;
    }

    /**
     * Standardize the intents returned to indicate actions by the Slice.
     * <p>
+30 −0
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@ import android.os.StrictMode;
import android.provider.Settings;
import android.provider.SettingsSlicesContract;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.KeyValueListParser;
import android.util.Log;
@@ -131,6 +132,8 @@ public class SettingsSliceProvider extends SliceProvider {

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

    final Map<Uri, SliceBackgroundWorker> mWorkerMap = new ArrayMap<>();

    public SettingsSliceProvider() {
        super(READ_SEARCH_INDEXABLES);
    }
@@ -165,6 +168,7 @@ public class SettingsSliceProvider extends SliceProvider {
            if (filter != null) {
                registerIntentToUri(filter, sliceUri);
            }
            startBackgroundWorker(sliceable);
            return;
        }

@@ -191,6 +195,7 @@ public class SettingsSliceProvider extends SliceProvider {
            SliceBroadcastRelay.unregisterReceivers(getContext(), sliceUri);
            mRegisteredUris.remove(sliceUri);
        }
        stopBackgroundWorker(sliceUri);
        mSliceDataCache.remove(sliceUri);
    }

@@ -348,6 +353,31 @@ public class SettingsSliceProvider extends SliceProvider {
        }
    }

    private void startBackgroundWorker(CustomSliceable sliceable) {
        final SliceBackgroundWorker worker = sliceable.getBackgroundWorker();
        if (worker == null) {
            return;
        }

        final Uri uri = sliceable.getUri();
        Log.d(TAG, "Starting background worker for: " + uri);
        if (mWorkerMap.containsKey(uri)) {
            return;
        }

        mWorkerMap.put(uri, worker);
        worker.onSlicePinned();
    }

    private void stopBackgroundWorker(Uri uri) {
        final SliceBackgroundWorker worker = mWorkerMap.get(uri);
        if (worker != null) {
            Log.d(TAG, "Stopping background worker for: " + uri);
            worker.onSliceUnpinned();
            mWorkerMap.remove(uri);
        }
    }

    private List<Uri> buildUrisFromKeys(List<String> keys, String authority) {
        final List<Uri> descendants = new ArrayList<>();

+85 −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.slices;

import android.content.ContentResolver;
import android.net.Uri;

import java.util.ArrayList;
import java.util.List;

/**
 * The Slice background worker is used to make Settings Slices be able to work with data that is
 * changing continuously, e.g. available Wi-Fi networks.
 *
 * The background worker will be started at {@link SettingsSliceProvider#onSlicePinned(Uri)}, and be
 * stopped at {@link SettingsSliceProvider#onSliceUnpinned(Uri)}.
 *
 * {@link SliceBackgroundWorker} caches the results, uses the cache to compare if there is any data
 * changed, and then notifies the Slice {@link Uri} to update.
 */
public abstract class SliceBackgroundWorker<E> {

    private final ContentResolver mContentResolver;
    private final Uri mUri;

    private List<E> mCachedResults;

    protected SliceBackgroundWorker(ContentResolver cr, Uri uri) {
        mContentResolver = cr;
        mUri = uri;
    }

    /**
     * Called when the Slice is pinned. This is the place to register callbacks or initialize scan
     * tasks.
     */
    protected abstract void onSlicePinned();

    /**
     * Called when the Slice is unpinned. This is the place to unregister callbacks or perform any
     * final cleanup.
     */
    protected abstract void onSliceUnpinned();

    /**
     * @return a {@link List} of cached results
     */
    public final List<E> getResults() {
        return mCachedResults == null ? null : new ArrayList<>(mCachedResults);
    }

    /**
     * Update the results when data changes
     */
    protected final void updateResults(List<E> results) {
        boolean needNotify = false;

        if (results == null) {
            if (mCachedResults != null) {
                needNotify = true;
            }
        } else {
            needNotify = !results.equals(mCachedResults);
        }

        if (needNotify) {
            mCachedResults = results;
            mContentResolver.notifyChange(mUri, null);
        }
    }
}
+5 −6
Original line number Diff line number Diff line
@@ -37,12 +37,6 @@ public class WifiDialogActivity extends Activity implements WifiDialog.WifiDialo

    private static final String TAG = "WifiDialogActivity";

    private static final int RESULT_CONNECTED = RESULT_FIRST_USER;
    private static final int RESULT_FORGET = RESULT_FIRST_USER + 1;

    private static final String KEY_ACCESS_POINT_STATE = "access_point_state";
    private static final String KEY_WIFI_CONFIGURATION = "wifi_configuration";

    /**
     * Boolean extra indicating whether this activity should connect to an access point on the
     * caller's behalf. If this is set to false, the caller should check
@@ -51,6 +45,11 @@ public class WifiDialogActivity extends Activity implements WifiDialog.WifiDialo
     */
    @VisibleForTesting
    static final String KEY_CONNECT_FOR_CALLER = "connect_for_caller";
    static final String KEY_ACCESS_POINT_STATE = "access_point_state";
    private static final String KEY_WIFI_CONFIGURATION = "wifi_configuration";

    private static final int RESULT_CONNECTED = RESULT_FIRST_USER;
    private static final int RESULT_FORGET = RESULT_FIRST_USER + 1;

    private WifiDialog mDialog;

+144 −3
Original line number Diff line number Diff line
@@ -29,6 +29,9 @@ import android.net.Uri;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiSsid;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.provider.SettingsSlicesContract;
import android.text.TextUtils;

@@ -42,8 +45,16 @@ 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.core.SubSettingLauncher;
import com.android.settings.slices.CustomSliceable;
import com.android.settings.slices.SliceBackgroundWorker;
import com.android.settings.slices.SliceBuilderUtils;
import com.android.settings.wifi.details.WifiNetworkDetailsFragment;
import com.android.settingslib.wifi.AccessPoint;
import com.android.settingslib.wifi.WifiTracker;

import java.util.ArrayList;
import java.util.List;

/**
 * Utility class to build a Wifi Slice, and handle all associated actions.
@@ -96,14 +107,73 @@ public class WifiSlice implements CustomSliceable {
        final SliceAction toggleSliceAction = new SliceAction(toggleAction, null /* actionTitle */,
                isWifiEnabled);

        return new ListBuilder(mContext, WIFI_URI, ListBuilder.INFINITY)
        final ListBuilder listBuilder = new ListBuilder(mContext, WIFI_URI, ListBuilder.INFINITY)
                .setAccentColor(color)
                .addRow(new RowBuilder()
                        .setTitle(title)
                        .setSubtitle(summary)
                        .addEndItem(toggleSliceAction)
                        .setPrimaryAction(primarySliceAction))
                .build();
                        .setPrimaryAction(primarySliceAction));

        if (isWifiEnabled) {
            final List<AccessPoint> result = getBackgroundWorker().getResults();
            if (result != null && !result.isEmpty()) {
                for (AccessPoint ap : result) {
                    listBuilder.addRow(getAccessPointRow(ap));
                }
                listBuilder.setSeeMoreAction(primaryAction);
            }
        }
        return listBuilder.build();
    }

    private RowBuilder getAccessPointRow(AccessPoint accessPoint) {
        final String title = accessPoint.getConfigName();
        final IconCompat levelIcon = IconCompat.createWithResource(mContext,
                com.android.settingslib.Utils.getWifiIconResource(accessPoint.getLevel()));
        final RowBuilder rowBuilder = new RowBuilder()
                .setTitleItem(levelIcon, ListBuilder.ICON_IMAGE)
                .setTitle(title)
                .setSubtitle(accessPoint.getSettingsSummary())
                .setPrimaryAction(new SliceAction(
                        getAccessPointAction(accessPoint), levelIcon, title));

        final IconCompat endIcon = getEndIcon(accessPoint);
        if (endIcon != null) {
            rowBuilder.addEndItem(endIcon, ListBuilder.ICON_IMAGE);
        }
        return rowBuilder;
    }

    private IconCompat getEndIcon(AccessPoint accessPoint) {
        if (accessPoint.isActive()) {
            return IconCompat.createWithResource(mContext, R.drawable.ic_settings);
        } else if (accessPoint.getSecurity() != AccessPoint.SECURITY_NONE) {
            return IconCompat.createWithResource(mContext, R.drawable.ic_friction_lock_closed);
        } else if (accessPoint.isMetered()) {
            return IconCompat.createWithResource(mContext, R.drawable.ic_friction_money);
        }
        return null;
    }

    private PendingIntent getAccessPointAction(AccessPoint accessPoint) {
        final Bundle extras = new Bundle();
        accessPoint.saveWifiState(extras);

        Intent intent;
        if (accessPoint.isActive()) {
            intent = new SubSettingLauncher(mContext)
                    .setTitleRes(R.string.pref_title_network_details)
                    .setDestination(WifiNetworkDetailsFragment.class.getName())
                    .setArguments(extras)
                    .setSourceMetricsCategory(MetricsEvent.WIFI)
                    .toIntent();
        } else {
            intent = new Intent(mContext, WifiDialogActivity.class);
            intent.putExtra(WifiDialogActivity.KEY_ACCESS_POINT_STATE, extras);
        }
        return PendingIntent.getActivity(mContext, accessPoint.hashCode() /* requestCode */,
                intent, 0 /* flags */);
    }

    /**
@@ -176,4 +246,75 @@ public class WifiSlice implements CustomSliceable {
        return PendingIntent.getActivity(mContext, 0 /* requestCode */,
                intent, 0 /* flags */);
    }

    @Override
    public SliceBackgroundWorker getBackgroundWorker() {
        return WifiScanWorker.getInstance(mContext, WIFI_URI);
    }

    private static class WifiScanWorker extends SliceBackgroundWorker<AccessPoint>
            implements WifiTracker.WifiListener {

        private static WifiScanWorker mWifiScanWorker;

        private final Context mContext;

        private WifiTracker mWifiTracker;
        private WifiManager mWifiManager;

        private WifiScanWorker(Context context, Uri uri) {
            super(context.getContentResolver(), uri);
            mContext = context;
        }

        public static WifiScanWorker getInstance(Context context, Uri uri) {
            if (mWifiScanWorker == null) {
                mWifiScanWorker = new WifiScanWorker(context, uri);
            }
            return mWifiScanWorker;
        }

        @Override
        protected void onSlicePinned() {
            new Handler(Looper.getMainLooper()).post(() -> {
                mWifiTracker = new WifiTracker(mContext, this, true, true);
                mWifiManager = mWifiTracker.getManager();
                mWifiTracker.onStart();
                onAccessPointsChanged();
            });
        }

        @Override
        protected void onSliceUnpinned() {
            mWifiTracker.onStop();
            mWifiTracker.onDestroy();
            mWifiScanWorker = null;
        }

        @Override
        public void onWifiStateChanged(int state) {
        }

        @Override
        public void onConnectedChanged() {
        }

        @Override
        public void onAccessPointsChanged() {
            // in case state has changed
            if (!mWifiManager.isWifiEnabled()) {
                updateResults(null);
                return;
            }
            // AccessPoints are sorted by the WifiTracker
            final List<AccessPoint> accessPoints = mWifiTracker.getAccessPoints();
            final List<AccessPoint> resultList = new ArrayList<>();
            for (AccessPoint ap : accessPoints) {
                if (ap.isReachable()) {
                    resultList.add(ap);
                }
            }
            updateResults(resultList);
        }
    }
}
Loading