chromium/chromeos/ash/services/ime/ime_service.cc

// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chromeos/ash/services/ime/ime_service.h"

#include <memory>
#include <string>
#include <utility>
#include <vector>

#include "ash/constants/ash_features.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/notreached.h"
#include "base/task/sequenced_task_runner.h"
#include "chromeos/ash/components/standalone_browser/standalone_browser_features.h"
#include "chromeos/ash/services/ime/constants.h"
#include "chromeos/ash/services/ime/decoder/decoder_engine.h"
#include "chromeos/ash/services/ime/decoder/system_engine.h"
#include "chromeos/ash/services/ime/ime_shared_library_wrapper.h"
#include "chromeos/ash/services/ime/input_method_user_data_service_impl.h"
#include "chromeos/ash/services/ime/user_data_c_api_impl.h"
#include "mojo/public/c/system/thunks.h"

namespace ash {
namespace ime {

namespace {

// Compose a relative FilePath beased on a C-string path.
base::FilePath RelativePathFromCStr(const char* path) {
  // Target path MUST be relative for security concerns.
  base::FilePath initial_path(path);
  base::FilePath relative_path(kInputMethodsDirName);
  return relative_path.Append(kLanguageDataDirName)
      .Append(initial_path.BaseName());
}

// Convert a final downloaded file path to a allowlisted path in string format.
std::string ResolveDownloadPath(const base::FilePath& file) {
  base::FilePath target(kUserInputMethodsDirPath);
  target = target.Append(kLanguageDataDirName).Append(file.BaseName());
  return target.MaybeAsASCII();
}

}  // namespace

std::string FieldTrialParamsRetrieverImpl::GetFieldTrialParamValueByFeature(
    const base::Feature& feature,
    const std::string& param_name) {
  return base::GetFieldTrialParamValueByFeature(feature, param_name);
}

ImeService::ImeService(
    mojo::PendingReceiver<mojom::ImeService> receiver,
    ImeSharedLibraryWrapper* ime_shared_library_wrapper,
    std::unique_ptr<FieldTrialParamsRetriever> field_trial_params_retriever)
    : receiver_(this, std::move(receiver)),
      main_task_runner_(base::SequencedTaskRunner::GetCurrentDefault()),
      ime_shared_library_(ime_shared_library_wrapper),
      field_trial_params_retriever_(std::move(field_trial_params_retriever)) {}

ImeService::~ImeService() = default;

void ImeService::SetPlatformAccessProvider(
    mojo::PendingRemote<mojom::PlatformAccessProvider> provider) {
  platform_access_.Bind(std::move(provider));
}

void ImeService::BindInputEngineManager(
    mojo::PendingReceiver<mojom::InputEngineManager> receiver) {
  manager_receivers_.Add(this, std::move(receiver));
}

void ImeService::ResetAllBackendConnections() {
  proto_mode_shared_lib_engine_.reset();
  mojo_mode_shared_lib_engine_.reset();
}

void ImeService::BindInputMethodUserDataService(
    mojo::PendingReceiver<mojom::InputMethodUserDataService> receiver) {
  if (input_method_user_data_api_ == nullptr) {
    std::optional<ImeSharedLibraryWrapper::EntryPoints> entry_points =
        ime_shared_library_->MaybeLoadThenReturnEntryPoints();
    if (!entry_points.has_value()) {
      LOG(ERROR) << "shared library entry_points not loaded";
      return;
    }
    input_method_user_data_api_ =
        std::make_unique<InputMethodUserDataServiceImpl>(
            std::make_unique<UserDataCApiImpl>(this, *entry_points));
  }
  input_method_user_data_api_->AddReceiver(std::move(receiver));
}

void ImeService::ConnectToImeEngine(
    const std::string& ime_spec,
    mojo::PendingReceiver<mojom::InputChannel> to_engine_request,
    mojo::PendingRemote<mojom::InputChannel> from_engine,
    const std::vector<uint8_t>& extra,
    ConnectToImeEngineCallback callback) {
  // There can only be one client using the decoder at any time. There are two
  // possible clients: NativeInputMethodEngine (for physical keyboard) and the
  // XKB extension (for virtual keyboard). The XKB extension may try to
  // connect the decoder even when it's not supposed to (due to race
  // conditions), so we must prevent the extension from taking over the
  // NativeInputMethodEngine connection.
  //
  // The extension will only use ConnectToImeEngine, and NativeInputMethodEngine
  // will only use ConnectToInputMethod.
  if (mojo_mode_shared_lib_engine_ &&
      mojo_mode_shared_lib_engine_->IsConnected()) {
    std::move(callback).Run(/*bound=*/false);
    return;
  }

  ResetAllBackendConnections();

  proto_mode_shared_lib_engine_ = std::make_unique<DecoderEngine>(
      this, ime_shared_library_->MaybeLoadThenReturnEntryPoints());
  bool bound = proto_mode_shared_lib_engine_->BindRequest(
      ime_spec, std::move(to_engine_request), std::move(from_engine), extra);
  std::move(callback).Run(bound);
}

void ImeService::InitializeConnectionFactory(
    mojo::PendingReceiver<mojom::ConnectionFactory> connection_factory,
    InitializeConnectionFactoryCallback callback) {
  ResetAllBackendConnections();

  mojo_mode_shared_lib_engine_ = std::make_unique<SystemEngine>(
      this, ime_shared_library_->MaybeLoadThenReturnEntryPoints());
  bool bound = mojo_mode_shared_lib_engine_->BindConnectionFactory(
      std::move(connection_factory));
  std::move(callback).Run(bound);
}

const char* ImeService::GetImeBundleDir() {
  return kBundledInputMethodsDirPath;
}

void ImeService::Unused3() {
  NOTIMPLEMENTED();
}

const char* ImeService::GetImeUserHomeDir() {
  return kUserInputMethodsDirPath;
}

void ImeService::RunInMainSequence(ImeSequencedTask task, int task_id) {
  // This helps ensure that tasks run in the **main** SequencedTaskRunner.
  // E.g. the Mojo Remotes are bound on the main_task_runner_, so that any task
  // invoked Mojo call from other threads (sequences) should be posted to
  // main_task_runner_ by this function.
  main_task_runner_->PostTask(FROM_HERE, base::BindOnce(task, task_id));
}

bool ImeService::IsFeatureEnabled(const char* feature_name) {
  static const base::Feature* kConsideredFeatures[] = {
      &features::kAssistEmojiEnhanced,
      &features::kAssistMultiWord,
      &features::kAutocorrectParamsTuning,
      &features::kFirstPartyVietnameseInput,
      &ash::standalone_browser::features::kLacrosOnly,
      &features::kSystemJapanesePhysicalTyping,
      &features::kImeDownloaderExperiment,
      &features::kImeDownloaderUpdate,
      &features::kImeKoreanOnlyModeSwitchOnRightAlt,
      &features::kImeUsEnglishExperimentalModel,
      &features::kImeUsEnglishModelUpdate,
      &features::kImeFstDecoderParamsUpdate,
      &features::kAutocorrectByDefault,
      &features::kAutocorrectUseReplaceSurroundingText,
      &features::kInputMethodKoreanRightAltKeyDownFix,
      &features::kImeKoreanModeSwitchDebug,
  };

  // Use consistent feature flag names as in CrOS base::Feature::name and always
  // wire 1:1 to CrOS feature flags without extra logic.
  for (const base::Feature* feature : kConsideredFeatures) {
    if (strcmp(feature_name, feature->name) == 0) {
      return base::FeatureList::IsEnabled(*feature);
    }
  }

  // For backwards-compatibility, check for the "LacrosSupport" flag, which was
  // replaced by LacrosOnly.
  // TODO(b/290714161): Remove this once the shared library no longer uses
  // LacrosSupport.
  if (strcmp(feature_name, "LacrosSupport") == 0) {
    return base::FeatureList::IsEnabled(
        ash::standalone_browser::features::kLacrosOnly);
  }

  return false;
}

const char* ImeService::GetFieldTrialParamValueByFeature(
    const char* feature_name,
    const char* param_name) {
  char* c_string_value;

  if (strcmp(feature_name, features::kAutocorrectParamsTuning.name) == 0) {
    std::string string_value =
        field_trial_params_retriever_->GetFieldTrialParamValueByFeature(
            features::kAutocorrectParamsTuning, param_name);
    c_string_value =
        new char[string_value.length() + 1];  // extra slot for NULL '\0' char
    strcpy(c_string_value, string_value.c_str());
  } else {
    c_string_value = new char[1];
    c_string_value[0] = '\0';
  }

  return c_string_value;
}

void ImeService::Unused2() {
  NOTIMPLEMENTED();
}

int ImeService::SimpleDownloadToFileV2(const char* url,
                                       const char* file_path,
                                       SimpleDownloadCallbackV2 callback) {
  if (!platform_access_.is_bound()) {
    callback(SIMPLE_DOWNLOAD_STATUS_ABORTED, url, "");
    LOG(ERROR) << "Failed to download due to missing binding.";
  } else {
    platform_access_->DownloadImeFileTo(
        GURL(url), RelativePathFromCStr(file_path),
        base::BindOnce(&ImeService::SimpleDownloadFinishedV2,
                       base::Unretained(this), std::move(callback),
                       std::string(url)));
  }

  // For |SimpleDownloadToFileV2|, always returns 0.
  return 0;
}

void ImeService::SimpleDownloadFinishedV2(SimpleDownloadCallbackV2 callback,
                                          const std::string& url_str,
                                          const base::FilePath& file) {
  if (file.empty()) {
    callback(SIMPLE_DOWNLOAD_STATUS_INVALID_ARGUMENT, url_str.c_str(), "");
  } else {
    callback(SIMPLE_DOWNLOAD_STATUS_OK, url_str.c_str(),
             ResolveDownloadPath(file).c_str());
  }
}

const MojoSystemThunks* ImeService::GetMojoSystemThunks() {
  return MojoEmbedderGetSystemThunks32();
}

const MojoSystemThunks2* ImeService::GetMojoSystemThunks2() {
  return MojoEmbedderGetSystemThunks2();
}

void ImeService::Unused1() {
  NOTIMPLEMENTED();
}

}  // namespace ime
}  // namespace ash