Merge changes I6ff80178,I03875cf1,I8d210b68
* changes: New setting for recommendation request timeout. Implemented the async recommendation request call. Async network recommendation requests.
This commit is contained in:
@@ -21,6 +21,7 @@ import android.net.NetworkKey;
|
||||
import android.net.RecommendationRequest;
|
||||
import android.net.RecommendationResult;
|
||||
import android.net.ScoredNetwork;
|
||||
import android.os.RemoteCallback;
|
||||
|
||||
/**
|
||||
* A service for updating network scores from a network scorer application.
|
||||
@@ -117,4 +118,16 @@ interface INetworkScoreService
|
||||
* scorer.
|
||||
*/
|
||||
String getActiveScorerPackage();
|
||||
|
||||
/**
|
||||
* Request a recommendation for the best network to connect to
|
||||
* taking into account the inputs from the {@link RecommendationRequest}.
|
||||
*
|
||||
* @param request a {@link RecommendationRequest} instance containing the details of the request
|
||||
* @param remoteCallback a {@link RemoteCallback} instance to invoke when the recommendation
|
||||
* is available.
|
||||
* @throws SecurityException if the caller is not the system
|
||||
*/
|
||||
oneway void requestRecommendationAsync(in RecommendationRequest request,
|
||||
in RemoteCallback remoteCallback);
|
||||
}
|
||||
|
||||
@@ -16,17 +16,24 @@
|
||||
|
||||
package android.net;
|
||||
|
||||
import static android.net.NetworkRecommendationProvider.EXTRA_RECOMMENDATION_RESULT;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.SdkConstant;
|
||||
import android.annotation.SdkConstant.SdkConstantType;
|
||||
import android.annotation.SystemApi;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.NetworkScorerAppManager.NetworkScorerAppData;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteCallback;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
import com.android.internal.util.Preconditions;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
@@ -356,4 +363,53 @@ public class NetworkScoreManager {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Request a recommendation for which network to connect to.
|
||||
*
|
||||
* <p>The callback will be run on the thread associated with provided {@link Handler}.
|
||||
*
|
||||
* @param request a {@link RecommendationRequest} instance containing additional
|
||||
* request details
|
||||
* @param callback a {@link RecommendationCallback} instance that will be invoked when
|
||||
* the {@link RecommendationResult} is available
|
||||
* @param handler a {@link Handler} instance representing the thread to run the callback on.
|
||||
* @throws SecurityException
|
||||
* @hide
|
||||
*/
|
||||
public void requestRecommendation(
|
||||
final @NonNull RecommendationRequest request,
|
||||
final @NonNull RecommendationCallback callback,
|
||||
final @NonNull Handler handler) {
|
||||
Preconditions.checkNotNull(request, "RecommendationRequest cannot be null.");
|
||||
Preconditions.checkNotNull(callback, "RecommendationCallback cannot be null.");
|
||||
Preconditions.checkNotNull(handler, "Handler cannot be null.");
|
||||
|
||||
RemoteCallback remoteCallback = new RemoteCallback(new RemoteCallback.OnResultListener() {
|
||||
@Override
|
||||
public void onResult(Bundle data) {
|
||||
RecommendationResult result = data.getParcelable(EXTRA_RECOMMENDATION_RESULT);
|
||||
callback.onRecommendationAvailable(result);
|
||||
}
|
||||
}, handler);
|
||||
|
||||
try {
|
||||
mService.requestRecommendationAsync(request, remoteCallback);
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callers of {@link #requestRecommendation(RecommendationRequest, RecommendationCallback, Handler)}
|
||||
* must pass in an implementation of this class.
|
||||
* @hide
|
||||
*/
|
||||
public abstract static class RecommendationCallback {
|
||||
/**
|
||||
* Invoked when a {@link RecommendationResult} is available.
|
||||
* @param result a {@link RecommendationResult} instance.
|
||||
*/
|
||||
public abstract void onRecommendationAvailable(RecommendationResult result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7672,6 +7672,16 @@ public final class Settings {
|
||||
public static final String NETWORK_RECOMMENDATIONS_ENABLED =
|
||||
"network_recommendations_enabled";
|
||||
|
||||
/**
|
||||
* The number of milliseconds the {@link com.android.server.NetworkScoreService}
|
||||
* will give a recommendation request to complete before returning a default response.
|
||||
*
|
||||
* Type: long
|
||||
* @hide
|
||||
*/
|
||||
public static final String NETWORK_RECOMMENDATION_REQUEST_TIMEOUT_MS =
|
||||
"network_recommendation_request_timeout_ms";
|
||||
|
||||
/**
|
||||
* Settings to allow BLE scans to be enabled even when Bluetooth is turned off for
|
||||
* connectivity.
|
||||
|
||||
@@ -42,15 +42,22 @@ import android.net.RecommendationResult;
|
||||
import android.net.ScoredNetwork;
|
||||
import android.net.Uri;
|
||||
import android.os.Binder;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.IRemoteCallback;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.RemoteCallback;
|
||||
import android.os.RemoteCallbackList;
|
||||
import android.os.RemoteException;
|
||||
import android.os.UserHandle;
|
||||
import android.provider.Settings;
|
||||
import android.provider.Settings.Global;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.util.TimedRemoteCaller;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
@@ -67,6 +74,7 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
@@ -75,7 +83,7 @@ import java.util.function.Consumer;
|
||||
*/
|
||||
public class NetworkScoreService extends INetworkScoreService.Stub {
|
||||
private static final String TAG = "NetworkScoreService";
|
||||
private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
|
||||
private static final boolean DBG = Build.IS_DEBUGGABLE && Log.isLoggable(TAG, Log.DEBUG);
|
||||
|
||||
private final Context mContext;
|
||||
private final NetworkScorerAppManager mNetworkScorerAppManager;
|
||||
@@ -83,13 +91,16 @@ public class NetworkScoreService extends INetworkScoreService.Stub {
|
||||
@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];
|
||||
private final Object mPackageMonitorLock = new Object();
|
||||
private final Object mServiceConnectionLock = new Object();
|
||||
private final Handler mHandler;
|
||||
private final DispatchingContentObserver mContentObserver;
|
||||
|
||||
@GuardedBy("mPackageMonitorLock")
|
||||
private NetworkScorerPackageMonitor mPackageMonitor;
|
||||
@GuardedBy("mServiceConnectionLock")
|
||||
private ScoringServiceConnection mServiceConnection;
|
||||
private volatile long mRecommendationRequestTimeoutMs;
|
||||
|
||||
private BroadcastReceiver mUserIntentReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
@@ -185,12 +196,25 @@ public class NetworkScoreService extends INetworkScoreService.Stub {
|
||||
}
|
||||
|
||||
/**
|
||||
* Reevaluates the service binding when the Settings toggle is changed.
|
||||
* Dispatches observed content changes to a handler for further processing.
|
||||
*/
|
||||
private class SettingsObserver extends ContentObserver {
|
||||
@VisibleForTesting
|
||||
public static class DispatchingContentObserver extends ContentObserver {
|
||||
final private Map<Uri, Integer> mUriEventMap;
|
||||
final private Context mContext;
|
||||
final private Handler mHandler;
|
||||
|
||||
public SettingsObserver() {
|
||||
super(null /*handler*/);
|
||||
public DispatchingContentObserver(Context context, Handler handler) {
|
||||
super(handler);
|
||||
mContext = context;
|
||||
mHandler = handler;
|
||||
mUriEventMap = new ArrayMap<>();
|
||||
}
|
||||
|
||||
void observe(Uri uri, int what) {
|
||||
mUriEventMap.put(uri, what);
|
||||
final ContentResolver resolver = mContext.getContentResolver();
|
||||
resolver.registerContentObserver(uri, false /*notifyForDescendants*/, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -201,16 +225,22 @@ public class NetworkScoreService extends INetworkScoreService.Stub {
|
||||
@Override
|
||||
public void onChange(boolean selfChange, Uri uri) {
|
||||
if (DBG) Log.d(TAG, String.format("onChange(%s, %s)", selfChange, uri));
|
||||
bindToScoringServiceIfNeeded();
|
||||
final Integer what = mUriEventMap.get(uri);
|
||||
if (what != null) {
|
||||
mHandler.obtainMessage(what).sendToTarget();
|
||||
} else {
|
||||
Log.w(TAG, "No matching event to send for URI = " + uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public NetworkScoreService(Context context) {
|
||||
this(context, new NetworkScorerAppManager(context));
|
||||
this(context, new NetworkScorerAppManager(context), Looper.myLooper());
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
NetworkScoreService(Context context, NetworkScorerAppManager networkScoreAppManager) {
|
||||
NetworkScoreService(Context context, NetworkScorerAppManager networkScoreAppManager,
|
||||
Looper looper) {
|
||||
mContext = context;
|
||||
mNetworkScorerAppManager = networkScoreAppManager;
|
||||
mScoreCaches = new ArrayMap<>();
|
||||
@@ -219,16 +249,19 @@ 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);
|
||||
mRecommendationRequestTimeoutMs = TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS;
|
||||
mHandler = new ServiceHandler(looper);
|
||||
mContentObserver = new DispatchingContentObserver(context, mHandler);
|
||||
}
|
||||
|
||||
/** Called when the system is ready to run third-party code but before it actually does so. */
|
||||
void systemReady() {
|
||||
if (DBG) Log.d(TAG, "systemReady");
|
||||
registerPackageMonitorIfNeeded();
|
||||
registerRecommendationSettingObserverIfNeeded();
|
||||
registerRecommendationSettingsObserver();
|
||||
refreshRecommendationRequestTimeoutMs();
|
||||
}
|
||||
|
||||
/** Called when the system is ready for us to start third-party code. */
|
||||
@@ -242,14 +275,18 @@ public class NetworkScoreService extends INetworkScoreService.Stub {
|
||||
bindToScoringServiceIfNeeded();
|
||||
}
|
||||
|
||||
private void registerRecommendationSettingObserverIfNeeded() {
|
||||
private void registerRecommendationSettingsObserver() {
|
||||
final List<String> providerPackages =
|
||||
mNetworkScorerAppManager.getPotentialRecommendationProviderPackages();
|
||||
if (!providerPackages.isEmpty()) {
|
||||
final ContentResolver resolver = mContext.getContentResolver();
|
||||
final Uri uri = Global.getUriFor(Global.NETWORK_RECOMMENDATIONS_ENABLED);
|
||||
resolver.registerContentObserver(uri, false, new SettingsObserver());
|
||||
final Uri enabledUri = Global.getUriFor(Global.NETWORK_RECOMMENDATIONS_ENABLED);
|
||||
mContentObserver.observe(enabledUri,
|
||||
ServiceHandler.MSG_RECOMMENDATIONS_ENABLED_CHANGED);
|
||||
}
|
||||
|
||||
final Uri timeoutUri = Global.getUriFor(Global.NETWORK_RECOMMENDATION_REQUEST_TIMEOUT_MS);
|
||||
mContentObserver.observe(timeoutUri,
|
||||
ServiceHandler.MSG_RECOMMENDATION_REQUEST_TIMEOUT_CHANGED);
|
||||
}
|
||||
|
||||
private void registerPackageMonitorIfNeeded() {
|
||||
@@ -553,6 +590,56 @@ public class NetworkScoreService extends INetworkScoreService.Stub {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Request a recommendation for the best network to connect to
|
||||
* taking into account the inputs from the {@link RecommendationRequest}.
|
||||
*
|
||||
* @param request a {@link RecommendationRequest} instance containing the details of the request
|
||||
* @param remoteCallback a {@link IRemoteCallback} instance to invoke when the recommendation
|
||||
* is available.
|
||||
* @throws SecurityException if the caller is not the system
|
||||
*/
|
||||
@Override
|
||||
public void requestRecommendationAsync(RecommendationRequest request,
|
||||
RemoteCallback remoteCallback) {
|
||||
mContext.enforceCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED, TAG);
|
||||
|
||||
final OneTimeCallback oneTimeCallback = new OneTimeCallback(remoteCallback);
|
||||
final Pair<RecommendationRequest, OneTimeCallback> pair =
|
||||
Pair.create(request, oneTimeCallback);
|
||||
final Message timeoutMsg = mHandler.obtainMessage(
|
||||
ServiceHandler.MSG_RECOMMENDATION_REQUEST_TIMEOUT, pair);
|
||||
final INetworkRecommendationProvider provider = getRecommendationProvider();
|
||||
final long token = Binder.clearCallingIdentity();
|
||||
try {
|
||||
if (provider != null) {
|
||||
try {
|
||||
mHandler.sendMessageDelayed(timeoutMsg, mRecommendationRequestTimeoutMs);
|
||||
provider.requestRecommendation(request, new IRemoteCallback.Stub() {
|
||||
@Override
|
||||
public void sendResult(Bundle data) throws RemoteException {
|
||||
// Remove the timeout message
|
||||
mHandler.removeMessages(timeoutMsg.what, pair);
|
||||
oneTimeCallback.sendResult(data);
|
||||
}
|
||||
}, 0 /*sequence*/);
|
||||
return;
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Failed to request a recommendation.", e);
|
||||
// TODO(jjoslin): 12/15/16 - Keep track of failures.
|
||||
// Remove the timeout message
|
||||
mHandler.removeMessages(timeoutMsg.what, pair);
|
||||
// Will fall through and send back the default recommendation.
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(token);
|
||||
}
|
||||
|
||||
// Else send back the default recommendation.
|
||||
sendDefaultRecommendationResponse(request, oneTimeCallback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requestScores(NetworkKey[] networks) {
|
||||
mContext.enforceCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES, TAG);
|
||||
@@ -651,6 +738,18 @@ public class NetworkScoreService extends INetworkScoreService.Stub {
|
||||
return null;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void refreshRecommendationRequestTimeoutMs() {
|
||||
final ContentResolver cr = mContext.getContentResolver();
|
||||
long timeoutMs = Settings.Global.getLong(cr,
|
||||
Global.NETWORK_RECOMMENDATION_REQUEST_TIMEOUT_MS, -1L /*default*/);
|
||||
if (timeoutMs < 0) {
|
||||
timeoutMs = TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS;
|
||||
}
|
||||
if (DBG) Log.d(TAG, "Updating the recommendation request timeout to " + timeoutMs + " ms");
|
||||
mRecommendationRequestTimeoutMs = timeoutMs;
|
||||
}
|
||||
|
||||
private static class ScoringServiceConnection implements ServiceConnection {
|
||||
private final ComponentName mComponentName;
|
||||
private final int mScoringAppUid;
|
||||
@@ -756,4 +855,83 @@ public class NetworkScoreService extends INetworkScoreService.Stub {
|
||||
return getResultTimed(sequence);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A wrapper around {@link RemoteCallback} that guarantees
|
||||
* {@link RemoteCallback#sendResult(Bundle)} will be invoked at most once.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public static final class OneTimeCallback {
|
||||
private final RemoteCallback mRemoteCallback;
|
||||
private final AtomicBoolean mCallbackRun;
|
||||
|
||||
public OneTimeCallback(RemoteCallback remoteCallback) {
|
||||
mRemoteCallback = remoteCallback;
|
||||
mCallbackRun = new AtomicBoolean(false);
|
||||
}
|
||||
|
||||
public void sendResult(Bundle data) {
|
||||
if (mCallbackRun.compareAndSet(false, true)) {
|
||||
mRemoteCallback.sendResult(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void sendDefaultRecommendationResponse(RecommendationRequest request,
|
||||
OneTimeCallback remoteCallback) {
|
||||
if (DBG) {
|
||||
Log.d(TAG, "Returning the default network recommendation.");
|
||||
}
|
||||
|
||||
final RecommendationResult result;
|
||||
if (request != null && request.getCurrentSelectedConfig() != null) {
|
||||
result = RecommendationResult.createConnectRecommendation(
|
||||
request.getCurrentSelectedConfig());
|
||||
} else {
|
||||
result = RecommendationResult.createDoNotConnectRecommendation();
|
||||
}
|
||||
|
||||
final Bundle data = new Bundle();
|
||||
data.putParcelable(EXTRA_RECOMMENDATION_RESULT, result);
|
||||
remoteCallback.sendResult(data);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public final class ServiceHandler extends Handler {
|
||||
public static final int MSG_RECOMMENDATION_REQUEST_TIMEOUT = 1;
|
||||
public static final int MSG_RECOMMENDATIONS_ENABLED_CHANGED = 2;
|
||||
public static final int MSG_RECOMMENDATION_REQUEST_TIMEOUT_CHANGED = 3;
|
||||
|
||||
public ServiceHandler(Looper looper) {
|
||||
super(looper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
final int what = msg.what;
|
||||
switch (what) {
|
||||
case MSG_RECOMMENDATION_REQUEST_TIMEOUT:
|
||||
if (DBG) {
|
||||
Log.d(TAG, "Network recommendation request timed out.");
|
||||
}
|
||||
final Pair<RecommendationRequest, OneTimeCallback> pair =
|
||||
(Pair<RecommendationRequest, OneTimeCallback>) msg.obj;
|
||||
final RecommendationRequest request = pair.first;
|
||||
final OneTimeCallback remoteCallback = pair.second;
|
||||
sendDefaultRecommendationResponse(request, remoteCallback);
|
||||
break;
|
||||
|
||||
case MSG_RECOMMENDATIONS_ENABLED_CHANGED:
|
||||
bindToScoringServiceIfNeeded();
|
||||
break;
|
||||
|
||||
case MSG_RECOMMENDATION_REQUEST_TIMEOUT_CHANGED:
|
||||
refreshRecommendationRequestTimeoutMs();
|
||||
break;
|
||||
|
||||
default:
|
||||
Log.w(TAG,"Unknown message: " + what);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import static junit.framework.Assert.assertEquals;
|
||||
import static junit.framework.Assert.assertFalse;
|
||||
import static junit.framework.Assert.assertNotNull;
|
||||
import static junit.framework.Assert.assertNull;
|
||||
import static junit.framework.Assert.assertSame;
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
import static junit.framework.Assert.fail;
|
||||
|
||||
@@ -60,21 +61,28 @@ import android.net.NetworkScorerAppManager.NetworkScorerAppData;
|
||||
import android.net.RecommendationRequest;
|
||||
import android.net.RecommendationResult;
|
||||
import android.net.ScoredNetwork;
|
||||
import android.net.Uri;
|
||||
import android.net.WifiKey;
|
||||
import android.net.wifi.WifiConfiguration;
|
||||
import android.os.Binder;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.IBinder;
|
||||
import android.os.IRemoteCallback;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.RemoteCallback;
|
||||
import android.os.RemoteException;
|
||||
import android.os.UserHandle;
|
||||
import android.provider.Settings;
|
||||
import android.support.test.InstrumentationRegistry;
|
||||
import android.support.test.filters.MediumTest;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.server.devicepolicy.MockUtils;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
@@ -89,6 +97,8 @@ import java.io.FileDescriptor;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Tests for {@link NetworkScoreService}.
|
||||
@@ -114,6 +124,9 @@ public class NetworkScoreServiceTest {
|
||||
private ContentResolver mContentResolver;
|
||||
private NetworkScoreService mNetworkScoreService;
|
||||
private RecommendationRequest mRecommendationRequest;
|
||||
private RemoteCallback mRemoteCallback;
|
||||
private OnResultListener mOnResultListener;
|
||||
private HandlerThread mHandlerThread;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
@@ -123,12 +136,25 @@ public class NetworkScoreServiceTest {
|
||||
mContentResolver = InstrumentationRegistry.getContext().getContentResolver();
|
||||
when(mContext.getContentResolver()).thenReturn(mContentResolver);
|
||||
when(mContext.getResources()).thenReturn(mResources);
|
||||
mNetworkScoreService = new NetworkScoreService(mContext, mNetworkScorerAppManager);
|
||||
mHandlerThread = new HandlerThread("NetworkScoreServiceTest");
|
||||
mHandlerThread.start();
|
||||
mNetworkScoreService = new NetworkScoreService(mContext, mNetworkScorerAppManager,
|
||||
mHandlerThread.getLooper());
|
||||
WifiConfiguration configuration = new WifiConfiguration();
|
||||
configuration.SSID = "NetworkScoreServiceTest_SSID";
|
||||
configuration.BSSID = "NetworkScoreServiceTest_BSSID";
|
||||
mRecommendationRequest = new RecommendationRequest.Builder()
|
||||
.setCurrentRecommendedWifiConfig(configuration).build();
|
||||
mOnResultListener = new OnResultListener();
|
||||
mRemoteCallback = new RemoteCallback(mOnResultListener);
|
||||
Settings.Global.putLong(mContentResolver,
|
||||
Settings.Global.NETWORK_RECOMMENDATION_REQUEST_TIMEOUT_MS, -1L);
|
||||
mNetworkScoreService.refreshRecommendationRequestTimeoutMs();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
mHandlerThread.quitSafely();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -261,6 +287,116 @@ public class NetworkScoreServiceTest {
|
||||
result.getWifiConfiguration().BSSID);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRequestRecommendationAsync_noPermission() throws Exception {
|
||||
doThrow(new SecurityException()).when(mContext)
|
||||
.enforceCallingOrSelfPermission(eq(permission.BROADCAST_NETWORK_PRIVILEGED),
|
||||
anyString());
|
||||
try {
|
||||
mNetworkScoreService.requestRecommendationAsync(mRecommendationRequest,
|
||||
mRemoteCallback);
|
||||
fail("BROADCAST_NETWORK_PRIVILEGED not enforced.");
|
||||
} catch (SecurityException e) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRequestRecommendationAsync_providerNotConnected() throws Exception {
|
||||
mNetworkScoreService.requestRecommendationAsync(mRecommendationRequest,
|
||||
mRemoteCallback);
|
||||
boolean callbackRan = mOnResultListener.countDownLatch.await(3, TimeUnit.SECONDS);
|
||||
assertTrue(callbackRan);
|
||||
verifyZeroInteractions(mRecommendationProvider);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRequestRecommendationAsync_requestTimesOut() throws Exception {
|
||||
injectProvider();
|
||||
Settings.Global.putLong(mContentResolver,
|
||||
Settings.Global.NETWORK_RECOMMENDATION_REQUEST_TIMEOUT_MS, 1L);
|
||||
mNetworkScoreService.refreshRecommendationRequestTimeoutMs();
|
||||
mNetworkScoreService.requestRecommendationAsync(mRecommendationRequest,
|
||||
mRemoteCallback);
|
||||
boolean callbackRan = mOnResultListener.countDownLatch.await(3, TimeUnit.SECONDS);
|
||||
assertTrue(callbackRan);
|
||||
verify(mRecommendationProvider).requestRecommendation(eq(mRecommendationRequest),
|
||||
isA(IRemoteCallback.Stub.class), anyInt());
|
||||
|
||||
assertTrue(mOnResultListener.receivedBundle.containsKey(EXTRA_RECOMMENDATION_RESULT));
|
||||
RecommendationResult result =
|
||||
mOnResultListener.receivedBundle.getParcelable(EXTRA_RECOMMENDATION_RESULT);
|
||||
assertTrue(result.hasRecommendation());
|
||||
assertEquals(mRecommendationRequest.getCurrentSelectedConfig().SSID,
|
||||
result.getWifiConfiguration().SSID);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRequestRecommendationAsync_requestSucceeds() throws Exception {
|
||||
injectProvider();
|
||||
final Bundle bundle = new Bundle();
|
||||
doAnswer(invocation -> {
|
||||
invocation.getArgumentAt(1, IRemoteCallback.class).sendResult(bundle);
|
||||
return null;
|
||||
}).when(mRecommendationProvider)
|
||||
.requestRecommendation(eq(mRecommendationRequest), isA(IRemoteCallback.class),
|
||||
anyInt());
|
||||
|
||||
mNetworkScoreService.requestRecommendationAsync(mRecommendationRequest,
|
||||
mRemoteCallback);
|
||||
boolean callbackRan = mOnResultListener.countDownLatch.await(3, TimeUnit.SECONDS);
|
||||
assertTrue(callbackRan);
|
||||
// If it's not the same instance then something else ran the callback.
|
||||
assertSame(bundle, mOnResultListener.receivedBundle);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRequestRecommendationAsync_requestThrowsRemoteException() throws Exception {
|
||||
injectProvider();
|
||||
doThrow(new RemoteException()).when(mRecommendationProvider)
|
||||
.requestRecommendation(eq(mRecommendationRequest), isA(IRemoteCallback.class),
|
||||
anyInt());
|
||||
|
||||
mNetworkScoreService.requestRecommendationAsync(mRecommendationRequest,
|
||||
mRemoteCallback);
|
||||
boolean callbackRan = mOnResultListener.countDownLatch.await(3, TimeUnit.SECONDS);
|
||||
assertTrue(callbackRan);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dispatchingContentObserver_nullUri() throws Exception {
|
||||
NetworkScoreService.DispatchingContentObserver observer =
|
||||
new NetworkScoreService.DispatchingContentObserver(mContext, null /*handler*/);
|
||||
|
||||
observer.onChange(false, null);
|
||||
// nothing to assert or verify but since we passed in a null handler we'd see a NPE
|
||||
// if it were interacted with.
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dispatchingContentObserver_dispatchUri() throws Exception {
|
||||
final CountDownHandler handler = new CountDownHandler(mHandlerThread.getLooper());
|
||||
NetworkScoreService.DispatchingContentObserver observer =
|
||||
new NetworkScoreService.DispatchingContentObserver(mContext, handler);
|
||||
Uri uri = Uri.parse("content://settings/global/network_score_service_test");
|
||||
int expectedWhat = 24;
|
||||
observer.observe(uri, expectedWhat);
|
||||
|
||||
observer.onChange(false, uri);
|
||||
final boolean msgHandled = handler.latch.await(3, TimeUnit.SECONDS);
|
||||
assertTrue(msgHandled);
|
||||
assertEquals(expectedWhat, handler.receivedWhat);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void oneTimeCallback_multipleCallbacks() throws Exception {
|
||||
NetworkScoreService.OneTimeCallback callback =
|
||||
new NetworkScoreService.OneTimeCallback(mRemoteCallback);
|
||||
callback.sendResult(null);
|
||||
callback.sendResult(null);
|
||||
assertEquals(1, mOnResultListener.resultCount);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateScores_notActiveScorer() {
|
||||
bindToScorer(false /*callerIsScorer*/);
|
||||
@@ -515,4 +651,32 @@ public class NetworkScoreServiceTest {
|
||||
isA(UserHandle.class))).thenReturn(true);
|
||||
mNetworkScoreService.systemRunning();
|
||||
}
|
||||
|
||||
private static class OnResultListener implements RemoteCallback.OnResultListener {
|
||||
private final CountDownLatch countDownLatch = new CountDownLatch(1);
|
||||
private int resultCount;
|
||||
private Bundle receivedBundle;
|
||||
|
||||
@Override
|
||||
public void onResult(Bundle result) {
|
||||
countDownLatch.countDown();
|
||||
resultCount++;
|
||||
receivedBundle = result;
|
||||
}
|
||||
}
|
||||
|
||||
private static class CountDownHandler extends Handler {
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
int receivedWhat;
|
||||
|
||||
CountDownHandler(Looper looper) {
|
||||
super(looper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
latch.countDown();
|
||||
receivedWhat = msg.what;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user