2018-01-12 23:45:42 +00:00
|
|
|
/*
|
|
|
|
* Copyright 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.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <chrono>
|
|
|
|
#include <queue>
|
|
|
|
#include <sstream>
|
|
|
|
#include <iostream>
|
|
|
|
|
|
|
|
#include <gmock/gmock.h>
|
|
|
|
#include <gtest/gtest.h>
|
|
|
|
|
|
|
|
#include <AVSCommon/AVS/Attachment/MockAttachmentManager.h>
|
|
|
|
#include <AVSCommon/AVS/IndicatorState.h>
|
|
|
|
#include <AVSCommon/AVS/Initialization/AlexaClientSDKInit.h>
|
|
|
|
#include <AVSCommon/SDKInterfaces/Audio/NotificationsAudioFactoryInterface.h>
|
|
|
|
#include <AVSCommon/SDKInterfaces/MockExceptionEncounteredSender.h>
|
|
|
|
#include <AVSCommon/SDKInterfaces/MockContextManager.h>
|
|
|
|
#include <AVSCommon/SDKInterfaces/MockDirectiveSequencer.h>
|
|
|
|
#include <AVSCommon/SDKInterfaces/MockDirectiveHandlerResult.h>
|
|
|
|
#include <AVSCommon/Utils/JSON/JSONUtils.h>
|
|
|
|
#include <AVSCommon/Utils/Logger/ConsoleLogger.h>
|
2018-03-09 00:55:39 +00:00
|
|
|
#include <RegistrationManager/CustomerDataManager.h>
|
2018-01-12 23:45:42 +00:00
|
|
|
|
|
|
|
#include "Notifications/NotificationsCapabilityAgent.h"
|
|
|
|
#include "Notifications/NotificationIndicator.h"
|
|
|
|
#include "Notifications/NotificationRendererInterface.h"
|
|
|
|
#include "Notifications/NotificationRendererObserverInterface.h"
|
|
|
|
|
|
|
|
namespace alexaClientSDK {
|
|
|
|
namespace capabilityAgents {
|
|
|
|
namespace notifications {
|
|
|
|
namespace test {
|
|
|
|
|
|
|
|
using namespace avsCommon::avs;
|
|
|
|
using namespace avsCommon::avs::initialization;
|
|
|
|
using namespace avsCommon::avs::attachment::test;
|
|
|
|
using namespace avsCommon::sdkInterfaces;
|
|
|
|
using namespace avsCommon::sdkInterfaces::audio;
|
|
|
|
using namespace avsCommon::sdkInterfaces::test;
|
|
|
|
using namespace ::testing;
|
|
|
|
|
|
|
|
/// Plenty of time for a test to complete.
|
|
|
|
static std::chrono::milliseconds WAIT_TIMEOUT(1000);
|
|
|
|
|
|
|
|
/// Time to simulate a notification rendering.
|
Version 1.8 alexa-client-sdk
Changes in this update:
Enhancements
Added local stop functionality. This allows a user to stop an active function, such as an alert or timer, by uttering "Alexa, stop" when an Alexa-enabled product is offline.
Alerts in the background now stream in 10 sec intervals, rather than continuously.
Added support for France to the sample app.
friendlyName can now be updated for BlueZ implementations of BlueZBluetoothDevice and BlueZHostController.
Bug Fixes
Fixed an issue where the Bluetooth agent didn't clear user data upon reset, including paired devices and the uuidMapping table.
Fixed MediaPlayer threading issues. Now each instance has it's own glib main loop thread, rather than utilizing the default main context worker thread.
Fixed segmentation fault issues that occurred when certain static initializers needed to be initialized in a certain order, but the order wasn't defined.
Known Issues
The ACL may encounter issues if audio attachments are received but not consumed.
SpeechSynthesizerState currently uses GAINING_FOCUS and LOSING_FOCUS as a workaround for handling intermediate state. These states may be removed in a future release.
The Alexa app doesn't always indicate when a device is successfully connected via Bluetooth.
Connecting a product to streaming media via Bluetooth will sometimes stop media playback within the source application. Resuming playback through the source application or toggling next/previous will correct playback.
When a source device is streaming silence via Bluetooth, the Alexa companion app indicates that audio content is streaming.
The Bluetooth agent assumes that the Bluetooth adapter is always connected to a power source. Disconnecting from a power source during operation is not yet supported.
On some products, interrupted Bluetooth playback may not resume if other content is locally streamed.
On Raspberry Pi, when streaming audio via Bluetooth, sometimes the audio stream stutters.
On Raspberry Pi, BlueALSA must be terminated each time the device boots. See Raspberry Pi Quick Start Guide for more information.
2018-06-27 21:41:15 +00:00
|
|
|
static std::chrono::milliseconds RENDER_TIME(10);
|
2018-01-12 23:45:42 +00:00
|
|
|
|
|
|
|
/// Notifications namespace
|
|
|
|
static const std::string NAMESPACE_NOTIFICATIONS("Notifications");
|
|
|
|
|
|
|
|
/// Name for Notifications SetIndicator directive
|
|
|
|
static const std::string NAME_SET_INDICATOR("SetIndicator");
|
|
|
|
|
|
|
|
/// Name for Notifications ClearIndicator directive
|
|
|
|
static const std::string NAME_CLEAR_INDICATOR("ClearIndicator");
|
|
|
|
|
|
|
|
/// The @c NamespaceAndName to send to the @c ContextManager
|
|
|
|
static const NamespaceAndName NAMESPACE_AND_NAME_INDICATOR_STATE{NAMESPACE_NOTIFICATIONS, "IndicatorState"};
|
|
|
|
|
|
|
|
/// Message Id for testing.
|
|
|
|
static const std::string MESSAGE_ID_TEST("MessageId_Test");
|
|
|
|
static const std::string MESSAGE_ID_TEST2("MessageId_Test2");
|
|
|
|
|
|
|
|
/// Context ID for testing
|
|
|
|
static const std::string CONTEXT_ID_TEST("ContextId_Test");
|
|
|
|
|
|
|
|
/// Test fields for payloads
|
|
|
|
static const std::string ASSET_ID1("assetId1");
|
|
|
|
static const std::string ASSET_ID2("assetId2");
|
|
|
|
static const std::string ASSET_URL1("assetUrl1");
|
|
|
|
static const std::string ASSET_URL2("assetUrl2");
|
|
|
|
|
|
|
|
/// Default "audio" for testing
|
|
|
|
static const std::string DEFAULT_NOTIFICATION_AUDIO{"default notification audio"};
|
|
|
|
|
|
|
|
/// Mocking the json config file
|
|
|
|
// clang-format off
|
|
|
|
static const std::string NOTIFICATIONS_CONFIG_JSON =
|
|
|
|
"{"
|
|
|
|
"\"notifications\":{"
|
|
|
|
"\"databaseFilePath\":\"notificationsUnitTest.db\""
|
|
|
|
"}"
|
|
|
|
"}";
|
|
|
|
// clang-format on
|
|
|
|
|
|
|
|
/// String to identify log entries originating from this file.
|
|
|
|
static const std::string TAG("NotificationsCapabilityAgentTest");
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create a LogEntry using this file's TAG and the specified event string.
|
|
|
|
*
|
|
|
|
* @param The event string for this @c LogEntry.
|
|
|
|
*/
|
|
|
|
#define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event)
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A test class that acts as a NotificationsAudioFactory.
|
|
|
|
*/
|
|
|
|
class TestNotificationsAudioFactory : public NotificationsAudioFactoryInterface {
|
|
|
|
public:
|
|
|
|
std::function<std::unique_ptr<std::istream>()> notificationDefault() const override;
|
|
|
|
|
|
|
|
private:
|
|
|
|
static std::unique_ptr<std::istream> defaultNotification() {
|
|
|
|
return std::unique_ptr<std::stringstream>(new std::stringstream(DEFAULT_NOTIFICATION_AUDIO));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
std::function<std::unique_ptr<std::istream>()> TestNotificationsAudioFactory::notificationDefault() const {
|
|
|
|
return defaultNotification;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A test class that acts as a NotificationsObserver.
|
|
|
|
*/
|
|
|
|
class TestNotificationsObserver : public NotificationsObserverInterface {
|
|
|
|
public:
|
|
|
|
TestNotificationsObserver();
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Waits for an IndicatorState change.
|
|
|
|
*
|
|
|
|
* @param state The IndicatorState to wait for.
|
|
|
|
* @param timeout The amount of time to wait for the state change.
|
|
|
|
*/
|
|
|
|
bool waitFor(IndicatorState state, std::chrono::milliseconds timeout);
|
|
|
|
|
|
|
|
void onSetIndicator(IndicatorState state) override;
|
|
|
|
|
|
|
|
private:
|
|
|
|
/// The most recently observed IndicatorState.
|
|
|
|
IndicatorState m_indicatorState;
|
|
|
|
|
|
|
|
/// Serializes access to m_conditionVariable.
|
|
|
|
std::mutex m_mutex;
|
|
|
|
|
|
|
|
/// Used to wait for a particular IndicatorState.
|
|
|
|
std::condition_variable m_conditionVariable;
|
|
|
|
};
|
|
|
|
|
|
|
|
TestNotificationsObserver::TestNotificationsObserver() : m_indicatorState{IndicatorState::OFF} {
|
|
|
|
}
|
|
|
|
|
|
|
|
bool TestNotificationsObserver::waitFor(IndicatorState state, std::chrono::milliseconds timeout) {
|
|
|
|
std::unique_lock<std::mutex> lock(m_mutex);
|
|
|
|
return m_conditionVariable.wait_for(lock, timeout, [this, state] { return m_indicatorState == state; });
|
|
|
|
}
|
|
|
|
|
|
|
|
void TestNotificationsObserver::onSetIndicator(IndicatorState state) {
|
|
|
|
ACSDK_ERROR(LX("onSetIndicator").d("indicatorState", indicatorStateToInt(state)));
|
|
|
|
std::lock_guard<std::mutex> lock(m_mutex);
|
|
|
|
m_indicatorState = state;
|
|
|
|
m_conditionVariable.notify_all();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A test class that acts as NotificationsStorage. This storage is implemented using a std::queue.
|
|
|
|
*/
|
|
|
|
class TestNotificationsStorage : public NotificationsStorageInterface {
|
|
|
|
public:
|
2018-03-09 00:55:39 +00:00
|
|
|
bool createDatabase() override;
|
2018-01-12 23:45:42 +00:00
|
|
|
|
2018-03-09 00:55:39 +00:00
|
|
|
bool open() override;
|
2018-01-12 23:45:42 +00:00
|
|
|
|
|
|
|
void close() override;
|
|
|
|
|
|
|
|
bool enqueue(const NotificationIndicator& notificationIndicator) override;
|
|
|
|
|
|
|
|
bool dequeue() override;
|
|
|
|
|
|
|
|
bool peek(NotificationIndicator* notificationIndicator) override;
|
|
|
|
|
|
|
|
bool setIndicatorState(IndicatorState state) override;
|
|
|
|
|
2018-03-09 00:55:39 +00:00
|
|
|
bool getIndicatorState(IndicatorState* state) override;
|
2018-01-12 23:45:42 +00:00
|
|
|
|
2018-03-09 00:55:39 +00:00
|
|
|
bool checkForEmptyQueue(bool* empty) override;
|
2018-01-12 23:45:42 +00:00
|
|
|
|
|
|
|
bool clearNotificationIndicators() override;
|
|
|
|
|
2018-03-09 00:55:39 +00:00
|
|
|
bool getQueueSize(int* size) override;
|
2018-01-12 23:45:42 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Waits until the queue is a particular size.
|
|
|
|
*
|
|
|
|
* @param size The size to wait for.
|
|
|
|
* @param timeout How much time to wait before failing.
|
|
|
|
*/
|
|
|
|
bool waitForQueueSizeToBe(size_t size, std::chrono::milliseconds timeout = WAIT_TIMEOUT);
|
|
|
|
|
|
|
|
private:
|
|
|
|
/// The underlying NotificationIndicator queue.
|
|
|
|
std::queue<NotificationIndicator> m_notificationQueue;
|
|
|
|
|
|
|
|
/// The currently stored IndicatorState.
|
|
|
|
IndicatorState m_indicatorState;
|
|
|
|
|
|
|
|
/// For waiting on a particular queue size.
|
|
|
|
std::mutex m_mutex;
|
|
|
|
std::condition_variable m_conditionVariable;
|
|
|
|
};
|
|
|
|
|
2018-03-09 00:55:39 +00:00
|
|
|
bool TestNotificationsStorage::createDatabase() {
|
2018-01-12 23:45:42 +00:00
|
|
|
if (!setIndicatorState(IndicatorState::OFF)) {
|
|
|
|
ACSDK_ERROR(LX("createTestDatabaseFailed").d("reason", "failed to set default indicator state"));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-03-09 00:55:39 +00:00
|
|
|
bool TestNotificationsStorage::open() {
|
2018-01-12 23:45:42 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void TestNotificationsStorage::close() {
|
|
|
|
// no-op
|
|
|
|
}
|
|
|
|
|
|
|
|
bool TestNotificationsStorage::enqueue(const NotificationIndicator& notificationIndicator) {
|
|
|
|
m_notificationQueue.push(notificationIndicator);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool TestNotificationsStorage::dequeue() {
|
|
|
|
if (m_notificationQueue.empty()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
m_notificationQueue.pop();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool TestNotificationsStorage::peek(NotificationIndicator* notificationIndicator) {
|
|
|
|
if (m_notificationQueue.empty() || !notificationIndicator) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
*notificationIndicator = m_notificationQueue.front();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool TestNotificationsStorage::setIndicatorState(IndicatorState state) {
|
|
|
|
m_indicatorState = state;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-03-09 00:55:39 +00:00
|
|
|
bool TestNotificationsStorage::getIndicatorState(IndicatorState* state) {
|
2018-01-12 23:45:42 +00:00
|
|
|
if (!state) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
*state = m_indicatorState;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-03-09 00:55:39 +00:00
|
|
|
bool TestNotificationsStorage::checkForEmptyQueue(bool* empty) {
|
2018-01-12 23:45:42 +00:00
|
|
|
*empty = m_notificationQueue.empty();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool TestNotificationsStorage::clearNotificationIndicators() {
|
|
|
|
m_notificationQueue = std::queue<NotificationIndicator>();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-03-09 00:55:39 +00:00
|
|
|
bool TestNotificationsStorage::getQueueSize(int* size) {
|
2018-01-12 23:45:42 +00:00
|
|
|
if (!size) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
*size = m_notificationQueue.size();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool TestNotificationsStorage::waitForQueueSizeToBe(size_t size, std::chrono::milliseconds timeout) {
|
|
|
|
std::unique_lock<std::mutex> lock(m_mutex);
|
|
|
|
return m_conditionVariable.wait_for(lock, timeout, [this, size] { return m_notificationQueue.size() == size; });
|
|
|
|
}
|
|
|
|
|
|
|
|
class MockNotificationRenderer : public NotificationRendererInterface {
|
|
|
|
public:
|
|
|
|
~MockNotificationRenderer();
|
|
|
|
|
|
|
|
MockNotificationRenderer();
|
|
|
|
|
|
|
|
static std::shared_ptr<NiceMock<MockNotificationRenderer>> create() {
|
|
|
|
auto renderer = std::make_shared<NiceMock<MockNotificationRenderer>>();
|
|
|
|
|
|
|
|
// tie the mock methods to mock implementations
|
|
|
|
ON_CALL(*renderer.get(), renderNotificationShim(_, _))
|
|
|
|
.WillByDefault(Invoke(renderer.get(), &MockNotificationRenderer::mockRender));
|
|
|
|
ON_CALL(*renderer.get(), cancelNotificationRenderingShim())
|
|
|
|
.WillByDefault(InvokeWithoutArgs(renderer.get(), &MockNotificationRenderer::mockCancel));
|
|
|
|
|
|
|
|
return renderer;
|
|
|
|
}
|
|
|
|
|
|
|
|
void addObserver(std::shared_ptr<NotificationRendererObserverInterface> observer) override;
|
|
|
|
|
|
|
|
void removeObserver(std::shared_ptr<NotificationRendererObserverInterface> observer) override;
|
|
|
|
|
|
|
|
bool renderNotification(std::function<std::unique_ptr<std::istream>()> audioFactory, const std::string& url)
|
|
|
|
override;
|
|
|
|
|
|
|
|
bool cancelNotificationRendering() override;
|
|
|
|
|
|
|
|
// create shim methods to prevent compiler from complaining
|
|
|
|
MOCK_METHOD2(
|
|
|
|
renderNotificationShim,
|
|
|
|
bool(std::function<std::unique_ptr<std::istream>()> audioFactory, const std::string& url));
|
|
|
|
MOCK_METHOD0(cancelNotificationRenderingShim, bool());
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A method mocking renderNotification().
|
|
|
|
* This method kicks off two threads (for waitForRenderCall() and waitForRenderCallDone()) and then
|
|
|
|
* notifies those threads. This method may be interrupted by mockCancel().
|
|
|
|
*
|
|
|
|
* Both params are ignored in this mock implementation.
|
|
|
|
*/
|
|
|
|
bool mockRender(std::function<std::unique_ptr<std::istream>()> audioFactory, const std::string& url);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A method mocking cancelRenderingNotification().
|
|
|
|
* This method attempts to sneak in between waitForRenderCall() and waitForRenderCallDone() by
|
|
|
|
* triggering m_renderTrigger before m_finishedRendering has been set to true;
|
|
|
|
*/
|
|
|
|
bool mockCancel();
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Waits for mockRender() to set m_startedRendering to true.
|
|
|
|
*/
|
|
|
|
bool waitForRenderCall();
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Waits for mockRender() to set m_finishedRendering to true.
|
|
|
|
*/
|
|
|
|
bool waitForRenderCallDone();
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Waits for the fulfillment of m_renderStartedPromise, then resets any needed variables.
|
|
|
|
*/
|
|
|
|
bool waitUntilRenderingStarted(std::chrono::milliseconds timeout = WAIT_TIMEOUT);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Waits for the fulfillment of m_renderFinishedPromise, then resets any needed variables.
|
|
|
|
*/
|
|
|
|
bool waitUntilRenderingFinished(std::chrono::milliseconds timeout = WAIT_TIMEOUT);
|
|
|
|
|
|
|
|
private:
|
|
|
|
/// The current renderer observer.
|
|
|
|
std::shared_ptr<NotificationRendererObserverInterface> m_observer;
|
|
|
|
|
|
|
|
/// Threads that are kicked off by mockRender() to wait for the rendering to begin and end, respectively.
|
|
|
|
std::thread m_renderStartedThread;
|
|
|
|
std::thread m_renderFinishedThread;
|
|
|
|
|
|
|
|
/// Used to notify the above threads.
|
|
|
|
std::condition_variable m_renderTrigger;
|
|
|
|
|
|
|
|
/// Flags for the state of the rendering operation.
|
|
|
|
bool m_startedRendering;
|
|
|
|
bool m_finishedRendering;
|
|
|
|
bool m_cancelling;
|
|
|
|
|
|
|
|
/// Promise to be fulfilled when @c renderNotification is called.
|
|
|
|
std::promise<void> m_renderStartedPromise;
|
|
|
|
|
|
|
|
/// Future to notify when @c renderNotification is called.
|
|
|
|
std::future<void> m_renderStartedFuture;
|
|
|
|
|
|
|
|
/// Promise to be fulfilled when @c renderNotification is done.
|
|
|
|
std::promise<void> m_renderFinishedPromise;
|
|
|
|
|
|
|
|
/// Future to notify when @c renderNotification is done.
|
|
|
|
std::future<void> m_renderFinishedFuture;
|
|
|
|
|
|
|
|
/// Serializes access to m_renderTrigger;
|
|
|
|
std::mutex m_mutex;
|
|
|
|
};
|
|
|
|
|
|
|
|
MockNotificationRenderer::~MockNotificationRenderer() {
|
|
|
|
if (m_renderStartedThread.joinable()) {
|
|
|
|
m_renderStartedThread.join();
|
|
|
|
}
|
|
|
|
if (m_renderFinishedThread.joinable()) {
|
|
|
|
m_renderFinishedThread.join();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
MockNotificationRenderer::MockNotificationRenderer() :
|
|
|
|
m_observer{nullptr},
|
|
|
|
m_startedRendering{false},
|
|
|
|
m_finishedRendering{false},
|
|
|
|
m_cancelling{false},
|
|
|
|
m_renderStartedPromise{},
|
|
|
|
m_renderStartedFuture{m_renderStartedPromise.get_future()},
|
|
|
|
m_renderFinishedPromise{},
|
|
|
|
m_renderFinishedFuture{m_renderFinishedPromise.get_future()} {
|
|
|
|
}
|
|
|
|
|
|
|
|
void MockNotificationRenderer::addObserver(std::shared_ptr<NotificationRendererObserverInterface> observer) {
|
|
|
|
if (observer) {
|
|
|
|
m_observer = observer;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void MockNotificationRenderer::removeObserver(std::shared_ptr<NotificationRendererObserverInterface> observer) {
|
|
|
|
m_observer = nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool MockNotificationRenderer::renderNotification(
|
|
|
|
std::function<std::unique_ptr<std::istream>()> audioFactory,
|
|
|
|
const std::string& url) {
|
|
|
|
return renderNotificationShim(audioFactory, url);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool MockNotificationRenderer::cancelNotificationRendering() {
|
|
|
|
return cancelNotificationRenderingShim();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool MockNotificationRenderer::mockRender(
|
|
|
|
std::function<std::unique_ptr<std::istream>()> audioFactory,
|
|
|
|
const std::string& url) {
|
|
|
|
std::lock_guard<std::mutex> lock(m_mutex);
|
|
|
|
|
|
|
|
if (m_renderStartedThread.joinable() && m_renderFinishedThread.joinable()) {
|
|
|
|
m_renderStartedThread.join();
|
|
|
|
m_renderFinishedThread.join();
|
|
|
|
}
|
|
|
|
|
|
|
|
m_renderStartedThread = std::thread(&MockNotificationRenderer::waitForRenderCall, this);
|
|
|
|
m_renderFinishedThread = std::thread(&MockNotificationRenderer::waitForRenderCallDone, this);
|
|
|
|
|
|
|
|
m_startedRendering = true;
|
|
|
|
m_renderTrigger.notify_all();
|
|
|
|
|
|
|
|
std::this_thread::sleep_for(RENDER_TIME);
|
|
|
|
|
|
|
|
m_finishedRendering = true;
|
|
|
|
m_renderTrigger.notify_all();
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool MockNotificationRenderer::mockCancel() {
|
|
|
|
m_cancelling = true;
|
|
|
|
m_renderTrigger.notify_all();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool MockNotificationRenderer::waitForRenderCall() {
|
|
|
|
std::unique_lock<std::mutex> lock(m_mutex);
|
|
|
|
m_renderTrigger.wait_for(lock, WAIT_TIMEOUT, [this]() { return m_startedRendering; });
|
|
|
|
m_renderStartedPromise.set_value();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool MockNotificationRenderer::waitForRenderCallDone() {
|
|
|
|
std::unique_lock<std::mutex> lock(m_mutex);
|
|
|
|
m_renderTrigger.wait_for(lock, WAIT_TIMEOUT, [this]() { return m_cancelling || m_finishedRendering; });
|
|
|
|
m_renderFinishedPromise.set_value();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool MockNotificationRenderer::waitUntilRenderingStarted(std::chrono::milliseconds timeout) {
|
|
|
|
if (m_renderStartedFuture.wait_for(timeout) == std::future_status::ready) {
|
|
|
|
m_startedRendering = false;
|
|
|
|
m_renderStartedPromise = std::promise<void>();
|
|
|
|
m_renderStartedFuture = m_renderStartedPromise.get_future();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool MockNotificationRenderer::waitUntilRenderingFinished(std::chrono::milliseconds timeout) {
|
|
|
|
if (m_renderFinishedFuture.wait_for(timeout) == std::future_status::ready) {
|
|
|
|
m_finishedRendering = false;
|
|
|
|
m_renderFinishedPromise = std::promise<void>();
|
|
|
|
m_renderFinishedFuture = m_renderFinishedPromise.get_future();
|
|
|
|
m_observer->onNotificationRenderingFinished();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
class NotificationsCapabilityAgentTest : public ::testing::Test {
|
|
|
|
public:
|
|
|
|
void SetUp() override;
|
|
|
|
void TearDown() override;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Utility function to create the capability agent. This allows modifying of subcomponents before the CA is created.
|
|
|
|
*/
|
|
|
|
void initializeCapabilityAgent();
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Utility function to send SetIndicatorDirective
|
|
|
|
*/
|
|
|
|
void sendSetIndicatorDirective(const std::string& payload, const std::string& messageId);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Utility function to send ClearIndicatorDirective
|
|
|
|
*/
|
|
|
|
void sendClearIndicatorDirective(const std::string& messageId);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Utility function to generate direcive payloads
|
|
|
|
*/
|
|
|
|
const std::string generatePayload(
|
|
|
|
bool persistVisualIndicator,
|
|
|
|
bool playAudioIndicator,
|
|
|
|
const std::string& assetId = ASSET_ID1,
|
|
|
|
const std::string& assetUrl = ASSET_URL1);
|
|
|
|
|
|
|
|
/// @c A test observer to wait for @c AudioPlayer state changes
|
|
|
|
std::shared_ptr<TestNotificationsObserver> m_testNotificationsObserver;
|
|
|
|
|
|
|
|
/// @c NotificationsCapabilityAgent to test
|
|
|
|
std::shared_ptr<NotificationsCapabilityAgent> m_notificationsCapabilityAgent;
|
|
|
|
|
|
|
|
/// @c Storage for notifications
|
|
|
|
std::shared_ptr<TestNotificationsStorage> m_notificationsStorage;
|
|
|
|
|
|
|
|
/// Player to play notification audio assets
|
|
|
|
std::shared_ptr<MockNotificationRenderer> m_renderer;
|
|
|
|
|
|
|
|
/// @c ContextManager to provide state and update state.
|
|
|
|
std::shared_ptr<MockContextManager> m_mockContextManager;
|
|
|
|
|
|
|
|
/// A directive handler result to send the result to.
|
|
|
|
std::unique_ptr<MockDirectiveHandlerResult> m_mockDirectiveHandlerResult;
|
|
|
|
|
|
|
|
/// An exception sender used to send exception encountered events to AVS.
|
|
|
|
std::shared_ptr<MockExceptionEncounteredSender> m_mockExceptionSender;
|
|
|
|
|
|
|
|
/// An audio factory for testing.
|
|
|
|
std::shared_ptr<TestNotificationsAudioFactory> m_testNotificationsAudioFactory;
|
|
|
|
|
|
|
|
/// Serializes access to m_setIndicatorTrigger.
|
|
|
|
std::mutex m_mutex;
|
|
|
|
|
|
|
|
/// Triggers threads waiting on a SetIndicator directive to be processed.
|
|
|
|
std::condition_variable m_setIndicatorTrigger;
|
|
|
|
|
2018-03-09 00:55:39 +00:00
|
|
|
/// Shared pointer to @c CustomerDataManager
|
|
|
|
std::shared_ptr<registrationManager::CustomerDataManager> m_dataManager;
|
|
|
|
|
2018-01-12 23:45:42 +00:00
|
|
|
/// A count of how many SetIndicator directives have been processed.
|
|
|
|
unsigned int m_numSetIndicatorsProcessed;
|
|
|
|
};
|
|
|
|
|
|
|
|
void NotificationsCapabilityAgentTest::initializeCapabilityAgent() {
|
|
|
|
m_notificationsCapabilityAgent = NotificationsCapabilityAgent::create(
|
|
|
|
m_notificationsStorage,
|
|
|
|
m_renderer,
|
|
|
|
m_mockContextManager,
|
|
|
|
m_mockExceptionSender,
|
2018-03-09 00:55:39 +00:00
|
|
|
m_testNotificationsAudioFactory,
|
|
|
|
m_dataManager);
|
2018-01-12 23:45:42 +00:00
|
|
|
ASSERT_TRUE(m_notificationsCapabilityAgent);
|
|
|
|
m_notificationsCapabilityAgent->addObserver(m_testNotificationsObserver);
|
|
|
|
m_renderer->addObserver(m_notificationsCapabilityAgent);
|
|
|
|
}
|
|
|
|
|
|
|
|
void NotificationsCapabilityAgentTest::SetUp() {
|
Version 1.7.0 of the avs-device-sdk
Changes in this update:
**Enhancements**
* `AuthDelegate` and `AuthServer.py` have been replaced by `CBLAUthDelegate`, which provides a more straightforward path to authorization.
* Added a new configuration property called [`cblAuthDelegate`](https://github.com/alexa/avs-device-sdk/blob/master/Integration/AlexaClientSDKConfig.json#L2). This object specifies parameters for `CBLAuthDelegate`.
* Added a new configuration property called [`miscDatabase`](https://github.com/alexa/avs-device-sdk/blob/master/Integration/AlexaClientSDKConfig.json#L34), which is a generic key/value database to be used by various components.
* Added a new configuration property called [`dcfDelegate`](https://github.com/alexa/avs-device-sdk/blob/master/Integration/AlexaClientSDKConfig.json#L17) This object specifies parameters for `DCFDelegate`. Within this object, values were added for the 'endpoint' and `overridenDcfPublishMessageBody`. 'endpoint' is the endpoint to connect to in order to send device capabilities. `overridenDcfPublishMessageBody`is the message that will get sent out to the Capabilities API. Note: values within the `dcfDelegate` object will only work in `DEBUG` builds.
* Added a new configuration property called [`deviceInfo`](https://github.com/alexa/avs-device-sdk/blob/master/Integration/AlexaClientSDKConfig.json#L9) which specifies device-identifying information for use by the Device Capability Framework (DCF), and for authorization (CBLAuthDelegate).
* Updated the Directive Sequencer to support wildcard directive handlers. This allows a handler for a given AVS interface to register at the namespace level, rather than specifying the names of all directives within that namespace.
* Updated the Raspberry Pi installation script to include `alsasink` in the configuration file.
* Added `audioSink` as a configuration option. This allows users to override the audio sink element used in `Gstreamer`.
* Added an interface for monitoring internet connection status: `InternetConnectionMonitorInterface.h`.
* The Alexa Communications Library (ACL) is no longer required to wait until authorization has succeeded before attempting to connect to AVS. Instead, `HTTP2Transport` handles waiting for authorization to complete.
* Added the Device Capabilities Framework (DCF) delegate. Device capabilities can now be sent for each capability interface using DCF publish messages.
* The sample app has been updated to send DCF publish messages, which will automatically occur when the sample app starts. Note: a DCF publish message must be successfully sent in order for communication with AVS to occur.
* The SDK now supports HTTP PUT messages.
* Added support for opt-arg style arguments and multiple configuration files. Now, the sample app can be invoked by either of these commands: `SampleApp <configfile> <debuglevel>` OR `SampleApp -C file1 -C file2 ... -L loglevel`.
**Bug Fixes**
* Issues [447](https://github.com/alexa/avs-device-sdk/issues/447) and [553](https://github.com/alexa/avs-device-sdk/issues/553) Fixed the `AttachmentRenderSource`'s handling of `BLOCKING` `AttachmentReaders`.
* Updated the `Logger` implementation to be more resilient to `nullptr` string inputs.
* Fixed a `TimeUtils` utility-related compile issue.
* Fixed a bug in which alerts failed to activate if the system was restarted without network connection.
* Fixed Android 64-bit build failure issue.
**Known Issues**
* The `ACL` may encounter issues if audio attachments are received but not consumed.
* `SpeechSynthesizerState` currently uses `GAINING_FOCUS` and `LOSING_FOCUS` as a workaround for handling intermediate state. These states may be removed in a future release.
* Some ERROR messages may be printed during start-up event if initialization proceeds normally and successfully.
* If an unrecoverable authorization error or an unrecoverable DCF error is encountered, the sample app may crash on shutdown.
* If a non-CBL `clientId` is included in the `deviceInfo` section of `AlexaClientSDKConfig.json`, the error will be reported as an unrecoverable authorization error, rather than a more specific error.
2018-04-18 22:17:28 +00:00
|
|
|
auto inString = std::shared_ptr<std::istringstream>(new std::istringstream(NOTIFICATIONS_CONFIG_JSON));
|
|
|
|
ASSERT_TRUE(AlexaClientSDKInit::initialize({inString}));
|
2018-01-12 23:45:42 +00:00
|
|
|
|
|
|
|
m_notificationsStorage = std::make_shared<TestNotificationsStorage>();
|
|
|
|
m_renderer = MockNotificationRenderer::create();
|
|
|
|
m_mockContextManager = std::make_shared<NiceMock<MockContextManager>>();
|
|
|
|
m_mockExceptionSender = std::make_shared<NiceMock<MockExceptionEncounteredSender>>();
|
|
|
|
m_testNotificationsAudioFactory = std::make_shared<TestNotificationsAudioFactory>();
|
|
|
|
|
|
|
|
m_testNotificationsObserver = std::make_shared<TestNotificationsObserver>();
|
|
|
|
|
|
|
|
m_mockDirectiveHandlerResult = std::unique_ptr<MockDirectiveHandlerResult>(new MockDirectiveHandlerResult);
|
|
|
|
|
|
|
|
m_numSetIndicatorsProcessed = 0;
|
2018-03-09 00:55:39 +00:00
|
|
|
m_dataManager = std::make_shared<registrationManager::CustomerDataManager>();
|
2018-01-12 23:45:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void NotificationsCapabilityAgentTest::TearDown() {
|
|
|
|
if (m_notificationsCapabilityAgent) {
|
|
|
|
m_notificationsCapabilityAgent->shutdown();
|
|
|
|
}
|
|
|
|
AlexaClientSDKInit::uninitialize();
|
|
|
|
}
|
|
|
|
|
|
|
|
void NotificationsCapabilityAgentTest::sendSetIndicatorDirective(
|
|
|
|
const std::string& payload,
|
|
|
|
const std::string& messageId) {
|
|
|
|
auto avsMessageHeader = std::make_shared<AVSMessageHeader>(NAMESPACE_NOTIFICATIONS, NAME_SET_INDICATOR, messageId);
|
|
|
|
|
|
|
|
auto mockAttachmentManager = std::make_shared<MockAttachmentManager>();
|
|
|
|
|
|
|
|
std::shared_ptr<AVSDirective> setIndicatorDirective =
|
|
|
|
AVSDirective::create("", avsMessageHeader, payload, mockAttachmentManager, CONTEXT_ID_TEST);
|
|
|
|
|
|
|
|
// cast to DirectiveHandlerInterface so the compiler can find the correct preHandleDirective signature
|
|
|
|
std::shared_ptr<DirectiveHandlerInterface> agentAsDirectiveHandler = m_notificationsCapabilityAgent;
|
|
|
|
|
|
|
|
agentAsDirectiveHandler->preHandleDirective(setIndicatorDirective, std::move(m_mockDirectiveHandlerResult));
|
|
|
|
agentAsDirectiveHandler->handleDirective(messageId);
|
|
|
|
|
|
|
|
m_numSetIndicatorsProcessed++;
|
|
|
|
m_setIndicatorTrigger.notify_all();
|
|
|
|
}
|
|
|
|
|
|
|
|
void NotificationsCapabilityAgentTest::sendClearIndicatorDirective(const std::string& messageId) {
|
|
|
|
auto avsMessageHeader =
|
|
|
|
std::make_shared<AVSMessageHeader>(NAMESPACE_NOTIFICATIONS, NAME_CLEAR_INDICATOR, messageId);
|
|
|
|
|
|
|
|
auto mockAttachmentManager = std::make_shared<MockAttachmentManager>();
|
|
|
|
|
|
|
|
std::shared_ptr<AVSDirective> clearIndicatorDirective =
|
|
|
|
AVSDirective::create("", avsMessageHeader, "", mockAttachmentManager, CONTEXT_ID_TEST);
|
|
|
|
|
|
|
|
// cast to DirectiveHandlerInterface so the compiler can find the correct preHandleDirective signature
|
|
|
|
std::shared_ptr<DirectiveHandlerInterface> agentAsDirectiveHandler = m_notificationsCapabilityAgent;
|
|
|
|
|
|
|
|
agentAsDirectiveHandler->preHandleDirective(clearIndicatorDirective, std::move(m_mockDirectiveHandlerResult));
|
|
|
|
agentAsDirectiveHandler->handleDirective(messageId);
|
|
|
|
}
|
|
|
|
|
|
|
|
const std::string NotificationsCapabilityAgentTest::generatePayload(
|
|
|
|
bool persistVisualIndicator,
|
|
|
|
bool playAudioIndicator,
|
|
|
|
const std::string& assetId,
|
|
|
|
const std::string& assetUrl) {
|
|
|
|
std::string stringPersistVisualIndicator(persistVisualIndicator ? "true" : "false");
|
|
|
|
std::string stringPlayAudioIndicator(playAudioIndicator ? "true" : "false");
|
|
|
|
|
|
|
|
// clang-format off
|
|
|
|
const std::string payload =
|
|
|
|
"{"
|
|
|
|
"\"persistVisualIndicator\":" + stringPersistVisualIndicator + ","
|
|
|
|
"\"playAudioIndicator\":" + stringPlayAudioIndicator + ","
|
|
|
|
"\"asset\": {"
|
|
|
|
"\"assetId\":\"" + assetId + "\","
|
|
|
|
"\"url\":\"" + assetUrl + "\""
|
|
|
|
|
|
|
|
"}"
|
|
|
|
"}";
|
|
|
|
// clang-format on
|
|
|
|
return payload;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Test create() with nullptrs
|
|
|
|
*/
|
|
|
|
TEST_F(NotificationsCapabilityAgentTest, testCreate) {
|
|
|
|
std::shared_ptr<NotificationsCapabilityAgent> testNotificationsCapabilityAgent;
|
|
|
|
|
|
|
|
testNotificationsCapabilityAgent = NotificationsCapabilityAgent::create(
|
2018-03-09 00:55:39 +00:00
|
|
|
nullptr,
|
|
|
|
m_renderer,
|
|
|
|
m_mockContextManager,
|
|
|
|
m_mockExceptionSender,
|
|
|
|
m_testNotificationsAudioFactory,
|
|
|
|
m_dataManager);
|
2018-01-12 23:45:42 +00:00
|
|
|
EXPECT_EQ(testNotificationsCapabilityAgent, nullptr);
|
|
|
|
|
|
|
|
testNotificationsCapabilityAgent = NotificationsCapabilityAgent::create(
|
2018-03-09 00:55:39 +00:00
|
|
|
m_notificationsStorage,
|
|
|
|
nullptr,
|
|
|
|
m_mockContextManager,
|
|
|
|
m_mockExceptionSender,
|
|
|
|
m_testNotificationsAudioFactory,
|
|
|
|
m_dataManager);
|
2018-01-12 23:45:42 +00:00
|
|
|
EXPECT_EQ(testNotificationsCapabilityAgent, nullptr);
|
|
|
|
|
|
|
|
testNotificationsCapabilityAgent = NotificationsCapabilityAgent::create(
|
2018-03-09 00:55:39 +00:00
|
|
|
m_notificationsStorage,
|
|
|
|
m_renderer,
|
|
|
|
nullptr,
|
|
|
|
m_mockExceptionSender,
|
|
|
|
m_testNotificationsAudioFactory,
|
|
|
|
m_dataManager);
|
2018-01-12 23:45:42 +00:00
|
|
|
EXPECT_EQ(testNotificationsCapabilityAgent, nullptr);
|
|
|
|
|
|
|
|
testNotificationsCapabilityAgent = NotificationsCapabilityAgent::create(
|
2018-03-09 00:55:39 +00:00
|
|
|
m_notificationsStorage,
|
|
|
|
m_renderer,
|
|
|
|
m_mockContextManager,
|
|
|
|
nullptr,
|
|
|
|
m_testNotificationsAudioFactory,
|
|
|
|
m_dataManager);
|
2018-01-12 23:45:42 +00:00
|
|
|
EXPECT_EQ(testNotificationsCapabilityAgent, nullptr);
|
|
|
|
|
|
|
|
testNotificationsCapabilityAgent = NotificationsCapabilityAgent::create(
|
2018-03-09 00:55:39 +00:00
|
|
|
m_notificationsStorage, m_renderer, m_mockContextManager, m_mockExceptionSender, nullptr, m_dataManager);
|
2018-01-12 23:45:42 +00:00
|
|
|
EXPECT_EQ(testNotificationsCapabilityAgent, nullptr);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Test starting up the capability agent with a non-empty queue.
|
|
|
|
* Expect that the next item in the queue will be played.
|
|
|
|
*/
|
|
|
|
TEST_F(NotificationsCapabilityAgentTest, testNonEmptyStartupQueue) {
|
|
|
|
NotificationIndicator ni(true, true, ASSET_ID1, ASSET_URL1);
|
|
|
|
ASSERT_TRUE(m_notificationsStorage->enqueue(ni));
|
|
|
|
|
|
|
|
EXPECT_CALL(*(m_renderer.get()), renderNotificationShim(_, ASSET_URL1)).Times(1);
|
|
|
|
initializeCapabilityAgent();
|
|
|
|
|
|
|
|
ASSERT_TRUE(m_renderer->waitUntilRenderingFinished());
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Test a single SetIndicator directive with persistVisualIndicator and playAudioIndicator set to false.
|
|
|
|
* Expect that the NotificationsObserver is notified of the indicator's state remaining OFF.
|
|
|
|
* Expect no calls to render notifications since playAudioIndicator is false.
|
|
|
|
*/
|
|
|
|
TEST_F(NotificationsCapabilityAgentTest, testSendSetIndicator) {
|
|
|
|
EXPECT_CALL(*(m_renderer.get()), renderNotificationShim(_, _)).Times(0);
|
|
|
|
initializeCapabilityAgent();
|
|
|
|
ASSERT_TRUE(m_testNotificationsObserver->waitFor(IndicatorState::OFF, WAIT_TIMEOUT));
|
|
|
|
|
|
|
|
sendSetIndicatorDirective(generatePayload(true, false), MESSAGE_ID_TEST);
|
|
|
|
ASSERT_TRUE(m_testNotificationsObserver->waitFor(IndicatorState::ON, WAIT_TIMEOUT));
|
|
|
|
|
|
|
|
// check that the NotificationIndicator was dequeued as expected
|
|
|
|
ASSERT_TRUE(m_notificationsStorage->waitForQueueSizeToBe(0));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Test a single SetIndicator directive with with playAudioIndicator set to true.
|
|
|
|
* Expect the renderer to start playback of the Notification.
|
|
|
|
* Expect that the NotificationsObserver is notified of the indicator's state being OFF.
|
|
|
|
*/
|
|
|
|
TEST_F(NotificationsCapabilityAgentTest, testSendSetIndicatorWithAudio) {
|
|
|
|
EXPECT_CALL(*(m_renderer.get()), renderNotificationShim(_, ASSET_URL1));
|
|
|
|
initializeCapabilityAgent();
|
|
|
|
|
|
|
|
sendSetIndicatorDirective(generatePayload(false, true, ASSET_ID1, ASSET_URL1), MESSAGE_ID_TEST);
|
|
|
|
ASSERT_TRUE(m_testNotificationsObserver->waitFor(IndicatorState::OFF, WAIT_TIMEOUT));
|
|
|
|
|
|
|
|
ASSERT_TRUE(m_renderer->waitUntilRenderingFinished());
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Test a single SetIndicator directive with with persistVisualIndicator set to true.
|
|
|
|
* Expect that the NotificationsObserver is notified of the indicator's state being ON.
|
|
|
|
*/
|
|
|
|
TEST_F(NotificationsCapabilityAgentTest, testSendSetIndicatorWithVisualIndicator) {
|
|
|
|
EXPECT_CALL(*(m_renderer.get()), renderNotificationShim(_, _)).Times(0);
|
|
|
|
initializeCapabilityAgent();
|
|
|
|
|
|
|
|
sendSetIndicatorDirective(generatePayload(true, false), MESSAGE_ID_TEST);
|
|
|
|
ASSERT_TRUE(m_testNotificationsObserver->waitFor(IndicatorState::ON, WAIT_TIMEOUT));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Test sending two SetIndicator directives where the second has the same assetId as the first.
|
|
|
|
* Expect that the renderer only gets one call to renderNotification().
|
|
|
|
*/
|
|
|
|
TEST_F(NotificationsCapabilityAgentTest, testSameAssetId) {
|
|
|
|
EXPECT_CALL(*(m_renderer.get()), renderNotificationShim(_, ASSET_URL1))
|
|
|
|
.Times(1)
|
|
|
|
.WillOnce(Invoke([this](std::function<std::unique_ptr<std::istream>()> audioFactory, const std::string& url) {
|
|
|
|
unsigned int expectedNumSetIndicators = 2;
|
|
|
|
|
|
|
|
std::unique_lock<std::mutex> lock(m_mutex);
|
|
|
|
if (!m_setIndicatorTrigger.wait_for(lock, WAIT_TIMEOUT, [this, expectedNumSetIndicators]() {
|
|
|
|
return m_numSetIndicatorsProcessed == expectedNumSetIndicators;
|
|
|
|
})) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
m_renderer->mockRender(audioFactory, url);
|
|
|
|
EXPECT_TRUE(m_renderer->waitUntilRenderingStarted());
|
|
|
|
return true;
|
|
|
|
}));
|
|
|
|
initializeCapabilityAgent();
|
|
|
|
|
|
|
|
sendSetIndicatorDirective(generatePayload(true, true, ASSET_ID1), MESSAGE_ID_TEST);
|
|
|
|
|
|
|
|
// send a second SetIndicator with the same assetId but persistVisualIndicator set to false.
|
|
|
|
sendSetIndicatorDirective(generatePayload(false, true, ASSET_ID1), MESSAGE_ID_TEST2);
|
|
|
|
|
|
|
|
// the IndicatorState should not have changed since the second directive should have been ignored.
|
|
|
|
ASSERT_TRUE(m_testNotificationsObserver->waitFor(IndicatorState::ON, WAIT_TIMEOUT));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Test that the persistVisualIndicator setting is preserved and used across shutdown.
|
|
|
|
*/
|
|
|
|
TEST_F(NotificationsCapabilityAgentTest, testPersistVisualIndicatorPreserved) {
|
|
|
|
initializeCapabilityAgent();
|
|
|
|
|
|
|
|
// set IndicatorState to ON
|
|
|
|
sendSetIndicatorDirective(generatePayload(true, false, ASSET_ID1), MESSAGE_ID_TEST);
|
|
|
|
ASSERT_TRUE(m_testNotificationsObserver->waitFor(IndicatorState::ON, WAIT_TIMEOUT));
|
|
|
|
|
|
|
|
m_notificationsCapabilityAgent->shutdown();
|
|
|
|
|
|
|
|
// reboot and check that the persistVisualIndicator value has been preserved
|
|
|
|
initializeCapabilityAgent();
|
|
|
|
ASSERT_TRUE(m_testNotificationsObserver->waitFor(IndicatorState::ON, WAIT_TIMEOUT));
|
|
|
|
|
|
|
|
// same test but with IndicatorState set to OFF
|
|
|
|
sendSetIndicatorDirective(generatePayload(false, false, ASSET_ID1), MESSAGE_ID_TEST);
|
|
|
|
ASSERT_TRUE(m_testNotificationsObserver->waitFor(IndicatorState::OFF, WAIT_TIMEOUT));
|
|
|
|
|
|
|
|
m_notificationsCapabilityAgent->shutdown();
|
|
|
|
|
|
|
|
initializeCapabilityAgent();
|
|
|
|
ASSERT_TRUE(m_testNotificationsObserver->waitFor(IndicatorState::OFF, WAIT_TIMEOUT));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Test sending a ClearIndicator directive with an empty queue, expecting nothing to happen.
|
|
|
|
*/
|
|
|
|
TEST_F(NotificationsCapabilityAgentTest, testClearIndicatorWithEmptyQueue) {
|
|
|
|
initializeCapabilityAgent();
|
|
|
|
sendClearIndicatorDirective(MESSAGE_ID_TEST);
|
|
|
|
ASSERT_TRUE(m_testNotificationsObserver->waitFor(IndicatorState::OFF, WAIT_TIMEOUT));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Test sending a ClearIndicator directive with an empty queue and the indicator state set to ON.
|
|
|
|
* Expect that the indicator is set to OFF.
|
|
|
|
*/
|
|
|
|
TEST_F(NotificationsCapabilityAgentTest, testClearIndicatorWithEmptyQueueAndIndicatorOn) {
|
|
|
|
EXPECT_CALL(*(m_renderer.get()), renderNotificationShim(_, ASSET_URL1)).Times(1);
|
|
|
|
initializeCapabilityAgent();
|
|
|
|
|
|
|
|
sendSetIndicatorDirective(generatePayload(true, true, ASSET_ID1), MESSAGE_ID_TEST);
|
|
|
|
|
|
|
|
ASSERT_TRUE(m_renderer->waitUntilRenderingFinished());
|
|
|
|
|
|
|
|
ASSERT_TRUE(m_testNotificationsObserver->waitFor(IndicatorState::ON, WAIT_TIMEOUT));
|
|
|
|
|
|
|
|
sendClearIndicatorDirective(MESSAGE_ID_TEST2);
|
|
|
|
|
|
|
|
ASSERT_TRUE(m_testNotificationsObserver->waitFor(IndicatorState::OFF, WAIT_TIMEOUT));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Test sending a ClearIndicator directive after multiple SetIndicator directives.
|
|
|
|
* Expect that the indicator is set to OFF.
|
|
|
|
*/
|
|
|
|
TEST_F(NotificationsCapabilityAgentTest, testClearIndicatorAfterMultipleSetIndicators) {
|
|
|
|
EXPECT_CALL(*(m_renderer.get()), renderNotificationShim(_, ASSET_URL1)).Times(1);
|
|
|
|
EXPECT_CALL(*(m_renderer.get()), cancelNotificationRenderingShim()).Times(1);
|
|
|
|
initializeCapabilityAgent();
|
|
|
|
|
|
|
|
sendSetIndicatorDirective(generatePayload(true, true, "assetId1"), "firstIndicatorMessageId");
|
|
|
|
sendSetIndicatorDirective(generatePayload(true, true, "assetId2"), "secondIndicatorMessageId");
|
|
|
|
sendSetIndicatorDirective(generatePayload(true, true, "assetId3"), "thirdIndicatorMessageId");
|
|
|
|
|
|
|
|
ASSERT_TRUE(m_renderer->waitUntilRenderingStarted());
|
|
|
|
sendClearIndicatorDirective(MESSAGE_ID_TEST);
|
|
|
|
|
|
|
|
// the renderer still calls onNotificationRenderingFinished() when a notification has been cancelled
|
|
|
|
ASSERT_TRUE(m_renderer->waitUntilRenderingFinished());
|
|
|
|
|
|
|
|
ASSERT_TRUE(m_notificationsStorage->waitForQueueSizeToBe(0));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Test sending multiple SetIndicators and letting them all render.
|
|
|
|
* Expect multiple calls to renderNotification().
|
|
|
|
*/
|
|
|
|
TEST_F(NotificationsCapabilityAgentTest, testMultipleSetIndicators) {
|
|
|
|
EXPECT_CALL(*(m_renderer.get()), renderNotificationShim(_, ASSET_URL1)).Times(3);
|
|
|
|
initializeCapabilityAgent();
|
|
|
|
|
|
|
|
sendSetIndicatorDirective(generatePayload(true, true, "id1"), "firstIndicatorMessageId");
|
|
|
|
sendSetIndicatorDirective(generatePayload(true, true, "id2"), "secondIndicatorMessageId");
|
|
|
|
sendSetIndicatorDirective(generatePayload(true, true, "id3"), "thirdIndicatorMessageId");
|
|
|
|
|
|
|
|
ASSERT_TRUE(m_renderer->waitUntilRenderingStarted());
|
|
|
|
ASSERT_TRUE(m_renderer->waitUntilRenderingFinished());
|
|
|
|
|
|
|
|
ASSERT_TRUE(m_renderer->waitUntilRenderingStarted());
|
|
|
|
ASSERT_TRUE(m_renderer->waitUntilRenderingFinished());
|
|
|
|
|
|
|
|
ASSERT_TRUE(m_renderer->waitUntilRenderingStarted());
|
|
|
|
ASSERT_TRUE(m_renderer->waitUntilRenderingFinished());
|
|
|
|
}
|
|
|
|
|
2018-03-09 00:55:39 +00:00
|
|
|
/**
|
|
|
|
* Test that @c clearData() removes all notifications and sets the indicator to OFF.
|
|
|
|
*/
|
|
|
|
TEST_F(NotificationsCapabilityAgentTest, testClearData) {
|
|
|
|
initializeCapabilityAgent();
|
|
|
|
sendSetIndicatorDirective(generatePayload(true, true, "assetId1"), "firstIndicatorMessageId");
|
|
|
|
ASSERT_TRUE(m_renderer->waitUntilRenderingStarted());
|
|
|
|
|
|
|
|
// Check that indicator is ON
|
|
|
|
IndicatorState state = IndicatorState::UNDEFINED;
|
|
|
|
m_notificationsStorage->getIndicatorState(&state);
|
|
|
|
ASSERT_EQ(state, IndicatorState::ON);
|
|
|
|
|
|
|
|
// Check that the notification queue is not empty
|
|
|
|
int queueSize;
|
|
|
|
m_notificationsStorage->getQueueSize(&queueSize);
|
|
|
|
ASSERT_GT(queueSize, 0);
|
|
|
|
|
|
|
|
m_notificationsCapabilityAgent->clearData();
|
|
|
|
ASSERT_TRUE(m_notificationsStorage->waitForQueueSizeToBe(0));
|
|
|
|
|
|
|
|
m_notificationsStorage->getIndicatorState(&state);
|
|
|
|
ASSERT_EQ(state, IndicatorState::OFF);
|
|
|
|
}
|
|
|
|
|
2018-01-12 23:45:42 +00:00
|
|
|
} // namespace test
|
|
|
|
} // namespace notifications
|
|
|
|
} // namespace capabilityAgents
|
|
|
|
} // namespace alexaClientSDK
|
Version 1.7.0 of the avs-device-sdk
Changes in this update:
**Enhancements**
* `AuthDelegate` and `AuthServer.py` have been replaced by `CBLAUthDelegate`, which provides a more straightforward path to authorization.
* Added a new configuration property called [`cblAuthDelegate`](https://github.com/alexa/avs-device-sdk/blob/master/Integration/AlexaClientSDKConfig.json#L2). This object specifies parameters for `CBLAuthDelegate`.
* Added a new configuration property called [`miscDatabase`](https://github.com/alexa/avs-device-sdk/blob/master/Integration/AlexaClientSDKConfig.json#L34), which is a generic key/value database to be used by various components.
* Added a new configuration property called [`dcfDelegate`](https://github.com/alexa/avs-device-sdk/blob/master/Integration/AlexaClientSDKConfig.json#L17) This object specifies parameters for `DCFDelegate`. Within this object, values were added for the 'endpoint' and `overridenDcfPublishMessageBody`. 'endpoint' is the endpoint to connect to in order to send device capabilities. `overridenDcfPublishMessageBody`is the message that will get sent out to the Capabilities API. Note: values within the `dcfDelegate` object will only work in `DEBUG` builds.
* Added a new configuration property called [`deviceInfo`](https://github.com/alexa/avs-device-sdk/blob/master/Integration/AlexaClientSDKConfig.json#L9) which specifies device-identifying information for use by the Device Capability Framework (DCF), and for authorization (CBLAuthDelegate).
* Updated the Directive Sequencer to support wildcard directive handlers. This allows a handler for a given AVS interface to register at the namespace level, rather than specifying the names of all directives within that namespace.
* Updated the Raspberry Pi installation script to include `alsasink` in the configuration file.
* Added `audioSink` as a configuration option. This allows users to override the audio sink element used in `Gstreamer`.
* Added an interface for monitoring internet connection status: `InternetConnectionMonitorInterface.h`.
* The Alexa Communications Library (ACL) is no longer required to wait until authorization has succeeded before attempting to connect to AVS. Instead, `HTTP2Transport` handles waiting for authorization to complete.
* Added the Device Capabilities Framework (DCF) delegate. Device capabilities can now be sent for each capability interface using DCF publish messages.
* The sample app has been updated to send DCF publish messages, which will automatically occur when the sample app starts. Note: a DCF publish message must be successfully sent in order for communication with AVS to occur.
* The SDK now supports HTTP PUT messages.
* Added support for opt-arg style arguments and multiple configuration files. Now, the sample app can be invoked by either of these commands: `SampleApp <configfile> <debuglevel>` OR `SampleApp -C file1 -C file2 ... -L loglevel`.
**Bug Fixes**
* Issues [447](https://github.com/alexa/avs-device-sdk/issues/447) and [553](https://github.com/alexa/avs-device-sdk/issues/553) Fixed the `AttachmentRenderSource`'s handling of `BLOCKING` `AttachmentReaders`.
* Updated the `Logger` implementation to be more resilient to `nullptr` string inputs.
* Fixed a `TimeUtils` utility-related compile issue.
* Fixed a bug in which alerts failed to activate if the system was restarted without network connection.
* Fixed Android 64-bit build failure issue.
**Known Issues**
* The `ACL` may encounter issues if audio attachments are received but not consumed.
* `SpeechSynthesizerState` currently uses `GAINING_FOCUS` and `LOSING_FOCUS` as a workaround for handling intermediate state. These states may be removed in a future release.
* Some ERROR messages may be printed during start-up event if initialization proceeds normally and successfully.
* If an unrecoverable authorization error or an unrecoverable DCF error is encountered, the sample app may crash on shutdown.
* If a non-CBL `clientId` is included in the `deviceInfo` section of `AlexaClientSDKConfig.json`, the error will be reported as an unrecoverable authorization error, rather than a more specific error.
2018-04-18 22:17:28 +00:00
|
|
|
|
|
|
|
int main(int argc, char** argv) {
|
|
|
|
::testing::InitGoogleTest(&argc, argv);
|
|
|
|
|
|
|
|
// ACSDK-1367 - Some tests fail on Windows
|
|
|
|
#if defined(_WIN32) && !defined(RESOLVED_ACSDK_1367)
|
|
|
|
::testing::GTEST_FLAG(filter) = "-NotificationsCapabilityAgentTest.testSameAssetId";
|
|
|
|
#endif
|
|
|
|
return RUN_ALL_TESTS();
|
|
|
|
}
|