/*
 * Copyright (c) 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 "start_options_utils.h"

#include "ability_manager_errors.h"
#include "ability_manager_service.h"
#include "ability_permission_util.h"
#include "ability_record.h"
#include "ability_util.h"
#include "app_scheduler.h"
#include "app_utils.h"
#include "hidden_start_utils.h"
#include "hilog_tag_wrapper.h"
#include "hitrace_meter.h"
#include "ipc_skeleton.h"
#include "iremote_object.h"
#include "permission_verification.h"
#include "process_options.h"
#include "scene_board_judgement.h"
#include "start_ability_utils.h"
#include "start_options.h"
#include "want.h"

namespace OHOS {
namespace AAFwk {
namespace {
constexpr const char* ACTION_CHOOSE = "ohos.want.action.select";
constexpr const char* FOUNDATION_PROCESS_NAME = "foundation";
}
int32_t StartOptionsUtils::CheckProcessOptions(const Want &want, const StartOptions &options,
    sptr<IRemoteObject> callerToken, int32_t userId)
{
    if (HiddenStartUtils::IsHiddenStart(options)) {
        return HiddenStartUtils::CheckHiddenStartSupported(options);
    }
    if (AbilityPermissionUtil::GetInstance().IsStartSelfUIAbility() &&
        options.processOptions != nullptr && options.processOptions->isStartFromNDK) {
        return CheckStartSelfUIAbilityStartOptions(want, options);
    }
    return CheckProcessOptionsInner(want, options, callerToken, userId);
}

int32_t StartOptionsUtils::CheckProcessOptionsInner(const Want &want, const StartOptions &options,
    sptr<IRemoteObject> callerToken, int32_t userId)
{
    HITRACE_METER_NAME(HITRACE_TAG_ABILITY_MANAGER, __PRETTY_FUNCTION__);
    if (options.processOptions == nullptr ||
        (!ProcessOptions::IsValidProcessMode(options.processOptions->processMode) &&
        !options.processOptions->isRestartKeepAlive)) {
        return ERR_OK;
    }

    TAG_LOGI(AAFwkTag::ABILITYMGR, "start ability with process options");
    bool isEnable = AppUtils::GetInstance().IsStartOptionsWithProcessOptions();
    CHECK_TRUE_RETURN_RET(!Rosen::SceneBoardJudgement::IsSceneBoardEnabled() || !isEnable,
        ERR_CAPABILITY_NOT_SUPPORT, "not support process options");

    auto element = want.GetElement();
    CHECK_TRUE_RETURN_RET(element.GetAbilityName().empty() || want.GetActionRef().compare(ACTION_CHOOSE) == 0,
        ERR_NOT_ALLOW_IMPLICIT_START, "not allow implicit start");

    if (PermissionVerification::GetInstance()->CheckSpecificSystemAbilityAccessPermission(FOUNDATION_PROCESS_NAME)
        && options.processOptions->isRestartKeepAlive) {
        TAG_LOGI(AAFwkTag::ABILITYMGR, "restart keep-alive app.");
        return ERR_OK;
    }

    StartAbilityUtils::ResolveTargetAppCloneIndex(want, callerToken, userId);
    int32_t appIndex = -1;
    appIndex = !StartAbilityUtils::GetAppIndex(want, callerToken, userId, appIndex) ? 0 : appIndex;
    CHECK_TRUE_RETURN_RET(!DelayedSingleton<AbilityManagerService>::GetInstance()->CheckCallingTokenId(
        element.GetBundleName(), userId, appIndex), ERR_NOT_SELF_APPLICATION, "not self application");

    auto uiAbilityManager = DelayedSingleton<AbilityManagerService>::GetInstance()->GetUIAbilityManagerByUid(
        IPCSkeleton::GetCallingUid());
    CHECK_POINTER_AND_RETURN(uiAbilityManager, ERR_INVALID_VALUE);

    auto callerPid = IPCSkeleton::GetCallingPid();
    AppExecFwk::RunningProcessInfo processInfo;
    DelayedSingleton<AppScheduler>::GetInstance()->GetRunningProcessInfoByPid(callerPid, processInfo);
    CHECK_TRUE_RETURN_RET((ProcessOptions::IsAttachToStatusBarMode(options.processOptions->processMode) &&
        !uiAbilityManager->IsCallerInStatusBar(processInfo.instanceKey)), ERR_START_OPTIONS_CHECK_FAILED,
        "not in status bar");

    auto abilityRecords = uiAbilityManager->GetAbilityRecordsByName(element, appIndex);
    CHECK_TRUE_RETURN_RET(!abilityRecords.empty() && abilityRecords[0] &&
        abilityRecords[0]->GetAbilityInfo().launchMode != AppExecFwk::LaunchMode::STANDARD &&
        abilityRecords[0]->GetAbilityInfo().launchMode != AppExecFwk::LaunchMode::SPECIFIED,
        ERR_ABILITY_ALREADY_RUNNING, "if not STANDARD or SPECIFIED mode, repeated starts not allowed");

    return ERR_OK;
}

int32_t StartOptionsUtils::CheckStartSelfUIAbilityStartOptions(const Want &want, const StartOptions &options)
{
    HITRACE_METER_NAME(HITRACE_TAG_ABILITY_MANAGER, __PRETTY_FUNCTION__);
    if (options.processOptions == nullptr) {
        return ERR_OK;
    }

    TAG_LOGI(AAFwkTag::ABILITYMGR, "start ability with process options");
    bool isEnable = AppUtils::GetInstance().IsStartOptionsWithProcessOptions();
    CHECK_TRUE_RETURN_RET(!Rosen::SceneBoardJudgement::IsSceneBoardEnabled() || !isEnable,
        ERR_CAPABILITY_NOT_SUPPORT, "not support process options");

    if (options.processOptions->startupVisibility == StartupVisibility::UNSPECIFIED &&
        options.processOptions->loadAbilityCallbackId > 0) {
        return ERR_OK;
    }

    auto uiAbilityManager = DelayedSingleton<AbilityManagerService>::GetInstance()->GetUIAbilityManagerByUid(
        IPCSkeleton::GetCallingUid());
    CHECK_POINTER_AND_RETURN(uiAbilityManager, ERR_INVALID_VALUE);

    auto callerPid = IPCSkeleton::GetCallingPid();
    AppExecFwk::RunningProcessInfo processInfo;
    DelayedSingleton<AppScheduler>::GetInstance()->GetRunningProcessInfoByChildProcessPid(callerPid, processInfo);
    CHECK_TRUE_RETURN_RET(!uiAbilityManager->IsCallerInStatusBar(processInfo.instanceKey),
        ERR_START_OPTIONS_CHECK_FAILED, "not in status bar");

    return ERR_OK;
}
}
}