/*
 * Copyright (C) 2008 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 android.telephony.SmsManager.STATUS_ON_ICC_FREE;
import static android.telephony.SmsManager.STATUS_ON_ICC_READ;
import static android.telephony.SmsManager.STATUS_ON_ICC_UNREAD;

import android.Manifest;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.app.AppOpsManager;
import android.app.PendingIntent;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.database.sqlite.SQLiteException;
import android.net.Uri;
import android.os.AsyncResult;
import android.os.Binder;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.provider.Telephony;
import android.telephony.SmsCbMessage;
import android.telephony.SmsManager;
import android.telephony.SmsMessage;
import android.telephony.emergency.EmergencyNumber;
import android.util.LocalLog;
import android.util.Log;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.cdma.CdmaSmsBroadcastConfigInfo;
import com.android.internal.telephony.emergency.EmergencyNumberTracker;
import com.android.internal.telephony.flags.FeatureFlags;
import com.android.internal.telephony.flags.Flags;
import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo;
import com.android.internal.telephony.uicc.IccConstants;
import com.android.internal.telephony.uicc.IccFileHandler;
import com.android.internal.telephony.uicc.IccUtils;
import com.android.internal.telephony.uicc.UiccController;
import com.android.internal.telephony.uicc.UiccProfile;
import com.android.internal.util.HexDump;
import com.android.telephony.Rlog;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * IccSmsInterfaceManager to provide an inter-process communication to
 * access Sms in Icc.
 */
public class IccSmsInterfaceManager {
    static final String LOG_TAG = "IccSmsInterfaceManager";
    static final boolean DBG = true;

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    private CellBroadcastRangeManager mCellBroadcastRangeManager =
            new CellBroadcastRangeManager();
    private CdmaBroadcastRangeManager mCdmaBroadcastRangeManager =
            new CdmaBroadcastRangeManager();

    private static final int EVENT_LOAD_DONE = 1;
    private static final int EVENT_UPDATE_DONE = 2;
    protected static final int EVENT_SET_BROADCAST_ACTIVATION_DONE = 3;
    protected static final int EVENT_SET_BROADCAST_CONFIG_DONE = 4;
    private static final int EVENT_GET_SMSC_DONE = 5;
    private static final int EVENT_SET_SMSC_DONE = 6;
    private static final int SMS_CB_CODE_SCHEME_MIN = 0;
    private static final int SMS_CB_CODE_SCHEME_MAX = 255;
    public static final int SMS_MESSAGE_PRIORITY_NOT_SPECIFIED = -1;
    public static final int SMS_MESSAGE_PERIOD_NOT_SPECIFIED = -1;

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    protected Phone mPhone;
    @UnsupportedAppUsage
    final protected Context mContext;
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    final protected AppOpsManager mAppOps;
    public SmsDispatchersController mDispatchersController;
    private SmsPermissions mSmsPermissions;

    private final LocalLog mCellBroadcastLocalLog = new LocalLog(64);

    private static final class Request {
        AtomicBoolean mStatus = new AtomicBoolean(false);
        Object mResult = null;
    }

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    protected Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            AsyncResult ar = (AsyncResult) msg.obj;
            Request request = (Request) ar.userObj;

            switch (msg.what) {
                case EVENT_UPDATE_DONE:
                case EVENT_SET_BROADCAST_ACTIVATION_DONE:
                case EVENT_SET_BROADCAST_CONFIG_DONE:
                case EVENT_SET_SMSC_DONE:
                    notifyPending(request, ar.exception == null);
                    break;
                case EVENT_LOAD_DONE:
                    List<SmsRawData> smsRawDataList = null;
                    if (ar.exception == null) {
                        smsRawDataList = buildValidRawData((ArrayList<byte[]>) ar.result);
                        //Mark SMS as read after importing it from card.
                        markMessagesAsRead((ArrayList<byte[]>) ar.result);
                    } else {
                        if (Rlog.isLoggable("SMS", Log.DEBUG)) {
                            loge("Cannot load Sms records");
                        }
                    }
                    notifyPending(request, smsRawDataList);
                    break;
                case EVENT_GET_SMSC_DONE:
                    String smsc = null;
                    if (ar.exception == null) {
                        smsc = (String) ar.result;
                    } else {
                        loge("Cannot read SMSC");
                    }
                    notifyPending(request, smsc);
                    break;
            }
        }

        private void notifyPending(Request request, Object result) {
            if (request != null) {
                synchronized (request) {
                    request.mResult = result;
                    request.mStatus.set(true);
                    request.notifyAll();
                }
            }
        }
    };

    protected IccSmsInterfaceManager(Phone phone, @NonNull FeatureFlags featureFlags) {
        this(phone, phone.getContext(),
                (AppOpsManager) phone.getContext().getSystemService(Context.APP_OPS_SERVICE),
                new SmsDispatchersController(
                        phone, phone.mSmsStorageMonitor, phone.mSmsUsageMonitor, featureFlags),
                new SmsPermissions(phone, phone.getContext(),
                        (AppOpsManager) phone.getContext().getSystemService(
                                Context.APP_OPS_SERVICE)));
    }

    @VisibleForTesting
    public IccSmsInterfaceManager(
            Phone phone, Context context, AppOpsManager appOps,
            SmsDispatchersController dispatchersController, SmsPermissions smsPermissions) {
        mPhone = phone;
        mContext = context;
        mAppOps = appOps;
        mDispatchersController = dispatchersController;
        mSmsPermissions = smsPermissions;
    }

    /**
     * PhoneFactory Dependencies for testing.
     */
    @VisibleForTesting
    public interface PhoneFactoryProxy {
        Phone getPhone(int index);
        Phone getDefaultPhone();
        Phone[] getPhones();
    }

    private PhoneFactoryProxy mPhoneFactoryProxy = new PhoneFactoryProxy() {
        @Override
        public Phone getPhone(int index) {
            return PhoneFactory.getPhone(index);
        }

        @Override
        public Phone getDefaultPhone() {
            return PhoneFactory.getDefaultPhone();
        }

        @Override
        public Phone[] getPhones() {
            return PhoneFactory.getPhones();
        }
    };

    /**
     * Overrides PhoneFactory dependencies for testing.
     */
    @VisibleForTesting
    public void setPhoneFactoryProxy(PhoneFactoryProxy proxy) {
        mPhoneFactoryProxy = proxy;
    }

    private void enforceNotOnHandlerThread(String methodName) {
        if (Looper.myLooper() == mHandler.getLooper()) {
            throw new RuntimeException("This method " + methodName + " will deadlock if called from"
                    + " the handler's thread.");
        }
    }

    protected void markMessagesAsRead(ArrayList<byte[]> messages) {
        if (messages == null) {
            return;
        }

        //IccFileHandler can be null, if icc card is absent.
        IccFileHandler fh = mPhone.getIccFileHandler();
        if (fh == null) {
            //shouldn't really happen, as messages are marked as read, only
            //after importing it from icc.
            if (Rlog.isLoggable("SMS", Log.DEBUG)) {
                loge("markMessagesAsRead - aborting, no icc card present.");
            }
            return;
        }

        int count = messages.size();

        for (int i = 0; i < count; i++) {
            byte[] ba = messages.get(i);
            if ((ba[0] & 0x07) == STATUS_ON_ICC_UNREAD) {
                int n = ba.length;
                byte[] nba = new byte[n - 1];
                System.arraycopy(ba, 1, nba, 0, n - 1);
                byte[] record = makeSmsRecordData(STATUS_ON_ICC_READ, nba);
                fh.updateEFLinearFixed(IccConstants.EF_SMS, i + 1, record, null, null);
                if (Rlog.isLoggable("SMS", Log.DEBUG)) {
                    log("SMS " + (i + 1) + " marked as read");
                }
            }
        }
    }

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    protected void enforceReceiveAndSend(String message) {
        mContext.enforceCallingOrSelfPermission(
                Manifest.permission.RECEIVE_SMS, message);
        mContext.enforceCallingOrSelfPermission(
                Manifest.permission.SEND_SMS, message);
    }

    /**
     * Enforce the permission for access messages on ICC
     */
    private void enforceAccessMessageOnICC(String message) {
        mContext.enforceCallingOrSelfPermission(
                Manifest.permission.ACCESS_MESSAGES_ON_ICC, message);
    }

    /**
     * Update the specified message on the Icc.
     *
     * @param index record index of message to update
     * @param status new message status (STATUS_ON_ICC_READ,
     *                  STATUS_ON_ICC_UNREAD, STATUS_ON_ICC_SENT,
     *                  STATUS_ON_ICC_UNSENT, STATUS_ON_ICC_FREE)
     * @param pdu the raw PDU to store
     * @return success or not
     *
     */

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    public boolean
    updateMessageOnIccEf(String callingPackage, int index, int status, byte[] pdu) {
        if (DBG) log("updateMessageOnIccEf: index=" + index +
                " status=" + status + " ==> " +
                "("+ Arrays.toString(pdu) + ")");
        enforceReceiveAndSend("Updating message on Icc");
        enforceAccessMessageOnICC("Updating message on Icc");
        enforceNotOnHandlerThread("updateMessageOnIccEf");
        if (mAppOps.noteOp(AppOpsManager.OPSTR_WRITE_ICC_SMS, Binder.getCallingUid(),
                callingPackage) != AppOpsManager.MODE_ALLOWED) {
            return false;
        }
        Request updateRequest = new Request();
        synchronized (updateRequest) {
            Message response = mHandler.obtainMessage(EVENT_UPDATE_DONE, updateRequest);

            if ((status & 0x01) == STATUS_ON_ICC_FREE) {
                // RIL_REQUEST_DELETE_SMS_ON_SIM
                // Special case FREE: call deleteSmsOnSim instead of
                // manipulating the record
                // Will eventually fail if icc card is not present.
                mPhone.mCi.deleteSmsOnSim(index, response);
            } else {
                //IccFilehandler can be null if ICC card is not present.
                IccFileHandler fh = mPhone.getIccFileHandler();
                if (fh == null) {
                    response.recycle();
                    return false; /* is false */
                }
                byte[] record = makeSmsRecordData(status, pdu);
                fh.updateEFLinearFixed(
                        IccConstants.EF_SMS,
                        index, record, null, response);
            }
            waitForResult(updateRequest);
        }
        return (boolean) updateRequest.mResult;
    }

    /**
     * Copies a raw SMS PDU to the ICC.
     *
     * @param callingPackage the package name of the calling app.
     * @param status message status. One of these status:
     *               <code>STATUS_ON_ICC_READ</code>
     *               <code>STATUS_ON_ICC_UNREAD</code>
     *               <code>STATUS_ON_ICC_SENT</code>
     *               <code>STATUS_ON_ICC_UNSENT</code>
     * @param pdu the raw PDU to store.
     * @param smsc the SMSC for this message. Null means use default.
     * @return true for success. Otherwise false.
     */
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    public boolean copyMessageToIccEf(String callingPackage, int status, byte[] pdu, byte[] smsc) {
        //NOTE smsc not used in RUIM
        if (DBG) log("copyMessageToIccEf: status=" + status + " ==> " +
                "pdu=("+ Arrays.toString(pdu) +
                "), smsc=(" + Arrays.toString(smsc) +")");
        enforceReceiveAndSend("Copying message to Icc");
        enforceNotOnHandlerThread("copyMessageToIccEf");
        if (mAppOps.noteOp(AppOpsManager.OPSTR_WRITE_ICC_SMS, Binder.getCallingUid(),
                callingPackage) != AppOpsManager.MODE_ALLOWED) {
            return false;
        }
        Request copyRequest = new Request();
        synchronized (copyRequest) {
            Message response = mHandler.obtainMessage(EVENT_UPDATE_DONE, copyRequest);

            //RIL_REQUEST_WRITE_SMS_TO_SIM vs RIL_REQUEST_CDMA_WRITE_SMS_TO_RUIM
            if (Flags.deleteCdma() || PhoneConstants.PHONE_TYPE_GSM == mPhone.getPhoneType()) {
                mPhone.mCi.writeSmsToSim(status, IccUtils.bytesToHexString(smsc),
                        IccUtils.bytesToHexString(pdu), response);
            } else {
                mPhone.mCi.writeSmsToRuim(status, pdu, response);
            }

            waitForResult(copyRequest);
        }
        return (boolean) copyRequest.mResult;
    }

    /**
     * Retrieves all messages currently stored on Icc.
     *
     * @return list of SmsRawData of all sms on Icc
     */

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    public List<SmsRawData> getAllMessagesFromIccEf(String callingPackage) {
        if (DBG) log("getAllMessagesFromEF");

        mContext.enforceCallingOrSelfPermission(
                Manifest.permission.RECEIVE_SMS,
                "Reading messages from Icc");
        enforceAccessMessageOnICC("Reading messages from Icc");
        enforceNotOnHandlerThread("getAllMessagesFromIccEf");
        if (mAppOps.noteOp(AppOpsManager.OPSTR_READ_ICC_SMS, Binder.getCallingUid(),
                callingPackage) != AppOpsManager.MODE_ALLOWED) {
            return new ArrayList<SmsRawData>();
        }
        Request getRequest = new Request();
        synchronized (getRequest) {

            IccFileHandler fh = mPhone.getIccFileHandler();
            if (fh == null) {
                loge("Cannot load Sms records. No icc card?");
                return null;
            }

            Message response = mHandler.obtainMessage(EVENT_LOAD_DONE, getRequest);
            fh.loadEFLinearFixedAll(IccConstants.EF_SMS, response);

            waitForResult(getRequest);
        }
        return (List<SmsRawData>) getRequest.mResult;
    }

    /**
     * A permissions check before passing to {@link IccSmsInterfaceManager#sendDataInternal}.
     * This method checks if the calling package or itself has the permission to send the data sms.
     */
    public void sendDataWithSelfPermissions(String callingPackage, int callingUser,
            String callingAttributionTag, String destAddr, String scAddr, int destPort, byte[] data,
            PendingIntent sentIntent, PendingIntent deliveryIntent, boolean isForVvm, int uid) {
        if (!mSmsPermissions.checkCallingOrSelfCanSendSms(callingPackage, callingAttributionTag,
                "Sending SMS message")) {
            returnUnspecifiedFailure(sentIntent);
            return;
        }
        sendDataInternal(callingPackage, callingUser, destAddr, scAddr, destPort, data, sentIntent,
                deliveryIntent, isForVvm, uid);
    }

    /**
     * @deprecated Use {@link #sendData(String, String, String, String, int, byte[], PendingIntent,
     * PendingIntent)} instead.
     */
    @Deprecated
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    public void sendData(String callingPackage, int callingUser, String destAddr, String scAddr,
            int destPort, byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) {
        sendData(callingPackage, callingUser, null, destAddr, scAddr, destPort, data,
                sentIntent, deliveryIntent, android.os.Process.INVALID_UID);
    }

    /**
     * A permissions check before passing to {@link IccSmsInterfaceManager#sendDataInternal}.
     * This method checks only if the calling package has the permission to send the data sms.
     */
    public void sendData(String callingPackage, int callingUser, String callingAttributionTag,
            String destAddr, String scAddr, int destPort, byte[] data, PendingIntent sentIntent,
            PendingIntent deliveryIntent, int uid) {
        if (!mSmsPermissions.checkCallingCanSendSms(callingPackage, callingAttributionTag,
                "Sending SMS message")) {
            returnUnspecifiedFailure(sentIntent);
            return;
        }
        sendDataInternal(callingPackage, callingUser, destAddr, scAddr, destPort, data, sentIntent,
                deliveryIntent, false /* isForVvm */, uid);
    }

    /**
     * Send a raw SMS PDU. Intended for STK App use only.
     *
     * @param callingPackage the package name of the caller
     * @param callingUser the user of the caller
     * @param destAddr the address to send the message to
     * @param pdu the raw SMS PDU to send
     * @param sentIntent if not NULL this <code>PendingIntent</code> is
     *  broadcast when the message is successfully sent, or failed.
     *  The result code will be <code>Activity.RESULT_OK</code> for success, or relevant errors
     *  the sentIntent may include the extra "errorCode" containing a radio technology specific
     *  value, generally only useful for troubleshooting.
     * @param deliveryIntent if not NULL this <code>PendingIntent</code> is
     *  broadcast when the message is delivered to the recipient.  The
     *  raw pdu of the status report is in the extended data ("pdu").
     * @param uid the android uid of the caller
     * Note: SEND_SMS permission should be checked by the caller of this method
     */
    @VisibleForTesting
    public void sendRawPdu(String callingPackage, int callingUser, String destAddr, byte[] pdu,
            PendingIntent sentIntent, PendingIntent deliveryIntent, int uid) {
        mDispatchersController.sendRawPdu(callingPackage, callingUser, destAddr, pdu, sentIntent,
                deliveryIntent, uid);
    }

    /**
     * Send a data based SMS to a specific application port.
     *
     * @param callingPackage the package name of the calling app
     * @param destAddr the address to send the message to
     * @param scAddr is the service center address or null to use
     *  the current default SMSC
     * @param destPort the port to deliver the message to
     * @param data the body of the message to send
     * @param sentIntent if not NULL this <code>PendingIntent</code> is
     *  broadcast when the message is successfully sent, or failed.
     *  The result code will be <code>Activity.RESULT_OK<code> for success,
     *  or one of these errors:<br>
     *  <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
     *  <code>RESULT_ERROR_RADIO_OFF</code><br>
     *  <code>RESULT_ERROR_NULL_PDU</code><br>
     *  For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
     *  the extra "errorCode" containing a radio technology specific value,
     *  generally only useful for troubleshooting.<br>
     *  The per-application based SMS control checks sentIntent. If sentIntent
     *  is NULL the caller will be checked against all unknown applications,
     *  which cause smaller number of SMS to be sent in checking period.
     * @param deliveryIntent if not NULL this <code>PendingIntent</code> is
     *  broadcast when the message is delivered to the recipient.  The
     *  raw pdu of the status report is in the extended data ("pdu").
     */

    private void sendDataInternal(String callingPackage, int callinUser, String destAddr,
            String scAddr, int destPort, byte[] data, PendingIntent sentIntent,
            PendingIntent deliveryIntent, boolean isForVvm, int uid) {
        if (Rlog.isLoggable("SMS", Log.VERBOSE)) {
            log("sendData: destAddr=" + destAddr + " scAddr=" + scAddr + " destPort="
                    + destPort + " data='" + HexDump.toHexString(data)  + "' sentIntent="
                    + sentIntent + " deliveryIntent=" + deliveryIntent + " isForVVM=" + isForVvm);
        }
        destAddr = filterDestAddress(destAddr);
        mDispatchersController.sendData(callingPackage, callinUser, destAddr, scAddr,
                destPort, data, sentIntent, deliveryIntent, isForVvm, uid);
    }

    /**
     * A permissions check before passing to {@link IccSmsInterfaceManager#sendTextInternal}.
     * This method checks only if the calling package has the permission to send the sms.
     * Note: SEND_SMS permission should be checked by the caller of this method
     */
    public void sendText(String callingPackage, int callingUser, String destAddr, String scAddr,
            String text, PendingIntent sentIntent, PendingIntent deliveryIntent,
            boolean persistMessageForNonDefaultSmsApp, long messageId, boolean skipShortCodeCheck,
            int uid) {
        sendTextInternal(callingPackage, callingUser, destAddr, scAddr, text, sentIntent,
                deliveryIntent, persistMessageForNonDefaultSmsApp,
                SMS_MESSAGE_PRIORITY_NOT_SPECIFIED, false /* expectMore */,
                SMS_MESSAGE_PERIOD_NOT_SPECIFIED, false /* isForVvm */,
                messageId, skipShortCodeCheck, uid);
    }

    /**
     * A permissions check before passing to {@link IccSmsInterfaceManager#sendTextInternal}.
     * This method checks if the calling package or itself has the permission to send the sms.
     */
    public void sendTextWithSelfPermissions(String callingPackage, int callingUser,
            String callingAttributeTag, String destAddr, String scAddr, String text,
            PendingIntent sentIntent, PendingIntent deliveryIntent, boolean persistMessage,
            boolean isForVvm, int uid) {
        if (!mSmsPermissions.checkCallingOrSelfCanSendSms(callingPackage, callingAttributeTag,
                "Sending SMS message")) {
            returnUnspecifiedFailure(sentIntent);
            return;
        }
        sendTextInternal(callingPackage, callingUser, destAddr, scAddr, text, sentIntent,
                deliveryIntent, persistMessage, SMS_MESSAGE_PRIORITY_NOT_SPECIFIED,
                false /* expectMore */, SMS_MESSAGE_PERIOD_NOT_SPECIFIED,
                isForVvm, 0L /* messageId */, uid);
    }


    private void sendTextInternal(String callingPackage, int callingUser, String destAddr,
            String scAddr, String text, PendingIntent sentIntent, PendingIntent deliveryIntent,
            boolean persistMessageForNonDefaultSmsApp, int priority, boolean expectMore,
            int validityPeriod, boolean isForVvm, long messageId, int uid) {
        sendTextInternal(callingPackage, callingUser, destAddr, scAddr, text, sentIntent,
                deliveryIntent, persistMessageForNonDefaultSmsApp, priority, expectMore,
                validityPeriod, isForVvm, messageId, false, uid);
    }

    /**
     * Send a text based SMS.
     *
     * @param destAddr the address to send the message to
     * @param scAddr is the service center address or null to use
     *  the current default SMSC
     * @param text the body of the message to send
     * @param sentIntent if not NULL this <code>PendingIntent</code> is
     *  broadcast when the message is successfully sent, or failed.
     *  The result code will be <code>Activity.RESULT_OK<code> for success,
     *  or one of these errors:<br>
     *  <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
     *  <code>RESULT_ERROR_RADIO_OFF</code><br>
     *  <code>RESULT_ERROR_NULL_PDU</code><br>
     *  For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
     *  the extra "errorCode" containing a radio technology specific value,
     *  generally only useful for troubleshooting.<br>
     *  The per-application based SMS control checks sentIntent. If sentIntent
     *  is NULL the caller will be checked against all unknown applications,
     *  which cause smaller number of SMS to be sent in checking period.
     * @param deliveryIntent if not NULL this <code>PendingIntent</code> is
     *  broadcast when the message is delivered to the recipient.  The
     *  raw pdu of the status report is in the extended data ("pdu").
     * @param persistMessageForNonDefaultSmsApp whether the sent message should
     *  be automatically persisted in the SMS db. It only affects messages sent
     *  by a non-default SMS app. Currently only the carrier app can set this
     *  parameter to false to skip auto message persistence.
     * @param priority Priority level of the message
     *  Refer specification See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1
     *  ---------------------------------
     *  PRIORITY      | Level of Priority
     *  ---------------------------------
     *      '00'      |     Normal
     *      '01'      |     Interactive
     *      '10'      |     Urgent
     *      '11'      |     Emergency
     *  ----------------------------------
     *  Any Other values including negative considered as Invalid Priority Indicator of the message.
     * @param expectMore is a boolean to indicate the sending messages through same link or not.
     * @param validityPeriod Validity Period of the message in mins.
     *  Refer specification 3GPP TS 23.040 V6.8.1 section 9.2.3.12.1.
     *  Validity Period(Minimum) -> 5 mins
     *  Validity Period(Maximum) -> 635040 mins(i.e.63 weeks).
     *  Any Other values including negative considered as Invalid Validity Period of the message.
     * @param messageId An id that uniquely identifies the message requested to be sent.
     *                 Used for logging and diagnostics purposes. The id may be 0.
     * @param skipShortCodeCheck Skip check for short code type destination address.
     */

    private void sendTextInternal(String callingPackage, int callingUser, String destAddr,
            String scAddr, String text, PendingIntent sentIntent, PendingIntent deliveryIntent,
            boolean persistMessageForNonDefaultSmsApp, int priority, boolean expectMore,
            int validityPeriod, boolean isForVvm, long messageId, boolean skipShortCodeCheck,
            int uid) {
        if (Rlog.isLoggable("SMS", Log.VERBOSE)) {
            log("sendText: destAddr=" + destAddr + " scAddr=" + scAddr
                    + " text='" + text + "' sentIntent=" + sentIntent + " deliveryIntent="
                    + deliveryIntent + " priority=" + priority + " expectMore=" + expectMore
                    + " validityPeriod=" + validityPeriod + " isForVVM=" + isForVvm
                    + " " + SmsController.formatCrossStackMessageId(messageId));
        }
        notifyIfOutgoingEmergencySms(destAddr);
        destAddr = filterDestAddress(destAddr);
        mDispatchersController.sendText(destAddr, scAddr, text, sentIntent, deliveryIntent,
                null/*messageUri*/, callingPackage, callingUser, persistMessageForNonDefaultSmsApp,
                priority, expectMore, validityPeriod, isForVvm, messageId, skipShortCodeCheck, uid);
    }

    /**
     * Send a text based SMS with Messaging Options.
     *
     * @param destAddr the address to send the message to
     * @param scAddr is the service center address or null to use
     *  the current default SMSC
     * @param text the body of the message to send
     * @param sentIntent if not NULL this <code>PendingIntent</code> is
     *  broadcast when the message is successfully sent, or failed.
     *  The result code will be <code>Activity.RESULT_OK<code> for success,
     *  or one of these errors:<br>
     *  <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
     *  <code>RESULT_ERROR_RADIO_OFF</code><br>
     *  <code>RESULT_ERROR_NULL_PDU</code><br>
     *  For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
     *  the extra "errorCode" containing a radio technology specific value,
     *  generally only useful for troubleshooting.<br>
     *  The per-application based SMS control checks sentIntent. If sentIntent
     *  is NULL the caller will be checked against all unknown applications,
     *  which cause smaller number of SMS to be sent in checking period.
     * @param deliveryIntent if not NULL this <code>PendingIntent</code> is
     *  broadcast when the message is delivered to the recipient.  The
     *  raw pdu of the status report is in the extended data ("pdu").
     * @param persistMessageForNonDefaultSmsApp whether the sent message should
     *  be automatically persisted in the SMS db. It only affects messages sent
     *  by a non-default SMS app. Currently only the carrier app can set this
     *  parameter to false to skip auto message persistence.
     * @param priority Priority level of the message
     *  Refer specification See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1
     *  ---------------------------------
     *  PRIORITY      | Level of Priority
     *  ---------------------------------
     *      '00'      |     Normal
     *      '01'      |     Interactive
     *      '10'      |     Urgent
     *      '11'      |     Emergency
     *  ----------------------------------
     *  Any Other values including negative considered as Invalid Priority Indicator of the message.
     * @param expectMore is a boolean to indicate the sending messages through same link or not.
     * @param validityPeriod Validity Period of the message in mins.
     *  Refer specification 3GPP TS 23.040 V6.8.1 section 9.2.3.12.1.
     *  Validity Period(Minimum) -> 5 mins
     *  Validity Period(Maximum) -> 635040 mins(i.e.63 weeks).
     *  Any Other values including negative considered as Invalid Validity Period of the message.
     */

    public void sendTextWithOptions(String callingPackage, int callingUser,
            String callingAttributionTag, String destAddr, String scAddr, String text,
            PendingIntent sentIntent, PendingIntent deliveryIntent,
            boolean persistMessageForNonDefaultSmsApp, int priority,
            boolean expectMore, int validityPeriod, int uid) {
        if (!mSmsPermissions.checkCallingCanSendText(persistMessageForNonDefaultSmsApp,
                    callingPackage, callingAttributionTag, "Sending SMS message")) {
            returnUnspecifiedFailure(sentIntent);
            return;
        }
        sendTextInternal(callingPackage, callingUser, destAddr, scAddr, text, sentIntent,
                deliveryIntent, persistMessageForNonDefaultSmsApp, priority, expectMore,
                validityPeriod, false /* isForVvm */, 0L /* messageId */, uid);
    }

    /**
     * Inject an SMS PDU into the android application framework.
     *
     * @param pdu is the byte array of pdu to be injected into android application framework
     * @param format is the format of SMS pdu (3gpp or 3gpp2)
     * @param receivedIntent if not NULL this <code>PendingIntent</code> is
     *  broadcast when the message is successfully received by the
     *  android application framework. This intent is broadcasted at
     *  the same time an SMS received from radio is acknowledged back.
     */
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    public void injectSmsPdu(byte[] pdu, String format, PendingIntent receivedIntent) {
        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
                != PackageManager.PERMISSION_GRANTED) {
            mSmsPermissions.enforceCallerIsImsAppOrCarrierApp("injectSmsPdu");
        }

        if (Rlog.isLoggable("SMS", Log.VERBOSE)) {
            log("pdu: " + IccUtils.bytesToHexString(pdu)
                    + "\n format=" + format
                    + "\n receivedIntent=" + receivedIntent);
        }
        mDispatchersController.injectSmsPdu(pdu, format, false /* isOverIms */,
                result -> {
                    if (receivedIntent != null) {
                        try {
                            receivedIntent.send(result);
                        } catch (PendingIntent.CanceledException e) {
                            loge("receivedIntent cancelled.");
                        }
                    }
                }
        );
    }

    /**
     * Send a multi-part text based SMS.
     *
     * @param destAddr the address to send the message to
     * @param scAddr is the service center address or null to use
     *   the current default SMSC
     * @param parts an <code>ArrayList</code> of strings that, in order,
     *   comprise the original message
     * @param sentIntents if not null, an <code>ArrayList</code> of
     *   <code>PendingIntent</code>s (one for each message part) that is
     *   broadcast when the corresponding message part has been sent.
     *   The result code will be <code>Activity.RESULT_OK<code> for success,
     *   or one of these errors:
     *   <code>RESULT_ERROR_GENERIC_FAILURE</code>
     *   <code>RESULT_ERROR_RADIO_OFF</code>
     *   <code>RESULT_ERROR_NULL_PDU</code>.
     *  The per-application based SMS control checks sentIntent. If sentIntent
     *  is NULL the caller will be checked against all unknown applications,
     *  which cause smaller number of SMS to be sent in checking period.
     * @param deliveryIntents if not null, an <code>ArrayList</code> of
     *   <code>PendingIntent</code>s (one for each message part) that is
     *   broadcast when the corresponding message part has been delivered
     *   to the recipient.  The raw pdu of the status report is in the
     *   extended data ("pdu").
     * @param messageId An id that uniquely identifies the message requested to be sent.
     *                 Used for logging and diagnostics purposes. The id may be 0.
     */

    public void sendMultipartText(String callingPackage, int callingUser,
            String callingAttributionTag, String destAddr, String scAddr, List<String> parts,
            List<PendingIntent> sentIntents, List<PendingIntent> deliveryIntents,
            boolean persistMessageForNonDefaultSmsApp, long messageId, int uid) {
        sendMultipartTextWithOptions(callingPackage, callingUser, callingAttributionTag, destAddr,
                scAddr, parts, sentIntents, deliveryIntents, persistMessageForNonDefaultSmsApp,
                SMS_MESSAGE_PRIORITY_NOT_SPECIFIED, false /* expectMore */,
                SMS_MESSAGE_PERIOD_NOT_SPECIFIED,
                messageId, uid);
    }

    /**
     * Send a multi-part text based SMS with Messaging Options.
     *
     * @param destAddr the address to send the message to
     * @param scAddr is the service center address or null to use
     *   the current default SMSC
     * @param parts an <code>ArrayList</code> of strings that, in order,
     *   comprise the original message
     * @param sentIntents if not null, an <code>ArrayList</code> of
     *   <code>PendingIntent</code>s (one for each message part) that is
     *   broadcast when the corresponding message part has been sent.
     *   The result code will be <code>Activity.RESULT_OK<code> for success,
     *   or one of these errors:
     *   <code>RESULT_ERROR_GENERIC_FAILURE</code>
     *   <code>RESULT_ERROR_RADIO_OFF</code>
     *   <code>RESULT_ERROR_NULL_PDU</code>.
     *  The per-application based SMS control checks sentIntent. If sentIntent
     *  is NULL the caller will be checked against all unknown applications,
     *  which cause smaller number of SMS to be sent in checking period.
     * @param deliveryIntents if not null, an <code>ArrayList</code> of
     *   <code>PendingIntent</code>s (one for each message part) that is
     *   broadcast when the corresponding message part has been delivered
     *   to the recipient.  The raw pdu of the status report is in the
     *   extended data ("pdu").
     * @param persistMessageForNonDefaultSmsApp whether the sent message should
     *   be automatically persisted in the SMS db. It only affects messages sent
     *   by a non-default SMS app. Currently only the carrier app can set this
     *   parameter to false to skip auto message persistence.
     * @param priority Priority level of the message
     *  Refer specification See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1
     *  ---------------------------------
     *  PRIORITY      | Level of Priority
     *  ---------------------------------
     *      '00'      |     Normal
     *      '01'      |     Interactive
     *      '10'      |     Urgent
     *      '11'      |     Emergency
     *  ----------------------------------
     *  Any Other values including negative considered as Invalid Priority Indicator of the message.
     * @param expectMore is a boolean to indicate the sending messages through same link or not.
     * @param validityPeriod Validity Period of the message in mins.
     *  Refer specification 3GPP TS 23.040 V6.8.1 section 9.2.3.12.1.
     *  Validity Period(Minimum) -> 5 mins
     *  Validity Period(Maximum) -> 635040 mins(i.e.63 weeks).
     *  Any Other values including negative considered as Invalid Validity Period of the message.
     * @param messageId An id that uniquely identifies the message requested to be sent.
     *                 Used for logging and diagnostics purposes. The id may be 0.
     */

    public void sendMultipartTextWithOptions(String callingPackage, int callingUser,
            String callingAttributionTag, String destAddr, String scAddr, List<String> parts,
            List<PendingIntent> sentIntents, List<PendingIntent> deliveryIntents,
            boolean persistMessageForNonDefaultSmsApp, int priority, boolean expectMore,
            int validityPeriod, long messageId, int uid) {
        if (!mSmsPermissions.checkCallingCanSendText(persistMessageForNonDefaultSmsApp,
                callingPackage, callingAttributionTag, "Sending SMS message")) {
            returnUnspecifiedFailure(sentIntents);
            return;
        }
        if (Rlog.isLoggable("SMS", Log.VERBOSE)) {
            int i = 0;
            for (String part : parts) {
                log("sendMultipartTextWithOptions: destAddr=" + destAddr + ", srAddr=" + scAddr
                        + ", part[" + (i++) + "]=" + part
                        + " " + SmsController.formatCrossStackMessageId(messageId));
            }
        }
        notifyIfOutgoingEmergencySms(destAddr);
        destAddr = filterDestAddress(destAddr);

        if (parts.size() > 1 && parts.size() < 10 && !SmsMessage.hasEmsSupport()) {
            for (int i = 0; i < parts.size(); i++) {
                // If EMS is not supported, we have to break down EMS into single segment SMS
                // and add page info " x/y".
                String singlePart = parts.get(i);
                if (SmsMessage.shouldAppendPageNumberAsPrefix()) {
                    singlePart = String.valueOf(i + 1) + '/' + parts.size() + ' ' + singlePart;
                } else {
                    singlePart = singlePart.concat(' ' + String.valueOf(i + 1) + '/'
                            + parts.size());
                }

                PendingIntent singleSentIntent = null;
                if (sentIntents != null && sentIntents.size() > i) {
                    singleSentIntent = sentIntents.get(i);
                }

                PendingIntent singleDeliveryIntent = null;
                if (deliveryIntents != null && deliveryIntents.size() > i) {
                    singleDeliveryIntent = deliveryIntents.get(i);
                }

                mDispatchersController.sendText(destAddr, scAddr, singlePart, singleSentIntent,
                        singleDeliveryIntent, null /* messageUri */, callingPackage, callingUser,
                        persistMessageForNonDefaultSmsApp, priority, expectMore, validityPeriod,
                        false /* isForVvm */, messageId, uid);
            }
            return;
        }

        mDispatchersController.sendMultipartText(destAddr,
                scAddr,
                (ArrayList<String>) parts,
                (ArrayList<PendingIntent>) sentIntents,
                (ArrayList<PendingIntent>) deliveryIntents,
                null, callingPackage, callingUser, persistMessageForNonDefaultSmsApp,
                priority, expectMore, validityPeriod, messageId, uid);

    }

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    public int getPremiumSmsPermission(String packageName) {
        return mDispatchersController.getPremiumSmsPermission(packageName);
    }


    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    public void setPremiumSmsPermission(String packageName, int permission) {
        mDispatchersController.setPremiumSmsPermission(packageName, permission);
    }

    /**
     * create SmsRawData lists from all sms record byte[]
     * Use null to indicate "free" record
     *
     * @param messages List of message records from EF_SMS.
     * @return SmsRawData list of all in-used records
     */
    protected ArrayList<SmsRawData> buildValidRawData(ArrayList<byte[]> messages) {
        int count = messages.size();
        ArrayList<SmsRawData> ret;

        ret = new ArrayList<SmsRawData>(count);

        for (int i = 0; i < count; i++) {
            byte[] ba = messages.get(i);
            if ((ba[0] & 0x01) == STATUS_ON_ICC_FREE) {
                ret.add(null);
            } else {
                ret.add(new SmsRawData(messages.get(i)));
            }
        }

        return ret;
    }

    /**
     * Generates an EF_SMS record from status and raw PDU.
     *
     * @param status Message status.  See TS 51.011 10.5.3.
     * @param pdu Raw message PDU.
     * @return byte array for the record.
     */
    protected byte[] makeSmsRecordData(int status, byte[] pdu) {
        byte[] data;
        if (Flags.deleteCdma() || PhoneConstants.PHONE_TYPE_GSM == mPhone.getPhoneType()) {
            data = new byte[SmsManager.SMS_RECORD_LENGTH];
        } else {
            data = new byte[SmsManager.CDMA_SMS_RECORD_LENGTH];
        }

        // Status bits for this record.  See TS 51.011 10.5.3
        data[0] = (byte) (status & 0x07);

        System.arraycopy(pdu, 0, data, 1, pdu.length);

        // Pad out with 0xFF's.
        for (int j = pdu.length+1; j < data.length; j++) {
            data[j] = -1;
        }

        return data;
    }

    /**
     * Gets the SMSC address from (U)SIM.
     *
     * @return the SMSC address string, null if failed.
     */
    public String getSmscAddressFromIccEf(String callingPackage) {
        if (!mSmsPermissions.checkCallingOrSelfCanGetSmscAddress(
                callingPackage, "getSmscAddressFromIccEf")) {
            loge("Caller do not have permission to call GetSmscAddress");
            return null;
        }
        enforceNotOnHandlerThread("getSmscAddressFromIccEf");
        Request getRequest = new Request();
        synchronized (getRequest) {
            Message response = mHandler.obtainMessage(EVENT_GET_SMSC_DONE, getRequest);
            mPhone.mCi.getSmscAddress(response);
            waitForResult(getRequest);
        }
        return (String) getRequest.mResult;
    }

    /**
     * Sets the SMSC address on (U)SIM.
     *
     * @param smsc the SMSC address string.
     * @return true for success, false otherwise.
     */
    public boolean setSmscAddressOnIccEf(String callingPackage, String smsc) {
        if (!mSmsPermissions.checkCallingOrSelfCanSetSmscAddress(
                callingPackage, "setSmscAddressOnIccEf")) {
            loge("Caller do not have permission to call SetSmscAddress");
            return false;
        }
        enforceNotOnHandlerThread("setSmscAddressOnIccEf");
        Request setRequest = new Request();
        synchronized (setRequest) {
            Message response = mHandler.obtainMessage(EVENT_SET_SMSC_DONE, setRequest);
            mPhone.mCi.setSmscAddress(smsc, response);
            waitForResult(setRequest);
        }
        return (boolean) setRequest.mResult;
    }

    public boolean enableCellBroadcastRange(int startMessageId, int endMessageId, int ranType) {
        mContext.enforceCallingPermission(android.Manifest.permission.RECEIVE_EMERGENCY_BROADCAST,
                "enabling cell broadcast range [" + startMessageId + "-" + endMessageId + "]. "
                        + "ranType=" + ranType);
        if (ranType == SmsCbMessage.MESSAGE_FORMAT_3GPP) {
            return enableGsmBroadcastRange(startMessageId, endMessageId);
        } else if (ranType == SmsCbMessage.MESSAGE_FORMAT_3GPP2) {
            return enableCdmaBroadcastRange(startMessageId, endMessageId);
        } else {
            throw new IllegalArgumentException("Not a supported RAN Type");
        }
    }

    public boolean disableCellBroadcastRange(int startMessageId, int endMessageId, int ranType) {
        mContext.enforceCallingPermission(android.Manifest.permission.RECEIVE_EMERGENCY_BROADCAST,
                "disabling cell broadcast range [" + startMessageId + "-" + endMessageId
                        + "]. ranType=" + ranType);
        if (ranType == SmsCbMessage.MESSAGE_FORMAT_3GPP) {
            return disableGsmBroadcastRange(startMessageId, endMessageId);
        } else if (ranType == SmsCbMessage.MESSAGE_FORMAT_3GPP2)  {
            return disableCdmaBroadcastRange(startMessageId, endMessageId);
        } else {
            throw new IllegalArgumentException("Not a supported RAN Type");
        }
    }

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    synchronized public boolean enableGsmBroadcastRange(int startMessageId, int endMessageId) {

        mContext.enforceCallingPermission(android.Manifest.permission.RECEIVE_EMERGENCY_BROADCAST,
                "Enabling cell broadcast SMS");

        String client = mContext.getPackageManager().getNameForUid(
                Binder.getCallingUid());

        String msg;
        if (!mCellBroadcastRangeManager.enableRange(startMessageId, endMessageId, client)) {
            msg = "Failed to add GSM cell broadcast channels range " + startMessageId
                    + " to " + endMessageId;
            log(msg);
            mCellBroadcastLocalLog.log(msg);
            return false;
        }

        if (DBG) {
            msg = "Added GSM cell broadcast channels range " + startMessageId
                    + " to " + endMessageId;
            log(msg);
            mCellBroadcastLocalLog.log(msg);
        }

        setCellBroadcastActivation(!mCellBroadcastRangeManager.isEmpty());

        return true;
    }

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    synchronized public boolean disableGsmBroadcastRange(int startMessageId, int endMessageId) {

        mContext.enforceCallingPermission(android.Manifest.permission.RECEIVE_EMERGENCY_BROADCAST,
                "Disabling cell broadcast SMS");

        String client = mContext.getPackageManager().getNameForUid(
                Binder.getCallingUid());

        String msg;
        if (!mCellBroadcastRangeManager.disableRange(startMessageId, endMessageId, client)) {
            msg = "Failed to remove GSM cell broadcast channels range " + startMessageId
                    + " to " + endMessageId;
            log(msg);
            mCellBroadcastLocalLog.log(msg);
            return false;
        }

        if (DBG) {
            msg = "Removed GSM cell broadcast channels range " + startMessageId
                    + " to " + endMessageId;
            log(msg);
            mCellBroadcastLocalLog.log(msg);
        }

        setCellBroadcastActivation(!mCellBroadcastRangeManager.isEmpty());

        return true;
    }

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    synchronized public boolean enableCdmaBroadcastRange(int startMessageId, int endMessageId) {

        mContext.enforceCallingPermission(android.Manifest.permission.RECEIVE_EMERGENCY_BROADCAST,
                "Enabling cdma broadcast SMS");

        String client = mContext.getPackageManager().getNameForUid(
                Binder.getCallingUid());

        String msg;
        if (!mCdmaBroadcastRangeManager.enableRange(startMessageId, endMessageId, client)) {
            msg = "Failed to add cdma broadcast channels range " + startMessageId + " to "
                    + endMessageId;
            log(msg);
            mCellBroadcastLocalLog.log(msg);
            return false;
        }

        if (DBG) {
            msg = "Added cdma broadcast channels range " + startMessageId + " to " + endMessageId;
            log(msg);
            mCellBroadcastLocalLog.log(msg);
        }

        setCdmaBroadcastActivation(!mCdmaBroadcastRangeManager.isEmpty());

        return true;
    }

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    synchronized public boolean disableCdmaBroadcastRange(int startMessageId, int endMessageId) {

        mContext.enforceCallingPermission(android.Manifest.permission.RECEIVE_EMERGENCY_BROADCAST,
                "Disabling cell broadcast SMS");

        String client = mContext.getPackageManager().getNameForUid(
                Binder.getCallingUid());

        String msg;
        if (!mCdmaBroadcastRangeManager.disableRange(startMessageId, endMessageId, client)) {
            msg = "Failed to remove cdma broadcast channels range " + startMessageId + " to "
                    + endMessageId;
            log(msg);
            mCellBroadcastLocalLog.log(msg);
            return false;
        }

        if (DBG) {
            msg = "Removed cdma broadcast channels range " + startMessageId + " to " + endMessageId;
            log(msg);
            mCellBroadcastLocalLog.log(msg);
        }

        setCdmaBroadcastActivation(!mCdmaBroadcastRangeManager.isEmpty());

        return true;
    }

    /**
     * Reset all cell broadcast ranges. Previously enabled ranges will become invalid after this.
     */
    @RequiresPermission(android.Manifest.permission.MODIFY_CELL_BROADCASTS)
    public void resetAllCellBroadcastRanges() {
        mContext.enforceCallingPermission(android.Manifest.permission.MODIFY_CELL_BROADCASTS,
                "resetAllCellBroadcastRanges");
        mCdmaBroadcastRangeManager.clearRanges();
        mCellBroadcastRangeManager.clearRanges();
        log("Cell broadcast ranges reset.");
    }

    class CellBroadcastRangeManager extends IntRangeManager {
        private ArrayList<SmsBroadcastConfigInfo> mConfigList =
                new ArrayList<SmsBroadcastConfigInfo>();

        /**
         * Called when the list of enabled ranges has changed. This will be
         * followed by zero or more calls to {@link #addRange} followed by
         * a call to {@link #finishUpdate}.
         */
        protected void startUpdate() {
            mConfigList.clear();
        }

        /**
         * Called after {@link #startUpdate} to indicate a range of enabled
         * values.
         * @param startId the first id included in the range
         * @param endId the last id included in the range
         */
        protected void addRange(int startId, int endId, boolean selected) {
            mConfigList.add(new SmsBroadcastConfigInfo(startId, endId,
                        SMS_CB_CODE_SCHEME_MIN, SMS_CB_CODE_SCHEME_MAX, selected));
        }

        /**
         * Called to indicate the end of a range update started by the
         * previous call to {@link #startUpdate}.
         * @return true if successful, false otherwise
         */
        protected boolean finishUpdate() {
            if (mConfigList.isEmpty()) {
                return true;
            } else {
                SmsBroadcastConfigInfo[] configs =
                        mConfigList.toArray(new SmsBroadcastConfigInfo[mConfigList.size()]);
                return setCellBroadcastConfig(configs);
            }
        }
    }

    class CdmaBroadcastRangeManager extends IntRangeManager {
        private ArrayList<CdmaSmsBroadcastConfigInfo> mConfigList =
                new ArrayList<CdmaSmsBroadcastConfigInfo>();

        /**
         * Called when the list of enabled ranges has changed. This will be
         * followed by zero or more calls to {@link #addRange} followed by a
         * call to {@link #finishUpdate}.
         */
        protected void startUpdate() {
            mConfigList.clear();
        }

        /**
         * Called after {@link #startUpdate} to indicate a range of enabled
         * values.
         * @param startId the first id included in the range
         * @param endId the last id included in the range
         */
        protected void addRange(int startId, int endId, boolean selected) {
            mConfigList.add(new CdmaSmsBroadcastConfigInfo(startId, endId,
                    1, selected));
        }

        /**
         * Called to indicate the end of a range update started by the previous
         * call to {@link #startUpdate}.
         * @return true if successful, false otherwise
         */
        protected boolean finishUpdate() {
            if (mConfigList.isEmpty()) {
                return true;
            } else {
                CdmaSmsBroadcastConfigInfo[] configs =
                        mConfigList.toArray(new CdmaSmsBroadcastConfigInfo[mConfigList.size()]);
                return setCdmaBroadcastConfig(configs);
            }
        }
    }

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    private boolean setCellBroadcastConfig(SmsBroadcastConfigInfo[] configs) {
        if (DBG) {
            log("Calling setGsmBroadcastConfig with " + configs.length + " configurations");
        }
        enforceNotOnHandlerThread("setCellBroadcastConfig");
        Request setRequest = new Request();
        synchronized (setRequest) {
            Message response = mHandler.obtainMessage(EVENT_SET_BROADCAST_CONFIG_DONE, setRequest);

            mPhone.mCi.setGsmBroadcastConfig(configs, response);

            waitForResult(setRequest);
        }

        return (boolean) setRequest.mResult;
    }

    private boolean setCellBroadcastActivation(boolean activate) {
        if (DBG) {
            log("Calling setCellBroadcastActivation(" + activate + ')');
        }

        enforceNotOnHandlerThread("setCellBroadcastConfig");
        Request setRequest = new Request();
        synchronized (setRequest) {
            Message response = mHandler.obtainMessage(EVENT_SET_BROADCAST_ACTIVATION_DONE,
                    setRequest);

            mPhone.mCi.setGsmBroadcastActivation(activate, response);
            waitForResult(setRequest);
        }

        return (boolean) setRequest.mResult;
    }

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    private boolean setCdmaBroadcastConfig(CdmaSmsBroadcastConfigInfo[] configs) {
        if (DBG) {
            log("Calling setCdmaBroadcastConfig with " + configs.length + " configurations");
        }

        enforceNotOnHandlerThread("setCdmaBroadcastConfig");
        Request setRequest = new Request();
        synchronized (setRequest) {
            Message response = mHandler.obtainMessage(EVENT_SET_BROADCAST_CONFIG_DONE, setRequest);

            mPhone.mCi.setCdmaBroadcastConfig(configs, response);

            waitForResult(setRequest);
        }

        return (boolean) setRequest.mResult;
    }

    private boolean setCdmaBroadcastActivation(boolean activate) {
        if (DBG) {
            log("Calling setCdmaBroadcastActivation(" + activate + ")");
        }

        enforceNotOnHandlerThread("setCdmaBroadcastActivation");
        Request setRequest = new Request();
        synchronized (setRequest) {
            Message response = mHandler.obtainMessage(EVENT_SET_BROADCAST_ACTIVATION_DONE,
                    setRequest);

            mPhone.mCi.setCdmaBroadcastActivation(activate, response);

            waitForResult(setRequest);
        }

        return (boolean) setRequest.mResult;
    }

    @UnsupportedAppUsage
    protected void log(String msg) {
        Rlog.d(LOG_TAG, msg);
    }

    protected void loge(String msg) {
        Rlog.e(LOG_TAG, msg);
    }

    protected void loge(String msg, Throwable e) {
        Rlog.e(LOG_TAG, msg, e);
    }

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    public boolean isImsSmsSupported() {
        return mDispatchersController.isIms();
    }

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    public String getImsSmsFormat() {
        return mDispatchersController.getImsSmsFormat();
    }

    /**
     * @deprecated Use {@link #sendStoredText(String, String, Uri, String, PendingIntent,
     * PendingIntent)} instead
     */
    @Deprecated
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    public void sendStoredText(String callingPkg, int callingUser, Uri messageUri, String scAddress,
            PendingIntent sentIntent, PendingIntent deliveryIntent) {
        sendStoredText(callingPkg, callingUser, null, messageUri,
                scAddress, sentIntent, deliveryIntent, android.os.Process.INVALID_UID);
    }

    public void sendStoredText(String callingPkg, int callingUser, String callingAttributionTag,
            Uri messageUri, String scAddress, PendingIntent sentIntent,
            PendingIntent deliveryIntent, int uid) {
        if (!mSmsPermissions.checkCallingCanSendSms(callingPkg, callingAttributionTag,
                "Sending SMS message")) {
            returnUnspecifiedFailure(sentIntent);
            return;
        }
        if (Rlog.isLoggable("SMS", Log.VERBOSE)) {
            log("sendStoredText: scAddr=" + scAddress + " messageUri=" + messageUri
                    + " sentIntent=" + sentIntent + " deliveryIntent=" + deliveryIntent);
        }
        final ContentResolver resolver = mContext.getContentResolver();
        if (!isSendableStoredMessage(resolver, messageUri)) {
            loge("sendStoredText: message not sendable");
            returnUnspecifiedFailure(sentIntent);
            return;
        }
        final String[] textAndAddress = loadTextAndAddress(resolver, messageUri);
        if (textAndAddress == null) {
            loge("sendStoredText: can not load text");
            returnUnspecifiedFailure(sentIntent);
            return;
        }
        notifyIfOutgoingEmergencySms(textAndAddress[1]);
        textAndAddress[1] = filterDestAddress(textAndAddress[1]);
        mDispatchersController.sendText(textAndAddress[1], scAddress, textAndAddress[0],
                sentIntent, deliveryIntent, messageUri, callingPkg, callingUser,
                true /* persistMessageForNonDefaultSmsApp */, SMS_MESSAGE_PRIORITY_NOT_SPECIFIED,
                false /* expectMore */, SMS_MESSAGE_PERIOD_NOT_SPECIFIED, false /* isForVvm */,
                0L /* messageId */, uid);
    }

    /**
     * @deprecated Use {@link #sendStoredMultipartText(String, String, Uri, String, List, List)}
     * instead
     */
    @Deprecated
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    public void sendStoredMultipartText(String callingPkg, int callingUser,
            Uri messageUri, String scAddress, List<PendingIntent> sentIntents,
            List<PendingIntent> deliveryIntents) {
        sendStoredMultipartText(callingPkg, callingUser, null,
                messageUri, scAddress, sentIntents, deliveryIntents,
                android.os.Process.INVALID_UID);
    }

    public void sendStoredMultipartText(String callingPkg, int callingUser,
            String callingAttributionTag, Uri messageUri, String scAddress,
            List<PendingIntent> sentIntents, List<PendingIntent> deliveryIntents, int uid) {
        if (!mSmsPermissions.checkCallingCanSendSms(callingPkg, callingAttributionTag,
                "Sending SMS message")) {
            returnUnspecifiedFailure(sentIntents);
            return;
        }
        final ContentResolver resolver = mContext.getContentResolver();
        if (!isSendableStoredMessage(resolver, messageUri)) {
            loge("sendStoredMultipartText: message not sendable");
            returnUnspecifiedFailure(sentIntents);
            return;
        }
        final String[] textAndAddress = loadTextAndAddress(resolver, messageUri);
        if (textAndAddress == null) {
            loge("sendStoredMultipartText: can not load text");
            returnUnspecifiedFailure(sentIntents);
            return;
        }
        final ArrayList<String> parts = SmsManager.getDefault().divideMessage(textAndAddress[0]);
        if (parts == null || parts.size() < 1) {
            loge("sendStoredMultipartText: can not divide text");
            returnUnspecifiedFailure(sentIntents);
            return;
        }
        notifyIfOutgoingEmergencySms(textAndAddress[1]);
        textAndAddress[1] = filterDestAddress(textAndAddress[1]);

        if (parts.size() > 1 && parts.size() < 10 && !SmsMessage.hasEmsSupport()) {
            for (int i = 0; i < parts.size(); i++) {
                // If EMS is not supported, we have to break down EMS into single segment SMS
                // and add page info " x/y".
                String singlePart = parts.get(i);
                if (SmsMessage.shouldAppendPageNumberAsPrefix()) {
                    singlePart = String.valueOf(i + 1) + '/' + parts.size() + ' ' + singlePart;
                } else {
                    singlePart = singlePart.concat(' ' + String.valueOf(i + 1) + '/'
                            + parts.size());
                }

                PendingIntent singleSentIntent = null;
                if (sentIntents != null && sentIntents.size() > i) {
                    singleSentIntent = sentIntents.get(i);
                }

                PendingIntent singleDeliveryIntent = null;
                if (deliveryIntents != null && deliveryIntents.size() > i) {
                    singleDeliveryIntent = deliveryIntents.get(i);
                }

                mDispatchersController.sendText(textAndAddress[1], scAddress, singlePart,
                        singleSentIntent, singleDeliveryIntent, messageUri, callingPkg,
                        callingUser, true  /* persistMessageForNonDefaultSmsApp */,
                        SMS_MESSAGE_PRIORITY_NOT_SPECIFIED,
                        false /* expectMore */, SMS_MESSAGE_PERIOD_NOT_SPECIFIED,
                        false /* isForVvm */, 0L /* messageId */, uid);
            }
            return;
        }

        mDispatchersController.sendMultipartText(
                textAndAddress[1], // destAddress
                scAddress,
                parts,
                (ArrayList<PendingIntent>) sentIntents,
                (ArrayList<PendingIntent>) deliveryIntents,
                messageUri,
                callingPkg, callingUser,
                true  /* persistMessageForNonDefaultSmsApp */,
                SMS_MESSAGE_PRIORITY_NOT_SPECIFIED,
                false /* expectMore */,
                SMS_MESSAGE_PERIOD_NOT_SPECIFIED,
                0L /* messageId */,
                uid);
    }

    public int getSmsCapacityOnIcc(String callingPackage, String callingFeatureId) {
        if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
                mContext, mPhone.getSubId(), callingPackage, callingFeatureId,
                "getSmsCapacityOnIcc")) {
            return 0;
        }

        int numberOnIcc = 0;
        if (mPhone.getIccRecordsLoaded()) {
            final UiccProfile uiccProfile = UiccController.getInstance()
                    .getUiccProfileForPhone(mPhone.getPhoneId());
            if(uiccProfile != null) {
                numberOnIcc = uiccProfile.getIccRecords().getSmsCapacityOnIcc();
            } else {
                loge("uiccProfile is null");
            }
        } else {
            loge("getSmsCapacityOnIcc - aborting, no icc card present.");
        }

        log("getSmsCapacityOnIcc().numberOnIcc = " + numberOnIcc);
        return numberOnIcc;
    }

    private boolean isSendableStoredMessage(ContentResolver resolver, Uri messageUri) {
        // Clear the calling identity and query the database using the phone user id
        // Otherwise the AppOps check in TelephonyProvider would complain about mismatch
        // between the calling uid and the package uid
        final long identity = Binder.clearCallingIdentity();
        Cursor cursor = null;
        try {
            cursor = resolver.query(
                    messageUri,
                    new String[]{ Telephony.Sms.TYPE },
                    null/*selection*/,
                    null/*selectionArgs*/,
                    null/*sortOrder*/);
            if (cursor != null && cursor.moveToFirst()) {
                final int type = cursor.getInt(0);
                return type == Telephony.Sms.MESSAGE_TYPE_DRAFT
                        || type == Telephony.Sms.MESSAGE_TYPE_FAILED
                        || (Flags.messagePromotion() && type == Telephony.Sms.MESSAGE_TYPE_OUTBOX);
            }
        } catch (SQLiteException e) {
            loge("isSendableStoredMessage: query message type failed", e);
        } finally {
            if (cursor != null) {
                cursor.close();
            }
            Binder.restoreCallingIdentity(identity);
        }
        return false;
    }

    // Return an array including both the SMS text (0) and address (1)
    public String[] loadTextAndAddress(ContentResolver resolver, Uri messageUri) {
        // Clear the calling identity and query the database using the phone user id
        // Otherwise the AppOps check in TelephonyProvider would complain about mismatch
        // between the calling uid and the package uid
        final long identity = Binder.clearCallingIdentity();
        Cursor cursor = null;
        try {
            cursor = resolver.query(
                    messageUri,
                    new String[]{
                            Telephony.Sms.BODY,
                            Telephony.Sms.ADDRESS
                    },
                    null/*selection*/,
                    null/*selectionArgs*/,
                    null/*sortOrder*/);
            if (cursor != null && cursor.moveToFirst()) {
                return new String[]{ cursor.getString(0), cursor.getString(1) };
            }
        } catch (SQLiteException e) {
            loge("loadText: query message text failed", e);
        } finally {
            if (cursor != null) {
                cursor.close();
            }
            Binder.restoreCallingIdentity(identity);
        }
        return null;
    }

    @VisibleForTesting
    public void notifyIfOutgoingEmergencySms(String destAddr) {
        Phone[] allPhones = mPhoneFactoryProxy.getPhones();
        EmergencyNumber emergencyNumber = getEmergencyNumber(mPhone, destAddr);
        if (emergencyNumber != null) {
            mPhone.notifyOutgoingEmergencySms(emergencyNumber);
        } else if (allPhones.length > 1) {
            // If there are multiple active SIMs, check all instances:
            for (Phone phone : allPhones) {
                // If the current iteration was already checked, skip:
                if (phone.getPhoneId() == mPhone.getPhoneId()) {
                    continue;
                }
                emergencyNumber = getEmergencyNumber(phone, destAddr);
                if (emergencyNumber != null) {
                    mPhone.notifyOutgoingEmergencySms(emergencyNumber);
                    break;
                }
            }
        }
    }

    private EmergencyNumber getEmergencyNumber(Phone phone, String number) {
        if (!phone.hasCalling()) return null;
        EmergencyNumberTracker tracker = phone.getEmergencyNumberTracker();
        if (tracker == null) return null;
        return tracker.getEmergencyNumber(number);
    }

    private void returnUnspecifiedFailure(PendingIntent pi) {
        if (pi != null) {
            try {
                pi.send(SmsManager.RESULT_ERROR_GENERIC_FAILURE);
            } catch (PendingIntent.CanceledException e) {
                // ignore
            }
        }
    }

    private void returnUnspecifiedFailure(List<PendingIntent> pis) {
        if (pis == null) {
            return;
        }
        for (PendingIntent pi : pis) {
            returnUnspecifiedFailure(pi);
        }
    }

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    private String filterDestAddress(String destAddr) {
        String result = SmsNumberUtils.filterDestAddr(mContext, mPhone.getSubId(), destAddr);
        return result != null ? result : destAddr;
    }

    private void waitForResult(Request request) {
        synchronized (request) {
            while (!request.mStatus.get()) {
                try {
                    request.wait();
                } catch (InterruptedException e) {
                    log("Interrupted while waiting for result");
                }
            }
        }
    }

    /**
     * Get InboundSmsHandler for the phone.
     */
    public InboundSmsHandler getInboundSmsHandler(boolean is3gpp2) {
        return mDispatchersController.getInboundSmsHandler(is3gpp2);
    }

    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
        pw.println("Enabled GSM channels: " + mCellBroadcastRangeManager);
        pw.println("Enabled CDMA channels: " + mCdmaBroadcastRangeManager);
        pw.println("CellBroadcast log:");
        mCellBroadcastLocalLog.dump(fd, pw, args);
        pw.println("SMS dispatcher controller log:");
        mDispatchersController.dump(fd, pw, args);
        pw.flush();
    }
}
