Commit ac0034cf authored by Charles Zhao's avatar Charles Zhao Committed by Commit Bot

Add HandwritingModelLoader for loading HandwritingLibrary.

(1) The HandwritingModel is loaded based on commandline flag
    "ondevice_handwriting" which is passed from chromeos USE flag.

(2) "use_rootfs" means loading HandwritingModel from rootfs, which will
    simply pass the call to chromeos.

(3) "use_dlc" means loading from dlc; which will take 3 steps:
    (a) verify that dlc already exists on device, return error otherwise.
    (b) mount the dlc, return error if mount fails.
    (c) pass LoadHandwritingModel to chromeos.

(4) unit tests are added to test the Loader behaviour as described.

Bug: 1054628

Change-Id: I3e06fd748573632a7207dc7d9de560f754598466
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2367257
Commit-Queue: Charles . <charleszhao@chromium.org>
Reviewed-by: default avatarAndrew Moylan <amoylan@chromium.org>
Reviewed-by: default avatarXinglong Luan <alanlxl@chromium.org>
Reviewed-by: default avatarCharles . <charleszhao@chromium.org>
Cr-Commit-Position: refs/heads/master@{#806074}
parent 3eb73d1c
......@@ -6,6 +6,8 @@ assert(is_chromeos, "Non-ChromeOS builds cannot depend on //chromeos")
source_set("cpp") {
sources = [
"handwriting_model_loader.cc",
"handwriting_model_loader.h",
"handwriting_recognizer_manager.cc",
"handwriting_recognizer_manager.h",
"service_connection.cc",
......@@ -13,6 +15,7 @@ source_set("cpp") {
]
deps = [
"//base",
"//chromeos/dbus/dlcservice",
"//chromeos/dbus/machine_learning",
"//chromeos/services/machine_learning/public/mojom",
]
......@@ -34,11 +37,15 @@ source_set("test_support") {
source_set("unit_tests") {
testonly = true
sources = [ "service_connection_unittest.cc" ]
sources = [
"handwriting_model_loader_unittest.cc",
"service_connection_unittest.cc",
]
deps = [
":cpp",
":test_support",
"//base/test:test_support",
"//chromeos/dbus/dlcservice:test_support",
"//chromeos/dbus/machine_learning",
"//chromeos/services/machine_learning/public/mojom",
"//mojo/core/embedder",
......
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromeos/services/machine_learning/public/cpp/handwriting_model_loader.h"
#include "base/command_line.h"
#include "base/metrics/histogram_macros.h"
#include "base/no_destructor.h"
#include "chromeos/services/machine_learning/public/cpp/service_connection.h"
#include "third_party/cros_system_api/dbus/service_constants.h"
namespace chromeos {
namespace machine_learning {
namespace {
using chromeos::machine_learning::mojom::LoadHandwritingModelResult;
// Records CrOSActionRecorder event.
void RecordLoadHandwritingModelResult(const LoadHandwritingModelResult val) {
UMA_HISTOGRAM_ENUMERATION(
"MachineLearningService.HandwritingModel.LoadModelResult.Event", val,
LoadHandwritingModelResult::LOAD_MODEL_FILES_ERROR);
}
// A list of supported language code.
constexpr char kLanguageCodeEn[] = "en";
constexpr char kLanguageCodeGesture[] = "gesture_in_context";
// Returns whether the `value` is set for command line switch
// kOndeviceHandwritingSwitch.
bool HandwritingSwitchHasValue(const std::string& value) {
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
return command_line->HasSwitch(
HandwritingModelLoader::kOndeviceHandwritingSwitch) &&
command_line->GetSwitchValueASCII(
HandwritingModelLoader::kOndeviceHandwritingSwitch) == value;
}
// Returns true if switch kOndeviceHandwritingSwitch is set to use_rootfs.
bool IsLibHandwritingRootfsEnabled() {
return HandwritingSwitchHasValue("use_rootfs");
}
// Returns true if switch kOndeviceHandwritingSwitch is set to use_dlc.
bool IsLibHandwritingDlcEnabled() {
return HandwritingSwitchHasValue("use_dlc");
}
} // namespace
constexpr char HandwritingModelLoader::kOndeviceHandwritingSwitch[];
constexpr char HandwritingModelLoader::kLibHandwritingDlcId[];
HandwritingModelLoader::HandwritingModelLoader(
mojom::HandwritingRecognizerSpecPtr spec,
mojo::PendingReceiver<mojom::HandwritingRecognizer> receiver,
mojom::MachineLearningService::LoadHandwritingModelCallback callback)
: dlc_client_(chromeos::DlcserviceClient::Get()),
spec_(std::move(spec)),
receiver_(std::move(receiver)),
callback_(std::move(callback)),
weak_ptr_factory_(this) {}
HandwritingModelLoader::~HandwritingModelLoader() = default;
void HandwritingModelLoader::Load() {
// Returns FEATURE_NOT_SUPPORTED_ERROR if both rootfs and dlc are not enabled.
if (!IsLibHandwritingRootfsEnabled() && !IsLibHandwritingDlcEnabled()) {
RecordLoadHandwritingModelResult(
LoadHandwritingModelResult::FEATURE_NOT_SUPPORTED_ERROR);
std::move(callback_).Run(
LoadHandwritingModelResult::FEATURE_NOT_SUPPORTED_ERROR);
return;
}
// Returns LANGUAGE_NOT_SUPPORTED_ERROR if the language is not supported yet.
if (spec_->language != kLanguageCodeEn &&
spec_->language != kLanguageCodeGesture) {
RecordLoadHandwritingModelResult(
LoadHandwritingModelResult::LANGUAGE_NOT_SUPPORTED_ERROR);
std::move(callback_).Run(
LoadHandwritingModelResult::LANGUAGE_NOT_SUPPORTED_ERROR);
return;
}
// Load from rootfs if enabled.
if (IsLibHandwritingRootfsEnabled()) {
ServiceConnection::GetInstance()->LoadHandwritingModel(
std::move(spec_), std::move(receiver_), std::move(callback_));
return;
}
// Gets existing dlc list and based on the presence of libhandwriting
// either returns an error or installs the libhandwriting dlc.
dlc_client_->GetExistingDlcs(
base::BindOnce(&HandwritingModelLoader::OnGetExistingDlcsComplete,
weak_ptr_factory_.GetWeakPtr()));
}
void HandwritingModelLoader::OnGetExistingDlcsComplete(
const std::string& err,
const dlcservice::DlcsWithContent& dlcs_with_content) {
// Loop over dlcs_with_content, and installs libhandwriting if already exists.
// Since we don't want to trigger downloading here, we only install(mount)
// the handwriting dlc if it is already on device.
for (const auto& dlc_info : dlcs_with_content.dlc_infos()) {
if (dlc_info.id() == HandwritingModelLoader::kLibHandwritingDlcId) {
dlc_client_->Install(
kLibHandwritingDlcId,
base::BindOnce(&HandwritingModelLoader::OnInstallDlcComplete,
weak_ptr_factory_.GetWeakPtr()),
chromeos::DlcserviceClient::IgnoreProgress);
return;
}
}
// Returns error if the handwriting dlc is not on the device.
RecordLoadHandwritingModelResult(
LoadHandwritingModelResult::DLC_DOES_NOT_EXIST);
std::move(callback_).Run(LoadHandwritingModelResult::DLC_DOES_NOT_EXIST);
}
void HandwritingModelLoader::OnInstallDlcComplete(
const chromeos::DlcserviceClient::InstallResult& result) {
// Call LoadHandwritingModelWithSpec if no error was found.
if (result.error == dlcservice::kErrorNone) {
ServiceConnection::GetInstance()->LoadHandwritingModel(
std::move(spec_), std::move(receiver_), std::move(callback_));
return;
}
RecordLoadHandwritingModelResult(
LoadHandwritingModelResult::DLC_INSTALL_ERROR);
std::move(callback_).Run(LoadHandwritingModelResult::DLC_INSTALL_ERROR);
}
} // namespace machine_learning
} // namespace chromeos
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROMEOS_SERVICES_MACHINE_LEARNING_PUBLIC_CPP_HANDWRITING_MODEL_LOADER_H_
#define CHROMEOS_SERVICES_MACHINE_LEARNING_PUBLIC_CPP_HANDWRITING_MODEL_LOADER_H_
#include "base/memory/weak_ptr.h"
#include "chromeos/dbus/dlcservice/dlcservice_client.h"
#include "chromeos/services/machine_learning/public/mojom/handwriting_recognizer.mojom.h"
#include "chromeos/services/machine_learning/public/mojom/machine_learning_service.mojom.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
namespace chromeos {
namespace machine_learning {
// Class that decides either to load handwriting model from rootfs or dlc.
// New Handwriting clients should call this helper instead of calling
// ServiceConnection::GetInstance()->LoadHandwritingModel.
// Three typical examples of the callstack are:
// Case 1: handwriting in enabled on rootfs.
// client calls HandwritingModelLoader("en", receiver, callback).Load()
// which calls LoadHandwritingModel -> handwriting model loaded from rootfs.
// Case 2: handwriting is enabled for dlc and dlc is already on the device.
// client calls HandwritingModelLoader("en", receiver, callback).Load()
// which calls -> GetExistingDlcs -> libhandwriting dlc already exists
// -> InstallDlc -> LoadHandwritingModel
// The correct handwriting model will be loaded and bond to the receiver.
// Case 3: handwriting is enabled for dlc and dlc is not on the device yet.
// client calls HandwritingModelLoader("en", receiver, callback).Load()
// which calls -> GetExistingDlcs -> NO libhandwriting dlc exists
// -> Return error DLC_NOT_EXISTED.
// Then it will be the client's duty to install the dlc and then calls
// HandwritingModelLoader("en", receiver, callback).Load() again.
class HandwritingModelLoader {
public:
HandwritingModelLoader(
mojom::HandwritingRecognizerSpecPtr spec,
mojo::PendingReceiver<mojom::HandwritingRecognizer> receiver,
mojom::MachineLearningService::LoadHandwritingModelCallback callback);
~HandwritingModelLoader();
// Load handwriting model based on the comandline flag and language.
void Load();
static constexpr char kOndeviceHandwritingSwitch[] = "ondevice_handwriting";
static constexpr char kLibHandwritingDlcId[] = "libhandwriting";
private:
friend class HandwritingModelLoaderTest;
// Called when the existing-dlc-list is returned.
// Returns an error if libhandwriting is not in the existing-dlc-list.
// Calls InstallDlc otherwise.
void OnGetExistingDlcsComplete(
const std::string& err,
const dlcservice::DlcsWithContent& dlcs_with_content);
// Called when InstallDlc completes.
// Returns an error if the `result.error` is not dlcservice::kErrorNone.
// Calls mlservice to LoadHandwritingModel otherwise.
void OnInstallDlcComplete(
const chromeos::DlcserviceClient::InstallResult& result);
DlcserviceClient* dlc_client_;
mojom::HandwritingRecognizerSpecPtr spec_;
mojo::PendingReceiver<mojom::HandwritingRecognizer> receiver_;
mojom::MachineLearningService::LoadHandwritingModelCallback callback_;
base::WeakPtrFactory<HandwritingModelLoader> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(HandwritingModelLoader);
};
} // namespace machine_learning
} // namespace chromeos
#endif // CHROMEOS_SERVICES_MACHINE_LEARNING_PUBLIC_CPP_HANDWRITING_MODEL_LOADER_H_
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromeos/services/machine_learning/public/cpp/handwriting_model_loader.h"
#include "base/bind.h"
#include "base/command_line.h"
#include "base/run_loop.h"
#include "base/test/scoped_command_line.h"
#include "base/test/task_environment.h"
#include "chromeos/dbus/dlcservice/fake_dlcservice_client.h"
#include "chromeos/services/machine_learning/public/cpp/fake_service_connection.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/cros_system_api/dbus/service_constants.h"
namespace chromeos {
namespace machine_learning {
using chromeos::machine_learning::mojom::LoadHandwritingModelResult;
class HandwritingModelLoaderTest : public testing::Test {
protected:
void SetUp() override {
ServiceConnection::UseFakeServiceConnectionForTesting(
&fake_service_connection_);
result_ = LoadHandwritingModelResult::DEPRECATED_MODEL_SPEC_ERROR;
loader_ = std::make_unique<HandwritingModelLoader>(
mojom::HandwritingRecognizerSpec::New("en"),
recognizer_.BindNewPipeAndPassReceiver(),
base::BindOnce(
&HandwritingModelLoaderTest::OnHandwritingModelLoaderComplete,
base::Unretained(this)));
loader_->dlc_client_ = &fake_client_;
}
// Callback that called when loader_->Load() is over to save the returned
// result.
void OnHandwritingModelLoaderComplete(
const LoadHandwritingModelResult result) {
result_ = result;
}
// Runs loader_->Load() and check the returned result as expected.
void ExpectLoadHandwritingModelResult(
const LoadHandwritingModelResult expected_result) {
loader_->Load();
base::RunLoop().RunUntilIdle();
EXPECT_EQ(result_, expected_result);
}
void SetLanguage(const std::string& language) {
loader_->spec_->language = language;
}
// Creates a dlc list with one dlc inside.
void AddDlcsWithContent(const std::string& dlc_id) {
dlcservice::DlcsWithContent dlcs_with_content;
dlcs_with_content.add_dlc_infos()->set_id(dlc_id);
fake_client_.set_dlcs_with_content(dlcs_with_content);
}
// Sets InstallDlc error.
void SetInstallError(const std::string& error) {
fake_client_.set_install_error(error);
fake_client_.set_install_root_path("/any-path");
}
// Sets "ondevice_handwriting" value.
void SetSwitchValue(const std::string& switch_value) {
base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
HandwritingModelLoader::kOndeviceHandwritingSwitch, switch_value);
}
private:
base::test::TaskEnvironment task_environment_{
base::test::TaskEnvironment::MainThreadType::DEFAULT,
base::test::TaskEnvironment::ThreadPoolExecutionMode::QUEUED};
base::test::ScopedCommandLine scoped_command_line_;
FakeDlcserviceClient fake_client_;
FakeServiceConnectionImpl fake_service_connection_;
LoadHandwritingModelResult result_;
mojo::Remote<mojom::HandwritingRecognizer> recognizer_;
std::unique_ptr<HandwritingModelLoader> loader_;
};
TEST_F(HandwritingModelLoaderTest, HandwritingNotEnabled) {
SetSwitchValue("random_string");
// Random switch value should return FEATURE_NOT_SUPPORTED_ERROR.
ExpectLoadHandwritingModelResult(
LoadHandwritingModelResult::FEATURE_NOT_SUPPORTED_ERROR);
}
TEST_F(HandwritingModelLoaderTest, LoadingWithInvalidLanguage) {
SetSwitchValue("use_rootfs");
SetLanguage("random string as language");
// Random language code should return LANGUAGE_NOT_SUPPORTED_ERROR.
ExpectLoadHandwritingModelResult(
LoadHandwritingModelResult::LANGUAGE_NOT_SUPPORTED_ERROR);
}
TEST_F(HandwritingModelLoaderTest, LoadingWithUseRootfs) {
SetSwitchValue("use_rootfs");
// Load from rootfs should return success.
ExpectLoadHandwritingModelResult(LoadHandwritingModelResult::OK);
}
TEST_F(HandwritingModelLoaderTest, LoadingWithoutDlcOnDevice) {
SetSwitchValue("use_dlc");
AddDlcsWithContent("random dlc-id");
// Random dlc id should return DLC_DOES_NOT_EXIST.
ExpectLoadHandwritingModelResult(
LoadHandwritingModelResult::DLC_DOES_NOT_EXIST);
}
TEST_F(HandwritingModelLoaderTest, DlcInstalledWithError) {
SetSwitchValue("use_dlc");
AddDlcsWithContent(HandwritingModelLoader::kLibHandwritingDlcId);
SetInstallError("random error");
// InstallDlc error should return DLC_INSTALL_ERROR.
ExpectLoadHandwritingModelResult(
LoadHandwritingModelResult::DLC_INSTALL_ERROR);
}
TEST_F(HandwritingModelLoaderTest, DlcInstalledWithoutError) {
SetSwitchValue("use_dlc");
AddDlcsWithContent(HandwritingModelLoader::kLibHandwritingDlcId);
SetInstallError(dlcservice::kErrorNone);
// InstallDlc without an error should return success.
ExpectLoadHandwritingModelResult(LoadHandwritingModelResult::OK);
}
} // namespace machine_learning
} // namespace chromeos
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment