Merge "Added data service manager"
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);
+    }
+
+}