| /* |
| * Copyright 2017 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.telephony.data; |
| |
| import android.annotation.CallbackExecutor; |
| import android.annotation.FlaggedApi; |
| import android.annotation.IntDef; |
| import android.annotation.IntRange; |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.annotation.SdkConstant; |
| import android.annotation.SystemApi; |
| import android.app.Service; |
| import android.content.Intent; |
| import android.net.LinkProperties; |
| import android.os.Handler; |
| import android.os.HandlerExecutor; |
| import android.os.HandlerThread; |
| import android.os.IBinder; |
| import android.os.Looper; |
| import android.os.Message; |
| import android.os.RemoteException; |
| import android.telephony.AccessNetworkConstants.RadioAccessNetworkType; |
| import android.util.Log; |
| import android.util.SparseArray; |
| |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.internal.telephony.IIntegerConsumer; |
| import com.android.internal.telephony.flags.Flags; |
| import com.android.internal.util.FunctionalUtils; |
| import com.android.telephony.Rlog; |
| |
| import java.lang.annotation.Retention; |
| import java.lang.annotation.RetentionPolicy; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Objects; |
| import java.util.concurrent.Executor; |
| import java.util.function.Consumer; |
| |
| /** |
| * Base class of data service. Services that extend DataService must register the service in |
| * their AndroidManifest to be detected by the framework. They must be protected by the permission |
| * "android.permission.BIND_TELEPHONY_DATA_SERVICE". The data service definition in the manifest |
| * must follow the following format: |
| * ... |
| * <service android:name=".xxxDataService" |
| * android:permission="android.permission.BIND_TELEPHONY_DATA_SERVICE" > |
| * <intent-filter> |
| * <action android:name="android.telephony.data.DataService" /> |
| * </intent-filter> |
| * </service> |
| * @hide |
| */ |
| @SystemApi |
| public abstract class DataService extends Service { |
| private static final String TAG = DataService.class.getSimpleName(); |
| |
| @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION) |
| public static final String SERVICE_INTERFACE = "android.telephony.data.DataService"; |
| |
| /** {@hide} */ |
| @IntDef(prefix = "REQUEST_REASON_", value = { |
| REQUEST_REASON_UNKNOWN, |
| REQUEST_REASON_NORMAL, |
| REQUEST_REASON_HANDOVER, |
| }) |
| @Retention(RetentionPolicy.SOURCE) |
| public @interface SetupDataReason {} |
| |
| /** {@hide} */ |
| @IntDef(prefix = "REQUEST_REASON_", value = { |
| REQUEST_REASON_UNKNOWN, |
| REQUEST_REASON_NORMAL, |
| REQUEST_REASON_SHUTDOWN, |
| REQUEST_REASON_HANDOVER, |
| }) |
| @Retention(RetentionPolicy.SOURCE) |
| public @interface DeactivateDataReason {} |
| |
| /** The reason of the data request is unknown */ |
| public static final int REQUEST_REASON_UNKNOWN = 0; |
| |
| /** The reason of the data request is normal */ |
| public static final int REQUEST_REASON_NORMAL = 1; |
| |
| /** The reason of the data request is device shutdown */ |
| public static final int REQUEST_REASON_SHUTDOWN = 2; |
| |
| /** The reason of the data request is IWLAN handover */ |
| public static final int REQUEST_REASON_HANDOVER = 3; |
| |
| private static final int DATA_SERVICE_CREATE_DATA_SERVICE_PROVIDER = 1; |
| private static final int DATA_SERVICE_REMOVE_DATA_SERVICE_PROVIDER = 2; |
| private static final int DATA_SERVICE_REMOVE_ALL_DATA_SERVICE_PROVIDERS = 3; |
| private static final int DATA_SERVICE_REQUEST_SETUP_DATA_CALL = 4; |
| private static final int DATA_SERVICE_REQUEST_DEACTIVATE_DATA_CALL = 5; |
| private static final int DATA_SERVICE_REQUEST_SET_INITIAL_ATTACH_APN = 6; |
| private static final int DATA_SERVICE_REQUEST_SET_DATA_PROFILE = 7; |
| private static final int DATA_SERVICE_REQUEST_REQUEST_DATA_CALL_LIST = 8; |
| private static final int DATA_SERVICE_REQUEST_REGISTER_DATA_CALL_LIST_CHANGED = 9; |
| private static final int DATA_SERVICE_REQUEST_UNREGISTER_DATA_CALL_LIST_CHANGED = 10; |
| private static final int DATA_SERVICE_INDICATION_DATA_CALL_LIST_CHANGED = 11; |
| private static final int DATA_SERVICE_REQUEST_START_HANDOVER = 12; |
| private static final int DATA_SERVICE_REQUEST_CANCEL_HANDOVER = 13; |
| private static final int DATA_SERVICE_REQUEST_REGISTER_APN_UNTHROTTLED = 14; |
| private static final int DATA_SERVICE_REQUEST_UNREGISTER_APN_UNTHROTTLED = 15; |
| private static final int DATA_SERVICE_INDICATION_APN_UNTHROTTLED = 16; |
| private static final int DATA_SERVICE_REQUEST_VALIDATION = 17; |
| |
| private final HandlerThread mHandlerThread; |
| |
| private final DataServiceHandler mHandler; |
| |
| private final Executor mHandlerExecutor; |
| |
| private final SparseArray<DataServiceProvider> mServiceMap = new SparseArray<>(); |
| |
| /** @hide */ |
| @VisibleForTesting |
| public final IDataServiceWrapper mBinder = new IDataServiceWrapper(); |
| |
| /** |
| * The abstract class of the actual data service implementation. The data service provider |
| * must extend this class to support data connection. Note that each instance of data service |
| * provider is associated with one physical SIM slot. |
| */ |
| public abstract class DataServiceProvider implements AutoCloseable { |
| |
| private final int mSlotIndex; |
| |
| private final List<IDataServiceCallback> mDataCallListChangedCallbacks = new ArrayList<>(); |
| |
| private final List<IDataServiceCallback> mApnUnthrottledCallbacks = new ArrayList<>(); |
| |
| /** |
| * Constructor |
| * @param slotIndex SIM slot index the data service provider associated with. |
| */ |
| public DataServiceProvider(int slotIndex) { |
| mSlotIndex = slotIndex; |
| } |
| |
| /** |
| * @return SIM slot index the data service provider associated with. |
| */ |
| public final int getSlotIndex() { |
| return mSlotIndex; |
| } |
| |
| /** |
| * Setup a data connection. The data service provider must implement this method to support |
| * establishing a packet data connection. When completed or error, the service must invoke |
| * the provided callback to notify the platform. |
| * |
| * @param accessNetworkType Access network type that the data call will be established on. |
| * Must be one of {@link android.telephony.AccessNetworkConstants.AccessNetworkType}. |
| * @param dataProfile Data profile used for data call setup. See {@link DataProfile} |
| * @param isRoaming True if the device is data roaming. |
| * @param allowRoaming True if data roaming is allowed by the user. |
| * @param reason The reason for data setup. Must be {@link #REQUEST_REASON_NORMAL} or |
| * {@link #REQUEST_REASON_HANDOVER}. |
| * @param linkProperties If {@code reason} is {@link #REQUEST_REASON_HANDOVER}, this is the |
| * link properties of the existing data connection, otherwise null. |
| * @param callback The result callback for this request. |
| */ |
| public void setupDataCall( |
| @RadioAccessNetworkType int accessNetworkType, @NonNull DataProfile dataProfile, |
| boolean isRoaming, boolean allowRoaming, |
| @SetupDataReason int reason, @Nullable LinkProperties linkProperties, |
| @NonNull DataServiceCallback callback) { |
| // The default implementation is to return unsupported. |
| if (callback != null) { |
| callback.onSetupDataCallComplete(DataServiceCallback.RESULT_ERROR_UNSUPPORTED, |
| null); |
| } |
| } |
| |
| /** |
| * Setup a data connection. The data service provider must implement this method to support |
| * establishing a packet data connection. When completed or error, the service must invoke |
| * the provided callback to notify the platform. |
| * |
| * @param accessNetworkType Access network type that the data call will be established on. |
| * Must be one of {@link android.telephony.AccessNetworkConstants.AccessNetworkType}. |
| * @param dataProfile Data profile used for data call setup. See {@link DataProfile} |
| * @param isRoaming True if the device is data roaming. |
| * @param allowRoaming True if data roaming is allowed by the user. |
| * @param reason The reason for data setup. Must be {@link #REQUEST_REASON_NORMAL} or |
| * {@link #REQUEST_REASON_HANDOVER}. |
| * @param linkProperties If {@code reason} is {@link #REQUEST_REASON_HANDOVER}, this is the |
| * link properties of the existing data connection, otherwise null. |
| * @param pduSessionId The pdu session id to be used for this data call. |
| * The standard range of values are 1-15 while 0 means no pdu session id |
| * was attached to this call. Reference: 3GPP TS 24.007 section |
| * 11.2.3.1b. |
| * @param sliceInfo used within the data connection when a handover occurs from EPDG to 5G. |
| * The value is null unless the access network is |
| * {@link android.telephony.AccessNetworkConstants.AccessNetworkType#NGRAN} and a |
| * handover is occurring from EPDG to 5G. If the slice passed is rejected, then |
| * {@link DataCallResponse#getCause()} is |
| * {@link android.telephony.DataFailCause#SLICE_REJECTED}. |
| * @param trafficDescriptor {@link TrafficDescriptor} for which data connection needs to be |
| * established. It is used for URSP traffic matching as described in 3GPP TS 24.526 |
| * Section 4.2.2. It includes an optional DNN which, if present, must be used for |
| * traffic matching; it does not specify the end point to be used for the data call. |
| * @param matchAllRuleAllowed Indicates if using default match-all URSP rule for this |
| * request is allowed. If false, this request must not use the match-all URSP rule |
| * and if a non-match-all rule is not found (or if URSP rules are not available) then |
| * {@link DataCallResponse#getCause()} is |
| * {@link android.telephony.DataFailCause#MATCH_ALL_RULE_NOT_ALLOWED}. This is needed |
| * as some requests need to have a hard failure if the intention cannot be met, |
| * for example, a zero-rating slice. |
| * @param callback The result callback for this request. |
| */ |
| public void setupDataCall( |
| @RadioAccessNetworkType int accessNetworkType, @NonNull DataProfile dataProfile, |
| boolean isRoaming, boolean allowRoaming, |
| @SetupDataReason int reason, |
| @Nullable LinkProperties linkProperties, |
| @IntRange(from = 0, to = 15) int pduSessionId, @Nullable NetworkSliceInfo sliceInfo, |
| @Nullable TrafficDescriptor trafficDescriptor, boolean matchAllRuleAllowed, |
| @NonNull DataServiceCallback callback) { |
| /* Call the old version since the new version isn't supported */ |
| setupDataCall(accessNetworkType, dataProfile, isRoaming, allowRoaming, reason, |
| linkProperties, callback); |
| } |
| |
| /** |
| * Deactivate a data connection. The data service provider must implement this method to |
| * support data connection tear down. When completed or error, the service must invoke the |
| * provided callback to notify the platform. |
| * |
| * @param cid Call id returned in the callback of {@link DataServiceProvider#setupDataCall( |
| * int, DataProfile, boolean, boolean, int, LinkProperties, DataServiceCallback)}. |
| * @param reason The reason for data deactivation. Must be {@link #REQUEST_REASON_NORMAL}, |
| * {@link #REQUEST_REASON_SHUTDOWN} or {@link #REQUEST_REASON_HANDOVER}. |
| * @param callback The result callback for this request. Null if the client does not care |
| * about the result. |
| * |
| */ |
| public void deactivateDataCall(int cid, @DeactivateDataReason int reason, |
| @Nullable DataServiceCallback callback) { |
| // The default implementation is to return unsupported. |
| if (callback != null) { |
| callback.onDeactivateDataCallComplete(DataServiceCallback.RESULT_ERROR_UNSUPPORTED); |
| } |
| } |
| |
| /** |
| * Set an APN to initial attach network. |
| * |
| * @param dataProfile Data profile used for data call setup. See {@link DataProfile}. |
| * @param isRoaming True if the device is data roaming. |
| * @param callback The result callback for this request. |
| */ |
| public void setInitialAttachApn(@NonNull DataProfile dataProfile, boolean isRoaming, |
| @NonNull DataServiceCallback callback) { |
| // The default implementation is to return unsupported. |
| if (callback != null) { |
| callback.onSetInitialAttachApnComplete( |
| DataServiceCallback.RESULT_ERROR_UNSUPPORTED); |
| } |
| } |
| |
| /** |
| * Send current carrier's data profiles to the data service for data call setup. This is |
| * only for CDMA carrier that can change the profile through OTA. The data service should |
| * always uses the latest data profile sent by the framework. |
| * |
| * @param dps A list of data profiles. |
| * @param isRoaming True if the device is data roaming. |
| * @param callback The result callback for this request. |
| */ |
| public void setDataProfile(@NonNull List<DataProfile> 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. |
| * <p/> |
| * If a handover was unsuccessful, then the framework calls |
| * {@link DataService#cancelHandover}. The target transport retains ownership over any of |
| * the resources being transferred. |
| * <p/> |
| * 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. |
| * |
| * <p/> |
| * 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. |
| * <p/> |
| * Since the handover was unsuccessful, the source transport retains ownership over any of |
| * the resources being transferred and is still responsible for releasing them. |
| * <p/> |
| * The handover can be cancelled up until either: |
| * <ul><li> |
| * The handover was successful after receiving a successful response from |
| * {@link DataService#setupDataCall} on the target transport. |
| * </li><li> |
| * The data call on the source transport was lost. |
| * </li> |
| * </ul> |
| * |
| * <p/> |
| * 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. |
| * |
| * <p>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}. |
| * |
| * <p> 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. |
| * |
| * <p>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 requestValidation(int cid, |
| @NonNull @CallbackExecutor Executor executor, |
| @NonNull @DataServiceCallback.ResultCode Consumer<Integer> resultCodeCallback) { |
| Objects.requireNonNull(executor, "executor cannot be null"); |
| Objects.requireNonNull(resultCodeCallback, "resultCodeCallback cannot be null"); |
| |
| Log.d(TAG, "requestValidation: " + 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<DataCallResponse> 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<DataProfile> dps; |
| public final boolean isRoaming; |
| public final IDataServiceCallback callback; |
| SetDataProfileRequest(List<DataProfile> 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<DataCallResponse> dataCallList; |
| public final IDataServiceCallback callback; |
| DataCallListChangedIndication(List<DataCallResponse> 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.requestValidation( |
| 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<DataProfile> 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 requestValidation(int slotIndex, int cid, IIntegerConsumer resultCodeCallback) { |
| if (resultCodeCallback == null) { |
| loge("requestValidation: 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); |
| } |
| } |