summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.clang-format102
-rw-r--r--.gitignore1
-rw-r--r--.gitmodules6
-rw-r--r--CMakeLists.txt21
-rw-r--r--License.txt202
-rw-r--r--README.md95
m---------afb-helpers0
m---------app-controller0
-rw-r--r--conf.d/CMakeLists.txt20
-rwxr-xr-xconf.d/autobuild/agl/autobuild67
-rwxr-xr-xconf.d/autobuild/linux/autobuild67
-rw-r--r--conf.d/cmake/00-debian-osconfig.cmake1
-rw-r--r--conf.d/cmake/00-default-osconfig.cmake1
-rw-r--r--conf.d/cmake/config.cmake223
-rw-r--r--conf.d/project/CMakeLists.txt20
-rw-r--r--conf.d/project/etc/CMakeLists.txt32
-rw-r--r--conf.d/project/etc/vshl-api.json96
-rw-r--r--conf.d/wgt/config.xml.in15
-rw-r--r--htdocs/AFB-websock.js177
-rw-r--r--htdocs/CMakeLists.txt33
-rw-r--r--htdocs/binding.css99
-rw-r--r--htdocs/binding.js226
-rw-r--r--htdocs/index.html95
-rw-r--r--src/CMakeLists.txt44
-rw-r--r--src/plugins/CMakeLists.txt182
-rw-r--r--src/plugins/TestMain.cpp20
-rw-r--r--src/plugins/VshlApi.cpp607
-rw-r--r--src/plugins/VshlApi.h46
-rw-r--r--src/plugins/afb/AFBApiImpl.cpp87
-rw-r--r--src/plugins/afb/AFBApiImpl.h61
-rw-r--r--src/plugins/afb/AFBRequestImpl.cpp37
-rw-r--r--src/plugins/afb/AFBRequestImpl.h49
-rw-r--r--src/plugins/afb/include/AFBEventImpl.h77
-rw-r--r--src/plugins/afb/src/AFBEventImpl.cpp89
-rwxr-xr-xsrc/plugins/agreement.sh43
-rw-r--r--src/plugins/capabilities/CapabilitiesFactory.cpp57
-rw-r--r--src/plugins/capabilities/CapabilitiesFactory.h63
-rw-r--r--src/plugins/capabilities/CapabilityMessagingService.cpp117
-rw-r--r--src/plugins/capabilities/CapabilityMessagingService.h82
-rw-r--r--src/plugins/capabilities/communication/include/PhoneControlCapability.h52
-rw-r--r--src/plugins/capabilities/communication/include/PhoneControlMessages.h128
-rw-r--r--src/plugins/capabilities/communication/src/PhoneControlCapability.cpp42
-rw-r--r--src/plugins/capabilities/core/include/MessageChannel.h68
-rw-r--r--src/plugins/capabilities/core/include/PublisherForwarder.h73
-rw-r--r--src/plugins/capabilities/core/include/SubscriberForwarder.h84
-rw-r--r--src/plugins/capabilities/core/src/MessageChannel.cpp51
-rw-r--r--src/plugins/capabilities/core/src/PublisherForwarder.cpp69
-rw-r--r--src/plugins/capabilities/core/src/SubscriberForwarder.cpp139
-rw-r--r--src/plugins/capabilities/guimetadata/include/GuiMetadataCapability.h52
-rw-r--r--src/plugins/capabilities/guimetadata/include/GuiMetadataMessages.h50
-rw-r--r--src/plugins/capabilities/guimetadata/src/GuiMetadataCapability.cpp42
-rw-r--r--src/plugins/capabilities/navigation/include/NavigationCapability.h52
-rw-r--r--src/plugins/capabilities/navigation/include/NavigationMessages.h48
-rw-r--r--src/plugins/capabilities/navigation/src/NavigationCapability.cpp42
-rw-r--r--src/plugins/capabilities/test/CapabilityMessagingServiceTest.cpp96
-rw-r--r--src/plugins/capabilities/test/PublisherForwarderTest.cpp116
-rw-r--r--src/plugins/capabilities/test/SubscriberForwarderTest.cpp262
-rw-r--r--src/plugins/cmake/gtest.cmake44
-rw-r--r--src/plugins/core/VRRequestProcessor.h75
-rw-r--r--src/plugins/core/VRRequestProcessorImpl.cpp72
-rw-r--r--src/plugins/core/include/VRAgentsObserver.h67
-rw-r--r--src/plugins/core/include/VRRequest.h80
-rw-r--r--src/plugins/core/include/VRRequestProcessorDelegate.h88
-rw-r--r--src/plugins/core/src/VRAgentsObserverImpl.cpp54
-rw-r--r--src/plugins/core/src/VRRequestImpl.cpp92
-rw-r--r--src/plugins/core/src/VRRequestProcessorDelegateImpl.cpp93
-rw-r--r--src/plugins/core/test/VRRequestProcessorTest.cpp213
-rw-r--r--src/plugins/core/test/VRRequestTest.cpp101
-rw-r--r--src/plugins/interfaces/afb/IAFBApi.h98
-rw-r--r--src/plugins/interfaces/capabilities/ICapability.h58
-rw-r--r--src/plugins/interfaces/utilities/events/IEventFilter.h46
-rw-r--r--src/plugins/interfaces/utilities/logging/ILogger.h42
-rw-r--r--src/plugins/interfaces/voiceagents/IVoiceAgent.h95
-rw-r--r--src/plugins/interfaces/voiceagents/IVoiceAgentsChangeObserver.h80
-rw-r--r--src/plugins/test/common/ConsoleLogger.cpp31
-rw-r--r--src/plugins/test/common/ConsoleLogger.h37
-rw-r--r--src/plugins/test/mocks/AFBApiMock.h41
-rw-r--r--src/plugins/test/mocks/AFBEventMock.h47
-rw-r--r--src/plugins/test/mocks/AFBRequestMock.h33
-rw-r--r--src/plugins/test/mocks/CapabilityMock.h35
-rw-r--r--src/plugins/test/mocks/VoiceAgentsChangeObserverMock.h38
-rw-r--r--src/plugins/utilities/events/EventRouter.cpp68
-rw-r--r--src/plugins/utilities/events/EventRouter.h66
-rw-r--r--src/plugins/utilities/logging/Logger.cpp56
-rw-r--r--src/plugins/utilities/logging/Logger.h53
-rw-r--r--src/plugins/utilities/uuid/UUIDGeneration.cpp137
-rw-r--r--src/plugins/utilities/uuid/UUIDGeneration.h42
-rw-r--r--src/plugins/voiceagents/VoiceAgentEventNames.h38
-rw-r--r--src/plugins/voiceagents/VoiceAgentsDataManager.h135
-rw-r--r--src/plugins/voiceagents/VoiceAgentsDataManagerImpl.cpp272
-rw-r--r--src/plugins/voiceagents/include/VoiceAgent.h95
-rw-r--r--src/plugins/voiceagents/include/VoiceAgentEventsHandler.h95
-rw-r--r--src/plugins/voiceagents/src/VoiceAgentEventsHandler.cpp139
-rw-r--r--src/plugins/voiceagents/src/VoiceAgentImpl.cpp126
-rw-r--r--src/plugins/voiceagents/test/VoiceAgentTest.cpp94
-rw-r--r--src/plugins/voiceagents/test/VoiceAgentsDataManagerTest.cpp294
-rw-r--r--src/plugins/voiceagents/test/VoiceAgentsTestData.h67
-rw-r--r--src/vshl-apidef.h43
-rw-r--r--src/vshl-apidef.json109
-rw-r--r--src/vshl-binding.c116
-rw-r--r--src/vshl-binding.h25
-rwxr-xr-xtools/pre-commit140
102 files changed, 8596 insertions, 0 deletions
diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..5752e40
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,102 @@
+---
+Language: Cpp
+BasedOnStyle: Google
+AccessModifierOffset: -4
+AlignAfterOpenBracket: AlwaysBreak
+#AlignConsecutiveAssignments: false
+#AlignConsecutiveDeclarations: false
+#AlignEscapedNewlines: Left
+#AlignOperands: true
+#AlignTrailingComments: true
+AllowAllParametersOfDeclarationOnNextLine: false
+#AllowShortBlocksOnASingleLine: false
+#AllowShortCaseLabelsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: None
+#AllowShortIfStatementsOnASingleLine: true
+#AllowShortLoopsOnASingleLine: true
+#AlwaysBreakAfterDefinitionReturnType: None
+#AlwaysBreakAfterReturnType: None
+#AlwaysBreakBeforeMultilineStrings: true
+#AlwaysBreakTemplateDeclarations: true
+BinPackArguments: false
+BinPackParameters: false
+#BraceWrapping:
+# AfterClass: false
+# AfterControlStatement: false
+# AfterEnum: false
+# AfterFunction: false
+# AfterNamespace: false
+# AfterObjCDeclaration: false
+# AfterStruct: false
+# AfterUnion: false
+# BeforeCatch: false
+# BeforeElse: false
+# IndentBraces: false
+# SplitEmptyFunctionBody: true
+#BreakBeforeBinaryOperators: None
+#BreakBeforeBraces: Attach
+BreakBeforeInheritanceComma: true
+#BreakBeforeTernaryOperators: true
+#BreakConstructorInitializersBeforeComma: false
+BreakConstructorInitializers: AfterColon
+#BreakAfterJavaFieldAnnotations: false
+#BreakStringLiterals: true
+ColumnLimit: 120
+#CommentPragmas: '^ IWYU pragma:'
+#CompactNamespaces: false
+#ConstructorInitializerAllOnOneLineOrOnePerLine: true
+ConstructorInitializerIndentWidth: 8
+#ContinuationIndentWidth: 4
+#Cpp11BracedListStyle: true
+DerivePointerAlignment: false
+#DisableFormat: false
+#ExperimentalAutoDetectBinPacking: false
+#FixNamespaceComments: true
+#ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ]
+#IncludeCategories:
+# - Regex: '^<.*\.h>'
+# Priority: 1
+# - Regex: '^<.*'
+# Priority: 2
+# - Regex: '.*'
+# Priority: 3
+#IncludeIsMainRegex: '([-_](test|unittest))?$'
+#IndentCaseLabels: true
+IndentWidth: 4
+#IndentWrappedFunctionNames: false
+#JavaScriptQuotes: Leave
+#JavaScriptWrapImports: true
+#KeepEmptyLinesAtTheStartOfBlocks: false
+#MacroBlockBegin: ''
+#MacroBlockEnd: ''
+#MaxEmptyLinesToKeep: 1
+#NamespaceIndentation: None
+#ObjCBlockIndentWidth: 2
+#ObjCSpaceAfterProperty: false
+#ObjCSpaceBeforeProtocolList: false
+#PenaltyBreakAssignment: 2
+#PenaltyBreakBeforeFirstCallParameter: 1
+#PenaltyBreakComment: 300
+#PenaltyBreakFirstLessLess: 120
+#PenaltyBreakString: 1000
+#PenaltyExcessCharacter: 1000000
+PenaltyReturnTypeOnItsOwnLine: 20000
+#PointerAlignment: Left
+#ReflowComments: true
+SortIncludes: false
+#SpaceAfterCStyleCast: false
+#SpaceAfterTemplateKeyword: true
+#SpaceBeforeAssignmentOperators: true
+#SpaceBeforeParens: ControlStatements
+#SpaceInEmptyParentheses: false
+#SpacesBeforeTrailingComments: 2
+#SpacesInAngles: false
+#SpacesInContainerLiterals: true
+#SpacesInCStyleCastParentheses: false
+#SpacesInParentheses: false
+#SpacesInSquareBrackets: false
+#Standard: Auto
+#TabWidth: 8
+#UseTab: Never
+...
+
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..567609b
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+build/
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..9b45c19
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,6 @@
+[submodule "afb-helpers"]
+ path = afb-helpers
+ url = https://gerrit.automotivelinux.org/gerrit/apps/app-afb-helpers-submodule
+[submodule "app-controller"]
+ path = app-controller
+ url = https://gerrit.automotivelinux.org/gerrit/apps/app-controller-submodule
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..f39acc4
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,21 @@
+###########################################################################
+# Copyright 2018 IoT.bzh
+#
+# author: Romain Forlot <romain.forlot@iot.bzh>
+#
+# 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.
+###########################################################################
+
+CMAKE_MINIMUM_REQUIRED(VERSION 3.5.1)
+
+include(${CMAKE_CURRENT_SOURCE_DIR}/conf.d/cmake/config.cmake)
diff --git a/License.txt b/License.txt
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/License.txt
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..8d814c9
--- /dev/null
+++ b/README.md
@@ -0,0 +1,95 @@
+# 1. High Level Voice Service (VSHL)
+This repository hosts the code for the AGL's high level voice service binding also known as VSHL.
+Please refer to the [architecture](https://confluence.automotivelinux.org/display/SPE/Speech+EG+Architecture) for more information.
+
+# 2. Build Dependencies and License Information
+
+During the build time, the following dependencies are fetched and run by the build system. Please refer to each of the individual entities for the particular licenses.
+* [Google Test v1.8.0](https://github.com/google/googletest) when compiled with ENABLE_UNIT_TESTS option.
+
+# 3. Getting the Source Code
+```
+export MY_PROJECTS_DIR = <Your Project Directory>
+pushd $MY_PROJECTS_DIR
+git clone --recursive https://gerrit.automotivelinux.org/gerrit/apps/agl-service-voice-high
+```
+
+# 4. Renesas R-Car M3 board
+## 4.1 Building VSHL
+
+```
+pushd agl-voiceservice-highlevel
+mkdir build
+pushd build
+source /opt/agl-sdk/6.0.1-aarch64/environment-setup-aarch64-agl-linux
+cmake ..
+make autobuild
+popd
+./conf.d/autobuild/agl/autobuild package
+```
+* The build output will be located at $MY_PROJECTS_DIR/agl-voiceservice-highlevel/build/vshl.wgt
+
+## 4.2 Running VSHL
+```
+# afm-util install vshl.wgt
+# afm-util start vshl@1.0
+```
+
+# 5. Ubuntu 16.04
+## 5.1 Building VSHL
+
+```
+pushd agl-voiceservice-highlevel/
+mkdir build
+pushd build
+cmake ..
+make autobuild
+popd
+./conf.d/autobuild/linux/autobuild package
+````
+To build the included unit tests modify the cmake step as following:
+cmake .. -DENABLE_UNIT_TESTS=ON
+
+## 5.2 Running VSHL
+```
+afb-daemon --port=1111 --name=afb-vshl --workdir=$MY_PROJECTS_DIR/agl-voice-service/build/package --ldpaths=lib --roothttp=htdocs --token= -vvv
+```
+
+# 6. Running the Unit Tests
+## 6.1 Ubuntu 16.04
+```
+pushd agl-voiceservice-highlevel/
+./build/src/plugins/vshl-api_Test
+popd
+```
+
+# 7. Testing VSHL
+* The binding can be tested by launching the HTML5 sample application that is bundled with the package in a browser.
+
+```
+http://localhost:1111
+```
+
+# 8. Contributing code
+Before contributing the source, its recommended to format the code with clang-format. This is done automatically during commit step if following instructions are followed.
+**Prerequisite**: Install clang-format-6.0 or greater.
+There are following 2 options.
+
+* Before commit, manually run clang-format on the file (It will use local .clang-format file for checking the rules)
+```
+clang-format -i <path to source file>
+```
+
+* Setup clang-format as pre-commit git hook. This is one time step after you clone the repo
+```
+cd ${VSHL_ROOT}
+cp tools/pre-commit .git/hooks/pre-commit
+```
+
+* With the hook in place, everytime you try to commit, it will check the format and disallow commit if format doesn't abide by the .clang-format rules.
+It will also give you the option to apply a patch (it creates a patch in /tmp folder) to make the source abide by the .clang-format rules. Apply the patch and proceed to commit
+```
+git apply /tmp/<patch>
+git add <source files>
+git commit
+``` \ No newline at end of file
diff --git a/afb-helpers b/afb-helpers
new file mode 160000
+Subproject f0ce5b665dd33b285d723720c16ac0542cde4e6
diff --git a/app-controller b/app-controller
new file mode 160000
+Subproject 33abde52666af1335571252143d21de5d305ca9
diff --git a/conf.d/CMakeLists.txt b/conf.d/CMakeLists.txt
new file mode 100644
index 0000000..3beb009
--- /dev/null
+++ b/conf.d/CMakeLists.txt
@@ -0,0 +1,20 @@
+###########################################################################
+# Copyright 2015, 2016, 2017 IoT.bzh
+#
+# author: Fulup Ar Foll <rfulup@iot.bzh>
+#
+# 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.
+###########################################################################
+
+# This component should be included as a submodule and wont compile as standalone
+project_subdirs_add()
diff --git a/conf.d/autobuild/agl/autobuild b/conf.d/autobuild/agl/autobuild
new file mode 100755
index 0000000..83097ab
--- /dev/null
+++ b/conf.d/autobuild/agl/autobuild
@@ -0,0 +1,67 @@
+#!/usr/bin/make -f
+# Copyright (C) 2015, 2016 "IoT.bzh"
+# Author "Romain Forlot" <romain.forlot@iot.bzh>
+#
+# 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.
+
+THISFILE := $(lastword $(MAKEFILE_LIST))
+BUILD_DIR := $(abspath $(dir $(THISFILE)/../../../../..)/build)
+DEST := ${BUILD_DIR}/target
+
+.PHONY: all clean distclean configure build package help update
+
+all: help
+
+help:
+ @echo "List of targets available:"
+ @echo ""
+ @echo "- all"
+ @echo "- clean"
+ @echo "- distclean"
+ @echo "- configure"
+ @echo "- build: compilation, link and prepare files for package into a widget"
+ @echo "- package: output a widget file '*.wgt'"
+ @echo "- install: install in your ${CMAKE_INSTALL_DIR} directory"
+ @echo ""
+ @echo "Usage: ./conf.d/autobuild/agl/autobuild package DEST=${HOME}/opt"
+ @echo "Don't use your build dir as DEST as wgt file is generated at this location"
+
+update: configure
+ @cmake --build ${BUILD_DIR} --target autobuild
+
+clean:
+ @([ -d ${BUILD_DIR} ] && make -C ${BUILD_DIR} clean) || echo Nothing to clean
+
+distclean:
+ @rm -rf ${BUILD_DIR}
+
+configure: ${BUILD_DIR}/Makefile
+
+build: configure
+ @cmake --build ${BUILD_DIR} ${BUILD_ARGS} --target all
+
+package: build
+ @mkdir -p ${BUILD_DIR}/$@/bin
+ @mkdir -p ${BUILD_DIR}/$@/etc
+ @mkdir -p ${BUILD_DIR}/$@/lib
+ @mkdir -p ${BUILD_DIR}/$@/htdocs
+ @mkdir -p ${BUILD_DIR}/$@/var
+ @cmake --build ${BUILD_DIR} --target widget
+ @mkdir -p ${DEST} && cp ${BUILD_DIR}/*wgt ${DEST}
+
+install: build
+ @cmake --build ${BUILD_DIR} --target install
+
+${BUILD_DIR}/Makefile:
+ @[ -d ${BUILD_DIR} ] || mkdir -p ${BUILD_DIR}
+ @[ -f ${BUILD_DIR}/Makefile ] || (cd ${BUILD_DIR} && cmake ${CONFIGURE_ARGS} ..)
diff --git a/conf.d/autobuild/linux/autobuild b/conf.d/autobuild/linux/autobuild
new file mode 100755
index 0000000..83097ab
--- /dev/null
+++ b/conf.d/autobuild/linux/autobuild
@@ -0,0 +1,67 @@
+#!/usr/bin/make -f
+# Copyright (C) 2015, 2016 "IoT.bzh"
+# Author "Romain Forlot" <romain.forlot@iot.bzh>
+#
+# 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.
+
+THISFILE := $(lastword $(MAKEFILE_LIST))
+BUILD_DIR := $(abspath $(dir $(THISFILE)/../../../../..)/build)
+DEST := ${BUILD_DIR}/target
+
+.PHONY: all clean distclean configure build package help update
+
+all: help
+
+help:
+ @echo "List of targets available:"
+ @echo ""
+ @echo "- all"
+ @echo "- clean"
+ @echo "- distclean"
+ @echo "- configure"
+ @echo "- build: compilation, link and prepare files for package into a widget"
+ @echo "- package: output a widget file '*.wgt'"
+ @echo "- install: install in your ${CMAKE_INSTALL_DIR} directory"
+ @echo ""
+ @echo "Usage: ./conf.d/autobuild/agl/autobuild package DEST=${HOME}/opt"
+ @echo "Don't use your build dir as DEST as wgt file is generated at this location"
+
+update: configure
+ @cmake --build ${BUILD_DIR} --target autobuild
+
+clean:
+ @([ -d ${BUILD_DIR} ] && make -C ${BUILD_DIR} clean) || echo Nothing to clean
+
+distclean:
+ @rm -rf ${BUILD_DIR}
+
+configure: ${BUILD_DIR}/Makefile
+
+build: configure
+ @cmake --build ${BUILD_DIR} ${BUILD_ARGS} --target all
+
+package: build
+ @mkdir -p ${BUILD_DIR}/$@/bin
+ @mkdir -p ${BUILD_DIR}/$@/etc
+ @mkdir -p ${BUILD_DIR}/$@/lib
+ @mkdir -p ${BUILD_DIR}/$@/htdocs
+ @mkdir -p ${BUILD_DIR}/$@/var
+ @cmake --build ${BUILD_DIR} --target widget
+ @mkdir -p ${DEST} && cp ${BUILD_DIR}/*wgt ${DEST}
+
+install: build
+ @cmake --build ${BUILD_DIR} --target install
+
+${BUILD_DIR}/Makefile:
+ @[ -d ${BUILD_DIR} ] || mkdir -p ${BUILD_DIR}
+ @[ -f ${BUILD_DIR}/Makefile ] || (cd ${BUILD_DIR} && cmake ${CONFIGURE_ARGS} ..)
diff --git a/conf.d/cmake/00-debian-osconfig.cmake b/conf.d/cmake/00-debian-osconfig.cmake
new file mode 100644
index 0000000..2ce0ad3
--- /dev/null
+++ b/conf.d/cmake/00-debian-osconfig.cmake
@@ -0,0 +1 @@
+list(APPEND PKG_REQUIRED_LIST lua-5.3>=5.3)
diff --git a/conf.d/cmake/00-default-osconfig.cmake b/conf.d/cmake/00-default-osconfig.cmake
new file mode 100644
index 0000000..a2b9325
--- /dev/null
+++ b/conf.d/cmake/00-default-osconfig.cmake
@@ -0,0 +1 @@
+list(APPEND PKG_REQUIRED_LIST lua>=5.3)
diff --git a/conf.d/cmake/config.cmake b/conf.d/cmake/config.cmake
new file mode 100644
index 0000000..1059bb1
--- /dev/null
+++ b/conf.d/cmake/config.cmake
@@ -0,0 +1,223 @@
+###########################################################################
+# Copyright 2018 IoT.bzh
+#
+# author: Sebastien Douheret <sebastien@iot.bzh>
+#
+# 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.
+###########################################################################
+
+# Project Info
+# ------------------
+set(PROJECT_NAME vshl)
+set(PROJECT_VERSION "1.0")
+set(PROJECT_PRETTY_NAME "High Level Voice Service APIs")
+set(PROJECT_DESCRIPTION "Binding that provide voice services to AGL apps.")
+set(PROJECT_ICON "icon.png")
+set(PROJECT_AUTHOR "Naveen Bobbili")
+set(PROJECT_AUTHOR_MAIL "nbobbili@amazon.com")
+set(PROJECT_LICENSE "APL2.0")
+set(PROJECT_LANGUAGES "CXX")
+
+# Where are stored the project configuration files
+# relative to the root project directory
+set(PROJECT_CMAKE_CONF_DIR "conf.d")
+
+# Where are stored your external libraries for your project. This is 3rd party library that you don't maintain
+# but used and must be built and linked.
+# set(PROJECT_LIBDIR "libs")
+
+# Which directories inspect to find CMakeLists.txt target files
+# set(PROJECT_SRC_DIR_PATTERN "*")
+
+# Compilation Mode (DEBUG, RELEASE)
+# ----------------------------------
+set(CMAKE_BUILD_TYPE "RELEASE")
+#set(USE_EFENCE 1)
+
+# Helpers Submodule parameters
+# set(AFB_HELPERS_QTWSCLIENT OFF CACHE BOOL "Adds QT5 WebSocket helpers from submodule")
+
+# Kernel selection if needed. You can choose between a
+# mandatory version to impose a minimal version.
+# Or check Kernel minimal version and just print a Warning
+# about missing features and define a preprocessor variable
+# to be used as preprocessor condition in code to disable
+# incompatibles features. Preprocessor define is named
+# KERNEL_MINIMAL_VERSION_OK.
+#
+# NOTE*** FOR NOW IT CHECKS KERNEL Yocto environment and
+# Yocto SDK Kernel version.
+# -----------------------------------------------
+#set (kernel_mandatory_version 4.8)
+#set (kernel_minimal_version 4.8)
+
+# Compiler selection if needed. Impose a minimal version.
+# -----------------------------------------------
+set (gcc_minimal_version 4.9)
+
+# PKG_CONFIG required packages
+# -----------------------------
+set (PKG_REQUIRED_LIST
+ json-c
+ libsystemd>=222
+ afb-daemon
+ libmicrohttpd>=0.9.55
+)
+
+# Prefix path where will be installed the files
+# Default: /usr/local (need root permission to write in)
+# ------------------------------------------------------
+#set(CMAKE_INSTALL_PREFIX $ENV{HOME}/opt)
+
+# Customize link option
+# -----------------------------
+#list(APPEND link_libraries -an-option)
+
+# Compilation options definition
+# Use CMake generator expressions to specify only for a specific language
+# Values are prefilled with default options that is currently used.
+# Either separate options with ";", or each options must be quoted separately
+# DO NOT PUT ALL OPTION QUOTED AT ONCE , COMPILATION COULD FAILED !
+# ----------------------------------------------------------------------------
+set(COMPILE_OPTIONS
+ -Wno-missing-field-initializers
+ -Wno-format-security
+# -Wall
+# -Wextra
+# -Wconversion
+# -Wno-unused-parameter
+# -Wno-sign-compare
+# -Wno-sign-conversion
+# -Werror=maybe-uninitialized
+# -Werror=implicit-function-declaration
+# -ffunction-sections
+# -fdata-sections
+# -fPIC
+ CACHE STRING "Compilation flags")
+#set(C_COMPILE_OPTIONS "" CACHE STRING "Compilation flags for C language.")
+#set(CXX_COMPILE_OPTIONS "" CACHE STRING "Compilation flags for C++ language.")
+#set(PROFILING_COMPILE_OPTIONS
+# -g
+# -O0
+# -pg
+# -Wp,-U_FORTIFY_SOURCE
+# CACHE STRING "Compilation flags for PROFILING build type.")
+#set(DEBUG_COMPILE_OPTIONS
+# -g
+# -ggdb
+# -Wp,-U_FORTIFY_SOURCE
+# CACHE STRING "Compilation flags for DEBUG build type.")
+#set(CCOV_COMPILE_OPTIONS
+# -g
+# -O2
+# --coverage
+# CACHE STRING "Compilation flags for CCOV build type.")
+#set(RELEASE_COMPILE_OPTIONS
+# -g
+# -O2
+# CACHE STRING "Compilation flags for RELEASE build type.")
+
+set(CONTROL_SUPPORT_LUA 1)
+# Search Paths: Build directory -> CMake installation path -> AFM installation path
+add_definitions(-DCONTROL_PLUGIN_PATH="${CMAKE_BINARY_DIR}/package/lib/plugins:${CMAKE_INSTALL_PREFIX}/${PROJECT_NAME}/lib/plugins:/var/local/lib/afm/applications/${PROJECT_NAME}/${PROJECT_VERSION}/lib/plugins")
+add_definitions(-DCONTROL_CONFIG_PATH="${CMAKE_BINARY_DIR}/package/etc:${CMAKE_INSTALL_PREFIX}/${PROJECT_NAME}:/var/local/lib/afm/applications/${PROJECT_NAME}")
+#add_definitions(-DCONTROL_LUA_PATH="${CMAKE_SOURCE_DIR}/conf.d/project/lua.d:${CMAKE_INSTALL_PREFIX}/${PROJECT_NAME}/var:${CMAKE_INSTALL_PREFIX}/${PROJECT_NAME}")
+add_definitions(-DCTL_PLUGIN_MAGIC=1286576532)
+add_definitions(-DUSE_API_DYN=1 -DAFB_BINDING_VERSION=3 -DAFB_BINDING_WANT_DYNAPI)
+
+# (BUG!!!) as PKG_CONFIG_PATH does not work [should be an env variable]
+# ---------------------------------------------------------------------
+#set(CMAKE_PREFIX_PATH ${CMAKE_INSTALL_PREFIX}/lib64/pkgconfig ${CMAKE_INSTALL_PREFIX}/lib/pkgconfig)
+#set(LD_LIBRARY_PATH ${CMAKE_INSTALL_PREFIX}/lib64 ${CMAKE_INSTALL_PREFIX}/lib)
+
+# Optional location for config.xml.in
+# -----------------------------------
+#set(WIDGET_ICON "\"conf.d/wgt/${PROJECT_ICON}\"" CACHE PATH "Path to the widget icon")
+set(WIDGET_CONFIG_TEMPLATE "${CMAKE_SOURCE_DIR}/conf.d/wgt/config.xml.in" CACHE PATH "Path to widget config file template (config.xml.in)")
+
+# Mandatory widget Mimetype specification of the main unit
+# --------------------------------------------------------------------------
+# Choose between :
+#- text/html : HTML application,
+# content.src designates the home page of the application
+#
+#- application/vnd.agl.native : AGL compatible native,
+# content.src designates the relative path of the binary.
+#
+# - application/vnd.agl.service: AGL service, content.src is not used.
+#
+#- ***application/x-executable***: Native application,
+# content.src designates the relative path of the binary.
+# For such application, only security setup is made.
+#
+set(WIDGET_TYPE application/vnd.agl.service)
+
+# Mandatory Widget entry point file of the main unit
+# --------------------------------------------------------------
+# This is the file that will be executed, loaded,
+# at launch time by the application framework.
+#
+set(WIDGET_ENTRY_POINT lib/afb-vshl.so)
+
+# Optional dependencies order
+# ---------------------------
+#set(EXTRA_DEPENDENCIES_ORDER)
+
+# Optional Extra global include path
+# -----------------------------------
+#set(EXTRA_INCLUDE_DIRS)
+
+# Optional extra libraries
+# -------------------------
+#set(EXTRA_LINK_LIBRARIES)
+
+# Optional force binding installation
+# ------------------------------------
+# set(BINDINGS_INSTALL_PREFIX PrefixPath )
+
+# Optional force binding Linking flag
+# ------------------------------------
+# set(BINDINGS_LINK_FLAG LinkOptions )
+
+# Optional force package prefix generation, like widget
+# -----------------------------------------------------
+# set(PKG_PREFIX DestinationPath)
+
+# Optional Application Framework security token
+# and port use for remote debugging.
+#------------------------------------------------------------
+set(AFB_TOKEN "" CACHE PATH "Default binder security token")
+set(AFB_REMPORT "1111" CACHE PATH "Default binder listening port")
+
+# Print a helper message when every thing is finished
+# ----------------------------------------------------
+set(CLOSING_MESSAGE "Typical binding launch: \
+afb-daemon --port=${AFB_REMPORT} --name=afb-speech --workdir=${CMAKE_BINARY_DIR}/package \
+--ldpaths=lib --roothttp=htdocs --token=\"${AFB_TOKEN}\" -vvv")
+
+set(PACKAGE_MESSAGE "Install widget file using in the target : afm-util install ${PROJECT_NAME}.wgt")
+
+# Optional schema validator about now only XML, LUA and JSON
+# are supported
+#------------------------------------------------------------
+#set(LUA_CHECKER "luac" "-p" CACHE STRING "LUA compiler")
+#set(XML_CHECKER "xmllint" CACHE STRING "XML linter")
+#set(JSON_CHECKER "json_verify" CACHE STRING "JSON linter")
+
+# This include is mandatory and MUST happens at the end
+# of this file, else you expose you to unexpected behavior
+#
+# This CMake module could be found at the following url:
+# https://gerrit.automotivelinux.org/gerrit/#/admin/projects/src/cmake-apps-module
+# -----------------------------------------------------------
+include(CMakeAfbTemplates) \ No newline at end of file
diff --git a/conf.d/project/CMakeLists.txt b/conf.d/project/CMakeLists.txt
new file mode 100644
index 0000000..3beb009
--- /dev/null
+++ b/conf.d/project/CMakeLists.txt
@@ -0,0 +1,20 @@
+###########################################################################
+# Copyright 2015, 2016, 2017 IoT.bzh
+#
+# author: Fulup Ar Foll <rfulup@iot.bzh>
+#
+# 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.
+###########################################################################
+
+# This component should be included as a submodule and wont compile as standalone
+project_subdirs_add()
diff --git a/conf.d/project/etc/CMakeLists.txt b/conf.d/project/etc/CMakeLists.txt
new file mode 100644
index 0000000..3aacae6
--- /dev/null
+++ b/conf.d/project/etc/CMakeLists.txt
@@ -0,0 +1,32 @@
+###########################################################################
+# Copyright 2017 IoT.bzh
+#
+# author: Fulup Ar Foll <fulup@iot.bzh>
+#
+# 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.
+###########################################################################
+
+##################################################
+# Control Policy Config file
+##################################################
+PROJECT_TARGET_ADD(vshl-api-config)
+
+ file(GLOB CONF_FILES "*.json")
+
+ add_input_files("${CONF_FILES}")
+
+ SET_TARGET_PROPERTIES(
+ ${TARGET_NAME} PROPERTIES
+ LABELS "BINDING-CONFIG"
+ OUTPUT_NAME ${TARGET_NAME}
+ ) \ No newline at end of file
diff --git a/conf.d/project/etc/vshl-api.json b/conf.d/project/etc/vshl-api.json
new file mode 100644
index 0000000..3f92f76
--- /dev/null
+++ b/conf.d/project/etc/vshl-api.json
@@ -0,0 +1,96 @@
+{
+ "$schema": "http://iot.bzh/download/public/schema/json/ctl-schema.json",
+ "metadata": {
+ "uid": "vshl",
+ "version": "1.0",
+ "api": "vshl",
+ "info": "High Level Voice Service APIs"
+ },
+
+ "onload": [{
+ "uid": "loadVoiceAgentsConfig",
+ "info": "Loading the information about voice agents managed by the high level voice service.",
+ "action": "plugin://vshl#loadVoiceAgentsConfig",
+ "args": {
+ "default": "VA-001",
+ "agents": [
+ {
+ "id": "VA-001",
+ "active": true,
+ "name": "Alexa",
+ "api": "alexa-voiceagent",
+ "wakewords": [
+ "alexa",
+ "computer",
+ "echo"
+ ],
+ "activewakeword": "alexa",
+ "description": "Alexa voice assistant by Amazon.",
+ "vendor": "Amazon.com Services Inc"
+ }
+ ]
+ }
+ }],
+
+ "plugins": [{
+ "uid": "vshl",
+ "info": "Plugin to handle high level voice service interface implementation",
+ "libs": [
+ "vshl-api.ctlso"
+ ]
+ }],
+
+ "events": [{
+ "uid": "alexa-voiceagent/voice_authstate_event",
+ "action": "plugin://vshl#onAuthStateEvent"
+ },{
+ "uid": "alexa-voiceagent/voice_connectionstate_event",
+ "action": "plugin://vshl#onConnectionStateEvent"
+ },{
+ "uid": "alexa-voiceagent/voice_dialogstate_event",
+ "action": "plugin://vshl#onDialogStateEvent"
+ }],
+
+ "controls": [{
+ "uid": "startListening",
+ "action": "plugin://vshl#startListening"
+ }, {
+ "uid": "cancelListening",
+ "action": "plugin://vshl#cancelListening"
+ }, {
+ "uid": "subscribe",
+ "action": "plugin://vshl#subscribe"
+ }, {
+ "uid": "enumerateVoiceAgents",
+ "privileges": "urn:AGL:permission:vshl:voiceagents:public",
+ "action": "plugin://vshl#enumerateVoiceAgents"
+ }, {
+ "uid": "setDefaultVoiceAgent",
+ "privileges": "urn:AGL:permission:vshl:voiceagents:public",
+ "action": "plugin://vshl#setDefaultVoiceAgent"
+ }, {
+ "uid": "guiMetadata/publish",
+ "privileges": "urn:AGL:permission:vshl:guiMetadata:public",
+ "action": "plugin://vshl#guiMetadataPublish"
+ }, {
+ "uid": "guiMetadata/subscribe",
+ "privileges": "urn:AGL:permission:vshl:guiMetadata:public",
+ "action": "plugin://vshl#guiMetadataSubscribe"
+ }, {
+ "uid": "phonecontrol/publish",
+ "privileges": "urn:AGL:permission:vshl:phonecontrol:public",
+ "action": "plugin://vshl#phonecontrolPublish"
+ }, {
+ "uid": "phonecontrol/subscribe",
+ "privileges": "urn:AGL:permission:vshl:phonecontrol:public",
+ "action": "plugin://vshl#phonecontrolSubscribe"
+ }, {
+ "uid": "navigation/publish",
+ "privileges": "urn:AGL:permission:vshl:navigation:public",
+ "action": "plugin://vshl#navigationPublish"
+ }, {
+ "uid": "navigation/subscribe",
+ "privileges": "urn:AGL:permission:vshl:navigation:public",
+ "action": "plugin://vshl#navigationSubscribe"
+ }]
+} \ No newline at end of file
diff --git a/conf.d/wgt/config.xml.in b/conf.d/wgt/config.xml.in
new file mode 100644
index 0000000..e1efb32
--- /dev/null
+++ b/conf.d/wgt/config.xml.in
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<widget xmlns="http://www.w3.org/ns/widgets" id="@PROJECT_NAME@" version="@PROJECT_VERSION@">
+ <name>@PROJECT_NAME@</name>
+ <icon src="@PROJECT_ICON@"/>
+ <content src="@WIDGET_ENTRY_POINT@" type="@WIDGET_TYPE@"/>
+ <description>@PROJECT_DESCRIPTION@</description>
+ <author>@PROJECT_AUTHOR@ &lt;@PROJECT_AUTHOR_MAIL@&gt;</author>
+ <license>@PROJECT_LICENSE@</license>
+ <feature name="urn:AGL:widget:provided-api">
+ <param name="vshl" value="ws" />
+ </feature>
+ <feature name="urn:AGL:widget:required-binding">
+ <param name="@WIDGET_ENTRY_POINT@" value="local" />
+ </feature>
+</widget>
diff --git a/htdocs/AFB-websock.js b/htdocs/AFB-websock.js
new file mode 100644
index 0000000..3d4831f
--- /dev/null
+++ b/htdocs/AFB-websock.js
@@ -0,0 +1,177 @@
+var urlWS;
+var urlhttp;
+
+AFB = function(base, initialtoken){
+
+urlWS = "ws://"+window.location.host+"/"+base;
+urlhttp = "http://"+window.location.host+"/"+base;
+
+/*********************************************/
+/**** ****/
+/**** AFB_context ****/
+/**** ****/
+/*********************************************/
+var AFB_context;
+{
+ var UUID = undefined;
+ var TOKEN = initialtoken;
+
+ var context = function(token, uuid) {
+ this.token = token;
+ this.uuid = uuid;
+ }
+
+ context.prototype = {
+ get token() {return TOKEN;},
+ set token(tok) {if(tok) TOKEN=tok;},
+ get uuid() {return UUID;},
+ set uuid(id) {if(id) UUID=id;}
+ };
+
+ AFB_context = new context();
+}
+/*********************************************/
+/**** ****/
+/**** AFB_websocket ****/
+/**** ****/
+/*********************************************/
+var AFB_websocket;
+{
+ var CALL = 2;
+ var RETOK = 3;
+ var RETERR = 4;
+ var EVENT = 5;
+
+ var PROTO1 = "x-afb-ws-json1";
+
+ AFB_websocket = function(onopen, onabort) {
+ var u = urlWS;
+ if (AFB_context.token) {
+ u = u + '?x-afb-token=' + AFB_context.token;
+ if (AFB_context.uuid)
+ u = u + '&x-afb-uuid=' + AFB_context.uuid;
+ }
+ this.ws = new WebSocket(u, [ PROTO1 ]);
+ this.pendings = {};
+ this.awaitens = {};
+ this.counter = 0;
+ this.ws.onopen = onopen.bind(this);
+ this.ws.onerror = onerror.bind(this);
+ this.ws.onclose = onclose.bind(this);
+ this.ws.onmessage = onmessage.bind(this);
+ this.onopen = onopen;
+ this.onabort = onabort;
+ this.onclose = onabort;
+ }
+
+ function onerror(event) {
+ var f = this.onabort;
+ if (f) {
+ delete this.onopen;
+ delete this.onabort;
+ f && f(this);
+ }
+ this.onerror && this.onerror(this);
+ }
+
+ function onopen(event) {
+ var f = this.onopen;
+ delete this.onopen;
+ delete this.onabort;
+ f && f(this);
+ }
+
+ function onclose(event) {
+ for (var id in this.pendings) {
+ var ferr = this.pendings[id].onerror;
+ ferr && ferr(null, this);
+ }
+ this.pendings = {};
+ this.onclose && this.onclose();
+ }
+
+ function fire(awaitens, name, data) {
+ var a = awaitens[name];
+ if (a)
+ a.forEach(function(handler){handler(data);});
+ var i = name.indexOf("/");
+ if (i >= 0) {
+ a = awaitens[name.substring(0,i)];
+ if (a)
+ a.forEach(function(handler){handler(data);});
+ }
+ a = awaitens["*"];
+ if (a)
+ a.forEach(function(handler){handler(data);});
+ }
+
+ function reply(pendings, id, ans, offset) {
+ if (id in pendings) {
+ var p = pendings[id];
+ delete pendings[id];
+ var f = p[offset];
+ f(ans);
+ }
+ }
+
+ function onmessage(event) {
+ var obj = JSON.parse(event.data);
+ var code = obj[0];
+ var id = obj[1];
+ var ans = obj[2];
+ AFB_context.token = obj[3];
+ switch (code) {
+ case RETOK:
+ reply(this.pendings, id, ans, 0);
+ break;
+ case RETERR:
+ reply(this.pendings, id, ans, 1);
+ break;
+ case EVENT:
+ default:
+ fire(this.awaitens, id, ans);
+ break;
+ }
+ }
+
+ function close() {
+ this.ws.close();
+ this.onabort();
+ }
+
+ function call(method, request) {
+ return new Promise((function(resolve, reject){
+ var id, arr;
+ do {
+ id = String(this.counter = 4095 & (this.counter + 1));
+ } while (id in this.pendings);
+ this.pendings[id] = [ resolve, reject ];
+ arr = [CALL, id, method, request ];
+ if (AFB_context.token) arr.push(AFB_context.token);
+ this.ws.send(JSON.stringify(arr));
+ }).bind(this));
+ }
+
+ function onevent(name, handler) {
+ var id = name;
+ var list = this.awaitens[id] || (this.awaitens[id] = []);
+ list.push(handler);
+ }
+
+ AFB_websocket.prototype = {
+ close: close,
+ call: call,
+ onevent: onevent
+ };
+}
+/*********************************************/
+/**** ****/
+/**** ****/
+/**** ****/
+/*********************************************/
+return {
+ context: AFB_context,
+ ws: AFB_websocket
+};
+};
+
diff --git a/htdocs/CMakeLists.txt b/htdocs/CMakeLists.txt
new file mode 100644
index 0000000..7aa0ce1
--- /dev/null
+++ b/htdocs/CMakeLists.txt
@@ -0,0 +1,33 @@
+###########################################################################
+# Copyright 2015, 2016, 2017 IoT.bzh
+#
+# author: Fulup Ar Foll <fulup@iot.bzh>
+#
+# 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.
+###########################################################################
+
+
+
+##################################################
+# HTML Testing Files
+##################################################
+PROJECT_TARGET_ADD(htdocs)
+
+ file(GLOB SOURCE_FILES "*.html" "*.js" "*.jpg" "*.css" "assets")
+
+ add_input_files("${SOURCE_FILES}")
+
+ SET_TARGET_PROPERTIES(${TARGET_NAME} PROPERTIES
+ LABELS "HTDOCS"
+ OUTPUT_NAME ${TARGET_NAME}
+ )
diff --git a/htdocs/binding.css b/htdocs/binding.css
new file mode 100644
index 0000000..99f84b4
--- /dev/null
+++ b/htdocs/binding.css
@@ -0,0 +1,99 @@
+body.page-content {
+ height: 100%;
+ width: auto;
+ margin-top: 20px;
+ background-size: cover;
+ background-position: center;
+}
+
+img {
+ float: right;
+}
+
+ol {
+ display: flex;
+ flex-direction: column;
+}
+
+#question,
+#output,
+#outevt {
+ white-space: pre-wrap;
+}
+
+div.row {
+ display: flex;
+ flex-direction: row;
+}
+
+div.col1 {
+ flex-basis: 0;
+ flex-grow: 1;
+ width: 100%;
+}
+
+div.col2 {
+ flex-basis: 0;
+ flex-grow: 1;
+ width: 30ch;
+}
+
+button {
+ margin-right: 10px;
+ padding: 6px 8px;
+ font-size: large;
+}
+
+pre {
+ outline: 1px solid #ccc;
+ padding: 5px;
+ margin: 5px;
+ background-color: white;
+ opacity: 0.85;
+ min-height: 5pc;
+ max-height: 5pc;
+ overflow: auto;
+}
+
+.string {
+ color: green;
+}
+
+.number {
+ color: darkorange;
+}
+
+.boolean {
+ color: blue;
+}
+
+.null {
+ color: magenta;
+}
+
+.key {
+ color: red;
+}
+
+dialog {
+ padding: 0;
+ border: 0;
+ border-radius: 0.6rem;
+ box-shadow: 0 0 1em black;
+}
+
+dialog::backdrop {
+ /* make the backdrop a semi-transparent black */
+ background-color: rgba(0, 0, 0, 0.4);
+}
+
+h3.dialogheader {
+ background-color: rgb(177, 177, 236);
+ padding: 1ch;
+}
+
+footer {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+} \ No newline at end of file
diff --git a/htdocs/binding.js b/htdocs/binding.js
new file mode 100644
index 0000000..c24d62e
--- /dev/null
+++ b/htdocs/binding.js
@@ -0,0 +1,226 @@
+var afb = new AFB("api", "mysecret");
+var ws;
+var evtIdx = 0;
+var count = 0;
+
+
+//**********************************************
+// Logger
+//**********************************************
+var log = {
+ command: function (api, verb, query) {
+ console.log("subscribe api=" + api + " verb=" + verb + " query=", query);
+ var question = urlWS + "/" + api + "/" + verb + "?query=" + JSON.stringify(query);
+ log._write("question", count + ": " + log.syntaxHighlight(question));
+ },
+
+ event: function (obj) {
+ console.log("gotevent:" + JSON.stringify(obj));
+ log._write("outevt", (evtIdx++) + ": " + JSON.stringify(obj));
+ },
+
+ reply: function (obj) {
+ console.log("replyok:" + JSON.stringify(obj));
+ log._write("output", count + ": OK: " + log.syntaxHighlight(obj));
+ },
+
+ error: function (obj) {
+ console.log("replyerr:" + JSON.stringify(obj));
+ log._write("output", count + ": ERROR: " + log.syntaxHighlight(obj));
+ },
+
+ _write: function (element, msg) {
+ var el = document.getElementById(element);
+ el.innerHTML += msg + '\n';
+
+ // auto scroll down
+ setTimeout(function () {
+ el.scrollTop = el.scrollHeight;
+ }, 100);
+ },
+
+ syntaxHighlight: function (json) {
+ if (typeof json !== 'string') {
+ json = JSON.stringify(json, undefined, 2);
+ }
+ json = json.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
+ return json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match) {
+ var cls = 'number';
+ if (/^"/.test(match)) {
+ if (/:$/.test(match)) {
+ cls = 'key';
+ } else {
+ cls = 'string';
+ }
+ } else if (/true|false/.test(match)) {
+ cls = 'boolean';
+ } else if (/null/.test(match)) {
+ cls = 'null';
+ }
+ return '<span class="' + cls + '">' + match + '</span>';
+ });
+ },
+};
+
+//**********************************************
+// Generic function to call binder
+//***********************************************
+function callbinder(api, verb, query) {
+ log.command(api, verb, query);
+
+ // ws.call return a Promise
+ return ws.call(api + '/' + verb, query)
+ .then(function (res) {
+ log.reply(res);
+ count++;
+ return res;
+ })
+ .catch(function (err) {
+ log.reply(err);
+ count++;
+ throw err;
+ });
+};
+
+//**********************************************
+// Init - establish Websocket connection
+//**********************************************
+function init(elemID, api, verb, query) {
+
+ function onopen() {
+ document.getElementById("main").style.visibility = "visible";
+ document.getElementById("connected").innerHTML = "Binder WS Active";
+ document.getElementById("connected").style.background = "lightgreen";
+ ws.onevent("*", log.event);
+ }
+
+ function onabort() {
+ document.getElementById("main").style.visibility = "hidden";
+ document.getElementById("connected").innerHTML = "Connected Closed";
+ document.getElementById("connected").style.background = "red";
+ }
+
+ ws = new afb.ws(onopen, onabort);
+}
+
+function clearPre(preId) {
+ const pre = document.getElementById(preId);
+ while (pre && pre.firstChild) {
+ pre.removeChild(pre.firstChild);
+ }
+}
+
+function fetchAndRenderVoiceAgents() {
+ const agentsDiv = document.getElementById('agentsDiv');
+ while (agentsDiv.firstChild) {
+ agentsDiv.removeChild(agentsDiv.firstChild);
+ }
+
+ const api = 'vshl';
+ const verb = 'enumerateVoiceAgents';
+ const query = {};
+
+ log.command(api, verb, query);
+
+ return ws.call(api + '/' + verb, query)
+ .then(function (res) {
+ log.reply(res);
+ for (let index = 0; index < res.response.agents.length; ++index) {
+ let voiceAgent = res.response.agents[index];
+ addVoiceAgent(agentsDiv, voiceAgent, res.response.default == voiceAgent.id);
+ }
+ })
+ .catch(function (err) {
+ log.reply(err);
+ console.log(JSON.stringify(err));
+ });
+}
+
+function addVoiceAgent(containerDiv, voiceAgent, isDefault) {
+ const agentDiv = document.createElement("div");
+
+ const agentName = document.createElement("h2");
+ agentName.innerHTML = voiceAgent.name;
+ agentDiv.appendChild(agentName);
+
+ const agentDescription = document.createElement("p");
+ agentDescription.innerHTML = voiceAgent.description;
+ agentDiv.appendChild(agentDescription);
+
+ if (!isDefault) {
+ const setDefaultBtn = document.createElement("button");
+ setDefaultBtn.addEventListener('click', (evt) => {
+ const query = {"id": voiceAgent.id};
+ callbinder('vshl', 'setDefaultVoiceAgent', query);
+ fetchAndRenderVoiceAgents();
+ });
+ setDefaultBtn.innerHTML = 'SetDefault';
+ agentDiv.appendChild(setDefaultBtn);
+ }
+
+ const subscribeBtn = document.createElement("button");
+ subscribeBtn.addEventListener('click', (evt) => {
+ showAgentEventChooserDialog(voiceAgent.id);
+ });
+ subscribeBtn.innerHTML = 'Subscribe';
+ agentDiv.appendChild(subscribeBtn);
+
+ containerDiv.appendChild(agentDiv);
+}
+
+function showAgentEventChooserDialog(voiceAgentId) {
+ const modal = document.getElementById('agent-event-chooser');
+ const subscribeBtn = document.getElementById('agent-subscribe-btn');
+
+ subscribeBtn.addEventListener('click', (evt) => {
+ const authState = document.getElementById('authstate').checked;
+ const dialogState = document.getElementById('dialogstate').checked;
+ const connectionState = document.getElementById('connectionstate').checked;
+
+ const query = {
+ "va_id": voiceAgentId,
+ "events":[]
+ };
+ if (authState)
+ query.events.push('voice_authstate_event');
+ if (dialogState)
+ query.events.push('voice_dialogstate_event');
+ if (connectionState)
+ query.events.push('voice_connectionstate_event');
+
+ callbinder('vshl', 'subscribe', query);
+ modal.close();
+ });
+
+ // makes modal appear (adds `open` attribute)
+ modal.showModal();
+}
+
+function showTemplateUIEventChooserDialog() {
+ const modal = document.getElementById('templateui-event-chooser');
+ const subscribeBtn = document.getElementById('templateui-subscribe-btn');
+
+ subscribeBtn.addEventListener('click', (evt) => {
+ const renderTemplate = document.getElementById('render_template').checked;
+ const clearTemplate = document.getElementById('clear_template').checked;
+ const renderPlayerInfo = document.getElementById('render_player_info').checked;
+ const clearPlayerInfo = document.getElementById('clear_player_info').checked;
+
+ const query = {"actions":[]};
+
+ if (renderTemplate)
+ query.actions.push('render_template');
+ if (clearTemplate)
+ query.actions.push('clear_template');
+ if (renderPlayerInfo)
+ query.actions.push('render_player_info');
+ if (clearPlayerInfo)
+ query.actions.push('clear_player_info');
+
+ callbinder('vshl', 'guiMetadata/subscribe', query);
+ modal.close();
+ });
+
+ // makes modal appear (adds `open` attribute)
+ modal.showModal();
+} \ No newline at end of file
diff --git a/htdocs/index.html b/htdocs/index.html
new file mode 100644
index 0000000..5a84ee8
--- /dev/null
+++ b/htdocs/index.html
@@ -0,0 +1,95 @@
+<html>
+
+<head>
+ <title>VSHL API Test</title>
+ <link rel="stylesheet" href="binding.css">
+ <script type="text/javascript" src="AFB-websock.js"></script>
+ <script type="text/javascript" src="binding.js"></script>
+</head>
+
+<body class="page-content" onload="init()">
+
+ <h1>Voice Service High Level API Tester</h1>
+
+ <button id="connected" onclick="init()">Binder WS Fail</button>
+ <button id="monitoring" onclick="window.open('/monitoring/monitor.html','_monitor_ctl')">Debug/Monitoring</a>
+ </button>
+ <button onclick="clearPre('question'); clearPre('output'); clearPre('outevt');">Clear</button>
+
+ <br>
+ <br>
+
+ <dialog id="agent-event-chooser">
+ <h3 class="dialogheader">Subscribe to the following agent events</h3>
+ <div>
+ <ol>
+ <li>
+ <input type="checkbox" id="authstate" checked>
+ <label>voice_authstate_event</label>
+ </li>
+ <li>
+ <input type="checkbox" id="dialogstate" checked>
+ <label>voice_dialogstate_event</label>
+ </li>
+ <li>
+ <input type="checkbox" id="connectionstate" checked>
+ <label>voice_connectionstate_event</label>
+ </li>
+ </ol>
+ </div>
+ <footer>
+ <button id="agent-subscribe-btn" type="button" style="margin: 10px">Subscribe</button>
+ </footer>
+ </dialog>
+
+ <dialog id="templateui-event-chooser">
+ <h3 class="dialogheader">Subscribe to the following GUI Metadata Messages</h3>
+ <div>
+ <ol>
+ <li>
+ <input type="checkbox" id="render_template" checked>
+ <label>render_template</label>
+ </li>
+ <li>
+ <input type="checkbox" id="clear_template" checked>
+ <label>clear_template</label>
+ </li>
+ <li>
+ <input type="checkbox" id="render_player_info" checked>
+ <label>render_player_info</label>
+ </li>
+ <li>
+ <input type="checkbox" id="clear_player_info" checked>
+ <label>clear_player_info</label>
+ </li>
+ </ol>
+ </div>
+ <footer>
+ <button id="templateui-subscribe-btn" type="button" style="margin: 10px">Subscribe</button>
+ </footer>
+ </dialog>
+
+ <div id="top" class="row">
+ <div id='actions' class="col1">
+ <div>
+ <h2>VSHL APIs</h2>
+ <p>APIs that are voiceagent agnostic</p>
+ <button onclick="callbinder('vshl','startListening',{});">startListening</button>
+ <button onclick="fetchAndRenderVoiceAgents();">enumerateAgents</button>
+ <button onclick="showTemplateUIEventChooserDialog();">Subscribe to GUI Metadata</button>
+ </div>
+
+ <div id="agentsDiv">
+ </div>
+ </div>
+
+ <div id="main" style="visibility:hidden" class="col2">
+ <ol>
+ <li>Question <pre id="question"></pre>
+ <li>Response <pre id="output"></pre>
+ <li>Events: <pre id="outevt"></pre>
+ </ol>
+ </div>
+ </div>
+
+</body> \ No newline at end of file
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
new file mode 100644
index 0000000..729dcb6
--- /dev/null
+++ b/src/CMakeLists.txt
@@ -0,0 +1,44 @@
+###########################################################################
+# Copyright 2017-2018 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.
+# 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.
+###########################################################################
+
+# Add target to project dependency list
+PROJECT_TARGET_ADD(vshl)
+ set(CMAKE_CXX_STANDARD 11)
+ set(CMAKE_CXX_STANDARD_REQUIRED ON)
+ set(CMAKE_CXX_EXTENSIONS OFF)
+
+ # Define project Targets
+ add_library(${TARGET_NAME} MODULE
+ ${TARGET_NAME}-binding.c
+ )
+
+ set(OPENAPI_DEF "${TARGET_NAME}-apidef" CACHE STRING "name and path to the JSON API definition without extension")
+
+ # Binder exposes a unique public entry point
+ SET_TARGET_PROPERTIES(${TARGET_NAME} PROPERTIES
+ PREFIX "afb-"
+ LABELS "BINDINGV3"
+ LINK_FLAGS ${BINDINGS_LINK_FLAG}
+ OUTPUT_NAME ${TARGET_NAME}
+ )
+
+ # Library dependencies (include updates automatically)
+ TARGET_LINK_LIBRARIES(${TARGET_NAME}
+ afb-helpers
+ ctl-utilities
+ ${link_libraries})
+
+add_subdirectory("plugins") \ No newline at end of file
diff --git a/src/plugins/CMakeLists.txt b/src/plugins/CMakeLists.txt
new file mode 100644
index 0000000..06ef7a4
--- /dev/null
+++ b/src/plugins/CMakeLists.txt
@@ -0,0 +1,182 @@
+###########################################################################
+# Copyright 2017-2018 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.
+# 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.
+###########################################################################
+
+PROJECT_TARGET_ADD(vshl-api)
+
+ set(CMAKE_CXX_STANDARD 11)
+ set(CMAKE_CXX_STANDARD_REQUIRED ON)
+ set(CMAKE_CXX_EXTENSIONS OFF)
+
+ set(VSHL_LIB_SRC
+ ${CMAKE_CURRENT_SOURCE_DIR}/VshlApi.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/VshlApi.h
+
+ # Interfaces
+ ${CMAKE_CURRENT_SOURCE_DIR}/interfaces/afb/IAFBApi.h
+ ${CMAKE_CURRENT_SOURCE_DIR}/interfaces/capabilities/ICapability.h
+ ${CMAKE_CURRENT_SOURCE_DIR}/interfaces/utilities/events/IEventFilter.h
+ ${CMAKE_CURRENT_SOURCE_DIR}/interfaces/utilities/logging/ILogger.h
+ ${CMAKE_CURRENT_SOURCE_DIR}/interfaces/voiceagents/IVoiceAgent.h
+ ${CMAKE_CURRENT_SOURCE_DIR}/interfaces/voiceagents/IVoiceAgentsChangeObserver.h
+
+ # AFB
+ ${CMAKE_CURRENT_SOURCE_DIR}/afb/AFBApiImpl.h
+ ${CMAKE_CURRENT_SOURCE_DIR}/afb/AFBApiImpl.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/afb/AFBRequestImpl.h
+ ${CMAKE_CURRENT_SOURCE_DIR}/afb/AFBRequestImpl.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/afb/include/AFBEventImpl.h
+ ${CMAKE_CURRENT_SOURCE_DIR}/afb/src/AFBEventImpl.cpp
+
+ # VoiceAgents
+ ${CMAKE_CURRENT_SOURCE_DIR}/voiceagents/VoiceAgentsDataManager.h
+ ${CMAKE_CURRENT_SOURCE_DIR}/voiceagents/VoiceAgentsDataManagerImpl.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/voiceagents/VoiceAgentEventNames.h
+ ${CMAKE_CURRENT_SOURCE_DIR}/voiceagents/include/VoiceAgent.h
+ ${CMAKE_CURRENT_SOURCE_DIR}/voiceagents/src/VoiceAgentImpl.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/voiceagents/include/VoiceAgentEventsHandler.h
+ ${CMAKE_CURRENT_SOURCE_DIR}/voiceagents/src/VoiceAgentEventsHandler.cpp
+
+ # Core
+ ${CMAKE_CURRENT_SOURCE_DIR}/core/VRRequestProcessor.h
+ ${CMAKE_CURRENT_SOURCE_DIR}/core/VRRequestProcessorImpl.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/core/include/VRAgentsObserver.h
+ ${CMAKE_CURRENT_SOURCE_DIR}/core/src/VRAgentsObserverImpl.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/core/include/VRRequest.h
+ ${CMAKE_CURRENT_SOURCE_DIR}/core/src/VRRequestImpl.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/core/include/VRRequestProcessorDelegate.h
+ ${CMAKE_CURRENT_SOURCE_DIR}/core/src/VRRequestProcessorDelegateImpl.cpp
+
+ #Capabilities
+ ${CMAKE_CURRENT_SOURCE_DIR}/capabilities/CapabilitiesFactory.h
+ ${CMAKE_CURRENT_SOURCE_DIR}/capabilities/CapabilitiesFactory.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/capabilities/CapabilityMessagingService.h
+ ${CMAKE_CURRENT_SOURCE_DIR}/capabilities/CapabilityMessagingService.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/capabilities/core/include/MessageChannel.h
+ ${CMAKE_CURRENT_SOURCE_DIR}/capabilities/core/src/MessageChannel.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/capabilities/core/include/PublisherForwarder.h
+ ${CMAKE_CURRENT_SOURCE_DIR}/capabilities/core/src/PublisherForwarder.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/capabilities/core/include/SubscriberForwarder.h
+ ${CMAKE_CURRENT_SOURCE_DIR}/capabilities/core/src/SubscriberForwarder.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/capabilities/communication/include/PhoneControlMessages.h
+ ${CMAKE_CURRENT_SOURCE_DIR}/capabilities/communication/include/PhoneControlCapability.h
+ ${CMAKE_CURRENT_SOURCE_DIR}/capabilities/communication/src/PhoneControlCapability.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/capabilities/navigation/include/NavigationMessages.h
+ ${CMAKE_CURRENT_SOURCE_DIR}/capabilities/navigation/include/NavigationCapability.h
+ ${CMAKE_CURRENT_SOURCE_DIR}/capabilities/navigation/src/NavigationCapability.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/capabilities/guimetadata/include/GuiMetadataMessages.h
+ ${CMAKE_CURRENT_SOURCE_DIR}/capabilities/guimetadata/include/GuiMetadataCapability.h
+ ${CMAKE_CURRENT_SOURCE_DIR}/capabilities/guimetadata/src/GuiMetadataCapability.cpp
+
+ #Utilities
+ ${CMAKE_CURRENT_SOURCE_DIR}/utilities/events/EventRouter.h
+ ${CMAKE_CURRENT_SOURCE_DIR}/utilities/events/EventRouter.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/utilities/logging/Logger.h
+ ${CMAKE_CURRENT_SOURCE_DIR}/utilities/logging/Logger.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/utilities/uuid/UUIDGeneration.h
+ ${CMAKE_CURRENT_SOURCE_DIR}/utilities/uuid/UUIDGeneration.cpp
+ )
+
+ # Define targets
+ ADD_LIBRARY(${TARGET_NAME} MODULE
+ ${VSHL_LIB_SRC}
+ )
+
+ # VSHL plugin properties
+ SET_TARGET_PROPERTIES(${TARGET_NAME} PROPERTIES
+ LABELS "PLUGIN"
+ PREFIX ""
+ SUFFIX ".ctlso"
+ OUTPUT_NAME ${TARGET_NAME}
+ )
+
+ # Define target includes
+ TARGET_INCLUDE_DIRECTORIES(${TARGET_NAME}
+ PUBLIC ${GLIB_PKG_INCLUDE_DIRS}
+ PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}"
+ PRIVATE "${CMAKE_SOURCE_DIR}/app-controller/ctl-lib"
+ )
+
+ # Library dependencies (include updates automatically)
+ TARGET_LINK_LIBRARIES(${TARGET_NAME}
+ afb-helpers
+ ${GLIB_PKG_LIBRARIES}
+ ${link_libraries}
+ )
+
+ option(ENABLE_UNIT_TESTS "Build unit tests or not" OFF)
+ if (ENABLE_UNIT_TESTS)
+ execute_process(
+ COMMAND "${CMAKE_CURRENT_SOURCE_DIR}/agreement.sh"
+ RESULT_VARIABLE AGREEMENT_RESULT
+ )
+ message(STATUS "Agreement Result: ${AGREEMENT_RESULT}")
+ if (${AGREEMENT_RESULT} MATCHES "1")
+ message(FATAL_ERROR "User agreement not accepted. Quitting")
+ endif()
+
+ include(cmake/gtest.cmake)
+
+ set(VSHL_TEST_SRC ${VSHL_LIB_SRC})
+ list(APPEND VSHL_TEST_SRC
+ # Main
+ ${CMAKE_CURRENT_SOURCE_DIR}/TestMain.cpp
+
+ # Test common
+ ${CMAKE_CURRENT_SOURCE_DIR}/test/common/ConsoleLogger.h
+ ${CMAKE_CURRENT_SOURCE_DIR}/test/common/ConsoleLogger.cpp
+
+ # Test Mocks
+ ${CMAKE_CURRENT_SOURCE_DIR}/test/mocks/AFBApiMock.h
+ ${CMAKE_CURRENT_SOURCE_DIR}/test/mocks/AFBEventMock.h
+ ${CMAKE_CURRENT_SOURCE_DIR}/test/mocks/AFBRequestMock.h
+ ${CMAKE_CURRENT_SOURCE_DIR}/test/mocks/CapabilityMock.h
+ ${CMAKE_CURRENT_SOURCE_DIR}/test/mocks/VoiceAgentsChangeObserverMock.h
+
+ # Capabilities
+ ${CMAKE_CURRENT_SOURCE_DIR}/capabilities/test/CapabilityMessagingServiceTest.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/capabilities/test/PublisherForwarderTest.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/capabilities/test/SubscriberForwarderTest.cpp
+
+ # Core
+ ${CMAKE_CURRENT_SOURCE_DIR}/core/test/VRRequestTest.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/core/test/VRRequestProcessorTest.cpp
+
+ # VoiceAgents
+ ${CMAKE_CURRENT_SOURCE_DIR}/voiceagents/test/VoiceAgentTest.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/voiceagents/test/VoiceAgentsDataManagerTest.cpp
+ )
+
+ ADD_EXECUTABLE(${TARGET_NAME}_Test
+ ${VSHL_TEST_SRC}
+ )
+
+ TARGET_INCLUDE_DIRECTORIES(${TARGET_NAME}_Test
+ PUBLIC ${GLIB_PKG_INCLUDE_DIRS}
+ PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}"
+ PRIVATE "${CMAKE_SOURCE_DIR}/app-controller/ctl-lib"
+ )
+
+ TARGET_LINK_LIBRARIES(${TARGET_NAME}_Test
+ afb-helpers
+ libgtest
+ libgmock
+ ${GLIB_PKG_LIBRARIES}
+ ${link_libraries}
+ )
+
+ ENABLE_TESTING()
+ ADD_TEST(VshlTest ${TARGET_NAME}_Test)
+ endif() \ No newline at end of file
diff --git a/src/plugins/TestMain.cpp b/src/plugins/TestMain.cpp
new file mode 100644
index 0000000..d4fcbec
--- /dev/null
+++ b/src/plugins/TestMain.cpp
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2018-2019 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.
+ */
+#include "gtest/gtest.h"
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+} \ No newline at end of file
diff --git a/src/plugins/VshlApi.cpp b/src/plugins/VshlApi.cpp
new file mode 100644
index 0000000..f2c7b7c
--- /dev/null
+++ b/src/plugins/VshlApi.cpp
@@ -0,0 +1,607 @@
+/*
+ * Copyright 2018-2019 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.
+ */
+#include "VshlApi.h"
+
+#include <list>
+
+#include "afb/AFBApiImpl.h"
+#include "afb/AFBRequestImpl.h"
+#include "capabilities/CapabilitiesFactory.h"
+#include "capabilities/CapabilityMessagingService.h"
+#include "core/VRRequestProcessor.h"
+#include "utilities/events/EventRouter.h"
+#include "utilities/logging/Logger.h"
+#include "voiceagents/VoiceAgentEventNames.h"
+#include "voiceagents/VoiceAgentsDataManager.h"
+
+#include "json.hpp"
+
+using namespace std;
+
+CTLP_CAPI_REGISTER("vshl-api");
+
+static std::string TAG = "vshl::plugins::VshlAPI";
+
+static std::string VA_JSON_ATTR_DEFAULT = "default";
+static std::string VA_JSON_ATTR_AGENTS = "agents";
+static std::string VA_JSON_ATTR_ID = "id";
+static std::string VA_JSON_ATTR_NAME = "name";
+static std::string VA_JSON_ATTR_API = "api";
+static std::string VA_JSON_ATTR_ACTIVE = "active";
+static std::string VA_JSON_ATTR_WWS = "wakewords";
+static std::string VA_JSON_ATTR_ACTIVE_WW = "activewakeword";
+static std::string VA_JSON_ATTR_DESCRIPTION = "description";
+static std::string VA_JSON_ATTR_VENDOR = "vendor";
+
+static std::string STARTLISTENING_JSON_ATTR_REQUEST = "request_id";
+
+static std::string EVENTS_JSON_ATTR_VA_ID = "va_id";
+static std::string EVENTS_JSON_ATTR_EVENTS = "events";
+
+static std::string CAPABILITIES_JSON_ATTR_ACTION = "action";
+static std::string CAPABILITIES_JSON_ATTR_ACTIONS = "actions";
+static std::string CAPABILITIES_JSON_ATTR_PAYLOAD = "payload";
+
+static std::shared_ptr<vshl::utilities::logging::Logger> sLogger;
+static std::shared_ptr<vshl::common::interfaces::IAFBApi> sAfbApi;
+static std::unique_ptr<vshl::capabilities::CapabilitiesFactory> sCapabilitiesFactory;
+static std::unique_ptr<vshl::capabilities::CapabilityMessagingService> sCapabilityMessagingService;
+static std::unique_ptr<vshl::core::VRRequestProcessor> sVRRequestProcessor;
+static std::unique_ptr<vshl::voiceagents::VoiceAgentsDataManager> sVoiceAgentsDataManager;
+static std::unique_ptr<vshl::utilities::events::EventRouter> sEventRouter;
+
+using json = nlohmann::json;
+using Level = vshl::utilities::logging::Logger::Level;
+
+CTLP_ONLOAD(plugin, ret) {
+ if (plugin->api == nullptr) {
+ return -1;
+ }
+
+ // Logger
+ sLogger = vshl::utilities::logging::Logger::create(plugin->api);
+ // sLogger->log(Level::INFO, TAG, "Vshl plugin loaded & initialized.");
+
+ // AFB Wrapper
+ sAfbApi = vshl::afb::AFBApiImpl::create(plugin->api);
+
+ // VRRequestProcessor
+ auto vrRequestProcessorDelegate = vshl::core::VRRequestProcessorDelegate::create(sLogger, sAfbApi);
+ sVRRequestProcessor = vshl::core::VRRequestProcessor::create(sLogger, vrRequestProcessorDelegate);
+ if (!sVRRequestProcessor) {
+ sLogger->log(Level::ERROR, TAG, "Failed to create VRRequestProcessor");
+ return -1;
+ }
+
+ // VoiceAgentDataManager
+ sVoiceAgentsDataManager = vshl::voiceagents::VoiceAgentsDataManager::create(sLogger, sAfbApi);
+ if (!sVoiceAgentsDataManager) {
+ sLogger->log(Level::ERROR, TAG, "Failed to create VoiceAgentsDataManager");
+ return -1;
+ }
+ sVoiceAgentsDataManager->addVoiceAgentsChangeObserver(sVRRequestProcessor->getVoiceAgentsChangeObserver());
+
+ // EventRouter
+ sEventRouter = vshl::utilities::events::EventRouter::create(sLogger);
+ if (!sEventRouter) {
+ sLogger->log(Level::ERROR, TAG, "Failed to create EventRouter");
+ return -1;
+ }
+ sEventRouter->addEventFilter(sVoiceAgentsDataManager->getEventFilter());
+
+ sCapabilitiesFactory = vshl::capabilities::CapabilitiesFactory::create();
+ if (!sCapabilitiesFactory) {
+ sLogger->log(Level::ERROR, TAG, "Failed to create CapabilitiesFactory");
+ return -1;
+ }
+
+ sCapabilityMessagingService = vshl::capabilities::CapabilityMessagingService::create(sLogger, sAfbApi);
+ if (!sCapabilityMessagingService) {
+ sLogger->log(Level::ERROR, TAG, "Failed to create CapabilityMessagingService");
+ return -1;
+ }
+
+ return 0;
+}
+
+CTLP_CAPI(onAuthStateEvent, source, argsJ, eventJ) {
+ if (sEventRouter == nullptr) {
+ return -1;
+ }
+
+ string eventName = vshl::voiceagents::VSHL_EVENT_AUTH_STATE_EVENT;
+ json eventJson = json::parse(json_object_to_json_string(eventJ));
+ if (eventJson.find(EVENTS_JSON_ATTR_VA_ID) == eventJson.end()) {
+ sLogger->log(Level::ERROR, TAG, "onAuthStateEvent: No voiceagent id found.");
+ return -1;
+ }
+ std::string voiceAgentId(eventJson[EVENTS_JSON_ATTR_VA_ID].get<string>());
+
+ if (!sEventRouter->handleIncomingEvent(eventName, voiceAgentId, json_object_to_json_string(eventJ))) {
+ sLogger->log(Level::ERROR, TAG, "onAuthStateEvent: Failed to handle.");
+ return -1;
+ }
+
+ return 0;
+}
+
+CTLP_CAPI(onConnectionStateEvent, source, argsJ, eventJ) {
+ if (sEventRouter == nullptr) {
+ return -1;
+ }
+
+ string eventName = vshl::voiceagents::VSHL_EVENT_CONNECTION_STATE_EVENT;
+ json eventJson = json::parse(json_object_to_json_string(eventJ));
+ if (eventJson.find(EVENTS_JSON_ATTR_VA_ID) == eventJson.end()) {
+ sLogger->log(Level::ERROR, TAG, "onConnectionStateEvent: No voiceagent id found.");
+ return -1;
+ }
+ std::string voiceAgentId(eventJson[EVENTS_JSON_ATTR_VA_ID].get<string>());
+
+ if (!sEventRouter->handleIncomingEvent(eventName, voiceAgentId, json_object_to_json_string(eventJ))) {
+ sLogger->log(Level::ERROR, TAG, "onConnectionStateEvent: Failed to handle.");
+ return -1;
+ }
+
+ return 0;
+}
+
+CTLP_CAPI(onDialogStateEvent, source, argsJ, eventJ) {
+ if (sEventRouter == nullptr) {
+ return -1;
+ }
+
+ string eventName = vshl::voiceagents::VSHL_EVENT_DIALOG_STATE_EVENT;
+ json eventJson = json::parse(json_object_to_json_string(eventJ));
+ if (eventJson.find(EVENTS_JSON_ATTR_VA_ID) == eventJson.end()) {
+ sLogger->log(Level::ERROR, TAG, "onDialogStateEvent: No voiceagent id found.");
+ return -1;
+ }
+ std::string voiceAgentId(eventJson[EVENTS_JSON_ATTR_VA_ID].get<string>());
+
+ if (!sEventRouter->handleIncomingEvent(eventName, voiceAgentId, json_object_to_json_string(eventJ))) {
+ sLogger->log(Level::ERROR, TAG, "onDialogStateEvent: Failed to handle.");
+ return -1;
+ }
+
+ return 0;
+}
+
+CTLP_CAPI(loadVoiceAgentsConfig, source, argsJ, eventJ) {
+ if (sVoiceAgentsDataManager == nullptr) {
+ sLogger->log(Level::WARNING, TAG, "loadVoiceAgentsConfig: Voice service not initialized.");
+ return -1;
+ }
+
+ if (argsJ == nullptr) {
+ sLogger->log(Level::WARNING, TAG, "loadVoiceAgentsConfig: No arguments supplied.");
+ return -1;
+ }
+
+ json agentsConfigJson = json::parse(json_object_to_json_string(argsJ));
+ if (agentsConfigJson.find(VA_JSON_ATTR_AGENTS) == agentsConfigJson.end()) {
+ sLogger->log(Level::ERROR, TAG, "loadVoiceAgentsConfig: No agents object found in agents json");
+ return -1;
+ }
+
+ json agentsJson = agentsConfigJson[VA_JSON_ATTR_AGENTS];
+ for (auto agentIt = agentsJson.begin(); agentIt != agentsJson.end(); ++agentIt) {
+ json agentJson = *agentIt;
+
+ if (agentJson.find(VA_JSON_ATTR_ID) == agentJson.end() ||
+ agentJson.find(VA_JSON_ATTR_ACTIVE) == agentJson.end() ||
+ agentJson.find(VA_JSON_ATTR_NAME) == agentJson.end() ||
+ agentJson.find(VA_JSON_ATTR_API) == agentJson.end() ||
+ agentJson.find(VA_JSON_ATTR_WWS) == agentJson.end() ||
+ agentJson.find(VA_JSON_ATTR_ACTIVE_WW) == agentJson.end() ||
+ agentJson.find(VA_JSON_ATTR_DESCRIPTION) == agentJson.end() ||
+ agentJson.find(VA_JSON_ATTR_VENDOR) == agentJson.end()) {
+ std::stringstream error;
+ error << "loadVoiceAgentsConfig: One or more missing params in agent "
+ "config "
+ << agentJson.dump();
+ sLogger->log(Level::WARNING, TAG, error.str().c_str());
+ continue;
+ }
+
+ std::string id(agentJson[VA_JSON_ATTR_ID].get<string>());
+ std::string name(agentJson[VA_JSON_ATTR_NAME].get<string>());
+ std::string api(agentJson[VA_JSON_ATTR_API].get<string>());
+ std::string description(agentJson[VA_JSON_ATTR_DESCRIPTION].get<string>());
+ std::string vendor(agentJson[VA_JSON_ATTR_VENDOR].get<string>());
+ std::string activeWakeword(agentJson[VA_JSON_ATTR_ACTIVE_WW].get<string>());
+ bool isActive(agentJson[VA_JSON_ATTR_ACTIVE].get<bool>());
+
+ shared_ptr<unordered_set<string>> wakewords = std::make_shared<unordered_set<string>>();
+ json wakewordsJson = agentJson[VA_JSON_ATTR_WWS];
+ for (auto wwIt = wakewordsJson.begin(); wwIt != wakewordsJson.end(); ++wwIt) {
+ wakewords->insert(wwIt->get<string>());
+ }
+
+ sVoiceAgentsDataManager->addNewVoiceAgent(
+ id, name, description, api, vendor, activeWakeword, isActive, wakewords);
+ }
+
+ // Set the default agent.
+ if (agentsConfigJson.find(VA_JSON_ATTR_DEFAULT) == agentsConfigJson.end()) {
+ sLogger->log(Level::ERROR, TAG, "loadVoiceAgentsConfig: No default agent found in agents json");
+ return -1;
+ }
+ std::string defaultAgentId(agentsConfigJson[VA_JSON_ATTR_DEFAULT].get<string>());
+ sVoiceAgentsDataManager->setDefaultVoiceAgent(defaultAgentId);
+
+ return 0;
+}
+
+CTLP_CAPI(startListening, source, argsJ, eventJ) {
+ if (sVRRequestProcessor == nullptr) {
+ return -1;
+ }
+
+ int result = 0;
+ string requestId = sVRRequestProcessor->startListening();
+
+ if (!requestId.empty()) {
+ json responseJson;
+ responseJson[STARTLISTENING_JSON_ATTR_REQUEST] = requestId;
+ AFB_ReqSuccess(source->request, json_tokener_parse(responseJson.dump().c_str()), NULL);
+ } else {
+ AFB_ReqFail(source->request, NULL, "Failed to startListening...");
+ }
+
+ return 0;
+}
+
+CTLP_CAPI(cancelListening, source, argsJ, eventJ) {
+ return 0;
+}
+
+CTLP_CAPI(enumerateVoiceAgents, source, argsJ, eventJ) {
+ if (sVoiceAgentsDataManager == nullptr) {
+ return -1;
+ }
+
+ auto agents = sVoiceAgentsDataManager->getAllVoiceAgents();
+ std::string defaultAgentId(sVoiceAgentsDataManager->getDefaultVoiceAgent());
+
+ json responseJson;
+ json agentsJson = json::array();
+
+ for (auto agent : agents) {
+ json agentJson;
+ agentJson[VA_JSON_ATTR_ID] = agent->getId();
+ agentJson[VA_JSON_ATTR_NAME] = agent->getName();
+ agentJson[VA_JSON_ATTR_DESCRIPTION] = agent->getDescription();
+ agentJson[VA_JSON_ATTR_API] = agent->getApi();
+ agentJson[VA_JSON_ATTR_VENDOR] = agent->getVendor();
+ agentJson[VA_JSON_ATTR_ACTIVE] = agent->isActive();
+ agentJson[VA_JSON_ATTR_ACTIVE_WW] = agent->getActiveWakeword();
+
+ auto wakewords = agent->getWakeWords();
+ if (wakewords != nullptr) {
+ json wakewordsJson;
+ for (auto wakeword : *wakewords) {
+ wakewordsJson.push_back(wakeword);
+ }
+ agentJson[VA_JSON_ATTR_WWS] = wakewordsJson;
+ }
+
+ agentsJson.push_back(agentJson);
+ }
+
+ responseJson[VA_JSON_ATTR_AGENTS] = agentsJson;
+ responseJson[VA_JSON_ATTR_DEFAULT] = defaultAgentId;
+
+ AFB_ReqSuccess(source->request, json_tokener_parse(responseJson.dump().c_str()), NULL);
+
+ return 0;
+}
+
+CTLP_CAPI(subscribe, source, argsJ, eventJ) {
+ if (sVoiceAgentsDataManager == nullptr) {
+ return -1;
+ }
+
+ if (eventJ == nullptr) {
+ sLogger->log(Level::WARNING, TAG, "subscribe: No arguments supplied.");
+ return -1;
+ }
+
+ json subscribeJson = json::parse(json_object_to_json_string(eventJ));
+ if (subscribeJson.find(EVENTS_JSON_ATTR_VA_ID) == subscribeJson.end()) {
+ sLogger->log(Level::ERROR, TAG, "subscribe: No voiceagent id found in subscribe json");
+ return -1;
+ }
+ std::string voiceAgentId(subscribeJson[EVENTS_JSON_ATTR_VA_ID].get<string>());
+
+ if (subscribeJson.find(EVENTS_JSON_ATTR_EVENTS) == subscribeJson.end()) {
+ sLogger->log(Level::ERROR, TAG, "subscribe: No events array found in subscribe json");
+ return -1;
+ }
+ list<string> events(subscribeJson[EVENTS_JSON_ATTR_EVENTS].get<list<string>>());
+
+ // Subscribe this client for the listed events.
+ auto request = vshl::afb::AFBRequestImpl::create(source->request);
+ for (auto event : events) {
+ if (!sVoiceAgentsDataManager->subscribeToVshlEventFromVoiceAgent(*request, event, voiceAgentId)) {
+ sLogger->log(Level::ERROR, TAG, "subscribe: Failed to subscribe to event: " + event);
+ return -1;
+ }
+ }
+
+ AFB_ReqSuccess(source->request, json_object_new_string("Subscription to events successfully completed."), NULL);
+
+ return 0;
+}
+
+CTLP_CAPI(setDefaultVoiceAgent, source, argsJ, eventJ) {
+ if (sVoiceAgentsDataManager == nullptr) {
+ return -1;
+ }
+
+ if (eventJ == nullptr) {
+ sLogger->log(Level::WARNING, TAG, "setDefaultVoiceAgent: No arguments supplied.");
+ return -1;
+ }
+
+ json jsonRequest = json::parse(json_object_to_json_string(eventJ));
+ if (jsonRequest.find(VA_JSON_ATTR_ID) == jsonRequest.end()) {
+ sLogger->log(Level::ERROR, TAG, "setDefaultVoiceAgent: voice agent id not found in request json");
+ return -1;
+ }
+
+ std::string voiceAgentId(jsonRequest[VA_JSON_ATTR_ID].get<string>());
+ if (!sVoiceAgentsDataManager->setDefaultVoiceAgent(voiceAgentId)) {
+ sLogger->log(Level::ERROR, TAG, "setDefaultVoiceAgent: Failed to set default agent");
+ return -1;
+ }
+
+ AFB_ReqSuccess(source->request, NULL, NULL);
+ return 0;
+}
+
+CTLP_CAPI(guiMetadataSubscribe, source, argsJ, eventJ) {
+ if (sCapabilitiesFactory == nullptr || sCapabilityMessagingService == nullptr) {
+ return -1;
+ }
+
+ shared_ptr<vshl::common::interfaces::ICapability> guMetadataCapability = sCapabilitiesFactory->getGuiMetadata();
+ if (!guMetadataCapability) {
+ sLogger->log(
+ Level::WARNING,
+ TAG,
+ "guimetadataSubscribe: Failed to "
+ "fetch guimetadata capability "
+ "object.");
+ return -1;
+ }
+
+ if (eventJ == nullptr) {
+ sLogger->log(Level::WARNING, TAG, "guimetadataSubscribe: No arguments supplied.");
+ return -1;
+ }
+
+ json subscribeJson = json::parse(json_object_to_json_string(eventJ));
+ if (subscribeJson.find(CAPABILITIES_JSON_ATTR_ACTIONS) == subscribeJson.end()) {
+ sLogger->log(Level::ERROR, TAG, "guimetadataSubscribe: No events array found in subscribe json");
+ return -1;
+ }
+ list<string> events(subscribeJson[CAPABILITIES_JSON_ATTR_ACTIONS].get<list<string>>());
+
+ // SUbscribe this client for the guimetadata events.
+ auto request = vshl::afb::AFBRequestImpl::create(source->request);
+ for (auto event : events) {
+ if (!sCapabilityMessagingService->subscribe(*request, guMetadataCapability, event)) {
+ sLogger->log(Level::ERROR, TAG, "guimetadataSubscribe: Failed to subscribe to event: " + event);
+ return -1;
+ }
+ }
+
+ AFB_ReqSuccess(
+ source->request, json_object_new_string("Subscription to guimetadata events successfully completed."), NULL);
+ return 0;
+}
+
+CTLP_CAPI(guiMetadataPublish, source, argsJ, eventJ) {
+ if (sCapabilitiesFactory == nullptr || sCapabilityMessagingService == nullptr) {
+ return -1;
+ }
+
+ shared_ptr<vshl::common::interfaces::ICapability> guMetadataCapability = sCapabilitiesFactory->getGuiMetadata();
+ if (!guMetadataCapability) {
+ sLogger->log(
+ Level::WARNING,
+ TAG,
+ "guimetadataPublish: Failed to fetch "
+ "guimetadata capability object.");
+ return -1;
+ }
+
+ if (eventJ == nullptr) {
+ sLogger->log(Level::WARNING, TAG, "guimetadataPublish: No arguments supplied.");
+ return -1;
+ }
+
+ json publishJson = json::parse(json_object_to_json_string(eventJ));
+ if (publishJson.find(CAPABILITIES_JSON_ATTR_ACTION) == publishJson.end()) {
+ sLogger->log(Level::ERROR, TAG, "guimetadataPublish: No action found in publish json");
+ return -1;
+ }
+ std::string action(publishJson[CAPABILITIES_JSON_ATTR_ACTION].get<string>());
+
+ if (publishJson.find(CAPABILITIES_JSON_ATTR_PAYLOAD) == publishJson.end()) {
+ sLogger->log(Level::ERROR, TAG, "guimetadataPublish: No payload found in publish json");
+ return -1;
+ }
+ std::string payload(publishJson[CAPABILITIES_JSON_ATTR_PAYLOAD].get<string>());
+
+ if (!sCapabilityMessagingService->publish(guMetadataCapability, action, payload)) {
+ sLogger->log(Level::ERROR, TAG, "guimetadataPublish: Failed to publish message: " + action);
+ return -1;
+ }
+
+ AFB_ReqSuccess(source->request, json_object_new_string("Successfully published guimetadata messages."), NULL);
+ return 0;
+}
+
+CTLP_CAPI(phonecontrolSubscribe, source, argsJ, eventJ) {
+ if (sCapabilitiesFactory == nullptr || sCapabilityMessagingService == nullptr) {
+ return -1;
+ }
+
+ shared_ptr<vshl::common::interfaces::ICapability> phoneControlCapability = sCapabilitiesFactory->getPhoneControl();
+ if (!phoneControlCapability) {
+ sLogger->log(Level::WARNING, TAG, "phoneControlSubscribe: Failed to fetch phone control capability object.");
+ return -1;
+ }
+
+ if (eventJ == nullptr) {
+ sLogger->log(Level::WARNING, TAG, "phoneControlSubscribe: No arguments supplied.");
+ return -1;
+ }
+
+ json subscribeJson = json::parse(json_object_to_json_string(eventJ));
+ if (subscribeJson.find(CAPABILITIES_JSON_ATTR_ACTIONS) == subscribeJson.end()) {
+ sLogger->log(Level::ERROR, TAG, "phoneControlSubscribe: No events array found in subscribe json");
+ return -1;
+ }
+ list<string> events(subscribeJson[CAPABILITIES_JSON_ATTR_ACTIONS].get<list<string>>());
+
+ // SUbscribe this client for the phone call control events.
+ auto request = vshl::afb::AFBRequestImpl::create(source->request);
+ for (auto event : events) {
+ if (!sCapabilityMessagingService->subscribe(*request, phoneControlCapability, event)) {
+ sLogger->log(Level::ERROR, TAG, "phoneControlSubscribe: Failed to subscribe to event: " + event);
+ return -1;
+ }
+ }
+
+ AFB_ReqSuccess(
+ source->request, json_object_new_string("Subscription to phone control events successfully completed."), NULL);
+ return 0;
+}
+
+CTLP_CAPI(phonecontrolPublish, source, argsJ, eventJ) {
+ if (sCapabilitiesFactory == nullptr || sCapabilityMessagingService == nullptr) {
+ return -1;
+ }
+
+ shared_ptr<vshl::common::interfaces::ICapability> phoneControlCapability = sCapabilitiesFactory->getPhoneControl();
+ if (!phoneControlCapability) {
+ sLogger->log(Level::WARNING, TAG, "phoneControlPublish: Failed to fetch navigation capability object.");
+ return -1;
+ }
+
+ if (eventJ == nullptr) {
+ sLogger->log(Level::WARNING, TAG, "phoneControlPublish: No arguments supplied.");
+ return -1;
+ }
+
+ json publishJson = json::parse(json_object_to_json_string(eventJ));
+ if (publishJson.find(CAPABILITIES_JSON_ATTR_ACTION) == publishJson.end()) {
+ sLogger->log(Level::ERROR, TAG, "phoneControlPublish: No action found in publish json");
+ return -1;
+ }
+ std::string action(publishJson[CAPABILITIES_JSON_ATTR_ACTION].get<string>());
+
+ if (publishJson.find(CAPABILITIES_JSON_ATTR_PAYLOAD) == publishJson.end()) {
+ sLogger->log(Level::ERROR, TAG, "phoneControlPublish: No payload found in publish json");
+ return -1;
+ }
+ std::string payload(publishJson[CAPABILITIES_JSON_ATTR_PAYLOAD].get<string>());
+
+ if (!sCapabilityMessagingService->publish(phoneControlCapability, action, payload)) {
+ sLogger->log(Level::ERROR, TAG, "phoneControlPublish: Failed to publish message: " + action);
+ return -1;
+ }
+
+ AFB_ReqSuccess(source->request, json_object_new_string("Successfully published phone control messages."), NULL);
+ return 0;
+}
+
+CTLP_CAPI(navigationSubscribe, source, argsJ, eventJ) {
+ if (sCapabilitiesFactory == nullptr || sCapabilityMessagingService == nullptr) {
+ return -1;
+ }
+
+ shared_ptr<vshl::common::interfaces::ICapability> navigationCapability = sCapabilitiesFactory->getNavigation();
+ if (!navigationCapability) {
+ sLogger->log(Level::WARNING, TAG, "navigationSubscribe: Failed to fetch navigation capability object.");
+ return -1;
+ }
+
+ if (eventJ == nullptr) {
+ sLogger->log(Level::WARNING, TAG, "navigationSubscribe: No arguments supplied.");
+ return -1;
+ }
+
+ json subscribeJson = json::parse(json_object_to_json_string(eventJ));
+ if (subscribeJson.find(CAPABILITIES_JSON_ATTR_ACTIONS) == subscribeJson.end()) {
+ sLogger->log(Level::ERROR, TAG, "navigationSubscribe: No events array found in subscribe json");
+ return -1;
+ }
+ list<string> events(subscribeJson[CAPABILITIES_JSON_ATTR_ACTIONS].get<list<string>>());
+
+ // SUbscribe this client for the navigation events.
+ auto request = vshl::afb::AFBRequestImpl::create(source->request);
+ for (auto event : events) {
+ if (!sCapabilityMessagingService->subscribe(*request, navigationCapability, event)) {
+ sLogger->log(Level::ERROR, TAG, "navigationSubscribe: Failed to subscribe to event: " + event);
+ return -1;
+ }
+ }
+
+ AFB_ReqSuccess(
+ source->request, json_object_new_string("Subscription to navigation events successfully completed."), NULL);
+ return 0;
+}
+
+CTLP_CAPI(navigationPublish, source, argsJ, eventJ) {
+ if (sCapabilitiesFactory == nullptr || sCapabilityMessagingService == nullptr) {
+ return -1;
+ }
+
+ shared_ptr<vshl::common::interfaces::ICapability> navigationCapability = sCapabilitiesFactory->getNavigation();
+ if (!navigationCapability) {
+ sLogger->log(Level::WARNING, TAG, "navigationPublish: Failed to fetch navigation capability object.");
+ return -1;
+ }
+
+ if (eventJ == nullptr) {
+ sLogger->log(Level::WARNING, TAG, "navigationPublish: No arguments supplied.");
+ return -1;
+ }
+
+ json publishJson = json::parse(json_object_to_json_string(eventJ));
+ if (publishJson.find(CAPABILITIES_JSON_ATTR_ACTION) == publishJson.end()) {
+ sLogger->log(Level::ERROR, TAG, "navigationPublish: No action found in publish json");
+ return -1;
+ }
+ std::string action(publishJson[CAPABILITIES_JSON_ATTR_ACTION].get<string>());
+
+ if (publishJson.find(CAPABILITIES_JSON_ATTR_PAYLOAD) == publishJson.end()) {
+ sLogger->log(Level::ERROR, TAG, "navigationPublish: No payload found in publish json");
+ return -1;
+ }
+ std::string payload(publishJson[CAPABILITIES_JSON_ATTR_PAYLOAD].get<string>());
+
+ if (!sCapabilityMessagingService->publish(navigationCapability, action, payload)) {
+ sLogger->log(Level::ERROR, TAG, "navigationPublish: Failed to publish message: " + action);
+ return -1;
+ }
+
+ AFB_ReqSuccess(source->request, json_object_new_string("Successfully published navigation messages."), NULL);
+ return 0;
+}
diff --git a/src/plugins/VshlApi.h b/src/plugins/VshlApi.h
new file mode 100644
index 0000000..f228943
--- /dev/null
+++ b/src/plugins/VshlApi.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2017-2018 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 VSHL_API_INCLUDE
+#define VSHL_API_INCLUDE
+
+#include "ctl-plugin.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+CTLP_ONLOAD(plugin, ret);
+CTLP_INIT(plugin, ret);
+int onAuthStateEvent(CtlSourceT* source, json_object* argsJ, json_object* queryJ);
+int onConnectionStateEvent(CtlSourceT* source, json_object* argsJ, json_object* queryJ);
+int onDialogStateEvent(CtlSourceT* source, json_object* argsJ, json_object* queryJ);
+int loadVoiceAgentsConfig(CtlSourceT* source, json_object* argsJ, json_object* queryJ);
+int startListening(CtlSourceT* source, json_object* argsJ, json_object* queryJ);
+int cancelListening(CtlSourceT* source, json_object* argsJ, json_object* queryJ);
+int enumerateVoiceAgents(CtlSourceT* source, json_object* argsJ, json_object* queryJ);
+int subscribe(CtlSourceT* source, json_object* argsJ, json_object* queryJ);
+int setDefaultVoiceAgent(CtlSourceT* source, json_object* argsJ, json_object* queryJ);
+int guiMetadataSubscribe(CtlSourceT* source, json_object* argsJ, json_object* queryJ);
+int guiMetadataPublish(CtlSourceT* source, json_object* argsJ, json_object* queryJ);
+int phonecontrolSubscribe(CtlSourceT* source, json_object* argsJ, json_object* queryJ);
+int phonecontrolPublish(CtlSourceT* source, json_object* argsJ, json_object* queryJ);
+int navigationSubscribe(CtlSourceT* source, json_object* argsJ, json_object* queryJ);
+int navigationPublish(CtlSourceT* source, json_object* argsJ, json_object* queryJ);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // VSHL_API_INCLUDE
diff --git a/src/plugins/afb/AFBApiImpl.cpp b/src/plugins/afb/AFBApiImpl.cpp
new file mode 100644
index 0000000..88d1e7e
--- /dev/null
+++ b/src/plugins/afb/AFBApiImpl.cpp
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2018-2019 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.
+ */
+
+#include "afb/AFBApiImpl.h"
+
+#include "afb/include/AFBEventImpl.h"
+#include "utilities/logging/Logger.h"
+
+extern "C" {
+#define AFB_BINDING_VERSION 3
+#define FREEIF(x) \
+ if (!x) { \
+ free(x); \
+ }
+#define BREAKIF(x) \
+ if (x) { \
+ result = false; \
+ break; \
+ }
+
+#include "afb-definitions.h"
+}
+
+static std::string TAG = "vshl::afb::AFBApiImpl";
+
+/**
+ * Specifies the severity level of a log message
+ */
+using Level = vshl::common::interfaces::ILogger::Level;
+using namespace vshl::common::interfaces;
+using namespace vshl::utilities::logging;
+
+namespace vshl {
+namespace afb {
+
+std::unique_ptr<AFBApiImpl> AFBApiImpl::create(AFB_ApiT api) {
+ return std::unique_ptr<AFBApiImpl>(new AFBApiImpl(api));
+}
+
+AFBApiImpl::AFBApiImpl(AFB_ApiT api) : mApi(api), mLogger(Logger::create(api)) {
+}
+
+AFBApiImpl::~AFBApiImpl() {
+}
+
+std::shared_ptr<IAFBApi::IAFBEvent> AFBApiImpl::createEvent(const std::string& eventName) {
+ return AFBEventImpl::create(mLogger, mApi, eventName);
+}
+
+int AFBApiImpl::callSync(
+ const std::string& api,
+ const std::string& verb,
+ struct json_object* request,
+ struct json_object** result,
+ std::string& error,
+ std::string& info) {
+ char* errorStr = NULL;
+ char* infoStr = NULL;
+ int rc = AFB_ApiSync(mApi, api.c_str(), verb.c_str(), request, result, &errorStr, &infoStr);
+
+ if (errorStr) {
+ error = errorStr;
+ free(errorStr);
+ }
+
+ if (infoStr) {
+ info = infoStr;
+ free(infoStr);
+ }
+
+ return rc;
+}
+
+} // namespace afb
+} // namespace vshl
diff --git a/src/plugins/afb/AFBApiImpl.h b/src/plugins/afb/AFBApiImpl.h
new file mode 100644
index 0000000..74aa7ef
--- /dev/null
+++ b/src/plugins/afb/AFBApiImpl.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2018-2019 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 VSHL_AFB_AFBAPIIMPL_H_
+#define VSHL_AFB_AFBAPIIMPL_H_
+
+#include <memory>
+
+extern "C" {
+#include "ctl-plugin.h"
+}
+
+#include "interfaces/afb/IAFBApi.h"
+#include "interfaces/utilities/logging/ILogger.h"
+
+using namespace std;
+
+namespace vshl {
+namespace afb {
+
+class AFBApiImpl : public vshl::common::interfaces::IAFBApi {
+public:
+ static std::unique_ptr<AFBApiImpl> create(AFB_ApiT api);
+
+ ~AFBApiImpl();
+
+ std::shared_ptr<IAFBEvent> createEvent(const std::string& eventName) override;
+
+ int callSync(
+ const std::string& api,
+ const std::string& verb,
+ struct json_object* request,
+ struct json_object** result,
+ std::string& error,
+ std::string& info) override;
+
+private:
+ AFBApiImpl(AFB_ApiT api);
+
+ // AFB API Binding
+ AFB_ApiT mApi;
+
+ // Logger
+ std::shared_ptr<vshl::common::interfaces::ILogger> mLogger;
+};
+
+} // namespace afb
+} // namespace vshl
+
+#endif // VSHL_AFB_AFBAPIIMPL_H_
diff --git a/src/plugins/afb/AFBRequestImpl.cpp b/src/plugins/afb/AFBRequestImpl.cpp
new file mode 100644
index 0000000..8ec5691
--- /dev/null
+++ b/src/plugins/afb/AFBRequestImpl.cpp
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2018-2019 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.
+ */
+
+#include "afb/AFBRequestImpl.h"
+
+extern "C" {
+#include "afb-definitions.h"
+}
+
+namespace vshl {
+namespace afb {
+
+std::unique_ptr<AFBRequestImpl> AFBRequestImpl::create(AFB_ReqT afbRequest) {
+ return std::unique_ptr<AFBRequestImpl>(new AFBRequestImpl(afbRequest));
+}
+
+AFBRequestImpl::AFBRequestImpl(AFB_ReqT afbRequest) : mAfbRequest(afbRequest) {
+}
+
+void* AFBRequestImpl::getNativeRequest() {
+ return mAfbRequest;
+}
+
+} // namespace afb
+} // namespace vshl
diff --git a/src/plugins/afb/AFBRequestImpl.h b/src/plugins/afb/AFBRequestImpl.h
new file mode 100644
index 0000000..2e6f3ab
--- /dev/null
+++ b/src/plugins/afb/AFBRequestImpl.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2018-2019 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 VSHL_AFB_AFBREQUESTIMPL_H_
+#define VSHL_AFB_AFBREQUESTIMPL_H_
+
+#include <memory>
+
+extern "C" {
+#include "ctl-plugin.h"
+}
+
+#include "interfaces/afb/IAFBApi.h"
+
+namespace vshl {
+namespace afb {
+
+/**
+ * AFB Request impl
+ */
+class AFBRequestImpl : public vshl::common::interfaces::IAFBRequest {
+public:
+ static std::unique_ptr<AFBRequestImpl> create(AFB_ReqT afbRequest);
+
+ // {@c IAFBRequest Implementation
+ void *getNativeRequest() override;
+ // @c IAFBRequest Implementation }
+
+private:
+ AFBRequestImpl(AFB_ReqT afbRequest);
+
+ AFB_ReqT mAfbRequest;
+};
+
+} // namespace afb
+} // namespace vshl
+
+#endif // VSHL_AFB_AFBREQUESTIMPL_H_
diff --git a/src/plugins/afb/include/AFBEventImpl.h b/src/plugins/afb/include/AFBEventImpl.h
new file mode 100644
index 0000000..45f85f9
--- /dev/null
+++ b/src/plugins/afb/include/AFBEventImpl.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2018-2019 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 VSHL_AFB_EVENT_H_
+#define VSHL_AFB_EVENT_H_
+
+#include <memory>
+#include <string>
+
+#include "interfaces/afb/IAFBApi.h"
+#include "interfaces/utilities/logging/ILogger.h"
+
+extern "C" {
+#include "ctl-plugin.h"
+#include <json-c/json.h>
+}
+
+using namespace std;
+
+namespace vshl {
+namespace afb {
+/*
+ * This class encapsulates AFB Event.
+ */
+class AFBEventImpl : public vshl::common::interfaces::IAFBApi::IAFBEvent {
+public:
+ static unique_ptr<AFBEventImpl>
+ create(shared_ptr<vshl::common::interfaces::ILogger> logger, AFB_ApiT api,
+ const string &eventName);
+
+ // Destructor
+ ~AFBEventImpl();
+
+ /// { @c IAFBEvent implementation
+ string getName() const override;
+ bool isValid() override;
+ int publishEvent(struct json_object *payload) override;
+ bool subscribe(vshl::common::interfaces::IAFBRequest &request) override;
+ bool unsubscribe(vshl::common::interfaces::IAFBRequest &request) override;
+ /// @c IAFBEvent implementation }
+
+private:
+ AFBEventImpl(shared_ptr<vshl::common::interfaces::ILogger> logger,
+ AFB_ApiT api, const string &eventName);
+
+ // Make the event. This is a lazy make that happens
+ // usually during the subscribe stage.
+ void makeEventIfNeccessary();
+
+ // Binding API reference
+ AFB_ApiT mAfbApi;
+
+ // AFB Event
+ afb_event_t mAfbEvent;
+
+ // Event Name
+ string mEventName;
+
+ // Logger
+ shared_ptr<vshl::common::interfaces::ILogger> mLogger;
+};
+
+} // namespace afb
+} // namespace vshl
+
+#endif // VSHL_AFB_EVENT_H_
diff --git a/src/plugins/afb/src/AFBEventImpl.cpp b/src/plugins/afb/src/AFBEventImpl.cpp
new file mode 100644
index 0000000..e3c902d
--- /dev/null
+++ b/src/plugins/afb/src/AFBEventImpl.cpp
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2018-2019 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.
+ */
+#include "afb/include/AFBEventImpl.h"
+
+static string TAG = "vshl::afb::Event";
+
+using Level = vshl::common::interfaces::ILogger::Level;
+using namespace vshl::common::interfaces;
+
+namespace vshl {
+namespace afb {
+
+unique_ptr<AFBEventImpl> AFBEventImpl::create(
+ shared_ptr<vshl::common::interfaces::ILogger> logger,
+ AFB_ApiT api,
+ const string& eventName) {
+ return unique_ptr<AFBEventImpl>(new AFBEventImpl(logger, api, eventName));
+}
+
+AFBEventImpl::AFBEventImpl(
+ shared_ptr<vshl::common::interfaces::ILogger> logger,
+ AFB_ApiT api,
+ const string& eventName) :
+ mLogger(logger),
+ mAfbApi(api),
+ mEventName(eventName),
+ mAfbEvent(nullptr) {
+}
+
+AFBEventImpl::~AFBEventImpl() {
+}
+
+string AFBEventImpl::getName() const {
+ return mEventName;
+}
+
+bool AFBEventImpl::isValid() {
+ makeEventIfNeccessary();
+ return afb_event_is_valid(mAfbEvent) == 1 ? true : false;
+}
+
+bool AFBEventImpl::subscribe(IAFBRequest& requestInterface) {
+ makeEventIfNeccessary();
+ auto request = static_cast<AFB_ReqT>(requestInterface.getNativeRequest());
+ if (isValid() && afb_req_subscribe(request, mAfbEvent) == 0) {
+ return true;
+ }
+
+ return false;
+}
+
+bool AFBEventImpl::unsubscribe(IAFBRequest& requestInterface) {
+ makeEventIfNeccessary();
+ auto request = static_cast<AFB_ReqT>(requestInterface.getNativeRequest());
+ if (isValid() && afb_req_unsubscribe(request, mAfbEvent) == 0) {
+ return true;
+ }
+
+ return false;
+}
+
+int AFBEventImpl::publishEvent(struct json_object* payload) {
+ makeEventIfNeccessary();
+ return afb_event_push(mAfbEvent, payload);
+}
+
+void AFBEventImpl::makeEventIfNeccessary() {
+ if (mAfbEvent) {
+ return;
+ }
+
+ mLogger->log(Level::NOTICE, TAG, "Creating VSHL event: " + mEventName);
+ mAfbEvent = afb_api_make_event(mAfbApi, mEventName.c_str());
+}
+
+} // namespace afb
+} // namespace vshl
diff --git a/src/plugins/agreement.sh b/src/plugins/agreement.sh
new file mode 100755
index 0000000..2a8eb71
--- /dev/null
+++ b/src/plugins/agreement.sh
@@ -0,0 +1,43 @@
+#********************************************************************************
+# Copyright 2018-2019 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.
+#*********************************************************************************
+
+agreement_check() {
+ cat << EOF
+
+*******************************************************************************
+The scripts provided herein will retrieve several third-party libraries,
+environments, and/or other software packages at build-time
+("External Dependencies") from third-party sources. These are terms and
+conditions that you need to agree to abide by if you choose to build the
+External Dependencies. Licenses for the External Dependencies may be found at
+README.md. If you do not agree with every term and condition
+associated with the External Dependencies, enter “QUIT” in the command line
+when prompted by the script.
+*******************************************************************************
+
+EOF
+
+ answer="dummy"
+ while [ ! -z $answer ]; do
+ read -p "Type \"QUIT\" to exit the script now, press ENTER to continue: " -r answer
+ if [ "$answer" = "QUIT" ]; then
+ exit 1
+ fi
+ echo ""
+ done
+}
+
+agreement_check
+exit 0 \ No newline at end of file
diff --git a/src/plugins/capabilities/CapabilitiesFactory.cpp b/src/plugins/capabilities/CapabilitiesFactory.cpp
new file mode 100644
index 0000000..2f92519
--- /dev/null
+++ b/src/plugins/capabilities/CapabilitiesFactory.cpp
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2018-2019 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.
+ */
+
+#include "capabilities/CapabilitiesFactory.h"
+
+#include "capabilities/communication/include/PhoneControlCapability.h"
+#include "capabilities/guimetadata/include/GuiMetadataCapability.h"
+#include "capabilities/navigation/include/NavigationCapability.h"
+
+static string TAG = "vshl::core::CapabilitiesFactory";
+
+using Level = vshl::utilities::logging::Logger::Level;
+
+namespace vshl {
+namespace capabilities {
+
+// Create CapabilitiesFactory
+std::unique_ptr<CapabilitiesFactory> CapabilitiesFactory::create() {
+ auto capabilitiesFactory = std::unique_ptr<CapabilitiesFactory>(new CapabilitiesFactory());
+ return capabilitiesFactory;
+}
+
+std::shared_ptr<common::interfaces::ICapability> CapabilitiesFactory::getGuiMetadata() {
+ if (!mGuiMetadata) {
+ mGuiMetadata = vshl::capabilities::guimetadata::GuiMetadata::create();
+ }
+ return mGuiMetadata;
+}
+
+std::shared_ptr<common::interfaces::ICapability> CapabilitiesFactory::getPhoneControl() {
+ if (!mPhoneControl) {
+ mPhoneControl = vshl::capabilities::phonecontrol::PhoneControl::create();
+ }
+ return mPhoneControl;
+}
+
+std::shared_ptr<common::interfaces::ICapability> CapabilitiesFactory::getNavigation() {
+ if (!mNavigation) {
+ mNavigation = vshl::capabilities::navigation::Navigation::create();
+ }
+ return mNavigation;
+}
+
+} // namespace capabilities
+} // namespace vshl \ No newline at end of file
diff --git a/src/plugins/capabilities/CapabilitiesFactory.h b/src/plugins/capabilities/CapabilitiesFactory.h
new file mode 100644
index 0000000..b73909b
--- /dev/null
+++ b/src/plugins/capabilities/CapabilitiesFactory.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2018-2019 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 VSHL_CAPABILITIES_CAPABILITIESFACTORY_H_
+#define VSHL_CAPABILITIES_CAPABILITIESFACTORY_H_
+
+#include <memory>
+
+#include "interfaces/capabilities/ICapability.h"
+#include "utilities/logging/Logger.h"
+
+using namespace std;
+
+namespace vshl {
+namespace capabilities {
+/*
+ * Factory for creating different capability objects.
+ */
+class CapabilitiesFactory {
+public:
+ // Create CapabilitiesFactory
+ static std::unique_ptr<CapabilitiesFactory> create();
+
+ // GUI Metadata capability
+ std::shared_ptr<common::interfaces::ICapability> getGuiMetadata();
+
+ // Phone call control capability
+ std::shared_ptr<common::interfaces::ICapability> getPhoneControl();
+
+ // Navigation capability
+ std::shared_ptr<common::interfaces::ICapability> getNavigation();
+
+ // Destructor
+ ~CapabilitiesFactory() = default;
+
+private:
+ // Constructor
+ CapabilitiesFactory() = default;
+
+ // Capabilities
+ shared_ptr<vshl::common::interfaces::ICapability> mGuiMetadata;
+ shared_ptr<vshl::common::interfaces::ICapability> mPhoneControl;
+ shared_ptr<vshl::common::interfaces::ICapability> mNavigation;
+
+ // Logger
+ unique_ptr<vshl::utilities::logging::Logger> mLogger;
+};
+
+} // namespace capabilities
+} // namespace vshl
+
+#endif // VSHL_CAPABILITIES_CAPABILITIESFACTORY_H_
diff --git a/src/plugins/capabilities/CapabilityMessagingService.cpp b/src/plugins/capabilities/CapabilityMessagingService.cpp
new file mode 100644
index 0000000..91b5f2b
--- /dev/null
+++ b/src/plugins/capabilities/CapabilityMessagingService.cpp
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2018-2019 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.
+ */
+#include "capabilities/CapabilityMessagingService.h"
+
+#include "capabilities/core/include/PublisherForwarder.h"
+#include "capabilities/core/include/SubscriberForwarder.h"
+
+static string TAG = "vshl::capabilities::CapabilityMessagingService";
+
+using Level = vshl::common::interfaces::ILogger::Level;
+
+namespace vshl {
+namespace capabilities {
+
+// Create a CapabilityMessagingService.
+unique_ptr<CapabilityMessagingService> CapabilityMessagingService::create(
+ shared_ptr<vshl::common::interfaces::ILogger> logger,
+ shared_ptr<vshl::common::interfaces::IAFBApi> afbApi) {
+ if (logger == nullptr) {
+ return nullptr;
+ }
+
+ if (afbApi == nullptr) {
+ logger->log(Level::ERROR, TAG, "Failed to create CapabilityMessagingService: AFB API null");
+ return nullptr;
+ }
+
+ auto capabilityMessageService =
+ std::unique_ptr<CapabilityMessagingService>(new CapabilityMessagingService(logger, afbApi));
+ return capabilityMessageService;
+}
+
+CapabilityMessagingService::~CapabilityMessagingService() {
+ mMessageChannelsMap.clear();
+}
+
+CapabilityMessagingService::CapabilityMessagingService(
+ shared_ptr<vshl::common::interfaces::ILogger> logger,
+ shared_ptr<vshl::common::interfaces::IAFBApi> afbApi) :
+ mAfbApi(afbApi),
+ mLogger(logger) {
+}
+
+// Subscribe to capability specific messages.
+bool CapabilityMessagingService::subscribe(
+ vshl::common::interfaces::IAFBRequest& request,
+ shared_ptr<common::interfaces::ICapability> capability,
+ const string action) {
+ auto capabilityName = capability->getName();
+
+ if (capabilityName.empty()) {
+ mLogger->log(Level::ERROR, TAG, "Failed to subscribe to message. Invalid input.");
+ return false;
+ }
+
+ auto messageChannel = getMessageChannel(capability);
+ return messageChannel->subscribe(request, action);
+}
+
+// Publish capability messages.
+bool CapabilityMessagingService::publish(
+ shared_ptr<common::interfaces::ICapability> capability,
+ const string action,
+ const string payload) {
+ auto capabilityName = capability->getName();
+
+ if (capabilityName.empty()) {
+ mLogger->log(Level::ERROR, TAG, "Failed to publish message. Invalid input.");
+ return false;
+ }
+
+ auto messageChannelIt = mMessageChannelsMap.find(capabilityName);
+ if (messageChannelIt == mMessageChannelsMap.end()) {
+ mLogger->log(
+ Level::ERROR,
+ TAG,
+ "Failed to publish message. Message channel doesn't exist for capability " + capabilityName);
+ return false;
+ }
+
+ return messageChannelIt->second->publish(action, payload);
+}
+
+shared_ptr<vshl::capabilities::core::MessageChannel> CapabilityMessagingService::getMessageChannel(
+ shared_ptr<common::interfaces::ICapability> capability) {
+ auto capabilityName = capability->getName();
+
+ if (capabilityName.empty()) {
+ mLogger->log(Level::ERROR, TAG, "Failed to create message channel. Invalid input.");
+ return nullptr;
+ }
+
+ auto messageChannelIt = mMessageChannelsMap.find(capabilityName);
+ if (messageChannelIt == mMessageChannelsMap.end()) {
+ mLogger->log(Level::INFO, TAG, "Creating new message channel for capability: " + capabilityName);
+ auto messageChannel = vshl::capabilities::core::MessageChannel::create(mLogger, mAfbApi, capability);
+ mMessageChannelsMap.insert(make_pair(capabilityName, messageChannel));
+ return messageChannel;
+ }
+
+ return messageChannelIt->second;
+}
+
+} // namespace capabilities
+} // namespace vshl
diff --git a/src/plugins/capabilities/CapabilityMessagingService.h b/src/plugins/capabilities/CapabilityMessagingService.h
new file mode 100644
index 0000000..535e806
--- /dev/null
+++ b/src/plugins/capabilities/CapabilityMessagingService.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2018-2019 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 VSHL_CAPABILITIES_CAPABILITYMESSAGINGSERVICE_H_
+#define VSHL_CAPABILITIES_CAPABILITYMESSAGINGSERVICE_H_
+
+#include <memory>
+#include <string>
+#include <unordered_map>
+
+#include "capabilities/core/include/MessageChannel.h"
+#include "interfaces/afb/IAFBApi.h"
+#include "interfaces/capabilities/ICapability.h"
+#include "interfaces/utilities/logging/ILogger.h"
+
+using namespace std;
+
+namespace vshl {
+namespace capabilities {
+/*
+ * This hosts service APIs that clients can use to subscribe and
+ * forward capability messages. Each capability has a name and
+ * direction (upstream/downstream). Upstream messages are from
+ * voiceagents to Apps and downstream messages are Apps to voiceagents.
+ * This class will use a factory to create publisher and subcribers for
+ * each capability and create assiociations between them.
+ */
+class CapabilityMessagingService {
+public:
+ // Create a CapabilityMessagingService.
+ static std::unique_ptr<CapabilityMessagingService>
+ create(shared_ptr<vshl::common::interfaces::ILogger> logger,
+ shared_ptr<vshl::common::interfaces::IAFBApi> afbApi);
+
+ // Subscribe to capability specific messages.
+ bool subscribe(vshl::common::interfaces::IAFBRequest &request,
+ shared_ptr<common::interfaces::ICapability> capability,
+ const string action);
+
+ // Publish capability messages.
+ bool publish(shared_ptr<common::interfaces::ICapability> capability,
+ const string action, const string payload);
+
+ // Destructor
+ ~CapabilityMessagingService();
+
+private:
+ // Constructor
+ CapabilityMessagingService(
+ shared_ptr<vshl::common::interfaces::ILogger> logger,
+ shared_ptr<vshl::common::interfaces::IAFBApi> afbApi);
+
+ // Binding API reference
+ shared_ptr<vshl::common::interfaces::IAFBApi> mAfbApi;
+
+ // Create a message channel for the capability.
+ shared_ptr<vshl::capabilities::core::MessageChannel>
+ getMessageChannel(shared_ptr<common::interfaces::ICapability> capability);
+
+ // Map of capabilities to message channels.
+ unordered_map<string, shared_ptr<vshl::capabilities::core::MessageChannel>>
+ mMessageChannelsMap;
+
+ // Logger
+ shared_ptr<vshl::common::interfaces::ILogger> mLogger;
+};
+
+} // namespace capabilities
+} // namespace vshl
+
+#endif // VSHL_CAPABILITIES_CAPABILITYMESSAGINGSERVICE_H_
diff --git a/src/plugins/capabilities/communication/include/PhoneControlCapability.h b/src/plugins/capabilities/communication/include/PhoneControlCapability.h
new file mode 100644
index 0000000..55ada8d
--- /dev/null
+++ b/src/plugins/capabilities/communication/include/PhoneControlCapability.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2018-2019 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 VSHL_CAPABILITIES_PHONECONTROL_CAPABILITY_H_
+#define VSHL_CAPABILITIES_PHONECONTROL_CAPABILITY_H_
+
+#include <memory>
+
+#include "interfaces/capabilities/ICapability.h"
+
+namespace vshl {
+namespace capabilities {
+namespace phonecontrol {
+
+/*
+ * PhoneControl capability. Calls are initiated in the endpoint.
+ */
+class PhoneControl : public common::interfaces::ICapability {
+public:
+ // Create a PhoneControl.
+ static std::shared_ptr<PhoneControl> create();
+
+ ~PhoneControl() = default;
+
+protected:
+ string getName() const override;
+
+ list<string> getUpstreamMessages() const override;
+
+ list<string> getDownstreamMessages() const override;
+
+private:
+ PhoneControl() = default;
+};
+
+} // namespace phonecontrol
+} // namespace capabilities
+} // namespace vshl
+
+#endif // VSHL_CAPABILITIES_PHONECONTROL_CAPABILITY_H_
diff --git a/src/plugins/capabilities/communication/include/PhoneControlMessages.h b/src/plugins/capabilities/communication/include/PhoneControlMessages.h
new file mode 100644
index 0000000..4c68455
--- /dev/null
+++ b/src/plugins/capabilities/communication/include/PhoneControlMessages.h
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2018-2019 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 VSHL_CAPABILITIES_PHONECONTROL_MESSAGES_H_
+#define VSHL_CAPABILITIES_PHONECONTROL_MESSAGES_H_
+
+#include <list>
+#include <string>
+
+using namespace std;
+
+namespace vshl {
+namespace capabilities {
+namespace phonecontrol {
+
+static string NAME = "phonecontrol";
+
+// Supported actions from VA -> Apps
+/* Dial message sent from VA to app handling the calling.
+ *
+ * Payload
+ * {
+ * "callId": "{{STRING}}",
+ * "callee": {
+ * "details": "{{STRING}}",
+ * "defaultAddress": {
+ * "protocol": "{{STRING}}",
+ * "format": "{{STRING}}",
+ * "value": "{{STRING}}"
+ * },
+ * "alternativeAddresses": [{
+ * "protocol": "{{STRING}}",
+ * "format": "{{STRING}}",
+ * "value": {{STRING}}
+ * }]
+ * }
+ * }
+ * }
+ *
+ * callId (required): A unique identifier for the call
+ * callee (required): The destination of the outgoing call
+ * callee.details (optional): Descriptive information about the callee
+ * callee.defaultAddress (required): The default address to use for calling the callee
+ * callee.alternativeAddresses (optional): An array of alternate addresses for the existing callee
+ * address.protocol (required): The protocol for this address of the callee (e.g. PSTN, SIP, H323, etc.)
+ * address.format (optional): The format for this address of the callee (e.g. E.164, E.163, E.123, DIN5008, etc.)
+ * address.value (required): The address of the callee.
+ *
+ */
+static string PHONECONTROL_DIAL = "dial";
+
+// Supported actions from Apps -> VA
+/*
+ * App notifies the voiceagents of a change in connection state of a calling device.
+ *
+ * Payload
+ * {
+ * "state" : "{{STRING}}" // CONNECTED or DISCONNECTED
+ * }
+ */
+static string PHONECONTROL_CONNECTIONSTATE_CHANGED = "connection_state_changed";
+/*
+ * App notifies the voiceagents that call is activated
+ *
+ * callId must match the one that is sent by VA with DIAL message above.
+ *
+ * Payload
+ * {
+ * "callId" : "{{STRING}}"
+ * }
+ */
+static string PHONECONTROL_CALL_ACTIVATED = "call_activated";
+/*
+ * App notifies the voiceagents of an error in initiating or maintaining a
+ * call on a calling device
+ *
+ * callId must match the one that is sent by VA with DIAL message above.
+ * error: below status codes.
+ * 4xx: Validation failure for the input from the DIAL message
+ * 500: Internal error on the platform unrelated to the cellular network
+ * 503: Error on the platform related to the cellular network
+ *
+ * Payload
+ * {
+ * "callId" : "{{STRING}}"
+ * "error" : "{{STRING}}"
+ * }
+ */
+static string PHONECONTROL_CALL_FAILED = "call_failed";
+/*
+ * App notifies the voiceagents that call is terminated
+ *
+ * callId must match the one that is sent by VA with DIAL message above.
+ *
+ * Payload
+ * {
+ * "callId" : "{{STRING}}"
+ * }
+ */
+static string PHONECONTROL_CALL_TERMINATED = "call_terminated";
+
+// List of actions that are delivered from VA -> Apps
+static list<string> PHONECONTROL_UPSTREAM_ACTIONS = {
+ PHONECONTROL_DIAL,
+};
+
+// List of actions that are delivered from Apps -> VA
+static list<string> PHONECONTROL_DOWNSTREAM_ACTIONS = {PHONECONTROL_CONNECTIONSTATE_CHANGED,
+ PHONECONTROL_CALL_ACTIVATED,
+ PHONECONTROL_CALL_FAILED,
+ PHONECONTROL_CALL_TERMINATED};
+
+} // namespace phonecontrol
+} // namespace capabilities
+} // namespace vshl
+
+#endif // VSHL_CAPABILITIES_PHONECONTROL_MESSAGES_H_
diff --git a/src/plugins/capabilities/communication/src/PhoneControlCapability.cpp b/src/plugins/capabilities/communication/src/PhoneControlCapability.cpp
new file mode 100644
index 0000000..6a74d5a
--- /dev/null
+++ b/src/plugins/capabilities/communication/src/PhoneControlCapability.cpp
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2018-2019 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.
+ */
+#include "capabilities/communication/include/PhoneControlCapability.h"
+#include "capabilities/communication/include/PhoneControlMessages.h"
+
+namespace vshl {
+namespace capabilities {
+namespace phonecontrol {
+
+// Create a phonecontrol.
+shared_ptr<PhoneControl> PhoneControl::create() {
+ auto phonecontrol = std::shared_ptr<PhoneControl>(new PhoneControl());
+ return phonecontrol;
+}
+
+string PhoneControl::getName() const {
+ return NAME;
+}
+
+list<string> PhoneControl::getUpstreamMessages() const {
+ return PHONECONTROL_UPSTREAM_ACTIONS;
+}
+
+list<string> PhoneControl::getDownstreamMessages() const {
+ return PHONECONTROL_DOWNSTREAM_ACTIONS;
+}
+
+} // namespace phonecontrol
+} // namespace capabilities
+} // namespace vshl
diff --git a/src/plugins/capabilities/core/include/MessageChannel.h b/src/plugins/capabilities/core/include/MessageChannel.h
new file mode 100644
index 0000000..504e241
--- /dev/null
+++ b/src/plugins/capabilities/core/include/MessageChannel.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2018-2019 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 VSHL_CAPABILITIES_CORE_MESSAGECHANNEL_H_
+#define VSHL_CAPABILITIES_CORE_MESSAGECHANNEL_H_
+
+#include <memory>
+
+#include "capabilities/core/include/PublisherForwarder.h"
+#include "capabilities/core/include/SubscriberForwarder.h"
+#include "interfaces/afb/IAFBApi.h"
+#include "interfaces/capabilities/ICapability.h"
+#include "interfaces/utilities/logging/ILogger.h"
+
+using namespace std;
+
+namespace vshl {
+namespace capabilities {
+namespace core {
+/*
+ * MessageChannel has one end as publisher forwarder and the other end
+ * as subscriber forwarder.
+ */
+class MessageChannel {
+public:
+ // Create a MessageChannel.
+ static std::shared_ptr<MessageChannel>
+ create(shared_ptr<vshl::common::interfaces::ILogger> logger,
+ shared_ptr<vshl::common::interfaces::IAFBApi> afbApi,
+ shared_ptr<vshl::common::interfaces::ICapability> capability);
+
+ // Sends the message
+ bool publish(const string action, const string payload);
+
+ // Subscribe
+ bool subscribe(vshl::common::interfaces::IAFBRequest &request,
+ const string action);
+
+ // Destructor
+ virtual ~MessageChannel() = default;
+
+private:
+ // Constructor
+ MessageChannel(shared_ptr<vshl::common::interfaces::ILogger> logger,
+ shared_ptr<vshl::common::interfaces::IAFBApi> afbApi,
+ shared_ptr<vshl::common::interfaces::ICapability> capability);
+
+ // Forwarders
+ shared_ptr<PublisherForwarder> mPublisherForwarder;
+ shared_ptr<SubscriberForwarder> mSubscriberForwarder;
+};
+
+} // namespace core
+} // namespace capabilities
+} // namespace vshl
+
+#endif // VSHL_CAPABILITIES_CORE_MESSAGECHANNEL_H_
diff --git a/src/plugins/capabilities/core/include/PublisherForwarder.h b/src/plugins/capabilities/core/include/PublisherForwarder.h
new file mode 100644
index 0000000..9cc89b5
--- /dev/null
+++ b/src/plugins/capabilities/core/include/PublisherForwarder.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2018-2019 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 VSHL_CAPABILITIES_CORE_PUBLISHERFORWARDER_H_
+#define VSHL_CAPABILITIES_CORE_PUBLISHERFORWARDER_H_
+
+#include <memory>
+
+#include "capabilities/core/include/SubscriberForwarder.h"
+
+#include "interfaces/afb/IAFBApi.h"
+#include "interfaces/capabilities/ICapability.h"
+#include "interfaces/utilities/logging/ILogger.h"
+
+using namespace std;
+
+namespace vshl {
+namespace capabilities {
+namespace core {
+/*
+ * This class is responsible for forwarding the messages to be published
+ * to subscriber forwarder. Subscriber forwarder will deliver the messages
+ * as AFB Events to all the subscribed clients.
+ * There is one PublisherForwarder and one SubscriberForwarder per capability.
+ */
+class PublisherForwarder {
+public:
+ // Create a PublisherForwarder.
+ static std::shared_ptr<PublisherForwarder> create(
+ shared_ptr<vshl::common::interfaces::ILogger> logger,
+ shared_ptr<vshl::common::interfaces::ICapability> capability);
+
+ // Connect a subscriber forwarder to this publisher forwarder
+ void setSubscriberForwarder(shared_ptr<SubscriberForwarder> subscriberForwarder);
+
+ // Forward message to the subscriber forwarder
+ bool forwardMessage(const string action, const string payload);
+
+ // Destructor
+ ~PublisherForwarder();
+
+private:
+ // Constructor
+ PublisherForwarder(
+ shared_ptr<vshl::common::interfaces::ILogger> logger,
+ shared_ptr<vshl::common::interfaces::ICapability> capability);
+
+ // Subscriber forwarder connected to this publisher forwarder.
+ shared_ptr<SubscriberForwarder> mSubscriberForwarder;
+
+ // Capability
+ shared_ptr<vshl::common::interfaces::ICapability> mCapability;
+
+ // Logger
+ shared_ptr<vshl::common::interfaces::ILogger> mLogger;
+};
+
+} // namespace core
+} // namespace capabilities
+} // namespace vshl
+
+#endif // VSHL_CAPABILITIES_CORE_PUBLISHERFORWARDER_H_
diff --git a/src/plugins/capabilities/core/include/SubscriberForwarder.h b/src/plugins/capabilities/core/include/SubscriberForwarder.h
new file mode 100644
index 0000000..94c04bf
--- /dev/null
+++ b/src/plugins/capabilities/core/include/SubscriberForwarder.h
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2018-2019 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 VSHL_CAPABILITIES_CORE_SUBSCRIBERFORWARDER_H_
+#define VSHL_CAPABILITIES_CORE_SUBSCRIBERFORWARDER_H_
+
+#include <memory>
+#include <string>
+#include <unordered_map>
+
+#include "interfaces/afb/IAFBApi.h"
+#include "interfaces/capabilities/ICapability.h"
+#include "interfaces/utilities/logging/ILogger.h"
+
+using namespace std;
+
+namespace vshl {
+namespace capabilities {
+namespace core {
+/*
+ * This class is responsible for forwarding the messages publishing
+ * to the actual clients using AFB.
+ */
+class SubscriberForwarder {
+public:
+ // Create a SubscriberForwarder.
+ static std::shared_ptr<SubscriberForwarder>
+ create(shared_ptr<vshl::common::interfaces::ILogger> logger,
+ shared_ptr<vshl::common::interfaces::IAFBApi> afbApi,
+ shared_ptr<vshl::common::interfaces::ICapability> capability);
+
+ // Publish a capability message to the actual client.
+ bool forwardMessage(const string action, const string payload);
+
+ // Subscribe
+ bool subscribe(vshl::common::interfaces::IAFBRequest &request,
+ const string action);
+
+ // Destructor
+ ~SubscriberForwarder();
+
+private:
+ // Constructor
+ SubscriberForwarder(
+ shared_ptr<vshl::common::interfaces::ILogger> logger,
+ shared_ptr<vshl::common::interfaces::IAFBApi> afbApi,
+ shared_ptr<vshl::common::interfaces::ICapability> capability);
+
+ // Creates both upstream and downstream events
+ void createEvents();
+
+ // Binding API reference
+ shared_ptr<vshl::common::interfaces::IAFBApi> mAfbApi;
+
+ // Capability
+ shared_ptr<vshl::common::interfaces::ICapability> mCapability;
+
+ // Maps of capability action events to its corresponding Event object.
+ // Event name maps to Action Name
+ unordered_map<string, shared_ptr<common::interfaces::IAFBApi::IAFBEvent>>
+ mUpstreamEventsMap;
+ unordered_map<string, shared_ptr<common::interfaces::IAFBApi::IAFBEvent>>
+ mDownstreamEventsMap;
+
+ // Logger
+ shared_ptr<vshl::common::interfaces::ILogger> mLogger;
+};
+
+} // namespace core
+} // namespace capabilities
+} // namespace vshl
+
+#endif // VSHL_CAPABILITIES_CORE_SUBSCRIBERFORWARDER_H_
diff --git a/src/plugins/capabilities/core/src/MessageChannel.cpp b/src/plugins/capabilities/core/src/MessageChannel.cpp
new file mode 100644
index 0000000..eaa1349
--- /dev/null
+++ b/src/plugins/capabilities/core/src/MessageChannel.cpp
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2018-2019 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.
+ */
+#include "capabilities/core/include/MessageChannel.h"
+
+namespace vshl {
+namespace capabilities {
+namespace core {
+
+// Create a MessageChannel.
+std::shared_ptr<MessageChannel> MessageChannel::create(
+ shared_ptr<vshl::common::interfaces::ILogger> logger,
+ shared_ptr<vshl::common::interfaces::IAFBApi> api,
+ shared_ptr<vshl::common::interfaces::ICapability> capability) {
+ auto messageChannel = std::shared_ptr<MessageChannel>(new MessageChannel(logger, api, capability));
+ return messageChannel;
+}
+
+MessageChannel::MessageChannel(
+ shared_ptr<vshl::common::interfaces::ILogger> logger,
+ shared_ptr<vshl::common::interfaces::IAFBApi> api,
+ shared_ptr<vshl::common::interfaces::ICapability> capability) {
+ // Subscriber forwarder
+ mSubscriberForwarder = SubscriberForwarder::create(logger, api, capability);
+ // Publisher forwarder
+ mPublisherForwarder = PublisherForwarder::create(logger, capability);
+ mPublisherForwarder->setSubscriberForwarder(mSubscriberForwarder);
+}
+
+bool MessageChannel::publish(const string action, const string payload) {
+ return mPublisherForwarder->forwardMessage(action, payload);
+}
+
+bool MessageChannel::subscribe(vshl::common::interfaces::IAFBRequest& request, const string action) {
+ return mSubscriberForwarder->subscribe(request, action);
+}
+
+} // namespace core
+} // namespace capabilities
+} // namespace vshl
diff --git a/src/plugins/capabilities/core/src/PublisherForwarder.cpp b/src/plugins/capabilities/core/src/PublisherForwarder.cpp
new file mode 100644
index 0000000..81de6a0
--- /dev/null
+++ b/src/plugins/capabilities/core/src/PublisherForwarder.cpp
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2018-2019 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.
+ */
+#include "capabilities/core/include/PublisherForwarder.h"
+
+static string TAG = "vshl::capabilities::PublisherForwarder";
+
+using Level = vshl::common::interfaces::ILogger::Level;
+
+namespace vshl {
+namespace capabilities {
+namespace core {
+
+// Create a PublisherForwarder.
+std::shared_ptr<PublisherForwarder> PublisherForwarder::create(
+ shared_ptr<vshl::common::interfaces::ILogger> logger,
+ shared_ptr<vshl::common::interfaces::ICapability> capability) {
+ if (logger == nullptr) {
+ return nullptr;
+ }
+
+ if (capability == nullptr) {
+ logger->log(Level::ERROR, TAG, "Failed to create PublisherForwarder: Capability null");
+ return nullptr;
+ }
+
+ auto publisherForwarder = std::shared_ptr<PublisherForwarder>(new PublisherForwarder(logger, capability));
+ return publisherForwarder;
+}
+
+// Constructor
+PublisherForwarder::PublisherForwarder(
+ shared_ptr<vshl::common::interfaces::ILogger> logger,
+ shared_ptr<vshl::common::interfaces::ICapability> capability) {
+ mCapability = capability;
+ mLogger = logger;
+}
+
+// Destructor
+PublisherForwarder::~PublisherForwarder() {
+}
+
+void PublisherForwarder::setSubscriberForwarder(shared_ptr<SubscriberForwarder> subscriberForwarder) {
+ mSubscriberForwarder = subscriberForwarder;
+}
+
+bool PublisherForwarder::forwardMessage(const string action, const string payload) {
+ if (!mSubscriberForwarder) {
+ mLogger->log(Level::ERROR, TAG, "Failed to forward message for capability: " + mCapability->getName());
+ return false;
+ }
+
+ return mSubscriberForwarder->forwardMessage(action, payload);
+}
+
+} // namespace core
+} // namespace capabilities
+} // namespace vshl
diff --git a/src/plugins/capabilities/core/src/SubscriberForwarder.cpp b/src/plugins/capabilities/core/src/SubscriberForwarder.cpp
new file mode 100644
index 0000000..ea42305
--- /dev/null
+++ b/src/plugins/capabilities/core/src/SubscriberForwarder.cpp
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2018-2019 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.
+ */
+#include "capabilities/core/include/SubscriberForwarder.h"
+
+static string TAG = "vshl::capabilities::SubscriberForwarder";
+
+using Level = vshl::common::interfaces::ILogger::Level;
+
+namespace vshl {
+namespace capabilities {
+namespace core {
+
+// Create a SubscriberForwarder.
+std::shared_ptr<SubscriberForwarder> SubscriberForwarder::create(
+ shared_ptr<vshl::common::interfaces::ILogger> logger,
+ shared_ptr<vshl::common::interfaces::IAFBApi> afbApi,
+ shared_ptr<vshl::common::interfaces::ICapability> capability) {
+ if (logger == nullptr) {
+ return nullptr;
+ }
+
+ if (afbApi == nullptr) {
+ logger->log(Level::ERROR, TAG, "Failed to create SubscriberForwarder: AFB API null");
+ return nullptr;
+ }
+
+ if (capability == nullptr) {
+ logger->log(Level::ERROR, TAG, "Failed to create SubscriberForwarder: Capability null");
+ return nullptr;
+ }
+
+ auto subscriberForwarder =
+ std::shared_ptr<SubscriberForwarder>(new SubscriberForwarder(logger, afbApi, capability));
+ return subscriberForwarder;
+}
+
+SubscriberForwarder::SubscriberForwarder(
+ shared_ptr<vshl::common::interfaces::ILogger> logger,
+ shared_ptr<vshl::common::interfaces::IAFBApi> afbApi,
+ shared_ptr<vshl::common::interfaces::ICapability> capability) :
+ mAfbApi(afbApi),
+ mLogger(logger),
+ mCapability(capability) {
+ createEvents();
+}
+
+SubscriberForwarder::~SubscriberForwarder() {
+ mUpstreamEventsMap.clear();
+ mDownstreamEventsMap.clear();
+}
+
+void SubscriberForwarder::createEvents() {
+ if (!mCapability) {
+ mLogger->log(Level::NOTICE, TAG, "Create Events failed. No capability assigned.");
+ return;
+ }
+
+ // Upstream events
+ auto upstreamEvents = mCapability->getUpstreamMessages();
+ for (auto upstreamEventName : upstreamEvents) {
+ auto it = mUpstreamEventsMap.find(upstreamEventName);
+ if (it == mUpstreamEventsMap.end() && mAfbApi) {
+ // create a new event and add it to the map.
+ shared_ptr<common::interfaces::IAFBApi::IAFBEvent> event = mAfbApi->createEvent(upstreamEventName);
+ if (event == nullptr) {
+ mLogger->log(Level::ERROR, TAG, "Failed to create upstream event: " + upstreamEventName);
+ } else {
+ mUpstreamEventsMap.insert(make_pair(upstreamEventName, event));
+ }
+ }
+ }
+
+ // Downstream events
+ auto downstreamEvents = mCapability->getDownstreamMessages();
+ for (auto downstreamEventName : downstreamEvents) {
+ auto it = mDownstreamEventsMap.find(downstreamEventName);
+ if (it == mDownstreamEventsMap.end() && mAfbApi) {
+ // create a new event and add it to the map.
+ shared_ptr<common::interfaces::IAFBApi::IAFBEvent> event = mAfbApi->createEvent(downstreamEventName);
+ if (event == nullptr) {
+ mLogger->log(Level::ERROR, TAG, "Failed to create downstream event: " + downstreamEventName);
+ } else {
+ mDownstreamEventsMap.insert(make_pair(downstreamEventName, event));
+ }
+ }
+ }
+}
+
+bool SubscriberForwarder::forwardMessage(const string action, const string payload) {
+ auto upstreamEventIt = mUpstreamEventsMap.find(action);
+ if (upstreamEventIt != mUpstreamEventsMap.end()) {
+ mLogger->log(Level::NOTICE, TAG, "Publishing upstream event: " + action);
+ upstreamEventIt->second->publishEvent(json_object_new_string(payload.c_str()));
+ return true;
+ }
+
+ auto downstreamEventIt = mDownstreamEventsMap.find(action);
+ if (downstreamEventIt != mDownstreamEventsMap.end()) {
+ mLogger->log(Level::NOTICE, TAG, "Publishing downstream event: " + action);
+ downstreamEventIt->second->publishEvent(json_object_new_string(payload.c_str()));
+ return true;
+ }
+
+ mLogger->log(Level::NOTICE, TAG, "Failed to publish upstream event: " + action);
+ return false;
+}
+
+bool SubscriberForwarder::subscribe(vshl::common::interfaces::IAFBRequest& request, const string action) {
+ auto upstreamEventIt = mUpstreamEventsMap.find(action);
+ if (upstreamEventIt != mUpstreamEventsMap.end()) {
+ mLogger->log(Level::NOTICE, TAG, "Subscribing to upstream event: " + action);
+ return upstreamEventIt->second->subscribe(request);
+ }
+
+ auto downstreamEventIt = mDownstreamEventsMap.find(action);
+ if (downstreamEventIt != mDownstreamEventsMap.end()) {
+ mLogger->log(Level::NOTICE, TAG, "Subscribing to downstream event: " + action);
+ return downstreamEventIt->second->subscribe(request);
+ }
+
+ mLogger->log(Level::NOTICE, TAG, "Failed to subscribe to upstream event: " + action);
+ return false;
+}
+
+} // namespace core
+} // namespace capabilities
+} // namespace vshl
diff --git a/src/plugins/capabilities/guimetadata/include/GuiMetadataCapability.h b/src/plugins/capabilities/guimetadata/include/GuiMetadataCapability.h
new file mode 100644
index 0000000..199f49a
--- /dev/null
+++ b/src/plugins/capabilities/guimetadata/include/GuiMetadataCapability.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2018-2019 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 VSHL_CAPABILITIES_GUIMETADATA_CAPABILITY_H_
+#define VSHL_CAPABILITIES_GUIMETADATA_CAPABILITY_H_
+
+#include <memory>
+
+#include "interfaces/capabilities/ICapability.h"
+
+namespace vshl {
+namespace capabilities {
+namespace guimetadata {
+
+/*
+ * GuiMetadata capability
+ */
+class GuiMetadata : public common::interfaces::ICapability {
+public:
+ // Create a GuiMetadata.
+ static std::shared_ptr<GuiMetadata> create();
+
+ ~GuiMetadata() = default;
+
+protected:
+ string getName() const override;
+
+ list<string> getUpstreamMessages() const override;
+
+ list<string> getDownstreamMessages() const override;
+
+private:
+ GuiMetadata() = default;
+};
+
+} // namespace guimetadata
+} // namespace capabilities
+} // namespace vshl
+
+#endif // VSHL_CAPABILITIES_GUIMETADATA_CAPABILITY_H_
diff --git a/src/plugins/capabilities/guimetadata/include/GuiMetadataMessages.h b/src/plugins/capabilities/guimetadata/include/GuiMetadataMessages.h
new file mode 100644
index 0000000..783b401
--- /dev/null
+++ b/src/plugins/capabilities/guimetadata/include/GuiMetadataMessages.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2018-2019 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 VSHL_CAPABILITIES_GUIMETADATA_ACTIONS_H_
+#define VSHL_CAPABILITIES_GUIMETADATA_ACTIONS_H_
+
+#include <list>
+#include <string>
+
+using namespace std;
+
+namespace vshl {
+namespace capabilities {
+namespace guimetadata {
+
+static string NAME = "guimetadata";
+
+// Supported actions from VA -> Apps
+static string GUIMETADATA_RENDER_TEMPLATE = "render_template";
+static string GUIMETADATA_CLEAR_TEMPLATE = "clear_template";
+static string GUIMETADATA_RENDER_PLAYER_INFO = "render_player_info";
+static string GUIMETADATA_CLEAR_PLAYER_INFO = "clear_player_info";
+
+// Supported actions from Apps -> VA
+
+// List of actions that are delivered from VA -> Apps
+static list<string> GUIMETADATA_UPSTREAM_ACTIONS = {GUIMETADATA_RENDER_TEMPLATE,
+ GUIMETADATA_CLEAR_TEMPLATE,
+ GUIMETADATA_RENDER_PLAYER_INFO,
+ GUIMETADATA_CLEAR_PLAYER_INFO};
+
+// List of actions that are delivered from Apps -> VA
+static list<string> GUIMETADATA_DOWNSTREAM_ACTIONS = {};
+
+} // namespace guimetadata
+} // namespace capabilities
+} // namespace vshl
+
+#endif // VSHL_CAPABILITIES_GUIMETADATA_ACTIONS_H_
diff --git a/src/plugins/capabilities/guimetadata/src/GuiMetadataCapability.cpp b/src/plugins/capabilities/guimetadata/src/GuiMetadataCapability.cpp
new file mode 100644
index 0000000..106fe99
--- /dev/null
+++ b/src/plugins/capabilities/guimetadata/src/GuiMetadataCapability.cpp
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2018-2019 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.
+ */
+#include "capabilities/guimetadata/include/GuiMetadataCapability.h"
+#include "capabilities/guimetadata/include/GuiMetadataMessages.h"
+
+namespace vshl {
+namespace capabilities {
+namespace guimetadata {
+
+// Create a GuiMetadata.
+shared_ptr<GuiMetadata> GuiMetadata::create() {
+ auto guiMetadata = std::shared_ptr<GuiMetadata>(new GuiMetadata());
+ return guiMetadata;
+}
+
+string GuiMetadata::getName() const {
+ return NAME;
+}
+
+list<string> GuiMetadata::getUpstreamMessages() const {
+ return GUIMETADATA_UPSTREAM_ACTIONS;
+}
+
+list<string> GuiMetadata::getDownstreamMessages() const {
+ return GUIMETADATA_DOWNSTREAM_ACTIONS;
+}
+
+} // namespace guimetadata
+} // namespace capabilities
+} // namespace vshl
diff --git a/src/plugins/capabilities/navigation/include/NavigationCapability.h b/src/plugins/capabilities/navigation/include/NavigationCapability.h
new file mode 100644
index 0000000..66109d5
--- /dev/null
+++ b/src/plugins/capabilities/navigation/include/NavigationCapability.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2018-2019 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 VSHL_CAPABILITIES_NAVIGATION_CAPABILITY_H_
+#define VSHL_CAPABILITIES_NAVIGATION_CAPABILITY_H_
+
+#include <memory>
+
+#include "interfaces/capabilities/ICapability.h"
+
+namespace vshl {
+namespace capabilities {
+namespace navigation {
+
+/*
+ * Navigation capability
+ */
+class Navigation : public common::interfaces::ICapability {
+public:
+ // Create a Navigation.
+ static std::shared_ptr<Navigation> create();
+
+ ~Navigation() = default;
+
+protected:
+ string getName() const override;
+
+ list<string> getUpstreamMessages() const override;
+
+ list<string> getDownstreamMessages() const override;
+
+private:
+ Navigation() = default;
+};
+
+} // namespace navigation
+} // namespace capabilities
+} // namespace vshl
+
+#endif // VSHL_CAPABILITIES_NAVIGATION_CAPABILITY_H_
diff --git a/src/plugins/capabilities/navigation/include/NavigationMessages.h b/src/plugins/capabilities/navigation/include/NavigationMessages.h
new file mode 100644
index 0000000..aed9b9e
--- /dev/null
+++ b/src/plugins/capabilities/navigation/include/NavigationMessages.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2018-2019 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 VSHL_CAPABILITIES_NAVIGATION_ACTIONS_H_
+#define VSHL_CAPABILITIES_NAVIGATION_ACTIONS_H_
+
+#include <list>
+#include <string>
+
+using namespace std;
+
+namespace vshl {
+namespace capabilities {
+namespace navigation {
+
+static string NAME = "navigation";
+
+// Supported actions from VA -> Apps
+static string NAVIGATION_SET_DESTINATION = "set_destination";
+static string NAVIGATION_CANCEL = "cancel_navigation";
+
+// Supported actions from Apps -> VA
+
+// List of actions that are delivered from VA -> Apps
+static list<string> NAVIGATION_UPSTREAM_ACTIONS = {
+ NAVIGATION_SET_DESTINATION,
+ NAVIGATION_CANCEL,
+};
+
+// List of actions that are delivered from Apps -> VA
+static list<string> NAVIGATION_DOWNSTREAM_ACTIONS = {};
+
+} // namespace navigation
+} // namespace capabilities
+} // namespace vshl
+
+#endif // VSHL_CAPABILITIES_NAVIGATION_ACTIONS_H_
diff --git a/src/plugins/capabilities/navigation/src/NavigationCapability.cpp b/src/plugins/capabilities/navigation/src/NavigationCapability.cpp
new file mode 100644
index 0000000..d4a5119
--- /dev/null
+++ b/src/plugins/capabilities/navigation/src/NavigationCapability.cpp
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2018-2019 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.
+ */
+#include "capabilities/navigation/include/NavigationCapability.h"
+#include "capabilities/navigation/include/NavigationMessages.h"
+
+namespace vshl {
+namespace capabilities {
+namespace navigation {
+
+// Create a Navigation.
+shared_ptr<Navigation> Navigation::create() {
+ auto navigation = std::shared_ptr<Navigation>(new Navigation());
+ return navigation;
+}
+
+string Navigation::getName() const {
+ return NAME;
+}
+
+list<string> Navigation::getUpstreamMessages() const {
+ return NAVIGATION_UPSTREAM_ACTIONS;
+}
+
+list<string> Navigation::getDownstreamMessages() const {
+ return NAVIGATION_DOWNSTREAM_ACTIONS;
+}
+
+} // namespace navigation
+} // namespace capabilities
+} // namespace vshl
diff --git a/src/plugins/capabilities/test/CapabilityMessagingServiceTest.cpp b/src/plugins/capabilities/test/CapabilityMessagingServiceTest.cpp
new file mode 100644
index 0000000..741a8aa
--- /dev/null
+++ b/src/plugins/capabilities/test/CapabilityMessagingServiceTest.cpp
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2018-2019 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.
+ */
+#include <gtest/gtest.h>
+
+#include "capabilities/CapabilityMessagingService.h"
+
+#include "test/common/ConsoleLogger.h"
+#include "test/mocks/AFBApiMock.h"
+#include "test/mocks/AFBEventMock.h"
+#include "test/mocks/AFBRequestMock.h"
+#include "test/mocks/CapabilityMock.h"
+
+using namespace vshl::common::interfaces;
+using namespace vshl::capabilities;
+using namespace vshl::test::common;
+
+namespace vshl {
+namespace test {
+
+class CapabilityMessagingServiceTest : public ::testing::Test {
+protected:
+ void SetUp() override {
+ mConsoleLogger = std::make_shared<ConsoleLogger>();
+ mAfbApi = std::make_shared<::testing::NiceMock<AFBApiMock>>();
+ }
+
+ std::shared_ptr<::testing::NiceMock<AFBApiMock>> mAfbApi;
+ std::shared_ptr<ConsoleLogger> mConsoleLogger;
+};
+
+TEST_F(CapabilityMessagingServiceTest, failsInitializationOnInvalidParams) {
+ auto service = CapabilityMessagingService::create(mConsoleLogger, nullptr);
+ ASSERT_EQ(service, nullptr);
+
+ service = CapabilityMessagingService::create(nullptr, mAfbApi);
+ ASSERT_EQ(service, nullptr);
+}
+
+TEST_F(CapabilityMessagingServiceTest, initializesSuccessfully) {
+ auto service = CapabilityMessagingService::create(mConsoleLogger, mAfbApi);
+ ASSERT_NE(service, nullptr);
+}
+
+TEST_F(CapabilityMessagingServiceTest, canSubscribeAndPublishCapabilityEvents) {
+ auto service = CapabilityMessagingService::create(mConsoleLogger, mAfbApi);
+
+ auto capability = std::make_shared<::testing::NiceMock<CapabilityMock>>();
+ std::list<std::string> upstreamEvents({"up-ev1", "up-ev2"});
+ std::list<std::string> downstreamEvents({"down-ev1", "down-ev2"});
+ std::string capabilityName = "weather";
+
+ ON_CALL(*capability, getName()).WillByDefault(::testing::Return(capabilityName));
+ ON_CALL(*capability, getUpstreamMessages()).WillByDefault(::testing::Return(upstreamEvents));
+ ON_CALL(*capability, getDownstreamMessages()).WillByDefault(::testing::Return(downstreamEvents));
+
+ // We don't care if and how many times subscribe method is called on event.
+ std::shared_ptr<AFBEventMock> mockEvent(new ::testing::NiceMock<AFBEventMock>());
+ ON_CALL(*mockEvent, subscribe(::testing::_)).WillByDefault(::testing::Return(true));
+ ON_CALL(*mockEvent, publishEvent(::testing::_)).WillByDefault(::testing::Return(true));
+ auto eventCreator = [mockEvent](const std::string& eventName) -> std::shared_ptr<IAFBApi::IAFBEvent> {
+ mockEvent->setName(eventName);
+ return mockEvent;
+ };
+
+ ON_CALL(*mAfbApi, createEvent(::testing::_)).WillByDefault(::testing::Invoke(eventCreator));
+
+ auto request = std::make_shared<::testing::StrictMock<AFBRequestMock>>();
+ std::string payload = "The answer to life the universe and everything = 42";
+
+ bool result = service->publish(capability, *upstreamEvents.begin(), payload);
+ ASSERT_FALSE(result); // Without subscribing to the event. Publish should fail.
+
+ result = service->subscribe(*request, capability, *upstreamEvents.begin());
+ ASSERT_TRUE(result);
+
+ result = service->subscribe(*request, capability, *downstreamEvents.begin());
+ ASSERT_TRUE(result);
+
+ result = service->publish(capability, *downstreamEvents.begin(), payload);
+ ASSERT_TRUE(result);
+}
+
+} // namespace test
+} // namespace vshl \ No newline at end of file
diff --git a/src/plugins/capabilities/test/PublisherForwarderTest.cpp b/src/plugins/capabilities/test/PublisherForwarderTest.cpp
new file mode 100644
index 0000000..d0ba319
--- /dev/null
+++ b/src/plugins/capabilities/test/PublisherForwarderTest.cpp
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2018-2019 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.
+ */
+#include <gtest/gtest.h>
+
+#include "capabilities/core/include/PublisherForwarder.h"
+#include "capabilities/core/include/SubscriberForwarder.h"
+
+#include "test/common/ConsoleLogger.h"
+#include "test/mocks/AFBApiMock.h"
+#include "test/mocks/AFBEventMock.h"
+#include "test/mocks/CapabilityMock.h"
+
+using namespace vshl::common::interfaces;
+using namespace vshl::capabilities::core;
+using namespace vshl::test::common;
+
+namespace vshl {
+namespace test {
+
+class PublisherForwarderTest : public ::testing::Test {
+protected:
+ void SetUp() override {
+ mConsoleLogger = std::make_shared<ConsoleLogger>();
+ mAfbApi = std::make_shared<::testing::StrictMock<AFBApiMock>>();
+
+ mEventCreatorFn = [](const std::string& eventName) -> std::shared_ptr<IAFBApi::IAFBEvent> {
+ std::shared_ptr<AFBEventMock> mockEvent(new ::testing::StrictMock<AFBEventMock>());
+ mockEvent->setName(eventName);
+ return mockEvent;
+ };
+ }
+
+ std::shared_ptr<SubscriberForwarder> createSubscriberForwarder(
+ std::shared_ptr<::testing::StrictMock<CapabilityMock>> capability) {
+ EXPECT_CALL(*capability, getUpstreamMessages()).Times(1);
+ EXPECT_CALL(*capability, getDownstreamMessages()).Times(1);
+
+ return SubscriberForwarder::create(mConsoleLogger, mAfbApi, capability);
+ }
+
+ std::shared_ptr<PublisherForwarder> createPublisherForwarder(
+ std::shared_ptr<::testing::StrictMock<CapabilityMock>> capability) {
+ return PublisherForwarder::create(mConsoleLogger, capability);
+ }
+
+ std::shared_ptr<::testing::StrictMock<AFBApiMock>> mAfbApi;
+ std::shared_ptr<ConsoleLogger> mConsoleLogger;
+ std::function<std::shared_ptr<IAFBApi::IAFBEvent>(const std::string&)> mEventCreatorFn;
+ std::shared_ptr<::testing::StrictMock<AFBEventMock>> mAfbEventMock;
+};
+
+TEST_F(PublisherForwarderTest, failsInitializationOnInvalidParams) {
+ auto capability = std::make_shared<::testing::StrictMock<CapabilityMock>>();
+
+ auto forwarder = PublisherForwarder::create(mConsoleLogger, nullptr);
+ ASSERT_EQ(forwarder, nullptr);
+
+ forwarder = PublisherForwarder::create(nullptr, capability);
+ ASSERT_EQ(forwarder, nullptr);
+}
+
+TEST_F(PublisherForwarderTest, initializesCorrectly) {
+ auto capability = std::make_shared<::testing::StrictMock<CapabilityMock>>();
+ auto forwarder = createPublisherForwarder(capability);
+
+ ASSERT_NE(forwarder, nullptr);
+}
+
+TEST_F(PublisherForwarderTest, canForwardMessages) {
+ std::shared_ptr<AFBEventMock> mockEvent(new ::testing::StrictMock<AFBEventMock>());
+ ON_CALL(*mockEvent, publishEvent(::testing::_)).WillByDefault(::testing::Return(true));
+ EXPECT_CALL(*mockEvent, publishEvent(::testing::_)).Times(4);
+ auto eventCreator = [mockEvent](const std::string& eventName) -> std::shared_ptr<IAFBApi::IAFBEvent> {
+ return mockEvent;
+ };
+
+ ON_CALL(*mAfbApi, createEvent(::testing::_)).WillByDefault(::testing::Invoke(eventCreator));
+ EXPECT_CALL(*mAfbApi, createEvent(::testing::_)).Times(4);
+
+ auto capability = std::make_shared<::testing::StrictMock<CapabilityMock>>();
+ std::list<std::string> upstreamEvents({"up-ev1", "up-ev2"});
+ std::list<std::string> downstreamEvents({"down-ev1", "down-ev2"});
+ ON_CALL(*capability, getUpstreamMessages()).WillByDefault(::testing::Return(upstreamEvents));
+ ON_CALL(*capability, getDownstreamMessages()).WillByDefault(::testing::Return(downstreamEvents));
+
+ auto publisherForwarder = createPublisherForwarder(capability);
+ ASSERT_NE(publisherForwarder, nullptr);
+
+ auto subscriberForwarder = createSubscriberForwarder(capability);
+ ASSERT_NE(subscriberForwarder, nullptr);
+
+ std::string payload = "The answer to life the universe and everything = 42";
+ publisherForwarder->setSubscriberForwarder(subscriberForwarder);
+
+ auto itCapability = downstreamEvents.begin();
+ ASSERT_TRUE(publisherForwarder->forwardMessage(*itCapability++, payload));
+ ASSERT_TRUE(publisherForwarder->forwardMessage(*itCapability++, payload));
+ itCapability = upstreamEvents.begin();
+ ASSERT_TRUE(publisherForwarder->forwardMessage(*itCapability++, payload));
+ ASSERT_TRUE(publisherForwarder->forwardMessage(*itCapability++, payload));
+}
+
+} // namespace test
+} // namespace vshl \ No newline at end of file
diff --git a/src/plugins/capabilities/test/SubscriberForwarderTest.cpp b/src/plugins/capabilities/test/SubscriberForwarderTest.cpp
new file mode 100644
index 0000000..ff438df
--- /dev/null
+++ b/src/plugins/capabilities/test/SubscriberForwarderTest.cpp
@@ -0,0 +1,262 @@
+/*
+ * Copyright 2018-2019 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.
+ */
+#include <gtest/gtest.h>
+
+#include "capabilities/core/include/SubscriberForwarder.h"
+
+#include "test/common/ConsoleLogger.h"
+#include "test/mocks/AFBApiMock.h"
+#include "test/mocks/AFBEventMock.h"
+#include "test/mocks/AFBRequestMock.h"
+#include "test/mocks/CapabilityMock.h"
+
+using namespace vshl::common::interfaces;
+using namespace vshl::capabilities::core;
+using namespace vshl::test::common;
+
+namespace vshl {
+namespace test {
+
+class SubscriberForwarderTest : public ::testing::Test {
+protected:
+ void SetUp() override {
+ mConsoleLogger = std::make_shared<ConsoleLogger>();
+ mAfbApi = std::make_shared<::testing::StrictMock<AFBApiMock>>();
+
+ mEventCreatorFn = [](const std::string& eventName) -> std::shared_ptr<IAFBApi::IAFBEvent> {
+ std::shared_ptr<AFBEventMock> mockEvent(new ::testing::StrictMock<AFBEventMock>());
+ mockEvent->setName(eventName);
+ return mockEvent;
+ };
+ }
+
+ std::shared_ptr<SubscriberForwarder> createSubscriberForwarder(
+ std::shared_ptr<::testing::StrictMock<CapabilityMock>> capability) {
+ EXPECT_CALL(*capability, getUpstreamMessages()).Times(1);
+ EXPECT_CALL(*capability, getDownstreamMessages()).Times(1);
+
+ return SubscriberForwarder::create(mConsoleLogger, mAfbApi, capability);
+ }
+
+ std::shared_ptr<::testing::StrictMock<AFBApiMock>> mAfbApi;
+ std::shared_ptr<ConsoleLogger> mConsoleLogger;
+ std::function<std::shared_ptr<IAFBApi::IAFBEvent>(const std::string&)> mEventCreatorFn;
+ std::shared_ptr<::testing::StrictMock<AFBEventMock>> mAfbEventMock;
+};
+
+TEST_F(SubscriberForwarderTest, failsInitializationOnInvalidParams) {
+ auto capability = std::make_shared<::testing::StrictMock<CapabilityMock>>();
+
+ auto forwarder = SubscriberForwarder::create(mConsoleLogger, mAfbApi, nullptr);
+ ASSERT_EQ(forwarder, nullptr);
+
+ forwarder = SubscriberForwarder::create(mConsoleLogger, nullptr, capability);
+ ASSERT_EQ(forwarder, nullptr);
+
+ forwarder = SubscriberForwarder::create(nullptr, mAfbApi, capability);
+ ASSERT_EQ(forwarder, nullptr);
+}
+
+TEST_F(SubscriberForwarderTest, initializesCorrectly) {
+ auto capability = std::make_shared<::testing::StrictMock<CapabilityMock>>();
+ auto forwarder = createSubscriberForwarder(capability);
+
+ ASSERT_NE(forwarder, nullptr);
+}
+
+TEST_F(SubscriberForwarderTest, createsEventsOnInitialization) {
+ ON_CALL(*mAfbApi, createEvent(::testing::_)).WillByDefault(::testing::Invoke(mEventCreatorFn));
+
+ std::list<std::string> upstreamEvents({"up-ev1", "up-ev2"});
+ std::list<std::string> downstreamEvents({"down-ev1", "down-ev2"});
+
+ auto capability = std::make_shared<::testing::StrictMock<CapabilityMock>>();
+ ON_CALL(*capability, getUpstreamMessages()).WillByDefault(::testing::Return(upstreamEvents));
+ ON_CALL(*capability, getDownstreamMessages()).WillByDefault(::testing::Return(downstreamEvents));
+
+ auto itCapability = upstreamEvents.begin();
+ EXPECT_CALL(*mAfbApi, createEvent(*itCapability)).Times(1);
+ itCapability++;
+ EXPECT_CALL(*mAfbApi, createEvent(*itCapability)).Times(1);
+
+ itCapability = downstreamEvents.begin();
+ EXPECT_CALL(*mAfbApi, createEvent(*itCapability)).Times(1);
+ itCapability++;
+ EXPECT_CALL(*mAfbApi, createEvent(*itCapability)).Times(1);
+
+ auto forwarder = createSubscriberForwarder(capability);
+ ASSERT_NE(forwarder, nullptr);
+}
+
+TEST_F(SubscriberForwarderTest, canNotSubscribeToNonExistentEvents) {
+ ON_CALL(*mAfbApi, createEvent(::testing::_)).WillByDefault(::testing::Invoke(mEventCreatorFn));
+ EXPECT_CALL(*mAfbApi, createEvent(::testing::_)).Times(4);
+
+ std::list<std::string> upstreamEvents({"up-ev1", "up-ev2"});
+ std::list<std::string> downstreamEvents({"down-ev1", "down-ev2"});
+
+ auto capability = std::make_shared<::testing::StrictMock<CapabilityMock>>();
+ ON_CALL(*capability, getUpstreamMessages()).WillByDefault(::testing::Return(upstreamEvents));
+ ON_CALL(*capability, getDownstreamMessages()).WillByDefault(::testing::Return(downstreamEvents));
+
+ auto forwarder = createSubscriberForwarder(capability);
+ ASSERT_NE(forwarder, nullptr);
+
+ auto nonExistentEvents = std::list<std::string>({"non", "existent", "events"});
+ auto itCapability = nonExistentEvents.begin();
+ auto request = std::make_shared<::testing::StrictMock<AFBRequestMock>>();
+ ASSERT_FALSE(forwarder->subscribe(*request, *itCapability++));
+ ASSERT_FALSE(forwarder->subscribe(*request, *itCapability++));
+ ASSERT_FALSE(forwarder->subscribe(*request, *itCapability++));
+}
+
+TEST_F(SubscriberForwarderTest, canNotSubscribeOnEventCreationFailure) {
+ // Fail the event creation and return null event.
+ ON_CALL(*mAfbApi, createEvent(::testing::_)).WillByDefault(::testing::Return(nullptr));
+ EXPECT_CALL(*mAfbApi, createEvent(::testing::_)).Times(4);
+
+ std::list<std::string> upstreamEvents({"up-ev1", "up-ev2"});
+ std::list<std::string> downstreamEvents({"down-ev1", "down-ev2"});
+
+ auto capability = std::make_shared<::testing::StrictMock<CapabilityMock>>();
+ ON_CALL(*capability, getUpstreamMessages()).WillByDefault(::testing::Return(upstreamEvents));
+ ON_CALL(*capability, getDownstreamMessages()).WillByDefault(::testing::Return(downstreamEvents));
+
+ auto forwarder = createSubscriberForwarder(capability);
+ ASSERT_NE(forwarder, nullptr);
+
+ auto itCapability = downstreamEvents.begin();
+ auto request = std::make_shared<::testing::StrictMock<AFBRequestMock>>();
+ std::string payload = "The answer to life the universe and everything = 42";
+ ASSERT_FALSE(forwarder->subscribe(*request, *itCapability++));
+ ASSERT_FALSE(forwarder->subscribe(*request, *itCapability++));
+ itCapability = upstreamEvents.begin();
+ ASSERT_FALSE(forwarder->subscribe(*request, *itCapability++));
+ ASSERT_FALSE(forwarder->subscribe(*request, *itCapability++));
+}
+
+TEST_F(SubscriberForwarderTest, canSubscribeToEvents) {
+ std::shared_ptr<AFBEventMock> mockEvent(new ::testing::StrictMock<AFBEventMock>());
+ ON_CALL(*mockEvent, subscribe(::testing::_)).WillByDefault(::testing::Return(true));
+ EXPECT_CALL(*mockEvent, subscribe(::testing::_)).Times(4);
+ auto eventCreator = [mockEvent](const std::string& eventName) -> std::shared_ptr<IAFBApi::IAFBEvent> {
+ return mockEvent;
+ };
+
+ ON_CALL(*mAfbApi, createEvent(::testing::_)).WillByDefault(::testing::Invoke(eventCreator));
+ EXPECT_CALL(*mAfbApi, createEvent(::testing::_)).Times(4);
+
+ std::list<std::string> upstreamEvents({"up-ev1", "up-ev2"});
+ std::list<std::string> downstreamEvents({"down-ev1", "down-ev2"});
+
+ auto capability = std::make_shared<::testing::StrictMock<CapabilityMock>>();
+ ON_CALL(*capability, getUpstreamMessages()).WillByDefault(::testing::Return(upstreamEvents));
+ ON_CALL(*capability, getDownstreamMessages()).WillByDefault(::testing::Return(downstreamEvents));
+
+ auto forwarder = createSubscriberForwarder(capability);
+ ASSERT_NE(forwarder, nullptr);
+
+ auto itCapability = downstreamEvents.begin();
+ auto request = std::make_shared<::testing::StrictMock<AFBRequestMock>>();
+ ASSERT_TRUE(forwarder->subscribe(*request, *itCapability++));
+ ASSERT_TRUE(forwarder->subscribe(*request, *itCapability));
+ itCapability = upstreamEvents.begin();
+ ASSERT_TRUE(forwarder->subscribe(*request, *itCapability++));
+ ASSERT_TRUE(forwarder->subscribe(*request, *itCapability));
+}
+
+TEST_F(SubscriberForwarderTest, canNotPublishNonExistentEvents) {
+ std::shared_ptr<AFBEventMock> mockEvent(new ::testing::StrictMock<AFBEventMock>());
+ EXPECT_CALL(*mockEvent, publishEvent(::testing::_)).Times(0);
+ auto eventCreator = [mockEvent](const std::string& eventName) -> std::shared_ptr<IAFBApi::IAFBEvent> {
+ return mockEvent;
+ };
+
+ ON_CALL(*mAfbApi, createEvent(::testing::_)).WillByDefault(::testing::Invoke(eventCreator));
+ EXPECT_CALL(*mAfbApi, createEvent(::testing::_)).Times(4);
+
+ std::list<std::string> upstreamEvents({"up-ev1", "up-ev2"});
+ std::list<std::string> downstreamEvents({"down-ev1", "down-ev2"});
+
+ auto capability = std::make_shared<::testing::StrictMock<CapabilityMock>>();
+ ON_CALL(*capability, getUpstreamMessages()).WillByDefault(::testing::Return(upstreamEvents));
+ ON_CALL(*capability, getDownstreamMessages()).WillByDefault(::testing::Return(downstreamEvents));
+
+ auto forwarder = createSubscriberForwarder(capability);
+ ASSERT_NE(forwarder, nullptr);
+
+ auto nonExistentEvents = std::list<std::string>({"non", "existent", "events"});
+ auto itCapability = nonExistentEvents.begin();
+ ASSERT_FALSE(forwarder->forwardMessage(*itCapability++, "My-Payload"));
+ ASSERT_FALSE(forwarder->forwardMessage(*itCapability++, "My-Payload"));
+ ASSERT_FALSE(forwarder->forwardMessage(*itCapability++, "My-Payload"));
+}
+
+TEST_F(SubscriberForwarderTest, canNotPublishOnEventCreationFailure) {
+ // Fail the event creation and return null event.
+ ON_CALL(*mAfbApi, createEvent(::testing::_)).WillByDefault(::testing::Return(nullptr));
+ EXPECT_CALL(*mAfbApi, createEvent(::testing::_)).Times(4);
+
+ std::list<std::string> upstreamEvents({"up-ev1", "up-ev2"});
+ std::list<std::string> downstreamEvents({"down-ev1", "down-ev2"});
+
+ auto capability = std::make_shared<::testing::StrictMock<CapabilityMock>>();
+ ON_CALL(*capability, getUpstreamMessages()).WillByDefault(::testing::Return(upstreamEvents));
+ ON_CALL(*capability, getDownstreamMessages()).WillByDefault(::testing::Return(downstreamEvents));
+
+ auto forwarder = createSubscriberForwarder(capability);
+ ASSERT_NE(forwarder, nullptr);
+
+ auto itCapability = downstreamEvents.begin();
+ std::string payload = "The answer to life the universe and everything = 42";
+ ASSERT_FALSE(forwarder->forwardMessage(*itCapability++, payload));
+ ASSERT_FALSE(forwarder->forwardMessage(*itCapability++, payload));
+ itCapability = upstreamEvents.begin();
+ ASSERT_FALSE(forwarder->forwardMessage(*itCapability++, payload));
+ ASSERT_FALSE(forwarder->forwardMessage(*itCapability++, payload));
+}
+
+TEST_F(SubscriberForwarderTest, canPublishEvents) {
+ std::shared_ptr<AFBEventMock> mockEvent(new ::testing::StrictMock<AFBEventMock>());
+ ON_CALL(*mockEvent, publishEvent(::testing::_)).WillByDefault(::testing::Return(true));
+ EXPECT_CALL(*mockEvent, publishEvent(::testing::_)).Times(4);
+ auto eventCreator = [mockEvent](const std::string& eventName) -> std::shared_ptr<IAFBApi::IAFBEvent> {
+ return mockEvent;
+ };
+
+ ON_CALL(*mAfbApi, createEvent(::testing::_)).WillByDefault(::testing::Invoke(eventCreator));
+ EXPECT_CALL(*mAfbApi, createEvent(::testing::_)).Times(4);
+
+ std::list<std::string> upstreamEvents({"up-ev1", "up-ev2"});
+ std::list<std::string> downstreamEvents({"down-ev1", "down-ev2"});
+
+ auto capability = std::make_shared<::testing::StrictMock<CapabilityMock>>();
+ ON_CALL(*capability, getUpstreamMessages()).WillByDefault(::testing::Return(upstreamEvents));
+ ON_CALL(*capability, getDownstreamMessages()).WillByDefault(::testing::Return(downstreamEvents));
+
+ auto forwarder = createSubscriberForwarder(capability);
+ ASSERT_NE(forwarder, nullptr);
+
+ auto itCapability = downstreamEvents.begin();
+ std::string payload = "The answer to life the universe and everything = 42";
+ ASSERT_TRUE(forwarder->forwardMessage(*itCapability++, payload));
+ ASSERT_TRUE(forwarder->forwardMessage(*itCapability++, payload));
+ itCapability = upstreamEvents.begin();
+ ASSERT_TRUE(forwarder->forwardMessage(*itCapability++, payload));
+ ASSERT_TRUE(forwarder->forwardMessage(*itCapability++, payload));
+}
+
+} // namespace test
+} // namespace vshl \ No newline at end of file
diff --git a/src/plugins/cmake/gtest.cmake b/src/plugins/cmake/gtest.cmake
new file mode 100644
index 0000000..def6559
--- /dev/null
+++ b/src/plugins/cmake/gtest.cmake
@@ -0,0 +1,44 @@
+# gtest
+
+find_package(Threads REQUIRED)
+
+# Enable ExternalProject CMake module
+INCLUDE(ExternalProject)
+
+ExternalProject_Add(
+ gtest
+ URL https://github.com/google/googletest/archive/release-1.8.1.zip
+ SOURCE_DIR "${CMAKE_BINARY_DIR}/gtest-src"
+ BINARY_DIR "${CMAKE_BINARY_DIR}/gtest-build"
+ INSTALL_COMMAND ""
+ LOG_DOWNLOAD ON
+ LOG_CONFIGURE ON
+ LOG_BUILD ON
+)
+
+# Get GTest source and binary directories from CMake project
+ExternalProject_Get_Property(gtest source_dir binary_dir)
+
+# Create a libgtest target to be used as a dependency by test programs
+ADD_LIBRARY(libgtest INTERFACE)
+TARGET_INCLUDE_DIRECTORIES(libgtest
+ INTERFACE
+ ${source_dir}/googletest/include
+)
+TARGET_LINK_LIBRARIES(libgtest
+ INTERFACE
+ ${binary_dir}/googlemock/gtest/libgtest.a
+ ${CMAKE_THREAD_LIBS_INIT}
+)
+
+# Create a libgmock target to be used as a dependency by test programs
+ADD_LIBRARY(libgmock INTERFACE)
+TARGET_INCLUDE_DIRECTORIES(libgmock
+ INTERFACE
+ ${source_dir}/googlemock/include
+)
+TARGET_LINK_LIBRARIES(libgmock
+ INTERFACE
+ ${binary_dir}/googlemock/libgmock.a
+ ${CMAKE_THREAD_LIBS_INIT}
+) \ No newline at end of file
diff --git a/src/plugins/core/VRRequestProcessor.h b/src/plugins/core/VRRequestProcessor.h
new file mode 100644
index 0000000..c349a19
--- /dev/null
+++ b/src/plugins/core/VRRequestProcessor.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2018-2019 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 VSHL_CORE_INCLUDE_VR_REQUESTPROCESSOR_H_
+#define VSHL_CORE_INCLUDE_VR_REQUESTPROCESSOR_H_
+
+#include <memory>
+#include <unordered_map>
+
+#include "core/include/VRRequest.h"
+#include "core/include/VRRequestProcessorDelegate.h"
+#include "interfaces/afb/IAFBApi.h"
+#include "interfaces/utilities/logging/ILogger.h"
+#include "interfaces/voiceagents/IVoiceAgentsChangeObserver.h"
+
+using namespace std;
+
+namespace vshl {
+namespace core {
+/*
+ * This class is the entry point for all the voice recognition request
+ * creation and arbitration.
+ */
+class VRRequestProcessor {
+public:
+ // Create a VRRequestProcessor.
+ static unique_ptr<VRRequestProcessor> create(
+ shared_ptr<vshl::common::interfaces::ILogger> logger,
+ shared_ptr<vshl::core::VRRequestProcessorDelegate> delegate);
+
+ // Triggers a voiceagent to start listening to user speech input.
+ // Returns the request ID. If start fails, then empty request ID
+ // is returned.
+ string startListening();
+
+ // Cancels all the active requests
+ void cancel();
+
+ // Returns the voiceagents observer that belongs to the core module.
+ shared_ptr<vshl::common::interfaces::IVoiceAgentsChangeObserver> getVoiceAgentsChangeObserver() const;
+
+ // Destructor
+ ~VRRequestProcessor();
+
+private:
+ // Constructor
+ VRRequestProcessor(
+ shared_ptr<vshl::common::interfaces::ILogger> logger,
+ shared_ptr<vshl::core::VRRequestProcessorDelegate> delegate);
+
+ // Voiceagents observer
+ shared_ptr<vshl::common::interfaces::IVoiceAgentsChangeObserver> mVoiceAgentsChangeObserver;
+
+ // Request Processor Delegate
+ shared_ptr<vshl::core::VRRequestProcessorDelegate> mDelegate;
+
+ // Logger
+ shared_ptr<vshl::common::interfaces::ILogger> mLogger;
+};
+
+} // namespace core
+} // namespace vshl
+
+#endif // VSHL_CORE_INCLUDE_VR_REQUESTPROCESSOR_H_
diff --git a/src/plugins/core/VRRequestProcessorImpl.cpp b/src/plugins/core/VRRequestProcessorImpl.cpp
new file mode 100644
index 0000000..7441a7d
--- /dev/null
+++ b/src/plugins/core/VRRequestProcessorImpl.cpp
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2018-2019 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.
+ */
+#include "core/VRRequestProcessor.h"
+
+#include "core/include/VRAgentsObserver.h"
+
+static string TAG = "vshl::core::VRRequestProcessor";
+
+using Level = vshl::utilities::logging::Logger::Level;
+
+namespace vshl {
+namespace core {
+// Create a VRRequestProcessor.
+unique_ptr<VRRequestProcessor> VRRequestProcessor::create(
+ shared_ptr<vshl::common::interfaces::ILogger> logger,
+ shared_ptr<vshl::core::VRRequestProcessorDelegate> delegate) {
+ auto processor = std::unique_ptr<VRRequestProcessor>(new VRRequestProcessor(logger, delegate));
+ return processor;
+}
+
+VRRequestProcessor::VRRequestProcessor(
+ shared_ptr<vshl::common::interfaces::ILogger> logger,
+ shared_ptr<vshl::core::VRRequestProcessorDelegate> delegate) :
+ mLogger(logger),
+ mDelegate(delegate) {
+ mVoiceAgentsChangeObserver = VRAgentsObserver::create(mDelegate);
+}
+
+VRRequestProcessor::~VRRequestProcessor() {
+ mDelegate->cancelAllRequests();
+}
+
+string VRRequestProcessor::startListening() {
+ // Currently start is simply going to send the request to
+ // the default voice agent as the wake word detection is not
+ // enabled.
+ shared_ptr<vshl::common::interfaces::IVoiceAgent> defaultVA = mDelegate->getDefaultVoiceAgent();
+ if (!defaultVA) {
+ mLogger->log(Level::ERROR, TAG, "Failed to start. No default voiceagent found.");
+ return "";
+ }
+
+ // If the requests container is not empty, then clear the
+ // existing requests in flight and create a new request.
+ mDelegate->cancelAllRequests();
+ return mDelegate->startRequestForVoiceAgent(defaultVA);
+}
+
+void VRRequestProcessor::cancel() {
+ // Cancel all pending requests
+ mDelegate->cancelAllRequests();
+}
+
+shared_ptr<vshl::common::interfaces::IVoiceAgentsChangeObserver> VRRequestProcessor::getVoiceAgentsChangeObserver()
+ const {
+ return mVoiceAgentsChangeObserver;
+}
+
+} // namespace core
+} // namespace vshl
diff --git a/src/plugins/core/include/VRAgentsObserver.h b/src/plugins/core/include/VRAgentsObserver.h
new file mode 100644
index 0000000..d4c0c7b
--- /dev/null
+++ b/src/plugins/core/include/VRAgentsObserver.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2018-2019 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 VSHL_CORE_INCLUDE_VR_AGENTS_OBSERVER_H_
+#define VSHL_CORE_INCLUDE_VR_AGENTS_OBSERVER_H_
+
+#include <memory>
+
+#include "core/include/VRRequestProcessorDelegate.h"
+#include "interfaces/voiceagents/IVoiceAgentsChangeObserver.h"
+#include "utilities/logging/Logger.h"
+
+using namespace std;
+
+namespace vshl {
+namespace core {
+/*
+ * This class will observe the changes to the voiceagents data and transfers
+ * the actual handling responsibility to its delegate.
+ */
+class VRAgentsObserver
+ : public vshl::common::interfaces::IVoiceAgentsChangeObserver {
+public:
+ // Create a VRAgentsObserver.
+ static shared_ptr<VRAgentsObserver>
+ create(weak_ptr<VRRequestProcessorDelegate> delegate);
+
+ ~VRAgentsObserver();
+
+protected:
+ void OnDefaultVoiceAgentChanged(
+ shared_ptr<vshl::common::interfaces::IVoiceAgent> defaultVoiceAgent)
+ override;
+ void OnVoiceAgentAdded(
+ shared_ptr<vshl::common::interfaces::IVoiceAgent> voiceAgent) override;
+ void OnVoiceAgentRemoved(
+ shared_ptr<vshl::common::interfaces::IVoiceAgent> voiceAgent) override;
+ void OnVoiceAgentActiveWakeWordChanged(
+ shared_ptr<vshl::common::interfaces::IVoiceAgent> voiceAgent) override;
+ void OnVoiceAgentActivated(
+ shared_ptr<vshl::common::interfaces::IVoiceAgent> voiceAgent) override;
+ void OnVoiceAgentDeactivated(
+ shared_ptr<vshl::common::interfaces::IVoiceAgent> voiceAgent) override;
+
+private:
+ // Constructor
+ VRAgentsObserver(weak_ptr<VRRequestProcessorDelegate> delegate);
+
+ // Delegate that needs to be informed of the voiceagent data changes.
+ weak_ptr<VRRequestProcessorDelegate> mWeakDelegate;
+};
+
+} // namespace core
+} // namespace vshl
+
+#endif // VSHL_CORE_INCLUDE_VR_AGENTS_OBSERVER_H_
diff --git a/src/plugins/core/include/VRRequest.h b/src/plugins/core/include/VRRequest.h
new file mode 100644
index 0000000..522ec78
--- /dev/null
+++ b/src/plugins/core/include/VRRequest.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2018-2019 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 VSHL_CORE_INCLUDE_VR_REQUEST_H_
+#define VSHL_CORE_INCLUDE_VR_REQUEST_H_
+
+#include <memory>
+
+#include "interfaces/afb/IAFBApi.h"
+#include "interfaces/utilities/logging/ILogger.h"
+#include "interfaces/voiceagents/IVoiceAgent.h"
+
+using namespace std;
+
+namespace vshl {
+namespace core {
+/*
+ * This class implements the notion of a Voice Recognition Request.
+ * Each VR Request is currently mapped to one voice agent.
+ */
+class VRRequest {
+public:
+ // API Verbs
+ static std::string VA_VERB_STARTLISTENING;
+ static std::string VA_VERB_CANCEL;
+
+ // Create a VRRequest.
+ static unique_ptr<VRRequest> create(
+ shared_ptr<vshl::common::interfaces::ILogger> logger,
+ shared_ptr<vshl::common::interfaces::IAFBApi> afbApi,
+ string requestId,
+ shared_ptr<vshl::common::interfaces::IVoiceAgent> voiceAgent);
+
+ // Destructor
+ ~VRRequest();
+
+ // Invokes the underlying voiceagent's startlistening API.
+ // Returns true if started successfully. False otherwise.
+ bool startListening();
+
+ // Cancels the voice recognition in the unlerlying voiceagent.
+ // Returns true if canceled successfully. False otherwise.
+ bool cancel();
+
+private:
+ // Constructor
+ VRRequest(
+ shared_ptr<vshl::common::interfaces::ILogger> logger,
+ shared_ptr<vshl::common::interfaces::IAFBApi> afbApi,
+ const string requestId,
+ shared_ptr<vshl::common::interfaces::IVoiceAgent> voiceAgent);
+
+ // Binding API reference.
+ shared_ptr<vshl::common::interfaces::IAFBApi> mApi;
+
+ // Voice agent associated with this request
+ shared_ptr<vshl::common::interfaces::IVoiceAgent> mVoiceAgent;
+
+ // Request ID
+ string mRequestId;
+
+ // Logger
+ shared_ptr<vshl::common::interfaces::ILogger> mLogger;
+};
+
+} // namespace core
+} // namespace vshl
+
+#endif // VSHL_CORE_INCLUDE_VR_REQUEST_H_
diff --git a/src/plugins/core/include/VRRequestProcessorDelegate.h b/src/plugins/core/include/VRRequestProcessorDelegate.h
new file mode 100644
index 0000000..94b7304
--- /dev/null
+++ b/src/plugins/core/include/VRRequestProcessorDelegate.h
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2018-2019 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 VSHL_CORE_INCLUDE_VR_REQUESTPROCESSORDELEGATE_H_
+#define VSHL_CORE_INCLUDE_VR_REQUESTPROCESSORDELEGATE_H_
+
+#include <memory>
+#include <unordered_map>
+
+#include "core/include/VRRequest.h"
+#include "interfaces/afb/IAFBApi.h"
+#include "interfaces/utilities/logging/ILogger.h"
+#include "interfaces/voiceagents/IVoiceAgent.h"
+#include "utilities/uuid/UUIDGeneration.h"
+
+using namespace std;
+
+namespace vshl {
+namespace core {
+/*
+ * This is a delegate for VRRequestProcessor actions.
+ * The lifetime and dependencies of this object is managed
+ * by VRRequestProcessor. It plays the role of a Delegate in
+ * Delegate pattern and a one shop stop for most of the core
+ * module's state.
+ */
+class VRRequestProcessorDelegate {
+public:
+ // create method
+ static shared_ptr<VRRequestProcessorDelegate> create(
+ shared_ptr<vshl::common::interfaces::ILogger> logger,
+ shared_ptr<vshl::common::interfaces::IAFBApi> afbApi);
+
+ // Destructor
+ ~VRRequestProcessorDelegate();
+
+ // Set default voiceagent
+ void setDefaultVoiceAgent(shared_ptr<vshl::common::interfaces::IVoiceAgent> voiceAgent);
+
+ // Get the default voiceagent
+ shared_ptr<vshl::common::interfaces::IVoiceAgent> getDefaultVoiceAgent() const;
+
+ // Add new request to the list and start processing it.
+ // New request is created and startListening on the
+ // voiceagent is called.
+ string startRequestForVoiceAgent(shared_ptr<vshl::common::interfaces::IVoiceAgent> voiceAgent);
+
+ // Cancel all requests
+ void cancelAllRequests();
+
+ // Get All outstanding requests
+ // Used only by Test
+ unordered_map<string, shared_ptr<VRRequest>> getAllRequests();
+
+private:
+ // Constructor
+ VRRequestProcessorDelegate(
+ shared_ptr<vshl::common::interfaces::ILogger> logger,
+ shared_ptr<vshl::common::interfaces::IAFBApi> afbApi);
+
+ // Binding API reference
+ shared_ptr<vshl::common::interfaces::IAFBApi> mApi;
+
+ // Default voiceagent
+ shared_ptr<vshl::common::interfaces::IVoiceAgent> mDefaultVoiceAgent;
+
+ // A map of voiceagent IDs and their respective VR Request objects.
+ unordered_map<string, shared_ptr<VRRequest>> mVRRequests;
+
+ // Logger
+ shared_ptr<vshl::common::interfaces::ILogger> mLogger;
+};
+
+} // namespace core
+} // namespace vshl
+
+#endif // VSHL_CORE_INCLUDE_VR_REQUESTPROCESSORDELEGATE_H_
diff --git a/src/plugins/core/src/VRAgentsObserverImpl.cpp b/src/plugins/core/src/VRAgentsObserverImpl.cpp
new file mode 100644
index 0000000..7ee4a7e
--- /dev/null
+++ b/src/plugins/core/src/VRAgentsObserverImpl.cpp
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2018-2019 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.
+ */
+#include "core/include/VRAgentsObserver.h"
+
+namespace vshl {
+namespace core {
+
+shared_ptr<VRAgentsObserver> VRAgentsObserver::create(weak_ptr<VRRequestProcessorDelegate> delegate) {
+ auto observer = std::shared_ptr<VRAgentsObserver>(new VRAgentsObserver(delegate));
+ return observer;
+}
+
+VRAgentsObserver::VRAgentsObserver(weak_ptr<VRRequestProcessorDelegate> delegate) {
+ mWeakDelegate = delegate;
+}
+
+VRAgentsObserver::~VRAgentsObserver() {
+}
+
+void VRAgentsObserver::OnDefaultVoiceAgentChanged(shared_ptr<vshl::common::interfaces::IVoiceAgent> defaultVoiceAgent) {
+ if (auto delegate = mWeakDelegate.lock()) {
+ delegate->setDefaultVoiceAgent(defaultVoiceAgent);
+ }
+}
+
+void VRAgentsObserver::OnVoiceAgentAdded(shared_ptr<vshl::common::interfaces::IVoiceAgent> voiceAgent) {
+}
+
+void VRAgentsObserver::OnVoiceAgentRemoved(shared_ptr<vshl::common::interfaces::IVoiceAgent> voiceAgent) {
+}
+
+void VRAgentsObserver::OnVoiceAgentActiveWakeWordChanged(shared_ptr<vshl::common::interfaces::IVoiceAgent> voiceAgent) {
+ // Not Implemented
+}
+
+void VRAgentsObserver::OnVoiceAgentActivated(shared_ptr<vshl::common::interfaces::IVoiceAgent> voiceAgent) {
+}
+
+void VRAgentsObserver::OnVoiceAgentDeactivated(shared_ptr<vshl::common::interfaces::IVoiceAgent> voiceAgent) {
+}
+} // namespace core
+} // namespace vshl
diff --git a/src/plugins/core/src/VRRequestImpl.cpp b/src/plugins/core/src/VRRequestImpl.cpp
new file mode 100644
index 0000000..00adf96
--- /dev/null
+++ b/src/plugins/core/src/VRRequestImpl.cpp
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2018-2019 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.
+ */
+#include "core/include/VRRequest.h"
+
+#define FREEIF(x) \
+ if (!x) { \
+ free(x); \
+ }
+#define BREAKIF(x) \
+ if (x) { \
+ result = false; \
+ break; \
+ }
+
+static string TAG = "vshl::core::VRRequest";
+
+using Level = vshl::common::interfaces::ILogger::Level;
+
+namespace vshl {
+namespace core {
+
+string VRRequest::VA_VERB_STARTLISTENING = "startListening";
+string VRRequest::VA_VERB_CANCEL = "cancel";
+
+unique_ptr<VRRequest> VRRequest::create(
+ shared_ptr<vshl::common::interfaces::ILogger> logger,
+ shared_ptr<vshl::common::interfaces::IAFBApi> afbApi,
+ const string requestId,
+ shared_ptr<vshl::common::interfaces::IVoiceAgent> voiceAgent) {
+ if (logger == nullptr) {
+ return nullptr;
+ }
+
+ if (afbApi == nullptr) {
+ logger->log(Level::ERROR, TAG, "Invalid AFB API");
+ return nullptr;
+ }
+
+ auto request = std::unique_ptr<VRRequest>(new VRRequest(logger, afbApi, requestId, voiceAgent));
+ return request;
+}
+
+VRRequest::VRRequest(
+ shared_ptr<vshl::common::interfaces::ILogger> logger,
+ shared_ptr<vshl::common::interfaces::IAFBApi> afbApi,
+ string requestId,
+ shared_ptr<vshl::common::interfaces::IVoiceAgent> voiceAgent) :
+ mApi(afbApi),
+ mRequestId(requestId),
+ mVoiceAgent(voiceAgent),
+ mLogger(logger) {
+}
+
+VRRequest::~VRRequest() {
+}
+
+bool VRRequest::startListening() {
+ json_object* object = NULL;
+ std::string error, info;
+ bool result = true;
+ int rc = mApi->callSync(mVoiceAgent->getApi(), VA_VERB_STARTLISTENING, NULL, &object, error, info);
+
+ FREEIF(object);
+
+ return true;
+}
+
+bool VRRequest::cancel() {
+ json_object* object = NULL;
+ std::string error, info;
+ bool result = true;
+ int rc = mApi->callSync(mVoiceAgent->getApi(), VA_VERB_CANCEL, NULL, &object, error, info);
+
+ FREEIF(object);
+
+ return true;
+}
+
+} // namespace core
+} // namespace vshl
diff --git a/src/plugins/core/src/VRRequestProcessorDelegateImpl.cpp b/src/plugins/core/src/VRRequestProcessorDelegateImpl.cpp
new file mode 100644
index 0000000..e20b22e
--- /dev/null
+++ b/src/plugins/core/src/VRRequestProcessorDelegateImpl.cpp
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2018-2019 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.
+ */
+#include "core/include/VRRequestProcessorDelegate.h"
+
+static string TAG = "vshl::core::VRRequestProcessorDelegate";
+
+using Level = vshl::common::interfaces::ILogger::Level;
+
+namespace vshl {
+namespace core {
+shared_ptr<VRRequestProcessorDelegate> VRRequestProcessorDelegate::create(
+ shared_ptr<vshl::common::interfaces::ILogger> logger,
+ shared_ptr<vshl::common::interfaces::IAFBApi> afbApi) {
+ auto delegate = std::shared_ptr<VRRequestProcessorDelegate>(new VRRequestProcessorDelegate(logger, afbApi));
+ return delegate;
+}
+
+VRRequestProcessorDelegate::VRRequestProcessorDelegate(
+ shared_ptr<vshl::common::interfaces::ILogger> logger,
+ shared_ptr<vshl::common::interfaces::IAFBApi> afbApi) :
+ mApi(afbApi),
+ mLogger(logger) {
+}
+
+VRRequestProcessorDelegate::~VRRequestProcessorDelegate() {
+ mVRRequests.clear();
+}
+
+string VRRequestProcessorDelegate::startRequestForVoiceAgent(
+ shared_ptr<vshl::common::interfaces::IVoiceAgent> voiceAgent) {
+ if (!mApi) {
+ mLogger->log(Level::ERROR, TAG, "Failed to startRequestForVoiceAgent: " + voiceAgent->getId() + ", No API.");
+ return "";
+ }
+
+ // Generate a new request ID.
+ string newReqId = vshl::utilities::uuid::generateUUID();
+
+ // Create a new request and start listening.
+ shared_ptr<VRRequest> newRequest = VRRequest::create(mLogger, mApi, newReqId, voiceAgent);
+
+ // mLogger->log(Level::DEBUG, TAG, "Starting request with ID: " + newReqId);
+ if (!newRequest->startListening()) {
+ mLogger->log(Level::ERROR, TAG, "Failed to start listening.");
+ return "";
+ }
+
+ // Insert only if its started successfully.
+ mVRRequests.insert(make_pair(voiceAgent->getId(), newRequest));
+
+ return newReqId;
+}
+
+void VRRequestProcessorDelegate::cancelAllRequests() {
+ // Cancel Pending requests
+ if (!mVRRequests.empty()) {
+ auto vrRequestsIt = mVRRequests.begin();
+ while (vrRequestsIt != mVRRequests.end()) {
+ if (!vrRequestsIt->second->cancel()) {
+ mLogger->log(Level::WARNING, TAG, "Failed to cancel request: " + vrRequestsIt->first);
+ }
+ vrRequestsIt++;
+ }
+ mVRRequests.clear();
+ }
+}
+
+unordered_map<string, shared_ptr<VRRequest>> VRRequestProcessorDelegate::getAllRequests() {
+ return mVRRequests;
+}
+
+void VRRequestProcessorDelegate::setDefaultVoiceAgent(shared_ptr<vshl::common::interfaces::IVoiceAgent> voiceAgent) {
+ mDefaultVoiceAgent = voiceAgent;
+}
+
+shared_ptr<vshl::common::interfaces::IVoiceAgent> VRRequestProcessorDelegate::getDefaultVoiceAgent() const {
+ return mDefaultVoiceAgent;
+}
+
+} // namespace core
+} // namespace vshl
diff --git a/src/plugins/core/test/VRRequestProcessorTest.cpp b/src/plugins/core/test/VRRequestProcessorTest.cpp
new file mode 100644
index 0000000..c1a37df
--- /dev/null
+++ b/src/plugins/core/test/VRRequestProcessorTest.cpp
@@ -0,0 +1,213 @@
+/*
+ * Copyright 2018-2019 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.
+ */
+
+#include <gtest/gtest.h>
+
+#include "core/VRRequestProcessor.h"
+#include "voiceagents/include/VoiceAgent.h"
+#include "voiceagents/test/VoiceAgentsTestData.h"
+
+#include "test/common/ConsoleLogger.h"
+#include "test/mocks/AFBApiMock.h"
+
+using namespace vshl::core;
+using namespace vshl::voiceagents;
+using namespace vshl::test::common;
+
+namespace vshl {
+namespace test {
+
+class VRRequestProcessorTest : public ::testing::Test {
+protected:
+ void SetUp() override {
+ mConsoleLogger = std::make_shared<ConsoleLogger>();
+ mAfbApi = std::make_shared<::testing::StrictMock<AFBApiMock>>();
+
+ auto vaTestData = *(getVoiceAgentsTestData().begin());
+ mVoiceAgent = VoiceAgent::create(
+ mConsoleLogger,
+ vaTestData.id,
+ vaTestData.name,
+ vaTestData.description,
+ vaTestData.api,
+ vaTestData.vendor,
+ vaTestData.activeWakeword,
+ vaTestData.isActive,
+ vaTestData.wakewords);
+
+ mVRReqProcessorDelegate = VRRequestProcessorDelegate::create(mConsoleLogger, mAfbApi);
+ mVRRequestProcessor = VRRequestProcessor::create(mConsoleLogger, mVRReqProcessorDelegate);
+ }
+
+ std::shared_ptr<::testing::StrictMock<AFBApiMock>> mAfbApi;
+ std::shared_ptr<ConsoleLogger> mConsoleLogger;
+ std::shared_ptr<VoiceAgent> mVoiceAgent;
+
+ std::shared_ptr<VRRequestProcessorDelegate> mVRReqProcessorDelegate;
+ std::shared_ptr<VRRequestProcessor> mVRRequestProcessor;
+};
+
+TEST_F(VRRequestProcessorTest, initializesCorrectly) {
+ ASSERT_NE(mVRRequestProcessor, nullptr);
+}
+
+TEST_F(VRRequestProcessorTest, startListeningFailsOnLackOfDefaultAgent) {
+ auto requestId = mVRRequestProcessor->startListening();
+ ASSERT_EQ(requestId, "");
+}
+
+TEST_F(VRRequestProcessorTest, startListeningAndCancelWorks) {
+ mVRReqProcessorDelegate->setDefaultVoiceAgent(mVoiceAgent);
+
+ {
+ ::testing::InSequence dummy;
+
+ EXPECT_CALL(
+ *mAfbApi,
+ callSync(
+ mVoiceAgent->getApi(),
+ VRRequest::VA_VERB_STARTLISTENING,
+ ::testing::_,
+ ::testing::_,
+ ::testing::_,
+ ::testing::_))
+ .Times(1);
+
+ EXPECT_CALL(
+ *mAfbApi,
+ callSync(
+ mVoiceAgent->getApi(),
+ VRRequest::VA_VERB_CANCEL,
+ ::testing::_,
+ ::testing::_,
+ ::testing::_,
+ ::testing::_))
+ .Times(1);
+ }
+
+ mVRRequestProcessor->startListening();
+ auto requests = mVRReqProcessorDelegate->getAllRequests();
+ ASSERT_EQ(requests.size(), 1);
+
+ mVRRequestProcessor->cancel();
+ requests = mVRReqProcessorDelegate->getAllRequests();
+ ASSERT_EQ(requests.size(), 0);
+}
+
+TEST_F(VRRequestProcessorTest, requestIsCancelledOnObjectDestruction) {
+ auto delegate = VRRequestProcessorDelegate::create(mConsoleLogger, mAfbApi);
+ auto processor = VRRequestProcessor::create(mConsoleLogger, delegate);
+
+ delegate->setDefaultVoiceAgent(mVoiceAgent);
+
+ {
+ ::testing::InSequence dummy;
+
+ EXPECT_CALL(
+ *mAfbApi,
+ callSync(
+ mVoiceAgent->getApi(),
+ VRRequest::VA_VERB_STARTLISTENING,
+ ::testing::_,
+ ::testing::_,
+ ::testing::_,
+ ::testing::_))
+ .Times(1);
+
+ EXPECT_CALL(
+ *mAfbApi,
+ callSync(
+ mVoiceAgent->getApi(),
+ VRRequest::VA_VERB_CANCEL,
+ ::testing::_,
+ ::testing::_,
+ ::testing::_,
+ ::testing::_))
+ .Times(1);
+ }
+
+ auto requestId = processor->startListening();
+ ASSERT_NE(requestId, "");
+
+ auto requests = delegate->getAllRequests();
+ ASSERT_EQ(requests.size(), 1);
+}
+
+TEST_F(VRRequestProcessorTest, backToBackStartListeningCancelsEarlierRequest) {
+ mVRReqProcessorDelegate->setDefaultVoiceAgent(mVoiceAgent);
+
+ {
+ ::testing::InSequence dummy;
+
+ EXPECT_CALL(
+ *mAfbApi,
+ callSync(
+ mVoiceAgent->getApi(),
+ VRRequest::VA_VERB_STARTLISTENING,
+ ::testing::_,
+ ::testing::_,
+ ::testing::_,
+ ::testing::_))
+ .Times(1);
+
+ EXPECT_CALL(
+ *mAfbApi,
+ callSync(
+ mVoiceAgent->getApi(),
+ VRRequest::VA_VERB_CANCEL,
+ ::testing::_,
+ ::testing::_,
+ ::testing::_,
+ ::testing::_))
+ .Times(1);
+
+ EXPECT_CALL(
+ *mAfbApi,
+ callSync(
+ mVoiceAgent->getApi(),
+ VRRequest::VA_VERB_STARTLISTENING,
+ ::testing::_,
+ ::testing::_,
+ ::testing::_,
+ ::testing::_))
+ .Times(1);
+
+ EXPECT_CALL(
+ *mAfbApi,
+ callSync(
+ mVoiceAgent->getApi(),
+ VRRequest::VA_VERB_CANCEL,
+ ::testing::_,
+ ::testing::_,
+ ::testing::_,
+ ::testing::_))
+ .Times(1);
+ }
+
+ mVRRequestProcessor->startListening();
+ auto requests = mVRReqProcessorDelegate->getAllRequests();
+ ASSERT_EQ(requests.size(), 1);
+
+ mVRRequestProcessor->startListening();
+ requests = mVRReqProcessorDelegate->getAllRequests();
+ ASSERT_EQ(requests.size(), 1);
+
+ mVRRequestProcessor->cancel();
+ requests = mVRReqProcessorDelegate->getAllRequests();
+ ASSERT_EQ(requests.size(), 0);
+}
+
+} // namespace test
+} // namespace vshl \ No newline at end of file
diff --git a/src/plugins/core/test/VRRequestTest.cpp b/src/plugins/core/test/VRRequestTest.cpp
new file mode 100644
index 0000000..b1cd0a6
--- /dev/null
+++ b/src/plugins/core/test/VRRequestTest.cpp
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2018-2019 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.
+ */
+
+#include <gtest/gtest.h>
+
+#include "core/include/VRRequest.h"
+#include "voiceagents/include/VoiceAgent.h"
+#include "voiceagents/test/VoiceAgentsTestData.h"
+
+#include "test/common/ConsoleLogger.h"
+#include "test/mocks/AFBApiMock.h"
+
+using namespace vshl::core;
+using namespace vshl::voiceagents;
+using namespace vshl::test::common;
+
+namespace vshl {
+namespace test {
+
+class VRRequestTest : public ::testing::Test {
+protected:
+ void SetUp() override {
+ mRequestId = "Req-0001";
+
+ mConsoleLogger = std::make_shared<ConsoleLogger>();
+ mAfbApi = std::make_shared<::testing::StrictMock<AFBApiMock>>();
+
+ auto vaTestData = *(getVoiceAgentsTestData().begin());
+ mVoiceAgent = VoiceAgent::create(
+ mConsoleLogger,
+ vaTestData.id,
+ vaTestData.name,
+ vaTestData.description,
+ vaTestData.api,
+ vaTestData.vendor,
+ vaTestData.activeWakeword,
+ vaTestData.isActive,
+ vaTestData.wakewords);
+
+ mVRRequest = VRRequest::create(mConsoleLogger, mAfbApi, mRequestId, mVoiceAgent);
+ }
+
+ std::string mRequestId;
+ std::shared_ptr<::testing::StrictMock<AFBApiMock>> mAfbApi;
+ std::shared_ptr<ConsoleLogger> mConsoleLogger;
+ std::shared_ptr<VoiceAgent> mVoiceAgent;
+
+ std::shared_ptr<VRRequest> mVRRequest;
+};
+
+TEST_F(VRRequestTest, initializesCorrectly) {
+ ASSERT_NE(mVRRequest, nullptr);
+}
+
+TEST_F(VRRequestTest, failsCreationOnInvalidParams) {
+ auto vrRequest = VRRequest::create(mConsoleLogger, nullptr, mRequestId, mVoiceAgent);
+ ASSERT_EQ(vrRequest, nullptr);
+
+ vrRequest = VRRequest::create(nullptr, mAfbApi, mRequestId, mVoiceAgent);
+ ASSERT_EQ(vrRequest, nullptr);
+}
+
+TEST_F(VRRequestTest, startsListeningSuccessfully) {
+ EXPECT_CALL(
+ *mAfbApi,
+ callSync(
+ mVoiceAgent->getApi(),
+ VRRequest::VA_VERB_STARTLISTENING,
+ ::testing::_,
+ ::testing::_,
+ ::testing::_,
+ ::testing::_))
+ .Times(1);
+
+ ASSERT_TRUE(mVRRequest->startListening());
+}
+
+TEST_F(VRRequestTest, cancelsSuccessfully) {
+ EXPECT_CALL(
+ *mAfbApi,
+ callSync(
+ mVoiceAgent->getApi(), VRRequest::VA_VERB_CANCEL, ::testing::_, ::testing::_, ::testing::_, ::testing::_))
+ .Times(1);
+
+ ASSERT_TRUE(mVRRequest->cancel());
+}
+
+} // namespace test
+} // namespace vshl \ No newline at end of file
diff --git a/src/plugins/interfaces/afb/IAFBApi.h b/src/plugins/interfaces/afb/IAFBApi.h
new file mode 100644
index 0000000..cd98006
--- /dev/null
+++ b/src/plugins/interfaces/afb/IAFBApi.h
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2018-2019 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 VSHL_COMMON_INTERFACES_AFBAPI_H_
+#define VSHL_COMMON_INTERFACES_AFBAPI_H_
+
+#include <memory>
+#include <string>
+
+#include <json-c/json_object.h>
+
+using namespace std;
+
+namespace vshl {
+namespace common {
+namespace interfaces {
+
+/**
+ * Interface to represent AFB Request.
+ */
+class IAFBRequest {
+public:
+ /**
+ * Gets the native request object.
+ */
+ virtual void* getNativeRequest() = 0;
+};
+
+/**
+ * Interface to encapsulate all AFB (AGL Application Framework Binding)
+ * functions.
+ */
+class IAFBApi {
+public:
+ /**
+ * Interface to represent AFB Event
+ */
+ class IAFBEvent {
+ public:
+ /**
+ * Gets human readable name of the event.
+ */
+ virtual std::string getName() const = 0;
+
+ /**
+ * Returns true if event is valid. False otherwise.
+ */
+ virtual bool isValid() = 0;
+
+ /**
+ * Publish event to all observers.
+ *
+ * @return The number of observers that received the event.
+ */
+ virtual int publishEvent(struct json_object* payload) = 0;
+
+ /**
+ * Subscribe to the event
+ *
+ * @c request Party interested in the event.
+ */
+ virtual bool subscribe(IAFBRequest& request) = 0;
+
+ /**
+ * Unsubscribe to the event
+ *
+ * @c request Party no longer interested in the event.
+ */
+ virtual bool unsubscribe(IAFBRequest& request) = 0;
+ };
+
+ virtual std::shared_ptr<IAFBEvent> createEvent(const std::string& eventName) = 0;
+
+ virtual int callSync(
+ const std::string& api,
+ const std::string& verb,
+ struct json_object* request,
+ struct json_object** result,
+ std::string& error,
+ std::string& info) = 0;
+};
+
+} // namespace interfaces
+} // namespace common
+} // namespace vshl
+
+#endif // VSHL_COMMON_INTERFACES_AFBAPI_H_
diff --git a/src/plugins/interfaces/capabilities/ICapability.h b/src/plugins/interfaces/capabilities/ICapability.h
new file mode 100644
index 0000000..4b134b7
--- /dev/null
+++ b/src/plugins/interfaces/capabilities/ICapability.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2018-2019 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 VSHL_COMMON_INTERFACES_ICAPABILITY_H_
+#define VSHL_COMMON_INTERFACES_ICAPABILITY_H_
+
+#include <list>
+#include <string>
+
+using namespace std;
+
+namespace vshl {
+namespace common {
+namespace interfaces {
+
+/*
+ * This interface defines the structure for a specific voiceagent capability.
+ */
+class ICapability {
+public:
+ /*
+ * Returns the capability's name.
+ */
+ virtual string getName() const = 0;
+
+ /*
+ * Returns the list of upstream messages.
+ */
+ virtual list<string> getUpstreamMessages() const = 0;
+
+ /*
+ * Returns the list of downstream messages
+ */
+ virtual list<string> getDownstreamMessages() const = 0;
+
+ /**
+ * Virtual destructor to assure proper cleanup of derived types.
+ */
+ virtual ~ICapability() = default;
+};
+
+} // namespace interfaces
+} // namespace common
+} // namespace vshl
+
+#endif // VSHL_COMMON_INTERFACES_ICAPABILITY_H_
diff --git a/src/plugins/interfaces/utilities/events/IEventFilter.h b/src/plugins/interfaces/utilities/events/IEventFilter.h
new file mode 100644
index 0000000..33d8202
--- /dev/null
+++ b/src/plugins/interfaces/utilities/events/IEventFilter.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2018-2019 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 VSHL_COMMON_INTERFACES_IEVENTFILTER_H_
+#define VSHL_COMMON_INTERFACES_IEVENTFILTER_H_
+
+#include <string>
+
+using namespace std;
+
+namespace vshl {
+namespace common {
+namespace interfaces {
+/*
+ * This is an abstract class that is responsible for filtering the events
+ * that are delivered to the high level voice service from apps or voiceagents.
+ */
+class IEventFilter {
+public:
+ // Name of the event filter.
+ virtual string getName() = 0;
+
+ // Every event filter needs to implement this method and
+ // return true if consuming the event or false otherwise.
+ virtual bool onIncomingEvent(const string eventName, const string voiceAgentId, const string payload) = 0;
+
+ // Destructor
+ virtual ~IEventFilter() = default;
+};
+
+} // namespace interfaces
+} // namespace common
+} // namespace vshl
+
+#endif // VSHL_COMMON_INTERFACES_IEVENTFILTER_H_
diff --git a/src/plugins/interfaces/utilities/logging/ILogger.h b/src/plugins/interfaces/utilities/logging/ILogger.h
new file mode 100644
index 0000000..a2618bc
--- /dev/null
+++ b/src/plugins/interfaces/utilities/logging/ILogger.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2018-2019 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 VSHL_COMMON_INTERFACES_LOGGER_H_
+#define VSHL_COMMON_INTERFACES_LOGGER_H_
+
+#include <string>
+
+namespace vshl {
+namespace common {
+namespace interfaces {
+
+class ILogger {
+public:
+ enum Level {
+ DEBUG,
+ INFO,
+ WARNING,
+ ERROR,
+ NOTICE,
+ };
+
+ virtual void log(Level level, const std::string &tag,
+ const std::string &message) = 0;
+};
+
+} // namespace interfaces
+} // namespace common
+} // namespace vshl
+
+#endif // VSHL_COMMON_INTERFACES_LOGGER_H_
diff --git a/src/plugins/interfaces/voiceagents/IVoiceAgent.h b/src/plugins/interfaces/voiceagents/IVoiceAgent.h
new file mode 100644
index 0000000..367ad72
--- /dev/null
+++ b/src/plugins/interfaces/voiceagents/IVoiceAgent.h
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2018-2019 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 VSHL_COMMON_INTERFACES_IVOICEAGENT_H_
+#define VSHL_COMMON_INTERFACES_IVOICEAGENT_H_
+
+#include <memory>
+#include <string>
+#include <unordered_set>
+
+using namespace std;
+
+namespace vshl {
+namespace common {
+namespace interfaces {
+
+/*
+ * This interface define the structure for VoiceAgent Information.
+ * The implementation of this structure is owned by voiceagents module.
+ */
+class IVoiceAgent {
+public:
+ /*
+ * Set the active wakeword for this voiceagent
+ */
+ virtual bool setActiveWakeWord(const string &wakeword) = 0;
+
+ /*
+ * Sets the activation state of this voiceagent
+ */
+ virtual void setIsActive(bool active) = 0;
+
+ /*
+ * Returns the voiceagent's ID.
+ */
+ virtual string getId() const = 0;
+
+ /*
+ * Returns the voiceagent's name.
+ */
+ virtual string getName() const = 0;
+
+ /*
+ * Returns the voiceagent's description.
+ */
+ virtual string getDescription() const = 0;
+
+ /*
+ * Returns the voiceagent's API.
+ */
+ virtual string getApi() const = 0;
+
+ /*
+ * Returns the voiceagent's vendor information/
+ */
+ virtual string getVendor() const = 0;
+
+ /*
+ * Returns the list of wakewords mapped to the voiceagent.
+ */
+ virtual shared_ptr<unordered_set<string>> getWakeWords() const = 0;
+
+ /*
+ * Returns true if the voiceagent is active. False otherwise.
+ */
+ virtual bool isActive() const = 0;
+
+ /*
+ * Returns the active wakeword for the voiceagent.
+ */
+ virtual string getActiveWakeword() const = 0;
+
+ /**
+ * Virtual destructor to assure proper cleanup of derived types.
+ */
+ virtual ~IVoiceAgent() = default;
+};
+
+} // namespace interfaces
+} // namespace common
+} // namespace vshl
+
+#endif // VSHL_COMMON_INTERFACES_IVOICEAGENT_H_
diff --git a/src/plugins/interfaces/voiceagents/IVoiceAgentsChangeObserver.h b/src/plugins/interfaces/voiceagents/IVoiceAgentsChangeObserver.h
new file mode 100644
index 0000000..e552ab5
--- /dev/null
+++ b/src/plugins/interfaces/voiceagents/IVoiceAgentsChangeObserver.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2018-2019 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 VSHL_COMMON_INTERFACES_IVOICEAGENTSCHANGEOBSERVER_H_
+#define VSHL_COMMON_INTERFACES_IVOICEAGENTSCHANGEOBSERVER_H_
+
+#include <memory>
+#include <string>
+
+#include "interfaces/voiceagents/IVoiceAgent.h"
+
+using namespace std;
+
+namespace vshl {
+namespace common {
+namespace interfaces {
+
+/*
+ * This interface is used to observe changes to the voiceagents datastore.
+ * The voiceagents data store is contained in the voiceagents module.
+ */
+class IVoiceAgentsChangeObserver {
+public:
+ /**
+ * This method notifies the observers that the default voiceagent selection
+ * has been updated.
+ */
+ virtual void
+ OnDefaultVoiceAgentChanged(shared_ptr<IVoiceAgent> defaultVoiceAgent) = 0;
+
+ /**
+ * This method notifies the observers that a new voiceagent has been added.
+ */
+ virtual void OnVoiceAgentAdded(shared_ptr<IVoiceAgent> voiceAgent) = 0;
+
+ /**
+ * This method notifies the observers that a voiceagent is removed.
+ */
+ virtual void OnVoiceAgentRemoved(shared_ptr<IVoiceAgent> voiceAgent) = 0;
+
+ /**
+ * This method notifies the observers that active wakeword for a voiceagent is
+ * updated.
+ */
+ virtual void
+ OnVoiceAgentActiveWakeWordChanged(shared_ptr<IVoiceAgent> voiceAgent) = 0;
+
+ /**
+ * This method notifies the observers that the voiceagent has been activated.
+ */
+ virtual void OnVoiceAgentActivated(shared_ptr<IVoiceAgent> voiceAgent) = 0;
+
+ /**
+ * This method notifies the observers that the voiceagent has been activated.
+ */
+ virtual void OnVoiceAgentDeactivated(shared_ptr<IVoiceAgent> voiceAgent) = 0;
+
+ /**
+ * Virtual destructor to assure proper cleanup of derived types.
+ */
+ virtual ~IVoiceAgentsChangeObserver() = default;
+};
+
+} // namespace interfaces
+} // namespace common
+} // namespace vshl
+
+#endif // VSHL_COMMON_INTERFACES_IVOICEAGENTSCHANGEOBSERVER_H_
diff --git a/src/plugins/test/common/ConsoleLogger.cpp b/src/plugins/test/common/ConsoleLogger.cpp
new file mode 100644
index 0000000..d4f9eef
--- /dev/null
+++ b/src/plugins/test/common/ConsoleLogger.cpp
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2018-2019 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.
+ */
+
+#include <iostream>
+
+#include "test/common/ConsoleLogger.h"
+
+namespace vshl {
+namespace test {
+namespace common {
+
+void ConsoleLogger::log(Level level, const std::string& tag, const std::string& message) {
+ string format_msg = "Tag: " + tag + ", message: " + message;
+ std::cout << format_msg << std::endl;
+}
+
+} // namespace common
+} // namespace test
+} // namespace vshl
diff --git a/src/plugins/test/common/ConsoleLogger.h b/src/plugins/test/common/ConsoleLogger.h
new file mode 100644
index 0000000..11bc0d8
--- /dev/null
+++ b/src/plugins/test/common/ConsoleLogger.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2018-2019 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 VSHL_TEST_COMMON_CONSOLE_LOGGER_H_
+#define VSHL_TEST_COMMON_CONSOLE_LOGGER_H_
+
+#include "interfaces/utilities/logging/ILogger.h"
+
+using namespace std;
+
+namespace vshl {
+namespace test {
+namespace common {
+
+class ConsoleLogger : public vshl::common::interfaces::ILogger {
+public:
+ // ILogger interface
+ void log(Level level, const std::string &tag,
+ const std::string &message) override;
+};
+
+} // namespace common
+} // namespace test
+} // namespace vshl
+
+#endif // VSHL_TEST_COMMON_CONSOLE_LOGGER_H_
diff --git a/src/plugins/test/mocks/AFBApiMock.h b/src/plugins/test/mocks/AFBApiMock.h
new file mode 100644
index 0000000..46e2e99
--- /dev/null
+++ b/src/plugins/test/mocks/AFBApiMock.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2018-2019 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 VSHL_TEST_MOCKS_AFBAPIMOCK_H_
+#define VSHL_TEST_MOCKS_AFBAPIMOCK_H_
+
+#include <gmock/gmock.h>
+
+#include "interfaces/afb/IAFBApi.h"
+
+namespace vshl {
+namespace test {
+
+class AFBApiMock : public vshl::common::interfaces::IAFBApi {
+public:
+ MOCK_METHOD1(createEvent, std::shared_ptr<IAFBEvent>(const std::string& eventName));
+ MOCK_METHOD6(
+ callSync,
+ int(const std::string& api,
+ const std::string& verb,
+ struct json_object* request,
+ struct json_object** result,
+ std::string& error,
+ std::string& info));
+};
+
+} // namespace test
+} // namespace vshl
+
+#endif // VSHL_TEST_MOCKS_AFBAPIMOCK_H_ \ No newline at end of file
diff --git a/src/plugins/test/mocks/AFBEventMock.h b/src/plugins/test/mocks/AFBEventMock.h
new file mode 100644
index 0000000..3d78e9f
--- /dev/null
+++ b/src/plugins/test/mocks/AFBEventMock.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2018-2019 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 VSHL_TEST_MOCKS_AFBEVENTMOCK_H_
+#define VSHL_TEST_MOCKS_AFBEVENTMOCK_H_
+
+#include <gmock/gmock.h>
+
+#include "interfaces/afb/IAFBApi.h"
+
+namespace vshl {
+namespace test {
+
+class AFBEventMock : public vshl::common::interfaces::IAFBApi::IAFBEvent {
+public:
+ void setName(const std::string& name) {
+ mName = name;
+ }
+
+ std::string getName() const override {
+ return mName;
+ }
+
+ MOCK_METHOD0(isValid, bool());
+ MOCK_METHOD1(publishEvent, int(struct json_object* payload));
+ MOCK_METHOD1(subscribe, bool(vshl::common::interfaces::IAFBRequest& request));
+ MOCK_METHOD1(unsubscribe, bool(vshl::common::interfaces::IAFBRequest& request));
+
+private:
+ std::string mName;
+};
+
+} // namespace test
+} // namespace vshl
+
+#endif // VSHL_TEST_MOCKS_AFBEVENTMOCK_H_ \ No newline at end of file
diff --git a/src/plugins/test/mocks/AFBRequestMock.h b/src/plugins/test/mocks/AFBRequestMock.h
new file mode 100644
index 0000000..5557565
--- /dev/null
+++ b/src/plugins/test/mocks/AFBRequestMock.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2018-2019 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 VSHL_TEST_MOCKS_AFBREQUESTMOCK_H_
+#define VSHL_TEST_MOCKS_AFBREQUESTMOCK_H_
+
+#include <gmock/gmock.h>
+
+#include "interfaces/afb/IAFBApi.h"
+
+namespace vshl {
+namespace test {
+
+class AFBRequestMock : public vshl::common::interfaces::IAFBRequest {
+public:
+ MOCK_METHOD0(getNativeRequest, void*());
+};
+
+} // namespace test
+} // namespace vshl
+
+#endif // VSHL_TEST_MOCKS_AFBREQUESTMOCK_H_ \ No newline at end of file
diff --git a/src/plugins/test/mocks/CapabilityMock.h b/src/plugins/test/mocks/CapabilityMock.h
new file mode 100644
index 0000000..a2201df
--- /dev/null
+++ b/src/plugins/test/mocks/CapabilityMock.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2018-2019 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 VSHL_TEST_MOCKS_CAPABILITYMOCK_H_
+#define VSHL_TEST_MOCKS_CAPABILITYMOCK_H_
+
+#include <gmock/gmock.h>
+
+#include "interfaces/capabilities/ICapability.h"
+
+namespace vshl {
+namespace test {
+
+class CapabilityMock : public vshl::common::interfaces::ICapability {
+public:
+ MOCK_CONST_METHOD0(getName, std::string());
+ MOCK_CONST_METHOD0(getUpstreamMessages, std::list<std::string>());
+ MOCK_CONST_METHOD0(getDownstreamMessages, std::list<std::string>());
+};
+
+} // namespace test
+} // namespace vshl
+
+#endif // VSHL_TEST_MOCKS_CAPABILITYMOCK_H_ \ No newline at end of file
diff --git a/src/plugins/test/mocks/VoiceAgentsChangeObserverMock.h b/src/plugins/test/mocks/VoiceAgentsChangeObserverMock.h
new file mode 100644
index 0000000..6edeea2
--- /dev/null
+++ b/src/plugins/test/mocks/VoiceAgentsChangeObserverMock.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2018-2019 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 VSHL_TEST_MOCKS_VOICEAGENTSCHANGEOBSERVERMOCK_H_
+#define VSHL_TEST_MOCKS_VOICEAGENTSCHANGEOBSERVERMOCK_H_
+
+#include <gmock/gmock.h>
+
+#include "interfaces/voiceagents/IVoiceAgentsChangeObserver.h"
+
+namespace vshl {
+namespace test {
+
+class VoiceAgentsChangeObserverMock : public vshl::common::interfaces::IVoiceAgentsChangeObserver {
+public:
+ MOCK_METHOD1(OnDefaultVoiceAgentChanged, void(shared_ptr<vshl::common::interfaces::IVoiceAgent> defaultVoiceAgent));
+ MOCK_METHOD1(OnVoiceAgentAdded, void(shared_ptr<vshl::common::interfaces::IVoiceAgent> voiceAgent));
+ MOCK_METHOD1(OnVoiceAgentRemoved, void(shared_ptr<vshl::common::interfaces::IVoiceAgent> voiceAgent));
+ MOCK_METHOD1(OnVoiceAgentActiveWakeWordChanged, void(shared_ptr<vshl::common::interfaces::IVoiceAgent> voiceAgent));
+ MOCK_METHOD1(OnVoiceAgentActivated, void(shared_ptr<vshl::common::interfaces::IVoiceAgent> voiceAgent));
+ MOCK_METHOD1(OnVoiceAgentDeactivated, void(shared_ptr<vshl::common::interfaces::IVoiceAgent> voiceAgent));
+};
+
+} // namespace test
+} // namespace vshl
+
+#endif // VSHL_TEST_MOCKS_VOICEAGENTSCHANGEOBSERVERMOCK_H_ \ No newline at end of file
diff --git a/src/plugins/utilities/events/EventRouter.cpp b/src/plugins/utilities/events/EventRouter.cpp
new file mode 100644
index 0000000..999c3dd
--- /dev/null
+++ b/src/plugins/utilities/events/EventRouter.cpp
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2018-2019 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.
+ */
+#include "utilities/events/EventRouter.h"
+
+static string TAG = "vshl::utilities::events::EventRouter";
+
+using Level = vshl::common::interfaces::ILogger::Level;
+
+namespace vshl {
+namespace utilities {
+namespace events {
+
+unique_ptr<EventRouter> EventRouter::create(shared_ptr<vshl::common::interfaces::ILogger> logger) {
+ return std::unique_ptr<EventRouter>(new EventRouter(logger));
+}
+
+EventRouter::EventRouter(shared_ptr<vshl::common::interfaces::ILogger> logger) : mLogger(logger) {
+}
+
+EventRouter::~EventRouter() {
+ mEventFilters.clear();
+}
+
+bool EventRouter::handleIncomingEvent(const string eventName, const string voiceAgentId, const string payload) {
+ for (auto eventFilter : mEventFilters) {
+ if (eventFilter->onIncomingEvent(eventName, voiceAgentId, payload)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool EventRouter::addEventFilter(shared_ptr<vshl::common::interfaces::IEventFilter> filter) {
+ if (!filter) {
+ mLogger->log(Level::ERROR, TAG, "Failed to add event filter. Invalid arguments.");
+ return false;
+ }
+
+ mEventFilters.insert(filter);
+ return true;
+}
+
+bool EventRouter::removeEventFilter(shared_ptr<vshl::common::interfaces::IEventFilter> filter) {
+ if (!filter) {
+ mLogger->log(Level::ERROR, TAG, "Failed to add remove filter. Invalid arguments.");
+ return false;
+ }
+
+ mEventFilters.erase(filter);
+ return true;
+}
+
+} // namespace events
+} // namespace utilities
+} // namespace vshl
diff --git a/src/plugins/utilities/events/EventRouter.h b/src/plugins/utilities/events/EventRouter.h
new file mode 100644
index 0000000..fd7f0c2
--- /dev/null
+++ b/src/plugins/utilities/events/EventRouter.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2018-2019 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 VSHL_UTILITIES_EVENTS_EVENTMANAGER_H_
+#define VSHL_UTILITIES_EVENTS_EVENTMANAGER_H_
+
+#include <memory>
+#include <string>
+#include <unordered_set>
+
+#include "interfaces/utilities/events/IEventFilter.h"
+#include "interfaces/utilities/logging/ILogger.h"
+
+using namespace std;
+
+namespace vshl {
+namespace utilities {
+namespace events {
+/*
+ * This class is responsible for routing incoming events to
+ * the appropriate event listener for consumption.
+ * Note: The listeners should implement the IEventFilter class.
+ */
+class EventRouter {
+public:
+ static unique_ptr<EventRouter> create(shared_ptr<vshl::common::interfaces::ILogger> logger);
+
+ // Destructor
+ ~EventRouter();
+
+ // Add event filter as listerner.
+ bool addEventFilter(shared_ptr<vshl::common::interfaces::IEventFilter> filter);
+
+ // Remove event filter as listerner.
+ bool removeEventFilter(shared_ptr<vshl::common::interfaces::IEventFilter> filter);
+
+ // This method is called by the controller for routing
+ // the event to appropriate listener.
+ bool handleIncomingEvent(const string eventName, const string voiceAgentId, const string payload);
+
+private:
+ EventRouter(shared_ptr<vshl::common::interfaces::ILogger> logger);
+
+ // set of event filters.
+ unordered_set<shared_ptr<vshl::common::interfaces::IEventFilter>> mEventFilters;
+
+ // Logger
+ shared_ptr<vshl::common::interfaces::ILogger> mLogger;
+};
+
+} // namespace events
+} // namespace utilities
+} // namespace vshl
+
+#endif // VSHL_UTILITIES_EVENTS_EVENTROUTER_H_
diff --git a/src/plugins/utilities/logging/Logger.cpp b/src/plugins/utilities/logging/Logger.cpp
new file mode 100644
index 0000000..6374e19
--- /dev/null
+++ b/src/plugins/utilities/logging/Logger.cpp
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2018-2019 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.
+ */
+#include "utilities/logging/Logger.h"
+
+namespace vshl {
+namespace utilities {
+namespace logging {
+
+// Constructor
+Logger::Logger(AFB_ApiT api) {
+ mApi = api;
+}
+
+unique_ptr<Logger> Logger::create(AFB_ApiT api) {
+ auto logger = std::unique_ptr<Logger>(new Logger(api));
+ return logger;
+}
+
+void Logger::log(Level level, const std::string& tag, const std::string& message) {
+ string format_msg = "Tag: " + tag + ", message: " + message;
+ switch (level) {
+ case Level::NOTICE:
+ AFB_ApiNotice(mApi, format_msg.c_str());
+ break;
+ case Level::WARNING:
+ AFB_ApiWarning(mApi, format_msg.c_str());
+ break;
+ case Level::DEBUG:
+ AFB_ApiDebug(mApi, format_msg.c_str());
+ break;
+ case Level::ERROR:
+ AFB_ApiError(mApi, format_msg.c_str());
+ break;
+ case Level::INFO:
+ AFB_ApiInfo(mApi, format_msg.c_str());
+ break;
+ default:
+ break;
+ }
+}
+
+} // namespace logging
+} // namespace utilities
+} // namespace vshl
diff --git a/src/plugins/utilities/logging/Logger.h b/src/plugins/utilities/logging/Logger.h
new file mode 100644
index 0000000..79c89a5
--- /dev/null
+++ b/src/plugins/utilities/logging/Logger.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2018-2019 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 VSHL_UTILITIES_LOGGING_LOGGER_H_
+#define VSHL_UTILITIES_LOGGING_LOGGER_H_
+
+#include <memory>
+
+extern "C" {
+#define AFB_BINDING_VERSION 3
+#include "afb-definitions.h"
+#include "ctl-plugin.h"
+};
+
+#include "interfaces/utilities/logging/ILogger.h"
+
+using namespace std;
+
+namespace vshl {
+namespace utilities {
+namespace logging {
+
+class Logger : public vshl::common::interfaces::ILogger {
+public:
+ static std::unique_ptr<Logger> create(AFB_ApiT api);
+
+ // ILogger interface
+ void log(Level level, const std::string &tag,
+ const std::string &message) override;
+
+private:
+ Logger(AFB_ApiT api);
+
+ // Binding API reference
+ AFB_ApiT mApi;
+};
+
+} // namespace logging
+} // namespace utilities
+} // namespace vshl
+
+#endif // VSHL_UTILITIES_LOGGING_LOGGER_H_
diff --git a/src/plugins/utilities/uuid/UUIDGeneration.cpp b/src/plugins/utilities/uuid/UUIDGeneration.cpp
new file mode 100644
index 0000000..bb03fc6
--- /dev/null
+++ b/src/plugins/utilities/uuid/UUIDGeneration.cpp
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2018-2019 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.
+ */
+// This code is copied from https://github.com/alexa/avs-device-sdk/
+// Minor modifications are made to the original file.
+
+#include <random>
+#include <chrono>
+#include <sstream>
+#include <iomanip>
+#include <mutex>
+#include <climits>
+#include <algorithm>
+#include <functional>
+
+#include "utilities/uuid/UUIDGeneration.h"
+
+namespace vshl {
+namespace utilities {
+namespace uuid {
+
+/// String to identify log entries originating from this file.
+static const std::string TAG("UUIDGeneration");
+
+/// The UUID version (Version 4), shifted into the correct position in the byte.
+static const uint8_t UUID_VERSION_VALUE = 4 << 4;
+
+/// The UUID variant (Variant 1), shifted into the correct position in the byte.
+static const size_t UUID_VARIANT_VALUE = 2 << 6;
+
+/// Separator used between UUID fields.
+static const std::string SEPARATOR("-");
+
+/// Number of bits in the replacement value.
+static const size_t MAX_NUM_REPLACEMENT_BITS = CHAR_BIT;
+
+/// Number of bits in a hex digit.
+static const size_t BITS_IN_HEX_DIGIT = 4;
+
+/**
+ * Randomly generate a string of hex digits. Before the conversion of hex to string,
+ * the function allows replacement of the bits of the first two generated hex digits.
+ * Replacement happens starting at the most significant, to the least significant bit.
+ *
+ * @param ibe A random number generator.
+ * @param numDigits The number of hex digits [0-9],[a-f] to generate.
+ * @param replacementBits The replacement value for up to the first two digits generated.
+ * @param numReplacementBits The number of bits of @c replacementBits to use.
+ *
+ * @return A hex string of length @c numDigits.
+ */
+static const std::string generateHexWithReplacement(
+ std::independent_bits_engine<std::default_random_engine, CHAR_BIT, uint8_t>& ibe,
+ unsigned int numDigits,
+ uint8_t replacementBits,
+ unsigned short numReplacementBits) {
+ if (numReplacementBits > MAX_NUM_REPLACEMENT_BITS) {
+ return "";
+ }
+
+ if (numReplacementBits > (numDigits * BITS_IN_HEX_DIGIT)) {
+ return "";
+ }
+
+ // Makes assumption that 1 digit = 4 bits.
+ std::vector<uint8_t> bytes(ceil(numDigits / 2.0));
+ std::generate(bytes.begin(), bytes.end(), std::ref(ibe));
+
+ // Replace the specified number of bits from the first byte.
+ bytes.at(0) &= (0xff >> numReplacementBits);
+ replacementBits &= (0xff << (MAX_NUM_REPLACEMENT_BITS - numReplacementBits));
+ bytes.at(0) |= replacementBits;
+
+ std::ostringstream oss;
+ for (const auto& byte : bytes) {
+ oss << std::hex << std::setfill('0') << std::setw(2) << static_cast<int>(byte);
+ }
+
+ std::string bytesText = oss.str();
+ // Remove the last digit for odd numDigits case.
+ bytesText.resize(numDigits);
+
+ return bytesText;
+}
+
+/**
+ * Randomly generate a string of hex digits.
+ *
+ * @param ibe A random number generator.
+ * @param numDigits The number of hex digits [0-9],[a-f] to generate.
+ *
+ * @return A hex string of length @c numDigits.
+ */
+static const std::string generateHex(
+ std::independent_bits_engine<std::default_random_engine, CHAR_BIT, uint8_t>& ibe,
+ unsigned int numDigits) {
+ return generateHexWithReplacement(ibe, numDigits, 0, 0);
+}
+
+const std::string generateUUID() {
+ static bool seeded = false;
+ static std::independent_bits_engine<std::default_random_engine, CHAR_BIT, uint8_t> ibe;
+ static std::mutex mutex;
+ std::unique_lock<std::mutex> lock(mutex);
+ if (!seeded) {
+ std::random_device rd;
+ ibe.seed(
+ rd() +
+ std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::steady_clock::now().time_since_epoch())
+ .count());
+ seeded = true;
+ }
+
+ std::ostringstream uuidText;
+ uuidText << generateHex(ibe, 8) << SEPARATOR << generateHex(ibe, 4) << SEPARATOR
+ << generateHexWithReplacement(ibe, 4, UUID_VERSION_VALUE, 4) << SEPARATOR
+ << generateHexWithReplacement(ibe, 4, UUID_VARIANT_VALUE, 2) << SEPARATOR << generateHex(ibe, 12);
+
+ lock.unlock();
+
+ return uuidText.str();
+}
+
+} // namespace uuid
+} // namespace utilities
+} // namespace vshl
diff --git a/src/plugins/utilities/uuid/UUIDGeneration.h b/src/plugins/utilities/uuid/UUIDGeneration.h
new file mode 100644
index 0000000..7af9cb1
--- /dev/null
+++ b/src/plugins/utilities/uuid/UUIDGeneration.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2018-2019 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.
+ */
+// This code is copied from https://github.com/alexa/avs-device-sdk/
+// Minor modifications are made to the original file.
+
+#ifndef VSHL_UTILITIES_UUID_UUIDGENERATION_H_
+#define VSHL_UTILITIES_UUID_UUIDGENERATION_H_
+
+#include <string>
+
+namespace vshl {
+namespace utilities {
+namespace uuid {
+
+/**
+ * Generates a variant 1, version 4 universally unique identifier (UUID) consisting of 32 hexadecimal digits.
+ * The UUID generated is of the format xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx where M indicates the version, and the two
+ * most significant bits of N indicates the variant. M is 0100 (binary) for version 4 and N is 10xx(binary) for
+ * variant 1.
+ * @see https://tools.ietf.org/html/rfc4122.
+ *
+ * @return A uuid as a string.
+ */
+const std::string generateUUID();
+
+} // namespace uuid
+} // namespace utilities
+} // namespace vshl
+
+#endif // VSHL_UTILITIES_UUID_UUIDGENERATION_H_ \ No newline at end of file
diff --git a/src/plugins/voiceagents/VoiceAgentEventNames.h b/src/plugins/voiceagents/VoiceAgentEventNames.h
new file mode 100644
index 0000000..4575528
--- /dev/null
+++ b/src/plugins/voiceagents/VoiceAgentEventNames.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2018-2019 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 VSHL_VOICEAGENTS_INCLUDE_VOICEAGENTEVENTNAMES_H_
+#define VSHL_VOICEAGENTS_INCLUDE_VOICEAGENTEVENTNAMES_H_
+
+#include <list>
+#include <string>
+
+using namespace std;
+
+namespace vshl {
+namespace voiceagents {
+
+static string VSHL_EVENT_AUTH_STATE_EVENT = "voice_authstate_event";
+static string VSHL_EVENT_CONNECTION_STATE_EVENT = "voice_connectionstate_event";
+static string VSHL_EVENT_DIALOG_STATE_EVENT = "voice_dialogstate_event";
+
+static list<string> VSHL_EVENTS = {
+ VSHL_EVENT_AUTH_STATE_EVENT, VSHL_EVENT_CONNECTION_STATE_EVENT,
+ VSHL_EVENT_DIALOG_STATE_EVENT,
+};
+
+} // namespace voiceagents
+} // namespace vshl
+
+#endif // VSHL_VOICEAGENTS_INCLUDE_VOICEAGENTEVENTNAMES_H_
diff --git a/src/plugins/voiceagents/VoiceAgentsDataManager.h b/src/plugins/voiceagents/VoiceAgentsDataManager.h
new file mode 100644
index 0000000..a4c9143
--- /dev/null
+++ b/src/plugins/voiceagents/VoiceAgentsDataManager.h
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2018-2019 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 VSHL_VOICEAGENTS_INCLUDE_VOICEAGENTS_H_
+#define VSHL_VOICEAGENTS_INCLUDE_VOICEAGENTS_H_
+
+#include <memory>
+#include <set>
+#include <unordered_map>
+#include <unordered_set>
+
+#include "interfaces/afb/IAFBApi.h"
+#include "interfaces/utilities/events/IEventFilter.h"
+#include "interfaces/utilities/logging/ILogger.h"
+#include "interfaces/voiceagents/IVoiceAgent.h"
+#include "interfaces/voiceagents/IVoiceAgentsChangeObserver.h"
+#include "voiceagents/include/VoiceAgent.h"
+#include "voiceagents/include/VoiceAgentEventsHandler.h"
+
+namespace vshl {
+namespace voiceagents {
+/*
+ * This class implements the data model for voiceagents.
+ * Supports add, remove and query operations on voiceagent data.
+ * Notifies the observers of the changes in the voiceagents data model.
+ */
+class VoiceAgentsDataManager {
+public:
+ // Create a VoiceAgentsDataManager.
+ static std::unique_ptr<VoiceAgentsDataManager> create(
+ shared_ptr<vshl::common::interfaces::ILogger> logger,
+ shared_ptr<vshl::common::interfaces::IAFBApi> afbApi);
+
+ /**
+ * Activates the list of voiceagents.
+ *
+ * @return Number of activated agents
+ */
+ uint32_t activateVoiceAgents(const unordered_set<string>& activeVoiceAgentIds);
+
+ /**
+ * Deactivates the list of voiceagents.
+ *
+ * @return Number of de-activated agents
+ */
+ uint32_t deactivateVoiceAgents(const unordered_set<string>& inactiveVoiceAgentIds);
+
+ // Sets the default voiceagent.
+ bool setDefaultVoiceAgent(const string& voiceAgentId);
+
+ // Sets the default voiceagent.
+ std::string getDefaultVoiceAgent();
+
+ // Sets the active wakeword for the voiceagent.
+ bool setActiveWakeWord(const string& voiceAgentId, const string& wakeword);
+
+ // Adds a new voiceagent to the cache and also persists the information in a
+ // database.
+ // This call would notify all the observers about the new voiceagent addition.
+ bool addNewVoiceAgent(
+ const string& id,
+ const string& name,
+ const string& description,
+ const string& api,
+ const string& vendor,
+ const string& activeWakeword,
+ const bool isActive,
+ const shared_ptr<unordered_set<string>> wakewords);
+
+ // Removes the voiceagent from thecache and also from persistent database.
+ // This call would notify all the observers about the removal of the
+ // voiceagent.
+ bool removeVoiceAgent(const string& voiceAgentId);
+
+ // Returns the set of all voice agents in @c VoiceAgentsDataManger cache
+ std::set<std::shared_ptr<vshl::common::interfaces::IVoiceAgent>> getAllVoiceAgents();
+
+ // Returns the event filter that belongs to the core module.
+ shared_ptr<vshl::common::interfaces::IEventFilter> getEventFilter() const;
+
+ // Subscribe to an event coming from the voiceagent.
+ bool subscribeToVshlEventFromVoiceAgent(
+ vshl::common::interfaces::IAFBRequest& request,
+ const string eventName,
+ const string voiceagentId);
+
+ // Adds a new voiceagent change observer.
+ bool addVoiceAgentsChangeObserver(shared_ptr<vshl::common::interfaces::IVoiceAgentsChangeObserver> observer);
+
+ // Removes the voiceagent change observer from the list.
+ bool removeVoiceAgentsChangeObserver(shared_ptr<vshl::common::interfaces::IVoiceAgentsChangeObserver> observer);
+
+ // Destructor
+ ~VoiceAgentsDataManager();
+
+private:
+ // Constructor
+ VoiceAgentsDataManager(
+ shared_ptr<vshl::common::interfaces::ILogger> logger,
+ shared_ptr<vshl::common::interfaces::IAFBApi> afbApi);
+
+ // Binding API reference
+ shared_ptr<vshl::common::interfaces::IAFBApi> mAfbApi;
+
+ // A list of all the voiceagent change observers
+ unordered_set<shared_ptr<vshl::common::interfaces::IVoiceAgentsChangeObserver>> mVoiceAgentChangeObservers;
+
+ // A map of voiceagents grouped by ID
+ unordered_map<string, shared_ptr<VoiceAgent>> mVoiceAgents;
+
+ // Voiceagent event handler.
+ shared_ptr<VoiceAgentEventsHandler> mVoiceAgentEventsHandler;
+
+ // Default voiceagent
+ string mDefaultVoiceAgentId;
+
+ // Logger
+ shared_ptr<vshl::common::interfaces::ILogger> mLogger;
+};
+
+} // namespace voiceagents
+} // namespace vshl
+
+#endif // VSHL_VOICEAGENTS_INCLUDE_VOICEAGENTS_H_
diff --git a/src/plugins/voiceagents/VoiceAgentsDataManagerImpl.cpp b/src/plugins/voiceagents/VoiceAgentsDataManagerImpl.cpp
new file mode 100644
index 0000000..626a7fc
--- /dev/null
+++ b/src/plugins/voiceagents/VoiceAgentsDataManagerImpl.cpp
@@ -0,0 +1,272 @@
+/*
+ * Copyright 2018-2019 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.
+ */
+#include "voiceagents/VoiceAgentsDataManager.h"
+
+#include "voiceagents/include/VoiceAgentEventsHandler.h"
+
+static string TAG = "vshl::voiceagents::VoiceAgentsDataManager";
+
+/**
+ * Specifies the severity level of a log message
+ */
+using Level = vshl::common::interfaces::ILogger::Level;
+
+namespace vshl {
+namespace voiceagents {
+
+std::unique_ptr<VoiceAgentsDataManager> VoiceAgentsDataManager::create(
+ shared_ptr<vshl::common::interfaces::ILogger> logger,
+ shared_ptr<vshl::common::interfaces::IAFBApi> afbApi) {
+ return std::unique_ptr<VoiceAgentsDataManager>(new VoiceAgentsDataManager(logger, afbApi));
+}
+
+// Constructor
+VoiceAgentsDataManager::VoiceAgentsDataManager(
+ shared_ptr<vshl::common::interfaces::ILogger> logger,
+ shared_ptr<vshl::common::interfaces::IAFBApi> afbApi) :
+ mLogger(logger),
+ mAfbApi(afbApi) {
+ mVoiceAgentEventsHandler = VoiceAgentEventsHandler::create(mLogger, mAfbApi);
+}
+
+// Destructor
+VoiceAgentsDataManager::~VoiceAgentsDataManager() {
+ // Clear the observers
+ mVoiceAgentChangeObservers.clear();
+ // Clear the voiceagents
+ mVoiceAgents.clear();
+}
+
+uint32_t VoiceAgentsDataManager::activateVoiceAgents(const unordered_set<string>& activeVoiceAgentIds) {
+ if (activeVoiceAgentIds.empty() || mVoiceAgents.empty()) {
+ mLogger->log(Level::ERROR, TAG, "Failed to activate voiceagents");
+ return 0;
+ }
+
+ uint32_t agentsActivated = 0;
+ for (auto voiceAgentId : activeVoiceAgentIds) {
+ auto voiceAgentIt = mVoiceAgents.find(voiceAgentId);
+ if (voiceAgentIt != mVoiceAgents.end()) {
+ // activate the voiceagent
+ ++agentsActivated;
+ if (!voiceAgentIt->second->isActive()) {
+ voiceAgentIt->second->setIsActive(true);
+ // Notify observers
+ for (auto observer : mVoiceAgentChangeObservers) {
+ observer->OnVoiceAgentActivated(voiceAgentIt->second);
+ }
+ }
+ }
+ }
+ return agentsActivated;
+}
+
+uint32_t VoiceAgentsDataManager::deactivateVoiceAgents(const unordered_set<string>& inactiveVoiceAgentIds) {
+ if (inactiveVoiceAgentIds.empty() || mVoiceAgents.empty()) {
+ mLogger->log(Level::ERROR, TAG, "Failed to deactivate voiceagents");
+ return 0;
+ }
+
+ uint32_t agentsDeactivated = 0;
+ for (auto voiceAgentId : inactiveVoiceAgentIds) {
+ auto voiceAgentIt = mVoiceAgents.find(voiceAgentId);
+ if (voiceAgentIt != mVoiceAgents.end()) {
+ ++agentsDeactivated;
+ if (voiceAgentIt->second->isActive()) {
+ // deactivate the voiceagent
+ voiceAgentIt->second->setIsActive(false);
+ // Notify observers
+ for (auto observer : mVoiceAgentChangeObservers) {
+ observer->OnVoiceAgentDeactivated(voiceAgentIt->second);
+ }
+ }
+ }
+ }
+
+ return agentsDeactivated;
+}
+
+bool VoiceAgentsDataManager::setDefaultVoiceAgent(const string& voiceAgentId) {
+ if (mVoiceAgents.empty() || voiceAgentId.empty()) {
+ string message = string("Failed to set default voiceagent id: ") + voiceAgentId;
+ mLogger->log(Level::ERROR, TAG, message);
+ return false;
+ }
+
+ auto defaultVoiceAgentIt = mVoiceAgents.find(voiceAgentId);
+
+ if (defaultVoiceAgentIt != mVoiceAgents.end()) {
+ if (mDefaultVoiceAgentId != voiceAgentId) {
+ // Notify observers
+ for (auto observer : mVoiceAgentChangeObservers) {
+ observer->OnDefaultVoiceAgentChanged(defaultVoiceAgentIt->second);
+ }
+ }
+ mDefaultVoiceAgentId = voiceAgentId;
+ } else {
+ mLogger->log(Level::ERROR, TAG, "Can't set default agent. Invalid voice agent id:" + voiceAgentId);
+ return false;
+ }
+
+ return true;
+}
+
+std::string VoiceAgentsDataManager::getDefaultVoiceAgent() {
+ return mDefaultVoiceAgentId;
+}
+
+bool VoiceAgentsDataManager::setActiveWakeWord(const string& voiceAgentId, const string& wakeword) {
+ if (mVoiceAgents.empty() || wakeword.empty()) {
+ string message =
+ string("Failed to set active wakeword: ") + wakeword + string(" for voiceagent id: ") + voiceAgentId;
+ mLogger->log(Level::ERROR, TAG, message);
+ return false;
+ }
+
+ auto voiceAgentIt = mVoiceAgents.find(voiceAgentId);
+ if (voiceAgentIt == mVoiceAgents.end()) {
+ return false;
+ }
+
+ string oldWakeWord = voiceAgentIt->second->getActiveWakeword();
+ if (oldWakeWord != wakeword) {
+ voiceAgentIt->second->setActiveWakeWord(wakeword);
+ // Notify observers
+ for (auto observer : mVoiceAgentChangeObservers) {
+ observer->OnVoiceAgentActiveWakeWordChanged(voiceAgentIt->second);
+ }
+ }
+
+ return true;
+}
+
+bool VoiceAgentsDataManager::addNewVoiceAgent(
+ const string& id,
+ const string& name,
+ const string& description,
+ const string& api,
+ const string& vendor,
+ const string& activeWakeword,
+ const bool isActive,
+ const shared_ptr<unordered_set<string>> wakewords) {
+ shared_ptr<VoiceAgent> voiceAgent =
+ VoiceAgent::create(mLogger, id, name, description, api, vendor, activeWakeword, isActive, wakewords);
+
+ if (voiceAgent.get() == nullptr || voiceAgent->getId().empty()) {
+ string message = string("Invalid Arguments: Failed to add new voiceagent");
+ mLogger->log(Level::ERROR, TAG, message);
+ return false;
+ }
+
+ if (!mVoiceAgents.empty() && mVoiceAgents.find(voiceAgent->getId()) != mVoiceAgents.end()) {
+ string message =
+ string("Failed to add new voiceagent. Voiceagent: ") + voiceAgent->getId() + string(" already exists.");
+ mLogger->log(Level::ERROR, TAG, message);
+ return false;
+ }
+
+ mVoiceAgents.insert(make_pair(voiceAgent->getId(), voiceAgent));
+
+ // Notify the observers
+ for (auto observer : mVoiceAgentChangeObservers) {
+ observer->OnVoiceAgentAdded(voiceAgent);
+ }
+
+ // Create all vshl events for the voiceagent.
+ mVoiceAgentEventsHandler->createVshlEventsForVoiceAgent(voiceAgent->getId());
+
+ return true;
+}
+
+bool VoiceAgentsDataManager::removeVoiceAgent(const string& voiceAgentId) {
+ if (mVoiceAgents.empty()) {
+ string message = string("Failed to remove voiceagent: ") + voiceAgentId + string(". Voiceagents data empty.");
+ mLogger->log(Level::ERROR, TAG, message);
+ return false;
+ }
+
+ auto voiceAgentIt = mVoiceAgents.find(voiceAgentId);
+ if (voiceAgentIt == mVoiceAgents.end()) {
+ string message = string("Failed to remove voiceagent: ") + voiceAgentId + string(". Doesn't exist.");
+ mLogger->log(Level::ERROR, TAG, message);
+ return false;
+ }
+
+ auto voiceAgent = voiceAgentIt->second;
+ // Remove from the map
+ mVoiceAgents.erase(voiceAgentId);
+ // Notify the observers
+ for (auto observer : mVoiceAgentChangeObservers) {
+ observer->OnVoiceAgentRemoved(voiceAgent);
+ }
+
+ // Remove all vshl events for the voiceagent.
+ mVoiceAgentEventsHandler->removeVshlEventsForVoiceAgent(voiceAgent->getId());
+
+ return true;
+}
+
+std::set<std::shared_ptr<vshl::common::interfaces::IVoiceAgent>> VoiceAgentsDataManager::getAllVoiceAgents() {
+ std::set<std::shared_ptr<vshl::common::interfaces::IVoiceAgent>> voiceAgentsSet;
+ for (auto element : mVoiceAgents) {
+ voiceAgentsSet.insert(element.second);
+ }
+
+ return voiceAgentsSet;
+}
+
+// Returns the event filter that belongs to the core module.
+shared_ptr<vshl::common::interfaces::IEventFilter> VoiceAgentsDataManager::getEventFilter() const {
+ return mVoiceAgentEventsHandler;
+}
+
+bool VoiceAgentsDataManager::subscribeToVshlEventFromVoiceAgent(
+ vshl::common::interfaces::IAFBRequest& request,
+ const string eventName,
+ const string voiceAgentId) {
+ auto voiceAgentIt = mVoiceAgents.find(voiceAgentId);
+ if (voiceAgentIt == mVoiceAgents.end()) {
+ mLogger->log(
+ Level::ERROR,
+ TAG,
+ "\
+ Failed to subscribe to VSHL events from voiceagent. VoiceAgent: " +
+ voiceAgentId + " doesn't exist.");
+ return false;
+ }
+ return mVoiceAgentEventsHandler->subscribeToVshlEventFromVoiceAgent(request, eventName, voiceAgentIt->second);
+}
+
+bool VoiceAgentsDataManager::addVoiceAgentsChangeObserver(
+ shared_ptr<vshl::common::interfaces::IVoiceAgentsChangeObserver> observer) {
+ if (!observer) {
+ return false;
+ }
+
+ mVoiceAgentChangeObservers.insert(observer);
+ return true;
+}
+
+bool VoiceAgentsDataManager::removeVoiceAgentsChangeObserver(
+ shared_ptr<vshl::common::interfaces::IVoiceAgentsChangeObserver> observer) {
+ if (!observer) {
+ return false;
+ }
+
+ mVoiceAgentChangeObservers.erase(observer);
+ return true;
+}
+} // namespace voiceagents
+} // namespace vshl
diff --git a/src/plugins/voiceagents/include/VoiceAgent.h b/src/plugins/voiceagents/include/VoiceAgent.h
new file mode 100644
index 0000000..4dd55d4
--- /dev/null
+++ b/src/plugins/voiceagents/include/VoiceAgent.h
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2018-2019 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 VSHL_VOICEAGENTS_INCLUDE_VOICEAGENT_H_
+#define VSHL_VOICEAGENTS_INCLUDE_VOICEAGENT_H_
+
+#include <memory>
+#include <unordered_set>
+
+#include "interfaces/utilities/logging/ILogger.h"
+#include "interfaces/voiceagents/IVoiceAgent.h"
+
+using namespace std;
+
+namespace vshl {
+namespace voiceagents {
+/*
+ * Default implementation of IVoiceAgent interface.
+ */
+class VoiceAgent : public vshl::common::interfaces::IVoiceAgent {
+public:
+ // Creates @c VoiceAgent instance
+ static shared_ptr<VoiceAgent>
+ create(shared_ptr<vshl::common::interfaces::ILogger> logger, const string &id,
+ const string &name, const string &description, const string &api,
+ const string &vendor, const string &activeWakeword,
+ const bool isActive,
+ const shared_ptr<unordered_set<string>> wakewords);
+
+ // Destructor
+ ~VoiceAgent();
+
+ // IVoiceAgent overriden methods
+ bool setActiveWakeWord(const string &wakeword) override;
+ void setIsActive(bool active) override;
+ string getId() const override;
+ string getName() const override;
+ string getDescription() const override;
+ string getApi() const override;
+ string getVendor() const override;
+ shared_ptr<unordered_set<string>> getWakeWords() const override;
+ bool isActive() const override;
+ string getActiveWakeword() const override;
+
+private:
+ // Constructor
+ VoiceAgent(shared_ptr<vshl::common::interfaces::ILogger> logger,
+ const string &id, const string &name, const string &description,
+ const string &api, const string &vendor,
+ const string &activeWakeword, const bool isActive,
+ const shared_ptr<unordered_set<string>> wakewords);
+
+ // Logger
+ shared_ptr<vshl::common::interfaces::ILogger> mLogger;
+
+ // Id
+ string mId;
+
+ // Name
+ string mName;
+
+ // Description
+ string mDescription;
+
+ // API
+ string mApi;
+
+ // Vendor
+ string mVendor;
+
+ // Active wakeword
+ string mActiveWakeword;
+
+ // Active ??
+ bool mIsActive;
+
+ // Wakewords
+ shared_ptr<unordered_set<string>> mWakewords;
+};
+
+} // namespace voiceagents
+} // namespace vshl
+
+#endif // VSHL_VOICEAGENTS_INCLUDE_VOICEAGENT_H_
diff --git a/src/plugins/voiceagents/include/VoiceAgentEventsHandler.h b/src/plugins/voiceagents/include/VoiceAgentEventsHandler.h
new file mode 100644
index 0000000..3c1ca6c
--- /dev/null
+++ b/src/plugins/voiceagents/include/VoiceAgentEventsHandler.h
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2018-2019 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 VSHL_VOICEAGENTS_INCLUDE_VOICEAGENTSTATE_EVENT_HANDLER_H_
+#define VSHL_VOICEAGENTS_INCLUDE_VOICEAGENTSTATE_EVENT_HANDLER_H_
+
+#include <algorithm>
+#include <memory>
+#include <unordered_map>
+
+#include "interfaces/afb/IAFBApi.h"
+#include "interfaces/utilities/events/IEventFilter.h"
+#include "interfaces/utilities/logging/ILogger.h"
+#include "voiceagents/VoiceAgentEventNames.h"
+#include "voiceagents/include/VoiceAgent.h"
+
+using namespace std;
+
+namespace vshl {
+namespace voiceagents {
+/*
+ * This class is reponsible for handling agent specific events
+ * subscription and delivery on behalf of the high level voice service.
+ * This class also listen to the incoming events from voice agents
+ * and implements propagation to application layer.
+ */
+class VoiceAgentEventsHandler : public vshl::common::interfaces::IEventFilter {
+public:
+ // Create a VREventFilter.
+ static shared_ptr<VoiceAgentEventsHandler> create(
+ shared_ptr<vshl::common::interfaces::ILogger> logger,
+ shared_ptr<vshl::common::interfaces::IAFBApi> afbApi);
+
+ // Creates all the vshl events for a specific voiceagent id.
+ // For e.g if voiceagent is VA-001 then a new vshl event
+ // voice_authstate_event#VA-001 for auth state will be created.
+ // Please see VoiceAgentEventNames.h for all the event names.
+ void createVshlEventsForVoiceAgent(const string voiceAgentId);
+
+ // Removes the events from its bookkeeping.
+ void removeVshlEventsForVoiceAgent(const string voiceAgentId);
+
+ // Subscribe to a vshl event corresponding to a voiceagent.
+ bool subscribeToVshlEventFromVoiceAgent(
+ vshl::common::interfaces::IAFBRequest& request,
+ const string eventName,
+ const shared_ptr<VoiceAgent> voiceAgent);
+
+ ~VoiceAgentEventsHandler();
+
+protected:
+ string getName() override;
+
+ // IEventFilter override
+ bool onIncomingEvent(const string eventName, const string voiceAgentId, const string payload) override;
+
+private:
+ // Constructor
+ VoiceAgentEventsHandler(
+ shared_ptr<vshl::common::interfaces::ILogger> logger,
+ shared_ptr<vshl::common::interfaces::IAFBApi> afbApi);
+
+ // Helper method to generate the event name with voiceagent Id
+ // concatenated.
+ string createEventNameWithVAId(string eventName, string voiceAgentId);
+
+ // call subscribe verb on the voiceagent. True if subscription successful.
+ // False otherwise.
+ bool callSubscribeVerb(const shared_ptr<VoiceAgent> voiceAgent);
+
+ // Binding API reference
+ shared_ptr<vshl::common::interfaces::IAFBApi> mAfbApi;
+
+ // A map of VSHL event ID to its Event object
+ unordered_map<string, shared_ptr<common::interfaces::IAFBApi::IAFBEvent>> mEventsMap;
+
+ // Logger
+ shared_ptr<vshl::common::interfaces::ILogger> mLogger;
+};
+
+} // namespace voiceagents
+} // namespace vshl
+
+#endif // VSHL_VOICEAGENTS_INCLUDE_VOICEAGENTSTATE_EVENT_HANDLER_H_
diff --git a/src/plugins/voiceagents/src/VoiceAgentEventsHandler.cpp b/src/plugins/voiceagents/src/VoiceAgentEventsHandler.cpp
new file mode 100644
index 0000000..4952721
--- /dev/null
+++ b/src/plugins/voiceagents/src/VoiceAgentEventsHandler.cpp
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2018-2019 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.
+ */
+#include "voiceagents/include/VoiceAgentEventsHandler.h"
+
+static string TAG = "vshl::voiceagents::VoiceAgentEventsHandler";
+static string VA_VERB_SUBSCRIBE = "subscribe";
+
+using Level = vshl::common::interfaces::ILogger::Level;
+using namespace vshl::common::interfaces;
+
+namespace vshl {
+namespace voiceagents {
+
+shared_ptr<VoiceAgentEventsHandler> VoiceAgentEventsHandler::create(
+ shared_ptr<vshl::common::interfaces::ILogger> logger,
+ shared_ptr<vshl::common::interfaces::IAFBApi> afbApi) {
+ auto eventFilter = std::shared_ptr<VoiceAgentEventsHandler>(new VoiceAgentEventsHandler(logger, afbApi));
+ return eventFilter;
+}
+
+VoiceAgentEventsHandler::VoiceAgentEventsHandler(
+ shared_ptr<vshl::common::interfaces::ILogger> logger,
+ shared_ptr<vshl::common::interfaces::IAFBApi> afbApi) :
+ mAfbApi(afbApi),
+ mLogger(logger) {
+}
+
+VoiceAgentEventsHandler::~VoiceAgentEventsHandler() {
+ mEventsMap.clear();
+}
+
+string VoiceAgentEventsHandler::getName() {
+ return TAG;
+}
+
+void VoiceAgentEventsHandler::createVshlEventsForVoiceAgent(const string voiceAgentId) {
+ // Update the events map with all the VSHL Events.
+ for (auto eventName : VSHL_EVENTS) {
+ string eventNameWithVAId = createEventNameWithVAId(eventName, voiceAgentId);
+ auto it = mEventsMap.find(eventNameWithVAId);
+ if (it == mEventsMap.end() && mAfbApi) {
+ // create a new event and add it to the map.
+ shared_ptr<IAFBApi::IAFBEvent> event = mAfbApi->createEvent(eventNameWithVAId);
+ mEventsMap.insert(make_pair(eventNameWithVAId, event));
+ }
+ }
+}
+
+void VoiceAgentEventsHandler::removeVshlEventsForVoiceAgent(const string voiceAgentId) {
+ // Update the events map with all the VSHL Events.
+ for (auto eventName : VSHL_EVENTS) {
+ string eventNameWithVAId = createEventNameWithVAId(eventName, voiceAgentId);
+ auto it = mEventsMap.find(eventNameWithVAId);
+ if (it != mEventsMap.end()) {
+ mEventsMap.erase(it);
+ }
+ }
+}
+
+bool VoiceAgentEventsHandler::subscribeToVshlEventFromVoiceAgent(
+ vshl::common::interfaces::IAFBRequest& request,
+ const string eventName,
+ const shared_ptr<VoiceAgent> voiceAgent) {
+ auto supportedEventsIt = find(VSHL_EVENTS.begin(), VSHL_EVENTS.end(), eventName);
+ if (supportedEventsIt == VSHL_EVENTS.end()) {
+ mLogger->log(Level::ERROR, TAG, "Event: " + eventName + " not a known event.");
+ return false;
+ }
+
+ // Check if the entry for the voiceagent is present in the
+ // events map. If not then return false because the responsibility
+ // of adding to the map lies in the hands of AddVoiceAgent method.
+ string eventNameWithVAId = createEventNameWithVAId(eventName, voiceAgent->getId());
+ auto createdEventsIt = mEventsMap.find(eventNameWithVAId);
+ if (createdEventsIt == mEventsMap.end()) {
+ mLogger->log(Level::ERROR, TAG, "Not able to subscribe. Event doesn't exist, " + eventNameWithVAId);
+ return false;
+ }
+ createdEventsIt->second->subscribe(request);
+
+ if (!callSubscribeVerb(voiceAgent)) {
+ mLogger->log(Level::WARNING, TAG, "Failed to subscribe to voiceagent: " + voiceAgent->getId());
+ }
+
+ return true;
+}
+
+// IEventFilter override.
+bool VoiceAgentEventsHandler::onIncomingEvent(const string eventName, const string voiceAgentId, const string payload) {
+ string eventNameWithVAId = createEventNameWithVAId(eventName, voiceAgentId);
+ auto it = mEventsMap.find(eventNameWithVAId);
+ if (it != mEventsMap.end()) {
+ return it->second->publishEvent(json_object_new_string(payload.c_str()));
+ }
+
+ return true;
+}
+
+string VoiceAgentEventsHandler::createEventNameWithVAId(string eventName, string voiceAgentId) {
+ return eventName + "#" + voiceAgentId;
+}
+
+bool VoiceAgentEventsHandler::callSubscribeVerb(const shared_ptr<VoiceAgent> voiceAgent) {
+ if (!voiceAgent) {
+ mLogger->log(Level::ERROR, TAG, "Failed to callSubscribeVerb. Invalid input parameter.");
+ return false;
+ }
+
+ if (!mAfbApi) {
+ mLogger->log(
+ Level::ERROR, TAG, "Failed to callSubscribeVerb on voicegent: " + voiceAgent->getId() + ", No API.");
+ return false;
+ }
+
+ // TODO(Naveen): Move to utilities.
+ json_object* object = NULL;
+ std::string error, info;
+ int rc = mAfbApi->callSync(voiceAgent->getApi(), VA_VERB_SUBSCRIBE, NULL, &object, error, info);
+
+ if (object) {
+ free(object);
+ }
+
+ return true;
+}
+} // namespace voiceagents
+} // namespace vshl
diff --git a/src/plugins/voiceagents/src/VoiceAgentImpl.cpp b/src/plugins/voiceagents/src/VoiceAgentImpl.cpp
new file mode 100644
index 0000000..f2ef8a1
--- /dev/null
+++ b/src/plugins/voiceagents/src/VoiceAgentImpl.cpp
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2018-2019 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.
+ */
+#include <sstream>
+
+#include "voiceagents/include/VoiceAgent.h"
+
+static string TAG = "vshl::voiceagents::VoiceAgent";
+
+/**
+ * Specifies the severity level of a log message
+ */
+using Level = vshl::common::interfaces::ILogger::Level;
+
+namespace vshl {
+namespace voiceagents {
+// Creates @c VoiceAgent instance
+shared_ptr<VoiceAgent> VoiceAgent::create(
+ shared_ptr<vshl::common::interfaces::ILogger> logger,
+ const string& id,
+ const string& name,
+ const string& description,
+ const string& api,
+ const string& vendor,
+ const string& activeWakeword,
+ const bool isActive,
+ const shared_ptr<unordered_set<string>> wakewords) {
+ if (wakewords == nullptr) {
+ logger->log(Level::ERROR, TAG, "Wakeword list null");
+ return nullptr;
+ }
+
+ auto voiceAgent = std::unique_ptr<VoiceAgent>(
+ new VoiceAgent(logger, id, name, description, api, vendor, activeWakeword, isActive, wakewords));
+ if (!voiceAgent->setActiveWakeWord(activeWakeword)) {
+ return nullptr;
+ }
+
+ return voiceAgent;
+}
+
+VoiceAgent::VoiceAgent(
+ shared_ptr<vshl::common::interfaces::ILogger> logger,
+ const string& id,
+ const string& name,
+ const string& description,
+ const string& api,
+ const string& vendor,
+ const string& activeWakeword,
+ const bool isActive,
+ const shared_ptr<unordered_set<string>> wakewords) :
+ mLogger(logger),
+ mId(id),
+ mName(name),
+ mDescription(description),
+ mApi(api),
+ mVendor(vendor),
+ mActiveWakeword(activeWakeword),
+ mIsActive(isActive),
+ mWakewords(wakewords) {
+}
+
+// Destructor
+VoiceAgent::~VoiceAgent() {
+}
+
+// Set the active wakeword for this voiceagent
+bool VoiceAgent::setActiveWakeWord(const string& wakeword) {
+ if (mWakewords->find(wakeword) != mWakewords->end()) {
+ mActiveWakeword = wakeword;
+ return true;
+ }
+
+ mLogger->log(Level::ERROR, TAG, "Wakeword: " + wakeword + " doesn't exist in wakeword list");
+ return false;
+}
+
+// Sets the activation state of this voiceagent
+void VoiceAgent::setIsActive(bool active) {
+ mIsActive = active;
+}
+
+string VoiceAgent::getId() const {
+ return mId;
+}
+
+string VoiceAgent::getName() const {
+ return mName;
+}
+
+string VoiceAgent::getDescription() const {
+ return mDescription;
+}
+
+string VoiceAgent::getApi() const {
+ return mApi;
+}
+
+string VoiceAgent::getVendor() const {
+ return mVendor;
+}
+
+shared_ptr<unordered_set<string>> VoiceAgent::getWakeWords() const {
+ return mWakewords;
+}
+
+bool VoiceAgent::isActive() const {
+ return mIsActive;
+}
+
+string VoiceAgent::getActiveWakeword() const {
+ return mActiveWakeword;
+}
+} // namespace voiceagents
+} // namespace vshl
diff --git a/src/plugins/voiceagents/test/VoiceAgentTest.cpp b/src/plugins/voiceagents/test/VoiceAgentTest.cpp
new file mode 100644
index 0000000..e5ad15e
--- /dev/null
+++ b/src/plugins/voiceagents/test/VoiceAgentTest.cpp
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2018-2019 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.
+ */
+
+#include <gtest/gtest.h>
+
+#include "voiceagents/include/VoiceAgent.h"
+
+#include "voiceagents/test/VoiceAgentsTestData.h"
+#include "test/common/ConsoleLogger.h"
+
+using namespace vshl::voiceagents;
+using namespace vshl::test::common;
+
+namespace vshl {
+namespace test {
+
+class VoiceAgentTest : public ::testing::Test {
+protected:
+ void SetUp() override {
+ mConsoleLogger = std::make_shared<ConsoleLogger>();
+
+ mVoiceAgentData = *(getVoiceAgentsTestData().begin());
+ mVoiceAgent = VoiceAgent::create(
+ mConsoleLogger,
+ mVoiceAgentData.id,
+ mVoiceAgentData.name,
+ mVoiceAgentData.description,
+ mVoiceAgentData.api,
+ mVoiceAgentData.vendor,
+ mVoiceAgentData.activeWakeword,
+ mVoiceAgentData.isActive,
+ mVoiceAgentData.wakewords);
+ }
+
+ std::shared_ptr<ConsoleLogger> mConsoleLogger;
+ std::shared_ptr<VoiceAgent> mVoiceAgent;
+ VoiceAgentTestData mVoiceAgentData;
+};
+
+TEST_F(VoiceAgentTest, InitializesCorrectly) {
+ ASSERT_NE(mVoiceAgent, nullptr);
+ ASSERT_EQ(mVoiceAgent->getId(), mVoiceAgentData.id);
+ ASSERT_EQ(mVoiceAgent->getName(), mVoiceAgentData.name);
+ ASSERT_EQ(mVoiceAgent->getDescription(), mVoiceAgentData.description);
+ ASSERT_EQ(mVoiceAgent->getApi(), mVoiceAgentData.api);
+ ASSERT_EQ(mVoiceAgent->getVendor(), mVoiceAgentData.vendor);
+ ASSERT_EQ(mVoiceAgent->getActiveWakeword(), mVoiceAgentData.activeWakeword);
+ ASSERT_EQ(mVoiceAgent->isActive(), mVoiceAgentData.isActive);
+
+ std::unordered_set<std::string> wakeWords = *mVoiceAgentData.wakewords;
+ ASSERT_EQ(*(mVoiceAgent->getWakeWords()), wakeWords);
+}
+
+TEST_F(VoiceAgentTest, FailsCreationOnNonExistentWakeword) {
+ std::string nonExistentWW = "non-existent";
+ auto voiceAgent = VoiceAgent::create(
+ mConsoleLogger,
+ mVoiceAgentData.id,
+ mVoiceAgentData.name,
+ mVoiceAgentData.description,
+ mVoiceAgentData.api,
+ mVoiceAgentData.vendor,
+ nonExistentWW,
+ mVoiceAgentData.isActive,
+ mVoiceAgentData.wakewords);
+ ASSERT_EQ(voiceAgent, nullptr);
+}
+
+TEST_F(VoiceAgentTest, SetsWakewordCorrectly) {
+ std::string wakeword = *(mVoiceAgentData.wakewords->begin());
+ ASSERT_TRUE(mVoiceAgent->setActiveWakeWord(wakeword));
+ ASSERT_EQ(mVoiceAgent->getActiveWakeword(), wakeword);
+}
+
+TEST_F(VoiceAgentTest, FailsToSetNonExistentWakeword) {
+ std::string nonExistentWW = "non-existent";
+ ASSERT_FALSE(mVoiceAgent->setActiveWakeWord(nonExistentWW));
+ ASSERT_EQ(mVoiceAgent->getActiveWakeword(), mVoiceAgentData.activeWakeword);
+}
+
+} // namespace test
+} // namespace vshl \ No newline at end of file
diff --git a/src/plugins/voiceagents/test/VoiceAgentsDataManagerTest.cpp b/src/plugins/voiceagents/test/VoiceAgentsDataManagerTest.cpp
new file mode 100644
index 0000000..58c62ed
--- /dev/null
+++ b/src/plugins/voiceagents/test/VoiceAgentsDataManagerTest.cpp
@@ -0,0 +1,294 @@
+/*
+ * Copyright 2018-2019 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.
+ */
+
+#include <gtest/gtest.h>
+
+#include "voiceagents/VoiceAgentsDataManager.h"
+
+#include "test/common/ConsoleLogger.h"
+#include "test/mocks/AFBApiMock.h"
+#include "test/mocks/VoiceAgentsChangeObserverMock.h"
+#include "voiceagents/test/VoiceAgentsTestData.h"
+
+using namespace vshl::common::interfaces;
+using namespace vshl::voiceagents;
+
+using namespace vshl::test::common;
+
+namespace vshl {
+namespace test {
+
+class VoiceAgentDataManagerTest : public ::testing::Test {
+protected:
+ void SetUp() override {
+ mConsoleLogger = std::make_shared<ConsoleLogger>();
+ mAfbApi = std::make_shared<::testing::NiceMock<AFBApiMock>>();
+ mVADataManager = VoiceAgentsDataManager::create(mConsoleLogger, mAfbApi);
+
+ mAgentsChangeObserver = std::make_shared<
+ ::testing::StrictMock<VoiceAgentsChangeObserverMock>>();
+ mVADataManager->addVoiceAgentsChangeObserver(mAgentsChangeObserver);
+
+ mVoiceAgentsData = getVoiceAgentsTestData();
+ }
+
+ void TearDown() override {
+ mVADataManager->removeVoiceAgentsChangeObserver(mAgentsChangeObserver);
+ }
+
+ static bool addVoiceAgent(VoiceAgentsDataManager &mgr,
+ VoiceAgentTestData &data) {
+ return mgr.addNewVoiceAgent(data.id, data.name, data.description, data.api,
+ data.vendor, data.activeWakeword, data.isActive,
+ data.wakewords);
+ }
+
+ static bool isEqual(const VoiceAgentTestData &lhs, const IVoiceAgent &rhs) {
+ return lhs.id == rhs.getId() && lhs.name == rhs.getName() &&
+ lhs.description == rhs.getDescription() && lhs.api == rhs.getApi() &&
+ lhs.vendor == rhs.getVendor() &&
+ lhs.activeWakeword == rhs.getActiveWakeword() &&
+ lhs.isActive == rhs.isActive() &&
+ *lhs.wakewords == *rhs.getWakeWords();
+ }
+
+ static std::shared_ptr<IVoiceAgent>
+ findVoiceAgent(std::set<std::shared_ptr<IVoiceAgent>> &voiceAgents,
+ std::string &vaId) {
+ for (auto va : voiceAgents) {
+ if (va->getId() == vaId)
+ return va;
+ }
+
+ return nullptr;
+ }
+
+ std::shared_ptr<ConsoleLogger> mConsoleLogger;
+
+ // It is a NiceMock because we don't want gtest to produce warnings about non
+ // interesting calls.
+ // The non interesting calls like createEvent is internal implementation
+ // detail for many of the
+ // tests in this class, and hence suppression of these warnings with NiceMock.
+ std::shared_ptr<::testing::NiceMock<AFBApiMock>> mAfbApi;
+
+ // It is a StrictMock because we want to fail the test for all non interesting
+ // calls.
+ std::shared_ptr<::testing::StrictMock<VoiceAgentsChangeObserverMock>>
+ mAgentsChangeObserver;
+
+ std::vector<VoiceAgentTestData> mVoiceAgentsData;
+ std::unique_ptr<VoiceAgentsDataManager> mVADataManager;
+};
+
+TEST_F(VoiceAgentDataManagerTest, InitializesCorrectly) {
+ ASSERT_NE(mVADataManager, nullptr);
+ ASSERT_EQ(mVADataManager->getAllVoiceAgents().size(), 0);
+ ASSERT_EQ(mVADataManager->getDefaultVoiceAgent(), std::string());
+}
+
+TEST_F(VoiceAgentDataManagerTest, addingVoiceAgentWithSameIdFails) {
+ EXPECT_CALL(*mAgentsChangeObserver, OnVoiceAgentAdded(::testing::_)).Times(1);
+
+ ASSERT_TRUE(addVoiceAgent(*mVADataManager, mVoiceAgentsData[0]));
+ ASSERT_FALSE(addVoiceAgent(*mVADataManager, mVoiceAgentsData[0]));
+
+ auto allVoiceAgents = mVADataManager->getAllVoiceAgents();
+ ASSERT_EQ(allVoiceAgents.size(), 1);
+}
+
+TEST_F(VoiceAgentDataManagerTest,
+ addingVoiceAgentWithNonExistentActiveWakewordFails) {
+ auto voiceAgetData = mVoiceAgentsData[0];
+ voiceAgetData.activeWakeword = "non-existent";
+ ASSERT_FALSE(addVoiceAgent(*mVADataManager, voiceAgetData));
+
+ auto allVoiceAgents = mVADataManager->getAllVoiceAgents();
+ ASSERT_EQ(allVoiceAgents.size(), 0);
+}
+
+TEST_F(VoiceAgentDataManagerTest, canAddNewVoiceAgents) {
+ EXPECT_CALL(*mAgentsChangeObserver, OnVoiceAgentAdded(::testing::_)).Times(2);
+
+ ASSERT_TRUE(addVoiceAgent(*mVADataManager, mVoiceAgentsData[0]));
+
+ auto allVoiceAgents = mVADataManager->getAllVoiceAgents();
+ ASSERT_EQ(allVoiceAgents.size(), 1);
+
+ ASSERT_TRUE(addVoiceAgent(*mVADataManager, mVoiceAgentsData[1]));
+
+ allVoiceAgents = mVADataManager->getAllVoiceAgents();
+ ASSERT_EQ(allVoiceAgents.size(), 2);
+
+ for (auto va : allVoiceAgents) {
+ bool voiceAgentFound = false;
+ for (auto vaData : mVoiceAgentsData) {
+ if (isEqual(vaData, *va)) {
+ voiceAgentFound = true;
+ break;
+ }
+ }
+ ASSERT_TRUE(voiceAgentFound);
+ }
+}
+
+TEST_F(VoiceAgentDataManagerTest, removingUnknonwVoiceAgentFails) {
+ EXPECT_CALL(*mAgentsChangeObserver, OnVoiceAgentAdded(::testing::_)).Times(1);
+
+ ASSERT_FALSE(mVADataManager->removeVoiceAgent("non-existent-vaid"));
+
+ ASSERT_TRUE(addVoiceAgent(*mVADataManager, mVoiceAgentsData[0]));
+ ASSERT_FALSE(mVADataManager->removeVoiceAgent("non-existent-vaid"));
+}
+
+TEST_F(VoiceAgentDataManagerTest, canRemoveVoiceAgents) {
+ EXPECT_CALL(*mAgentsChangeObserver, OnVoiceAgentAdded(::testing::_)).Times(2);
+ EXPECT_CALL(*mAgentsChangeObserver, OnVoiceAgentRemoved(::testing::_))
+ .Times(2);
+
+ ASSERT_TRUE(addVoiceAgent(*mVADataManager, mVoiceAgentsData[0]));
+ ASSERT_TRUE(addVoiceAgent(*mVADataManager, mVoiceAgentsData[1]));
+
+ auto allVoiceAgents = mVADataManager->getAllVoiceAgents();
+ ASSERT_EQ(allVoiceAgents.size(), 2);
+
+ ASSERT_TRUE(mVADataManager->removeVoiceAgent(mVoiceAgentsData[0].id));
+
+ allVoiceAgents = mVADataManager->getAllVoiceAgents();
+ ASSERT_EQ(allVoiceAgents.size(), 1);
+
+ ASSERT_TRUE(mVADataManager->removeVoiceAgent(mVoiceAgentsData[1].id));
+
+ allVoiceAgents = mVADataManager->getAllVoiceAgents();
+ ASSERT_EQ(allVoiceAgents.size(), 0);
+}
+
+TEST_F(VoiceAgentDataManagerTest, activatingNonExistentVoiceAgentsFails) {
+ uint32_t result = mVADataManager->activateVoiceAgents({"non", "existent"});
+ ASSERT_EQ(result, 0);
+
+ EXPECT_CALL(*mAgentsChangeObserver, OnVoiceAgentAdded(::testing::_)).Times(2);
+
+ ASSERT_TRUE(addVoiceAgent(*mVADataManager, mVoiceAgentsData[0]));
+ ASSERT_TRUE(addVoiceAgent(*mVADataManager, mVoiceAgentsData[1]));
+
+ result = mVADataManager->activateVoiceAgents({"non", "existent"});
+ ASSERT_EQ(result, 0);
+}
+
+TEST_F(VoiceAgentDataManagerTest, deactivatingNonExistentVoiceAgentsFails) {
+ uint32_t result = mVADataManager->deactivateVoiceAgents({"non", "existent"});
+ ASSERT_EQ(result, 0);
+
+ EXPECT_CALL(*mAgentsChangeObserver, OnVoiceAgentAdded(::testing::_)).Times(2);
+
+ ASSERT_TRUE(addVoiceAgent(*mVADataManager, mVoiceAgentsData[0]));
+ ASSERT_TRUE(addVoiceAgent(*mVADataManager, mVoiceAgentsData[1]));
+
+ result = mVADataManager->deactivateVoiceAgents({"non", "existent"});
+ ASSERT_EQ(result, 0);
+}
+
+TEST_F(VoiceAgentDataManagerTest, canActivateDeactivateVoiceAgents) {
+ {
+ ::testing::InSequence dummy;
+
+ EXPECT_CALL(*mAgentsChangeObserver, OnVoiceAgentAdded(::testing::_))
+ .Times(2);
+ EXPECT_CALL(*mAgentsChangeObserver, OnVoiceAgentDeactivated(::testing::_))
+ .Times(1);
+ EXPECT_CALL(*mAgentsChangeObserver, OnVoiceAgentActivated(::testing::_))
+ .Times(1);
+ }
+
+ ASSERT_TRUE(addVoiceAgent(*mVADataManager, mVoiceAgentsData[0]));
+ ASSERT_TRUE(addVoiceAgent(*mVADataManager, mVoiceAgentsData[1]));
+
+ std::string vaId = mVoiceAgentsData[0].id;
+
+ auto allVA = mVADataManager->getAllVoiceAgents();
+ auto voiceAgent = findVoiceAgent(allVA, vaId);
+ ASSERT_NE(voiceAgent, nullptr);
+ ASSERT_TRUE(voiceAgent->isActive());
+
+ uint32_t result =
+ mVADataManager->deactivateVoiceAgents({"non-existent", vaId});
+ ASSERT_EQ(result, 1);
+ ASSERT_FALSE(voiceAgent->isActive());
+
+ // Try de-activating already de-activated agent
+ result = mVADataManager->deactivateVoiceAgents({vaId});
+ ASSERT_EQ(result, 1);
+ ASSERT_FALSE(voiceAgent->isActive());
+
+ result = mVADataManager->activateVoiceAgents({"non-existent", vaId});
+ ASSERT_EQ(result, 1);
+ ASSERT_TRUE(voiceAgent->isActive());
+
+ // Try activating already activated agent
+ result = mVADataManager->activateVoiceAgents({vaId});
+ ASSERT_EQ(result, 1);
+ ASSERT_TRUE(voiceAgent->isActive());
+}
+
+TEST_F(VoiceAgentDataManagerTest,
+ NoDefaultAgentIsReturnedWhenNoDefaultAgentIsSet) {
+ EXPECT_CALL(*mAgentsChangeObserver, OnVoiceAgentAdded(::testing::_)).Times(2);
+
+ ASSERT_TRUE(addVoiceAgent(*mVADataManager, mVoiceAgentsData[0]));
+ ASSERT_TRUE(addVoiceAgent(*mVADataManager, mVoiceAgentsData[1]));
+
+ std::string defaultAgentId = mVADataManager->getDefaultVoiceAgent();
+ ASSERT_EQ(defaultAgentId, "");
+}
+
+TEST_F(VoiceAgentDataManagerTest, DefaultAgentCanBeSet) {
+ EXPECT_CALL(*mAgentsChangeObserver, OnVoiceAgentAdded(::testing::_)).Times(2);
+
+ ASSERT_TRUE(addVoiceAgent(*mVADataManager, mVoiceAgentsData[0]));
+ ASSERT_TRUE(addVoiceAgent(*mVADataManager, mVoiceAgentsData[1]));
+
+ auto allAgents = mVADataManager->getAllVoiceAgents();
+ std::string vaId1 = mVoiceAgentsData[1].id;
+ std::string vaId2 = mVoiceAgentsData[0].id;
+ auto va1 = findVoiceAgent(allAgents, vaId1);
+ auto va2 = findVoiceAgent(allAgents, vaId2);
+
+ {
+ ::testing::InSequence dummy;
+
+ EXPECT_CALL(*mAgentsChangeObserver, OnDefaultVoiceAgentChanged(va1))
+ .Times(1);
+ EXPECT_CALL(*mAgentsChangeObserver, OnDefaultVoiceAgentChanged(va2))
+ .Times(1);
+ }
+
+ ASSERT_TRUE(mVADataManager->setDefaultVoiceAgent(vaId1));
+ ASSERT_EQ(mVADataManager->getDefaultVoiceAgent(), vaId1);
+
+ ASSERT_TRUE(mVADataManager->setDefaultVoiceAgent(vaId2));
+ ASSERT_EQ(mVADataManager->getDefaultVoiceAgent(), vaId2);
+
+ ASSERT_FALSE(mVADataManager->setDefaultVoiceAgent("non-existent"));
+ ASSERT_EQ(mVADataManager->getDefaultVoiceAgent(), vaId2);
+
+ // Setting default agent to already default agent shouldn't result in extra
+ // callback to OnDefaultVoiceAgentChanged
+ ASSERT_TRUE(mVADataManager->setDefaultVoiceAgent(vaId2));
+ ASSERT_EQ(mVADataManager->getDefaultVoiceAgent(), vaId2);
+}
+
+} // namespace test
+} // namespace vshl \ No newline at end of file
diff --git a/src/plugins/voiceagents/test/VoiceAgentsTestData.h b/src/plugins/voiceagents/test/VoiceAgentsTestData.h
new file mode 100644
index 0000000..ced068f
--- /dev/null
+++ b/src/plugins/voiceagents/test/VoiceAgentsTestData.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2018-2019 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.
+ */
+
+#include <memory>
+#include <string>
+#include <unordered_set>
+#include <vector>
+
+namespace vshl {
+namespace test {
+
+typedef std::shared_ptr<std::unordered_set<std::string>> WakeWords;
+
+struct VoiceAgentTestData {
+ std::string id;
+ std::string name;
+ std::string description;
+ std::string api;
+ std::string vendor;
+ std::string activeWakeword;
+ bool isActive;
+ WakeWords wakewords;
+};
+
+static std::vector<VoiceAgentTestData> getVoiceAgentsTestData() {
+ std::vector<VoiceAgentTestData> voiceAgentsTestData{
+ {
+ "VA-001", // Id
+ "Foundation", // Name
+ "Voice Agent For Galactic Empire", // Description
+ "api-1", // API
+ "Asimov", // Vendor
+ "Hari Seldon", // Active Wakeword
+ true, // Is Active
+ std::shared_ptr<std::unordered_set<std::string>>(
+ new std::unordered_set<std::string>{"Hari Seldon", "Cleon I ", "Eto Demerzel"}) // Wake Words
+ },
+ {
+ "VA-002", // Id
+ "Betelgeuse", // Name
+ "Voice Agent For Galaxy hopper", // Description
+ "api-2", // API
+ "Douglas Adams", // Vendor
+ "Ford Prefect", // Active Wakeword
+ true, // Is Active
+ std::shared_ptr<std::unordered_set<std::string>>(
+ new std::unordered_set<std::string>{"Ford Prefect", "Zaphod Beeblebrox"}) // Wake Words
+ },
+ };
+
+ return voiceAgentsTestData;
+}
+
+} // namespace test
+} // namespace vshl
diff --git a/src/vshl-apidef.h b/src/vshl-apidef.h
new file mode 100644
index 0000000..7a14498
--- /dev/null
+++ b/src/vshl-apidef.h
@@ -0,0 +1,43 @@
+
+static const char _afb_description_vshl[] =
+ "{\"openapi\":\"3.0.0\",\"$schema\":\"http://iot.bzh/download/openapi/sch"
+ "ema-3.0/default-schema.json\",\"info\":{\"description\":\"\",\"title\":\""
+ "High Level Voice Service API\",\"version\":\"1.0\",\"x-binding-c-generat"
+ "or\":{\"api\":\"vshl\",\"version\":3,\"prefix\":\"afv_\",\"postfix\":\"\""
+ ",\"start\":null,\"onevent\":null,\"init\":\"init\",\"scope\":\"\",\"priv"
+ "ate\":false,\"noconcurrency\":true}},\"servers\":[{\"url\":\"ws://{host}"
+ ":{port}/api/monitor\",\"description\":\"TS caching binding\",\"variables"
+ "\":{\"host\":{\"default\":\"localhost\"},\"port\":{\"default\":\"1234\"}"
+ "},\"x-afb-events\":[{\"$ref\":\"#/components/schemas/afb-event\"}]}],\"c"
+ "omponents\":{\"schemas\":{\"afb-reply\":{\"$ref\":\"#/components/schemas"
+ "/afb-reply-v3\"},\"afb-event\":{\"$ref\":\"#/components/schemas/afb-even"
+ "t-v3\"},\"afb-reply-v3\":{\"title\":\"Generic response.\",\"type\":\"obj"
+ "ect\",\"required\":[\"jtype\",\"request\"],\"properties\":{\"jtype\":{\""
+ "type\":\"string\",\"const\":\"afb-reply\"},\"request\":{\"type\":\"objec"
+ "t\",\"required\":[\"status\"],\"properties\":{\"status\":{\"type\":\"str"
+ "ing\"},\"info\":{\"type\":\"string\"},\"token\":{\"type\":\"string\"},\""
+ "uuid\":{\"type\":\"string\"},\"reqid\":{\"type\":\"string\"}}},\"respons"
+ "e\":{\"type\":\"object\"}}},\"afb-event-v3\":{\"type\":\"object\",\"requ"
+ "ired\":[\"jtype\",\"event\"],\"properties\":{\"jtype\":{\"type\":\"strin"
+ "g\",\"const\":\"afb-event\"},\"event\":{\"type\":\"string\"},\"data\":{\""
+ "type\":\"object\"}}}},\"responses\":{\"200\":{\"description\":\"A comple"
+ "x object array response\",\"content\":{\"application/json\":{\"schema\":"
+ "{\"$ref\":\"#/components/schemas/afb-reply\"}}}}}}}";
+
+static const struct afb_verb_v3 _afb_verbs_vshl[] = {
+ {.verb = NULL, .callback = NULL, .auth = NULL, .info = NULL, .vcbdata = NULL, .session = 0, .glob = 0}};
+
+int init(afb_api_t api);
+
+const struct afb_binding_v3 afbBindingV3 = {.api = "vshl",
+ .specification = _afb_description_vshl,
+ .info = "",
+ .verbs = _afb_verbs_vshl,
+ .preinit = NULL,
+ .init = init,
+ .onevent = NULL,
+ .userdata = NULL,
+ .provide_class = NULL,
+ .require_class = NULL,
+ .require_api = NULL,
+ .noconcurrency = 1};
diff --git a/src/vshl-apidef.json b/src/vshl-apidef.json
new file mode 100644
index 0000000..67bda88
--- /dev/null
+++ b/src/vshl-apidef.json
@@ -0,0 +1,109 @@
+{
+ "openapi": "3.0.0",
+ "$schema": "http://iot.bzh/download/openapi/schema-3.0/default-schema.json",
+ "info": {
+ "description": "",
+ "title": "High Level Voice Service API",
+ "version": "1.0",
+ "x-binding-c-generator": {
+ "api": "vshl",
+ "version": 3,
+ "prefix": "afv_",
+ "postfix": "",
+ "start": null,
+ "onevent": null,
+ "init": "init",
+ "scope": "",
+ "private": false,
+ "noconcurrency": true
+ }
+ },
+ "servers": [{
+ "url": "ws://{host}:{port}/api/monitor",
+ "description": "TS caching binding",
+ "variables": {
+ "host": {
+ "default": "localhost"
+ },
+ "port": {
+ "default": "1234"
+ }
+ },
+ "x-afb-events": [{
+ "$ref": "#/components/schemas/afb-event"
+ }]
+ }],
+ "components": {
+ "schemas": {
+ "afb-reply": {
+ "$ref": "#/components/schemas/afb-reply-v3"
+ },
+ "afb-event": {
+ "$ref": "#/components/schemas/afb-event-v3"
+ },
+ "afb-reply-v3": {
+ "title": "Generic response.",
+ "type": "object",
+ "required": ["jtype", "request"],
+ "properties": {
+ "jtype": {
+ "type": "string",
+ "const": "afb-reply"
+ },
+ "request": {
+ "type": "object",
+ "required": ["status"],
+ "properties": {
+ "status": {
+ "type": "string"
+ },
+ "info": {
+ "type": "string"
+ },
+ "token": {
+ "type": "string"
+ },
+ "uuid": {
+ "type": "string"
+ },
+ "reqid": {
+ "type": "string"
+ }
+ }
+ },
+ "response": {
+ "type": "object"
+ }
+ }
+ },
+ "afb-event-v3": {
+ "type": "object",
+ "required": ["jtype", "event"],
+ "properties": {
+ "jtype": {
+ "type": "string",
+ "const": "afb-event"
+ },
+ "event": {
+ "type": "string"
+ },
+ "data": {
+ "type": "object"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "A complex object array response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/afb-reply"
+ }
+ }
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/vshl-binding.c b/src/vshl-binding.c
new file mode 100644
index 0000000..17afbad
--- /dev/null
+++ b/src/vshl-binding.c
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2018-2019 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.
+ */
+#define _GNU_SOURCE
+#include "vshl-binding.h"
+
+afb_dynapi* AFB_default;
+
+// Config Section definition (note: controls section index should match handle
+// retrieval in HalConfigExec)
+static CtlSectionT ctrlSections[] = {{.key = "plugins", .loadCB = PluginConfig},
+ {.key = "onload", .loadCB = OnloadConfig},
+ {.key = "controls", .loadCB = ControlConfig},
+ {.key = "events", .loadCB = EventConfig},
+ {.key = NULL}};
+
+static AFB_ApiVerbs ctrlApiVerbs[] = {
+ {.verb = NULL} /* marker for end of the array */
+};
+
+static int ctrlLoadStaticVerbs(afb_dynapi* apiHandle, AFB_ApiVerbs* verbs) {
+ int errcount = 0;
+
+ for (int idx = 0; verbs[idx].verb; idx++) {
+ errcount += afb_dynapi_add_verb(
+ apiHandle,
+ ctrlApiVerbs[idx].verb,
+ NULL,
+ ctrlApiVerbs[idx].callback,
+ (void*)&ctrlApiVerbs[idx],
+ ctrlApiVerbs[idx].auth,
+ 0);
+ }
+
+ return errcount;
+};
+
+// next generation dynamic API-V3 mode
+#include <signal.h>
+
+static int ctrlLoadOneApi(void* cbdata, AFB_ApiT apiHandle) {
+ CtlConfigT* ctrlConfig = (CtlConfigT*)cbdata;
+
+ // save closure as api's data context
+ afb_dynapi_set_userdata(apiHandle, ctrlConfig);
+
+ // add static controls verbs
+ int err = ctrlLoadStaticVerbs(apiHandle, ctrlApiVerbs);
+ if (err) {
+ AFB_ApiError(apiHandle, "ctrlLoadStaticVerbs fail to register static V2 verbs");
+ return ERROR;
+ }
+
+ // load section for corresponding API
+ err = CtlLoadSections(apiHandle, ctrlConfig, ctrlSections);
+ if (err) {
+ AFB_ApiError(apiHandle, "CtlLoadSections fail to load the sections");
+ return ERROR;
+ }
+
+ // declare an event event manager for this API;
+ afb_dynapi_on_event(apiHandle, CtrlDispatchApiEvent);
+
+ // init API function (does not receive user closure ???
+ // afb_dynapi_on_init(apiHandle, CtrlInitOneApi);
+
+ afb_dynapi_seal(apiHandle);
+ return err;
+}
+
+int afbBindingEntry(afb_dynapi* apiHandle) {
+ AFB_default = apiHandle;
+ AFB_ApiNotice(apiHandle, "Controller in afbBindingEntry");
+
+ const char* dirList = getenv("CONTROL_CONFIG_PATH");
+ if (!dirList) dirList = CONTROL_CONFIG_PATH;
+
+ const char* configPath = CtlConfigSearch(apiHandle, dirList, "");
+ if (!configPath) {
+ AFB_ApiError(apiHandle, "CtlPreInit: No %s* config found in %s ", GetBinderName(), dirList);
+ return ERROR;
+ }
+
+ // load config file and create API
+ CtlConfigT* ctrlConfig = CtlLoadMetaData(apiHandle, configPath);
+ if (!ctrlConfig) {
+ AFB_ApiError(apiHandle, "CtrlBindingDyn No valid control config file in:\n-- %s", configPath);
+ return ERROR;
+ }
+
+ if (!ctrlConfig->api) {
+ AFB_ApiError(apiHandle, "CtrlBindingDyn API Missing from metadata in:\n-- %s", configPath);
+ return ERROR;
+ }
+
+ AFB_ApiNotice(apiHandle, "Controller API='%s' info='%s'", ctrlConfig->api, ctrlConfig->info);
+
+ // create one API per config file (Pre-V3 return code ToBeChanged)
+ int status = afb_dynapi_new_api(apiHandle, ctrlConfig->api, ctrlConfig->info, 1, ctrlLoadOneApi, ctrlConfig);
+
+ // config exec should be done after api init in order to enable onload to use newly defined ctl API.
+ if (!status) status = CtlConfigExec(apiHandle, ctrlConfig);
+
+ return status;
+}
diff --git a/src/vshl-binding.h b/src/vshl-binding.h
new file mode 100644
index 0000000..75e7c91
--- /dev/null
+++ b/src/vshl-binding.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2018-2019 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 _CTL_BINDING_INCLUDE_
+#define _CTL_BINDING_INCLUDE_
+
+#define AFB_BINDING_VERSION 3
+#include <ctl-config.h>
+
+#ifndef ERROR
+#define ERROR -1
+#endif
+
+#endif /* _CTL_BINDING_INCLUDE_ */ \ No newline at end of file
diff --git a/tools/pre-commit b/tools/pre-commit
new file mode 100755
index 0000000..87a9fc3
--- /dev/null
+++ b/tools/pre-commit
@@ -0,0 +1,140 @@
+#!/bin/bash
+
+# git pre-commit hook that runs an clang-format stylecheck.
+# Features:
+# - abort commit when commit does not comply with the style guidelines
+# - create a patch of the proposed style changes
+
+# modifications for clang-format by rene.milk@wwu.de
+# This file is part of a set of unofficial pre-commit hooks available
+# at github.
+# Link: https://gist.github.com/wangkuiyi/7379a242f0d4089eaa75
+
+
+##################################################################
+# SETTINGS
+# set path to clang-format binary
+CLANG_FORMAT="`which clang-format`"
+
+# remove any older patches from previous commits. Set to true or false.
+# DELETE_OLD_PATCHES=false
+DELETE_OLD_PATCHES=false
+
+# only parse files with the extensions in FILE_EXTS. Set to true or false.
+# if false every changed file in the commit will be parsed with clang-format.
+# if true only files matching one of the extensions are parsed with clang-format.
+# PARSE_EXTS=true
+PARSE_EXTS=true
+
+# file types to parse. Only effective when PARSE_EXTS is true.
+# FILE_EXTS=".c .h .cpp .hpp"
+FILE_EXTS=".c .h .cpp .hpp .cc .hh .cxx .m"
+
+##################################################################
+# There should be no need to change anything below this line.
+
+# Reference: http://stackoverflow.com/questions/1055671/how-can-i-get-the-behavior-of-gnus-readlink-f-on-a-mac
+canonicalize_filename () {
+ local target_file=$1
+ local physical_directory=""
+ local result=""
+
+ # Need to restore the working directory after work.
+ pushd `pwd` > /dev/null
+
+ cd "$(dirname "$target_file")"
+ target_file=`basename $target_file`
+
+ # Iterate down a (possible) chain of symlinks
+ while [ -L "$target_file" ]
+ do
+ target_file=$(readlink "$target_file")
+ cd "$(dirname "$target_file")"
+ target_file=$(basename "$target_file")
+ done
+
+ # Compute the canonicalized name by finding the physical path
+ # for the directory we're in and appending the target file.
+ physical_directory=`pwd -P`
+ result="$physical_directory"/"$target_file"
+
+ # restore the working directory after work.
+ popd > /dev/null
+
+ echo "$result"
+}
+
+# exit on error
+set -e
+
+# check whether the given file matches any of the set extensions
+matches_extension() {
+ local filename=$(basename "$1")
+ local extension=".${filename##*.}"
+ local ext
+
+ for ext in $FILE_EXTS; do [[ "$ext" == "$extension" ]] && return 0; done
+
+ return 1
+}
+
+# necessary check for initial commit
+if git rev-parse --verify HEAD >/dev/null 2>&1 ; then
+ against=HEAD
+else
+ # Initial commit: diff against an empty tree object
+ against=4b825dc642cb6eb9a060e54bf8d69288fbee4904
+fi
+
+if [ ! -x "$CLANG_FORMAT" ] ; then
+ printf "Error: clang-format executable not found.\n"
+ printf "Set the correct path in $(canonicalize_filename "$0").\n"
+ exit 1
+fi
+
+# create a random filename to store our generated patch
+prefix="pre-commit-clang-format"
+suffix="$(date +%s)"
+patch="/tmp/$prefix-$suffix.patch"
+
+# clean up any older clang-format patches
+$DELETE_OLD_PATCHES && rm -f /tmp/$prefix*.patch
+
+# create one patch containing all changes to the files
+git diff-index --cached --diff-filter=ACMR --name-only $against -- | while read file;
+do
+ # ignore file if we do check for file extensions and the file
+ # does not match any of the extensions specified in $FILE_EXTS
+ if $PARSE_EXTS && ! matches_extension "$file"; then
+ continue;
+ fi
+
+ # clang-format our sourcefile, create a patch with diff and append it to our $patch
+ # The sed call is necessary to transform the patch from
+ # --- $file timestamp
+ # +++ - timestamp
+ # to both lines working on the same file and having a a/ and b/ prefix.
+ # Else it can not be applied with 'git apply'.
+ "$CLANG_FORMAT" -style=file "$file" | \
+ diff -u "$file" - | \
+ sed -e "1s|--- |--- a/|" -e "2s|+++ -|+++ b/$file|" >> "$patch"
+done
+
+# if no patch has been generated all is ok, clean up the file stub and exit
+if [ ! -s "$patch" ] ; then
+ printf "Files in this commit comply with the clang-format rules.\n"
+ rm -f "$patch"
+ exit 0
+fi
+
+# a patch has been created, notify the user and exit
+printf "\nThe following differences were found between the code to commit "
+printf "and the clang-format rules:\n\n"
+cat "$patch"
+
+printf "\nYou can apply these changes with:\n git apply $patch\n"
+printf "(may need to be called from the root directory of your repository)\n"
+printf "Aborting commit. Apply changes and commit again or skip checking with"
+printf " --no-verify (not recommended).\n"
+
+exit 1