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

Commit 8557ae4c authored by Hui Wang's avatar Hui Wang
Browse files

Add new Telephony APIs for Cell Broadcast

Bug: 256043136
Test: atest CtsTelephonyTestCases
Test: atest com.android.internal.telephony.GsmCdmaPhoneTest
Test: manual
Change-Id: Ie7f8b82820b930ea688cff68d1ce194264e6c580
parent 7c4d8d73
Loading
Loading
Loading
Loading
+461 −0
Original line number Diff line number Diff line
/*
 * Copyright 2022 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 android.annotation.NonNull;
import android.os.AsyncResult;
import android.os.Build;
import android.os.Handler;
import android.os.Message;
import android.telephony.CellBroadcastIdRange;
import android.telephony.SmsCbMessage;
import android.telephony.TelephonyManager;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.cdma.CdmaSmsBroadcastConfigInfo;
import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Consumer;

/**
 * This class is to track the state to set cell broadcast config
 */

public final class CellBroadcastConfigTracker extends StateMachine {
    private static final boolean DBG = Build.IS_DEBUGGABLE;

    private static final int EVENT_REQUEST = 1;
    private static final int EVENT_CONFIGURATION_DONE = 2;
    private static final int EVENT_ACTIVATION_DONE = 3;

    private static final int SMS_CB_CODE_SCHEME_MIN = 0;
    private static final int SMS_CB_CODE_SCHEME_MAX = 255;

    // Cache of current cell broadcast id ranges of 3gpp
    private List<CellBroadcastIdRange> mCbRanges3gpp = new CopyOnWriteArrayList<>();
    // Cache of current cell broadcast id ranges of 3gpp2
    private List<CellBroadcastIdRange> mCbRanges3gpp2 = new CopyOnWriteArrayList<>();
    private Phone mPhone;


    /**
     * The class is to present the request to set cell broadcast id ranges
     */
    private static class Request {
        private final List<CellBroadcastIdRange> mCbRangesRequest3gpp =
                new CopyOnWriteArrayList<>();
        private final List<CellBroadcastIdRange> mCbRangesRequest3gpp2 =
                new CopyOnWriteArrayList<>();
        Consumer<Integer> mCallback;

        Request(@NonNull List<CellBroadcastIdRange> ranges, @NonNull Consumer<Integer> callback) {
            ranges.forEach(r -> {
                if (r.getType() == SmsCbMessage.MESSAGE_FORMAT_3GPP) {
                    mCbRangesRequest3gpp.add(r);
                } else {
                    mCbRangesRequest3gpp2.add(r);
                }
            });
            mCallback = callback;
        }

        List<CellBroadcastIdRange> get3gppRanges() {
            return mCbRangesRequest3gpp;
        }

        List<CellBroadcastIdRange> get3gpp2Ranges() {
            return mCbRangesRequest3gpp2;
        }

        Consumer<Integer> getCallback() {
            return mCallback;
        }

        @Override
        public String toString() {
            return "Request[mCbRangesRequest3gpp = " + mCbRangesRequest3gpp + ", "
                    + "mCbRangesRequest3gpp2 = " + mCbRangesRequest3gpp2 + ", "
                    + "mCallback = " + mCallback + "]";
        }
    }

    /*
     * The idle state which does not have ongoing radio request.
     */
    private class IdleState extends State {
        @Override
        public boolean processMessage(Message msg) {
            boolean retVal = NOT_HANDLED;
            if (DBG) {
                logd("IdleState message:" + msg.what);
            }
            switch (msg.what) {
                case EVENT_REQUEST:
                    Request request = (Request) msg.obj;
                    if (DBG) {
                        logd("IdleState handle EVENT_REQUEST with request:" + request);
                    }
                    if (!mCbRanges3gpp.equals(request.get3gppRanges())) {
                        // set gsm config if the config is changed
                        setGsmConfig(request.get3gppRanges(), request);
                        transitionTo(mGsmConfiguringState);
                    } else if (!mCbRanges3gpp2.equals(request.get3gpp2Ranges())) {
                        // set cdma config directly if no gsm config change but cdma config is
                        // changed
                        setCdmaConfig(request.get3gpp2Ranges(), request);
                        transitionTo(mCdmaConfiguringState);
                    } else {
                        logd("Do nothing as the requested ranges are same as now");
                        request.getCallback().accept(TelephonyManager.CELLBROADCAST_RESULT_SUCCESS);
                    }
                    retVal = HANDLED;
                    break;
                default:
                    break;
            }
            return retVal;
        }
    }
    private IdleState mIdleState = new IdleState();

    /*
     * The state waiting for the result to set gsm config.
     */
    private class GsmConfiguringState extends State {
        @Override
        public boolean processMessage(Message msg) {
            boolean retVal = NOT_HANDLED;
            if (DBG) {
                logd("GsmConfiguringState message:" + msg.what);
            }
            switch (msg.what) {
                case EVENT_REQUEST:
                    deferMessage(msg);
                    retVal = HANDLED;
                    break;
                case EVENT_CONFIGURATION_DONE:
                    AsyncResult ar = (AsyncResult) msg.obj;
                    Request request = (Request) ar.userObj;
                    if (DBG) {
                        logd("GsmConfiguringState handle EVENT_CONFIGURATION_DONE with request:"
                                + request);
                    }
                    if (ar.exception == null) {
                        // set gsm activation and transit to gsm activating state
                        setActivation(SmsCbMessage.MESSAGE_FORMAT_3GPP,
                                !request.get3gppRanges().isEmpty(), request);
                        transitionTo(mGsmActivatingState);
                    } else {
                        logd("Failed to set gsm config");
                        request.getCallback().accept(
                                TelephonyManager.CELLBROADCAST_RESULT_FAIL_CONFIG);
                        // transit to idle state on the failure case
                        transitionTo(mIdleState);
                    }
                    retVal = HANDLED;
                    break;
                default:
                    break;
            }
            return retVal;
        }
    }
    private GsmConfiguringState mGsmConfiguringState = new GsmConfiguringState();

    /*
     * The state waiting for the result to set gsm activation.
     */
    private class GsmActivatingState extends State {
        @Override
        public boolean processMessage(Message msg) {
            boolean retVal = NOT_HANDLED;
            if (DBG) {
                logd("GsmActivatingState message:" + msg.what);
            }
            switch (msg.what) {
                case EVENT_REQUEST:
                    deferMessage(msg);
                    retVal = HANDLED;
                    break;
                case EVENT_ACTIVATION_DONE:
                    AsyncResult ar = (AsyncResult) msg.obj;
                    Request request = (Request) ar.userObj;
                    if (DBG) {
                        logd("GsmActivatingState handle EVENT_ACTIVATION_DONE with request:"
                                + request);
                    }
                    if (ar.exception == null) {
                        mCbRanges3gpp = request.get3gppRanges();
                        if (!mCbRanges3gpp2.equals(request.get3gpp2Ranges())) {
                            // set cdma config and transit to cdma configuring state if the config
                            // is changed.
                            setCdmaConfig(request.get3gpp2Ranges(), request);
                            transitionTo(mCdmaConfiguringState);
                        } else {
                            logd("Done as no need to update ranges for 3gpp2");
                            request.getCallback().accept(
                                    TelephonyManager.CELLBROADCAST_RESULT_SUCCESS);
                            // transit to idle state if there is no cdma config change
                            transitionTo(mIdleState);
                        }
                    } else {
                        logd("Failed to set gsm activation");
                        request.getCallback().accept(
                                TelephonyManager.CELLBROADCAST_RESULT_FAIL_ACTIVATION);
                        // transit to idle state on the failure case
                        transitionTo(mIdleState);
                    }
                    retVal = HANDLED;
                    break;
                default:
                    break;
            }
            return retVal;
        }
    }
    private GsmActivatingState mGsmActivatingState = new GsmActivatingState();

    /*
     * The state waiting for the result to set cdma config.
     */
    private class CdmaConfiguringState extends State {
        @Override
        public boolean processMessage(Message msg) {
            boolean retVal = NOT_HANDLED;
            if (DBG) {
                logd("CdmaConfiguringState message:" + msg.what);
            }
            switch (msg.what) {
                case EVENT_REQUEST:
                    deferMessage(msg);
                    retVal = HANDLED;
                    break;
                case EVENT_CONFIGURATION_DONE:
                    AsyncResult ar = (AsyncResult) msg.obj;
                    Request request = (Request) ar.userObj;
                    if (DBG) {
                        logd("CdmaConfiguringState handle EVENT_ACTIVATION_DONE with request:"
                                + request);
                    }
                    if (ar.exception == null) {
                        // set cdma activation and transit to cdma activating state
                        setActivation(SmsCbMessage.MESSAGE_FORMAT_3GPP2,
                                !request.get3gpp2Ranges().isEmpty(), request);
                        transitionTo(mCdmaActivatingState);
                    } else {
                        logd("Failed to set cdma config");
                        request.getCallback().accept(
                                TelephonyManager.CELLBROADCAST_RESULT_FAIL_CONFIG);
                        // transit to idle state on the failure case
                        transitionTo(mIdleState);
                    }
                    retVal = HANDLED;
                    break;
                default:
                    break;
            }
            return retVal;
        }
    }
    private CdmaConfiguringState mCdmaConfiguringState = new CdmaConfiguringState();

    /*
     * The state waiting for the result to set cdma activation.
     */
    private class CdmaActivatingState extends State {
        @Override
        public boolean processMessage(Message msg) {
            boolean retVal = NOT_HANDLED;
            if (DBG) {
                logd("CdmaActivatingState message:" + msg.what);
            }
            switch (msg.what) {
                case EVENT_REQUEST:
                    deferMessage(msg);
                    retVal = HANDLED;
                    break;
                case EVENT_ACTIVATION_DONE:
                    AsyncResult ar = (AsyncResult) msg.obj;
                    Request request = (Request) ar.userObj;
                    if (DBG) {
                        logd("CdmaActivatingState handle EVENT_ACTIVATION_DONE with request:"
                                + request);
                    }
                    if (ar.exception == null) {
                        mCbRanges3gpp2 = request.get3gpp2Ranges();
                        request.getCallback().accept(
                                    TelephonyManager.CELLBROADCAST_RESULT_SUCCESS);
                    } else {
                        logd("Failed to set cdma activation");
                        request.getCallback().accept(
                                TelephonyManager.CELLBROADCAST_RESULT_FAIL_ACTIVATION);
                    }
                    // transit to idle state anyway
                    transitionTo(mIdleState);
                    retVal = HANDLED;
                    break;
                default:
                    break;
            }
            return retVal;
        }
    }
    private CdmaActivatingState mCdmaActivatingState = new CdmaActivatingState();

    private CellBroadcastConfigTracker(Phone phone) {
        super("CellBroadcastConfigTracker-" + phone.getPhoneId());
        init(phone);
    }

    private CellBroadcastConfigTracker(Phone phone, Handler handler) {
        super("CellBroadcastConfigTracker-" + phone.getPhoneId(), handler);
        init(phone);
    }

    private void init(Phone phone) {
        logd("init");
        mPhone = phone;

        addState(mIdleState);
        addState(mGsmConfiguringState);
        addState(mGsmActivatingState);
        addState(mCdmaConfiguringState);
        addState(mCdmaActivatingState);
        setInitialState(mIdleState);
    }

    /**
     * create a CellBroadcastConfigTracker instance for the phone
     */
    public static CellBroadcastConfigTracker make(Phone phone, Handler handler) {
        CellBroadcastConfigTracker tracker = handler == null
                ? new CellBroadcastConfigTracker(phone)
                : new CellBroadcastConfigTracker(phone, handler);
        tracker.start();
        return tracker;
    }

    /**
     * Return current cell broadcast ranges.
     */
    @NonNull public List<CellBroadcastIdRange> getCellBroadcastIdRanges() {
        List<CellBroadcastIdRange> ranges = new ArrayList<>();
        ranges.addAll(mCbRanges3gpp);
        ranges.addAll(mCbRanges3gpp2);
        return ranges;
    }

    /**
     * Set reception of cell broadcast messages with the list of the given ranges.
     */
    public void setCellBroadcastIdRanges(
            @NonNull List<CellBroadcastIdRange> ranges, @NonNull Consumer<Integer> callback) {
        if (DBG) {
            logd("setCellBroadcastIdRanges with ranges:" + ranges);
        }
        ranges = mergeRangesAsNeeded(ranges);
        sendMessage(EVENT_REQUEST, new Request(ranges, callback));
    }

    /**
     * Merge the overlapped CellBroadcastIdRanges in the list as needed
     * @param ranges the list of CellBroadcastIdRanges
     * @return the list of CellBroadcastIdRanges without overlapping
     *
     * @throws IllegalArgumentException if there is conflict of the ranges. For instance,
     * the channel is enabled in some range, but disable in others.
     */
    @VisibleForTesting
    public static @NonNull List<CellBroadcastIdRange> mergeRangesAsNeeded(
            @NonNull List<CellBroadcastIdRange> ranges) throws IllegalArgumentException {
        ranges.sort((r1, r2) -> r1.getType() != r2.getType() ? r1.getType() - r2.getType()
                : (r1.getStartId() != r2.getStartId() ? r1.getStartId() - r2.getStartId()
                : r2.getEndId() - r1.getEndId()));
        final List<CellBroadcastIdRange> newRanges = new ArrayList<>();
        ranges.forEach(r -> {
            if (newRanges.isEmpty() || newRanges.get(newRanges.size() - 1).getType() != r.getType()
                    || newRanges.get(newRanges.size() - 1).getEndId() + 1 < r.getStartId()
                    || (newRanges.get(newRanges.size() - 1).getEndId() + 1 == r.getStartId()
                    && newRanges.get(newRanges.size() - 1).isEnabled() != r.isEnabled())) {
                newRanges.add(new CellBroadcastIdRange(r.getStartId(), r.getEndId(),
                        r.getType(), r.isEnabled()));
            } else {
                if (newRanges.get(newRanges.size() - 1).isEnabled() != r.isEnabled()) {
                    throw new IllegalArgumentException("range conflict " + r);
                }
                if (r.getEndId() > newRanges.get(newRanges.size() - 1).getEndId()) {
                    CellBroadcastIdRange range = newRanges.get(newRanges.size() - 1);
                    newRanges.set(newRanges.size() - 1, new CellBroadcastIdRange(
                            range.getStartId(), r.getEndId(), range.getType(), range.isEnabled()));
                }
            }
        });
        return newRanges;
    }

    private void setGsmConfig(List<CellBroadcastIdRange> ranges, Request request) {
        if (DBG) {
            logd("setGsmConfig with " + ranges);
        }

        SmsBroadcastConfigInfo[] configs = new SmsBroadcastConfigInfo[ranges.size()];
        for (int i = 0; i < configs.length; i++) {
            CellBroadcastIdRange r = ranges.get(i);
            configs[i] = new SmsBroadcastConfigInfo(r.getStartId(), r.getEndId(),
                    SMS_CB_CODE_SCHEME_MIN, SMS_CB_CODE_SCHEME_MAX, r.isEnabled());
        }

        Message response = obtainMessage(EVENT_CONFIGURATION_DONE, request);
        mPhone.mCi.setGsmBroadcastConfig(configs, response);
    }

    private void setCdmaConfig(List<CellBroadcastIdRange> ranges, Request request) {
        if (DBG) {
            logd("setCdmaConfig with " + ranges);
        }

        CdmaSmsBroadcastConfigInfo[] configs =
                new CdmaSmsBroadcastConfigInfo[ranges.size()];
        for (int i = 0; i < configs.length; i++) {
            CellBroadcastIdRange r = ranges.get(i);
            configs[i] = new CdmaSmsBroadcastConfigInfo(
                    r.getStartId(), r.getEndId(), 1, r.isEnabled());
        }

        Message response = obtainMessage(EVENT_CONFIGURATION_DONE, request);
        mPhone.mCi.setCdmaBroadcastConfig(configs, response);
    }

    private void setActivation(int type, boolean activate, Request request) {
        if (DBG) {
            logd("setActivation(" + type + "." + activate + ')');
        }

        Message response = obtainMessage(EVENT_ACTIVATION_DONE, request);

        if (type == SmsCbMessage.MESSAGE_FORMAT_3GPP) {
            mPhone.mCi.setGsmBroadcastActivation(activate, response);
        } else if (type == SmsCbMessage.MESSAGE_FORMAT_3GPP2) {
            mPhone.mCi.setCdmaBroadcastActivation(activate, response);
        }
    }
}
+20 −0
Original line number Diff line number Diff line
@@ -71,6 +71,7 @@ import android.telephony.Annotation.DataActivityType;
import android.telephony.Annotation.RadioPowerState;
import android.telephony.BarringInfo;
import android.telephony.CarrierConfigManager;
import android.telephony.CellBroadcastIdRange;
import android.telephony.CellIdentity;
import android.telephony.ImsiEncryptionInfo;
import android.telephony.LinkCapacityEstimate;
@@ -237,6 +238,9 @@ public class GsmCdmaPhone extends Phone {
    private String mImeiSv;
    private String mVmNumber;

    CellBroadcastConfigTracker mCellBroadcastConfigTracker =
            CellBroadcastConfigTracker.make(this, null);

    // Create Cfu (Call forward unconditional) so that dialing number &
    // mOnComplete (Message object passed by client) can be packed &
    // given as a single Cfu object as user data to RIL.
@@ -4960,6 +4964,22 @@ public class GsmCdmaPhone extends Phone {
        return mIccSmsInterfaceManager.getInboundSmsHandler(is3gpp2);
    }

    /**
     * Return current cell broadcast ranges.
     */
    public List<CellBroadcastIdRange> getCellBroadcastIdRanges() {
        return mCellBroadcastConfigTracker.getCellBroadcastIdRanges();
    }

    /**
     * Set reception of cell broadcast messages with the list of the given ranges.
     */
    @Override
    public void setCellBroadcastIdRanges(
            @NonNull List<CellBroadcastIdRange> ranges, Consumer<Integer> callback) {
        mCellBroadcastConfigTracker.setCellBroadcastIdRanges(ranges, callback);
    }

    /**
     * The following function checks if supplementary service request is blocked due to FDN.
     * @param requestType request type associated with the supplementary service
+16 −0
Original line number Diff line number Diff line
@@ -46,6 +46,7 @@ import android.telephony.AccessNetworkConstants;
import android.telephony.Annotation.SrvccState;
import android.telephony.CarrierConfigManager;
import android.telephony.CarrierRestrictionRules;
import android.telephony.CellBroadcastIdRange;
import android.telephony.CellIdentity;
import android.telephony.CellInfo;
import android.telephony.ClientRequestStats;
@@ -5109,6 +5110,21 @@ public abstract class Phone extends Handler implements PhoneInternalInterface {
        mCi.isN1ModeEnabled(result);
    }

    /**
     * Return current cell broadcast ranges.
     */
    public List<CellBroadcastIdRange> getCellBroadcastIdRanges() {
        return new ArrayList<>();
    }

    /**
     * Set reception of cell broadcast messages with the list of the given ranges.
     */
    public void setCellBroadcastIdRanges(
            @NonNull List<CellBroadcastIdRange> ranges, Consumer<Integer> callback) {
        callback.accept(TelephonyManager.CELLBROADCAST_RESULT_UNSUPPORTED);
    }

    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
        pw.println("Phone: subId=" + getSubId());
        pw.println(" mPhoneId=" + mPhoneId);
+20 −0
Original line number Diff line number Diff line
@@ -17,6 +17,8 @@

package com.android.internal.telephony.cdma;

import java.util.Objects;

/**
 * CdmaSmsBroadcastConfigInfo defines one configuration of Cdma Broadcast
 * Message to be received by the ME
@@ -84,4 +86,22 @@ public class CdmaSmsBroadcastConfigInfo {
            mFromServiceCategory + ", " + mToServiceCategory + "] " +
            (isSelected() ? "ENABLED" : "DISABLED");
    }

    @Override
    public int hashCode() {
        return Objects.hash(mFromServiceCategory, mToServiceCategory, mLanguage, mSelected);
    }

    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof CdmaSmsBroadcastConfigInfo)) {
            return false;
        }

        CdmaSmsBroadcastConfigInfo other = (CdmaSmsBroadcastConfigInfo) obj;

        return mFromServiceCategory == other.mFromServiceCategory
                && mToServiceCategory == other.mToServiceCategory
                && mLanguage == other.mLanguage && mSelected == other.mSelected;
    }
}
+376 −0

File changed.

Preview size limit exceeded, changes collapsed.