blob: 674c15361c49b440ab7d1d86c939ad8798218605 [file] [log] [blame]
//
// Copyright (C) 2020 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.
#include "host/commands/modem_simulator/call_service.h"
#include <android-base/logging.h>
#include <chrono>
#include <iostream>
#include <thread>
#include "host/commands/modem_simulator/nvram_config.h"
namespace cuttlefish {
CallService::CallService(int32_t service_id, ChannelMonitor* channel_monitor,
ThreadLooper* thread_looper)
: ModemService(service_id, this->InitializeCommandHandlers(),
channel_monitor, thread_looper) {
InitializeServiceState();
}
void CallService::InitializeServiceState() {
auto nvram_config = NvramConfig::Get();
auto instance = nvram_config->ForInstance(service_id_);
in_emergency_mode_ = instance.emergency_mode();
last_active_call_index_ = 1;
mute_on_ = false;
}
void CallService::SetupDependency(SimService* sim, NetworkService* net) {
sim_service_ = sim;
network_service_ = net;
}
std::vector<CommandHandler> CallService::InitializeCommandHandlers() {
std::vector<CommandHandler> command_handlers = {
CommandHandler("D",
[this](const Client& client, std::string& cmd) {
this->HandleDial(client, cmd);
}),
CommandHandler(
"A",
[this](const Client& client) { this->HandleAcceptCall(client); }),
CommandHandler(
"H",
[this](const Client& client) { this->HandleRejectCall(client); }),
CommandHandler(
"+CLCC",
[this](const Client& client) { this->HandleCurrentCalls(client); }),
CommandHandler("+CHLD=",
[this](const Client& client, std::string& cmd) {
this->HandleHangup(client, cmd);
}),
CommandHandler("+CMUT",
[this](const Client& client, std::string& cmd) {
this->HandleMute(client, cmd);
}),
CommandHandler("+VTS=",
[this](const Client& client, std::string& cmd) {
this->HandleSendDtmf(client, cmd);
}),
CommandHandler("+CUSD=",
[this](const Client& client, std::string& cmd) {
this->HandleCancelUssd(client, cmd);
}),
CommandHandler("+WSOS=0",
[this](const Client& client, std::string& cmd) {
this->HandleEmergencyMode(client, cmd);
}),
CommandHandler("+REMOTECALL",
[this](const Client& client, std::string& cmd) {
this->HandleRemoteCall(client, cmd);
}),
};
return (command_handlers);
}
// This also resumes held calls
void CallService::SimulatePendingCallsAnswered() {
for (auto& iter : active_calls_) {
if (iter.second.isCallDialing()) {
iter.second.SetCallActive();
}
}
}
void CallService::TimerWaitingRemoteCallResponse(CallToken call_token) {
LOG(DEBUG) << "Dialing id: " << call_token.first
<< ", number: " << call_token.second << "timeout, cancel";
auto iter = active_calls_.find(call_token.first);
if (iter != active_calls_.end() && iter->second.number == call_token.second) {
if (iter->second.remote_client != std::nullopt) {
CloseRemoteConnection(*(iter->second.remote_client));
}
active_calls_.erase(iter); // match
CallStateUpdate();
} // else not match, ignore
}
/* ATD */
void CallService::HandleDial(const Client& client, const std::string& command) {
// Check the network registration state
auto registration_state = NetworkService::NET_REGISTRATION_UNKNOWN;
if (network_service_) {
registration_state = network_service_->GetVoiceRegistrationState();
}
bool emergency_only = false;
if (registration_state == NetworkService::NET_REGISTRATION_HOME ||
registration_state == NetworkService::NET_REGISTRATION_ROAMING) {
emergency_only = false;
} else if (registration_state == NetworkService::NET_REGISTRATION_EMERGENCY) {
emergency_only = true;
} else {
client.SendCommandResponse(kCmeErrorNoNetworkService);
return;
}
CommandParser cmd(command);
cmd.SkipPrefixAT();
std::string number;
bool emergency_number = false;
/**
* Normal dial: ATDnumber[clir];
* Emergency dial: ATDnumber@[category],#[clir];
*/
auto pos = cmd->find_last_of('@');
if (pos != std::string_view::npos) {
emergency_number = true;
number = cmd->substr(1, pos -1); // Skip 'D' and ignore category, clir
} else { // Remove 'i' or 'I' or ';'
pos = cmd->find_last_of('i');
if (pos == std::string_view::npos) {
pos = cmd->find_last_of('I');
if (pos == std::string_view::npos) {
pos = cmd->find_last_of(';');
}
}
if (pos == std::string_view::npos) {
number = cmd->substr(1);
} else {
number = cmd->substr(1, pos -1);
}
}
// Check the number is valid digits or not
if (strspn(number.c_str(), "1234567890") != number.size()) {
client.SendCommandResponse(kCmeErrorInCorrectParameters);
return;
}
if (emergency_only && !emergency_number) {
client.SendCommandResponse(kCmeErrorNetworkNotAllowedEmergencyCallsOnly);
return;
}
// If the number is not emergency number, FDN enabled and the number is not in
// the fdn list, return kCmeErrorFixedDialNumberOnlyAllowed.
if (!emergency_number && sim_service_->IsFDNEnabled() &&
!sim_service_->IsFixedDialNumber(number)) {
client.SendCommandResponse(kCmeErrorFixedDialNumberOnlyAllowed);
return;
}
int port = 0;
if (number.length() == 11) {
port = std::stoi(number.substr(7));
} else if (number.length() == 4) {
port = std::stoi(number);
}
if (port >= kRemotePortRange.first &&
port <= kRemotePortRange.second) { // May be a remote call
std::stringstream ss;
ss << port;
auto remote_port = ss.str();
auto remote_client = ConnectToRemoteCvd(remote_port);
if (!remote_client->IsOpen()) {
client.SendCommandResponse(kCmeErrorNoNetworkService);
return;
}
auto local_host_port = GetHostId();
if (local_host_port == remote_port) {
client.SendCommandResponse(kCmeErrorOperationNotAllowed);
return;
}
if (channel_monitor_) {
channel_monitor_->SetRemoteClient(remote_client, false);
}
ss.clear();
ss.str("");
ss << "AT+REMOTECALL=4,0,0,\"" << local_host_port << "\",129";
SendCommandToRemote(remote_client, "REM0");
SendCommandToRemote(remote_client, ss.str());
CallStatus call_status(remote_port);
call_status.is_remote_call = true;
call_status.is_mobile_terminated = false;
call_status.call_state = CallStatus::CALL_STATE_DIALING;
call_status.remote_client = remote_client;
int index = last_active_call_index_++;
auto call_token = std::make_pair(index, call_status.number);
call_status.timeout_serial = thread_looper_->Post(
makeSafeCallback<CallService>(this,
[call_token](CallService* me) {
me->TimerWaitingRemoteCallResponse(
call_token);
}),
std::chrono::minutes(1));
active_calls_[index] = call_status;
} else {
CallStatus call_status(number);
call_status.is_mobile_terminated = false;
call_status.call_state = CallStatus::CALL_STATE_DIALING;
auto index = last_active_call_index_++;
active_calls_[index] = call_status;
if (emergency_number) {
in_emergency_mode_ = true;
SendUnsolicitedCommand("+WSOS: 1");
}
thread_looper_->Post(
makeSafeCallback(this, &CallService::SimulatePendingCallsAnswered),
std::chrono::seconds(1));
}
client.SendCommandResponse("OK");
std::this_thread::sleep_for(std::chrono::seconds(2));
}
void CallService::SendCallStatusToRemote(CallStatus& call,
CallStatus::CallState state) {
if (call.is_remote_call && call.remote_client != std::nullopt) {
std::stringstream ss;
ss << "AT+REMOTECALL=" << state << "," << call.is_voice_mode << ","
<< call.is_multi_party << ",\"" << GetHostId() << "\","
<< call.is_international;
SendCommandToRemote(*(call.remote_client), ss.str());
if (state == CallStatus::CALL_STATE_HANGUP) {
CloseRemoteConnection(*(call.remote_client));
}
}
}
/* ATA */
void CallService::HandleAcceptCall(const Client& client) {
for (auto& iter : active_calls_) {
if (iter.second.isCallIncoming()) {
iter.second.SetCallActive();
SendCallStatusToRemote(iter.second, CallStatus::CALL_STATE_ACTIVE);
} else if (iter.second.isCallActive()) {
iter.second.SetCallBackground();
SendCallStatusToRemote(iter.second, CallStatus::CALL_STATE_HELD);
}
}
client.SendCommandResponse("OK");
}
/* ATH */
void CallService::HandleRejectCall(const Client& client) {
for (auto iter = active_calls_.begin(); iter != active_calls_.end();) {
/* ATH: hangup, since user is busy */
if (iter->second.isCallIncoming()) {
SendCallStatusToRemote(iter->second, CallStatus::CALL_STATE_HANGUP);
iter = active_calls_.erase(iter);
} else {
++iter;
}
}
client.SendCommandResponse("OK");
}
/**
* AT+CLCC
* Returns list of current calls of MT. If command succeeds but no
* calls are available, no information response is sent to TE.
*
* command Possible response(s)
* AT+CLCC [+CLCC: <ccid1>,<dir>,<stat>,<mode>,<mpty>
* [,<number>,<type>[,<alpha>[,<priority>
* [,<CLI validity>]]]][<CR><LF>
* +CLCC: <ccid2>,<dir>,<stat>,<mode>,<mpty>
* [,<number>,<type>[,<alpha>[,<priority>[,<CLI validity>]]]]
* +CME ERROR: <err>
*
* <ccidx>: integer type. This number can be used in +CHLD command
* operations. Value range is from 1 to N. N, the maximum number of
* simultaneous call control processes is implementation specific.
* <dir>: integer type
* 0 mobile originated (MO) call
1 mobile terminated (MT) call
* <stat>: integer type (state of the call)
* 0 active
* 1 held
* 2 dialing (MO call)
* 3 alerting (MO call)
* 4 incoming (MT call)
* 5 waiting (MT call)
* <mode>: integer type (bearer/teleservice)
* 0 voice
* 1 data
* 2 fax
* 3 voice followed by data, voice mode
* 4 alternating voice/data, voice mode
* 5 alternating voice/fax, voice mode
* 6 voice followed by data, data mode
* 7 alternating voice/data, data mode
* 8 alternating voice/fax, fax mode
* 9 unknown
* <mpty>: integer type
* 0 call is not one of multiparty (conference) call parties
* 1 call is one of multiparty (conference) call parties
* <number>: string type phone number in format specified by <type>.
* <type>: type of address octet in integer format
*
*see RIL_REQUEST_GET_CURRENT_CALLS in RIL
*/
void CallService::HandleCurrentCalls(const Client& client) {
std::vector<std::string> responses;
std::stringstream ss;
// AT+CLCC
// [+CLCC: <ccid1>,<dir>,<stat>,<mode>,<mpty>[,<number>,<type>[,<alpha>[,<priority>[,<CLI validity>]]]]
// [+CLCC: <ccid2>,<dir>,<stat>,<mode>,<mpty>[,<number>,<type>[,<alpha>[,<priority>[,<CLI validity>]]]]
// [...]]]
for (auto iter = active_calls_.begin(); iter != active_calls_.end(); ++iter) {
int index = iter->first;
int dir = iter->second.is_mobile_terminated;
CallStatus::CallState call_state = iter->second.call_state;
int mode = iter->second.is_voice_mode;
int mpty = iter->second.is_multi_party;
int type = iter->second.is_international ? 145 : 129;
std::string number = iter->second.number;
ss.clear();
ss << "+CLCC: " << index << "," << dir << "," << call_state << ","
<< mode << "," << mpty << "," << number<< "," << type;
responses.push_back(ss.str());
ss.str("");
}
responses.push_back("OK");
client.SendCommandResponse(responses);
}
/**
* AT+CHLD
* This command allows the control of the following call related services:
* 1) a call can be temporarily disconnected from the MT but the connection
* is retained by the network;
* 2) multiparty conversation (conference calls);
* 3) the served subscriber who has two calls (one held and the other
* either active or alerting) can connect the other parties and release
* the served subscriber's own connection.
*
* Calls can be put on hold, recovered, released, added to conversation,
* and transferred similarly.
*
* command Possible response(s)
* +CHLD=<n> +CME ERROR: <err>
*
* +CHLD=? +CHLD: (list of supported <n>s)
* e.g. +CHLD: (0,1,1x,2,2x,3,4)
*
*
* see RIL_REQUEST_HANGUP_WAITING_OR_BACKGROUND
* RIL_REQUEST_HANGUP_FOREGROUND_RESUME_BACKGROUND
* RIL_REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE
* RIL_REQUEST_CONFERENCE
* RIL_REQUEST_SEPARATE_CONNECTION
* RIL_REQUEST_HANGUP
* RIL_REQUEST_UDUB in RIL
*/
void CallService::HandleHangup(const Client& client,
const std::string& command) {
std::vector<std::string> responses;
CommandParser cmd(command);
cmd.SkipPrefix();
std::string action(*cmd);
int n = std::stoi(action.substr(0, 1));
int index = -1;
if (cmd->length() > 1) {
index = std::stoi(action.substr(1));
}
switch (n) {
case 0: // Release all held calls or set User Determined User Busy(UDUB) for a waiting call
for (auto iter = active_calls_.begin(); iter != active_calls_.end();) {
if (iter->second.isCallIncoming() ||
iter->second.isCallBackground() ||
iter->second.isCallWaiting()) {
SendCallStatusToRemote(iter->second, CallStatus::CALL_STATE_HANGUP);
iter = active_calls_.erase(iter);
} else {
++iter;
}
}
break;
case 1:
if (index == -1) { // Release all active calls and accepts the other(hold or waiting) call
for (auto iter = active_calls_.begin(); iter != active_calls_.end();) {
if (iter->second.isCallActive()) {
SendCallStatusToRemote(iter->second, CallStatus::CALL_STATE_HANGUP);
iter = active_calls_.erase(iter);
continue;
} else if (iter->second.isCallBackground() ||
iter->second.isCallWaiting()) {
iter->second.SetCallActive();
SendCallStatusToRemote(iter->second, CallStatus::CALL_STATE_ACTIVE);
}
++iter;
}
} else { // Release a specific active call
auto iter = active_calls_.find(index);
if (iter != active_calls_.end()) {
SendCallStatusToRemote(iter->second, CallStatus::CALL_STATE_HANGUP);
active_calls_.erase(iter);
}
}
break;
case 2:
if (index == -1) { // Place all active calls and the waiting calls, activates all held calls
for (auto& iter : active_calls_) {
if (iter.second.isCallActive() || iter.second.isCallWaiting()) {
iter.second.SetCallBackground();
SendCallStatusToRemote(iter.second, CallStatus::CALL_STATE_HELD);
} else if (iter.second.isCallBackground()) {
iter.second.SetCallActive();
SendCallStatusToRemote(iter.second, CallStatus::CALL_STATE_ACTIVE);
}
}
} else { // Disconnect a call from the conversation
auto iter = active_calls_.find(index);
if (iter != active_calls_.end()) {
SendCallStatusToRemote(iter->second, CallStatus::CALL_STATE_HANGUP);
active_calls_.erase(iter);
}
}
break;
case 3: // Adds an held call to the conversation
for (auto iter = active_calls_.begin(); iter != active_calls_.end(); ++iter) {
if (iter->second.isCallBackground()) {
iter->second.SetCallActive();
SendCallStatusToRemote(iter->second, CallStatus::CALL_STATE_ACTIVE);
}
}
break;
case 4: // Connect the two calls
for (auto iter = active_calls_.begin(); iter != active_calls_.end(); ++iter) {
if (iter->second.isCallBackground()) {
iter->second.SetCallActive();
SendCallStatusToRemote(iter->second, CallStatus::CALL_STATE_ACTIVE);
}
}
break;
default:
client.SendCommandResponse(kCmeErrorOperationNotAllowed);
return;
}
client.SendCommandResponse("OK");
}
/**
* AT+CMUT
* This command is used to enable and disable the uplink voice muting
* during a voice call.
* Read command returns the current value of <n>.
*
* Command Possible response(s)
* +CMUT=[<n>] +CME ERROR: <err>
* +CMUT? +CMUT: <n>
* +CME ERROR: <err>
*
* <n>: integer type
* 0 mute off
* 1 mute on
*
* see RIL_REQUEST_SET_MUTE or RIL_REQUEST_GET_MUTE in RIL
*/
void CallService::HandleMute(const Client& client, const std::string& command) {
std::vector<std::string> responses;
std::stringstream ss;
CommandParser cmd(command);
cmd.SkipPrefix(); // If AT+CMUT?, it remains AT+CMUT?
if (cmd == "AT+CMUT?") {
ss << "+CMUT: " << mute_on_;
responses.push_back(ss.str());
} else { // AT+CMUT = <n>
int n = cmd.GetNextInt();
switch (n) {
case 0: // Mute off
mute_on_ = false;
break;
case 1: // Mute on
mute_on_ = true;
break;
default:
client.SendCommandResponse(kCmeErrorInCorrectParameters);
return;
}
}
responses.push_back("OK");
client.SendCommandResponse(responses);
}
/**
* AT+VTS
* This command transmits DTMF, after a successful call connection.
* Setting Command is used to send one or more ASCII characters which make
* MSC (Mobile Switching Center) send DTMF tone to remote User.
*
* Command Possible response(s)
* AT+VTS=<dtmf>[,<duration>] +CME ERROR: <err>
*
* <dtmf>
* A single ASCII character in the set { 0 -9, #, *, A – D}.
* <duration>
* Refer to duration value range of +VTD command
*
* see RIL_REQUEST_DTMF in RIL
*/
void CallService::HandleSendDtmf(const Client& client,
const std::string& /*command*/) {
client.SendCommandResponse("OK");
}
void CallService::HandleCancelUssd(const Client& client,
const std::string& /*command*/) {
client.SendCommandResponse("OK");
}
/**
* AT+WSOS
*
* Command Possible response(s)
* +WSOS=[<n>] +CME ERROR: <err>
* +WSOS? +WSOS: <n>
* +CME ERROR: <err>
*
* <n>: integer type
* 0 enter emergency mode
* 1 exit emergency mode
*
* see RIL_REQUEST_EXIT_EMERGENCY_CALLBACK_MODE
* RIL_UNSOL_ENTER_EMERGENCY_CALLBACK_MODE
* RIL_UNSOL_EXIT_EMERGENCY_CALLBACK_MODE in RIL
*/
void CallService::HandleEmergencyMode(const Client& client,
const std::string& command) {
std::vector<std::string> responses;
CommandParser cmd(command);
cmd.SkipPrefix();
if (cmd == "AT+WSOS?") {
std::stringstream ss;
ss << "+WSOS: " << in_emergency_mode_;
responses.push_back(ss.str());
} else {
int n = cmd.GetNextInt();
switch (n) {
case 0: // Exit
in_emergency_mode_ = false;
break;
case 1: // Enter
in_emergency_mode_ = true;
break;
default:
client.SendCommandResponse(kCmeErrorInCorrectParameters);
return;
}
auto nvram_config = NvramConfig::Get();
auto instance = nvram_config->ForInstance(service_id_);
instance.set_emergency_mode(in_emergency_mode_);
NvramConfig::SaveToFile();
}
client.SendCommandResponse("OK");
}
void CallService::CallStateUpdate() {
SendUnsolicitedCommand("RING");
}
/**
* AT+REMOTECALL=<dir>,<stat>,<mode>,<mpty>,<number>,<num_type>
* This command allows to dial a remote voice call with another cuttlefish
* emulator. If request is successful, the remote emulator can simulate hold on,
* hang up, reject and so on.
*
* e.g. AT+REMOTECALL=4,0,0,6521,129
*
* <stat>: integer type (state of the call)
* 0 active
* 1 held
* 2 dialing (MO call)
* 3 alerting (MO call)
* 4 incoming (MT call)
* 5 waiting (MT call)
* <mode>: integer type
* 0 voice
* 1 data
* 2 fax
* 3 voice followed by data, voice mode
* 4 alternating voice/data, voice mode
* 5 alternating voice/fax, voice mode
* 6 voice followed by data, data mode
* 7 alternating voice/data, data mode
* 8 alternating voice/fax, fax mode
* 9 unknown
* <mpty>: integer type
* 0 call is not one of multiparty (conference) call parties
* 1 call is one of multiparty (conference) call parties
* <number>: string here maybe remote port
* <num_type>: type of address octet in integer format
*
* Note: reason should be added to indicate why hang up. Since not realizing
* RIL_LAST_CALL_FAIL_CAUSE, delay to be implemented.
*/
void CallService::HandleRemoteCall(const Client& client,
const std::string& command) {
CommandParser cmd(command);
cmd.SkipPrefix();
int state = cmd.GetNextInt();
int mode = cmd.GetNextInt();
int mpty = cmd.GetNextInt();
auto number = cmd.GetNextStr();
int num_type = cmd.GetNextInt();
// According to the number to determine whether it is a existing call
auto iter = active_calls_.begin();
for (; iter != active_calls_.end(); ++iter) {
if (iter->second.number == number) {
break;
}
}
switch (state) {
case CallStatus::CALL_STATE_ACTIVE: {
if (iter != active_calls_.end()) {
iter->second.SetCallActive();
if (iter->second.timeout_serial != std::nullopt) {
thread_looper_->CancelSerial(*(iter->second.timeout_serial));
}
}
break;
}
case CallStatus::CALL_STATE_HELD:
if (iter != active_calls_.end()) {
iter->second.SetCallBackground();
if (iter->second.timeout_serial != std::nullopt) {
thread_looper_->CancelSerial(*(iter->second.timeout_serial));
}
}
break;
case CallStatus::CALL_STATE_HANGUP:
if (iter != active_calls_.end()) {
auto client = iter->second.remote_client;
if (client != std::nullopt) {
CloseRemoteConnection(*client);
}
if (iter->second.timeout_serial != std::nullopt) {
thread_looper_->CancelSerial(*(iter->second.timeout_serial));
}
active_calls_.erase(iter);
}
break;
case CallStatus::CALL_STATE_INCOMING: {
if (network_service_) {
if (network_service_->isRadioOff()) {
LOG(DEBUG) << " radio is off, reject incoming call from: " << number;
client.client_fd->Close();
return;
}
}
CallStatus call_status(number);
call_status.is_remote_call = true;
call_status.is_voice_mode = mode;
call_status.is_multi_party = mpty;
call_status.is_mobile_terminated = true;
call_status.is_international = (num_type == 145);
call_status.remote_client = client.client_fd;
call_status.call_state = CallStatus::CALL_STATE_INCOMING;
auto index = last_active_call_index_++;
active_calls_[index] = call_status;
break;
}
default: // Unsupported call state
return;
}
thread_looper_->Post(makeSafeCallback(this, &CallService::CallStateUpdate));
}
} // namespace cuttlefish