/* * Copyright (C) 2021 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.TelephonyManager.HAL_SERVICE_VOICE; import static com.android.internal.telephony.RILConstants.RIL_UNSOL_CALL_RING; import static com.android.internal.telephony.RILConstants.RIL_UNSOL_CDMA_CALL_WAITING; import static com.android.internal.telephony.RILConstants.RIL_UNSOL_CDMA_INFO_REC; import static com.android.internal.telephony.RILConstants.RIL_UNSOL_CDMA_OTA_PROVISION_STATUS; import static com.android.internal.telephony.RILConstants.RIL_UNSOL_EMERGENCY_NUMBER_LIST; import static com.android.internal.telephony.RILConstants.RIL_UNSOL_ENTER_EMERGENCY_CALLBACK_MODE; import static com.android.internal.telephony.RILConstants.RIL_UNSOL_EXIT_EMERGENCY_CALLBACK_MODE; import static com.android.internal.telephony.RILConstants.RIL_UNSOL_ON_SS; import static com.android.internal.telephony.RILConstants.RIL_UNSOL_ON_USSD; import static com.android.internal.telephony.RILConstants.RIL_UNSOL_RESEND_INCALL_MUTE; import static com.android.internal.telephony.RILConstants.RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED; import static com.android.internal.telephony.RILConstants.RIL_UNSOL_RINGBACK_TONE; import static com.android.internal.telephony.RILConstants.RIL_UNSOL_SRVCC_STATE_NOTIFY; import static com.android.internal.telephony.RILConstants.RIL_UNSOL_STK_CALL_SETUP; import static com.android.internal.telephony.RILConstants.RIL_UNSOL_STK_CC_ALPHA_NOTIFY; import android.hardware.radio.voice.IRadioVoiceIndication; import android.os.AsyncResult; import android.telephony.emergency.EmergencyNumber; import com.android.internal.telephony.cdma.CdmaCallWaitingNotification; import com.android.internal.telephony.cdma.CdmaInformationRecords; import com.android.internal.telephony.gsm.SsData; import java.util.ArrayList; import java.util.List; /** * Interface declaring unsolicited radio indications for voice APIs. */ public class VoiceIndication extends IRadioVoiceIndication.Stub { private final RIL mRil; public VoiceIndication(RIL ril) { mRil = ril; } /** * Ring indication for an incoming call (eg, RING or CRING event). * The rate of these events is controlled by ro.telephony.call_ring.delay and has a default * value of 3000 (3 seconds) if absent. * @param indicationType Type of radio indication * @param isGsm true for GSM & false for CDMA * @param record CDMA signal information record */ public void callRing(int indicationType, boolean isGsm, android.hardware.radio.voice.CdmaSignalInfoRecord record) { mRil.processIndication(HAL_SERVICE_VOICE, indicationType); char[] response = null; // Ignore record for gsm if (!isGsm) { // TODO: Clean this up with a parcelable class for better self-documentation response = new char[4]; response[0] = (char) (record.isPresent ? 1 : 0); response[1] = (char) record.signalType; response[2] = (char) record.alertPitch; response[3] = (char) record.signal; mRil.writeMetricsCallRing(response); } if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_CALL_RING, response); if (mRil.mRingRegistrant != null) { mRil.mRingRegistrant.notifyRegistrant(new AsyncResult(null, response, null)); } } /** * Indicates when call state has changed. Redundant or extraneous invocations are tolerated. * @param indicationType Type of radio indication */ public void callStateChanged(int indicationType) { mRil.processIndication(HAL_SERVICE_VOICE, indicationType); if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED); mRil.mCallStateRegistrants.notifyRegistrants(); } /** * Indicates when CDMA radio receives a call waiting indication. * @param indicationType Type of radio indication * @param callWaitingRecord Cdma CallWaiting information */ public void cdmaCallWaiting(int indicationType, android.hardware.radio.voice.CdmaCallWaiting callWaitingRecord) { mRil.processIndication(HAL_SERVICE_VOICE, indicationType); // TODO: create a CdmaCallWaitingNotification constructor that takes in these fields to make // sure no fields are missing CdmaCallWaitingNotification notification = new CdmaCallWaitingNotification(); notification.number = callWaitingRecord.number; notification.numberPresentation = CdmaCallWaitingNotification.presentationFromCLIP( callWaitingRecord.numberPresentation); notification.name = callWaitingRecord.name; notification.namePresentation = notification.numberPresentation; notification.isPresent = callWaitingRecord.signalInfoRecord.isPresent ? 1 : 0; notification.signalType = callWaitingRecord.signalInfoRecord.signalType; notification.alertPitch = callWaitingRecord.signalInfoRecord.alertPitch; notification.signal = callWaitingRecord.signalInfoRecord.signal; notification.numberType = callWaitingRecord.numberType; notification.numberPlan = callWaitingRecord.numberPlan; if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_CDMA_CALL_WAITING, notification); mRil.mCallWaitingInfoRegistrants.notifyRegistrants( new AsyncResult(null, notification, null)); } /** * Indicates when CDMA radio receives one or more info recs. * @param indicationType Type of radio indication * @param records New CDMA information */ public void cdmaInfoRec(int indicationType, android.hardware.radio.voice.CdmaInformationRecord[] records) { mRil.processIndication(HAL_SERVICE_VOICE, indicationType); for (int i = 0; i < records.length; i++) { android.hardware.radio.voice.CdmaInformationRecord record = records[i]; int id = record.name; CdmaInformationRecords cdmaInformationRecords; switch (id) { case CdmaInformationRecords.RIL_CDMA_DISPLAY_INFO_REC: case CdmaInformationRecords.RIL_CDMA_EXTENDED_DISPLAY_INFO_REC: CdmaInformationRecords.CdmaDisplayInfoRec cdmaDisplayInfoRec = new CdmaInformationRecords.CdmaDisplayInfoRec(id, record.display[0].alphaBuf); cdmaInformationRecords = new CdmaInformationRecords(cdmaDisplayInfoRec); break; case CdmaInformationRecords.RIL_CDMA_CALLED_PARTY_NUMBER_INFO_REC: case CdmaInformationRecords.RIL_CDMA_CALLING_PARTY_NUMBER_INFO_REC: case CdmaInformationRecords.RIL_CDMA_CONNECTED_NUMBER_INFO_REC: android.hardware.radio.voice.CdmaNumberInfoRecord numInfoRecord = record.number[0]; CdmaInformationRecords.CdmaNumberInfoRec cdmaNumberInfoRec = new CdmaInformationRecords.CdmaNumberInfoRec(id, numInfoRecord.number, numInfoRecord.numberType, numInfoRecord.numberPlan, numInfoRecord.pi, numInfoRecord.si); cdmaInformationRecords = new CdmaInformationRecords(cdmaNumberInfoRec); break; case CdmaInformationRecords.RIL_CDMA_SIGNAL_INFO_REC: android.hardware.radio.voice.CdmaSignalInfoRecord signalInfoRecord = record.signal[0]; CdmaInformationRecords.CdmaSignalInfoRec cdmaSignalInfoRec = new CdmaInformationRecords.CdmaSignalInfoRec( signalInfoRecord.isPresent ? 1 : 0, signalInfoRecord.signalType, signalInfoRecord.alertPitch, signalInfoRecord.signal); cdmaInformationRecords = new CdmaInformationRecords(cdmaSignalInfoRec); break; case CdmaInformationRecords.RIL_CDMA_REDIRECTING_NUMBER_INFO_REC: android.hardware.radio.voice.CdmaRedirectingNumberInfoRecord redirectingNumberInfoRecord = record.redir[0]; CdmaInformationRecords.CdmaRedirectingNumberInfoRec cdmaRedirectingNumberInfoRec = new CdmaInformationRecords.CdmaRedirectingNumberInfoRec( redirectingNumberInfoRecord.redirectingNumber.number, redirectingNumberInfoRecord.redirectingNumber.numberType, redirectingNumberInfoRecord.redirectingNumber.numberPlan, redirectingNumberInfoRecord.redirectingNumber.pi, redirectingNumberInfoRecord.redirectingNumber.si, redirectingNumberInfoRecord.redirectingReason); cdmaInformationRecords = new CdmaInformationRecords( cdmaRedirectingNumberInfoRec); break; case CdmaInformationRecords.RIL_CDMA_LINE_CONTROL_INFO_REC: android.hardware.radio.voice.CdmaLineControlInfoRecord lineControlInfoRecord = record.lineCtrl[0]; CdmaInformationRecords.CdmaLineControlInfoRec cdmaLineControlInfoRec = new CdmaInformationRecords.CdmaLineControlInfoRec( lineControlInfoRecord.lineCtrlPolarityIncluded, lineControlInfoRecord.lineCtrlToggle, lineControlInfoRecord.lineCtrlReverse, lineControlInfoRecord.lineCtrlPowerDenial); cdmaInformationRecords = new CdmaInformationRecords(cdmaLineControlInfoRec); break; case CdmaInformationRecords.RIL_CDMA_T53_CLIR_INFO_REC: CdmaInformationRecords.CdmaT53ClirInfoRec cdmaT53ClirInfoRec = new CdmaInformationRecords.CdmaT53ClirInfoRec(record.clir[0].cause); cdmaInformationRecords = new CdmaInformationRecords(cdmaT53ClirInfoRec); break; case CdmaInformationRecords.RIL_CDMA_T53_AUDIO_CONTROL_INFO_REC: android.hardware.radio.voice.CdmaT53AudioControlInfoRecord audioControlInfoRecord = record.audioCtrl[0]; CdmaInformationRecords.CdmaT53AudioControlInfoRec cdmaT53AudioControlInfoRec = new CdmaInformationRecords.CdmaT53AudioControlInfoRec( audioControlInfoRecord.upLink, audioControlInfoRecord.downLink); cdmaInformationRecords = new CdmaInformationRecords(cdmaT53AudioControlInfoRec); break; default: throw new RuntimeException("RIL_UNSOL_CDMA_INFO_REC: unsupported record. Got " + CdmaInformationRecords.idToString(id) + " "); } if (mRil.isLogOrTrace()) { mRil.unsljLogRet(RIL_UNSOL_CDMA_INFO_REC, cdmaInformationRecords); } mRil.notifyRegistrantsCdmaInfoRec(cdmaInformationRecords); } } /** * Indicates when CDMA radio receives an update of the progress of an OTASP/OTAPA call. * @param indicationType Type of radio indication * @param status CDMA OTA provision status */ public void cdmaOtaProvisionStatus(int indicationType, int status) { mRil.processIndication(HAL_SERVICE_VOICE, indicationType); int[] response = new int[] {status}; if (mRil.isLogOrTrace()) { mRil.unsljLogRet(RIL_UNSOL_CDMA_OTA_PROVISION_STATUS, response); } mRil.mOtaProvisionRegistrants.notifyRegistrants(new AsyncResult(null, response, null)); } /** * Indicates current emergency number list. * @param indicationType Type of radio indication * @param emergencyNumberList Current list of emergency numbers known to radio */ public void currentEmergencyNumberList(int indicationType, android.hardware.radio.voice.EmergencyNumber[] emergencyNumberList) { mRil.processIndication(HAL_SERVICE_VOICE, indicationType); List response = new ArrayList<>(emergencyNumberList.length); for (android.hardware.radio.voice.EmergencyNumber enHal : emergencyNumberList) { EmergencyNumber emergencyNumber = new EmergencyNumber(enHal.number, MccTable.countryCodeForMcc(enHal.mcc), enHal.mnc, enHal.categories, RILUtils.primitiveArrayToArrayList(enHal.urns), enHal.sources, EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN); response.add(emergencyNumber); } if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_EMERGENCY_NUMBER_LIST, response); // Cache emergency number list from last indication. mRil.cacheEmergencyNumberListIndication(response); // Notify emergency number list from radio to registrants mRil.mEmergencyNumberListRegistrants.notifyRegistrants( new AsyncResult(null, response, null)); } /** * Indicates that the radio system selection module has autonomously entered emergency * callback mode. * @param indicationType Type of radio indication */ public void enterEmergencyCallbackMode(int indicationType) { mRil.processIndication(HAL_SERVICE_VOICE, indicationType); if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_ENTER_EMERGENCY_CALLBACK_MODE); if (mRil.mEmergencyCallbackModeRegistrant != null) { mRil.mEmergencyCallbackModeRegistrant.notifyRegistrant(); } } /** * Indicates when Emergency Callback Mode ends. Indicates that the radio system selection module * has proactively exited emergency callback mode. * @param indicationType Type of radio indication */ public void exitEmergencyCallbackMode(int indicationType) { mRil.processIndication(HAL_SERVICE_VOICE, indicationType); if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_EXIT_EMERGENCY_CALLBACK_MODE); mRil.mExitEmergencyCallbackModeRegistrants.notifyRegistrants(); } /** * Indicates that network doesn't have in-band information, need to play out-band tone. * @param indicationType Type of radio indication * @param start true = start play ringback tone, false = stop playing ringback tone */ public void indicateRingbackTone(int indicationType, boolean start) { mRil.processIndication(HAL_SERVICE_VOICE, indicationType); if (mRil.isLogOrTrace()) mRil.unsljLogvRet(RIL_UNSOL_RINGBACK_TONE, start); mRil.mRingbackToneRegistrants.notifyRegistrants(new AsyncResult(null, start, null)); } /** * Indicates when Supplementary service(SS) response is received when DIAL/USSD/SS is changed to * SS by call control. * @param indicationType Type of radio indication * @param ss StkCcUnsolSsResult */ public void onSupplementaryServiceIndication(int indicationType, android.hardware.radio.voice.StkCcUnsolSsResult ss) { mRil.processIndication(HAL_SERVICE_VOICE, indicationType); int num; SsData ssData = new SsData(); ssData.serviceType = ssData.ServiceTypeFromRILInt(ss.serviceType); ssData.requestType = ssData.RequestTypeFromRILInt(ss.requestType); ssData.teleserviceType = ssData.TeleserviceTypeFromRILInt(ss.teleserviceType); ssData.serviceClass = ss.serviceClass; // This is service class sent in the SS request. ssData.result = ss.result; // This is the result of the SS request. if (ssData.serviceType.isTypeCF() && ssData.requestType.isTypeInterrogation()) { android.hardware.radio.voice.CfData cfData = ss.cfData[0]; num = cfData.cfInfo.length; ssData.cfInfo = new CallForwardInfo[num]; for (int i = 0; i < num; i++) { android.hardware.radio.voice.CallForwardInfo cfInfo = cfData.cfInfo[i]; ssData.cfInfo[i] = new CallForwardInfo(); ssData.cfInfo[i].status = cfInfo.status; ssData.cfInfo[i].reason = cfInfo.reason; ssData.cfInfo[i].serviceClass = cfInfo.serviceClass; ssData.cfInfo[i].toa = cfInfo.toa; ssData.cfInfo[i].number = cfInfo.number; ssData.cfInfo[i].timeSeconds = cfInfo.timeSeconds; mRil.riljLog("[SS Data] CF Info " + i + " : " + ssData.cfInfo[i]); } } else { android.hardware.radio.voice.SsInfoData ssInfo = ss.ssInfo[0]; num = ssInfo.ssInfo.length; ssData.ssInfo = new int[num]; for (int i = 0; i < num; i++) { ssData.ssInfo[i] = ssInfo.ssInfo[i]; mRil.riljLog("[SS Data] SS Info " + i + " : " + ssData.ssInfo[i]); } } if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_ON_SS, ssData); if (mRil.mSsRegistrant != null) { mRil.mSsRegistrant.notifyRegistrant(new AsyncResult(null, ssData, null)); } } /** * Indicates when a new USSD message is received. The USSD session is assumed to persist if the * type code is REQUEST, otherwise the current session (if any) is assumed to have terminated. * @param indicationType Type of radio indication * @param ussdModeType USSD type code * @param msg Message string in UTF-8, if applicable */ public void onUssd(int indicationType, int ussdModeType, String msg) { mRil.processIndication(HAL_SERVICE_VOICE, indicationType); if (mRil.isLogOrTrace()) mRil.unsljLogMore(RIL_UNSOL_ON_USSD, "" + ussdModeType); // TODO: Clean this up with a parcelable class for better self-documentation String[] resp = new String[]{"" + ussdModeType, msg}; if (mRil.mUSSDRegistrant != null) { mRil.mUSSDRegistrant.notifyRegistrant(new AsyncResult(null, resp, null)); } } /** * Indicates that framework/application must reset the uplink mute state. * @param indicationType Type of radio indication */ public void resendIncallMute(int indicationType) { mRil.processIndication(HAL_SERVICE_VOICE, indicationType); if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_RESEND_INCALL_MUTE); mRil.mResendIncallMuteRegistrants.notifyRegistrants(); } /** * Indicates when Single Radio Voice Call Continuity (SRVCC) progress state has changed. * @param indicationType Type of radio indication * @param state New SRVCC State */ public void srvccStateNotify(int indicationType, int state) { mRil.processIndication(HAL_SERVICE_VOICE, indicationType); int[] response = new int[] {state}; if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_SRVCC_STATE_NOTIFY, response); mRil.writeMetricsSrvcc(state); mRil.mSrvccStateRegistrants.notifyRegistrants(new AsyncResult(null, response, null)); } /** * Indicates when there is an ALPHA from UICC during Call Control. * @param indicationType Type of radio indication * @param alpha ALPHA string from UICC in UTF-8 format */ public void stkCallControlAlphaNotify(int indicationType, String alpha) { mRil.processIndication(HAL_SERVICE_VOICE, indicationType); if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_STK_CC_ALPHA_NOTIFY, alpha); if (mRil.mCatCcAlphaRegistrant != null) { mRil.mCatCcAlphaRegistrant.notifyRegistrant(new AsyncResult(null, alpha, null)); } } /** * Indicates when SIM wants application to setup a voice call. * @param indicationType Type of radio indication * @param timeout Timeout value in milliseconds for setting up voice call */ public void stkCallSetup(int indicationType, long timeout) { mRil.processIndication(HAL_SERVICE_VOICE, indicationType); if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_STK_CALL_SETUP, timeout); if (mRil.mCatCallSetUpRegistrant != null) { mRil.mCatCallSetUpRegistrant.notifyRegistrant(new AsyncResult(null, timeout, null)); } } @Override public String getInterfaceHash() { return IRadioVoiceIndication.HASH; } @Override public int getInterfaceVersion() { return IRadioVoiceIndication.VERSION; } }