Merge "Make the onRequestRecommendation() method async."

This commit is contained in:
Treehugger Robot
2016-12-13 16:04:35 +00:00
committed by Gerrit Code Review
3 changed files with 190 additions and 15 deletions

View File

@@ -25609,11 +25609,15 @@ package android.net {
public abstract class NetworkRecommendationProvider {
ctor public NetworkRecommendationProvider(android.os.Handler);
method public final android.os.IBinder getBinder();
method public abstract android.net.RecommendationResult onRequestRecommendation(android.net.RecommendationRequest);
method public abstract void onRequestRecommendation(android.net.RecommendationRequest, android.net.NetworkRecommendationProvider.ResultCallback);
field public static final java.lang.String EXTRA_RECOMMENDATION_RESULT = "android.net.extra.RECOMMENDATION_RESULT";
field public static final java.lang.String EXTRA_SEQUENCE = "android.net.extra.SEQUENCE";
}
public static final class NetworkRecommendationProvider.ResultCallback {
method public void onResult(android.net.RecommendationResult);
}
public class NetworkRequest implements android.os.Parcelable {
method public int describeContents();
method public void writeToParcel(android.os.Parcel, int);

View File

@@ -10,6 +10,11 @@ import android.os.Message;
import android.os.RemoteException;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* The base class for implementing a network recommendation provider.
* @hide
@@ -42,11 +47,12 @@ public abstract class NetworkRecommendationProvider {
*
* @param request a {@link RecommendationRequest} instance containing additional
* request details
* @return a {@link RecommendationResult} instance containing the recommended
* network to connect to
* @param callback a {@link ResultCallback} instance. When a {@link RecommendationResult} is
* available it must be passed into
* {@link ResultCallback#onResult(RecommendationResult)}.
*/
public abstract RecommendationResult onRequestRecommendation(RecommendationRequest request);
public abstract void onRequestRecommendation(RecommendationRequest request,
ResultCallback callback);
/**
* Services that can handle {@link NetworkScoreManager#ACTION_RECOMMEND_NETWORKS} should
@@ -56,6 +62,60 @@ public abstract class NetworkRecommendationProvider {
return mService;
}
/**
* A callback implementing applications should invoke when a {@link RecommendationResult}
* is available.
*/
public static final class ResultCallback {
private final IRemoteCallback mCallback;
private final int mSequence;
private final AtomicBoolean mCallbackRun;
/**
* @hide
*/
@VisibleForTesting
public ResultCallback(IRemoteCallback callback, int sequence) {
mCallback = callback;
mSequence = sequence;
mCallbackRun = new AtomicBoolean(false);
}
/**
* Run the callback with the available {@link RecommendationResult}.
* @param result a {@link RecommendationResult} instance.
*/
public void onResult(RecommendationResult result) {
if (!mCallbackRun.compareAndSet(false, true)) {
throw new IllegalStateException("The callback cannot be run more than once.");
}
final Bundle data = new Bundle();
data.putInt(EXTRA_SEQUENCE, mSequence);
data.putParcelable(EXTRA_RECOMMENDATION_RESULT, result);
try {
mCallback.sendResult(data);
} catch (RemoteException e) {
Log.w(TAG, "Callback failed for seq: " + mSequence, e);
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ResultCallback that = (ResultCallback) o;
return mSequence == that.mSequence
&& Objects.equals(mCallback, that.mCallback);
}
@Override
public int hashCode() {
return Objects.hash(mCallback, mSequence);
}
}
private final class ServiceHandler extends Handler {
static final int MSG_GET_RECOMMENDATION = 1;
@@ -72,16 +132,8 @@ public abstract class NetworkRecommendationProvider {
final int seq = msg.arg1;
final RecommendationRequest request =
msg.getData().getParcelable(EXTRA_RECOMMENDATION_REQUEST);
final RecommendationResult result = onRequestRecommendation(request);
final Bundle data = new Bundle();
data.putInt(EXTRA_SEQUENCE, seq);
data.putParcelable(EXTRA_RECOMMENDATION_RESULT, result);
try {
callback.sendResult(data);
} catch (RemoteException e) {
Log.w(TAG, "Callback failed for seq: " + seq, e);
}
final ResultCallback resultCallback = new ResultCallback(callback, seq);
onRequestRecommendation(request, resultCallback);
break;
default:

View File

@@ -0,0 +1,119 @@
package android.net;
import static android.net.NetworkRecommendationProvider.EXTRA_RECOMMENDATION_RESULT;
import static android.net.NetworkRecommendationProvider.EXTRA_SEQUENCE;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IRemoteCallback;
import android.test.InstrumentationTestCase;
import android.test.suitebuilder.annotation.MediumTest;
import android.test.suitebuilder.annotation.SmallTest;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* Unit test for the {@link NetworkRecommendationProvider}.
*/
public class NetworkRecommendationProviderTest extends InstrumentationTestCase {
@Mock private IRemoteCallback mMockRemoteCallback;
private NetworkRecProvider mRecProvider;
private Handler mHandler;
private INetworkRecommendationProvider mStub;
private CountDownLatch mCountDownLatch;
@Override
public void setUp() throws Exception {
super.setUp();
// Configuration needed to make mockito/dexcache work.
final Context context = getInstrumentation().getTargetContext();
System.setProperty("dexmaker.dexcache",
context.getCacheDir().getPath());
ClassLoader newClassLoader = getInstrumentation().getClass().getClassLoader();
Thread.currentThread().setContextClassLoader(newClassLoader);
MockitoAnnotations.initMocks(this);
HandlerThread thread = new HandlerThread("NetworkRecommendationProviderTest");
thread.start();
mCountDownLatch = new CountDownLatch(1);
mHandler = new Handler(thread.getLooper());
mRecProvider = new NetworkRecProvider(mHandler, mCountDownLatch);
mStub = INetworkRecommendationProvider.Stub.asInterface(mRecProvider.getBinder());
}
@MediumTest
public void testRequestReceived() throws Exception {
final RecommendationRequest request = new RecommendationRequest.Builder().build();
final int sequence = 100;
mStub.requestRecommendation(request, mMockRemoteCallback, sequence);
// wait for onRequestRecommendation() to be called in our impl below.
mCountDownLatch.await(200, TimeUnit.MILLISECONDS);
NetworkRecommendationProvider.ResultCallback expectedResultCallback =
new NetworkRecommendationProvider.ResultCallback(mMockRemoteCallback, sequence);
assertEquals(request, mRecProvider.mCapturedRequest);
assertEquals(expectedResultCallback, mRecProvider.mCapturedCallback);
}
@SmallTest
public void testResultCallbackOnResult() throws Exception {
final int sequence = 100;
final NetworkRecommendationProvider.ResultCallback callback =
new NetworkRecommendationProvider.ResultCallback(mMockRemoteCallback, sequence);
final RecommendationResult result = new RecommendationResult(null);
callback.onResult(result);
final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
Mockito.verify(mMockRemoteCallback).sendResult(bundleCaptor.capture());
Bundle capturedBundle = bundleCaptor.getValue();
assertEquals(sequence, capturedBundle.getInt(EXTRA_SEQUENCE));
assertSame(result, capturedBundle.getParcelable(EXTRA_RECOMMENDATION_RESULT));
}
@SmallTest
public void testResultCallbackOnResult_runTwice_throwsException() throws Exception {
final int sequence = 100;
final NetworkRecommendationProvider.ResultCallback callback =
new NetworkRecommendationProvider.ResultCallback(mMockRemoteCallback, sequence);
final RecommendationResult result = new RecommendationResult(null);
callback.onResult(result);
try {
callback.onResult(result);
fail("Callback ran more than once.");
} catch (IllegalStateException e) {
// expected
}
}
private static class NetworkRecProvider extends NetworkRecommendationProvider {
private final CountDownLatch mCountDownLatch;
RecommendationRequest mCapturedRequest;
ResultCallback mCapturedCallback;
NetworkRecProvider(Handler handler, CountDownLatch countDownLatch) {
super(handler);
mCountDownLatch = countDownLatch;
}
@Override
public void onRequestRecommendation(RecommendationRequest request,
ResultCallback callback) {
mCapturedRequest = request;
mCapturedCallback = callback;
mCountDownLatch.countDown();
}
}
}