Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c5c0eb63 authored by Jack Yu's avatar Jack Yu Committed by android-build-merger
Browse files

Merge "Added cellular data service" am: de35e3b7 am: 79c3584e

am: dc04ebc1

Change-Id: I50cee6e39036cf7bf09261f93c5713d85612731a
parents 37bb8b55 dc04ebc1
Loading
Loading
Loading
Loading
+334 −0
Original line number Diff line number Diff line
/*
 * 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.hardware.radio.V1_0.SetupDataCallResult;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.NetworkUtils;
import android.os.AsyncResult;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.telephony.Rlog;
import android.telephony.SubscriptionManager;
import android.telephony.data.DataCallResponse;
import android.telephony.data.DataProfile;
import android.telephony.data.DataService;
import android.telephony.data.DataServiceCallback;
import android.text.TextUtils;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneFactory;

import java.net.Inet4Address;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * This class represents cellular data service which handles telephony data requests and response
 * from the cellular modem.
 */
public class CellularDataService extends DataService {
    private static final String TAG = CellularDataService.class.getSimpleName();

    private static final boolean DBG = false;

    private static final int SETUP_DATA_CALL_COMPLETE               = 1;
    private static final int DEACTIVATE_DATA_ALL_COMPLETE           = 2;
    private static final int SET_INITIAL_ATTACH_APN_COMPLETE        = 3;
    private static final int SET_DATA_PROFILE_COMPLETE              = 4;
    private static final int GET_DATA_CALL_LIST_COMPLETE            = 5;
    private static final int DATA_CALL_LIST_CHANGED                 = 6;

    private class CellularDataServiceProvider extends DataService.DataServiceProvider {

        private final Map<Message, DataServiceCallback> mCallbackMap = new HashMap<>();

        private final Looper mLooper;

        private final Handler mHandler;

        private final Phone mPhone;

        private CellularDataServiceProvider(int slotId) {
            super(slotId);

            mPhone = PhoneFactory.getPhone(getSlotId());

            HandlerThread thread = new HandlerThread(CellularDataService.class.getSimpleName());
            thread.start();
            mLooper = thread.getLooper();
            mHandler = new Handler(mLooper) {
                @Override
                public void handleMessage(Message message) {
                    DataServiceCallback callback = mCallbackMap.remove(message);

                    AsyncResult ar = (AsyncResult) message.obj;
                    switch (message.what) {
                        case SETUP_DATA_CALL_COMPLETE:
                            SetupDataCallResult result = (SetupDataCallResult) ar.result;
                            callback.onSetupDataCallComplete(ar.exception != null
                                    ? DataServiceCallback.RESULT_ERROR_ILLEGAL_STATE
                                    : DataServiceCallback.RESULT_SUCCESS,
                                    convertDataCallResult(result));
                            break;
                        case DEACTIVATE_DATA_ALL_COMPLETE:
                            callback.onDeactivateDataCallComplete(ar.exception != null
                                    ? DataServiceCallback.RESULT_ERROR_ILLEGAL_STATE
                                    : DataServiceCallback.RESULT_SUCCESS);
                            break;
                        case SET_INITIAL_ATTACH_APN_COMPLETE:
                            callback.onSetInitialAttachApnComplete(ar.exception != null
                                    ? DataServiceCallback.RESULT_ERROR_ILLEGAL_STATE
                                    : DataServiceCallback.RESULT_SUCCESS);
                            break;
                        case SET_DATA_PROFILE_COMPLETE:
                            callback.onSetDataProfileComplete(ar.exception != null
                                    ? DataServiceCallback.RESULT_ERROR_ILLEGAL_STATE
                                    : DataServiceCallback.RESULT_SUCCESS);
                            break;
                        case GET_DATA_CALL_LIST_COMPLETE:
                            callback.onGetDataCallListComplete(
                                    ar.exception != null
                                            ? DataServiceCallback.RESULT_ERROR_ILLEGAL_STATE
                                            : DataServiceCallback.RESULT_SUCCESS,
                                    ar.exception != null
                                            ? null
                                            : getDataCallList((List<SetupDataCallResult>) ar.result)
                                    );
                            break;
                        case DATA_CALL_LIST_CHANGED:
                            notifyDataCallListChanged(getDataCallList(
                                    (List<SetupDataCallResult>) ar.result));
                            break;
                        default:
                            loge("Unexpected event: " + message.what);
                            return;
                    }
                }
            };

            if (DBG) log("Register for data call list changed.");
            mPhone.mCi.registerForDataCallListChanged(mHandler, DATA_CALL_LIST_CHANGED, null);
        }

        private List<DataCallResponse> getDataCallList(List<SetupDataCallResult> dcList) {
            List<DataCallResponse> dcResponseList = new ArrayList<>();
            for (SetupDataCallResult dcResult : dcList) {
                dcResponseList.add(convertDataCallResult(dcResult));
            }
            return dcResponseList;
        }

        @Override
        public void setupDataCall(int radioTechnology, DataProfile dataProfile, boolean isRoaming,
                                  boolean allowRoaming, int reason, LinkProperties linkProperties,
                                  DataServiceCallback callback) {
            if (DBG) log("setupDataCall " + getSlotId());

            Message message = null;
            // Only obtain the message when the caller wants a callback. If the caller doesn't care
            // the request completed or results, then no need to pass the message down.
            if (callback != null) {
                message = Message.obtain(mHandler, SETUP_DATA_CALL_COMPLETE);
                mCallbackMap.put(message, callback);
            }

            mPhone.mCi.setupDataCall(radioTechnology, dataProfile, isRoaming, allowRoaming, reason,
                    linkProperties, message);
        }

        @Override
        public void deactivateDataCall(int cid, int reason, DataServiceCallback callback) {
            if (DBG) log("deactivateDataCall " + getSlotId());

            Message message = null;
            // Only obtain the message when the caller wants a callback. If the caller doesn't care
            // the request completed or results, then no need to pass the message down.
            if (callback != null) {
                message = Message.obtain(mHandler, DEACTIVATE_DATA_ALL_COMPLETE);
                mCallbackMap.put(message, callback);
            }

            mPhone.mCi.deactivateDataCall(cid, reason, message);
        }

        @Override
        public void setInitialAttachApn(DataProfile dataProfile, boolean isRoaming,
                                        DataServiceCallback callback) {
            if (DBG) log("setInitialAttachApn " + getSlotId());

            Message message = null;
            // Only obtain the message when the caller wants a callback. If the caller doesn't care
            // the request completed or results, then no need to pass the message down.
            if (callback != null) {
                message = Message.obtain(mHandler, SET_INITIAL_ATTACH_APN_COMPLETE);
                mCallbackMap.put(message, callback);
            }

            mPhone.mCi.setInitialAttachApn(dataProfile, isRoaming, message);
        }

        @Override
        public void setDataProfile(List<DataProfile> dps, boolean isRoaming,
                                   DataServiceCallback callback) {
            if (DBG) log("setDataProfile " + getSlotId());

            Message message = null;
            // Only obtain the message when the caller wants a callback. If the caller doesn't care
            // the request completed or results, then no need to pass the message down.
            if (callback != null) {
                message = Message.obtain(mHandler, SET_DATA_PROFILE_COMPLETE);
                mCallbackMap.put(message, callback);
            }

            mPhone.mCi.setDataProfile(dps.toArray(new DataProfile[dps.size()]), isRoaming, message);
        }

        @Override
        public void getDataCallList(DataServiceCallback callback) {
            if (DBG) log("getDataCallList " + getSlotId());

            Message message = null;
            // Only obtain the message when the caller wants a callback. If the caller doesn't care
            // the request completed or results, then no need to pass the message down.
            if (callback != null) {
                message = Message.obtain(mHandler, GET_DATA_CALL_LIST_COMPLETE);
                mCallbackMap.put(message, callback);
            }
            mPhone.mCi.getDataCallList(message);
        }
    }

    @Override
    public DataServiceProvider createDataServiceProvider(int slotId) {
        log("Cellular data service created for slot " + slotId);
        if (!SubscriptionManager.isValidSlotIndex(slotId)) {
            loge("Tried to cellular data service with invalid slotId " + slotId);
            return null;
        }
        return new CellularDataServiceProvider(slotId);
    }

    /**
     * Convert SetupDataCallResult defined in types.hal into DataCallResponse
     * @param dcResult setup data call result
     * @return converted DataCallResponse object
     */
    @VisibleForTesting
    public DataCallResponse convertDataCallResult(SetupDataCallResult dcResult) {
        if (dcResult == null) return null;

        // Process address
        String[] addresses = null;
        if (!TextUtils.isEmpty(dcResult.addresses)) {
            addresses = dcResult.addresses.split("\\s+");
        }

        List<LinkAddress> laList = new ArrayList<>();
        if (addresses != null) {
            for (String address : addresses) {
                address = address.trim();
                if (address.isEmpty()) continue;

                try {
                    LinkAddress la;
                    // Check if the address contains prefix length. If yes, LinkAddress
                    // can parse that.
                    if (address.split("/").length == 2) {
                        la = new LinkAddress(address);
                    } else {
                        InetAddress ia = NetworkUtils.numericToInetAddress(address);
                        la = new LinkAddress(ia, (ia instanceof Inet4Address) ? 32 : 128);
                    }

                    laList.add(la);
                } catch (IllegalArgumentException e) {
                    loge("Unknown address: " + address + ", exception = " + e);
                }
            }
        }

        // Process dns
        String[] dnses = null;
        if (!TextUtils.isEmpty(dcResult.dnses)) {
            dnses = dcResult.dnses.split("\\s+");
        }

        List<InetAddress> dnsList = new ArrayList<>();
        if (dnses != null) {
            for (String dns : dnses) {
                dns = dns.trim();
                InetAddress ia;
                try {
                    ia = NetworkUtils.numericToInetAddress(dns);
                    dnsList.add(ia);
                } catch (IllegalArgumentException e) {
                    loge("Unknown dns: " + dns + ", exception = " + e);
                }
            }
        }

        // Process gateway
        String[] gateways = null;
        if (!TextUtils.isEmpty(dcResult.gateways)) {
            gateways = dcResult.gateways.split("\\s+");
        }

        List<InetAddress> gatewayList = new ArrayList<>();
        if (gateways != null) {
            for (String gateway : gateways) {
                gateway = gateway.trim();
                InetAddress ia;
                try {
                    ia = NetworkUtils.numericToInetAddress(gateway);
                    gatewayList.add(ia);
                } catch (IllegalArgumentException e) {
                    loge("Unknown gateway: " + gateway + ", exception = " + e);
                }
            }
        }

        return new DataCallResponse(dcResult.status,
                dcResult.suggestedRetryTime,
                dcResult.cid,
                dcResult.active,
                dcResult.type,
                dcResult.ifname,
                laList,
                dnsList,
                gatewayList,
                new ArrayList<>(Arrays.asList(dcResult.pcscf.trim().split("\\s+"))),
                dcResult.mtu
        );
    }

    private void log(String s) {
        Rlog.d(TAG, s);
    }

    private void loge(String s) {
        Rlog.e(TAG, s);
    }
}
+102 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 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;


import static junit.framework.Assert.assertEquals;

import android.hardware.radio.V1_0.SetupDataCallResult;
import android.net.LinkAddress;
import android.net.NetworkUtils;
import android.telephony.data.DataCallResponse;

import com.android.internal.telephony.dataconnection.CellularDataService;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.util.Arrays;

public class CellularDataServiceTest extends TelephonyTest {

    private CellularDataService mCellularDataService;

    @Before
    public void setUp() throws Exception {
        super.setUp(getClass().getSimpleName());
        mCellularDataService = new CellularDataService();
    }

    @After
    public void tearDown() throws Exception {
        super.tearDown();
    }

    @Test
    public void testConvertDataCallResult() throws Exception {

        SetupDataCallResult result = new SetupDataCallResult();
        result.status = 0;
        result.suggestedRetryTime = -1;
        result.cid = 1;
        result.active = 1;
        result.type = "IP";
        result.ifname = "eth0";
        result.addresses = "10.0.2.15";
        result.dnses = "10.0.2.3";
        result.gateways = "10.0.2.15 fe80::2";
        result.pcscf = "";
        result.mtu = 1500;

        DataCallResponse response = new DataCallResponse(0, -1, 1, 1, "IP",
                "eth0",
                Arrays.asList(new LinkAddress(NetworkUtils.numericToInetAddress("10.0.2.15"), 32)),
                Arrays.asList(NetworkUtils.numericToInetAddress("10.0.2.3")),
                Arrays.asList(NetworkUtils.numericToInetAddress("10.0.2.15"),
                        NetworkUtils.numericToInetAddress("fe80::2")),
                Arrays.asList(""),
                1500);

        assertEquals(response, mCellularDataService.convertDataCallResult(result));

        result.status = 0;
        result.suggestedRetryTime = -1;
        result.cid = 0;
        result.active = 2;
        result.type = "IPV4V6";
        result.ifname = "ifname";
        result.addresses = "2607:fb90:a620:651d:eabe:f8da:c107:44be/64";
        result.dnses = "fd00:976a::9      fd00:976a::10";
        result.gateways = "fe80::4c61:1832:7b28:d36c    1.2.3.4";
        result.pcscf = "fd00:976a:c206:20::6   fd00:976a:c206:20::9    fd00:976a:c202:1d::9";
        result.mtu = 1500;

        response = new DataCallResponse(0, -1, 0, 2, "IPV4V6",
                "ifname",
                Arrays.asList(new LinkAddress("2607:fb90:a620:651d:eabe:f8da:c107:44be/64")),
                Arrays.asList(NetworkUtils.numericToInetAddress("fd00:976a::9"),
                        NetworkUtils.numericToInetAddress("fd00:976a::10")),
                Arrays.asList(NetworkUtils.numericToInetAddress("fe80::4c61:1832:7b28:d36c"),
                        NetworkUtils.numericToInetAddress("1.2.3.4")),
                Arrays.asList("fd00:976a:c206:20::6", "fd00:976a:c206:20::9",
                        "fd00:976a:c202:1d::9"),
                1500);

        assertEquals(response, mCellularDataService.convertDataCallResult(result));
    }
}