diff --git a/core/java/android/net/INetworkScoreService.aidl b/core/java/android/net/INetworkScoreService.aidl index 59cbf6e93894e..542a0a7d40408 100644 --- a/core/java/android/net/INetworkScoreService.aidl +++ b/core/java/android/net/INetworkScoreService.aidl @@ -56,16 +56,25 @@ interface INetworkScoreService void disableScoring(); /** - * Register a network subsystem for scoring. + * Register a cache to receive scoring updates. * * @param networkType the type of network this cache can handle. See {@link NetworkKey#type}. * @param scoreCache implementation of {@link INetworkScoreCache} to store the scores. * @throws SecurityException if the caller is not the system. - * @throws IllegalArgumentException if a score cache is already registed for this type. * @hide */ void registerNetworkScoreCache(int networkType, INetworkScoreCache scoreCache); + /** + * Unregister a cache to receive scoring updates. + * + * @param networkType the type of network this cache can handle. See {@link NetworkKey#type}. + * @param scoreCache implementation of {@link INetworkScoreCache} to store the scores. + * @throws SecurityException if the caller is not the system. + * @hide + */ + void unregisterNetworkScoreCache(int networkType, INetworkScoreCache scoreCache); + /** * Request a recommendation for the best network to connect to * taking into account the inputs from the {@link RecommendationRequest}. diff --git a/core/java/android/net/NetworkScoreManager.java b/core/java/android/net/NetworkScoreManager.java index c301fe3b73fe6..af21cefce7ed4 100644 --- a/core/java/android/net/NetworkScoreManager.java +++ b/core/java/android/net/NetworkScoreManager.java @@ -278,6 +278,24 @@ public class NetworkScoreManager { } } + /** + * Unregister a network score cache. + * + * @param networkType the type of network this cache can handle. See {@link NetworkKey#type}. + * @param scoreCache implementation of {@link INetworkScoreCache} to store the scores. + * @throws SecurityException if the caller does not hold the + * {@link android.Manifest.permission#BROADCAST_NETWORK_PRIVILEGED} permission. + * @throws IllegalArgumentException if a score cache is already registered for this type. + * @hide + */ + public void unregisterNetworkScoreCache(int networkType, INetworkScoreCache scoreCache) { + try { + mService.unregisterNetworkScoreCache(networkType, scoreCache); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * Request a recommendation for which network to connect to. * diff --git a/services/core/java/com/android/server/NetworkScoreService.java b/services/core/java/com/android/server/NetworkScoreService.java index 4c9ea58e63950..6412e01c1ff12 100644 --- a/services/core/java/com/android/server/NetworkScoreService.java +++ b/services/core/java/com/android/server/NetworkScoreService.java @@ -36,10 +36,12 @@ import android.net.ScoredNetwork; import android.net.wifi.WifiConfiguration; import android.os.Binder; import android.os.IBinder; +import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.UserHandle; import android.provider.Settings; import android.text.TextUtils; +import android.util.ArrayMap; import android.util.Log; import com.android.internal.R; @@ -52,11 +54,11 @@ import java.io.FileDescriptor; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; +import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Map; -import java.util.Set; +import java.util.function.Consumer; /** * Backing service for {@link android.net.NetworkScoreManager}. @@ -68,7 +70,8 @@ public class NetworkScoreService extends INetworkScoreService.Stub { private final Context mContext; private final NetworkScorerAppManager mNetworkScorerAppManager; - private final Map mScoreCaches; + @GuardedBy("mScoreCaches") + private final Map> mScoreCaches; /** Lock used to update mPackageMonitor when scorer package changes occur. */ private final Object mPackageMonitorLock = new Object[0]; @@ -166,7 +169,7 @@ public class NetworkScoreService extends INetworkScoreService.Stub { NetworkScoreService(Context context, NetworkScorerAppManager networkScoreAppManager) { mContext = context; mNetworkScorerAppManager = networkScoreAppManager; - mScoreCaches = new HashMap<>(); + mScoreCaches = new ArrayMap<>(); IntentFilter filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED); // TODO: Need to update when we support per-user scorers. http://b/23422763 mContext.registerReceiverAsUser( @@ -276,7 +279,7 @@ public class NetworkScoreService extends INetworkScoreService.Stub { } // Separate networks by type. - Map> networksByType = new HashMap<>(); + Map> networksByType = new ArrayMap<>(); for (ScoredNetwork network : networks) { List networkList = networksByType.get(network.networkKey.type); if (networkList == null) { @@ -287,19 +290,32 @@ public class NetworkScoreService extends INetworkScoreService.Stub { } // Pass the scores of each type down to the appropriate network scorer. - for (Map.Entry> entry : networksByType.entrySet()) { - INetworkScoreCache scoreCache = mScoreCaches.get(entry.getKey()); - if (scoreCache != null) { - try { - scoreCache.updateScores(entry.getValue()); - } catch (RemoteException e) { - if (Log.isLoggable(TAG, Log.VERBOSE)) { - Log.v(TAG, "Unable to update scores of type " + entry.getKey(), e); + for (final Map.Entry> entry : networksByType.entrySet()) { + final RemoteCallbackList callbackList; + final boolean isEmpty; + synchronized (mScoreCaches) { + callbackList = mScoreCaches.get(entry.getKey()); + isEmpty = callbackList == null || callbackList.getRegisteredCallbackCount() == 0; + } + if (isEmpty) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "No scorer registered for type " + entry.getKey() + ", discarding"); + } + continue; + } + + sendCallback(new Consumer() { + @Override + public void accept(INetworkScoreCache networkScoreCache) { + try { + networkScoreCache.updateScores(entry.getValue()); + } catch (RemoteException e) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "Unable to update scores of type " + entry.getKey(), e); + } } } - } else if (Log.isLoggable(TAG, Log.VERBOSE)) { - Log.v(TAG, "No scorer registered for type " + entry.getKey() + ", discarding"); - } + }, Collections.singleton(callbackList)); } return true; @@ -394,28 +410,52 @@ public class NetworkScoreService extends INetworkScoreService.Stub { /** Clear scores. Callers are responsible for checking permissions as appropriate. */ private void clearInternal() { - Set cachesToClear = getScoreCaches(); - - for (INetworkScoreCache scoreCache : cachesToClear) { - try { - scoreCache.clearScores(); - } catch (RemoteException e) { - if (Log.isLoggable(TAG, Log.VERBOSE)) { - Log.v(TAG, "Unable to clear scores", e); + sendCallback(new Consumer() { + @Override + public void accept(INetworkScoreCache networkScoreCache) { + try { + networkScoreCache.clearScores(); + } catch (RemoteException e) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "Unable to clear scores", e); + } } } - } + }, getScoreCacheLists()); } @Override public void registerNetworkScoreCache(int networkType, INetworkScoreCache scoreCache) { mContext.enforceCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED, TAG); synchronized (mScoreCaches) { - if (mScoreCaches.containsKey(networkType)) { - throw new IllegalArgumentException( - "Score cache already registered for type " + networkType); + RemoteCallbackList callbackList = mScoreCaches.get(networkType); + if (callbackList == null) { + callbackList = new RemoteCallbackList<>(); + mScoreCaches.put(networkType, callbackList); + } + if (!callbackList.register(scoreCache)) { + if (callbackList.getRegisteredCallbackCount() == 0) { + mScoreCaches.remove(networkType); + } + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "Unable to register NetworkScoreCache for type " + networkType); + } + } + } + } + + @Override + public void unregisterNetworkScoreCache(int networkType, INetworkScoreCache scoreCache) { + mContext.enforceCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED, TAG); + synchronized (mScoreCaches) { + RemoteCallbackList callbackList = mScoreCaches.get(networkType); + if (callbackList == null || !callbackList.unregister(scoreCache)) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "Unable to unregister NetworkScoreCache for type " + networkType); + } + } else if (callbackList.getRegisteredCallbackCount() == 0) { + mScoreCaches.remove(networkType); } - mScoreCaches.put(networkType, scoreCache); } } @@ -430,7 +470,7 @@ public class NetworkScoreService extends INetworkScoreService.Stub { } @Override - protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) { + protected void dump(final FileDescriptor fd, final PrintWriter writer, final String[] args) { mContext.enforceCallingOrSelfPermission(permission.DUMP, TAG); NetworkScorerAppData currentScorer = mNetworkScorerAppManager.getActiveScorer(); if (currentScorer == null) { @@ -439,13 +479,17 @@ public class NetworkScoreService extends INetworkScoreService.Stub { } writer.println("Current scorer: " + currentScorer.mPackageName); - for (INetworkScoreCache scoreCache : getScoreCaches()) { - try { - TransferPipe.dumpAsync(scoreCache.asBinder(), fd, args); - } catch (IOException | RemoteException e) { - writer.println("Failed to dump score cache: " + e); + sendCallback(new Consumer() { + @Override + public void accept(INetworkScoreCache networkScoreCache) { + try { + TransferPipe.dumpAsync(networkScoreCache.asBinder(), fd, args); + } catch (IOException | RemoteException e) { + writer.println("Failed to dump score cache: " + e); + } } - } + }, getScoreCacheLists()); + if (mServiceConnection != null) { mServiceConnection.dump(fd, writer, args); } else { @@ -455,14 +499,30 @@ public class NetworkScoreService extends INetworkScoreService.Stub { } /** - * Returns a set of all score caches that are currently active. + * Returns a {@link Collection} of all {@link RemoteCallbackList}s that are currently active. * *

May be used to perform an action on all score caches without potentially strange behavior * if a new scorer is registered during that action's execution. */ - private Set getScoreCaches() { + private Collection> getScoreCacheLists() { synchronized (mScoreCaches) { - return new HashSet<>(mScoreCaches.values()); + return new ArrayList<>(mScoreCaches.values()); + } + } + + private void sendCallback(Consumer consumer, + Collection> remoteCallbackLists) { + for (RemoteCallbackList 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)); + } + } finally { + callbackList.finishBroadcast(); + } + } } } diff --git a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java new file mode 100644 index 0000000000000..0139671cc6118 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java @@ -0,0 +1,422 @@ +/* + * Copyright (C) 2012 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.server; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +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.Mockito.doThrow; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import android.Manifest.permission; +import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.net.INetworkScoreCache; +import android.net.NetworkKey; +import android.net.NetworkScoreManager; +import android.net.NetworkScorerAppManager; +import android.net.NetworkScorerAppManager.NetworkScorerAppData; +import android.net.ScoredNetwork; +import android.net.WifiKey; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.UserHandle; +import android.provider.Settings; +import android.provider.Settings.Global; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.MediumTest; +import android.support.test.runner.AndroidJUnit4; + +import com.android.internal.R; +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; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Tests for {@link NetworkScoreService}. + */ +@RunWith(AndroidJUnit4.class) +@MediumTest +public class NetworkScoreServiceTest { + private static final ScoredNetwork SCORED_NETWORK = + new ScoredNetwork(new NetworkKey(new WifiKey("\"ssid\"", "00:00:00:00:00:00")), + null /* rssiCurve*/); + private static final NetworkScorerAppData PREV_SCORER = new NetworkScorerAppData( + "prevPackageName", 0, "prevScorerName", null /* configurationActivityClassName */, + "prevScoringServiceClass"); + private static final NetworkScorerAppData NEW_SCORER = new NetworkScorerAppData( + "newPackageName", 1, "newScorerName", null /* configurationActivityClassName */, + "newScoringServiceClass"); + + @Mock private PackageManager mPackageManager; + @Mock private NetworkScorerAppManager mNetworkScorerAppManager; + @Mock private Context mContext; + @Mock private Resources mResources; + @Mock private INetworkScoreCache.Stub mNetworkScoreCache, mNetworkScoreCache2; + @Mock private IBinder mIBinder, mIBinder2; + @Captor private ArgumentCaptor> mScoredNetworkCaptor; + + private ContentResolver mContentResolver; + private NetworkScoreService mNetworkScoreService; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + when(mNetworkScoreCache.asBinder()).thenReturn(mIBinder); + when(mNetworkScoreCache2.asBinder()).thenReturn(mIBinder2); + mContentResolver = InstrumentationRegistry.getContext().getContentResolver(); + when(mContext.getContentResolver()).thenReturn(mContentResolver); + when(mContext.getResources()).thenReturn(mResources); + mNetworkScoreService = new NetworkScoreService(mContext, mNetworkScorerAppManager); + } + + @Test + public void testSystemReady_networkScorerProvisioned() throws Exception { + Settings.Global.putInt(mContentResolver, Global.NETWORK_SCORING_PROVISIONED, 1); + + mNetworkScoreService.systemReady(); + + verify(mNetworkScorerAppManager, never()).setActiveScorer(anyString()); + } + + @Test + public void testSystemReady_networkScorerNotProvisioned_defaultScorer() throws Exception { + Settings.Global.putInt(mContentResolver, Global.NETWORK_SCORING_PROVISIONED, 0); + + when(mResources.getString(R.string.config_defaultNetworkScorerPackageName)) + .thenReturn(NEW_SCORER.mPackageName); + + mNetworkScoreService.systemReady(); + + verify(mNetworkScorerAppManager).setActiveScorer(NEW_SCORER.mPackageName); + assertEquals(1, + Settings.Global.getInt(mContentResolver, Global.NETWORK_SCORING_PROVISIONED)); + + } + + @Test + public void testSystemReady_networkScorerNotProvisioned_noDefaultScorer() throws Exception { + Settings.Global.putInt(mContentResolver, Global.NETWORK_SCORING_PROVISIONED, 0); + + when(mResources.getString(R.string.config_defaultNetworkScorerPackageName)) + .thenReturn(null); + + mNetworkScoreService.systemReady(); + + verify(mNetworkScorerAppManager, never()).setActiveScorer(anyString()); + assertEquals(1, + Settings.Global.getInt(mContentResolver, Global.NETWORK_SCORING_PROVISIONED)); + } + + @Test + public void testSystemRunning() { + when(mNetworkScorerAppManager.getActiveScorer()).thenReturn(NEW_SCORER); + + mNetworkScoreService.systemRunning(); + + verify(mContext).bindServiceAsUser(MockUtils.checkIntent(new Intent().setComponent( + new ComponentName(NEW_SCORER.mPackageName, NEW_SCORER.mScoringServiceClassName))), + any(ServiceConnection.class), + eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE), + eq(UserHandle.SYSTEM)); + } + + @Test + public void testUpdateScores_notActiveScorer() { + when(mNetworkScorerAppManager.isCallerActiveScorer(anyInt())).thenReturn(false); + + try { + mNetworkScoreService.updateScores(new ScoredNetwork[0]); + fail("SecurityException expected"); + } catch (SecurityException e) { + // expected + } + } + + @Test + public void testUpdateScores_oneRegisteredCache() throws RemoteException { + when(mNetworkScorerAppManager.isCallerActiveScorer(anyInt())).thenReturn(true); + + mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache); + + mNetworkScoreService.updateScores(new ScoredNetwork[]{SCORED_NETWORK}); + + verify(mNetworkScoreCache).updateScores(mScoredNetworkCaptor.capture()); + + assertEquals(1, mScoredNetworkCaptor.getValue().size()); + assertEquals(SCORED_NETWORK, mScoredNetworkCaptor.getValue().get(0)); + } + + @Test + public void testUpdateScores_twoRegisteredCaches() throws RemoteException { + when(mNetworkScorerAppManager.isCallerActiveScorer(anyInt())).thenReturn(true); + + mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache); + mNetworkScoreService.registerNetworkScoreCache( + NetworkKey.TYPE_WIFI, mNetworkScoreCache2); + + // updateScores should update both caches + mNetworkScoreService.updateScores(new ScoredNetwork[]{SCORED_NETWORK}); + + verify(mNetworkScoreCache).updateScores(anyListOf(ScoredNetwork.class)); + verify(mNetworkScoreCache2).updateScores(anyListOf(ScoredNetwork.class)); + + mNetworkScoreService.unregisterNetworkScoreCache( + NetworkKey.TYPE_WIFI, mNetworkScoreCache2); + + // updateScores should only update the first cache since the 2nd has been unregistered + mNetworkScoreService.updateScores(new ScoredNetwork[]{SCORED_NETWORK}); + + verify(mNetworkScoreCache, times(2)).updateScores(anyListOf(ScoredNetwork.class)); + + mNetworkScoreService.unregisterNetworkScoreCache( + NetworkKey.TYPE_WIFI, mNetworkScoreCache); + + // updateScores should not update any caches since they are both unregistered + mNetworkScoreService.updateScores(new ScoredNetwork[]{SCORED_NETWORK}); + + verifyNoMoreInteractions(mNetworkScoreCache, mNetworkScoreCache2); + } + + @Test + public void testClearScores_notActiveScorer_noBroadcastNetworkPermission() { + when(mNetworkScorerAppManager.isCallerActiveScorer(anyInt())).thenReturn(false); + when(mContext.checkCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED)) + .thenReturn(PackageManager.PERMISSION_DENIED); + try { + mNetworkScoreService.clearScores(); + fail("SecurityException expected"); + } catch (SecurityException e) { + // expected + } + } + + @Test + public void testClearScores_activeScorer_noBroadcastNetworkPermission() { + when(mNetworkScorerAppManager.isCallerActiveScorer(anyInt())).thenReturn(true); + when(mContext.checkCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED)) + .thenReturn(PackageManager.PERMISSION_DENIED); + + mNetworkScoreService.clearScores(); + } + + @Test + public void testClearScores_activeScorer() throws RemoteException { + when(mNetworkScorerAppManager.isCallerActiveScorer(anyInt())).thenReturn(true); + + mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache); + mNetworkScoreService.clearScores(); + + verify(mNetworkScoreCache).clearScores(); + } + + @Test + public void testClearScores_notActiveScorer_hasBroadcastNetworkPermission() + throws RemoteException { + when(mNetworkScorerAppManager.isCallerActiveScorer(anyInt())).thenReturn(false); + when(mContext.checkCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED)) + .thenReturn(PackageManager.PERMISSION_GRANTED); + + mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache); + mNetworkScoreService.clearScores(); + + verify(mNetworkScoreCache).clearScores(); + } + + @Test + public void testSetActiveScorer_noScoreNetworksPermission() { + doThrow(new SecurityException()).when(mContext) + .enforceCallingOrSelfPermission(eq(permission.SCORE_NETWORKS), anyString()); + + try { + mNetworkScoreService.setActiveScorer(null); + fail("SecurityException expected"); + } catch (SecurityException e) { + // expected + } + } + + @Test + public void testSetActiveScorer_failure() throws RemoteException { + when(mNetworkScorerAppManager.getActiveScorer()).thenReturn(PREV_SCORER); + when(mNetworkScorerAppManager.setActiveScorer(NEW_SCORER.mPackageName)).thenReturn(false); + mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache); + + boolean success = mNetworkScoreService.setActiveScorer(NEW_SCORER.mPackageName); + + assertFalse(success); + verify(mNetworkScoreCache).clearScores(); + verify(mContext).bindServiceAsUser(MockUtils.checkIntent(new Intent().setComponent( + new ComponentName(PREV_SCORER.mPackageName, PREV_SCORER.mScoringServiceClassName))), + any(ServiceConnection.class), + eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE), + eq(UserHandle.SYSTEM)); + } + + @Test + public void testSetActiveScorer_success() throws RemoteException { + when(mNetworkScorerAppManager.getActiveScorer()).thenReturn(PREV_SCORER, NEW_SCORER); + when(mNetworkScorerAppManager.setActiveScorer(NEW_SCORER.mPackageName)).thenReturn(true); + mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache); + + boolean success = mNetworkScoreService.setActiveScorer(NEW_SCORER.mPackageName); + + assertTrue(success); + verify(mNetworkScoreCache).clearScores(); + verify(mContext).bindServiceAsUser(MockUtils.checkIntent(new Intent().setComponent( + new ComponentName(NEW_SCORER.mPackageName, NEW_SCORER.mScoringServiceClassName))), + any(ServiceConnection.class), + eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE), + eq(UserHandle.SYSTEM)); + verify(mContext, times(2)).sendBroadcastAsUser( + MockUtils.checkIntentAction(NetworkScoreManager.ACTION_SCORER_CHANGED), + eq(UserHandle.SYSTEM)); + } + + @Test + public void testDisableScoring_notActiveScorer_noBroadcastNetworkPermission() { + when(mNetworkScorerAppManager.isCallerActiveScorer(anyInt())).thenReturn(false); + when(mContext.checkCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED)) + .thenReturn(PackageManager.PERMISSION_DENIED); + + try { + mNetworkScoreService.disableScoring(); + fail("SecurityException expected"); + } catch (SecurityException e) { + // expected + } + + } + + @Test + public void testDisableScoring_activeScorer() throws RemoteException { + when(mNetworkScorerAppManager.isCallerActiveScorer(anyInt())).thenReturn(true); + when(mNetworkScorerAppManager.getActiveScorer()).thenReturn(PREV_SCORER, null); + when(mNetworkScorerAppManager.setActiveScorer(null)).thenReturn(true); + mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache); + + mNetworkScoreService.disableScoring(); + + verify(mNetworkScoreCache).clearScores(); + verify(mContext).sendBroadcastAsUser( + MockUtils.checkIntent(new Intent(NetworkScoreManager.ACTION_SCORER_CHANGED) + .setPackage(PREV_SCORER.mPackageName)), + eq(UserHandle.SYSTEM)); + verify(mContext, never()).bindServiceAsUser(any(Intent.class), + any(ServiceConnection.class), anyInt(), any(UserHandle.class)); + } + + @Test + public void testDisableScoring_notActiveScorer_hasBroadcastNetworkPermission() + throws RemoteException { + when(mNetworkScorerAppManager.isCallerActiveScorer(anyInt())).thenReturn(false); + when(mContext.checkCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED)) + .thenReturn(PackageManager.PERMISSION_GRANTED); + when(mNetworkScorerAppManager.getActiveScorer()).thenReturn(PREV_SCORER, null); + when(mNetworkScorerAppManager.setActiveScorer(null)).thenReturn(true); + mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache); + + mNetworkScoreService.disableScoring(); + + verify(mNetworkScoreCache).clearScores(); + verify(mContext).sendBroadcastAsUser( + MockUtils.checkIntent(new Intent(NetworkScoreManager.ACTION_SCORER_CHANGED) + .setPackage(PREV_SCORER.mPackageName)), + eq(UserHandle.SYSTEM)); + verify(mContext, never()).bindServiceAsUser(any(Intent.class), + any(ServiceConnection.class), anyInt(), any(UserHandle.class)); + } + + @Test + public void testRegisterNetworkScoreCache_noBroadcastNetworkPermission() { + doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission( + eq(permission.BROADCAST_NETWORK_PRIVILEGED), anyString()); + + try { + mNetworkScoreService.registerNetworkScoreCache( + NetworkKey.TYPE_WIFI, mNetworkScoreCache); + fail("SecurityException expected"); + } catch (SecurityException e) { + // expected + } + } + + @Test + public void testUnregisterNetworkScoreCache_noBroadcastNetworkPermission() { + doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission( + eq(permission.BROADCAST_NETWORK_PRIVILEGED), anyString()); + + try { + mNetworkScoreService.unregisterNetworkScoreCache( + NetworkKey.TYPE_WIFI, mNetworkScoreCache); + fail("SecurityException expected"); + } catch (SecurityException e) { + // expected + } + } + + @Test + public void testDump_noDumpPermission() { + doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission( + eq(permission.DUMP), anyString()); + + try { + mNetworkScoreService.dump( + new FileDescriptor(), new PrintWriter(new StringWriter()), new String[0]); + fail("SecurityException expected"); + } catch (SecurityException e) { + // expected + } + } + + @Test + public void testDump_doesNotCrash() { + when(mNetworkScorerAppManager.getActiveScorer()).thenReturn(NEW_SCORER); + StringWriter stringWriter = new StringWriter(); + + mNetworkScoreService.dump( + new FileDescriptor(), new PrintWriter(stringWriter), new String[0]); + + assertFalse(stringWriter.toString().isEmpty()); + } +} diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java index 58db192f117a3..3806da6692973 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java @@ -82,6 +82,21 @@ public class MockUtils { return Mockito.argThat(m); } + public static Intent checkIntent(final Intent intent) { + final Matcher m = new BaseMatcher() { + @Override + public boolean matches(Object item) { + if (item == null) return false; + return intent.filterEquals((Intent) item); + } + @Override + public void describeTo(Description description) { + description.appendText(intent.toString()); + } + }; + return Mockito.argThat(m); + } + public static Bundle checkUserRestrictions(String... keys) { final Bundle expected = DpmTestUtils.newRestrictions(Preconditions.checkNotNull(keys)); final Matcher m = new BaseMatcher() {