| // |
| // 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/stk_service.h" |
| |
| #include <android-base/logging.h> |
| namespace cuttlefish { |
| |
| StkService::StkService(int32_t service_id, ChannelMonitor* channel_monitor, |
| ThreadLooper* thread_looper) |
| : ModemService(service_id, this->InitializeCommandHandlers(), |
| channel_monitor, thread_looper) {} |
| |
| std::vector<CommandHandler> StkService::InitializeCommandHandlers() { |
| std::vector<CommandHandler> command_handlers = { |
| CommandHandler("+CUSATD?", |
| [this](const Client& client) { |
| this->HandleReportStkServiceIsRunning(client); |
| }), |
| CommandHandler("+CUSATE=", |
| [this](const Client& client, std::string& cmd) { |
| this->HandleSendEnvelope(client, cmd); |
| }), |
| CommandHandler("+CUSATT=", |
| [this](const Client& client, std::string& cmd) { |
| this->HandleSendTerminalResponseToSim(client, cmd); |
| }), |
| }; |
| return (command_handlers); |
| } |
| |
| void StkService::SetupDependency(SimService* sim) { sim_service_ = sim; } |
| |
| /** |
| * AT+CUSATD |
| * This command determines if, and optionally which profile should be downloaded |
| * to the UICC automatically upon start-up. |
| * |
| * Command Possible response(s) |
| * +CUSATD=[<download>[,<reporting>]] +CME ERROR: <err> |
| * +CUSATD? +CUSATD: <download>,<reporting> |
| * |
| * <download>: integer type. |
| * 0 Download MT default profile automatically during next start-up. |
| * 1 Download the combined TE and MT profile |
| * 2 Halt next UICC start-up when ready for profile download. |
| * <reporting>: integer type. |
| * 0 Disable +CUSATS, i.e. no notification. |
| * 1 Enable +CUSATS, i.e. notify TE. |
| * |
| * see RIL_REQUEST_REPORT_STK_SERVICE_IS_RUNNING in RIL |
| */ |
| void StkService::HandleReportStkServiceIsRunning(const Client& client) { |
| std::vector<std::string> response = {{"+CUSATD: 0,1"}, {"OK"}}; |
| client.SendCommandResponse(response); |
| |
| if (!sim_service_) return; |
| |
| XMLElement *root = sim_service_->GetIccProfile(); |
| if (!root) return; |
| |
| XMLElement *setup_menu = root->FirstChildElement("SETUPMENU"); |
| auto text = setup_menu->FindAttribute("text"); |
| |
| std::string unsol_command = "+CUSATP:"; |
| unsol_command += text ? text->Value() : ""; |
| SendUnsolicitedCommand(unsol_command); |
| } |
| |
| /** |
| * AT+CUSATE |
| * Execution command allows the TE to send a USAT envelope command to the MT |
| * |
| * Command Possible response(s) |
| * +CUSATE=<envelope_command> +CUSATE: <envelope_response>[,<busy>] |
| * [<CR><LF>+CUSATE2: <sw1>,<sw2>] |
| * +CME ERROR: <err> |
| * |
| * <envelope_command>: string type in hexadecimal character format. |
| * <envelope_response>: string type in hexadecimal character format. |
| * <busy>: integer type. |
| * 0 UICC indicated normal ending of the command. |
| * 1 UICC responded with USAT is busy, no retry by the MT. |
| * 2 UICC responded with USAT is busy even after one or more retries by the MT. |
| * <sw1>: integer type. |
| * <sw2>: integer type. |
| * |
| * see RIL_REQUEST_STK_SEND_ENVELOPE_COMMAND in RIL |
| */ |
| void StkService::HandleSendEnvelope(const Client& client , std::string& command) { |
| std::vector<std::string> response = {{"+CUSATE: 0"}, {"OK"}}; |
| client.SendCommandResponse(response); |
| |
| if (!sim_service_) return; |
| |
| CommandParser cmd(command); |
| cmd.SkipPrefix(); |
| auto data = cmd.GetNextStr(); |
| std::string menu_id(data.substr(data.size() - 2)); // get the last two char |
| |
| XMLElement *root = sim_service_->GetIccProfile(); |
| if (!root) return; |
| |
| XMLElement *setup_menu = root->FirstChildElement("SETUPMENU"); |
| if (!setup_menu) return; |
| |
| auto select_item = setup_menu->FirstChildElement("SELECTITEM"); |
| while (select_item) { |
| auto menu_id_attr = select_item->FindAttribute("menuId"); |
| if (menu_id_attr && menu_id_attr->Value() == menu_id) { |
| break; |
| } |
| select_item = select_item->NextSiblingElement("SELECTITEM"); |
| } |
| if (!select_item) { |
| LOG(ERROR) << "Not found menu id: " << menu_id; |
| return; |
| } |
| |
| auto select_item_cmd = select_item->FindAttribute("cmd"); |
| if (select_item_cmd) { |
| std::string cmd_str = select_item_cmd->Value(); |
| if (cmd_str == "24") { // SELECT_ITEM |
| current_select_item_menu_ids_.push_back(menu_id); |
| } |
| } |
| |
| std::string unsol_command = "+CUSATP:"; |
| auto text = select_item->FindAttribute("text"); |
| std::string text_value = text ? text->Value() : ""; |
| unsol_command.append(text_value); |
| SendUnsolicitedCommand(unsol_command); |
| } |
| |
| /** |
| * AT+CUSATT |
| * Execution command sends a USAT terminal response to the MT as an answer to |
| * a preceding USAT proactive command sent from the UICC with unsolicited result |
| * code +CUSATP: <proactive_command> |
| * |
| * Command Possible response(s) |
| * +CUSATT=<terminal_response> +CME ERROR: <err> |
| * |
| * <terminal_response>: string type in hexadecimal character format. |
| * |
| * see RIL_REQUEST_STK_SEND_TERMINAL_RESPONSE in RIL |
| */ |
| void StkService::HandleSendTerminalResponseToSim(const Client& client, std::string& command) { |
| std::vector<std::string> response = {{"+CUSATT: 0"}, {"OK"}}; |
| client.SendCommandResponse(response); |
| |
| OnUnsolicitedCommandForTR(command); |
| } |
| |
| XMLElement* StkService::GetCurrentSelectItem() { |
| if (!sim_service_) return nullptr; |
| |
| XMLElement *root = sim_service_->GetIccProfile(); |
| if (!root) { |
| current_select_item_menu_ids_.clear(); |
| return nullptr; |
| } |
| |
| XMLElement *menu = root->FirstChildElement("SETUPMENU"); |
| if (!menu) { |
| current_select_item_menu_ids_.clear(); |
| return nullptr; |
| } |
| |
| /** |
| * e.g. current_select_item_menu_ids_: {"1", "02"} |
| * <SELECTITEM id="1"> |
| * <SELECTITEM id="01"> |
| * </SELECTITEM> |
| * <SELECTITEM id="02"> |
| * </SELECTITEM> |
| * </SELECTITEM> |
| */ |
| XMLElement* select_item = nullptr; |
| auto iter = current_select_item_menu_ids_.begin(); |
| for (; iter != current_select_item_menu_ids_.end(); ++iter) { |
| select_item = menu->FirstChildElement("SELECTITEM"); |
| while (select_item) { |
| auto menu_id_attr = select_item->FindAttribute("menuId"); |
| if (menu_id_attr && menu_id_attr->Value() == *iter) { |
| auto menu_id_str = menu_id_attr->Value(); |
| if (menu_id_str == *iter) break; |
| } |
| select_item = select_item->NextSiblingElement("SELECTITEM"); |
| } |
| if (!select_item) break; |
| menu = select_item; |
| } |
| |
| return select_item; |
| } |
| |
| void StkService::OnUnsolicitedCommandForTR(std::string& command) { |
| CommandParser cmd(command); |
| cmd.SkipPrefix(); |
| auto data = cmd.GetNextStr(); |
| auto menu_id = data.substr(data.size() - 2); |
| |
| // '10': UICC_SESSION_TERM_BY_USER |
| // '12': NO_RESPONSE_FROM_USER |
| if (menu_id == "10" || menu_id == "12") { |
| current_select_item_menu_ids_.clear(); |
| SendUnsolicitedCommand("+CUSATEND"); |
| return; |
| } |
| |
| XMLElement *select_item = GetCurrentSelectItem(); |
| if (!select_item) { |
| current_select_item_menu_ids_.clear(); |
| SendUnsolicitedCommand("+CUSATEND"); |
| return; |
| } |
| |
| if (menu_id == "11") { // BACKWARD_MOVE_BY_USER |
| current_select_item_menu_ids_.pop_back(); |
| if (current_select_item_menu_ids_.size() >= 1) { |
| select_item = GetCurrentSelectItem(); |
| auto text = select_item->FindAttribute("text"); |
| if (text) { |
| std::string unsol_command = "+CUSATP: "; |
| unsol_command += text->Value(); |
| SendUnsolicitedCommand(unsol_command); |
| } |
| } else { |
| SendUnsolicitedCommand("+CUSATEND"); |
| } |
| return; |
| } else if (menu_id == "00") { // OK |
| auto text = select_item->FindAttribute("text"); |
| if (text) { |
| std::string unsol_command = "+CUSATP: "; |
| unsol_command += text->Value(); |
| SendUnsolicitedCommand(unsol_command); |
| } |
| return; |
| } |
| |
| auto final = select_item->FirstChildElement(); |
| while (final) { |
| auto attr = final->FindAttribute("menuId"); |
| if (attr && attr->Value() == menu_id) { |
| std::string attr_value = attr->Value(); |
| if (attr_value == menu_id) break; |
| } |
| final = final->NextSiblingElement(); |
| } |
| if (!final) { |
| current_select_item_menu_ids_.clear(); |
| SendUnsolicitedCommand("+CUSATEND"); |
| return; |
| } |
| |
| auto cmd_attr = final->FindAttribute("cmd"); |
| if (cmd_attr) { |
| std::string cmd_attr_str = cmd_attr->Value(); |
| if (cmd_attr_str == "24") { |
| std::string menu_id_str(menu_id); |
| current_select_item_menu_ids_.push_back(menu_id_str); |
| } |
| } |
| auto text = final->FindAttribute("text"); |
| std::string unsol_command = "+CUSATP:"; |
| unsol_command += text ? text->Value() : ""; |
| SendUnsolicitedCommand(unsol_command); |
| } |
| |
| } // namespace cuttlefish |