dps, boolean isRoaming,
@NonNull DataServiceCallback callback) {
// The default implementation is to return unsupported.
if (callback != null) {
callback.onSetDataProfileComplete(DataServiceCallback.RESULT_ERROR_UNSUPPORTED);
}
}
/**
* Indicates that a handover has begun. This is called on the source transport.
*
* Any resources being transferred cannot be released while a
* handover is underway.
*
* If a handover was unsuccessful, then the framework calls
* {@link DataService#cancelHandover}. The target transport retains ownership over any of
* the resources being transferred.
*
* If a handover was successful, the framework calls {@link DataService#deactivateDataCall}
* with reason {@link DataService.REQUEST_REASON_HANDOVER}. The target transport now owns
* the transferred resources and is responsible for releasing them.
*
*
* Note that the callback will be executed on binder thread.
*
* @param cid The identifier of the data call which is provided in {@link DataCallResponse}
* @param callback The result callback for this request.
*
* @hide
*/
public void startHandover(int cid, @NonNull DataServiceCallback callback) {
Objects.requireNonNull(callback, "callback cannot be null");
// The default implementation is to return unsupported.
Log.d(TAG, "startHandover: " + cid);
callback.onHandoverStarted(DataServiceCallback.RESULT_ERROR_UNSUPPORTED);
}
/**
* Indicates that a handover was cancelled after a call to
* {@link DataService#startHandover}. This is called on the source transport.
*
* Since the handover was unsuccessful, the source transport retains ownership over any of
* the resources being transferred and is still responsible for releasing them.
*
* The handover can be cancelled up until either:
* -
* The handover was successful after receiving a successful response from
* {@link DataService#setupDataCall} on the target transport.
*
-
* The data call on the source transport was lost.
*
*
*
*
* Note that the callback will be executed on binder thread.
*
* @param cid The identifier of the data call which is provided in {@link DataCallResponse}
* @param callback The result callback for this request.
*
* @hide
*/
public void cancelHandover(int cid, @NonNull DataServiceCallback callback) {
Objects.requireNonNull(callback, "callback cannot be null");
// The default implementation is to return unsupported.
Log.d(TAG, "cancelHandover: " + cid);
callback.onHandoverCancelled(DataServiceCallback.RESULT_ERROR_UNSUPPORTED);
}
/**
* Get the active data call list.
*
* @param callback The result callback for this request.
*/
public void requestDataCallList(@NonNull DataServiceCallback callback) {
// The default implementation is to return unsupported.
callback.onRequestDataCallListComplete(DataServiceCallback.RESULT_ERROR_UNSUPPORTED,
Collections.EMPTY_LIST);
}
private void registerForDataCallListChanged(IDataServiceCallback callback) {
synchronized (mDataCallListChangedCallbacks) {
mDataCallListChangedCallbacks.add(callback);
}
}
private void unregisterForDataCallListChanged(IDataServiceCallback callback) {
synchronized (mDataCallListChangedCallbacks) {
mDataCallListChangedCallbacks.remove(callback);
}
}
private void registerForApnUnthrottled(IDataServiceCallback callback) {
synchronized (mApnUnthrottledCallbacks) {
mApnUnthrottledCallbacks.add(callback);
}
}
private void unregisterForApnUnthrottled(IDataServiceCallback callback) {
synchronized (mApnUnthrottledCallbacks) {
mApnUnthrottledCallbacks.remove(callback);
}
}
/**
* Request validation check to see if the network is working properly for a given data call.
*
* This request is completed immediately after submitting the request to the data service
* provider and receiving {@link DataServiceCallback.ResultCode}, and progress status or
* validation results are notified through {@link
* DataCallResponse#getNetworkValidationStatus}.
*
*
If the network validation request is submitted successfully, {@link
* DataServiceCallback#RESULT_SUCCESS} is passed to {@code resultCodeCallback}. If the
* network validation feature is not supported by the data service provider itself, {@link
* DataServiceCallback#RESULT_ERROR_UNSUPPORTED} is passed to {@code resultCodeCallback}.
* See {@link DataServiceCallback.ResultCode} for the type of response that indicates
* whether the request was successfully submitted or had an error.
*
*
In response to this network validation request, providers can validate the data call
* in their own way. For example, in IWLAN, the DPD (Dead Peer Detection) can be used as a
* tool to check whether a data call is alive.
*
* @param cid The identifier of the data call which is provided in {@link DataCallResponse}
* @param executor The callback executor for the response.
* @param resultCodeCallback Listener for the {@link DataServiceCallback.ResultCode} that
* request validation to the DataService and checks if the request has been submitted.
*/
@FlaggedApi(Flags.FLAG_NETWORK_VALIDATION)
public void requestNetworkValidation(int cid,
@NonNull @CallbackExecutor Executor executor,
@NonNull @DataServiceCallback.ResultCode Consumer resultCodeCallback) {
Objects.requireNonNull(executor, "executor cannot be null");
Objects.requireNonNull(resultCodeCallback, "resultCodeCallback cannot be null");
Log.d(TAG, "requestNetworkValidation: " + cid);
// The default implementation is to return unsupported.
executor.execute(() -> resultCodeCallback
.accept(DataServiceCallback.RESULT_ERROR_UNSUPPORTED));
}
/**
* Notify the system that current data call list changed. Data service must invoke this
* method whenever there is any data call status changed.
*
* @param dataCallList List of the current active data call.
*/
public final void notifyDataCallListChanged(List dataCallList) {
synchronized (mDataCallListChangedCallbacks) {
for (IDataServiceCallback callback : mDataCallListChangedCallbacks) {
mHandler.obtainMessage(DATA_SERVICE_INDICATION_DATA_CALL_LIST_CHANGED,
mSlotIndex, 0, new DataCallListChangedIndication(dataCallList,
callback)).sendToTarget();
}
}
}
/**
* Notify the system that a given APN was unthrottled.
*
* @param apn Access Point Name defined by the carrier.
*/
public final void notifyApnUnthrottled(@NonNull String apn) {
synchronized (mApnUnthrottledCallbacks) {
for (IDataServiceCallback callback : mApnUnthrottledCallbacks) {
mHandler.obtainMessage(DATA_SERVICE_INDICATION_APN_UNTHROTTLED,
mSlotIndex, 0, new ApnUnthrottledIndication(apn,
callback)).sendToTarget();
}
}
}
/**
* Notify the system that a given DataProfile was unthrottled.
*
* @param dataProfile DataProfile associated with an APN returned from the modem
*/
public final void notifyDataProfileUnthrottled(@NonNull DataProfile dataProfile) {
synchronized (mApnUnthrottledCallbacks) {
for (IDataServiceCallback callback : mApnUnthrottledCallbacks) {
mHandler.obtainMessage(DATA_SERVICE_INDICATION_APN_UNTHROTTLED,
mSlotIndex, 0, new ApnUnthrottledIndication(dataProfile,
callback)).sendToTarget();
}
}
}
/**
* Called when the instance of data service is destroyed (e.g. got unbind or binder died)
* or when the data service provider is removed. The extended class should implement this
* method to perform cleanup works.
*/
@Override
public abstract void close();
}
private static final class SetupDataCallRequest {
public final int accessNetworkType;
public final DataProfile dataProfile;
public final boolean isRoaming;
public final boolean allowRoaming;
public final int reason;
public final LinkProperties linkProperties;
public final int pduSessionId;
public final NetworkSliceInfo sliceInfo;
public final TrafficDescriptor trafficDescriptor;
public final boolean matchAllRuleAllowed;
public final IDataServiceCallback callback;
SetupDataCallRequest(int accessNetworkType, DataProfile dataProfile, boolean isRoaming,
boolean allowRoaming, int reason, LinkProperties linkProperties, int pduSessionId,
NetworkSliceInfo sliceInfo, TrafficDescriptor trafficDescriptor,
boolean matchAllRuleAllowed, IDataServiceCallback callback) {
this.accessNetworkType = accessNetworkType;
this.dataProfile = dataProfile;
this.isRoaming = isRoaming;
this.allowRoaming = allowRoaming;
this.linkProperties = linkProperties;
this.reason = reason;
this.pduSessionId = pduSessionId;
this.sliceInfo = sliceInfo;
this.trafficDescriptor = trafficDescriptor;
this.matchAllRuleAllowed = matchAllRuleAllowed;
this.callback = callback;
}
}
private static final class DeactivateDataCallRequest {
public final int cid;
public final int reason;
public final IDataServiceCallback callback;
DeactivateDataCallRequest(int cid, int reason, IDataServiceCallback callback) {
this.cid = cid;
this.reason = reason;
this.callback = callback;
}
}
private static final class SetInitialAttachApnRequest {
public final DataProfile dataProfile;
public final boolean isRoaming;
public final IDataServiceCallback callback;
SetInitialAttachApnRequest(DataProfile dataProfile, boolean isRoaming,
IDataServiceCallback callback) {
this.dataProfile = dataProfile;
this.isRoaming = isRoaming;
this.callback = callback;
}
}
private static final class SetDataProfileRequest {
public final List dps;
public final boolean isRoaming;
public final IDataServiceCallback callback;
SetDataProfileRequest(List dps, boolean isRoaming,
IDataServiceCallback callback) {
this.dps = dps;
this.isRoaming = isRoaming;
this.callback = callback;
}
}
private static final class BeginCancelHandoverRequest {
public final int cid;
public final IDataServiceCallback callback;
BeginCancelHandoverRequest(int cid,
IDataServiceCallback callback) {
this.cid = cid;
this.callback = callback;
}
}
private static final class DataCallListChangedIndication {
public final List dataCallList;
public final IDataServiceCallback callback;
DataCallListChangedIndication(List dataCallList,
IDataServiceCallback callback) {
this.dataCallList = dataCallList;
this.callback = callback;
}
}
private static final class ApnUnthrottledIndication {
public final DataProfile dataProfile;
public final String apn;
public final IDataServiceCallback callback;
ApnUnthrottledIndication(String apn,
IDataServiceCallback callback) {
this.dataProfile = null;
this.apn = apn;
this.callback = callback;
}
ApnUnthrottledIndication(DataProfile dataProfile, IDataServiceCallback callback) {
this.dataProfile = dataProfile;
this.apn = null;
this.callback = callback;
}
}
private static final class ValidationRequest {
public final int cid;
public final Executor executor;
public final IIntegerConsumer callback;
ValidationRequest(int cid, Executor executor, IIntegerConsumer callback) {
this.cid = cid;
this.executor = executor;
this.callback = callback;
}
}
private class DataServiceHandler extends Handler {
DataServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message message) {
IDataServiceCallback callback;
final int slotIndex = message.arg1;
DataServiceProvider serviceProvider = mServiceMap.get(slotIndex);
switch (message.what) {
case DATA_SERVICE_CREATE_DATA_SERVICE_PROVIDER:
serviceProvider = onCreateDataServiceProvider(message.arg1);
if (serviceProvider != null) {
mServiceMap.put(slotIndex, serviceProvider);
}
break;
case DATA_SERVICE_REMOVE_DATA_SERVICE_PROVIDER:
if (serviceProvider != null) {
serviceProvider.close();
mServiceMap.remove(slotIndex);
}
break;
case DATA_SERVICE_REMOVE_ALL_DATA_SERVICE_PROVIDERS:
for (int i = 0; i < mServiceMap.size(); i++) {
serviceProvider = mServiceMap.get(i);
if (serviceProvider != null) {
serviceProvider.close();
}
}
mServiceMap.clear();
break;
case DATA_SERVICE_REQUEST_SETUP_DATA_CALL:
if (serviceProvider == null) break;
SetupDataCallRequest setupDataCallRequest = (SetupDataCallRequest) message.obj;
serviceProvider.setupDataCall(setupDataCallRequest.accessNetworkType,
setupDataCallRequest.dataProfile, setupDataCallRequest.isRoaming,
setupDataCallRequest.allowRoaming, setupDataCallRequest.reason,
setupDataCallRequest.linkProperties, setupDataCallRequest.pduSessionId,
setupDataCallRequest.sliceInfo, setupDataCallRequest.trafficDescriptor,
setupDataCallRequest.matchAllRuleAllowed,
(setupDataCallRequest.callback != null)
? new DataServiceCallback(setupDataCallRequest.callback)
: null);
break;
case DATA_SERVICE_REQUEST_DEACTIVATE_DATA_CALL:
if (serviceProvider == null) break;
DeactivateDataCallRequest deactivateDataCallRequest =
(DeactivateDataCallRequest) message.obj;
serviceProvider.deactivateDataCall(deactivateDataCallRequest.cid,
deactivateDataCallRequest.reason,
(deactivateDataCallRequest.callback != null)
? new DataServiceCallback(deactivateDataCallRequest.callback)
: null);
break;
case DATA_SERVICE_REQUEST_SET_INITIAL_ATTACH_APN:
if (serviceProvider == null) break;
SetInitialAttachApnRequest setInitialAttachApnRequest =
(SetInitialAttachApnRequest) message.obj;
serviceProvider.setInitialAttachApn(setInitialAttachApnRequest.dataProfile,
setInitialAttachApnRequest.isRoaming,
(setInitialAttachApnRequest.callback != null)
? new DataServiceCallback(setInitialAttachApnRequest.callback)
: null);
break;
case DATA_SERVICE_REQUEST_SET_DATA_PROFILE:
if (serviceProvider == null) break;
SetDataProfileRequest setDataProfileRequest =
(SetDataProfileRequest) message.obj;
serviceProvider.setDataProfile(setDataProfileRequest.dps,
setDataProfileRequest.isRoaming,
(setDataProfileRequest.callback != null)
? new DataServiceCallback(setDataProfileRequest.callback)
: null);
break;
case DATA_SERVICE_REQUEST_REQUEST_DATA_CALL_LIST:
if (serviceProvider == null) break;
serviceProvider.requestDataCallList(new DataServiceCallback(
(IDataServiceCallback) message.obj));
break;
case DATA_SERVICE_REQUEST_REGISTER_DATA_CALL_LIST_CHANGED:
if (serviceProvider == null) break;
serviceProvider.registerForDataCallListChanged((IDataServiceCallback) message.obj);
break;
case DATA_SERVICE_REQUEST_UNREGISTER_DATA_CALL_LIST_CHANGED:
if (serviceProvider == null) break;
callback = (IDataServiceCallback) message.obj;
serviceProvider.unregisterForDataCallListChanged(callback);
break;
case DATA_SERVICE_INDICATION_DATA_CALL_LIST_CHANGED:
if (serviceProvider == null) break;
DataCallListChangedIndication indication =
(DataCallListChangedIndication) message.obj;
try {
indication.callback.onDataCallListChanged(indication.dataCallList);
} catch (RemoteException e) {
loge("Failed to call onDataCallListChanged. " + e);
}
break;
case DATA_SERVICE_REQUEST_START_HANDOVER:
if (serviceProvider == null) break;
BeginCancelHandoverRequest bReq = (BeginCancelHandoverRequest) message.obj;
serviceProvider.startHandover(bReq.cid,
(bReq.callback != null)
? new DataServiceCallback(bReq.callback) : null);
break;
case DATA_SERVICE_REQUEST_CANCEL_HANDOVER:
if (serviceProvider == null) break;
BeginCancelHandoverRequest cReq = (BeginCancelHandoverRequest) message.obj;
serviceProvider.cancelHandover(cReq.cid,
(cReq.callback != null)
? new DataServiceCallback(cReq.callback) : null);
break;
case DATA_SERVICE_REQUEST_REGISTER_APN_UNTHROTTLED:
if (serviceProvider == null) break;
serviceProvider.registerForApnUnthrottled((IDataServiceCallback) message.obj);
break;
case DATA_SERVICE_REQUEST_UNREGISTER_APN_UNTHROTTLED:
if (serviceProvider == null) break;
callback = (IDataServiceCallback) message.obj;
serviceProvider.unregisterForApnUnthrottled(callback);
break;
case DATA_SERVICE_INDICATION_APN_UNTHROTTLED:
if (serviceProvider == null) break;
ApnUnthrottledIndication apnUnthrottledIndication =
(ApnUnthrottledIndication) message.obj;
try {
if (apnUnthrottledIndication.dataProfile != null) {
apnUnthrottledIndication.callback
.onDataProfileUnthrottled(apnUnthrottledIndication.dataProfile);
} else {
apnUnthrottledIndication.callback
.onApnUnthrottled(apnUnthrottledIndication.apn);
}
} catch (RemoteException e) {
loge("Failed to call onApnUnthrottled. " + e);
}
break;
case DATA_SERVICE_REQUEST_VALIDATION:
if (serviceProvider == null) break;
ValidationRequest validationRequest = (ValidationRequest) message.obj;
serviceProvider.requestNetworkValidation(
validationRequest.cid,
validationRequest.executor,
FunctionalUtils
.ignoreRemoteException(validationRequest.callback::accept));
break;
}
}
}
/**
* Default constructor.
*/
public DataService() {
mHandlerThread = new HandlerThread(TAG);
mHandlerThread.start();
mHandler = new DataServiceHandler(mHandlerThread.getLooper());
mHandlerExecutor = new HandlerExecutor(mHandler);
log("Data service created");
}
/**
* Create the instance of {@link DataServiceProvider}. Data service provider must override
* this method to facilitate the creation of {@link DataServiceProvider} instances. The system
* will call this method after binding the data service for each active SIM slot id.
*
* This methead is guaranteed to be invoked in {@link DataService}'s internal handler thread
* whose looper can be retrieved with {@link Looper.myLooper()} when override this method.
*
* @param slotIndex SIM slot id the data service associated with.
* @return Data service object. Null if failed to create the provider (e.g. invalid slot index)
*/
@Nullable
public abstract DataServiceProvider onCreateDataServiceProvider(int slotIndex);
@Override
public IBinder onBind(Intent intent) {
if (intent == null || !SERVICE_INTERFACE.equals(intent.getAction())) {
loge("Unexpected intent " + intent);
return null;
}
return mBinder;
}
@Override
public boolean onUnbind(Intent intent) {
mHandler.obtainMessage(DATA_SERVICE_REMOVE_ALL_DATA_SERVICE_PROVIDERS).sendToTarget();
return false;
}
@Override
public void onDestroy() {
mHandlerThread.quitSafely();
super.onDestroy();
}
/**
* A wrapper around IDataService that forwards calls to implementations of {@link DataService}.
*/
private class IDataServiceWrapper extends IDataService.Stub {
@Override
public void createDataServiceProvider(int slotIndex) {
mHandler.obtainMessage(DATA_SERVICE_CREATE_DATA_SERVICE_PROVIDER, slotIndex, 0)
.sendToTarget();
}
@Override
public void removeDataServiceProvider(int slotIndex) {
mHandler.obtainMessage(DATA_SERVICE_REMOVE_DATA_SERVICE_PROVIDER, slotIndex, 0)
.sendToTarget();
}
@Override
public void setupDataCall(int slotIndex, int accessNetworkType, DataProfile dataProfile,
boolean isRoaming, boolean allowRoaming, int reason,
LinkProperties linkProperties, int pduSessionId, NetworkSliceInfo sliceInfo,
TrafficDescriptor trafficDescriptor, boolean matchAllRuleAllowed,
IDataServiceCallback callback) {
mHandler.obtainMessage(DATA_SERVICE_REQUEST_SETUP_DATA_CALL, slotIndex, 0,
new SetupDataCallRequest(accessNetworkType, dataProfile, isRoaming,
allowRoaming, reason, linkProperties, pduSessionId, sliceInfo,
trafficDescriptor, matchAllRuleAllowed, callback))
.sendToTarget();
}
@Override
public void deactivateDataCall(int slotIndex, int cid, int reason,
IDataServiceCallback callback) {
mHandler.obtainMessage(DATA_SERVICE_REQUEST_DEACTIVATE_DATA_CALL, slotIndex, 0,
new DeactivateDataCallRequest(cid, reason, callback))
.sendToTarget();
}
@Override
public void setInitialAttachApn(int slotIndex, DataProfile dataProfile, boolean isRoaming,
IDataServiceCallback callback) {
mHandler.obtainMessage(DATA_SERVICE_REQUEST_SET_INITIAL_ATTACH_APN, slotIndex, 0,
new SetInitialAttachApnRequest(dataProfile, isRoaming, callback))
.sendToTarget();
}
@Override
public void setDataProfile(int slotIndex, List dps, boolean isRoaming,
IDataServiceCallback callback) {
mHandler.obtainMessage(DATA_SERVICE_REQUEST_SET_DATA_PROFILE, slotIndex, 0,
new SetDataProfileRequest(dps, isRoaming, callback)).sendToTarget();
}
@Override
public void requestDataCallList(int slotIndex, IDataServiceCallback callback) {
if (callback == null) {
loge("requestDataCallList: callback is null");
return;
}
mHandler.obtainMessage(DATA_SERVICE_REQUEST_REQUEST_DATA_CALL_LIST, slotIndex, 0,
callback).sendToTarget();
}
@Override
public void registerForDataCallListChanged(int slotIndex, IDataServiceCallback callback) {
if (callback == null) {
loge("registerForDataCallListChanged: callback is null");
return;
}
mHandler.obtainMessage(DATA_SERVICE_REQUEST_REGISTER_DATA_CALL_LIST_CHANGED, slotIndex,
0, callback).sendToTarget();
}
@Override
public void unregisterForDataCallListChanged(int slotIndex, IDataServiceCallback callback) {
if (callback == null) {
loge("unregisterForDataCallListChanged: callback is null");
return;
}
mHandler.obtainMessage(DATA_SERVICE_REQUEST_UNREGISTER_DATA_CALL_LIST_CHANGED,
slotIndex, 0, callback).sendToTarget();
}
@Override
public void startHandover(int slotIndex, int cid, IDataServiceCallback callback) {
if (callback == null) {
loge("startHandover: callback is null");
return;
}
BeginCancelHandoverRequest req = new BeginCancelHandoverRequest(cid, callback);
mHandler.obtainMessage(DATA_SERVICE_REQUEST_START_HANDOVER,
slotIndex, 0, req)
.sendToTarget();
}
@Override
public void cancelHandover(int slotIndex, int cid, IDataServiceCallback callback) {
if (callback == null) {
loge("cancelHandover: callback is null");
return;
}
BeginCancelHandoverRequest req = new BeginCancelHandoverRequest(cid, callback);
mHandler.obtainMessage(DATA_SERVICE_REQUEST_CANCEL_HANDOVER,
slotIndex, 0, req).sendToTarget();
}
@Override
public void registerForUnthrottleApn(int slotIndex, IDataServiceCallback callback) {
if (callback == null) {
loge("registerForUnthrottleApn: callback is null");
return;
}
mHandler.obtainMessage(DATA_SERVICE_REQUEST_REGISTER_APN_UNTHROTTLED, slotIndex,
0, callback).sendToTarget();
}
@Override
public void unregisterForUnthrottleApn(int slotIndex, IDataServiceCallback callback) {
if (callback == null) {
loge("uregisterForUnthrottleApn: callback is null");
return;
}
mHandler.obtainMessage(DATA_SERVICE_REQUEST_UNREGISTER_APN_UNTHROTTLED,
slotIndex, 0, callback).sendToTarget();
}
@Override
public void requestNetworkValidation(int slotIndex, int cid,
IIntegerConsumer resultCodeCallback) {
if (resultCodeCallback == null) {
loge("requestNetworkValidation: resultCodeCallback is null");
return;
}
ValidationRequest validationRequest =
new ValidationRequest(cid, mHandlerExecutor, resultCodeCallback);
mHandler.obtainMessage(DATA_SERVICE_REQUEST_VALIDATION,
slotIndex, 0, validationRequest).sendToTarget();
}
}
private void log(String s) {
Rlog.d(TAG, s);
}
private void loge(String s) {
Rlog.e(TAG, s);
}
}