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

Commit 482ebe9b authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Implemented network score cache filtering."

parents ff256204 ba242734
Loading
Loading
Loading
Loading
+25 −0
Original line number Diff line number Diff line
@@ -16,10 +16,14 @@

package android.net;

import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiSsid;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;

import java.util.Objects;

@@ -64,6 +68,27 @@ public class NetworkKey implements Parcelable {
                        '"' + result.wifiSsid.toString() + '"', result.BSSID));
    }

    /**
     * Constructs a new NetworkKey for the given {@link WifiInfo}.
     *
     * @param wifiInfo the {@link WifiInfo} to create a {@link NetworkKey} for.
     * @return A new {@link NetworkKey} instance or <code>null</code> if the given {@link WifiInfo}
     *         instance doesn't represent a connected WiFi network.
     * @hide
     */
    @Nullable
    public static NetworkKey createFromWifiInfo(@Nullable WifiInfo wifiInfo) {
        if (wifiInfo != null) {
            final String ssid = wifiInfo.getSSID();
            final String bssid = wifiInfo.getBSSID();
            if (!TextUtils.isEmpty(ssid) && !ssid.equals(WifiSsid.NONE)
                    && !TextUtils.isEmpty(bssid)) {
                return new NetworkKey(new WifiKey(ssid, bssid));
            }
        }
        return null;
    }

    /**
     * Construct a new {@link NetworkKey} for a Wi-Fi network.
     * @param wifiKey the {@link WifiKey} identifying this Wi-Fi network.
+75 −0
Original line number Diff line number Diff line
package android.net;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.Mockito.when;

import android.net.wifi.WifiInfo;
import android.net.wifi.WifiSsid;
import android.support.test.runner.AndroidJUnit4;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

@RunWith(AndroidJUnit4.class)
public class NetworkKeyTest {
    private static final String VALID_SSID = "\"ssid1\"";
    private static final String VALID_BSSID = "00:00:00:00:00:00";
    @Mock private WifiInfo mWifiInfo;

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void createFromWifi_nullInput() throws Exception {
        assertNull(NetworkKey.createFromWifiInfo(null));
    }

    @Test
    public void createFromWifi_nullSsid() throws Exception {
        when(mWifiInfo.getBSSID()).thenReturn(VALID_BSSID);
        assertNull(NetworkKey.createFromWifiInfo(mWifiInfo));
    }

    @Test
    public void createFromWifi_emptySsid() throws Exception {
        when(mWifiInfo.getSSID()).thenReturn("");
        when(mWifiInfo.getBSSID()).thenReturn(VALID_BSSID);
        assertNull(NetworkKey.createFromWifiInfo(mWifiInfo));
    }

    @Test
    public void createFromWifi_noneSsid() throws Exception {
        when(mWifiInfo.getSSID()).thenReturn(WifiSsid.NONE);
        when(mWifiInfo.getBSSID()).thenReturn(VALID_BSSID);
        assertNull(NetworkKey.createFromWifiInfo(mWifiInfo));
    }

    @Test
    public void createFromWifi_nullBssid() throws Exception {
        when(mWifiInfo.getSSID()).thenReturn(VALID_SSID);
        assertNull(NetworkKey.createFromWifiInfo(mWifiInfo));
    }

    @Test
    public void createFromWifi_emptyBssid() throws Exception {
        when(mWifiInfo.getSSID()).thenReturn(VALID_SSID);
        when(mWifiInfo.getBSSID()).thenReturn("");
        assertNull(NetworkKey.createFromWifiInfo(mWifiInfo));
    }

    @Test
    public void createFromWifi_validWifiInfo() throws Exception {
        when(mWifiInfo.getSSID()).thenReturn(VALID_SSID);
        when(mWifiInfo.getBSSID()).thenReturn(VALID_BSSID);

        NetworkKey expected = new NetworkKey(new WifiKey(VALID_SSID, VALID_BSSID));
        final NetworkKey actual = NetworkKey.createFromWifiInfo(mWifiInfo);
        assertEquals(expected, actual);
    }
}
+242 −19
Original line number Diff line number Diff line
@@ -41,6 +41,10 @@ import android.net.RecommendationRequest;
import android.net.RecommendationResult;
import android.net.ScoredNetwork;
import android.net.Uri;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiScanner;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
@@ -76,7 +80,9 @@ import java.util.Map;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.Supplier;

/**
 * Backing service for {@link android.net.NetworkScoreManager}.
@@ -391,6 +397,7 @@ public class NetworkScoreService extends INetworkScoreService.Stub {
                    isEmpty = callbackList == null
                            || callbackList.getRegisteredCallbackCount() == 0;
                }

                if (isEmpty) {
                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
                        Log.v(TAG, "No scorer registered for type " + entry.getKey()
@@ -399,23 +406,238 @@ public class NetworkScoreService extends INetworkScoreService.Stub {
                    continue;
                }

                sendCallback(new Consumer<INetworkScoreCache>() {
                final BiConsumer<INetworkScoreCache, Object> consumer =
                        new FilteringCacheUpdatingConsumer(mContext, entry.getValue(),
                                entry.getKey());
                sendCacheUpdateCallback(consumer, Collections.singleton(callbackList));
            }

            return true;
        } finally {
            Binder.restoreCallingIdentity(token);
        }
    }

    /**
     * A {@link BiConsumer} implementation that filters the given {@link ScoredNetwork}
     * list (if needed) before invoking {@link INetworkScoreCache#updateScores(List)} on the
     * accepted {@link INetworkScoreCache} implementation.
     */
    @VisibleForTesting
    public static class FilteringCacheUpdatingConsumer
            implements BiConsumer<INetworkScoreCache, Object> {
        private final Context mContext;
        private final List<ScoredNetwork> mScoredNetworkList;
        private final int mNetworkType;
        // TODO(jjoslin): 1/23/17 - Consider a Map if we implement more filters.
        private Function<List<ScoredNetwork>, List<ScoredNetwork>> mCurrentNetworkFilter;
        private Function<List<ScoredNetwork>, List<ScoredNetwork>> mScanResultsFilter;

        public FilteringCacheUpdatingConsumer(Context context,
                List<ScoredNetwork> scoredNetworkList, int networkType) {
            this(context, scoredNetworkList, networkType, null, null);
        }

        @VisibleForTesting
        public FilteringCacheUpdatingConsumer(Context context,
                List<ScoredNetwork> scoredNetworkList, int networkType,
                Function<List<ScoredNetwork>, List<ScoredNetwork>> currentNetworkFilter,
                Function<List<ScoredNetwork>, List<ScoredNetwork>> scanResultsFilter) {
            mContext = context;
            mScoredNetworkList = scoredNetworkList;
            mNetworkType = networkType;
            mCurrentNetworkFilter = currentNetworkFilter;
            mScanResultsFilter = scanResultsFilter;
        }

        @Override
                    public void accept(INetworkScoreCache networkScoreCache) {
        public void accept(INetworkScoreCache networkScoreCache, Object cookie) {
            int filterType = NetworkScoreManager.CACHE_FILTER_NONE;
            if (cookie instanceof Integer) {
                filterType = (Integer) cookie;
            }

            try {
                            networkScoreCache.updateScores(entry.getValue());
                final List<ScoredNetwork> filteredNetworkList =
                        filterScores(mScoredNetworkList, filterType);
                if (!filteredNetworkList.isEmpty()) {
                    networkScoreCache.updateScores(
                            Collections.unmodifiableList(filteredNetworkList));
                }
            } catch (RemoteException e) {
                if (Log.isLoggable(TAG, Log.VERBOSE)) {
                                Log.v(TAG, "Unable to update scores of type " + entry.getKey(), e);
                    Log.v(TAG, "Unable to update scores of type " + mNetworkType, e);
                }
            }
        }

        /**
         * Applies the appropriate filter and returns the filtered results.
         */
        private List<ScoredNetwork> filterScores(List<ScoredNetwork> scoredNetworkList,
                int filterType) {
            switch (filterType) {
                case NetworkScoreManager.CACHE_FILTER_NONE:
                    return scoredNetworkList;

                case NetworkScoreManager.CACHE_FILTER_CURRENT_NETWORK:
                    if (mCurrentNetworkFilter == null) {
                        mCurrentNetworkFilter =
                                new CurrentNetworkScoreCacheFilter(new WifiInfoSupplier(mContext));
                    }
                }, Collections.singleton(callbackList));
                    return mCurrentNetworkFilter.apply(scoredNetworkList);

                case NetworkScoreManager.CACHE_FILTER_SCAN_RESULTS:
                    if (mScanResultsFilter == null) {
                        mScanResultsFilter = new ScanResultsScoreCacheFilter(
                                new ScanResultsSupplier(mContext));
                    }
                    return mScanResultsFilter.apply(scoredNetworkList);

            return true;
        } finally {
            Binder.restoreCallingIdentity(token);
                default:
                    Log.w(TAG, "Unknown filter type: " + filterType);
                    return scoredNetworkList;
            }
        }
    }

    /**
     * Helper class that improves the testability of the cache filter Functions.
     */
    private static class WifiInfoSupplier implements Supplier<WifiInfo> {
        private final Context mContext;

        WifiInfoSupplier(Context context) {
            mContext = context;
        }

        @Override
        public WifiInfo get() {
            WifiManager wifiManager = mContext.getSystemService(WifiManager.class);
            if (wifiManager != null) {
                return wifiManager.getConnectionInfo();
            }
            Log.w(TAG, "WifiManager is null, failed to return the WifiInfo.");
            return null;
        }
    }

    /**
     * Helper class that improves the testability of the cache filter Functions.
     */
    private static class ScanResultsSupplier implements Supplier<List<ScanResult>> {
        private final Context mContext;

        ScanResultsSupplier(Context context) {
            mContext = context;
        }

        @Override
        public List<ScanResult> get() {
            WifiScanner wifiScanner = mContext.getSystemService(WifiScanner.class);
            if (wifiScanner != null) {
                return wifiScanner.getSingleScanResults();
            }
            Log.w(TAG, "WifiScanner is null, failed to return scan results.");
            return Collections.emptyList();
        }
    }

    /**
     * Filters the given set of {@link ScoredNetwork}s and returns a new List containing only the
     * {@link ScoredNetwork} associated with the current network. If no network is connected the
     * returned list will be empty.
     * <p>
     * Note: this filter performs some internal caching for consistency and performance. The
     *       current network is determined at construction time and never changed. Also, the
     *       last filtered list is saved so if the same input is provided multiple times in a row
     *       the computation is only done once.
     */
    @VisibleForTesting
    public static class CurrentNetworkScoreCacheFilter
            implements Function<List<ScoredNetwork>, List<ScoredNetwork>> {
        private final NetworkKey mCurrentNetwork;
        private Pair<List<ScoredNetwork>, Integer> mCache;

        CurrentNetworkScoreCacheFilter(Supplier<WifiInfo> wifiInfoSupplier) {
            mCurrentNetwork = NetworkKey.createFromWifiInfo(wifiInfoSupplier.get());
        }

        @Override
        public List<ScoredNetwork> apply(List<ScoredNetwork> scoredNetworks) {
            if (mCurrentNetwork == null || scoredNetworks.isEmpty()) {
                return Collections.emptyList();
            }

            final int inputListHash = scoredNetworks.hashCode();
            if (mCache == null || mCache.second != inputListHash) {
                ScoredNetwork currentScore = null;
                for (int i = 0; i < scoredNetworks.size(); i++) {
                    final ScoredNetwork scoredNetwork = scoredNetworks.get(i);
                    if (scoredNetwork.networkKey.equals(mCurrentNetwork)) {
                        currentScore = scoredNetwork;
                        break;
                    }
                }

                if (currentScore == null) {
                    mCache = Pair.create(Collections.emptyList(), inputListHash);
                } else {
                    mCache = Pair.create(Collections.singletonList(currentScore), inputListHash);
                }
            }

            return mCache.first;
        }
    }

    /**
     * Filters the given set of {@link ScoredNetwork}s and returns a new List containing only the
     * {@link ScoredNetwork} associated with the current set of {@link ScanResult}s.
     * If there are no {@link ScanResult}s the returned list will be empty.
     * <p>
     * Note: this filter performs some internal caching for consistency and performance. The
     *       current set of ScanResults is determined at construction time and never changed.
     *       Also, the last filtered list is saved so if the same input is provided multiple
     *       times in a row the computation is only done once.
     */
    @VisibleForTesting
    public static class ScanResultsScoreCacheFilter
            implements Function<List<ScoredNetwork>, List<ScoredNetwork>> {
        private final List<NetworkKey> mScanResultKeys;
        private Pair<List<ScoredNetwork>, Integer> mCache;

        ScanResultsScoreCacheFilter(Supplier<List<ScanResult>> resultsSupplier) {
            mScanResultKeys = new ArrayList<>();
            List<ScanResult> scanResults = resultsSupplier.get();
            for (int i = 0; i < scanResults.size(); i++) {
                ScanResult scanResult = scanResults.get(i);
                mScanResultKeys.add(NetworkKey.createFromScanResult(scanResult));
            }
        }

        @Override
        public List<ScoredNetwork> apply(List<ScoredNetwork> scoredNetworks) {
            if (mScanResultKeys.isEmpty() || scoredNetworks.isEmpty()) {
                return Collections.emptyList();
            }

            final int inputListHash = scoredNetworks.hashCode();
            if (mCache == null || mCache.second != inputListHash) {
                List<ScoredNetwork> filteredScores = new ArrayList<>();
                for (int i = 0; i < scoredNetworks.size(); i++) {
                    final ScoredNetwork scoredNetwork = scoredNetworks.get(i);
                    for (int j = 0; j < mScanResultKeys.size(); j++) {
                        final NetworkKey scanResultKey = mScanResultKeys.get(j);
                        if (scanResultKey.equals(scoredNetwork.networkKey)) {
                            filteredScores.add(scoredNetwork);
                        }
                    }
                }
                mCache = Pair.create(filteredScores, inputListHash);
            }

            return mCache.first;
        }
    }

@@ -499,9 +721,9 @@ public class NetworkScoreService extends INetworkScoreService.Stub {

    /** Clear scores. Callers are responsible for checking permissions as appropriate. */
    private void clearInternal() {
        sendCallback(new Consumer<INetworkScoreCache>() {
        sendCacheUpdateCallback(new BiConsumer<INetworkScoreCache, Object>() {
            @Override
            public void accept(INetworkScoreCache networkScoreCache) {
            public void accept(INetworkScoreCache networkScoreCache, Object cookie) {
                try {
                    networkScoreCache.clearScores();
                } catch (RemoteException e) {
@@ -675,9 +897,9 @@ public class NetworkScoreService extends INetworkScoreService.Stub {
        }
        writer.println("Current scorer: " + currentScorer.packageName);

        sendCallback(new Consumer<INetworkScoreCache>() {
        sendCacheUpdateCallback(new BiConsumer<INetworkScoreCache, Object>() {
            @Override
            public void accept(INetworkScoreCache networkScoreCache) {
            public void accept(INetworkScoreCache networkScoreCache, Object cookie) {
                try {
                  TransferPipe.dumpAsync(networkScoreCache.asBinder(), fd, args);
                } catch (IOException | RemoteException e) {
@@ -708,14 +930,15 @@ public class NetworkScoreService extends INetworkScoreService.Stub {
        }
    }

    private void sendCallback(Consumer<INetworkScoreCache> consumer,
    private void sendCacheUpdateCallback(BiConsumer<INetworkScoreCache, Object> consumer,
            Collection<RemoteCallbackList<INetworkScoreCache>> remoteCallbackLists) {
        for (RemoteCallbackList<INetworkScoreCache> callbackList : remoteCallbackLists) {
            synchronized (callbackList) { // Ensure only one active broadcast per RemoteCallbackList
                final int count = callbackList.beginBroadcast();
                try {
                    for (int i = 0; i < count; i++) {
                        consumer.accept(callbackList.getBroadcastItem(i));
                        consumer.accept(callbackList.getBroadcastItem(i),
                                callbackList.getRegisteredCallbackCookie(i));
                    }
                } finally {
                    callbackList.finishBroadcast();
+207 −2

File changed.

Preview size limit exceeded, changes collapsed.