Added data service manager
Data service manager manages handling data requests and responses
on data services (e.g.Cellular data service, IWLAN data service).
Test: Telephony sanity tests
Bug: 64132030
Change-Id: I5435578cf7c68bff586d29cc5f0061ecebc22e45
diff --git a/src/java/com/android/internal/telephony/dataconnection/DataServiceManager.java b/src/java/com/android/internal/telephony/dataconnection/DataServiceManager.java
new file mode 100644
index 0000000..185864c
--- /dev/null
+++ b/src/java/com/android/internal/telephony/dataconnection/DataServiceManager.java
@@ -0,0 +1,464 @@
+/*
+ * Copyright 2018 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 com.android.internal.telephony.dataconnection;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.net.LinkProperties;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.PersistableBundle;
+import android.os.RegistrantList;
+import android.os.RemoteException;
+import android.telephony.AccessNetworkConstants;
+import android.telephony.CarrierConfigManager;
+import android.telephony.Rlog;
+import android.telephony.data.DataCallResponse;
+import android.telephony.data.DataProfile;
+import android.telephony.data.DataService;
+import android.telephony.data.DataServiceCallback;
+import android.telephony.data.IDataService;
+import android.telephony.data.IDataServiceCallback;
+import android.text.TextUtils;
+
+import com.android.internal.telephony.Phone;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Data service manager manages handling data requests and responses on data services (e.g.
+ * Cellular data service, IWLAN data service).
+ */
+public class DataServiceManager {
+ private static final String TAG = DataServiceManager.class.getSimpleName();
+ private static final boolean DBG = true;
+
+ static final String DATA_CALL_RESPONSE = "data_call_response";
+
+ private final Phone mPhone;
+
+ private final CarrierConfigManager mCarrierConfigManager;
+
+ private final int mTransportType;
+
+ private boolean mBound;
+
+ private IDataService mIDataService;
+
+ private DataServiceManagerDeathRecipient mDeathRecipient;
+
+ private final RegistrantList mServiceBindingChangedRegistrants = new RegistrantList();
+
+ private final Map<CellularDataServiceCallback, Message> mMessageMap = new HashMap<>();
+
+ private final RegistrantList mDataCallListChangedRegistrants = new RegistrantList();
+
+ private class DataServiceManagerDeathRecipient implements IBinder.DeathRecipient {
+
+ private final ComponentName mComponentName;
+
+ DataServiceManagerDeathRecipient(ComponentName name) {
+ mComponentName = name;
+ }
+
+ @Override
+ public void binderDied() {
+ // TODO: try to rebind the service.
+ loge("DataService(" + mComponentName + " transport type " + mTransportType
+ + ") died.");
+ }
+ }
+
+ private final class CellularDataServiceConnection implements ServiceConnection {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ if (DBG) log("onServiceConnected");
+ mIDataService = IDataService.Stub.asInterface(service);
+ mDeathRecipient = new DataServiceManagerDeathRecipient(name);
+ mBound = true;
+
+ try {
+ service.linkToDeath(mDeathRecipient, 0);
+ mIDataService.createDataServiceProvider(mPhone.getPhoneId());
+ mIDataService.registerForDataCallListChanged(mPhone.getPhoneId(),
+ new CellularDataServiceCallback());
+ } catch (RemoteException e) {
+ mDeathRecipient.binderDied();
+ loge("Remote exception. " + e);
+ return;
+ }
+
+ mServiceBindingChangedRegistrants.notifyResult(true);
+ }
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ if (DBG) log("onServiceDisconnected");
+ mIDataService.asBinder().unlinkToDeath(mDeathRecipient, 0);
+ mIDataService = null;
+ mBound = false;
+ mServiceBindingChangedRegistrants.notifyResult(false);
+ }
+ }
+
+ private final class CellularDataServiceCallback extends IDataServiceCallback.Stub {
+ @Override
+ public void onSetupDataCallComplete(@DataServiceCallback.ResultCode int resultCode,
+ DataCallResponse response) {
+ if (DBG) {
+ log("onSetupDataCallComplete. resultCode = " + resultCode + ", response = "
+ + response);
+ }
+ Message msg = mMessageMap.remove(CellularDataServiceCallback.this);
+ msg.getData().putParcelable(DATA_CALL_RESPONSE, response);
+ sendCompleteMessage(msg, resultCode);
+ }
+
+ @Override
+ public void onDeactivateDataCallComplete(@DataServiceCallback.ResultCode int resultCode) {
+ if (DBG) log("onDeactivateDataCallComplete. resultCode = " + resultCode);
+ Message msg = mMessageMap.remove(CellularDataServiceCallback.this);
+ sendCompleteMessage(msg, resultCode);
+ }
+
+ @Override
+ public void onSetInitialAttachApnComplete(@DataServiceCallback.ResultCode int resultCode) {
+ if (DBG) log("onSetInitialAttachApnComplete. resultCode = " + resultCode);
+ Message msg = mMessageMap.remove(CellularDataServiceCallback.this);
+ sendCompleteMessage(msg, resultCode);
+ }
+
+ @Override
+ public void onSetDataProfileComplete(@DataServiceCallback.ResultCode int resultCode) {
+ if (DBG) log("onSetDataProfileComplete. resultCode = " + resultCode);
+ Message msg = mMessageMap.remove(CellularDataServiceCallback.this);
+ sendCompleteMessage(msg, resultCode);
+ }
+
+ @Override
+ public void onGetDataCallListComplete(@DataServiceCallback.ResultCode int resultCode,
+ List<DataCallResponse> dataCallList) {
+ if (DBG) log("onGetDataCallListComplete. resultCode = " + resultCode);
+ Message msg = mMessageMap.remove(CellularDataServiceCallback.this);
+ sendCompleteMessage(msg, resultCode);
+ }
+
+ @Override
+ public void onDataCallListChanged(List<DataCallResponse> dataCallList) {
+ mDataCallListChangedRegistrants.notifyRegistrants(
+ new AsyncResult(null, dataCallList, null));
+ }
+ }
+
+ /**
+ * Constructor
+ *
+ * @param phone The phone object
+ * @param transportType The transport type. Must be a
+ * {@link AccessNetworkConstants.TransportType}.
+ */
+ public DataServiceManager(Phone phone, int transportType) {
+ mPhone = phone;
+ mTransportType = transportType;
+ mBound = false;
+ mCarrierConfigManager = (CarrierConfigManager) phone.getContext().getSystemService(
+ Context.CARRIER_CONFIG_SERVICE);
+
+ bindDataService();
+ }
+
+ private void bindDataService() {
+ String packageName = getDataServicePackageName();
+ if (TextUtils.isEmpty(packageName)) {
+ loge("Can't find the binding package");
+ return;
+ }
+
+ try {
+ if (!mPhone.getContext().bindService(
+ new Intent(DataService.DATA_SERVICE_INTERFACE).setPackage(packageName),
+ new CellularDataServiceConnection(),
+ Context.BIND_AUTO_CREATE)) {
+ loge("Cannot bind to the data service.");
+ }
+ } catch (Exception e) {
+ loge("Cannot bind to the data service. Exception: " + e);
+ }
+ }
+
+ private String getDataServicePackageName() {
+ String packageName;
+ int resourceId;
+ String carrierConfig;
+
+ switch (mTransportType) {
+ case AccessNetworkConstants.TransportType.WWAN:
+ resourceId = com.android.internal.R.string.config_wwan_data_service_package;
+ carrierConfig = CarrierConfigManager
+ .KEY_CARRIER_DATA_SERVICE_WWAN_PACKAGE_OVERRIDE_STRING;
+ break;
+ case AccessNetworkConstants.TransportType.WLAN:
+ resourceId = com.android.internal.R.string.config_wlan_data_service_package;
+ carrierConfig = CarrierConfigManager
+ .KEY_CARRIER_DATA_SERVICE_WLAN_PACKAGE_OVERRIDE_STRING;
+ break;
+ default:
+ throw new IllegalStateException("Transport type not WWAN or WLAN. type="
+ + mTransportType);
+ }
+
+ // Read package name from resource overlay
+ packageName = mPhone.getContext().getResources().getString(resourceId);
+
+ PersistableBundle b = mCarrierConfigManager.getConfigForSubId(mPhone.getSubId());
+
+ if (b != null) {
+ // If carrier config overrides it, use the one from carrier config
+ packageName = b.getString(carrierConfig, packageName);
+ }
+
+ return packageName;
+ }
+
+ private void sendCompleteMessage(Message msg, int code) {
+ if (msg != null) {
+ msg.arg1 = code;
+ msg.sendToTarget();
+ }
+ }
+
+ /**
+ * 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 DataService#REQUEST_REASON_NORMAL} or
+ * {@link DataService#REQUEST_REASON_HANDOVER}.
+ * @param linkProperties If {@code reason} is {@link DataService#REQUEST_REASON_HANDOVER}, this
+ * is the link properties of the existing data connection, otherwise null.
+ * @param onCompleteMessage The result message for this request. Null if the client does not
+ * care about the result.
+ */
+ public void setupDataCall(int accessNetworkType, DataProfile dataProfile, boolean isRoaming,
+ boolean allowRoaming, int reason, LinkProperties linkProperties,
+ Message onCompleteMessage) {
+ if (!mBound) {
+ loge("Data service not bound.");
+ sendCompleteMessage(onCompleteMessage, DataServiceCallback.RESULT_ERROR_ILLEGAL_STATE);
+ return;
+ }
+
+ CellularDataServiceCallback callback = new CellularDataServiceCallback();
+ try {
+ mIDataService.setupDataCall(mPhone.getPhoneId(), accessNetworkType, dataProfile,
+ isRoaming, allowRoaming, reason, linkProperties, callback);
+ } catch (RemoteException e) {
+ loge("Cannot invoke setupDataCall on data service.");
+ sendCompleteMessage(onCompleteMessage, DataServiceCallback.RESULT_ERROR_ILLEGAL_STATE);
+ return;
+ }
+ mMessageMap.put(callback, onCompleteMessage);
+ }
+
+ /**
+ * 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 #setupDataCall(int, DataProfile,
+ * boolean, boolean, int, LinkProperties, Message)}
+ * @param reason The reason for data deactivation. Must be
+ * {@link DataService#REQUEST_REASON_NORMAL}, {@link DataService#REQUEST_REASON_SHUTDOWN}
+ * or {@link DataService#REQUEST_REASON_HANDOVER}.
+ * @param onCompleteMessage The result message for this request. Null if the client does not
+ * care about the result.
+ */
+ public void deactivateDataCall(int cid, int reason, Message onCompleteMessage) {
+ if (!mBound) {
+ loge("Data service not bound.");
+ sendCompleteMessage(onCompleteMessage, DataServiceCallback.RESULT_ERROR_ILLEGAL_STATE);
+ return;
+ }
+
+ CellularDataServiceCallback callback = new CellularDataServiceCallback();
+ try {
+ mIDataService.deactivateDataCall(mPhone.getPhoneId(), cid, reason, callback);
+ } catch (RemoteException e) {
+ loge("Cannot invoke deactivateDataCall on data service.");
+ sendCompleteMessage(onCompleteMessage, DataServiceCallback.RESULT_ERROR_ILLEGAL_STATE);
+ return;
+ }
+ mMessageMap.put(callback, onCompleteMessage);
+ }
+
+ /**
+ * 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 onCompleteMessage The result message for this request. Null if the client does not
+ * care about the result.
+ */
+ public void setInitialAttachApn(DataProfile dataProfile, boolean isRoaming,
+ Message onCompleteMessage) {
+ if (!mBound) {
+ loge("Data service not bound.");
+ sendCompleteMessage(onCompleteMessage, DataServiceCallback.RESULT_ERROR_ILLEGAL_STATE);
+ return;
+ }
+
+ CellularDataServiceCallback callback = new CellularDataServiceCallback();
+ try {
+ mIDataService.setInitialAttachApn(mPhone.getPhoneId(), dataProfile, isRoaming,
+ callback);
+ } catch (RemoteException e) {
+ loge("Cannot invoke setInitialAttachApn on data service.");
+ sendCompleteMessage(onCompleteMessage, DataServiceCallback.RESULT_ERROR_ILLEGAL_STATE);
+ return;
+ }
+ mMessageMap.put(callback, onCompleteMessage);
+ }
+
+ /**
+ * 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 onCompleteMessage The result message for this request. Null if the client does not
+ * care about the result.
+ */
+ public void setDataProfile(List<DataProfile> dps, boolean isRoaming,
+ Message onCompleteMessage) {
+ if (!mBound) {
+ loge("Data service not bound.");
+ sendCompleteMessage(onCompleteMessage, DataServiceCallback.RESULT_ERROR_ILLEGAL_STATE);
+ return;
+ }
+
+ CellularDataServiceCallback callback = new CellularDataServiceCallback();
+ try {
+ mIDataService.setDataProfile(mPhone.getPhoneId(), dps, isRoaming, callback);
+ } catch (RemoteException e) {
+ loge("Cannot invoke setDataProfile on data service.");
+ sendCompleteMessage(onCompleteMessage, DataServiceCallback.RESULT_ERROR_ILLEGAL_STATE);
+ return;
+ }
+ mMessageMap.put(callback, onCompleteMessage);
+ }
+
+ /**
+ * Get the active data call list.
+ *
+ * @param onCompleteMessage The result message for this request. Null if the client does not
+ * care about the result.
+ */
+ public void getDataCallList(Message onCompleteMessage) {
+ if (!mBound) {
+ loge("Data service not bound.");
+ sendCompleteMessage(onCompleteMessage, DataServiceCallback.RESULT_ERROR_ILLEGAL_STATE);
+ return;
+ }
+
+ CellularDataServiceCallback callback = new CellularDataServiceCallback();
+ try {
+ mIDataService.getDataCallList(mPhone.getPhoneId(), callback);
+ } catch (RemoteException e) {
+ loge("Cannot invoke getDataCallList on data service.");
+ sendCompleteMessage(onCompleteMessage, DataServiceCallback.RESULT_ERROR_ILLEGAL_STATE);
+ return;
+ }
+ mMessageMap.put(callback, onCompleteMessage);
+ }
+
+ /**
+ * Register for data call list changed event.
+ *
+ * @param h The target to post the event message to.
+ * @param what The event.
+ */
+ public void registerForDataCallListChanged(Handler h, int what) {
+ if (h != null) {
+ mDataCallListChangedRegistrants.addUnique(h, what, null);
+ }
+ }
+
+ /**
+ * Unregister for data call list changed event.
+ *
+ * @param h The handler
+ */
+ public void unregisterForDataCallListChanged(Handler h) {
+ if (h != null) {
+ mDataCallListChangedRegistrants.remove(h);
+ }
+ }
+
+ /**
+ * Register for data service binding status changed event.
+ *
+ * @param h The target to post the event message to.
+ * @param what The event.
+ * @param obj The user object.
+ */
+ public void registerForServiceBindingChanged(Handler h, int what, Object obj) {
+ if (h != null) {
+ mServiceBindingChangedRegistrants.addUnique(h, what, obj);
+ }
+
+ }
+
+ /**
+ * Unregister for data service binding status changed event.
+ *
+ * @param h The handler
+ */
+ public void unregisterForServiceBindingChanged(Handler h) {
+ if (h != null) {
+ mServiceBindingChangedRegistrants.remove(h);
+ }
+ }
+
+ /**
+ * Get the transport type. Must be a {@link AccessNetworkConstants.TransportType}.
+ *
+ * @return
+ */
+ public int getTransportType() {
+ return mTransportType;
+ }
+
+ private void log(String s) {
+ Rlog.d(TAG, s);
+ }
+
+ private void loge(String s) {
+ Rlog.e(TAG, s);
+ }
+
+}