diff options
| author | 2016-12-13 16:19:14 +0000 | |
|---|---|---|
| committer | 2016-12-13 16:19:14 +0000 | |
| commit | eb0a34f26bcfde0c4cb506a8c7261efd3cc40f62 (patch) | |
| tree | 70429baf9876acfa3cfdcd018230c9192963198e | |
| parent | cd6720274d8724e02dc0cc617771006601e95e9d (diff) | |
| parent | a6c6bc333d2d98bc6091250100f582c24ad8b5fa (diff) | |
Merge "Make the onRequestRecommendation() method async." am: fbeacb02c0
am: a6c6bc333d
Change-Id: I56a830273f8e3246b598c2ea7a027b87e3066e16
| -rw-r--r-- | api/system-current.txt | 6 | ||||
| -rw-r--r-- | core/java/android/net/NetworkRecommendationProvider.java | 80 | ||||
| -rw-r--r-- | core/tests/coretests/src/android/net/NetworkRecommendationProviderTest.java | 119 |
3 files changed, 190 insertions, 15 deletions
diff --git a/api/system-current.txt b/api/system-current.txt index d904355b03d3..4ddc901f2eb8 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -25605,11 +25605,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); diff --git a/core/java/android/net/NetworkRecommendationProvider.java b/core/java/android/net/NetworkRecommendationProvider.java index cd2ede84cfdc..fc3213f486cd 100644 --- a/core/java/android/net/NetworkRecommendationProvider.java +++ b/core/java/android/net/NetworkRecommendationProvider.java @@ -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: diff --git a/core/tests/coretests/src/android/net/NetworkRecommendationProviderTest.java b/core/tests/coretests/src/android/net/NetworkRecommendationProviderTest.java new file mode 100644 index 000000000000..5ac8f56dae95 --- /dev/null +++ b/core/tests/coretests/src/android/net/NetworkRecommendationProviderTest.java @@ -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(); + } + } +} |