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

Commit 03004291 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Make the onRequestRecommendation() method async."

parents bf249c13 375320ed
Loading
Loading
Loading
Loading
+5 −1
Original line number Diff line number Diff line
@@ -25998,11 +25998,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();
        }
    }
}