Merge "Support multiple caches in NetworkScoreService." am: 78f3f0049e
am: a7c0b73971
Change-Id: Idf846f55436c44930f5db2a07b7362412274f46e
diff --git a/core/java/android/net/INetworkScoreService.aidl b/core/java/android/net/INetworkScoreService.aidl
index 59cbf6e..542a0a7 100644
--- a/core/java/android/net/INetworkScoreService.aidl
+++ b/core/java/android/net/INetworkScoreService.aidl
@@ -56,17 +56,26 @@
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 c301fe3..af21cef 100644
--- a/core/java/android/net/NetworkScoreManager.java
+++ b/core/java/android/net/NetworkScoreManager.java
@@ -279,6 +279,24 @@
}
/**
+ * 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.
*
* @param request a {@link RecommendationRequest} instance containing additional
diff --git a/services/core/java/com/android/server/NetworkScoreService.java b/services/core/java/com/android/server/NetworkScoreService.java
index 4c9ea58..6412e01 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.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.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 @@
private final Context mContext;
private final NetworkScorerAppManager mNetworkScorerAppManager;
- private final Map<Integer, INetworkScoreCache> mScoreCaches;
+ @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];
@@ -166,7 +169,7 @@
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 @@
}
// Separate networks by type.
- Map<Integer, List<ScoredNetwork>> networksByType = new HashMap<>();
+ Map<Integer, List<ScoredNetwork>> networksByType = new ArrayMap<>();
for (ScoredNetwork network : networks) {
List<ScoredNetwork> networkList = networksByType.get(network.networkKey.type);
if (networkList == null) {
@@ -287,19 +290,32 @@
}
// Pass the scores of each type down to the appropriate network scorer.
- for (Map.Entry<Integer, List<ScoredNetwork>> 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<Integer, List<ScoredNetwork>> entry : networksByType.entrySet()) {
+ final RemoteCallbackList<INetworkScoreCache> 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<INetworkScoreCache>() {
+ @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 @@
/** Clear scores. Callers are responsible for checking permissions as appropriate. */
private void clearInternal() {
- Set<INetworkScoreCache> 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<INetworkScoreCache>() {
+ @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<INetworkScoreCache> callbackList = mScoreCaches.get(networkType);
+ if (callbackList == null) {
+ callbackList = new RemoteCallbackList<>();
+ mScoreCaches.put(networkType, callbackList);
}
- mScoreCaches.put(networkType, scoreCache);
+ 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<INetworkScoreCache> 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);
+ }
}
}
@@ -430,7 +470,7 @@
}
@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 @@
}
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<INetworkScoreCache>() {
+ @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 @@
}
/**
- * Returns a set of all score caches that are currently active.
+ * Returns a {@link Collection} of all {@link RemoteCallbackList}s that are currently active.
*
* <p>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<INetworkScoreCache> getScoreCaches() {
+ private Collection<RemoteCallbackList<INetworkScoreCache>> getScoreCacheLists() {
synchronized (mScoreCaches) {
- return new HashSet<>(mScoreCaches.values());
+ return new ArrayList<>(mScoreCaches.values());
+ }
+ }
+
+ private void sendCallback(Consumer<INetworkScoreCache> 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));
+ }
+ } 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 0000000..0139671
--- /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<List<ScoredNetwork>> 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 58db192..3806da6 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 @@
return Mockito.argThat(m);
}
+ public static Intent checkIntent(final Intent intent) {
+ final Matcher<Intent> m = new BaseMatcher<Intent>() {
+ @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<Bundle> m = new BaseMatcher<Bundle>() {