blob: 372bdf1c0f812e23d002d30e424f3edce947fa46 [file] [log] [blame]
/*
* 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.IntDef;
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.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.telephony.AccessNetworkConstants;
import android.telephony.Rlog;
import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
/**
* 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 final HandlerThread mHandlerThread;
private final DataServiceHandler mHandler;
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<>();
/**
* 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 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(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);
}
}
/**
* 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);
}
}
/**
* 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,
null);
}
private void registerForDataCallListChanged(IDataServiceCallback callback) {
synchronized (mDataCallListChangedCallbacks) {
mDataCallListChangedCallbacks.add(callback);
}
}
private void unregisterForDataCallListChanged(IDataServiceCallback callback) {
synchronized (mDataCallListChangedCallbacks) {
mDataCallListChangedCallbacks.remove(callback);
}
}
/**
* 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();
}
}
}
/**
* 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 IDataServiceCallback callback;
SetupDataCallRequest(int accessNetworkType, DataProfile dataProfile, boolean isRoaming,
boolean allowRoaming, int reason, LinkProperties linkProperties,
IDataServiceCallback callback) {
this.accessNetworkType = accessNetworkType;
this.dataProfile = dataProfile;
this.isRoaming = isRoaming;
this.allowRoaming = allowRoaming;
this.linkProperties = linkProperties;
this.reason = reason;
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 DataCallListChangedIndication {
public final List<DataCallResponse> dataCallList;
public final IDataServiceCallback callback;
DataCallListChangedIndication(List<DataCallResponse> dataCallList,
IDataServiceCallback callback) {
this.dataCallList = dataCallList;
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.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;
}
}
}
/**
* Default constructor.
*/
public DataService() {
mHandlerThread = new HandlerThread(TAG);
mHandlerThread.start();
mHandler = new DataServiceHandler(mHandlerThread.getLooper());
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.
*
* @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.quit();
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, IDataServiceCallback callback) {
mHandler.obtainMessage(DATA_SERVICE_REQUEST_SETUP_DATA_CALL, slotIndex, 0,
new SetupDataCallRequest(accessNetworkType, dataProfile, isRoaming,
allowRoaming, reason, linkProperties, 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();
}
}
private void log(String s) {
Rlog.d(TAG, s);
}
private void loge(String s) {
Rlog.e(TAG, s);
}
}