720 lines
24 KiB
C++
720 lines
24 KiB
C++
//
|
||
// 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();
|
||
|
||
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;
|
||
auto index = FindFreeCallIndex();
|
||
|
||
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 = FindFreeCallIndex();
|
||
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 = FindFreeCallIndex();
|
||
active_calls_[index] = call_status;
|
||
break;
|
||
}
|
||
default: // Unsupported call state
|
||
return;
|
||
}
|
||
thread_looper_->Post(makeSafeCallback(this, &CallService::CallStateUpdate));
|
||
}
|
||
|
||
} // namespace cuttlefish
|