Loading core/java/android/net/NetworkScoreManager.java +6 −12 Original line number Diff line number Diff line Loading @@ -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; } /** Loading Loading @@ -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 Loading services/core/java/com/android/server/NetworkScoreService.java +137 −23 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading @@ -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; /** Loading @@ -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() { Loading Loading @@ -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. */ Loading Loading @@ -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)) { Loading @@ -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(); } Loading @@ -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(); } Loading Loading @@ -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(); Loading @@ -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; } Loading @@ -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(); } Loading Loading @@ -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; Loading Loading @@ -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 Loading @@ -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) { Loading @@ -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); } } } services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java +163 −4 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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}. Loading @@ -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 { Loading @@ -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 Loading @@ -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); Loading Loading @@ -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(); } } Loading
core/java/android/net/NetworkScoreManager.java +6 −12 Original line number Diff line number Diff line Loading @@ -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; } /** Loading Loading @@ -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 Loading
services/core/java/com/android/server/NetworkScoreService.java +137 −23 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading @@ -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; /** Loading @@ -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() { Loading Loading @@ -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. */ Loading Loading @@ -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)) { Loading @@ -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(); } Loading @@ -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(); } Loading Loading @@ -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(); Loading @@ -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; } Loading @@ -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(); } Loading Loading @@ -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; Loading Loading @@ -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 Loading @@ -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) { Loading @@ -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); } } }
services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java +163 −4 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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}. Loading @@ -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 { Loading @@ -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 Loading @@ -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); Loading Loading @@ -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(); } }