Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 375320ed authored by Jeremy Joslin's avatar Jeremy Joslin
Browse files

Make the onRequestRecommendation() method async.

Converted the NetworkRecommendationProvider.onRequestRecommendation()
method into an async call to give implementors more flexibility.

Added unit tests for NetworkRecommendationProvider.

Test: Added NetworkRecommendationProviderTest.
BUG: 32909424
Change-Id: Iebe72f260133e9ad1946b0b75e2f69635e154ef3
parent 939e6cf6
Loading
Loading
Loading
Loading
+5 −1
Original line number Diff line number Diff line
@@ -25989,11 +25989,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);
+66 −14
Original line number Diff line number Diff line
@@ -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:
+119 −0
Original line number Diff line number Diff line
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();
        }
    }
}