9f22d4f8cf
Replaced HIDL spec implementation with AIDL spec in confirmationui module. Ignore-AOSP-First: Dependent on internal change. Bug: b/205760172 Test: Run confirmation UI test using CTS Verifier, atest VtsHalConfirmationUITargetTest Change-Id: I49b9cb6d93fa35fd611003f7545d2ce4976eec7c
511 lines
20 KiB
C++
511 lines
20 KiB
C++
/*
|
|
*
|
|
* Copyright 2019, 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 "TrustyConfirmationUI.h"
|
|
|
|
#include <android-base/logging.h>
|
|
#include <fcntl.h>
|
|
#include <linux/input.h>
|
|
#include <poll.h>
|
|
#include <pthread.h>
|
|
#include <secure_input/evdev.h>
|
|
#include <secure_input/secure_input_device.h>
|
|
#include <secure_input/secure_input_proto.h>
|
|
#include <signal.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <teeui/msg_formatting.h>
|
|
#include <teeui/utils.h>
|
|
#include <time.h>
|
|
|
|
#include <atomic>
|
|
#include <functional>
|
|
#include <memory>
|
|
#include <thread>
|
|
#include <tuple>
|
|
#include <vector>
|
|
|
|
namespace aidl::android::hardware::confirmationui {
|
|
using namespace secure_input;
|
|
|
|
using ::android::trusty::confirmationui::TrustyAppError;
|
|
|
|
using ::teeui::AbortMsg;
|
|
using ::teeui::DeliverTestCommandMessage;
|
|
using ::teeui::DeliverTestCommandResponse;
|
|
using ::teeui::FetchConfirmationResult;
|
|
using ::teeui::MsgString;
|
|
using ::teeui::MsgVector;
|
|
using ::teeui::PromptUserConfirmationMsg;
|
|
using ::teeui::PromptUserConfirmationResponse;
|
|
using ::teeui::ResultMsg;
|
|
|
|
using ::secure_input::createSecureInput;
|
|
|
|
using ::std::tie;
|
|
|
|
using TeeuiRc = ::teeui::ResponseCode;
|
|
|
|
constexpr const char kTrustyDeviceName[] = "/dev/trusty-ipc-dev0";
|
|
constexpr const char kConfirmationuiAppName[] = CONFIRMATIONUI_PORT;
|
|
|
|
namespace {
|
|
|
|
class Finalize {
|
|
private:
|
|
std::function<void()> f_;
|
|
|
|
public:
|
|
Finalize(std::function<void()> f) : f_(f) {}
|
|
~Finalize() {
|
|
if (f_) f_();
|
|
}
|
|
void release() { f_ = {}; }
|
|
};
|
|
|
|
int convertRc(TeeuiRc trc) {
|
|
static_assert(
|
|
uint32_t(TeeuiRc::OK) == uint32_t(IConfirmationUI::OK) &&
|
|
uint32_t(TeeuiRc::Canceled) == uint32_t(IConfirmationUI::CANCELED) &&
|
|
uint32_t(TeeuiRc::Aborted) == uint32_t(IConfirmationUI::ABORTED) &&
|
|
uint32_t(TeeuiRc::OperationPending) == uint32_t(IConfirmationUI::OPERATION_PENDING) &&
|
|
uint32_t(TeeuiRc::Ignored) == uint32_t(IConfirmationUI::IGNORED) &&
|
|
uint32_t(TeeuiRc::SystemError) == uint32_t(IConfirmationUI::SYSTEM_ERROR) &&
|
|
uint32_t(TeeuiRc::Unimplemented) == uint32_t(IConfirmationUI::UNIMPLEMENTED) &&
|
|
uint32_t(TeeuiRc::Unexpected) == uint32_t(IConfirmationUI::UNEXPECTED) &&
|
|
uint32_t(TeeuiRc::UIError) == uint32_t(IConfirmationUI::UI_ERROR) &&
|
|
uint32_t(TeeuiRc::UIErrorMissingGlyph) ==
|
|
uint32_t(IConfirmationUI::UI_ERROR_MISSING_GLYPH) &&
|
|
uint32_t(TeeuiRc::UIErrorMessageTooLong) ==
|
|
uint32_t(IConfirmationUI::UI_ERROR_MESSAGE_TOO_LONG) &&
|
|
uint32_t(TeeuiRc::UIErrorMalformedUTF8Encoding) ==
|
|
uint32_t(IConfirmationUI::UI_ERROR_MALFORMED_UTF8ENCODING),
|
|
"teeui::ResponseCode and "
|
|
"::android::hardware::confirmationui::V1_0::Responsecude are out of "
|
|
"sync");
|
|
return static_cast<int>(trc);
|
|
}
|
|
|
|
teeui::UIOption convertUIOption(UIOption uio) {
|
|
static_assert(uint32_t(UIOption::ACCESSIBILITY_INVERTED) ==
|
|
uint32_t(teeui::UIOption::AccessibilityInverted) &&
|
|
uint32_t(UIOption::ACCESSIBILITY_MAGNIFIED) ==
|
|
uint32_t(teeui::UIOption::AccessibilityMagnified),
|
|
"teeui::UIOPtion and ::android::hardware::confirmationui::V1_0::UIOption "
|
|
"are out of sync");
|
|
return teeui::UIOption(uio);
|
|
}
|
|
|
|
inline MsgString stdString2MsgString(const string& s) {
|
|
return {s.c_str(), s.c_str() + s.size()};
|
|
}
|
|
template <typename T> inline MsgVector<T> stdVector2MsgVector(const vector<T>& v) {
|
|
return {v};
|
|
}
|
|
|
|
inline MsgVector<teeui::UIOption> stdVector2MsgVector(const vector<UIOption>& v) {
|
|
MsgVector<teeui::UIOption> result(v.size());
|
|
for (unsigned int i = 0; i < v.size(); ++i) {
|
|
result[i] = convertUIOption(v[i]);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TrustyConfirmationUI::TrustyConfirmationUI()
|
|
: listener_state_(ListenerState::None), prompt_result_(IConfirmationUI::IGNORED) {}
|
|
|
|
TrustyConfirmationUI::~TrustyConfirmationUI() {
|
|
ListenerState state = listener_state_;
|
|
if (state == ListenerState::SetupDone || state == ListenerState::Interactive) {
|
|
abort();
|
|
}
|
|
if (state != ListenerState::None) {
|
|
callback_thread_.join();
|
|
}
|
|
}
|
|
|
|
std::tuple<TeeuiRc, MsgVector<uint8_t>, MsgVector<uint8_t>>
|
|
TrustyConfirmationUI::promptUserConfirmation_(const MsgString& promptText,
|
|
const MsgVector<uint8_t>& extraData,
|
|
const MsgString& locale,
|
|
const MsgVector<teeui::UIOption>& uiOptions) {
|
|
std::unique_lock<std::mutex> stateLock(listener_state_lock_);
|
|
/*
|
|
* This is the main listener thread function. The listener thread life cycle
|
|
* is equivalent to the life cycle of a single confirmation request. The life
|
|
* cycle is devided in four phases.
|
|
* * The starting phase:
|
|
* * The Trusted App gets loaded and/or the connection to it gets established.
|
|
* * A connection to the secure input device is established.
|
|
* * The prompt is initiated. This sends all information required by the
|
|
* confirmation dialog to the TA. The dialog is not yet displayed.
|
|
* * An event loop is created.
|
|
* * The event loop listens for user input events, fetches them from the
|
|
* secure input device, and delivers them to the TA.
|
|
* * All evdev devices are grabbed to give confirmationui exclusive access
|
|
* to user input.
|
|
*
|
|
* Note: During the starting phase the hwbinder service thread is blocked and
|
|
* waiting for possible Errors. If the setup phase concludes sucessfully, the
|
|
* hwbinder service thread gets unblocked and returns successfully. Errors
|
|
* that occur after the first phase are delivered by callback interface.
|
|
*
|
|
* * The 2nd phase - non interactive phase
|
|
* * The event loop thread is started.
|
|
* * After a grace period:
|
|
* * A handshake between the secure input device SecureInput and the TA
|
|
* is performed.
|
|
* * The input event handler are armed to process user input events.
|
|
*
|
|
* * The 3rd phase - interactive phase
|
|
* * We wait to any external event
|
|
* * Abort
|
|
* * Secure user input asserted
|
|
* * Secure input delivered (for non interactive VTS testing)
|
|
* * The result is fetched from the TA.
|
|
*
|
|
* * The 4th phase - cleanup
|
|
* The cleanup phase is given by the scope of automatic variables created
|
|
* in this function. The cleanup commences in reverse order of their creation.
|
|
* Here is a list of more complex items in the order in which they go out of
|
|
* scope
|
|
* * finalizeSecureTouch - signals and joins the secure touch thread.
|
|
* * eventloop - signals and joins the event loop thread. The event
|
|
* handlers also own all EventDev instances which ungrab the event devices.
|
|
* When the eventloop goes out of scope the EventDevs get destroyed
|
|
* relinquishing the exclusive hold on the event devices.
|
|
* * finalizeConfirmationPrompt - calls abort on the TA, making sure a
|
|
* pending operation gets canceled. If the prompt concluded successfully this
|
|
* is a spurious call but semantically a no op.
|
|
* * secureInput - shuts down the connection to the secure input device
|
|
* SecureInput.
|
|
* * app - disconnects the TA. Since app is a shared pointer this may not
|
|
* unload the app here. It is possible that more instances of the shared
|
|
* pointer are held in TrustyConfirmationUI::deliverSecureInputEvent and
|
|
* TrustyConfirmationUI::abort. But these instances are extremely short lived
|
|
* and it is safe if they are destroyed by either.
|
|
* * stateLock - unlocks the listener_state_lock_ if it happens to be held
|
|
* at the time of return.
|
|
*/
|
|
|
|
std::tuple<TeeuiRc, MsgVector<uint8_t>, MsgVector<uint8_t>> result;
|
|
TeeuiRc& rc = std::get<TeeuiRc>(result);
|
|
rc = TeeuiRc::SystemError;
|
|
|
|
listener_state_ = ListenerState::Starting;
|
|
|
|
auto app = std::make_shared<TrustyApp>(kTrustyDeviceName, kConfirmationuiAppName);
|
|
if (!app) return result; // TeeuiRc::SystemError
|
|
|
|
app_ = app;
|
|
|
|
auto hsBegin = [&]() -> std::tuple<TeeuiRc, Nonce> {
|
|
auto [error, result] =
|
|
app->issueCmd<secure_input::InputHandshake, secure_input::InputHandshakeResponse>();
|
|
auto& [rc, nCo] = result;
|
|
|
|
if (error != TrustyAppError::OK || rc != TeeuiRc::OK) {
|
|
LOG(ERROR) << "Failed to begin secure input handshake (" << int32_t(error) << "/"
|
|
<< uint32_t(rc) << ")";
|
|
rc = error != TrustyAppError::OK ? TeeuiRc::SystemError : rc;
|
|
}
|
|
return result;
|
|
};
|
|
|
|
auto hsFinalize = [&](const Signature& sig, const Nonce& nCi) -> TeeuiRc {
|
|
auto [error, finalizeResponse] =
|
|
app->issueCmd<FinalizeInputSessionHandshake, FinalizeInputSessionHandshakeResponse>(
|
|
nCi, sig);
|
|
auto& [rc] = finalizeResponse;
|
|
if (error != TrustyAppError::OK || rc != TeeuiRc::OK) {
|
|
LOG(ERROR) << "Failed to finalize secure input handshake (" << int32_t(error) << "/"
|
|
<< uint32_t(rc) << ")";
|
|
rc = error != TrustyAppError::OK ? TeeuiRc::SystemError : rc;
|
|
}
|
|
return rc;
|
|
};
|
|
|
|
auto deliverInput = [&](DTupKeyEvent event,
|
|
const Signature& sig) -> std::tuple<TeeuiRc, InputResponse> {
|
|
auto [error, result] =
|
|
app->issueCmd<DeliverInputEvent, DeliverInputEventResponse>(event, sig);
|
|
auto& [rc, ir] = result;
|
|
if (error != TrustyAppError::OK) {
|
|
LOG(ERROR) << "Failed to deliver input command";
|
|
rc = TeeuiRc::SystemError;
|
|
}
|
|
return result;
|
|
};
|
|
|
|
std::atomic<TeeuiRc> eventRC = TeeuiRc::OperationPending;
|
|
auto inputResult = [&](TeeuiRc rc) {
|
|
TeeuiRc expected = TeeuiRc::OperationPending;
|
|
if (eventRC.compare_exchange_strong(expected, rc)) {
|
|
listener_state_condv_.notify_all();
|
|
}
|
|
};
|
|
|
|
// create Secure Input device.
|
|
auto secureInput = createSecureInput(hsBegin, hsFinalize, deliverInput, inputResult);
|
|
if (!secureInput || !(*secureInput)) {
|
|
LOG(ERROR) << "Failed to open secure input device";
|
|
return result; // TeeuiRc::SystemError;
|
|
}
|
|
|
|
Finalize finalizeConfirmationPrompt([app] {
|
|
LOG(INFO) << "Calling abort for cleanup";
|
|
app->issueCmd<AbortMsg>();
|
|
});
|
|
|
|
// initiate prompt
|
|
LOG(INFO) << "Initiating prompt";
|
|
TrustyAppError error;
|
|
auto initResponse = std::tie(rc);
|
|
std::tie(error, initResponse) =
|
|
app->issueCmd<PromptUserConfirmationMsg, PromptUserConfirmationResponse>(
|
|
promptText, extraData, locale, uiOptions);
|
|
if (error == TrustyAppError::MSG_TOO_LONG) {
|
|
LOG(ERROR) << "PromptUserConfirmationMsg failed: message too long";
|
|
rc = TeeuiRc::UIErrorMessageTooLong;
|
|
return result;
|
|
} else if (error != TrustyAppError::OK) {
|
|
LOG(ERROR) << "PromptUserConfirmationMsg failed: " << int32_t(error);
|
|
return result; // TeeuiRc::SystemError;
|
|
}
|
|
if (rc != TeeuiRc::OK) {
|
|
LOG(ERROR) << "PromptUserConfirmationMsg failed: " << uint32_t(rc);
|
|
return result;
|
|
}
|
|
|
|
LOG(INFO) << "Grabbing event devices";
|
|
EventLoop eventloop;
|
|
bool grabbed =
|
|
grabAllEvDevsAndRegisterCallbacks(&eventloop, [&](short flags, const EventDev& evDev) {
|
|
if (!(flags & POLLIN)) return;
|
|
secureInput->handleEvent(evDev);
|
|
});
|
|
|
|
if (!grabbed) {
|
|
rc = TeeuiRc::SystemError;
|
|
return result;
|
|
}
|
|
|
|
abort_called_ = false;
|
|
secureInputDelivered_ = false;
|
|
|
|
// ############################## Start 2nd Phase #############################################
|
|
listener_state_ = ListenerState::SetupDone;
|
|
stateLock.unlock();
|
|
listener_state_condv_.notify_all();
|
|
|
|
if (!eventloop.start()) {
|
|
rc = TeeuiRc::SystemError;
|
|
return result;
|
|
}
|
|
|
|
stateLock.lock();
|
|
|
|
LOG(INFO) << "going to sleep for the grace period";
|
|
auto then = std::chrono::system_clock::now() +
|
|
std::chrono::milliseconds(kUserPreInputGracePeriodMillis) +
|
|
std::chrono::microseconds(50);
|
|
listener_state_condv_.wait_until(stateLock, then, [&]() { return abort_called_; });
|
|
LOG(INFO) << "waking up";
|
|
|
|
if (abort_called_) {
|
|
LOG(ERROR) << "Abort called";
|
|
result = {TeeuiRc::Aborted, {}, {}};
|
|
return result;
|
|
}
|
|
|
|
LOG(INFO) << "Arming event poller";
|
|
// tell the event poller to act on received input events from now on.
|
|
secureInput->start();
|
|
|
|
// ############################## Start 3rd Phase - interactive phase #########################
|
|
LOG(INFO) << "Transition to Interactive";
|
|
listener_state_ = ListenerState::Interactive;
|
|
stateLock.unlock();
|
|
listener_state_condv_.notify_all();
|
|
|
|
stateLock.lock();
|
|
listener_state_condv_.wait(stateLock, [&]() {
|
|
return eventRC != TeeuiRc::OperationPending || abort_called_ || secureInputDelivered_;
|
|
});
|
|
LOG(INFO) << "Listener waking up";
|
|
if (abort_called_) {
|
|
LOG(ERROR) << "Abort called";
|
|
result = {TeeuiRc::Aborted, {}, {}};
|
|
return result;
|
|
}
|
|
|
|
if (!secureInputDelivered_) {
|
|
if (eventRC != TeeuiRc::OK) {
|
|
LOG(ERROR) << "Bad input response";
|
|
result = {eventRC, {}, {}};
|
|
return result;
|
|
}
|
|
}
|
|
|
|
stateLock.unlock();
|
|
|
|
LOG(INFO) << "Fetching Result";
|
|
std::tie(error, result) = app->issueCmd<FetchConfirmationResult, ResultMsg>();
|
|
LOG(INFO) << "Result yields " << int32_t(error) << "/" << uint32_t(rc);
|
|
if (error != TrustyAppError::OK) {
|
|
result = {TeeuiRc::SystemError, {}, {}};
|
|
}
|
|
return result;
|
|
|
|
// ############################## Start 4th Phase - cleanup ##################################
|
|
}
|
|
|
|
// Methods from ::aidl::android::hardware::confirmationui::IConfirmationUI
|
|
// follow.
|
|
::ndk::ScopedAStatus TrustyConfirmationUI::promptUserConfirmation(
|
|
const shared_ptr<IConfirmationResultCallback>& resultCB, const vector<uint8_t>& promptTextBytes,
|
|
const vector<uint8_t>& extraData, const string& locale, const vector<UIOption>& uiOptions) {
|
|
std::unique_lock<std::mutex> stateLock(listener_state_lock_, std::defer_lock);
|
|
string promptText(promptTextBytes.begin(), promptTextBytes.end());
|
|
if (!stateLock.try_lock()) {
|
|
return ndk::ScopedAStatus(
|
|
AStatus_fromServiceSpecificError(IConfirmationUI::OPERATION_PENDING));
|
|
}
|
|
switch (listener_state_) {
|
|
case ListenerState::None:
|
|
break;
|
|
case ListenerState::Starting:
|
|
case ListenerState::SetupDone:
|
|
case ListenerState::Interactive:
|
|
return ndk::ScopedAStatus(
|
|
AStatus_fromServiceSpecificError(IConfirmationUI::OPERATION_PENDING));
|
|
case ListenerState::Terminating:
|
|
callback_thread_.join();
|
|
listener_state_ = ListenerState::None;
|
|
break;
|
|
default:
|
|
return ndk::ScopedAStatus(AStatus_fromServiceSpecificError(IConfirmationUI::UNEXPECTED));
|
|
}
|
|
|
|
assert(listener_state_ == ListenerState::None);
|
|
|
|
callback_thread_ = std::thread(
|
|
[this](const shared_ptr<IConfirmationResultCallback>& resultCB, const string& promptText,
|
|
const vector<uint8_t>& extraData, const string& locale,
|
|
const vector<UIOption>& uiOptions) {
|
|
auto [trc, msg, token] = promptUserConfirmation_(
|
|
stdString2MsgString(promptText), stdVector2MsgVector(extraData),
|
|
stdString2MsgString(locale), stdVector2MsgVector(uiOptions));
|
|
bool do_callback = (listener_state_ == ListenerState::Interactive ||
|
|
listener_state_ == ListenerState::SetupDone) &&
|
|
resultCB;
|
|
prompt_result_ = convertRc(trc);
|
|
listener_state_ = ListenerState::Terminating;
|
|
if (do_callback) {
|
|
auto error = resultCB->result(prompt_result_, msg, token);
|
|
if (!error.isOk()) {
|
|
LOG(ERROR) << "Result callback failed " << error.getDescription();
|
|
}
|
|
} else {
|
|
listener_state_condv_.notify_all();
|
|
}
|
|
},
|
|
resultCB, promptText, extraData, locale, uiOptions);
|
|
|
|
listener_state_condv_.wait(stateLock, [this] {
|
|
return listener_state_ == ListenerState::SetupDone ||
|
|
listener_state_ == ListenerState::Interactive ||
|
|
listener_state_ == ListenerState::Terminating;
|
|
});
|
|
if (listener_state_ == ListenerState::Terminating) {
|
|
callback_thread_.join();
|
|
listener_state_ = ListenerState::None;
|
|
return ndk::ScopedAStatus(AStatus_fromServiceSpecificError(prompt_result_));
|
|
}
|
|
return ndk::ScopedAStatus::ok();
|
|
}
|
|
|
|
::ndk::ScopedAStatus
|
|
TrustyConfirmationUI::deliverSecureInputEvent(const HardwareAuthToken& secureInputToken) {
|
|
int rc = IConfirmationUI::IGNORED;
|
|
{
|
|
/*
|
|
* deliverSecureInputEvent is only used by the VTS test to mock human input. A correct
|
|
* implementation responds with a mock confirmation token signed with a test key. The
|
|
* problem is that the non interactive grace period was not formalized in the HAL spec,
|
|
* so that the VTS test does not account for the grace period. (It probably should.)
|
|
* This means we can only pass the VTS test if we block until the grace period is over
|
|
* (SetupDone -> Interactive) before we deliver the input event.
|
|
*
|
|
* The true secure input is delivered by a different mechanism and gets ignored -
|
|
* not queued - until the grace period is over.
|
|
*
|
|
*/
|
|
std::unique_lock<std::mutex> stateLock(listener_state_lock_);
|
|
listener_state_condv_.wait(stateLock,
|
|
[this] { return listener_state_ != ListenerState::SetupDone; });
|
|
|
|
if (listener_state_ != ListenerState::Interactive)
|
|
return ndk::ScopedAStatus(AStatus_fromServiceSpecificError(IConfirmationUI::IGNORED));
|
|
auto sapp = app_.lock();
|
|
if (!sapp)
|
|
return ndk::ScopedAStatus(AStatus_fromServiceSpecificError(IConfirmationUI::IGNORED));
|
|
auto [error, response] =
|
|
sapp->issueCmd<DeliverTestCommandMessage, DeliverTestCommandResponse>(
|
|
static_cast<teeui::TestModeCommands>(secureInputToken.challenge));
|
|
if (error != TrustyAppError::OK)
|
|
return ndk::ScopedAStatus(
|
|
AStatus_fromServiceSpecificError(IConfirmationUI::SYSTEM_ERROR));
|
|
auto& [trc] = response;
|
|
if (trc != TeeuiRc::Ignored) secureInputDelivered_ = true;
|
|
rc = convertRc(trc);
|
|
}
|
|
if (secureInputDelivered_) listener_state_condv_.notify_all();
|
|
// VTS test expect an OK response if the event was successfully delivered.
|
|
// But since the TA returns the callback response now, we have to translate
|
|
// Canceled into OK. Canceled is only returned if the delivered event canceled
|
|
// the operation, which means that the event was successfully delivered. Thus
|
|
// we return OK.
|
|
if (rc == IConfirmationUI::CANCELED) return ndk::ScopedAStatus::ok();
|
|
if (rc != IConfirmationUI::OK) {
|
|
return ndk::ScopedAStatus(AStatus_fromServiceSpecificError(rc));
|
|
}
|
|
return ndk::ScopedAStatus::ok();
|
|
}
|
|
|
|
::ndk::ScopedAStatus TrustyConfirmationUI::abort() {
|
|
{
|
|
std::unique_lock<std::mutex> stateLock(listener_state_lock_);
|
|
if (listener_state_ == ListenerState::SetupDone ||
|
|
listener_state_ == ListenerState::Interactive) {
|
|
auto sapp = app_.lock();
|
|
if (sapp) sapp->issueCmd<AbortMsg>();
|
|
abort_called_ = true;
|
|
}
|
|
}
|
|
listener_state_condv_.notify_all();
|
|
return ndk::ScopedAStatus::ok();
|
|
}
|
|
|
|
std::shared_ptr<IConfirmationUI> createTrustyConfirmationUI() {
|
|
return ndk::SharedRefBase::make<TrustyConfirmationUI>();
|
|
}
|
|
|
|
} // namespace aidl::android::hardware::confirmationui
|