blob: 16ae867d81e792cc8d53279511215087f2cfbddd [file] [log] [blame]
package android.net;
import android.annotation.SystemApi;
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.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
*/
@SystemApi
public abstract class NetworkRecommendationProvider {
private static final String TAG = "NetworkRecProvider";
/** The key into the callback Bundle where the RecommendationResult will be found. */
public static final String EXTRA_RECOMMENDATION_RESULT =
"android.net.extra.RECOMMENDATION_RESULT";
/** The key into the callback Bundle where the sequence will be found. */
public static final String EXTRA_SEQUENCE = "android.net.extra.SEQUENCE";
private static final String EXTRA_RECOMMENDATION_REQUEST =
"android.net.extra.RECOMMENDATION_REQUEST";
private final IBinder mService;
/**
* Constructs a new instance.
* @param handler indicates which thread to use when handling requests. Cannot be {@code null}.
*/
public NetworkRecommendationProvider(Handler handler) {
if (handler == null) {
throw new IllegalArgumentException("The provided handler cannot be null.");
}
mService = new ServiceWrapper(new ServiceHandler(handler.getLooper()));
}
/**
* Invoked when a recommendation has been requested.
*
* @param request a {@link RecommendationRequest} instance containing additional
* request details
* @param callback a {@link ResultCallback} instance. When a {@link RecommendationResult} is
* available it must be passed into
* {@link ResultCallback#onResult(RecommendationResult)}.
*/
public abstract void onRequestRecommendation(RecommendationRequest request,
ResultCallback callback);
/**
* Invoked when network scores have been requested.
* <p>
* Use {@link NetworkScoreManager#updateScores(ScoredNetwork[])} to respond to score requests.
*
* @param networks a non-empty array of {@link NetworkKey}s to score.
*/
public abstract void onRequestScores(NetworkKey[] networks);
/**
* Services that can handle {@link NetworkScoreManager#ACTION_RECOMMEND_NETWORKS} should
* return this Binder from their <code>onBind()</code> method.
*/
public final IBinder getBinder() {
return mService;
}
/**
* A callback implementing applications should invoke when a {@link RecommendationResult}
* is available.
*/
public static 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;
static final int MSG_REQUEST_SCORES = 2;
ServiceHandler(Looper looper) {
super(looper, null /*callback*/, true /*async*/);
}
@Override
public void handleMessage(Message msg) {
final int what = msg.what;
switch (what) {
case MSG_GET_RECOMMENDATION:
final IRemoteCallback callback = (IRemoteCallback) msg.obj;
final int seq = msg.arg1;
final RecommendationRequest request =
msg.getData().getParcelable(EXTRA_RECOMMENDATION_REQUEST);
final ResultCallback resultCallback = new ResultCallback(callback, seq);
onRequestRecommendation(request, resultCallback);
break;
case MSG_REQUEST_SCORES:
final NetworkKey[] networks = (NetworkKey[]) msg.obj;
onRequestScores(networks);
break;
default:
throw new IllegalArgumentException("Unknown message: " + what);
}
}
}
/**
* A wrapper around INetworkRecommendationProvider that sends calls to the internal Handler.
*/
private static final class ServiceWrapper extends INetworkRecommendationProvider.Stub {
private final Handler mHandler;
ServiceWrapper(Handler handler) {
mHandler = handler;
}
@Override
public void requestRecommendation(RecommendationRequest request, IRemoteCallback callback,
int sequence) throws RemoteException {
final Message msg = mHandler.obtainMessage(
ServiceHandler.MSG_GET_RECOMMENDATION, sequence, 0 /*arg2*/, callback);
final Bundle data = new Bundle();
data.putParcelable(EXTRA_RECOMMENDATION_REQUEST, request);
msg.setData(data);
msg.sendToTarget();
}
@Override
public void requestScores(NetworkKey[] networks) throws RemoteException {
if (networks != null && networks.length > 0) {
mHandler.obtainMessage(ServiceHandler.MSG_REQUEST_SCORES, networks).sendToTarget();
}
}
}
}