Skip to content

Commit

Permalink
March 2022 Release of the APL 1.8.3 compliant APL Client Library
Browse files Browse the repository at this point in the history
For more details on this release refer to CHANGELOG.md

To learn about APL see: https://developer.amazon.com/docs/alexa-presentation-language/understand-apl.html
  • Loading branch information
Reginald Thomas committed Mar 9, 2022
1 parent 8af0347 commit ac96cbc
Show file tree
Hide file tree
Showing 13 changed files with 166 additions and 32 deletions.
50 changes: 50 additions & 0 deletions APLClient/include/APLClient/AplCommandExecutionEvent.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0/
*
* or in the "license" file accompanying this file. This file 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.
*/

#ifndef APLCLIENT_INCLUDE_APLCOMMANDEXECUTIONEVENT_H_
#define APLCLIENT_INCLUDE_APLCOMMANDEXECUTIONEVENT_H_

namespace APLClient {

/// Enumeration of APL Command Execution Events that could be sent from APLClient to runtime when processing commands.
enum class AplCommandExecutionEvent {
/// APL Core Engine resolved the commands sequence.
RESOLVED,

/// APL Core Engine terminated the commands sequence.
TERMINATED,

/// APL Client/Core failed to parse/handle the commands sequence.
FAILED
};

/**
* This is a function to convert the @c AplCommandExecutionEvent to a string.
*/
inline std::string commandExecutionEventToString(const AplCommandExecutionEvent event) {
switch (event) {
case AplCommandExecutionEvent::RESOLVED:
return "RESOLVED";
case AplCommandExecutionEvent::TERMINATED:
return "TERMINATED";
case AplCommandExecutionEvent::FAILED:
return "FAILED";
}
return "UNKNOWN";
}

} // namespace APLClient

#endif // APLCLIENT_INCLUDE_APLCOMMANDEXECUTIONEVENT_H_
8 changes: 5 additions & 3 deletions APLClient/include/APLClient/AplOptionsInterface.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include <chrono>
#include <string>

#include "AplCommandExecutionEvent.h"
#include "AplRenderingEvent.h"
#include "Extensions/AplCoreExtensionEventCallbackResultInterface.h"

Expand Down Expand Up @@ -91,10 +92,11 @@ class AplOptionsInterface {

/**
* Command execution has completed
* @param token The APL token
* @param result Whether the command executed to completion successfully
* @param token The APL token.
* @param event The @c AplCommandExecutionEvent.
* @param message The message concerning the completion state.
*/
virtual void onCommandExecutionComplete(const std::string& token, bool result) = 0;
virtual void onCommandExecutionComplete(const std::string& token, AplCommandExecutionEvent event, const std::string& message) = 0;

/**
* Rendering the APL document has completed
Expand Down
40 changes: 28 additions & 12 deletions APLClient/src/AplCoreConnectionManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -393,45 +393,61 @@ void AplCoreConnectionManager::handleUpdateDisplayState(const rapidjson::Value&

void AplCoreConnectionManager::executeCommands(const std::string& command, const std::string& token) {
auto aplOptions = m_aplConfiguration->getAplOptions();

auto commandFailedFunc = [this, token](const std::string& failureMessage) {
auto aplOptions = m_aplConfiguration->getAplOptions();
aplOptions->logMessage(LogLevel::ERROR, "executeCommandsFailed", failureMessage);
aplOptions->onCommandExecutionComplete(token, AplCommandExecutionEvent::FAILED, failureMessage);
};

if (!m_Root) {
aplOptions->logMessage(LogLevel::ERROR, "executeCommandsFailed", "Root context is missing");
commandFailedFunc("Root context is missing");
return;
}

std::shared_ptr<rapidjson::Document> document(new rapidjson::Document);
if (document->Parse(command).HasParseError()) {
aplOptions->logMessage(LogLevel::ERROR, "executeCommandsFailed", "Parse commands failed");
commandFailedFunc("Parse commands failed");
return;
}

auto it = document->FindMember("commands");
if (it == document->MemberEnd() || it->value.GetType() != rapidjson::Type::kArrayType) {
aplOptions->logMessage(LogLevel::ERROR, "executeCommandsFailed", "Missing commands, or is not array");
commandFailedFunc("Missing commands, or is not array");
return;
}

apl::Object object{it->value};
auto action = m_Root->executeCommands(object, false);
if (!action) {
aplOptions->logMessage(LogLevel::ERROR, "executeCommandsFailed", "Execute commands failed");
commandFailedFunc("APL Core could not process commands");
return;
}

aplOptions->onActivityStarted(token, APL_COMMAND_EXECUTION);

action->then([this, document, token](const apl::ActionPtr& action) {
auto actionResolvedFunc = [this, document, token](const apl::ActionPtr&) {
auto aplOptions = m_aplConfiguration->getAplOptions();
aplOptions->logMessage(LogLevel::DBG, "executeCommands", "Command sequence complete");
aplOptions->onCommandExecutionComplete(token, true);
aplOptions->logMessage(LogLevel::DBG, "executeCommandsResolved", "Command sequence completed");
aplOptions->onCommandExecutionComplete(token, AplCommandExecutionEvent::RESOLVED, "Command sequence completed");
aplOptions->onActivityEnded(token, APL_COMMAND_EXECUTION);
});
};

action->addTerminateCallback([this, document, token](const apl::TimersPtr&) {
auto actionTerminatedFunc = [this, document, token](const apl::TimersPtr&) {
auto aplOptions = m_aplConfiguration->getAplOptions();
aplOptions->logMessage(LogLevel::DBG, "executeCommandsFailed", "Command sequence failed");
aplOptions->onCommandExecutionComplete(token, false);
aplOptions->logMessage(LogLevel::DBG, "executeCommandsTerminated", "Command sequence terminated");
aplOptions->onCommandExecutionComplete(token, AplCommandExecutionEvent::TERMINATED, "Command sequence terminated");
aplOptions->onActivityEnded(token, APL_COMMAND_EXECUTION);
});
};

if (action->isPending()) {
action->then(actionResolvedFunc);
action->addTerminateCallback(actionTerminatedFunc);
} else if(action->isResolved()) {
actionResolvedFunc(nullptr);
} else if (action->isTerminated()) {
actionTerminatedFunc(nullptr);
}
}

void AplCoreConnectionManager::onExtensionEvent(
Expand Down
3 changes: 1 addition & 2 deletions APLClient/src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ AplCoreGuiRenderer.cpp
AplCoreMetrics.cpp
AplCoreTextMeasurement.cpp
AplCoreLocaleMethods.cpp
AplClientRenderer.cpp
)
AplClientRenderer.cpp)

if(NOT APLCORE_RAPIDJSON_INCLUDE_DIR)
message(FATAL_ERROR "APLCORE_RAPIDJSON_INCLUDE_DIR is required to build APLClientLibrary")
Expand Down
60 changes: 56 additions & 4 deletions APLClient/test/AplCoreConnectionManagerTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -516,10 +516,10 @@ TEST_F(AplCoreConnectionManagerTest, CheckDocumentTimeoutInSettings) {
BuildDocument(DOCUMENT_WITH_IDLETIMEOUT, DATA, VIEWPORT);
}

TEST_F(AplCoreConnectionManagerTest, ExecuteCommandsSuccess) {
TEST_F(AplCoreConnectionManagerTest, ExecuteCommandsAsyncResolve) {
SetupMocksForDocumentRender();
EXPECT_CALL(*m_mockAplOptions, onActivityStarted(_, APL_COMMAND_EXECUTION)).Times(1);
EXPECT_CALL(*m_mockAplOptions, onCommandExecutionComplete(_, true)).Times(1);
EXPECT_CALL(*m_mockAplOptions, onCommandExecutionComplete(_, AplCommandExecutionEvent::RESOLVED, _)).Times(1);
EXPECT_CALL(*m_mockAplOptions, onActivityEnded(_, APL_COMMAND_EXECUTION)).Times(2);

BuildDocument(DOCUMENT, DATA, VIEWPORT);
Expand All @@ -544,10 +544,43 @@ TEST_F(AplCoreConnectionManagerTest, ExecuteCommandsSuccess) {
m_aplCoreConnectionManager->onUpdateTick();
}

TEST_F(AplCoreConnectionManagerTest, ExecuteCommandsAutoResolve) {
SetupMocksForDocumentRender();
EXPECT_CALL(*m_mockAplOptions, onActivityStarted(_, APL_COMMAND_EXECUTION)).Times(1);
EXPECT_CALL(*m_mockAplOptions, onCommandExecutionComplete(_, AplCommandExecutionEvent::RESOLVED, _)).Times(1);
EXPECT_CALL(*m_mockAplOptions, onActivityEnded(_, APL_COMMAND_EXECUTION)).Times(2);

BuildDocument(DOCUMENT, DATA, VIEWPORT);

const std::string payload =
"{"
" \"commands\": ["
" {"
" \"type\": \"Idle\","
" \"sequencer\": \"custom_sequencer\""
" }"
" ]"
"}";
m_aplCoreConnectionManager->executeCommands(payload, "");
}

TEST_F(AplCoreConnectionManagerTest, ExecuteCommandsParseFailed) {
SetupMocksForDocumentRender();
EXPECT_CALL(*m_mockAplOptions, onCommandExecutionComplete(_, AplCommandExecutionEvent::FAILED, _)).Times(1);

BuildDocument(DOCUMENT, DATA, VIEWPORT);

const std::string payload =
"{"
" \"command\": {}"
"}";
m_aplCoreConnectionManager->executeCommands(payload, "");
}

TEST_F(AplCoreConnectionManagerTest, ExecuteCommandsInterrupt) {
SetupMocksForDocumentRender();
EXPECT_CALL(*m_mockAplOptions, onActivityStarted(_, APL_COMMAND_EXECUTION)).Times(1);
EXPECT_CALL(*m_mockAplOptions, onCommandExecutionComplete(_, false)).Times(1);
EXPECT_CALL(*m_mockAplOptions, onCommandExecutionComplete(_, AplCommandExecutionEvent::TERMINATED, _)).Times(1);
EXPECT_CALL(*m_mockAplOptions, onActivityEnded(_, APL_COMMAND_EXECUTION)).Times(2);

BuildDocument(DOCUMENT, DATA, VIEWPORT);
Expand All @@ -567,6 +600,26 @@ TEST_F(AplCoreConnectionManagerTest, ExecuteCommandsInterrupt) {
m_aplCoreConnectionManager->interruptCommandSequence();
}

TEST_F(AplCoreConnectionManagerTest, ExecuteCommandsFinishTermination) {
SetupMocksForDocumentRender();
EXPECT_CALL(*m_mockAplOptions, onActivityStarted(_, APL_COMMAND_EXECUTION)).Times(1);
EXPECT_CALL(*m_mockAplOptions, onCommandExecutionComplete(_, AplCommandExecutionEvent::TERMINATED, _)).Times(1);
EXPECT_CALL(*m_mockAplOptions, onActivityEnded(_, APL_COMMAND_EXECUTION)).Times(2);

BuildDocument(DOCUMENT, DATA, VIEWPORT);

const std::string payload =
"{"
" \"commands\": ["
" {"
" \"type\": \"Finish\","
" \"delay\": \"3000\""
" }"
" ]"
"}";
m_aplCoreConnectionManager->executeCommands(payload, "");
}

/**
* Tests HandleMessage function with update type.
*/
Expand All @@ -587,7 +640,6 @@ TEST_F(AplCoreConnectionManagerTest, HandleUpdateSuccess) {
m_aplCoreConnectionManager->handleMessage(payload);
}


/**
* Tests HandleMessage function with updateMedia type.
*/
Expand Down
2 changes: 1 addition & 1 deletion APLClient/test/MockAplOptionsInterface.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class MockAplOptionsInterface : public AplOptionsInterface {
MOCK_METHOD2(onActivityStarted, void(const std::string& token, const std::string& source));
MOCK_METHOD2(onActivityEnded, void(const std::string& token, const std::string& source));
MOCK_METHOD2(onSendEvent, void(const std::string& token, const std::string& event));
MOCK_METHOD2(onCommandExecutionComplete, void(const std::string& token, bool result));
MOCK_METHOD3(onCommandExecutionComplete, void(const std::string& token, AplCommandExecutionEvent event, const std::string& message));
MOCK_METHOD3(onRenderDocumentComplete, void(const std::string& token, bool result, const std::string& error));
MOCK_METHOD3(
onVisualContextAvailable,
Expand Down
7 changes: 7 additions & 0 deletions APLClientSandbox/GUI/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,13 @@ const executeCommand = () => {
})
};

const attentionStateChange = (value) => {
socket.send({
type: 'updateAttentionSystemState',
payload: value
});
}

const switchTab = (evt, tabName) => {
const content = document.getElementsByClassName('content');
const tabs = document.getElementsByClassName('tab');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ class AplClientBridge
void onActivityStarted(const std::string& token, const std::string& source) override;
void onActivityEnded(const std::string& token, const std::string& source) override;
void onSendEvent(const std::string& token, const std::string& event) override;
void onCommandExecutionComplete(const std::string& token, bool result) override;
void onCommandExecutionComplete(const std::string& token, APLClient::AplCommandExecutionEvent event, const std::string& message) override;
void onRenderDocumentComplete(const std::string& token, bool result, const std::string& error) override;
void onVisualContextAvailable(
const std::string& token,
Expand Down
4 changes: 2 additions & 2 deletions APLClientSandbox/src/AplClientBridge.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -185,8 +185,8 @@ void AplClientBridge::onSendEvent(const std::string& token, const std::string& e
Logger::info("AplClientBridge::onSendEvent", event);
}

void AplClientBridge::onCommandExecutionComplete(const std::string& token, bool result) {
Logger::info("AplClientBridge::onCommandExecutionComplete", "success:", result);
void AplClientBridge::onCommandExecutionComplete(const std::string& token, APLClient::AplCommandExecutionEvent event, const std::string& message) {
Logger::info("AplClientBridge::onCommandExecutionComplete", "event:", APLClient::commandExecutionEventToString(event), ", message:", message);
}

void AplClientBridge::onRenderDocumentComplete(const std::string& token, bool result, const std::string& error) {
Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
# Changelog for apl-client-library
## [1.8.3]

### Changed

- Fix latency on renderer destruction
- Fix video player does not reload while destroying elements
- Fix audio node undefined error
- Fix execute commands not handling callbacks correctly

## [1.8.2]

Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Alexa Presentation Language (APL) Client Library

<p>
<a href="https://github.com/alexa/apl-client-library/tree/v1.8.2" alt="version">
<img src="https://img.shields.io/badge/stable%20version-1.8.2-brightgreen" /></a>
<a href="https://github.com/alexa/apl-client-library/tree/v1.8.3" alt="version">
<img src="https://img.shields.io/badge/stable%20version-1.8.3-brightgreen" /></a>
<a href="https://github.com/alexa/apl-core-library/tree/v1.8.1" alt="APLCore">
<img src="https://img.shields.io/badge/apl%20core%20library-1.8.1-navy" /></a>
</p>
Expand Down

0 comments on commit ac96cbc

Please sign in to comment.