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

Commit b50fdc7f authored by Jeremy Joslin's avatar Jeremy Joslin Committed by android-build-merger
Browse files

Merge "Implement the request and recommend calls."

am: 1e814b39

Change-Id: I91c905ab2fc7e061462fb96c2c81a783aa93a391
parents 8fbeb1d7 1e814b39
Loading
Loading
Loading
Loading
+6 −12
Original line number Diff line number Diff line
@@ -272,19 +272,11 @@ public class NetworkScoreManager {
     * @hide
     */
    public boolean requestScores(NetworkKey[] networks) throws SecurityException {
        String activeScorer = getActiveScorerPackage();
        if (activeScorer == null) {
            return false;
        try {
            return mService.requestScores(networks);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
        Intent intent = new Intent(ACTION_SCORE_NETWORKS);
        intent.setPackage(activeScorer);
        intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
        intent.putExtra(EXTRA_NETWORKS_TO_SCORE, networks);
        // A scorer should never become active if its package doesn't hold SCORE_NETWORKS, but
        // ensure the package still holds it to be extra safe.
        // TODO: http://b/23422763
        mContext.sendBroadcastAsUser(intent, UserHandle.SYSTEM, Manifest.permission.SCORE_NETWORKS);
        return true;
    }

    /**
@@ -344,6 +336,8 @@ public class NetworkScoreManager {
    /**
     * Request a recommendation for which network to connect to.
     *
     * <p>It is not safe to call this method from the main thread.
     *
     * @param request a {@link RecommendationRequest} instance containing additional
     *                request details
     * @return a {@link RecommendationResult} instance containing the recommended network
+137 −23
Original line number Diff line number Diff line
@@ -16,7 +16,11 @@

package com.android.server;

import static android.net.NetworkRecommendationProvider.EXTRA_RECOMMENDATION_RESULT;
import static android.net.NetworkRecommendationProvider.EXTRA_SEQUENCE;

import android.Manifest.permission;
import android.annotation.Nullable;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
@@ -26,6 +30,7 @@ import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.database.ContentObserver;
import android.net.INetworkRecommendationProvider;
import android.net.INetworkScoreCache;
import android.net.INetworkScoreService;
import android.net.NetworkKey;
@@ -36,17 +41,22 @@ import android.net.RecommendationResult;
import android.net.ScoredNetwork;
import android.net.Uri;
import android.net.wifi.WifiConfiguration;
import android.os.Bundle;
import android.os.IBinder;
import android.os.IRemoteCallback;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.Settings.Global;
import android.util.ArrayMap;
import android.util.Log;
import android.util.TimedRemoteCaller;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.content.PackageMonitor;
import com.android.internal.os.TransferPipe;

import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
@@ -55,6 +65,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;

/**
@@ -63,17 +74,20 @@ import java.util.function.Consumer;
 */
public class NetworkScoreService extends INetworkScoreService.Stub {
    private static final String TAG = "NetworkScoreService";
    private static final boolean DBG = false;
    private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);

    private final Context mContext;
    private final NetworkScorerAppManager mNetworkScorerAppManager;
    private final RequestRecommendationCaller mRequestRecommendationCaller;
    @GuardedBy("mScoreCaches")
    private final Map<Integer, RemoteCallbackList<INetworkScoreCache>> mScoreCaches;
    /** Lock used to update mPackageMonitor when scorer package changes occur. */
    private final Object mPackageMonitorLock = new Object[0];
    private final Object mServiceConnectionLock = new Object[0];

    @GuardedBy("mPackageMonitorLock")
    private NetworkScorerPackageMonitor mPackageMonitor;
    @GuardedBy("mServiceConnectionLock")
    private ScoringServiceConnection mServiceConnection;

    private BroadcastReceiver mUserIntentReceiver = new BroadcastReceiver() {
@@ -194,6 +208,9 @@ public class NetworkScoreService extends INetworkScoreService.Stub {
        mContext.registerReceiverAsUser(
                mUserIntentReceiver, UserHandle.SYSTEM, filter, null /* broadcastPermission*/,
                null /* scheduler */);
        // TODO(jjoslin): 12/15/16 - Make timeout configurable.
        mRequestRecommendationCaller =
            new RequestRecommendationCaller(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS);
    }

    /** Called when the system is ready to run third-party code but before it actually does so. */
@@ -264,6 +281,7 @@ public class NetworkScoreService extends INetworkScoreService.Stub {
        if (scorerData != null && scorerData.recommendationServiceClassName != null) {
            ComponentName componentName = new ComponentName(scorerData.packageName,
                    scorerData.recommendationServiceClassName);
            synchronized (mServiceConnectionLock) {
                // If we're connected to a different component then drop it.
                if (mServiceConnection != null
                        && !mServiceConnection.mComponentName.equals(componentName)) {
@@ -277,6 +295,7 @@ public class NetworkScoreService extends INetworkScoreService.Stub {

                // Make sure the connection is connected (idempotent)
                mServiceConnection.connect(mContext);
            }
        } else { // otherwise make sure it isn't bound.
            unbindFromScoringServiceIfNeeded();
        }
@@ -284,10 +303,12 @@ public class NetworkScoreService extends INetworkScoreService.Stub {

    private void unbindFromScoringServiceIfNeeded() {
        if (DBG) Log.d(TAG, "unbindFromScoringServiceIfNeeded");
        synchronized (mServiceConnectionLock) {
            if (mServiceConnection != null) {
                mServiceConnection.disconnect(mContext);
            }
            mServiceConnection = null;
        }
        clearInternal();
    }

@@ -441,7 +462,22 @@ public class NetworkScoreService extends INetworkScoreService.Stub {

    @Override
    public RecommendationResult requestRecommendation(RecommendationRequest request) {
        // TODO(jjoslin): 11/25/16 - Update with real impl.
        mContext.enforceCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED, TAG);
        throwIfCalledOnMainThread();
        final INetworkRecommendationProvider provider = getRecommendationProvider();
        if (provider != null) {
            try {
                return mRequestRecommendationCaller.getRecommendationResult(provider, request);
            } catch (RemoteException | TimeoutException e) {
                Log.w(TAG, "Failed to request a recommendation.", e);
                // TODO(jjoslin): 12/15/16 - Keep track of failures.
            }
        }

        if (DBG) {
            Log.d(TAG, "Returning the default network recommendation.");
        }

        WifiConfiguration selectedConfig = null;
        if (request != null) {
            selectedConfig = request.getCurrentSelectedConfig();
@@ -451,7 +487,19 @@ public class NetworkScoreService extends INetworkScoreService.Stub {

    @Override
    public boolean requestScores(NetworkKey[] networks) {
        // TODO(jjoslin): 12/13/16 - Implement
        mContext.enforceCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED, TAG);
        final INetworkRecommendationProvider provider = getRecommendationProvider();
        if (provider != null) {
            try {
                provider.requestScores(networks);
                // TODO(jjoslin): 12/15/16 - Consider pushing null scores into the cache to prevent
                // repeated requests for the same scores.
                return true;
            } catch (RemoteException e) {
                Log.w(TAG, "Failed to request scores.", e);
                // TODO(jjoslin): 12/15/16 - Keep track of failures.
            }
        }
        return false;
    }

@@ -476,11 +524,13 @@ public class NetworkScoreService extends INetworkScoreService.Stub {
            }
        }, getScoreCacheLists());

        synchronized (mServiceConnectionLock) {
            if (mServiceConnection != null) {
                mServiceConnection.dump(fd, writer, args);
            } else {
                writer.println("ScoringServiceConnection: null");
            }
        }
        writer.flush();
    }

@@ -512,10 +562,27 @@ public class NetworkScoreService extends INetworkScoreService.Stub {
        }
    }

    private void throwIfCalledOnMainThread() {
        if (Thread.currentThread() == mContext.getMainLooper().getThread()) {
            throw new RuntimeException("Cannot invoke on the main thread");
        }
    }

    @Nullable
    private INetworkRecommendationProvider getRecommendationProvider() {
        synchronized (mServiceConnectionLock) {
            if (mServiceConnection != null) {
                return mServiceConnection.getRecommendationProvider();
            }
        }
        return null;
    }

    private static class ScoringServiceConnection implements ServiceConnection {
        private final ComponentName mComponentName;
        private boolean mBound = false;
        private boolean mConnected = false;
        private volatile boolean mBound = false;
        private volatile boolean mConnected = false;
        private volatile INetworkRecommendationProvider mRecommendationProvider;

        ScoringServiceConnection(ComponentName componentName) {
            mComponentName = componentName;
@@ -546,12 +613,19 @@ public class NetworkScoreService extends INetworkScoreService.Stub {
            } catch (RuntimeException e) {
                Log.e(TAG, "Unbind failed.", e);
            }

            mRecommendationProvider = null;
        }

        INetworkRecommendationProvider getRecommendationProvider() {
            return mRecommendationProvider;
        }

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            if (DBG) Log.d(TAG, "ScoringServiceConnection: " + name.flattenToString());
            mConnected = true;
            mRecommendationProvider = INetworkRecommendationProvider.Stub.asInterface(service);
        }

        @Override
@@ -560,6 +634,7 @@ public class NetworkScoreService extends INetworkScoreService.Stub {
                Log.d(TAG, "ScoringServiceConnection, disconnected: " + name.flattenToString());
            }
            mConnected = false;
            mRecommendationProvider = null;
        }

        public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
@@ -567,4 +642,43 @@ public class NetworkScoreService extends INetworkScoreService.Stub {
                    + ", connected: " + mConnected);
        }
    }

    /**
     * Executes the async requestRecommendation() call with a timeout.
     */
    private static final class RequestRecommendationCaller
            extends TimedRemoteCaller<RecommendationResult> {
        private final IRemoteCallback mCallback;

        RequestRecommendationCaller(long callTimeoutMillis) {
            super(callTimeoutMillis);
            mCallback = new IRemoteCallback.Stub() {
                @Override
                public void sendResult(Bundle data) throws RemoteException {
                    final RecommendationResult result =
                            data.getParcelable(EXTRA_RECOMMENDATION_RESULT);
                    final int sequence = data.getInt(EXTRA_SEQUENCE, -1);
                    onRemoteMethodResult(result, sequence);
                }
            };
        }

        /**
         * Runs the requestRecommendation() call on the given {@link INetworkRecommendationProvider}
         * instance.
         *
         * @param target the {@link INetworkRecommendationProvider} to request a recommendation
         *               from
         * @param request the {@link RecommendationRequest} from the calling client
         * @return a {@link RecommendationResult} from the provider
         * @throws RemoteException if the call failed
         * @throws TimeoutException if the call took longer than the set timeout
         */
        RecommendationResult getRecommendationResult(INetworkRecommendationProvider target,
                RecommendationRequest request) throws RemoteException, TimeoutException {
            final int sequence = onBeforeRemoteCall();
            target.requestRecommendation(request, mCallback, sequence);
            return getResultTimed(sequence);
        }
    }
}
+163 −4
Original line number Diff line number Diff line
@@ -16,20 +16,30 @@

package com.android.server;

import static android.net.NetworkRecommendationProvider.EXTRA_RECOMMENDATION_RESULT;
import static android.net.NetworkRecommendationProvider.EXTRA_SEQUENCE;
import static android.net.NetworkScoreManager.CACHE_FILTER_NONE;

import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertTrue;
import static junit.framework.Assert.fail;

import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyListOf;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Matchers.isA;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;

import android.Manifest.permission;
@@ -40,23 +50,28 @@ import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.net.INetworkRecommendationProvider;
import android.net.INetworkScoreCache;
import android.net.NetworkKey;
import android.net.NetworkScorerAppManager;
import android.net.NetworkScorerAppManager.NetworkScorerAppData;
import android.net.RecommendationRequest;
import android.net.RecommendationResult;
import android.net.ScoredNetwork;
import android.net.WifiKey;
import android.net.wifi.WifiConfiguration;
import android.os.Bundle;
import android.os.IBinder;
import android.os.IRemoteCallback;
import android.os.Looper;
import android.os.RemoteException;
import android.os.UserHandle;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.MediumTest;
import android.support.test.runner.AndroidJUnit4;

import com.android.server.devicepolicy.MockUtils;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.List;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -64,6 +79,13 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.List;

/**
 * Tests for {@link NetworkScoreService}.
@@ -83,10 +105,12 @@ public class NetworkScoreServiceTest {
    @Mock private Resources mResources;
    @Mock private INetworkScoreCache.Stub mNetworkScoreCache, mNetworkScoreCache2;
    @Mock private IBinder mIBinder, mIBinder2;
    @Mock private INetworkRecommendationProvider mRecommendationProvider;
    @Captor private ArgumentCaptor<List<ScoredNetwork>> mScoredNetworkCaptor;

    private ContentResolver mContentResolver;
    private NetworkScoreService mNetworkScoreService;
    private RecommendationRequest mRecommendationRequest;

    @Before
    public void setUp() throws Exception {
@@ -97,6 +121,9 @@ public class NetworkScoreServiceTest {
        when(mContext.getContentResolver()).thenReturn(mContentResolver);
        when(mContext.getResources()).thenReturn(mResources);
        mNetworkScoreService = new NetworkScoreService(mContext, mNetworkScorerAppManager);
        WifiConfiguration configuration = new WifiConfiguration();
        mRecommendationRequest = new RecommendationRequest.Builder()
            .setCurrentRecommendedWifiConfig(configuration).build();
    }

    @Test
@@ -113,6 +140,118 @@ public class NetworkScoreServiceTest {
                eq(UserHandle.SYSTEM));
    }

    @Test
    public void testRequestScores_noPermission() throws Exception {
        doThrow(new SecurityException()).when(mContext)
            .enforceCallingOrSelfPermission(eq(permission.BROADCAST_NETWORK_PRIVILEGED),
                anyString());
        try {
            mNetworkScoreService.requestScores(null);
            fail("BROADCAST_NETWORK_PRIVILEGED not enforced.");
        } catch (SecurityException e) {
            // expected
        }
    }

    @Test
    public void testRequestScores_providerNotConnected() throws Exception {
        assertFalse(mNetworkScoreService.requestScores(new NetworkKey[0]));
        verifyZeroInteractions(mRecommendationProvider);
    }

    @Test
    public void testRequestScores_providerThrowsRemoteException() throws Exception {
        injectProvider();
        doThrow(new RemoteException()).when(mRecommendationProvider)
            .requestScores(any(NetworkKey[].class));

        assertFalse(mNetworkScoreService.requestScores(new NetworkKey[0]));
    }

    @Test
    public void testRequestScores_providerAvailable() throws Exception {
        injectProvider();

        final NetworkKey[] networks = new NetworkKey[0];
        assertTrue(mNetworkScoreService.requestScores(networks));
        verify(mRecommendationProvider).requestScores(networks);
    }

    @Test
    public void testRequestRecommendation_noPermission() throws Exception {
        doThrow(new SecurityException()).when(mContext)
            .enforceCallingOrSelfPermission(eq(permission.BROADCAST_NETWORK_PRIVILEGED),
                anyString());
        try {
            mNetworkScoreService.requestRecommendation(mRecommendationRequest);
            fail("BROADCAST_NETWORK_PRIVILEGED not enforced.");
        } catch (SecurityException e) {
            // expected
        }
    }

    @Test
    public void testRequestRecommendation_mainThread() throws Exception {
        when(mContext.getMainLooper()).thenReturn(Looper.myLooper());
        try {
            mNetworkScoreService.requestRecommendation(mRecommendationRequest);
            fail("requestRecommendation run on main thread.");
        } catch (RuntimeException e) {
            // expected
        }
    }

    @Test
    public void testRequestRecommendation_providerNotConnected() throws Exception {
        when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper());

        final RecommendationResult result =
                mNetworkScoreService.requestRecommendation(mRecommendationRequest);
        assertNotNull(result);
        assertEquals(mRecommendationRequest.getCurrentSelectedConfig(),
                result.getWifiConfiguration());
    }

    @Test
    public void testRequestRecommendation_providerThrowsRemoteException() throws Exception {
        injectProvider();
        when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper());
        doThrow(new RemoteException()).when(mRecommendationProvider)
                .requestRecommendation(eq(mRecommendationRequest), isA(IRemoteCallback.class),
                        anyInt());

        final RecommendationResult result =
                mNetworkScoreService.requestRecommendation(mRecommendationRequest);
        assertNotNull(result);
        assertEquals(mRecommendationRequest.getCurrentSelectedConfig(),
                result.getWifiConfiguration());
    }

    @Test
    public void testRequestRecommendation_resultReturned() throws Exception {
        injectProvider();
        when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper());
        final WifiConfiguration wifiConfiguration = new WifiConfiguration();
        wifiConfiguration.SSID = "testRequestRecommendation_resultReturned";
        final RecommendationResult providerResult =
                new RecommendationResult(wifiConfiguration);
        final Bundle bundle = new Bundle();
        bundle.putParcelable(EXTRA_RECOMMENDATION_RESULT, providerResult);
        doAnswer(invocation -> {
            bundle.putInt(EXTRA_SEQUENCE, invocation.getArgumentAt(2, int.class));
            invocation.getArgumentAt(1, IRemoteCallback.class).sendResult(bundle);
            return null;
        }).when(mRecommendationProvider)
                .requestRecommendation(eq(mRecommendationRequest), isA(IRemoteCallback.class),
                        anyInt());

        final RecommendationResult result =
                mNetworkScoreService.requestRecommendation(mRecommendationRequest);
        assertNotNull(result);
        assertEquals(providerResult.getWifiConfiguration().SSID,
                result.getWifiConfiguration().SSID);
    }

    @Test
    public void testUpdateScores_notActiveScorer() {
        when(mNetworkScorerAppManager.isCallerActiveScorer(anyInt())).thenReturn(false);
@@ -301,4 +440,24 @@ public class NetworkScoreServiceTest {

        assertFalse(stringWriter.toString().isEmpty());
    }

    // "injects" the mock INetworkRecommendationProvider into the NetworkScoreService.
    private void injectProvider() {
        final ComponentName componentName = new ComponentName(NEW_SCORER.packageName,
                NEW_SCORER.recommendationServiceClassName);
        when(mNetworkScorerAppManager.getActiveScorer()).thenReturn(NEW_SCORER);
        when(mContext.bindServiceAsUser(isA(Intent.class), isA(ServiceConnection.class), anyInt(),
                isA(UserHandle.class))).thenAnswer(new Answer<Boolean>() {
            @Override
            public Boolean answer(InvocationOnMock invocation) throws Throwable {
                IBinder mockBinder = mock(IBinder.class);
                when(mockBinder.queryLocalInterface(anyString()))
                        .thenReturn(mRecommendationProvider);
                invocation.getArgumentAt(1, ServiceConnection.class)
                        .onServiceConnected(componentName, mockBinder);
                return true;
            }
        });
        mNetworkScoreService.systemRunning();
    }
}