* Copyright (C) 2024-2025 Huawei Device Co., Ltd.
* 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 "spam_call_adapter.h"
#include "call_manager_base.h"
#include "call_manager_info.h"
#include "extension_manager_client.h"
#include "ipc_skeleton.h"
#include "nlohmann/json.hpp"
#include "telephony_log_wrapper.h"
#include "cJSON.h"
#include <securec.h>
#include "time_wait_helper.h"
#include "spam_call_connection.h"
#include "common_event_manager.h"
#include "common_event_support.h"
#include "telephony_permission.h"
#ifdef CALL_MANAGER_WATCH_CALL_BLOCKING
#include "watch_lite_call_manager_node.h"
#endif
namespace OHOS {
namespace Telephony {
constexpr int32_t DEFAULT_USER_ID = -1;
constexpr char REMINDER_RESULT[] = "reminderResult";
constexpr char SLOT_ID[] = "slotId";
constexpr char DETECT_RESULT[] = "detectResult";
constexpr char DECISION_REASON[] = "decisionReason";
constexpr char MARK_TYPE[] = "markType";
constexpr char MARK_COUNT[] = "markCount";
constexpr char MARK_SOURCE[] = "markSource";
constexpr char MARK_CONTENT[] = "markContent";
constexpr char IS_CLOUD[] = "isCloud";
constexpr char MARK_DETAILS[] = "markDetails";
constexpr char DETECT_DETAILS[] = "detectDetails";
sptr<SpamCallConnection> connection_ = nullptr;
#ifdef CALL_MANAGER_WATCH_CALL_BLOCKING
const std::unordered_map<CallMarkType, MarkType> CALL_MARK_MAP = {
{CallMarkType::UNKNOWN, MarkType::MARK_TYPE_NONE},
{CallMarkType::EXPRESS_DELIVERY, MarkType::MARK_TYPE_EXPRESS},
{CallMarkType::TAXI, MarkType::MARK_TYPE_TAXI},
{CallMarkType::EDUCATION_TRAINING, MarkType::MARK_TYPE_EDUCATION_TRAINING},
{CallMarkType::HEADHUNTING, MarkType::MARK_TYPE_HEADHUNTING},
{CallMarkType::INSURANCE, MarkType::MARK_TYPE_INSURANCE},
{CallMarkType::LOAN, MarkType::MARK_TYPE_LOAN},
{CallMarkType::REAL_ESTATE, MarkType::MARK_TYPE_HOUSE_AGENT},
{CallMarkType::ADVERTISEMENT, MarkType::MARK_TYPE_PROMOTE_SALES},
{CallMarkType::HARASSMENT, MarkType::MARK_TYPE_CRANK},
{CallMarkType::SCAM, MarkType::MARK_TYPE_FRAUD}
};
#endif
SpamCallAdapter::SpamCallAdapter()
{
timeWaitHelper_ = std::make_unique<TimeWaitHelper>(WAIT_TIME_FIVE_SECOND);
}
SpamCallAdapter::~SpamCallAdapter()
{
TELEPHONY_LOGW("~SpamCallAdapter");
}
bool SpamCallAdapter::DetectSpamCall(const std::string &phoneNumber, const int32_t &slotId,
IWatchTelephonyNode *watchTelephonyNode)
{
TELEPHONY_LOGW("DetectSpamCall start");
phoneNumber_ = phoneNumber;
#ifdef CALL_MANAGER_WATCH_CALL_BLOCKING
if (watchTelephonyNode == nullptr) {
TELEPHONY_LOGE("node nullptr");
return false;
}
uint64_t startCallerStatusTime = GetCurrentTimeMs();
std::unique_lock<ffrt::mutex> lock(spamMutex_);
result_ = "";
isQueryComplete_ = false;
lock.unlock();
SubmitCallerStatusQuery(phoneNumber, watchTelephonyNode);
std::unique_lock<ffrt::mutex> lockForWait(spamMutex_);
if (!spamCv_.wait_for(lockForWait, std::chrono::milliseconds(WAIT_TIME_FIVE_SECOND),
[this]() { return isQueryComplete_; })) {
TELEPHONY_LOGE("wait caller status timeout");
return false;
}
std::string dispositionJson = result_;
result_.clear();
lockForWait.unlock();
TELEPHONY_LOGI("resultMap[%{public}s], cost time[%{public}lu]", dispositionJson.c_str(),
GetCurrentTimeMs() - startCallerStatusTime);
isRefreshMarkInfo_ = ParseCallerResult(dispositionJson, callDisposition_, info_);
#else
AAFwk::Want want;
std::string bundleName = "com.spamshield";
std::string abilityName = "SpamShieldServiceExtAbility";
want.SetElementName(bundleName, abilityName);
bool connectResult = ConnectSpamCallAbility(want, phoneNumber, slotId);
if (!connectResult) {
TELEPHONY_LOGE("DetectSpamCall failed!");
return false;
}
#endif
return true;
}
#ifdef CALL_MANAGER_WATCH_CALL_BLOCKING
bool SpamCallAdapter::IsRefreshMarkInfo()
{
return isRefreshMarkInfo_;
}
NumberMarkInfo SpamCallAdapter::GetNumberMarkInfo()
{
return info_;
}
CallDisposition SpamCallAdapter::GetCallDisposition()
{
return callDisposition_;
}
bool SpamCallAdapter::ParseCallerResult(const std::string &dispositionJson, CallDisposition &callDisposition,
NumberMarkInfo &numberMarkInfo)
{
cJSON *root = cJSON_Parse(dispositionJson.c_str());
if (root == nullptr) {
TELEPHONY_LOGE("json string invalid");
return false;
}
int32_t numberValue = static_cast<int32_t>(CallDisposition::NORMAL_PROCESS);
if (!JsonGetNumberValue(root, "callerResult", numberValue)) {
cJSON_Delete(root);
return false;
}
callDisposition = static_cast<CallDisposition>(numberValue);
if (callDisposition != CallDisposition::NORMAL_PROCESS) {
TELEPHONY_LOGW("not normal process, don't parse mark info");
cJSON_Delete(root);
return false;
}
auto res = ParseNumberMarkInfo(root, numberMarkInfo);
cJSON_Delete(root);
return res;
}
bool SpamCallAdapter::ParseNumberMarkInfo(cJSON *root, NumberMarkInfo &numberMarkInfo)
{
if (root == nullptr) {
TELEPHONY_LOGE("invalid param");
return false;
}
int32_t markType = static_cast<int32_t>(CallMarkType::UNKNOWN);
if (!JsonGetNumberValue(root, "markerId", markType)) {
TELEPHONY_LOGE("parse markType failed");
return false;
}
std::string markStr = "";
if (!JsonGetStringValue(root, "markerType", markStr)) {
TELEPHONY_LOGE("parse markStr failed");
return false;
}
int32_t markCnt = 0;
if (!JsonGetNumberValue(root, "markerCnt", markCnt)) {
TELEPHONY_LOGE("parse markCnt failed");
return false;
}
auto it = CALL_MARK_MAP.find(static_cast<CallMarkType>(markType));
if (it == CALL_MARK_MAP.end()) {
TELEPHONY_LOGW("invalid markType[%{public}d]", markType);
return false;
}
if (memcpy_s(numberMarkInfo.markContent, kMaxNumberLen, markStr.c_str(), markStr.size()) != EOK) {
TELEPHONY_LOGE("memcpy_s failed!");
return false;
}
numberMarkInfo.isCloud = true;
numberMarkInfo.markCount = markCnt;
numberMarkInfo.markType = it->second;
TELEPHONY_LOGI("parse markInfo success markType[%{public}d], markCnt[%{public}d], markstr[%{public}s]",
markType, markCnt, markStr.c_str());
return true;
}
uint64_t SpamCallAdapter::GetCurrentTimeMs()
{
auto timeNow = std::chrono::system_clock::now();
return std::chrono::duration_cast<std::chrono::milliseconds>(timeNow.time_since_epoch()).count();
}
void SpamCallAdapter::SubmitCallerStatusQuery(const std::string &phoneNumber, IWatchTelephonyNode *watchTelephonyNode)
{
auto weak = weak_from_this();
ffrt::submit_h([weak, phoneNumber, watchTelephonyNode] {
auto strong = weak.lock();
if (strong == nullptr) {
return;
}
std::string dispositionJson = "";
int32_t res = watchTelephonyNode->GetCallerStatus(phoneNumber, dispositionJson);
TELEPHONY_LOGI("query result[%{public}d]", res);
std::unique_lock<ffrt::mutex> lock(strong->spamMutex_);
strong->isQueryComplete_ = true;
strong->result_ = dispositionJson;
strong->spamCv_.notify_all();
});
}
#endif
bool SpamCallAdapter::ConnectSpamCallAbility(const AAFwk::Want &want, const std::string &phoneNumber,
const int32_t &slotId)
{
std::lock_guard<ffrt::mutex> lock(mutex_);
TELEPHONY_LOGW("ConnectSpamCallAbility start");
connection_ = sptr<SpamCallConnection>::MakeSptr(phoneNumber, slotId, shared_from_this());
if (connection_ == nullptr) {
TELEPHONY_LOGE("connection_ is nullptr");
return false;
}
std::string identity = IPCSkeleton::ResetCallingIdentity();
auto connectResult = AAFwk::ExtensionManagerClient::GetInstance().ConnectServiceExtensionAbility(want,
connection_, nullptr, DEFAULT_USER_ID);
IPCSkeleton::SetCallingIdentity(identity);
if (connectResult != 0) {
TELEPHONY_LOGE("ConnectServiceExtensionAbility Failed!");
return false;
}
return true;
}
void SpamCallAdapter::DisconnectSpamCallAbility()
{
std::lock_guard<ffrt::mutex> lock(mutex_);
TELEPHONY_LOGW("DisconnectSpamCallAbility start");
if (connection_ == nullptr) {
TELEPHONY_LOGE("connection_ is nullptr");
return;
}
auto disconnectResult = AAFwk::ExtensionManagerClient::GetInstance().DisconnectAbility(connection_);
connection_.clear();
if (disconnectResult != 0) {
TELEPHONY_LOGE("DisconnectAbility failed! %d", disconnectResult);
}
}
bool SpamCallAdapter::JsonGetNumberValue(cJSON *json, const std::string key, int32_t &out)
{
do {
cJSON *cursor = cJSON_GetObjectItem(json, key.c_str());
if (!cJSON_IsNumber(cursor)) {
TELEPHONY_LOGE("ParseNumberValue failed to get %{public}s", key.c_str());
return false;
}
out = static_cast<int32_t>(cJSON_GetNumberValue(cursor));
} while (0);
return true;
}
bool SpamCallAdapter::JsonGetStringValue(cJSON *json, const std::string key, std::string &out)
{
do {
out = "";
cJSON *cursor = cJSON_GetObjectItem(json, key.c_str());
if (!cJSON_IsString(cursor)) {
TELEPHONY_LOGE("ParseStringValue failed to get %{public}s", key.c_str());
return false;
}
char *value = cJSON_GetStringValue(cursor);
if (value != nullptr) {
out = value;
}
} while (0);
return true;
}
bool SpamCallAdapter::JsonGetBoolValue(cJSON *json, const std::string key)
{
cJSON *cursor = cJSON_GetObjectItem(json, key.c_str());
bool value = cJSON_IsTrue(cursor);
TELEPHONY_LOGW("ParseBoolValue %{public}s: %{public}d", key.c_str(), value);
return value;
}
void SpamCallAdapter::ParseNeedNotifyResult(const std::string &jsonData)
{
if (jsonData.empty()) {
return;
}
const char *data = jsonData.c_str();
cJSON *root = cJSON_Parse(data);
if (root == nullptr) {
TELEPHONY_LOGE("ParseNeedNotifyResult failed to parse JSON");
return;
}
int32_t slotId = 0;
if (!JsonGetNumberValue(root, SLOT_ID, slotId)) {
cJSON_Delete(root);
return;
}
bool result = JsonGetBoolValue(root, REMINDER_RESULT);
TELEPHONY_LOGI("result: %{public}d, slotId: %{public}d", result, slotId);
if (result) {
TELEPHONY_LOGI("send notify to contacts");
AAFwk::Want want;
want.SetParam("isHarassmentGuidance", true);
want.SetParam("slotId", slotId);
want.SetAction(EventFwk::CommonEventSupport::COMMON_EVENT_INCOMING_CALL_MISSED);
EventFwk::CommonEventData eventData;
eventData.SetWant(want);
EventFwk::CommonEventPublishInfo publishInfo;
std::vector<std::string> callPermissions;
callPermissions.emplace_back(Permission::GET_TELEPHONY_STATE);
publishInfo.SetSubscriberPermissions(callPermissions);
if (!EventFwk::CommonEventManager::PublishCommonEvent(eventData, publishInfo, nullptr)) {
TELEPHONY_LOGE("PublishCommonEvent fail.");
}
}
cJSON_Delete(root);
}
void SpamCallAdapter::ParseDetectResult(const std::string &jsonData, bool &isBlock,
NumberMarkInfo &info, int32_t &blockReason, std::string &detectDetails)
{
if (jsonData.empty()) {
return;
}
const char *data = jsonData.c_str();
cJSON *root = cJSON_Parse(data);
if (root == nullptr) {
TELEPHONY_LOGE("ParseDetectResult failed to parse JSON");
return;
}
int32_t numberValue = 0;
if (!JsonGetNumberValue(root, DETECT_RESULT, numberValue)) {
cJSON_Delete(root);
return;
}
isBlock = numberValue == 1;
TELEPHONY_LOGI("DetectSpamCall detectResult: %{public}d", isBlock);
if (!JsonGetNumberValue(root, DECISION_REASON, numberValue)) {
cJSON_Delete(root);
return;
}
blockReason = numberValue;
TELEPHONY_LOGI("DetectSpamCall decisionReason: %{public}d", blockReason);
ParseMarkResults(info, root, detectDetails, isBlock);
cJSON_Delete(root);
}
void SpamCallAdapter::ParseMarkResults(NumberMarkInfo &info, cJSON *root, std::string &detectDetails, bool isBlock)
{
int32_t numberValue = 0;
std::string stringValue = "";
if (JsonGetNumberValue(root, MARK_TYPE, numberValue)) {
info.markType = static_cast<MarkType>(numberValue);
}
TELEPHONY_LOGI("DetectSpamCall markType: %{public}d", info.markType);
if (!isBlock && (info.markType == MarkType::MARK_TYPE_CRANK || info.markType == MarkType::MARK_TYPE_FRAUD ||
info.markType == MarkType::MARK_TYPE_PROMOTE_SALES || info.markType == MarkType::MARK_TYPE_HOUSE_AGENT)) {
std::lock_guard<ffrt::mutex> lock(mutex_);
if (connection_ != nullptr) {
connection_->RequireCallReminder();
}
}
if (JsonGetNumberValue(root, MARK_COUNT, numberValue)) {
info.markCount = numberValue;
}
JsonGetStringValue(root, MARK_SOURCE, stringValue);
if (strcpy_s(info.markSource, sizeof(info.markSource), stringValue.c_str()) != EOK) {
TELEPHONY_LOGE("strcpy_s markSource fail.");
}
JsonGetStringValue(root, MARK_CONTENT, stringValue);
if (strcpy_s(info.markContent, sizeof(info.markContent), stringValue.c_str()) != EOK) {
TELEPHONY_LOGE("strcpy_s markContent fail.");
}
info.isCloud = JsonGetBoolValue(root, IS_CLOUD);
JsonGetStringValue(root, MARK_DETAILS, stringValue);
if (strcpy_s(info.markDetails, sizeof(info.markDetails), stringValue.c_str()) != EOK) {
TELEPHONY_LOGE("strcpy_s markDetails fail.");
}
JsonGetStringValue(root, DETECT_DETAILS, detectDetails);
TELEPHONY_LOGI("DetectSpamCall detectDetails length: %{public}zu", detectDetails.length());
}
void SpamCallAdapter::GetDetectResult(int32_t &errCode, std::string &result)
{
errCode = errCode_;
result = result_;
}
void SpamCallAdapter::SetDetectResult(int32_t &errCode, std::string &result)
{
errCode_ = errCode;
result_ = result;
}
void SpamCallAdapter::GetParseResult(bool &isBlock, NumberMarkInfo &info,
int32_t &blockReason, std::string &detectDetails)
{
isBlock = isBlock_;
info = info_;
blockReason = blockReason_;
detectDetails = detectDetails_;
}
void SpamCallAdapter::SetParseResult(bool &isBlock, NumberMarkInfo &info,
int32_t &blockReason, std::string &detectDetails)
{
isBlock_ = isBlock;
info_ = info;
blockReason_ = blockReason;
detectDetails_ = detectDetails;
}
std::string SpamCallAdapter::GetDetectPhoneNum()
{
return phoneNumber_;
}
void SpamCallAdapter::NotifyAll()
{
if (timeWaitHelper_ == nullptr) {
TELEPHONY_LOGE("timeWaitHelper_ is null");
return;
}
timeWaitHelper_->NotifyAll();
}
bool SpamCallAdapter::WaitForDetectResult()
{
if (timeWaitHelper_ == nullptr) {
TELEPHONY_LOGE("timeWaitHelper_ is null");
return false;
}
if (!timeWaitHelper_->WaitForResult()) {
DisconnectSpamCallAbility();
return false;
}
DisconnectSpamCallAbility();
return true;
}
}
}