diff --git a/ACL/include/ACL/AVSConnectionManager.h b/ACL/include/ACL/AVSConnectionManager.h index 0a9c5aaa..e9fbe8b6 100644 --- a/ACL/include/ACL/AVSConnectionManager.h +++ b/ACL/include/ACL/AVSConnectionManager.h @@ -1,5 +1,5 @@ /* - * Copyright 2016-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2016-2020 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. diff --git a/ACL/include/ACL/Transport/HTTP2Transport.h b/ACL/include/ACL/Transport/HTTP2Transport.h index 4d605ee1..ef6cd1a2 100644 --- a/ACL/include/ACL/Transport/HTTP2Transport.h +++ b/ACL/include/ACL/Transport/HTTP2Transport.h @@ -1,5 +1,5 @@ /* - * Copyright 2016-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2016-2020 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. @@ -27,16 +27,18 @@ #include #include -#include #include #include +#include +#include +#include #include "ACL/Transport/MessageConsumerInterface.h" #include "ACL/Transport/PingHandler.h" #include "ACL/Transport/PostConnectFactoryInterface.h" #include "ACL/Transport/PostConnectObserverInterface.h" -#include "ACL/Transport/TransportInterface.h" #include "ACL/Transport/TransportObserverInterface.h" +#include "ACL/Transport/SynchronizedMessageRequestQueue.h" namespace alexaClientSDK { namespace acl { @@ -50,6 +52,7 @@ class HTTP2Transport , public PostConnectObserverInterface , public avsCommon::sdkInterfaces::PostConnectSendMessageInterface , public avsCommon::sdkInterfaces::AuthObserverInterface + , public avsCommon::utils::http2::HTTP2ConnectionObserverInterface , public ExchangeHandlerContextInterface { public: /* @@ -75,7 +78,9 @@ public: * @param attachmentManager The attachment manager that manages the attachments. * @param transportObserver The observer of the new instance of TransportInterface. * @param postConnectFactory The object used to create @c PostConnectInterface instances. + * @param sharedRequestQueue Request queue shared by all instances of HTTPTransportInterface. * @param configuration An optional configuration to specify HTTP2/2 connection settings. + * @param metricRecorder The metric recorder. * @return A shared pointer to a HTTP2Transport object. */ static std::shared_ptr create( @@ -86,7 +91,9 @@ public: std::shared_ptr attachmentManager, std::shared_ptr transportObserver, std::shared_ptr postConnectFactory, - Configuration configuration = Configuration()); + std::shared_ptr sharedRequestQueue, + Configuration configuration = Configuration(), + std::shared_ptr metricRecorder = nullptr); /** * Method to add a TransportObserverInterface instance. @@ -114,7 +121,7 @@ public: bool connect() override; void disconnect() override; bool isConnected() override; - void send(std::shared_ptr request) override; + void onRequestEnqueued() override; /// @} /// @name PostConnectSendMessageInterface methods. @@ -157,6 +164,10 @@ public: std::string getAVSGateway() override; /// @} + /// @name HTTP2ConnectionObserverInterface methods. + void onGoawayReceived() override; + /// @} + private: /** * Enum to track the (internal) state of the HTTP2Transport @@ -196,7 +207,9 @@ private: * @param attachmentManager The attachment manager that manages the attachments. * @param transportObserver The observer of the new instance of TransportInterface. * @param postConnect The object used to create PostConnectInterface instances. + * @param sharedRequestQueue Request queue shared by all instances of HTTPTransportInterface. * @param configuration The HTTP2/2 connection settings. + * @param metricRecorder The metric recorder. */ HTTP2Transport( std::shared_ptr authDelegate, @@ -206,7 +219,9 @@ private: std::shared_ptr attachmentManager, std::shared_ptr transportObserver, std::shared_ptr postConnectFactory, - Configuration configuration); + std::shared_ptr sharedRequestQueue, + Configuration configuration, + std::shared_ptr metricRecorder); /** * Main loop for servicing the various states. @@ -277,12 +292,17 @@ private: State handleShutdown(); /** - * Enqueue a MessageRequest for sending. + * Monitor the shared message queue while waiting for a state change. If messages have been sitting in the + * queue for too long (e.g. during prolonged internet connectivity outtage), complete them as TIMEDOUT. * - * @param request The MessageRequest to enqueue. - * @param beforeConnected Whether or not to only allow enqueuing of messages before connected. + * @param whileState The @c State to keep waiting in. + * @param wakeTime The max time point to wait for. + * @return The new state. */ - void enqueueRequest(std::shared_ptr request, bool beforeConnected); + State monitorSharedQueueWhileWaiting( + State whileState, + std::chrono::time_point maxWakeTime = + std::chrono::time_point::max()); /** * Handle sending @c MessageRequests and pings while in @c State::POST_CONNECTING or @c State::CONNECTED. @@ -290,7 +310,7 @@ private: * @param whileState Continue sending @c MessageRequests and pings while in this state. * @return The current value of @c m_state. */ - State sendMessagesAndPings(State whileState); + State sendMessagesAndPings(State whileState, MessageRequestQueueInterface& requestQueue); /** * Set the state to a new state. @@ -342,6 +362,9 @@ private: */ State getState(); + /// The metric recorder. + std::shared_ptr m_metricRecorder; + /// Mutex for accessing @c m_state and @c m_messageQueue std::mutex m_mutex; @@ -369,6 +392,9 @@ private: /// Factory for creating @c PostConnectInterface instances. std::shared_ptr m_postConnectFactory; + /// Queue of @c MessageRequest instances to send that are shared between instances of @c HTTP2Transport. + std::shared_ptr m_sharedRequestQueue; + /// Mutex to protect access to the m_observers variable. std::mutex m_observerMutex; @@ -381,15 +407,12 @@ private: /// PostConnect object is used to perform activities required once a connection is established. std::shared_ptr m_postConnect; - /// Queue of @c MessageRequest instances to send. Serialized by @c m_mutex. - std::deque> m_requestQueue; + /// Queue of @c MessageRequest instances to send during the POST_CONNECTING state. Serialized by @c m_mutex. + MessageRequestQueue m_requestQueue; /// Number of times connecting has been retried. int m_connectRetryCount; - /// Is a message handler awaiting a response? - bool m_isMessageHandlerAwaitingResponse; - /// The number of message handlers that are not finished with their request. int m_countOfUnfinishedMessageHandlers; diff --git a/ACL/include/ACL/Transport/HTTP2TransportFactory.h b/ACL/include/ACL/Transport/HTTP2TransportFactory.h index 99148010..7fdfbda2 100644 --- a/ACL/include/ACL/Transport/HTTP2TransportFactory.h +++ b/ACL/include/ACL/Transport/HTTP2TransportFactory.h @@ -1,5 +1,5 @@ /* - * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018-2020 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. @@ -22,6 +22,7 @@ #include #include #include +#include #include "ACL/Transport/MessageConsumerInterface.h" #include "ACL/Transport/PostConnectFactoryInterface.h" @@ -41,10 +42,12 @@ public: * * @param connectionFactory Object used to create instances of HTTP2ConnectionInterface. * @param postConnectFactory Object used to create instances of the PostConnectInterface. + * @param metricRecorder The metric recorder. */ HTTP2TransportFactory( std::shared_ptr connectionFactory, - std::shared_ptr postConnectFactory); + std::shared_ptr postConnectFactory, + std::shared_ptr metricRecorder = nullptr); /// @name TransportFactoryInterface methods. /// @{ @@ -53,7 +56,8 @@ public: std::shared_ptr attachmentManager, const std::string& avsGateway, std::shared_ptr messageConsumerInterface, - std::shared_ptr transportObserverInterface) override; + std::shared_ptr transportObserverInterface, + std::shared_ptr sharedMessageRequestQueue) override; /// @} /** @@ -67,6 +71,9 @@ private: /// Save a pointer to the object used to create instances of the PostConnectInterface. std::shared_ptr m_postConnectFactory; + + /// The metric recorder. + std::shared_ptr m_metricRecorder; }; } // namespace acl diff --git a/ACL/include/ACL/Transport/MessageRequestHandler.h b/ACL/include/ACL/Transport/MessageRequestHandler.h index b47fc452..3ac9940a 100644 --- a/ACL/include/ACL/Transport/MessageRequestHandler.h +++ b/ACL/include/ACL/Transport/MessageRequestHandler.h @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2016-2020 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. @@ -18,9 +18,10 @@ #include -#include #include +#include #include +#include #include "ACL/Transport/ExchangeHandler.h" #include "ACL/Transport/MessageConsumerInterface.h" @@ -51,6 +52,7 @@ public: * @param messageRequest The MessageRequest to send. * @param messageConsumer Where to send messages. * @param attachmentManager Where to get attachments to write to. + * @param metricRecorder The metric recorder. * @return A new MessageRequestHandler or nullptr if the operation fails. */ static std::shared_ptr create( @@ -58,7 +60,8 @@ public: const std::string& authToken, std::shared_ptr messageRequest, std::shared_ptr messageConsumer, - std::shared_ptr attachmentManager); + std::shared_ptr attachmentManager, + std::shared_ptr metricRecorder); private: /** @@ -67,11 +70,13 @@ private: * @param context The ExchangeContext in which this MessageRequest handler will operate. * @param authToken The token to use to authorize the request. * @param messageRequest The MessageRequest to send. + * @param metricRecorder The metric recorder. */ MessageRequestHandler( std::shared_ptr context, const std::string& authToken, - std::shared_ptr messageRequest); + std::shared_ptr messageRequest, + std::shared_ptr metricRecorder); /** * Notify the associated HTTP2Transport instance that the message request failed or was acknowledged by AVS. @@ -116,13 +121,16 @@ private: /// Reader for current attachment (if any). std::shared_ptr m_namedReader; + /// The metric recorder. + std::shared_ptr m_metricRecorder; + /// Whether acknowledge of the @c MessageRequest was reported. bool m_wasMessageRequestAcknowledgeReported; /// Whether finish of the @c MessageRequest was reported. bool m_wasMessageRequestFinishedReported; - /// Response code received through @c onReciveResponseCode (or zero). + /// Response code received through @c onReceiveResponseCode (or zero). long m_responseCode; }; diff --git a/ACL/include/ACL/Transport/MessageRequestQueue.h b/ACL/include/ACL/Transport/MessageRequestQueue.h new file mode 100644 index 00000000..79f75125 --- /dev/null +++ b/ACL/include/ACL/Transport/MessageRequestQueue.h @@ -0,0 +1,87 @@ +/* + * Copyright 2019-2020 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 ALEXA_CLIENT_SDK_ACL_INCLUDE_ACL_TRANSPORT_MESSAGEREQUESTQUEUE_H_ +#define ALEXA_CLIENT_SDK_ACL_INCLUDE_ACL_TRANSPORT_MESSAGEREQUESTQUEUE_H_ + +#include +#include +#include +#include + +#include +#include + +#include "ACL/Transport/MessageRequestQueueInterface.h" + +namespace alexaClientSDK { +namespace acl { + +/** + * Class to manage @c MessageRequest send queues in HTTP2Transport. + * + * Note: This class is not thread safe. The user should ensure thread safety. + */ +class MessageRequestQueue : public MessageRequestQueueInterface { +public: + /// Helper structure to keep track of the SendQueue. + struct MessageRequestQueueStruct { + /// Indicates if the queue is waiting on a response. + bool isQueueWaitingForResponse; + + /// The queue used to send @c MessageRequest. + std::deque, + std::shared_ptr>> + queue; + + MessageRequestQueueStruct() : isQueueWaitingForResponse{false} { + } + }; + + /** + * Constructor. + */ + MessageRequestQueue(); + + /** + * Destructor. + */ + ~MessageRequestQueue() override; + + /// Override MessageRequestQueueInterface methods + /// @{ + void enqueueRequest(std::shared_ptr messageRequest) override; + avsCommon::utils::Optional> peekRequestTime() override; + std::shared_ptr dequeueRequest() override; + bool isMessageRequestAvailable() const override; + void setWaitingFlagForQueue() override; + void clearWaitingFlagForQueue() override; + bool empty() const override; + void clear() override; + /// @} + +private: + /// Member to keep track of the current @c MessageRequests present. + int m_size; + + /// The struct that contains the message queue + MessageRequestQueueStruct m_sendQueue; +}; + +} // namespace acl +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_ACL_INCLUDE_ACL_TRANSPORT_MESSAGEREQUESTQUEUE_H_ diff --git a/ACL/include/ACL/Transport/MessageRequestQueueInterface.h b/ACL/include/ACL/Transport/MessageRequestQueueInterface.h new file mode 100644 index 00000000..130d763d --- /dev/null +++ b/ACL/include/ACL/Transport/MessageRequestQueueInterface.h @@ -0,0 +1,101 @@ +/* + * Copyright 2019-2020 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 ALEXA_CLIENT_SDK_ACL_INCLUDE_ACL_TRANSPORT_MESSAGEREQUESTQUEUEINTERFACE_H_ +#define ALEXA_CLIENT_SDK_ACL_INCLUDE_ACL_TRANSPORT_MESSAGEREQUESTQUEUEINTERFACE_H_ + +#include +#include +#include +#include + +#include +#include + +namespace alexaClientSDK { +namespace acl { + +/** + * An interface that abstracts the operations of a @c MessageRequestQueuesMap between the standard version + * and the synchronized implementation. + */ +class MessageRequestQueueInterface { +public: + /** + * Destructor. + */ + virtual ~MessageRequestQueueInterface() = default; + + /** + * Enqueues the @c MessageRequest to the corresponding send queue based on the send queue type. + * If send queue type is not present, a new queue is created before enqueuing the request. + */ + virtual void enqueueRequest(std::shared_ptr messageRequest) = 0; + + /** + * Peek at the next item in the queue and retrieve the time that the request was queued. + * + * @return The time that the next request (if any) was queued. + */ + virtual avsCommon::utils::Optional> peekRequestTime() = 0; + + /** + * Dequeues the next available @c MessageRequest by taking into account if the queue is waiting for + * a response. This method keeps track of the queue being processed and once a request from the queue + * is dequeued moves it to the next queue. It also loops back to the starting queue at the end and exits + * the loop if a valid MessageRequest is found or if it reaches the place it started. + * + * @return @c MessageRequest if an available, else return nullptr. + */ + virtual std::shared_ptr dequeueRequest() = 0; + + /** + * This method checks if there is a @c MessageRequest available to be sent. + * + * @return true if @c MessageRequest is available to be sent, else false. + */ + virtual bool isMessageRequestAvailable() const = 0; + + /** + * Sets the waiting flag for the queue specified by the given send queue type. + * + * @param sendQueueType the send queue type of the queue to set the waiting flag on. + */ + virtual void setWaitingFlagForQueue() = 0; + + /** + * Clear the waiting flag for the queue specified by the given send queue type. + * + * @param sendQueueType the send queue type of the queue to clear the waiting flag on. + */ + virtual void clearWaitingFlagForQueue() = 0; + + /** + * Checks if there are any @c MessageRequests. + * + * @return true if there are no messageRequests in the queue, else false. + */ + virtual bool empty() const = 0; + + /** + * Clears all the @c MessageRequests along with the corresponding queues. + */ + virtual void clear() = 0; +}; + +} // namespace acl +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_ACL_INCLUDE_ACL_TRANSPORT_MESSAGEREQUESTQUEUEINTERFACE_H_ diff --git a/ACL/include/ACL/Transport/MessageRouter.h b/ACL/include/ACL/Transport/MessageRouter.h index 88eb2226..ce274fdd 100644 --- a/ACL/include/ACL/Transport/MessageRouter.h +++ b/ACL/include/ACL/Transport/MessageRouter.h @@ -33,6 +33,7 @@ #include "ACL/Transport/TransportFactoryInterface.h" #include "ACL/Transport/TransportInterface.h" #include "ACL/Transport/TransportObserverInterface.h" +#include "ACL/Transport/SynchronizedMessageRequestQueue.h" namespace alexaClientSDK { namespace acl { @@ -202,6 +203,9 @@ private: /// The transport factory. std::shared_ptr m_transportFactory; + /// The synchonized queue of messages to send that is shared between transports. + std::shared_ptr m_requestQueue; + protected: /** * Executor to perform asynchronous operations: diff --git a/ACL/include/ACL/Transport/SynchronizedMessageRequestQueue.h b/ACL/include/ACL/Transport/SynchronizedMessageRequestQueue.h new file mode 100644 index 00000000..f2057260 --- /dev/null +++ b/ACL/include/ACL/Transport/SynchronizedMessageRequestQueue.h @@ -0,0 +1,67 @@ +/* + * Copyright 2019-2020 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 ALEXA_CLIENT_SDK_ACL_INCLUDE_ACL_TRANSPORT_SYNCHRONIZEDMESSAGEREQUESTQUEUE_H_ +#define ALEXA_CLIENT_SDK_ACL_INCLUDE_ACL_TRANSPORT_SYNCHRONIZEDMESSAGEREQUESTQUEUE_H_ + +#include +#include +#include + +#include +#include +#include +#include "ACL/Transport/MessageRequestQueueInterface.h" + +namespace alexaClientSDK { +namespace acl { + +/** + * Class to manage @c MessageRequest send queue that is shared between instances of HTTP2Transport. + */ +class SynchronizedMessageRequestQueue : public MessageRequestQueueInterface { +public: + /** + * Constructor. + */ + SynchronizedMessageRequestQueue() = default; + + /** + * Destructor. + */ + ~SynchronizedMessageRequestQueue() override; + + /// Override MessageRequestQueueInterface methods + /// @{ + void enqueueRequest(std::shared_ptr messageRequest) override; + avsCommon::utils::Optional> peekRequestTime() override; + std::shared_ptr dequeueRequest() override; + bool isMessageRequestAvailable() const override; + void setWaitingFlagForQueue() override; + void clearWaitingFlagForQueue() override; + bool empty() const override; + void clear() override; + /// @} + +private: + mutable std::mutex m_mutex; + + MessageRequestQueue m_requestQueue; +}; + +} // namespace acl +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_ACL_INCLUDE_ACL_TRANSPORT_SYNCHRONIZEDMESSAGEREQUESTQUEUE_H_ diff --git a/ACL/include/ACL/Transport/TransportDefines.h b/ACL/include/ACL/Transport/TransportDefines.h index 843f0342..942bb130 100644 --- a/ACL/include/ACL/Transport/TransportDefines.h +++ b/ACL/include/ACL/Transport/TransportDefines.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -28,6 +28,11 @@ public: /// Retry Timer Object for transport. static avsCommon::utils::RetryTimer RETRY_TIMER; + + /// Static function member to get RETRY_TIMER + static avsCommon::utils::RetryTimer getRetryTimer() { + return RETRY_TIMER; + } }; } // namespace acl diff --git a/ACL/include/ACL/Transport/TransportFactoryInterface.h b/ACL/include/ACL/Transport/TransportFactoryInterface.h index db86d71b..eadbf0d6 100644 --- a/ACL/include/ACL/Transport/TransportFactoryInterface.h +++ b/ACL/include/ACL/Transport/TransportFactoryInterface.h @@ -1,5 +1,5 @@ /* - * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018-2020 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. @@ -25,6 +25,7 @@ #include "ACL/Transport/TransportInterface.h" #include "ACL/Transport/MessageConsumerInterface.h" #include "ACL/Transport/TransportObserverInterface.h" +#include "ACL/Transport/SynchronizedMessageRequestQueue.h" namespace alexaClientSDK { namespace acl { @@ -38,9 +39,11 @@ public: * Creates a new transport. * * @param authDelegate The AuthDelegateInterface to use for authentication and authorization with AVS. + * @param attachmentManager The attachment manager that manages the attachments. * @param avsGateway The URL for the AVS server we will connect to. * @param messageConsumerInterface The object which should be notified on messages which arrive from AVS. * @param transportObserverInterface A pointer to the transport observer the new transport should notify. + * @param sharedRequestQueue Request queue shared by all instances of TransportInterface. * @return A new MessageRouter object. */ virtual std::shared_ptr createTransport( @@ -48,7 +51,8 @@ public: std::shared_ptr attachmentManager, const std::string& avsGateway, std::shared_ptr messageConsumerInterface, - std::shared_ptr transportObserverInterface) = 0; + std::shared_ptr transportObserverInterface, + std::shared_ptr sharedMessageRequestQueue) = 0; /** * Destructor. diff --git a/ACL/include/ACL/Transport/TransportInterface.h b/ACL/include/ACL/Transport/TransportInterface.h index 7dd0ee64..36175fee 100644 --- a/ACL/include/ACL/Transport/TransportInterface.h +++ b/ACL/include/ACL/Transport/TransportInterface.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-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. @@ -58,11 +58,9 @@ public: virtual bool isConnected() = 0; /** - * Sends an message request. This call blocks until the message can be sent. - * - * @param request The requested message. + * A message request has been added to the shared synchronized queue and is ready to be read. */ - virtual void send(std::shared_ptr request) = 0; + virtual void onRequestEnqueued() = 0; /** * Deleted copy constructor diff --git a/ACL/src/AVSConnectionManager.cpp b/ACL/src/AVSConnectionManager.cpp index c9d16f3d..746b620f 100644 --- a/ACL/src/AVSConnectionManager.cpp +++ b/ACL/src/AVSConnectionManager.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. diff --git a/ACL/src/Transport/DownchannelHandler.cpp b/ACL/src/Transport/DownchannelHandler.cpp index dbded473..6f48294e 100644 --- a/ACL/src/Transport/DownchannelHandler.cpp +++ b/ACL/src/Transport/DownchannelHandler.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018-2020 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. @@ -119,6 +119,7 @@ bool DownchannelHandler::onReceiveResponseCode(long responseCode) { case HTTPResponseCode::REDIRECTION_PERMANENT_REDIRECT: case HTTPResponseCode::CLIENT_ERROR_BAD_REQUEST: case HTTPResponseCode::SERVER_ERROR_INTERNAL: + case HTTPResponseCode::SERVER_ERROR_NOT_IMPLEMENTED: break; case HTTPResponseCode::SUCCESS_OK: m_context->onDownchannelConnected(); diff --git a/ACL/src/Transport/HTTP2Transport.cpp b/ACL/src/Transport/HTTP2Transport.cpp index 83a354d7..1498a110 100644 --- a/ACL/src/Transport/HTTP2Transport.cpp +++ b/ACL/src/Transport/HTTP2Transport.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2016-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2016-2020 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. @@ -13,6 +13,7 @@ * permissions and limitations under the License. */ +#include #include #include #include @@ -59,6 +60,9 @@ static const int MAX_MESSAGE_HANDLERS = MAX_STREAMS - 2; /// Timeout to send a ping to AVS if there has not been any other acitivity on the connection. static std::chrono::minutes INACTIVITY_TIMEOUT{5}; +/// Max time a @c MessageRequest should linger unprocessed before it should be consider TIMEDOUT. +static const std::chrono::seconds MESSAGE_QUEUE_TIMEOUT = std::chrono::seconds(15); + /** * Write a @c HTTP2Transport::State value to an @c ostream as a string. * @@ -101,7 +105,9 @@ std::shared_ptr HTTP2Transport::create( std::shared_ptr attachmentManager, std::shared_ptr transportObserver, std::shared_ptr postConnectFactory, - Configuration configuration) { + std::shared_ptr sharedRequestQueue, + Configuration configuration, + std::shared_ptr metricRecorder) { ACSDK_DEBUG5(LX(__func__) .d("authDelegate", authDelegate.get()) .d("avsGateway", avsGateway) @@ -109,7 +115,8 @@ std::shared_ptr HTTP2Transport::create( .d("messageConsumer", messageConsumer.get()) .d("attachmentManager", attachmentManager.get()) .d("transportObserver", transportObserver.get()) - .d("postConnectFactory", postConnectFactory.get())); + .d("postConnectFactory", postConnectFactory.get()) + .d("sharedRequestQueue", sharedRequestQueue.get())); if (!authDelegate) { ACSDK_ERROR(LX("createFailed").d("reason", "nullAuthDelegate")); @@ -141,15 +148,22 @@ std::shared_ptr HTTP2Transport::create( return nullptr; } + if (!sharedRequestQueue) { + ACSDK_ERROR(LX("createFailed").d("reason", "nullSharedRequestQueue")); + return nullptr; + } + auto transport = std::shared_ptr(new HTTP2Transport( - authDelegate, + std::move(authDelegate), avsGateway, - http2Connection, - messageConsumer, - attachmentManager, - transportObserver, - postConnectFactory, - configuration)); + std::move(http2Connection), + std::move(messageConsumer), + std::move(attachmentManager), + std::move(transportObserver), + std::move(postConnectFactory), + std::move(sharedRequestQueue), + configuration, + std::move(metricRecorder))); return transport; } @@ -162,28 +176,23 @@ HTTP2Transport::HTTP2Transport( std::shared_ptr attachmentManager, std::shared_ptr transportObserver, std::shared_ptr postConnectFactory, - Configuration configuration) : + std::shared_ptr sharedRequestQueue, + Configuration configuration, + std::shared_ptr metricRecorder) : + m_metricRecorder{std::move(metricRecorder)}, m_state{State::INIT}, - m_authDelegate{authDelegate}, + m_authDelegate{std::move(authDelegate)}, m_avsGateway{avsGateway}, - m_http2Connection{http2Connection}, - m_messageConsumer{messageConsumer}, - m_attachmentManager{attachmentManager}, - m_postConnectFactory{postConnectFactory}, + m_http2Connection{std::move(http2Connection)}, + m_messageConsumer{std::move(messageConsumer)}, + m_attachmentManager{std::move(attachmentManager)}, + m_postConnectFactory{std::move(postConnectFactory)}, + m_sharedRequestQueue{std::move(sharedRequestQueue)}, m_connectRetryCount{0}, - m_isMessageHandlerAwaitingResponse{false}, m_countOfUnfinishedMessageHandlers{0}, m_postConnected{false}, m_configuration{configuration}, m_disconnectReason{ConnectionStatusObserverInterface::ChangedReason::NONE} { - ACSDK_DEBUG7(LX(__func__) - .d("authDelegate", authDelegate.get()) - .d("avsGateway", avsGateway) - .d("http2Connection", http2Connection.get()) - .d("messageConsumer", messageConsumer.get()) - .d("attachmentManager", attachmentManager.get()) - .d("transportObserver", transportObserver.get()) - .d("postConnectFactory", postConnectFactory.get())); m_observers.insert(transportObserver); } @@ -250,14 +259,43 @@ bool HTTP2Transport::isConnected() { return State::CONNECTED == m_state; } -void HTTP2Transport::send(std::shared_ptr request) { +void HTTP2Transport::onRequestEnqueued() { ACSDK_DEBUG7(LX(__func__)); - enqueueRequest(request, false); + std::lock_guard lock(m_mutex); + m_wakeEvent.notify_all(); } void HTTP2Transport::sendPostConnectMessage(std::shared_ptr request) { ACSDK_DEBUG7(LX(__func__)); - enqueueRequest(request, true); + if (!request) { + ACSDK_ERROR(LX("enqueueRequestFailed").d("reason", "nullRequest")); + } + std::unique_lock lock(m_mutex); + bool allowed = false; + switch (m_state) { + case State::INIT: + case State::AUTHORIZING: + case State::CONNECTING: + case State::WAITING_TO_RETRY_CONNECTING: + case State::POST_CONNECTING: + allowed = true; + break; + case State::CONNECTED: + case State::SERVER_SIDE_DISCONNECT: + case State::DISCONNECTING: + case State::SHUTDOWN: + allowed = false; + break; + } + + if (allowed) { + m_requestQueue.enqueueRequest(request); + m_wakeEvent.notify_all(); + } else { + ACSDK_ERROR(LX("enqueueRequestFailed").d("reason", "notInAllowedState").d("m_state", m_state)); + lock.unlock(); + request->sendCompleted(MessageRequestObserverInterface::Status::NOT_CONNECTED); + } } void HTTP2Transport::onPostConnected() { @@ -398,7 +436,7 @@ void HTTP2Transport::onDownchannelFinished() { void HTTP2Transport::onMessageRequestSent() { std::lock_guard lock(m_mutex); - m_isMessageHandlerAwaitingResponse = true; + m_sharedRequestQueue->setWaitingFlagForQueue(); m_countOfUnfinishedMessageHandlers++; ACSDK_DEBUG7(LX(__func__).d("countOfUnfinishedMessageHandlers", m_countOfUnfinishedMessageHandlers)); } @@ -415,7 +453,7 @@ void HTTP2Transport::onMessageRequestTimeout() { void HTTP2Transport::onMessageRequestAcknowledged() { ACSDK_DEBUG7(LX(__func__)); std::lock_guard lock(m_mutex); - m_isMessageHandlerAwaitingResponse = false; + m_sharedRequestQueue->clearWaitingFlagForQueue(); m_wakeEvent.notify_all(); } @@ -465,8 +503,13 @@ std::string HTTP2Transport::getAVSGateway() { return m_avsGateway; } +void HTTP2Transport::onGoawayReceived() { + ACSDK_DEBUG5(LX(__func__)); +} + void HTTP2Transport::mainLoop() { ACSDK_DEBUG7(LX(__func__)); + m_http2Connection->addObserver(shared_from_this()); m_postConnect = m_postConnectFactory->createPostConnect(); if (!m_postConnect || !m_postConnect->doPostConnect(shared_from_this(), shared_from_this())) { @@ -526,55 +569,46 @@ HTTP2Transport::State HTTP2Transport::handleAuthorizing() { m_authDelegate->addAuthObserver(shared_from_this()); - std::unique_lock lock(m_mutex); - m_wakeEvent.wait(lock, [this]() { return m_state != State::AUTHORIZING; }); - return m_state; + return monitorSharedQueueWhileWaiting(State::AUTHORIZING); } HTTP2Transport::State HTTP2Transport::handleConnecting() { ACSDK_DEBUG5(LX(__func__)); - std::unique_lock lock(m_mutex); + auto authToken = m_authDelegate->getAuthToken(); - while (State::CONNECTING == m_state) { - lock.unlock(); - - auto authToken = m_authDelegate->getAuthToken(); - - if (authToken.empty()) { - setState( - State::WAITING_TO_RETRY_CONNECTING, ConnectionStatusObserverInterface::ChangedReason::INVALID_AUTH); - break; - } - - auto downchannelHandler = - DownchannelHandler::create(shared_from_this(), authToken, m_messageConsumer, m_attachmentManager); - lock.lock(); - - if (!downchannelHandler) { - ACSDK_ERROR(LX("handleConnectingFailed").d("reason", "createDownchannelHandlerFailed")); - setStateLocked( - State::WAITING_TO_RETRY_CONNECTING, ConnectionStatusObserverInterface::ChangedReason::INTERNAL_ERROR); - return m_state; - } - - while (State::CONNECTING == m_state) { - m_wakeEvent.wait(lock); - } + if (authToken.empty()) { + ACSDK_DEBUG0(LX("Empty AuthToken")); + std::lock_guard lock(m_mutex); + setStateLocked( + State::WAITING_TO_RETRY_CONNECTING, ConnectionStatusObserverInterface::ChangedReason::INVALID_AUTH); + return m_state; } - return m_state; + auto downchannelHandler = + DownchannelHandler::create(shared_from_this(), authToken, m_messageConsumer, m_attachmentManager); + + if (!downchannelHandler) { + ACSDK_ERROR(LX("handleConnectingFailed").d("reason", "createDownchannelHandlerFailed")); + std::lock_guard lock(m_mutex); + setStateLocked( + State::WAITING_TO_RETRY_CONNECTING, ConnectionStatusObserverInterface::ChangedReason::INTERNAL_ERROR); + return m_state; + } + + return monitorSharedQueueWhileWaiting(State::CONNECTING); } HTTP2Transport::State HTTP2Transport::handleWaitingToRetryConnecting() { - ACSDK_DEBUG7(LX(__func__)); - - std::chrono::milliseconds timeout = TransportDefines::RETRY_TIMER.calculateTimeToRetry(m_connectRetryCount); + auto timeout = TransportDefines::getRetryTimer().calculateTimeToRetry(m_connectRetryCount); ACSDK_DEBUG7( LX("handleConnectingWaitingToRetry").d("connectRetryCount", m_connectRetryCount).d("timeout", timeout.count())); m_connectRetryCount++; - std::unique_lock lock(m_mutex); - m_wakeEvent.wait_for(lock, timeout, [this] { return m_state != State::WAITING_TO_RETRY_CONNECTING; }); + + auto wakeTime = std::chrono::steady_clock::now() + timeout; + monitorSharedQueueWhileWaiting(State::WAITING_TO_RETRY_CONNECTING, wakeTime); + + std::lock_guard lock(m_mutex); if (State::WAITING_TO_RETRY_CONNECTING == m_state) { setStateLocked(State::CONNECTING, ConnectionStatusObserverInterface::ChangedReason::NONE); } @@ -587,7 +621,7 @@ HTTP2Transport::State HTTP2Transport::handlePostConnecting() { setState(State::CONNECTED, ConnectionStatusObserverInterface::ChangedReason::SUCCESS); return State::CONNECTED; } - return sendMessagesAndPings(State::POST_CONNECTING); + return sendMessagesAndPings(State::POST_CONNECTING, m_requestQueue); } HTTP2Transport::State HTTP2Transport::handleConnected() { @@ -596,7 +630,7 @@ HTTP2Transport::State HTTP2Transport::handleConnected() { m_postConnect.reset(); } notifyObserversOnConnected(); - return sendMessagesAndPings(State::CONNECTED); + return sendMessagesAndPings(State::CONNECTED, *m_sharedRequestQueue); } HTTP2Transport::State HTTP2Transport::handleServerSideDisconnect() { @@ -621,12 +655,19 @@ HTTP2Transport::State HTTP2Transport::handleShutdown() { { std::lock_guard lock(m_mutex); - for (auto request : m_requestQueue) { - request->sendCompleted(MessageRequestObserverInterface::Status::NOT_CONNECTED); + + // Flags are stored in the shared queue but the local request queue is drained. + m_sharedRequestQueue->clearWaitingFlagForQueue(); + while (!m_requestQueue.empty()) { + auto request = m_requestQueue.dequeueRequest(); + if (request != nullptr) { + request->sendCompleted(MessageRequestObserverInterface::Status::NOT_CONNECTED); + } } m_requestQueue.clear(); } + m_http2Connection->removeObserver(shared_from_this()); m_http2Connection->disconnect(); notifyObserversOnDisconnect(m_disconnectReason); @@ -634,51 +675,53 @@ HTTP2Transport::State HTTP2Transport::handleShutdown() { return State::SHUTDOWN; } -void HTTP2Transport::enqueueRequest(std::shared_ptr request, bool beforeConnected) { - ACSDK_DEBUG7(LX(__func__).d("beforeConnected", beforeConnected)); +HTTP2Transport::State HTTP2Transport::monitorSharedQueueWhileWaiting( + alexaClientSDK::acl::HTTP2Transport::State whileState, + std::chrono::time_point maxWakeTime) { + while (true) { + auto wakeTime = maxWakeTime; - if (!request) { - ACSDK_ERROR(LX("enqueueRequestFailed").d("reason", "nullRequest")); - } - std::unique_lock lock(m_mutex); - bool allowed = false; - switch (m_state) { - case State::INIT: - case State::AUTHORIZING: - case State::CONNECTING: - case State::WAITING_TO_RETRY_CONNECTING: - case State::POST_CONNECTING: - allowed = beforeConnected; - break; - case State::CONNECTED: - allowed = !beforeConnected; - break; - case State::SERVER_SIDE_DISCONNECT: - case State::DISCONNECTING: - case State::SHUTDOWN: - allowed = false; - break; - } + while (true) { + auto messageRequestTime = m_sharedRequestQueue->peekRequestTime(); + if (!messageRequestTime.hasValue()) { + // No more messages queued, break out to wait to connect. + break; + } - if (allowed) { - m_requestQueue.push_back(request); - m_wakeEvent.notify_all(); - } else { - lock.unlock(); - ACSDK_ERROR(LX("enqueueRequestFailed").d("reason", "notInAllowedState").d("m_state", m_state)); - request->sendCompleted(MessageRequestObserverInterface::Status::NOT_CONNECTED); + auto messageTimeoutTime = messageRequestTime.value() + MESSAGE_QUEUE_TIMEOUT; + if (messageTimeoutTime > std::chrono::steady_clock::now()) { + // Next message has not timed out, break out to wait to connect. + wakeTime = std::min(messageTimeoutTime, wakeTime); + break; + } + + auto request = m_sharedRequestQueue->dequeueRequest(); + request->sendCompleted(MessageRequestObserverInterface::Status::TIMEDOUT); + } + + auto messageRequestTime = m_sharedRequestQueue->peekRequestTime(); + + std::unique_lock lock(m_mutex); + m_wakeEvent.wait_until(lock, wakeTime, [this, whileState, messageRequestTime] { + return m_state != whileState || m_sharedRequestQueue->peekRequestTime() != messageRequestTime; + }); + + if (whileState != m_state || std::chrono::steady_clock::now() >= maxWakeTime) { + return m_state; + } } } -HTTP2Transport::State HTTP2Transport::sendMessagesAndPings(alexaClientSDK::acl::HTTP2Transport::State whileState) { +HTTP2Transport::State HTTP2Transport::sendMessagesAndPings( + alexaClientSDK::acl::HTTP2Transport::State whileState, + MessageRequestQueueInterface& requestQueue) { ACSDK_DEBUG7(LX(__func__).d("whileState", whileState)); std::unique_lock lock(m_mutex); - auto canSendMessage = [this] { + auto canSendMessage = [this, &requestQueue] { return ( - !m_isMessageHandlerAwaitingResponse && !m_requestQueue.empty() && - (m_countOfUnfinishedMessageHandlers < MAX_MESSAGE_HANDLERS)); + requestQueue.isMessageRequestAvailable() && (m_countOfUnfinishedMessageHandlers < MAX_MESSAGE_HANDLERS)); }; auto wakePredicate = [this, whileState, canSendMessage] { @@ -702,15 +745,19 @@ HTTP2Transport::State HTTP2Transport::sendMessagesAndPings(alexaClientSDK::acl:: } if (canSendMessage()) { - auto messageRequest = m_requestQueue.front(); - m_requestQueue.pop_front(); + auto messageRequest = requestQueue.dequeueRequest(); lock.unlock(); auto authToken = m_authDelegate->getAuthToken(); if (!authToken.empty()) { auto handler = MessageRequestHandler::create( - shared_from_this(), authToken, messageRequest, m_messageConsumer, m_attachmentManager); + shared_from_this(), + authToken, + messageRequest, + m_messageConsumer, + m_attachmentManager, + m_metricRecorder); if (!handler) { messageRequest->sendCompleted(MessageRequestObserverInterface::Status::INTERNAL_ERROR); } diff --git a/ACL/src/Transport/HTTP2TransportFactory.cpp b/ACL/src/Transport/HTTP2TransportFactory.cpp index d7a98600..be083293 100644 --- a/ACL/src/Transport/HTTP2TransportFactory.cpp +++ b/ACL/src/Transport/HTTP2TransportFactory.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -31,7 +31,8 @@ std::shared_ptr HTTP2TransportFactory::createTransport( std::shared_ptr attachmentManager, const std::string& avsGateway, std::shared_ptr messageConsumerInterface, - std::shared_ptr transportObserverInterface) { + std::shared_ptr transportObserverInterface, + std::shared_ptr sharedMessageRequestQueue) { auto connection = m_connectionFactory->createHTTP2Connection(); if (!connection) { return nullptr; @@ -44,14 +45,19 @@ std::shared_ptr HTTP2TransportFactory::createTransport( messageConsumerInterface, attachmentManager, transportObserverInterface, - m_postConnectFactory); + m_postConnectFactory, + sharedMessageRequestQueue, + HTTP2Transport::Configuration(), + m_metricRecorder); } HTTP2TransportFactory::HTTP2TransportFactory( std::shared_ptr connectionFactory, - std::shared_ptr postConnectFactory) : - m_connectionFactory{connectionFactory}, - m_postConnectFactory{postConnectFactory} { + std::shared_ptr postConnectFactory, + std::shared_ptr metricRecorder) : + m_connectionFactory{std::move(connectionFactory)}, + m_postConnectFactory{std::move(postConnectFactory)}, + m_metricRecorder{std::move(metricRecorder)} { } } // namespace acl diff --git a/ACL/src/Transport/MessageRequestHandler.cpp b/ACL/src/Transport/MessageRequestHandler.cpp index 59cfe79a..8aa66ba0 100644 --- a/ACL/src/Transport/MessageRequestHandler.cpp +++ b/ACL/src/Transport/MessageRequestHandler.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018-2020 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. @@ -21,6 +21,9 @@ #include #include #include +#include +#include +#include #include "ACL/Transport/HTTP2Transport.h" #include "ACL/Transport/MimeResponseSink.h" @@ -33,6 +36,7 @@ using namespace avsCommon::avs::attachment; using namespace avsCommon::sdkInterfaces; using namespace avsCommon::utils::http; using namespace avsCommon::utils::http2; +using namespace avsCommon::utils::metrics; /// URL to send events to const static std::string AVS_EVENT_URL_PATH_EXTENSION = "/v20160207/events"; @@ -63,6 +67,24 @@ static const std::string MESSAGEREQUEST_ID_PREFIX = "AVSEvent-"; /// String to identify log entries originating from this file. static const std::string TAG("MessageRequestHandler"); +/// Prefix used to identify metrics published by this module. +static const std::string ACL_METRIC_SOURCE_PREFIX = "ACL-"; + +/// Metric identifier for send mime data error +static const std::string SEND_DATA_ERROR = "ERROR.SEND_DATA_ERROR"; + +/// Read status tag +static const std::string READ_STATUS_TAG = "READ_STATUS"; + +/// Read overrun error +static const std::string ERROR_READ_OVERRUN = "READ_OVERRUN"; + +/// Internal error +static const std::string ERROR_INTERNAL = "INTERNAL_ERROR"; + +/// Send completed +static const std::string SEND_COMPLETED = "SEND_COMPLETED"; + /** * Create a LogEntry using this file's TAG and the specified event string. * @@ -70,6 +92,26 @@ static const std::string TAG("MessageRequestHandler"); */ #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) +/** + * Capture metric for the last send data result. + * + * @param metricRecorder The metric recorder object. + * @param count Number of errors. + * @param readStatus The read status. + */ +static void collectSendDataResultMetric( + const std::shared_ptr& metricRecorder, + int count, + const std::string& readStatus) { + recordMetric( + metricRecorder, + MetricEventBuilder{} + .setActivityName(ACL_METRIC_SOURCE_PREFIX + SEND_DATA_ERROR) + .addDataPoint(DataPointCounterBuilder{}.setName(SEND_DATA_ERROR).increment(count).build()) + .addDataPoint(DataPointStringBuilder{}.setName(READ_STATUS_TAG).setValue(readStatus).build()) + .build()); +} + MessageRequestHandler::~MessageRequestHandler() { reportMessageRequestAcknowledged(); reportMessageRequestFinished(); @@ -80,7 +122,8 @@ std::shared_ptr MessageRequestHandler::create( const std::string& authToken, std::shared_ptr messageRequest, std::shared_ptr messageConsumer, - std::shared_ptr attachmentManager) { + std::shared_ptr attachmentManager, + std::shared_ptr metricRecorder) { ACSDK_DEBUG7(LX(__func__).d("context", context.get()).d("messageRequest", messageRequest.get())); if (!context) { @@ -93,7 +136,8 @@ std::shared_ptr MessageRequestHandler::create( return nullptr; } - std::shared_ptr handler(new MessageRequestHandler(context, authToken, messageRequest)); + std::shared_ptr handler( + new MessageRequestHandler(context, authToken, messageRequest, std::move(metricRecorder))); // Allow custom path extension, if provided by the sender of the MessageRequest @@ -126,13 +170,15 @@ std::shared_ptr MessageRequestHandler::create( MessageRequestHandler::MessageRequestHandler( std::shared_ptr context, const std::string& authToken, - std::shared_ptr messageRequest) : + std::shared_ptr messageRequest, + std::shared_ptr metricRecorder) : ExchangeHandler{context, authToken}, m_messageRequest{messageRequest}, m_json{messageRequest->getJsonContent()}, m_jsonNext{m_json.c_str()}, m_countOfJsonBytesLeft{m_json.size()}, m_countOfPartsSent{0}, + m_metricRecorder{metricRecorder}, m_wasMessageRequestAcknowledgeReported{false}, m_wasMessageRequestFinishedReported{false}, m_responseCode{0} { @@ -219,11 +265,17 @@ HTTP2SendDataResult MessageRequestHandler::onSendMimePartData(char* bytes, size_ // Stream consumed. Move on to next part. m_namedReader.reset(); m_countOfPartsSent++; + collectSendDataResultMetric(m_metricRecorder, 0, SEND_COMPLETED); return HTTP2SendDataResult::COMPLETE; // Handle any attachment read errors. case AttachmentReader::ReadStatus::ERROR_OVERRUN: + collectSendDataResultMetric(m_metricRecorder, 1, ERROR_READ_OVERRUN); + // Stream failure. Abort sending the request. + return HTTP2SendDataResult::ABORT; + case AttachmentReader::ReadStatus::ERROR_INTERNAL: + collectSendDataResultMetric(m_metricRecorder, 1, ERROR_INTERNAL); // Stream failure. Abort sending the request. return HTTP2SendDataResult::ABORT; diff --git a/ACL/src/Transport/MessageRequestQueue.cpp b/ACL/src/Transport/MessageRequestQueue.cpp new file mode 100644 index 00000000..f68493de --- /dev/null +++ b/ACL/src/Transport/MessageRequestQueue.cpp @@ -0,0 +1,96 @@ +/* + * Copyright 2019-2020 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 +#include + +#include "ACL/Transport/MessageRequestQueue.h" + +namespace alexaClientSDK { +namespace acl { + +using namespace avsCommon::avs; + +/// String to identify log entries originating from this file. +static const std::string TAG("MessageRequestQueue"); + +static const std::string EMPTY_QUEUE_NAME = ""; + +/** + * 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) + +MessageRequestQueue::MessageRequestQueue() : m_size{0} { +} + +MessageRequestQueue::~MessageRequestQueue() { + clearWaitingFlagForQueue(); + clear(); +} + +void MessageRequestQueue::enqueueRequest(std::shared_ptr messageRequest) { + if (messageRequest != nullptr) { + m_sendQueue.queue.push_back({std::chrono::steady_clock::now(), messageRequest}); + m_size++; + } else { + ACSDK_ERROR(LX("enqueueRequest").d("reason", "nullMessageRequest")); + } +} + +avsCommon::utils::Optional> MessageRequestQueue::peekRequestTime() { + if (m_size > 0) { + return m_sendQueue.queue.front().first; + } + + return avsCommon::utils::Optional>(); +} + +std::shared_ptr MessageRequestQueue::dequeueRequest() { + std::shared_ptr messageRequest; + + if (m_size > 0) { + messageRequest = m_sendQueue.queue.front().second; + m_sendQueue.queue.pop_front(); + m_size--; + } + return messageRequest; +} + +bool MessageRequestQueue::isMessageRequestAvailable() const { + return !m_sendQueue.queue.empty() && !m_sendQueue.isQueueWaitingForResponse; +} + +void MessageRequestQueue::setWaitingFlagForQueue() { + m_sendQueue.isQueueWaitingForResponse = true; +} + +void MessageRequestQueue::clearWaitingFlagForQueue() { + m_sendQueue.isQueueWaitingForResponse = false; +} + +bool MessageRequestQueue::empty() const { + return (0 == m_size); +} + +void MessageRequestQueue::clear() { + m_sendQueue.queue.clear(); + m_size = 0; +} + +} // namespace acl +} // namespace alexaClientSDK diff --git a/ACL/src/Transport/MessageRouter.cpp b/ACL/src/Transport/MessageRouter.cpp index f410a527..05cabfc7 100644 --- a/ACL/src/Transport/MessageRouter.cpp +++ b/ACL/src/Transport/MessageRouter.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2016-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2016-2020 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. @@ -52,7 +52,8 @@ MessageRouter::MessageRouter( m_connectionReason{ConnectionStatusObserverInterface::ChangedReason::ACL_CLIENT_REQUEST}, m_isEnabled{false}, m_attachmentManager{attachmentManager}, - m_transportFactory{transportFactory} { + m_transportFactory{transportFactory}, + m_requestQueue{std::make_shared()} { } MessageRouterInterface::ConnectionStatus MessageRouter::getConnectionStatus() { @@ -82,6 +83,20 @@ void MessageRouter::enable() { void MessageRouter::doShutdown() { disable(); + + // The above call will release all the transports. If m_requestQueue is non-empty once all of the transports + // have been released, any outstanding MessageRequest instances must receive an onCompleted(NOT_CONNECTED) + // notification. + std::unique_lock lock{m_connectionMutex}; + if (!m_requestQueue->empty()) { + auto request = m_requestQueue->dequeueRequest(); + if (request != nullptr) { + request->sendCompleted(MessageRequestObserverInterface::Status::NOT_CONNECTED); + } + m_requestQueue->clear(); + } + lock.unlock(); + m_executor.shutdown(); } @@ -98,7 +113,8 @@ void MessageRouter::sendMessage(std::shared_ptr request) { } std::unique_lock lock{m_connectionMutex}; if (m_activeTransport) { - m_activeTransport->send(request); + m_requestQueue->enqueueRequest(request); + m_activeTransport->onRequestEnqueued(); } else { ACSDK_ERROR(LX("sendFailed").d("reason", "noActiveTransport")); request->sendCompleted(MessageRequestObserverInterface::Status::NOT_CONNECTED); @@ -240,7 +256,7 @@ void MessageRouter::notifyObserverOnReceive(const std::string& contextId, const void MessageRouter::createActiveTransportLocked() { auto transport = m_transportFactory->createTransport( - m_authDelegate, m_attachmentManager, m_avsGateway, shared_from_this(), shared_from_this()); + m_authDelegate, m_attachmentManager, m_avsGateway, shared_from_this(), shared_from_this(), m_requestQueue); if (transport && transport->connect()) { m_transports.push_back(transport); m_activeTransport = transport; diff --git a/ACL/src/Transport/SynchronizedMessageRequestQueue.cpp b/ACL/src/Transport/SynchronizedMessageRequestQueue.cpp new file mode 100644 index 00000000..4d01228b --- /dev/null +++ b/ACL/src/Transport/SynchronizedMessageRequestQueue.cpp @@ -0,0 +1,83 @@ +/* + * Copyright 2019-2020 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 +#include + +#include "ACL/Transport/SynchronizedMessageRequestQueue.h" + +namespace alexaClientSDK { +namespace acl { + +using namespace avsCommon::avs; + +/// String to identify log entries originating from this file. +static const std::string TAG("SynchronizedMessageRequestQueue"); + +/** + * 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) + +SynchronizedMessageRequestQueue::~SynchronizedMessageRequestQueue() { + clearWaitingFlagForQueue(); + clear(); +} + +void SynchronizedMessageRequestQueue::enqueueRequest(std::shared_ptr messageRequest) { + std::lock_guard lock{m_mutex}; + m_requestQueue.enqueueRequest(std::move(messageRequest)); +} + +avsCommon::utils::Optional> SynchronizedMessageRequestQueue:: + peekRequestTime() { + std::lock_guard lock{m_mutex}; + return m_requestQueue.peekRequestTime(); +} + +std::shared_ptr SynchronizedMessageRequestQueue::dequeueRequest() { + std::lock_guard lock{m_mutex}; + return m_requestQueue.dequeueRequest(); +} + +bool SynchronizedMessageRequestQueue::isMessageRequestAvailable() const { + std::lock_guard lock{m_mutex}; + return m_requestQueue.isMessageRequestAvailable(); +} + +void SynchronizedMessageRequestQueue::setWaitingFlagForQueue() { + std::lock_guard lock{m_mutex}; + m_requestQueue.setWaitingFlagForQueue(); +} + +void SynchronizedMessageRequestQueue::clearWaitingFlagForQueue() { + std::lock_guard lock{m_mutex}; + m_requestQueue.clearWaitingFlagForQueue(); +} + +bool SynchronizedMessageRequestQueue::empty() const { + std::lock_guard lock{m_mutex}; + return m_requestQueue.empty(); +} + +void SynchronizedMessageRequestQueue::clear() { + std::lock_guard lock{m_mutex}; + m_requestQueue.clear(); +} + +} // namespace acl +} // namespace alexaClientSDK diff --git a/ACL/src/Transport/TransportDefines.cpp b/ACL/src/Transport/TransportDefines.cpp index 3cb207d6..ab4384cc 100644 --- a/ACL/src/Transport/TransportDefines.cpp +++ b/ACL/src/Transport/TransportDefines.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-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. diff --git a/ACL/test/AVSConnectionManagerTest.cpp b/ACL/test/AVSConnectionManagerTest.cpp index c0629def..15936794 100644 --- a/ACL/test/AVSConnectionManagerTest.cpp +++ b/ACL/test/AVSConnectionManagerTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. diff --git a/ACL/test/CMakeLists.txt b/ACL/test/CMakeLists.txt index eef610ec..c94ffece 100644 --- a/ACL/test/CMakeLists.txt +++ b/ACL/test/CMakeLists.txt @@ -2,9 +2,9 @@ add_subdirectory("Transport") set(LIBRARIES ACL ${CMAKE_THREAD_LIBS_INIT} ACLTransportCommonTestLib) set(INCLUDE_PATH - ${AVSCommon_INCLUDE_DIRS} + "${AVSCommon_INCLUDE_DIRS}" "${ACL_SOURCE_DIR}/include" "${AVSCommon_SOURCE_DIR}/AVS/test" "${AVSCommon_SOURCE_DIR}/SDKInterfaces/test" "${AVSCommon_SOURCE_DIR}/Utils/test") -discover_unit_tests( "${INCLUDE_PATH}" "${LIBRARIES}") +discover_unit_tests("${INCLUDE_PATH}" "${LIBRARIES}") diff --git a/ACL/test/Transport/Common/CMakeLists.txt b/ACL/test/Transport/Common/CMakeLists.txt index 5290a2b4..977ca20a 100644 --- a/ACL/test/Transport/Common/CMakeLists.txt +++ b/ACL/test/Transport/Common/CMakeLists.txt @@ -5,7 +5,8 @@ add_library(ACLTransportCommonTestLib MockHTTP2Request.cpp MockMimeResponseSink.cpp) target_include_directories(ACLTransportCommonTestLib PUBLIC - "${ACL_SOURCE_DIR}/include" "${ACL_SOURCE_DIR}/test/Transport") + "${ACL_SOURCE_DIR}/include" + "${ACL_SOURCE_DIR}/test/Transport") target_link_libraries(ACLTransportCommonTestLib AVSCommon gtest_main diff --git a/ACL/test/Transport/Common/MockHTTP2Connection.cpp b/ACL/test/Transport/Common/MockHTTP2Connection.cpp index 4d134242..5d17c565 100644 --- a/ACL/test/Transport/Common/MockHTTP2Connection.cpp +++ b/ACL/test/Transport/Common/MockHTTP2Connection.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018-2020 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. @@ -251,6 +251,15 @@ std::size_t MockHTTP2Connection::getMaxPostRequestsEnqueud() { return m_maxPostRequestsEnqueued; } +void MockHTTP2Connection::addObserver(std::shared_ptr observer) { + std::lock_guard lock{m_observersMutex}; + m_observers.insert(observer); +} +void MockHTTP2Connection::removeObserver(std::shared_ptr observer) { + std::lock_guard lock{m_observersMutex}; + m_observers.erase(observer); +} + } // namespace test } // namespace http2 } // namespace utils diff --git a/ACL/test/Transport/HTTP2TransportTest.cpp b/ACL/test/Transport/HTTP2TransportTest.cpp index fe9d0197..0a125b12 100644 --- a/ACL/test/Transport/HTTP2TransportTest.cpp +++ b/ACL/test/Transport/HTTP2TransportTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018-2020 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. @@ -30,6 +30,7 @@ #include #include #include +#include #include "MockAuthDelegate.h" #include "MockHTTP2Connection.h" @@ -39,6 +40,8 @@ #include "MockPostConnectFactory.h" #include "MockTransportObserver.h" +#include "ACL/Transport/SynchronizedMessageRequestQueue.h" + namespace alexaClientSDK { namespace acl { namespace transport { @@ -202,6 +205,12 @@ protected: /// The mock @c PostConnectInterface. std::shared_ptr m_mockPostConnect; + /// The mock @c MetricRecorder. + std::shared_ptr m_mockMetricRecorder; + + /// The message queue map. + std::shared_ptr m_synchronizedMessageRequestQueue; + /// A promise that the Auth Observer will be set. PromiseFuturePair> m_authObserverSet; @@ -253,7 +262,11 @@ void HTTP2TransportTest::SetUp() { m_mockTransportObserver = std::make_shared>(); m_mockPostConnectFactory = std::make_shared>(); m_mockPostConnect = std::make_shared>(); + m_mockMetricRecorder = std::make_shared>(); m_mockAuthDelegate->setAuthToken(CBL_AUTHORIZATION_TOKEN); + HTTP2Transport::Configuration cfg; + m_synchronizedMessageRequestQueue = std::make_shared(); + m_http2Transport = HTTP2Transport::create( m_mockAuthDelegate, TEST_AVS_GATEWAY_STRING, @@ -261,7 +274,10 @@ void HTTP2TransportTest::SetUp() { m_mockMessageConsumer, m_attachmentManager, m_mockTransportObserver, - m_mockPostConnectFactory); + m_mockPostConnectFactory, + m_synchronizedMessageRequestQueue, + cfg, + m_mockMetricRecorder); ASSERT_NE(m_http2Transport, nullptr); } @@ -652,7 +668,8 @@ TEST_F(HTTP2TransportTest, testSlow_messageRequestsQueuing) { auto messageObserver = std::make_shared(); messageObservers.push_back(messageObserver); messageReq->addObserver(messageObserver); - m_http2Transport->send(messageReq); + m_synchronizedMessageRequestQueue->enqueueRequest(messageReq); + m_http2Transport->onRequestEnqueued(); } // Give m_http2Transport a chance to misbehave and send more than a single request before receiving a response. @@ -690,6 +707,73 @@ TEST_F(HTTP2TransportTest, testSlow_messageRequestsQueuing) { m_http2Transport->shutdown(); + // Count the number of messages that received CANCELED or NOT_CONNECTED event. + unsigned messagesCanceled = 0; + unsigned messagesRemaining = 0; + for (unsigned messageNum = 0; messageNum < messagesCount; messageNum++) { + if (messageObservers[messageNum]->m_status.waitFor(RESPONSE_TIMEOUT)) { + switch (messageObservers[messageNum]->m_status.getValue()) { + case MessageRequestObserverInterface::Status::CANCELED: + case MessageRequestObserverInterface::Status::NOT_CONNECTED: + messagesCanceled++; + default: + break; + } + } + } + while (m_synchronizedMessageRequestQueue->dequeueRequest()) { + ++messagesRemaining; + } + + ASSERT_EQ(messagesCanceled + messagesRemaining, messagesCount); +} + +/** + * Test MessageRequests are sent for sequential queue types. + */ +TEST_F(HTTP2TransportTest, messageRequests_SequentialSend) { + authorizeAndConnect(); + + // Send 5 messages. + std::vector> messageObservers; + unsigned int messagesCount = 5; // number of test messages to Send + for (unsigned messageNum = 0; messageNum < messagesCount; messageNum++) { + std::shared_ptr messageReq = std::make_shared(TEST_MESSAGE, ""); + auto messageObserver = std::make_shared(); + messageObservers.push_back(messageObserver); + messageReq->addObserver(messageObserver); + m_synchronizedMessageRequestQueue->enqueueRequest(messageReq); + m_http2Transport->onRequestEnqueued(); + } + + unsigned int postsRequestsCount = 0; + while (postsRequestsCount < messagesCount) { + // Delayed 200 response for each POST request. + std::this_thread::sleep_for(SHORT_DELAY); + postsRequestsCount++; + + ASSERT_EQ((int)m_mockHttp2Connection->getPostRequestsNum(), (int)postsRequestsCount); + auto request = m_mockHttp2Connection->waitForPostRequest(RESPONSE_TIMEOUT); + if (request) { + request->getSink()->onReceiveResponseCode(HTTPResponseCode::SUCCESS_OK); + } + } + + // Make sure HTTP2Transport sends out the 5 POST requests. + ASSERT_EQ(postsRequestsCount, messagesCount); + + // On disconnect, send CANCELED response for each POST REQUEST. + EXPECT_CALL(*m_mockHttp2Connection, disconnect()).WillOnce(Invoke([this]() { + while (true) { + auto request = m_mockHttp2Connection->dequePostRequest(); + if (!request) break; + + request->getSink()->onResponseFinished(HTTP2ResponseFinishedStatus::CANCELLED); + }; + })); + + m_http2Transport->shutdown(); + // Count the number of messages that received CANCELED or NOT_CONNECTED event. unsigned messagesCanceled = 0; for (unsigned messageNum = 0; messageNum < messagesCount; messageNum++) { @@ -792,7 +876,8 @@ TEST_F(HTTP2TransportTest, test_onSendCompletedNotification) { auto messageObserver = std::make_shared(); messageObservers.push_back(messageObserver); messageReq->addObserver(messageObserver); - m_http2Transport->send(messageReq); + m_synchronizedMessageRequestQueue->enqueueRequest(messageReq); + m_http2Transport->onRequestEnqueued(); } // Send the response code for each POST request. @@ -836,7 +921,8 @@ TEST_F(HTTP2TransportTest, test_onExceptionReceivedNon200Content) { std::shared_ptr messageReq = std::make_shared(TEST_MESSAGE, ""); auto messageObserver = std::make_shared(); messageReq->addObserver(messageObserver); - m_http2Transport->send(messageReq); + m_synchronizedMessageRequestQueue->enqueueRequest(messageReq); + m_http2Transport->onRequestEnqueued(); auto request = m_mockHttp2Connection->waitForPostRequest(RESPONSE_TIMEOUT); ASSERT_NE(request, nullptr); @@ -876,7 +962,8 @@ TEST_F(HTTP2TransportTest, test_messageConsumerReceiveDirective) { std::shared_ptr messageReq = std::make_shared(TEST_MESSAGE, ""); auto messageObserver = std::make_shared(); messageReq->addObserver(messageObserver); - m_http2Transport->send(messageReq); + m_synchronizedMessageRequestQueue->enqueueRequest(messageReq); + m_http2Transport->onRequestEnqueued(); auto eventStream = m_mockHttp2Connection->waitForPostRequest(RESPONSE_TIMEOUT); ASSERT_NE(eventStream, nullptr); @@ -904,7 +991,8 @@ TEST_F(HTTP2TransportTest, test_onServerSideDisconnectOnDownchannelClosure) { // Send a message. std::shared_ptr messageReq = std::make_shared(TEST_MESSAGE, ""); - m_http2Transport->send(messageReq); + m_synchronizedMessageRequestQueue->enqueueRequest(messageReq); + m_http2Transport->onRequestEnqueued(); PromiseFuturePair gotOnServerSideDisconnect; auto setGotOnServerSideDisconnect = [&gotOnServerSideDisconnect] { gotOnServerSideDisconnect.setValue(); }; @@ -943,7 +1031,8 @@ TEST_F(HTTP2TransportTest, test_messageRequestTimeoutPingRequest) { // Send a message. std::shared_ptr messageReq = std::make_shared(TEST_MESSAGE, ""); - m_http2Transport->send(messageReq); + m_synchronizedMessageRequestQueue->enqueueRequest(messageReq); + m_http2Transport->onRequestEnqueued(); // Upon receiving the message, the mock HTTP2Connection/request will reply to the request with // onResponseFinished(TIMEOUT). @@ -978,7 +1067,9 @@ TEST_F(HTTP2TransportTest, testTimer_networkInactivityPingRequest) { m_attachmentManager, m_mockTransportObserver, m_mockPostConnectFactory, - cfg); + m_synchronizedMessageRequestQueue, + cfg, + m_mockMetricRecorder); authorizeAndConnect(); @@ -1023,7 +1114,9 @@ TEST_F(HTTP2TransportTest, testSlow_tearDownPingTimeout) { m_attachmentManager, m_mockTransportObserver, m_mockPostConnectFactory, - cfg); + m_synchronizedMessageRequestQueue, + cfg, + m_mockMetricRecorder); authorizeAndConnect(); @@ -1037,7 +1130,7 @@ TEST_F(HTTP2TransportTest, testSlow_tearDownPingTimeout) { // Reply to a ping request. std::thread pingThread([this]() { auto pingRequest = m_mockHttp2Connection->waitForPingRequest(RESPONSE_TIMEOUT); - ASSERT_TRUE(pingRequest); + ASSERT_NE(pingRequest, nullptr); m_mockHttp2Connection->dequePingRequest(); pingRequest->getSink()->onResponseFinished(HTTP2ResponseFinishedStatus::TIMEOUT); }); @@ -1065,7 +1158,9 @@ TEST_F(HTTP2TransportTest, testSlow_tearDownPingFailure) { m_attachmentManager, m_mockTransportObserver, m_mockPostConnectFactory, - cfg); + m_synchronizedMessageRequestQueue, + cfg, + m_mockMetricRecorder); authorizeAndConnect(); @@ -1079,7 +1174,7 @@ TEST_F(HTTP2TransportTest, testSlow_tearDownPingFailure) { // Reply to a ping request. std::thread pingThread([this]() { auto pingRequest = m_mockHttp2Connection->waitForPingRequest(RESPONSE_TIMEOUT); - ASSERT_TRUE(pingRequest); + ASSERT_NE(pingRequest, nullptr); m_mockHttp2Connection->dequePingRequest(); pingRequest->getSink()->onReceiveResponseCode(HTTPResponseCode::CLIENT_ERROR_BAD_REQUEST); pingRequest->getSink()->onResponseFinished(HTTP2ResponseFinishedStatus::COMPLETE); @@ -1112,11 +1207,12 @@ TEST_F(HTTP2TransportTest, testSlow_avsStreamsLimit) { auto messageObserver = std::make_shared(); messageObservers.push_back(messageObserver); messageReq->addObserver(messageObserver); - m_http2Transport->send(messageReq); + m_synchronizedMessageRequestQueue->enqueueRequest(messageReq); + m_http2Transport->onRequestEnqueued(); } // Check that there was a downchannel request sent out. - ASSERT_TRUE(m_mockHttp2Connection->getDownchannelRequest(RESPONSE_TIMEOUT)); + ASSERT_NE(m_mockHttp2Connection->getDownchannelRequest(RESPONSE_TIMEOUT), nullptr); // Check the messages we sent were limited. ASSERT_EQ(m_mockHttp2Connection->getPostRequestsNum(), MAX_POST_STREAMS); diff --git a/ACL/test/Transport/MessageRouterTest.cpp b/ACL/test/Transport/MessageRouterTest.cpp index 8c9774bd..674c53d5 100644 --- a/ACL/test/Transport/MessageRouterTest.cpp +++ b/ACL/test/Transport/MessageRouterTest.cpp @@ -105,7 +105,7 @@ TEST_F(MessageRouterTest, test_sendIsSuccessfulWhenConnected) { auto messageRequest = createMessageRequest(); // Expect to have the message sent to the transport - EXPECT_CALL(*m_mockTransport, send(messageRequest)).Times(1); + EXPECT_CALL(*m_mockTransport, onRequestEnqueued()).Times(1); m_router->sendMessage(messageRequest); @@ -116,8 +116,8 @@ TEST_F(MessageRouterTest, test_sendIsSuccessfulWhenConnected) { TEST_F(MessageRouterTest, test_sendFailsWhenDisconnected) { auto messageRequest = createMessageRequest(); - // Expect to have the message sent to the transport - EXPECT_CALL(*m_mockTransport, send(messageRequest)).Times(0); + // Expect to have the message to be enqueued but the transport is not notified + EXPECT_CALL(*m_mockTransport, onRequestEnqueued()).Times(0); m_router->sendMessage(messageRequest); } @@ -130,7 +130,7 @@ TEST_F(MessageRouterTest, test_sendFailsWhenPending) { auto messageRequest = createMessageRequest(); // Expect to have the message sent to the transport. - EXPECT_CALL(*m_mockTransport, send(messageRequest)).Times(1); + EXPECT_CALL(*m_mockTransport, onRequestEnqueued()).Times(1); m_router->sendMessage(messageRequest); waitOnMessageRouter(SHORT_TIMEOUT_MS); @@ -145,7 +145,7 @@ TEST_F(MessageRouterTest, test_sendMessageDoesNotSendAfterDisconnected) { m_router->disable(); // Expect to have the message sent to the transport - EXPECT_CALL(*m_mockTransport, send(messageRequest)).Times(0); + EXPECT_CALL(*m_mockTransport, onRequestEnqueued()).Times(0); m_router->sendMessage(messageRequest); } @@ -202,9 +202,9 @@ TEST_F(MessageRouterTest, test_serverSideDisconnectCreatesANewTransport) { auto messageRequest = createMessageRequest(); - EXPECT_CALL(*oldTransport.get(), send(messageRequest)).Times(0); + EXPECT_CALL(*oldTransport.get(), onRequestEnqueued()).Times(0); - EXPECT_CALL(*newTransport.get(), send(messageRequest)).Times(1); + EXPECT_CALL(*newTransport.get(), onRequestEnqueued()).Times(1); m_router->sendMessage(messageRequest); diff --git a/ACL/test/Transport/MessageRouterTest.h b/ACL/test/Transport/MessageRouterTest.h index 623ff1b9..55dd3dd6 100644 --- a/ACL/test/Transport/MessageRouterTest.h +++ b/ACL/test/Transport/MessageRouterTest.h @@ -80,7 +80,8 @@ private: std::shared_ptr attachmentManager, const std::string& avsGateway, std::shared_ptr messageConsumerInterface, - std::shared_ptr transportObserverInterface) override { + std::shared_ptr transportObserverInterface, + std::shared_ptr sharedMessageRequestQueue) override { return m_mockTransport; } diff --git a/ACL/test/Transport/MockHTTP2Connection.h b/ACL/test/Transport/MockHTTP2Connection.h index 060f8183..c5db3dc6 100644 --- a/ACL/test/Transport/MockHTTP2Connection.h +++ b/ACL/test/Transport/MockHTTP2Connection.h @@ -1,5 +1,5 @@ /* - * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018-2020 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. @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -57,6 +58,8 @@ public: /// @{ std::shared_ptr createAndSendRequest(const HTTP2RequestConfig& config); MOCK_METHOD0(disconnect, void()); + void addObserver(std::shared_ptr observer); + void removeObserver(std::shared_ptr observer); /// @} /** @@ -237,6 +240,12 @@ private: /// Queue of Ping requests. Serialized by @c m_pingRequestMutex. std::deque> m_pingRequestQueue; + /// Mutex for observers. + std::mutex m_observersMutex; + + /// Observers + std::unordered_set> m_observers; + /// Serializes access to receiving Ping requests. std::mutex m_pingRequestMutex; diff --git a/ACL/test/Transport/MockTransport.h b/ACL/test/Transport/MockTransport.h index 9f4cf5e3..8b7c6897 100644 --- a/ACL/test/Transport/MockTransport.h +++ b/ACL/test/Transport/MockTransport.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-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. @@ -40,7 +40,8 @@ public: MOCK_METHOD0(disconnect, void()); MOCK_METHOD0(isConnected, bool()); MOCK_METHOD0(isPendingDisconnected, bool()); - MOCK_METHOD1(send, void(std::shared_ptr)); + MOCK_METHOD0(onRequestEnqueued, void()); + MOCK_METHOD1(sendPostConnectMessage, void(std::shared_ptr)); MOCK_METHOD2(onAttachmentReceived, void(const std::string& contextId, const std::string& message)); const int m_id; diff --git a/ADSL/include/ADSL/DirectiveRouter.h b/ADSL/include/ADSL/DirectiveRouter.h index 79596692..2a713f0a 100644 --- a/ADSL/include/ADSL/DirectiveRouter.h +++ b/ADSL/include/ADSL/DirectiveRouter.h @@ -24,6 +24,8 @@ #include #include #include +#include +#include namespace alexaClientSDK { namespace adsl { @@ -34,7 +36,8 @@ namespace adsl { class DirectiveRouter : public avsCommon::utils::RequiresShutdown { public: /// Constructor. - DirectiveRouter(); + /// @param metricRecorder The metric recorder. + DirectiveRouter(std::shared_ptr metricRecorder = nullptr); /** * Add mappings from handler's directives to @c BlockingPolicy values, gotten through the @@ -194,6 +197,9 @@ private: */ bool removeDirectiveHandlerLocked(std::shared_ptr handler); + /// The metric recorder. + std::shared_ptr m_metricRecorder; + /// A mutex used to serialize access to @c m_configuration and @c m_handlerReferenceCounts. std::mutex m_mutex; @@ -211,6 +217,16 @@ private: */ std::unordered_map, int> m_handlerReferenceCounts; + + /** + * Submit metrics related to the given directive. + * + * @param metricEventBuilder The metric event builder to be used. + * @param directive The given directive. + */ + void submitMetric( + avsCommon::utils::metrics::MetricEventBuilder& metricEventBuilder, + const std::shared_ptr& directive); }; } // namespace adsl diff --git a/ADSL/include/ADSL/DirectiveSequencer.h b/ADSL/include/ADSL/DirectiveSequencer.h index d381118b..9c3aefc5 100644 --- a/ADSL/include/ADSL/DirectiveSequencer.h +++ b/ADSL/include/ADSL/DirectiveSequencer.h @@ -24,6 +24,7 @@ #include #include +#include #include "ADSL/DirectiveProcessor.h" #include "ADSL/DirectiveRouter.h" @@ -41,10 +42,12 @@ public: * * @param exceptionSender An instance of the @c ExceptionEncounteredSenderInterface used to send * ExceptionEncountered messages to AVS for directives that are not handled. + * @param metricRecorder The metric recorder. * @return Returns a new DirectiveSequencer, or nullptr if the operation failed. */ static std::unique_ptr create( - std::shared_ptr exceptionSender); + std::shared_ptr exceptionSender, + std::shared_ptr metricRecorder = nullptr); bool addDirectiveHandler(std::shared_ptr handler) override; @@ -63,11 +66,13 @@ public: private: /** * Constructor. - * * @param exceptionSender An instance of the @c ExceptionEncounteredSenderInterface used to send * ExceptionEncountered messages to AVS for directives that are not handled. + * @param metricRecorder The metric recorder. */ - DirectiveSequencer(std::shared_ptr exceptionSender); + DirectiveSequencer( + std::shared_ptr exceptionSender, + std::shared_ptr metricRecorder); void doShutdown() override; diff --git a/ADSL/include/ADSL/MessageInterpreter.h b/ADSL/include/ADSL/MessageInterpreter.h index 1873b91a..e8aaa8da 100644 --- a/ADSL/include/ADSL/MessageInterpreter.h +++ b/ADSL/include/ADSL/MessageInterpreter.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -21,6 +21,7 @@ #include #include #include +#include namespace alexaClientSDK { namespace adsl { @@ -39,11 +40,13 @@ public: * @param directiveSequencerInterface The DirectiveSequencerInterface implementation, which will receive * @c AVSDirectives. * @param attachmentManager The @c AttachmentManager which created @c AVSDirectives will use to acquire Attachments. + * @param metricRecorder The metric recorder. */ MessageInterpreter( std::shared_ptr exceptionEncounteredSender, std::shared_ptr directiveSequencer, - std::shared_ptr attachmentManager); + std::shared_ptr attachmentManager, + std::shared_ptr metricRecorder = nullptr); void receive(const std::string& contextId, const std::string& message) override; @@ -52,8 +55,10 @@ private: std::shared_ptr m_exceptionEncounteredSender; /// Object to which we will send @c AVSDirectives. std::shared_ptr m_directiveSequencer; - // The attachment manager. + /// The attachment manager. std::shared_ptr m_attachmentManager; + /// The metric recorder. + std::shared_ptr m_metricRecorder; }; } // namespace adsl diff --git a/ADSL/src/DirectiveRouter.cpp b/ADSL/src/DirectiveRouter.cpp index d6b37d2f..f1fa29e3 100644 --- a/ADSL/src/DirectiveRouter.cpp +++ b/ADSL/src/DirectiveRouter.cpp @@ -19,8 +19,9 @@ #include #include -#include #include +#include +#include #include #include "ADSL/DirectiveRouter.h" @@ -42,8 +43,40 @@ using namespace avsCommon::avs; using namespace avsCommon::avs::directiveRoutingRule; using namespace avsCommon::sdkInterfaces; using namespace avsCommon::utils; +using namespace avsCommon::utils::metrics; -DirectiveRouter::DirectiveRouter() : RequiresShutdown{"DirectiveRouter"} { +/// Prefix used in metrics published in this module. +static const std::string DIRECTIVE_SEQUENCER_METRIC_PREFIX = "DIRECTIVE_SEQUENCER-"; + +/// Metric name for directives that were dispatched immediately. +static const std::string DIRECTIVE_DISPATCHED_IMMEDIATE = "DIRECTIVE_DISPATCHED_IMMEDIATE"; + +/// Metric name for directives that were pre-handled. +static const std::string DIRECTIVE_DISPATCHED_PRE_HANDLE = "DIRECTIVE_DISPATCHED_PRE_HANDLE"; + +/// Metric name for directives that were handled directly. +static const std::string DIRECTIVE_DISPATCHED_HANDLE = "DIRECTIVE_DISPATCHED_HANDLE"; + +void DirectiveRouter::submitMetric( + MetricEventBuilder& metricEventBuilder, + const std::shared_ptr& directive) { + if (directive) { + metricEventBuilder.addDataPoint( + DataPointStringBuilder{}.setName("HTTP2_STREAM").setValue(directive->getAttachmentContextId()).build()); + metricEventBuilder.addDataPoint( + DataPointStringBuilder{}.setName("DIRECTIVE_MESSAGE_ID").setValue(directive->getMessageId()).build()); + } + auto metricEvent = metricEventBuilder.build(); + if (metricEvent) { + recordMetric(m_metricRecorder, metricEvent); + } else { + ACSDK_ERROR(LX("submitMetricFailed").d("reason", "buildMetricFailed")); + } +} + +DirectiveRouter::DirectiveRouter(std::shared_ptr metricRecorder) : + RequiresShutdown{"DirectiveRouter"}, + m_metricRecorder{metricRecorder} { } bool DirectiveRouter::addDirectiveHandler(std::shared_ptr handler) { @@ -161,6 +194,13 @@ bool DirectiveRouter::handleDirectiveImmediately(std::shared_ptrgetMessageId()).d("action", "calling")); HandlerCallScope scope(lock, this, handlerAndPolicy.handler); + + submitMetric( + MetricEventBuilder{} + .setActivityName(DIRECTIVE_SEQUENCER_METRIC_PREFIX + DIRECTIVE_DISPATCHED_IMMEDIATE) + .addDataPoint(DataPointCounterBuilder{}.setName(DIRECTIVE_DISPATCHED_IMMEDIATE).increment(1).build()), + directive); + handlerAndPolicy.handler->handleDirectiveImmediately(directive); return true; } @@ -178,6 +218,13 @@ bool DirectiveRouter::preHandleDirective( } ACSDK_INFO(LX("preHandleDirective").d("messageId", directive->getMessageId()).d("action", "calling")); HandlerCallScope scope(lock, this, handler); + + submitMetric( + MetricEventBuilder{} + .setActivityName(DIRECTIVE_SEQUENCER_METRIC_PREFIX + DIRECTIVE_DISPATCHED_PRE_HANDLE) + .addDataPoint(DataPointCounterBuilder{}.setName(DIRECTIVE_DISPATCHED_PRE_HANDLE).increment(1).build()), + directive); + handler->preHandleDirective(directive, std::move(result)); return true; } @@ -192,6 +239,13 @@ bool DirectiveRouter::handleDirective(const std::shared_ptr& direc } ACSDK_INFO(LX("handleDirective").d("messageId", directive->getMessageId()).d("action", "calling")); HandlerCallScope scope(lock, this, handler); + + submitMetric( + MetricEventBuilder{} + .setActivityName(DIRECTIVE_SEQUENCER_METRIC_PREFIX + DIRECTIVE_DISPATCHED_HANDLE) + .addDataPoint(DataPointCounterBuilder{}.setName(DIRECTIVE_DISPATCHED_HANDLE).increment(1).build()), + directive); + auto result = handler->handleDirective(directive->getMessageId()); if (!result) { ACSDK_WARN(LX("messageIdNotRecognized") diff --git a/ADSL/src/DirectiveSequencer.cpp b/ADSL/src/DirectiveSequencer.cpp index d8c361c8..c8771d78 100644 --- a/ADSL/src/DirectiveSequencer.cpp +++ b/ADSL/src/DirectiveSequencer.cpp @@ -42,12 +42,13 @@ using namespace avsCommon::sdkInterfaces; using namespace avsCommon::utils; std::unique_ptr DirectiveSequencer::create( - std::shared_ptr exceptionSender) { + std::shared_ptr exceptionSender, + std::shared_ptr metricRecorder) { if (!exceptionSender) { ACSDK_INFO(LX("createFailed").d("reason", "nullptrExceptionSender")); return nullptr; } - return std::unique_ptr(new DirectiveSequencer(exceptionSender)); + return std::unique_ptr(new DirectiveSequencer(exceptionSender, metricRecorder)); } bool DirectiveSequencer::addDirectiveHandler(std::shared_ptr handler) { @@ -86,12 +87,14 @@ bool DirectiveSequencer::onDirective(std::shared_ptr directive) { } DirectiveSequencer::DirectiveSequencer( - std::shared_ptr exceptionSender) : + std::shared_ptr exceptionSender, + std::shared_ptr metricRecorder) : DirectiveSequencerInterface{"DirectiveSequencer"}, m_mutex{}, m_exceptionSender{exceptionSender}, m_isShuttingDown{false}, - m_isEnabled{true} { + m_isEnabled{true}, + m_directiveRouter{metricRecorder} { m_directiveProcessor = std::make_shared(&m_directiveRouter); m_receivingThread = std::thread(&DirectiveSequencer::receivingLoop, this); } diff --git a/ADSL/src/MessageInterpreter.cpp b/ADSL/src/MessageInterpreter.cpp index 0179a88b..817f2692 100644 --- a/ADSL/src/MessageInterpreter.cpp +++ b/ADSL/src/MessageInterpreter.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-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. @@ -15,6 +15,9 @@ #include "ADSL/MessageInterpreter.h" +#include +#include +#include #include #include @@ -27,6 +30,13 @@ using namespace avsCommon::avs; using namespace avsCommon::avs::attachment; using namespace avsCommon::sdkInterfaces; using namespace avsCommon::utils; +using namespace avsCommon::utils::metrics; + +/// Metric recorded when parse has been completed. +static const std::string PARSE_COMPLETE("PARSE_COMPLETE"); + +/// The metric activity name for parsing completed. +static const std::string PARSE_COMPLETE_ACTIVITY_NAME("MESSAGE_INTERPRETER-" + PARSE_COMPLETE); /// String to identify log entries originating from this file. static const std::string TAG("MessageInterpreter"); @@ -41,10 +51,12 @@ static const std::string TAG("MessageInterpreter"); MessageInterpreter::MessageInterpreter( std::shared_ptr exceptionEncounteredSender, std::shared_ptr directiveSequencer, - std::shared_ptr attachmentManager) : + std::shared_ptr attachmentManager, + std::shared_ptr metricRecorder) : m_exceptionEncounteredSender{exceptionEncounteredSender}, m_directiveSequencer{directiveSequencer}, - m_attachmentManager{attachmentManager} { + m_attachmentManager{attachmentManager}, + m_metricRecorder{metricRecorder} { } void MessageInterpreter::receive(const std::string& contextId, const std::string& message) { @@ -64,6 +76,24 @@ void MessageInterpreter::receive(const std::string& contextId, const std::string return; } + auto metricEvent = + MetricEventBuilder{} + .setActivityName(PARSE_COMPLETE_ACTIVITY_NAME) + .addDataPoint(DataPointCounterBuilder{}.setName(PARSE_COMPLETE).increment(1).build()) + .addDataPoint(DataPointStringBuilder{} + .setName("HTTP2_STREAM") + .setValue(avsDirective->getAttachmentContextId()) + .build()) + .addDataPoint( + DataPointStringBuilder{}.setName("DIRECTIVE_MESSAGE_ID").setValue(avsDirective->getMessageId()).build()) + .build(); + + if (metricEvent == nullptr) { + ACSDK_ERROR(LX("Error creating metric.")); + return; + } + recordMetric(m_metricRecorder, metricEvent); + if (avsDirective->getName() == "StopCapture" || avsDirective->getName() == "Speak") { ACSDK_METRIC_MSG(TAG, avsDirective, Metrics::Location::ADSL_ENQUEUE); } diff --git a/ADSL/test/DirectiveRouterTest.cpp b/ADSL/test/DirectiveRouterTest.cpp index 5e80ad94..ad894722 100644 --- a/ADSL/test/DirectiveRouterTest.cpp +++ b/ADSL/test/DirectiveRouterTest.cpp @@ -22,7 +22,6 @@ #include #include -#include #include #include "ADSL/DirectiveRouter.h" diff --git a/AFML/include/AFML/Channel.h b/AFML/include/AFML/Channel.h index 57d1cc78..5b175979 100644 --- a/AFML/include/AFML/Channel.h +++ b/AFML/include/AFML/Channel.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -16,25 +16,33 @@ #ifndef ALEXA_CLIENT_SDK_AFML_INCLUDE_AFML_CHANNEL_H_ #define ALEXA_CLIENT_SDK_AFML_INCLUDE_AFML_CHANNEL_H_ +#include #include +#include #include #include -#include +#include #include +#include +#include +#include namespace alexaClientSDK { namespace afml { /** * A Channel represents a focusable layer with a priority, allowing the observer which has acquired the Channel to - * understand focus changes. + * understand focus changes. A Channel can be acquired by multiple Activities, but only a single Activity can be + * the primary activity. All other Activities that are not the primary Activity will be backgrounded. + * + * A Channel can optionally be virtual. Activity changes will not be captured for virtual + * channels. */ class Channel { public: /* - * This class contains the states of the @c Channel. The states inside this structure are intended to be shared via - * the @c ActivityTrackerInterface. + * This class contains the states of the @c Channel. */ struct State { /// Constructor with @c Channel name as the parameter. @@ -64,8 +72,11 @@ public: * * @param name The channel's name. * @param priority The priority of the channel. + * @param isVirtual bool to indicate if this channel is a virtual channel. + * + * @note refer to the class level doc on virtual channel paramter. */ - Channel(const std::string& name, const unsigned int priority); + Channel(const std::string& name, const unsigned int priority, bool isVirtual = false); /** * Returns the name of a channel. @@ -82,28 +93,52 @@ public: unsigned int getPriority() const; /** - * Updates the focus and notifies the Channel's observer, if there is one, of the focus change. This method does - * not return until the ChannelObserverInterface##onFocusChanged() callback to the observer returns. If the focus - * @c NONE, the observer will be removed from the Channel. + * Updates the focus and notifies all activities associated with the @c Channel of the focus change. If @c + * FocusState is FOREGROUND, then the most recent Activity added to the Channel will be moved to foreground state, + * and all other Activites will remain in background state. This method does not return until the + * ChannelObserverInterface##onFocusChanged() callbacks to all the associated activities return. If the focus is @c + * NONE, the Activity will be removed from the Channel. + * + * @note the @c MixingBehavior should pertain to the primary Activity for the Channel. * * @param focus The focus of the Channel. + * @param behavior The MixingBehavior of the Channel. + * @param forceUpdate bool to indicate if the operation must be forced (irrespective of focus/behavior change). * @return @c true if focus changed, else @c false. */ - bool setFocus(avsCommon::avs::FocusState focus); + bool setFocus(avsCommon::avs::FocusState focus, avsCommon::avs::MixingBehavior behavior, bool forceUpdate = false); /** - * Sets a new observer. + * Set the Primary Activity on the @c Channel given the Activity Object. The function must be called after the + * correct FocusState of the Channel is set. Any other Activities on the Channel will be moved to the background + * state. * - * @param observer The observer of the Channel. + * @param activity @c Activity to be set as Primary Activity. */ - void setObserver(std::shared_ptr observer); + void setPrimaryActivity(std::shared_ptr activity); /** - * Checks whether the Channel has an observer. + * Activity to be released by observer. * - * @return @c true if the Channel has an observer, else @c false. + * @param observer Channel Observer to release. + * @return @c true if Activity was found and released, @c false otherwise. */ - bool hasObserver() const; + bool releaseActivity(std::shared_ptr observer); + + /** + * Activity to be released by interface. + * + * @param interfaceName The interface name of the Activity to release. + */ + bool releaseActivity(const std::string& interfaceName); + + /** + * Returns if channel is active. + * A Channel is considered active if there is any Activity on the Channel. + * + * @return @c true if Channel is active, @c false otherwise. + */ + bool isActive(); /** * Compares this Channel and another Channel and checks which is higher priority. A Channel is considered higher @@ -114,27 +149,12 @@ public: bool operator>(const Channel& rhs) const; /** - * Updates the AVS interface occupying the Channel. - * - * @param interface The name of the interface occupying the Channel. - */ - void setInterface(const std::string& interface); - - /** - * Returns the name of the AVS interface occupying the Channel. + * Returns the name of the interface of the Primary Activity occupying the Channel. * * @return The name of the AVS interface. */ std::string getInterface() const; - /** - * Checks whether the observer passed in currently owns the Channel. - * - * @param observer The observer to check. - * @return @c true if the observer currently owns the Channel and @c false otherwise. - */ - bool doesObserverOwnChannel(std::shared_ptr observer) const; - /** * Returns the @c State of the @c Channel. * @@ -142,17 +162,131 @@ public: */ Channel::State getState() const; + /** + * Returns a list of Channel::State updates. This API will internally clear + * the stored updates when called. + * + * @return vector containing all Channel::State Updates + */ + std::vector getActivityUpdates(); + + /** + * Returns the primary activity associated with this channel + * + * @return the primary activity associated with the channel. + */ + std::shared_ptr getPrimaryActivity(); + + /** + * Gets the activity associated with a given interfaceName. + * + * @param interfaceName interface name associated with the activity being searched + * @return activity associated with @param interfaceName, nullptr if not found. + */ + std::shared_ptr getActivity( + const std::string& interfaceName); + + /** + * Retrieve vector of interfaces of all Activites on the Channel. + */ + std::vector getInterfaceList() const; + +private: + /** + * Notify all the Activities associated with the @c Channel. All activities that are not + * the primary activity, will be passed MixingBehavior::MUST_PAUSE. The primary activity shall + * receive the behavior passed as input to the API. + * + * @param behavior MixingBehavior to notify the activities with. + * @param focusState FocusState to notify the activities with. + */ + void notifyActivities(avsCommon::avs::MixingBehavior behavior, avsCommon::avs::FocusState focusState); + + /** + * Activity to be released. + * + * @param activityToRelease The Activity to be released from the Channel. + * @return bool true if the operation was successful, otherwise false. + */ + bool releaseActivityLocked( + std::shared_ptr activityToRelease); + + /** + * Helper to remove Activity from the list, also update Channel::State updates as required. + * + * @param activity Activity Iterator to remove. + * @return The @c true if successful and @c false otherwise. + */ + bool removeActivityHelperLocked( + std::list>::iterator + activityToRemoveIt); + /** + * Adds a Channel State entry to Channel::State updates with current m_state with a specified + * focusState and interface name. + * + * @param interfaceName Interface Name to add to the Channel::State updates. + * @param focusState FocusState to add to the Channel::State updates. + */ + void addToChannelUpdatesLocked(const std::string& interfaceName, avsCommon::avs::FocusState focusState); + + /** + * Given incoming and current activity, process the policies to either release, not release, + * or start patience timer for the current Activity. + * + * @param incomingActivity incoming Activity. + * @param currentActivity current Activity. + */ + void processPolicyLocked( + std::shared_ptr incomingActivity, + std::shared_ptr currentActivity); + + /** + * Patience Timer Callback. Function is to be executed when patience timer expires. + * + * @param activity activity to release after timer is expired. + */ + void patienceTimerCallback(std::shared_ptr activity); + + /** + * Update Channel Interface with the Activity at top of stack. + */ + void updateChannelInterfaceLocked(); + + /** + * Returns the primary activity associated with this channel in a locked context. + * + * @return the primary activity associated with the channel. + */ + std::shared_ptr getPrimaryActivityLocked() const; + private: /// The priority of the Channel. const unsigned int m_priority; + /// Flag to indicate if this is a virtual channel + bool m_isVirtual; + /// The @c State of the @c Channel. State m_state; - /// The current observer of the Channel. - std::shared_ptr m_observer; -}; + /// The list to hold shared pointer to all Activities. + std::list> m_activities; + /// Mutex to protect m_state and m_activities. + mutable std::mutex m_mutex; + + /// The vector to hold activity updates for the Channel. + std::vector m_activityUpdates; + + /// A timer task to trace the patience. + avsCommon::utils::timing::Timer m_patienceTimer; + + /// Activity to track the initiator of patience. + std::shared_ptr m_patienceInitiator; + + /// Activity to track the receiver of patience. + std::shared_ptr m_patienceReceiver; +}; } // namespace afml } // namespace alexaClientSDK diff --git a/AFML/include/AFML/FocusManager.h b/AFML/include/AFML/FocusManager.h index 4d0cdbfe..19b91ec0 100644 --- a/AFML/include/AFML/FocusManager.h +++ b/AFML/include/AFML/FocusManager.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -16,6 +16,7 @@ #ifndef ALEXA_CLIENT_SDK_AFML_INCLUDE_AFML_FOCUSMANAGER_H_ #define ALEXA_CLIENT_SDK_AFML_INCLUDE_AFML_FOCUSMANAGER_H_ +#include #include #include #include @@ -29,6 +30,7 @@ #include "AFML/Channel.h" #include "AFML/ActivityTrackerInterface.h" #include "AVSCommon/Utils/Threading/Executor.h" +#include "InterruptModel/InterruptModel.h" namespace alexaClientSDK { namespace afml { @@ -56,7 +58,8 @@ public: * The configuration used by the FocusManager to create Channel objects. Each configuration object has a * name and priority. */ - struct ChannelConfiguration { + class ChannelConfiguration { + public: /** * Constructor. * @@ -83,6 +86,18 @@ public: /// The priority of the channel. unsigned int priority; + + /** + * Get the virtual channel configurations. + * + * @param channelTypeKey The key of the virtual channel configuration to get. + * @param[out] virtualChannelConfiguration The @c ChannelConfiguration for the virtual channels as specified in + * @c channelTypeKey. An empty vector if there is no such info on the configuration file. + * @return true if there's no error, false otherwise. + */ + static bool readChannelConfiguration( + const std::string& channelTypeKey, + std::vector* virtualChannelConfigurations); }; /** @@ -93,15 +108,26 @@ public: * same name or priority, the latter Channels with that name or priority will not be created. * @param activityTrackerInterface The interface to notify the activity tracker a vector of channel states that has * been updated. + * @param virtualChannelConfigurations A vector of @c channelConfiguration objects that will be used to create the + * Virtual Channels. No two Channels should have the same name or priority. If there are multiple configurations + * with the same name or priority, the latter Channels with that name or priority will not be created. + * @param interruptModel @c InterruptModel object that provides MixingBehavior inputs to ChannelObservers upon + * Focus State Change. */ FocusManager( - const std::vector channelConfigurations, - std::shared_ptr activityTrackerInterface = nullptr); + const std::vector& channelConfigurations, + std::shared_ptr activityTrackerInterface = nullptr, + const std::vector& virtualChannelConfigurations = std::vector(), + std::shared_ptr interruptModel = nullptr); bool acquireChannel( const std::string& channelName, std::shared_ptr channelObserver, - const std::string& interface) override; + const std::string& interfaceName) override; + + bool acquireChannel( + const std::string& channelName, + std::shared_ptr channelActivity) override; std::future releaseChannel( const std::string& channelName, @@ -116,6 +142,11 @@ public: void removeObserver( const std::shared_ptr& observer) override; + void modifyContentType( + const std::string& channelName, + const std::string& interfaceName, + avsCommon::avs::ContentType contentType) override; + /** * Retrieves the default @c ChannelConfiguration for AVS audio channels. * @@ -150,26 +181,40 @@ private: } }; + /** + * Helper function to read the @c ChannelConfiguration into @c m_allChannels. This function also ensures the name + * and priority for all channels are unique. + * + * @param channelConfigurations The @c channelConfigurations of the channels. + * @param isVirtual Whether the channels are virtual or not. + */ + void readChannelConfiguration(const std::vector& channelConfigurations, bool isVirtual); + /** * Sets the @c FocusState for @c channel and notifies observers of the change. * * @param channel The @c Channel to set the @c FocusState for. * @param focus The @c FocusState to set @c channel to. + * @param behavior The @c MixingBehavior to set @c channel to. + * @param forceUpdate optional, if set to true this function will update + * activitytracker context (even if focus/behavior did not change). */ - void setChannelFocus(const std::shared_ptr& channel, avsCommon::avs::FocusState focus); + void setChannelFocus( + const std::shared_ptr& channel, + avsCommon::avs::FocusState focus, + avsCommon::avs::MixingBehavior behavior, + bool forceUpdate = false); /** * Grants access to the Channel specified and updates other Channels as needed. This function provides the full * implementation which the public method will call. * * @param channelToAcquire The Channel to acquire. - * @param channelObserver The new observer of the Channel. - * @param interface The name of the AVS inferface on the Channel. + * @param channelActivity The Activity to acquire. */ void acquireChannelHelper( std::shared_ptr channelToAcquire, - std::shared_ptr channelObserver, - const std::string& interface); + std::shared_ptr channelActivity); /** * Releases the Channel specified and updates other Channels as needed. This function provides the full @@ -261,6 +306,30 @@ private: */ void notifyActivityTracker(); + /** + * Get the mixing behavior for ChannelObserver associated with a low priority channel , when a high priority channel + * barges in + * + * @param lowPrioChannel channel with the lower priority + * @param highPrioChannel channel with the higher priority + * @return MixingBehavior to be taken by the ChannelObserver associated with the lowPrioChannel + */ + avsCommon::avs::MixingBehavior getMixingBehavior( + std::shared_ptr lowPrioChannel, + std::shared_ptr highPrioChannel); + + /** + * This function determines the mixingBehavior for each backgrounded channel, when the @param foregroundChannel is + * in Foreground. It also invokes the ChannelObserverInterface::onFocusChanged callback for each backgrounded + * channel. + * + * @param foregroundChannel the channel currently holding foreground focus + */ + void setBackgroundChannelMixingBehavior(std::shared_ptr foregroundChannel); + + /// Mutex used to lock m_activeChannels, m_observers and Channels' interface name. + std::mutex m_mutex; + /// Map of channel names to shared_ptrs of Channel objects and contains every channel. std::unordered_map> m_allChannels; @@ -270,9 +339,6 @@ private: /// The set of observers to notify about focus changes. std::unordered_set> m_observers; - /// Mutex used to lock m_activeChannels, m_observers and Channels' interface name. - std::mutex m_mutex; - /* * A vector of channel's State that has been updated due to @c acquireChannel(), @c releaseChannel() or * stopForegroundActivity(). This is accessed by functions in the @c m_executor worker thread, and do not require @@ -283,6 +349,9 @@ private: /// The interface to notify its activity tracker of any changes to its channels. std::shared_ptr m_activityTracker; + /// The interrupt Model associated with the focus manager + std::shared_ptr m_interruptModel; + /** * @c Executor which queues up operations from asynchronous API calls. * diff --git a/AFML/src/CMakeLists.txt b/AFML/src/CMakeLists.txt index 1b34e49d..abf6c86e 100644 --- a/AFML/src/CMakeLists.txt +++ b/AFML/src/CMakeLists.txt @@ -5,8 +5,8 @@ add_library(AFML SHARED VisualActivityTracker.cpp) add_definitions("-DACSDK_LOG_MODULE=afml") -include_directories(AFML "${AFML_SOURCE_DIR}/include") -target_link_libraries(AFML AVSCommon) +include_directories(AFML "${AFML_SOURCE_DIR}/include" "{InterruptModel_SOURCE_DIR}/include") +target_link_libraries(AFML AVSCommon InterruptModel) # install target asdk_install() diff --git a/AFML/src/Channel.cpp b/AFML/src/Channel.cpp index f515d394..829433ef 100644 --- a/AFML/src/Channel.cpp +++ b/AFML/src/Channel.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -14,6 +14,7 @@ */ #include "AFML/Channel.h" +#include namespace alexaClientSDK { namespace afml { @@ -21,6 +22,16 @@ namespace afml { using namespace avsCommon::avs; using namespace avsCommon::sdkInterfaces; +/// String to identify log entries originating from this file. +static const std::string TAG("Channel"); + +/** + * 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) + Channel::State::State(const std::string& name) : name{name}, focusState{FocusState::NONE}, @@ -30,10 +41,10 @@ Channel::State::State(const std::string& name) : Channel::State::State() : focusState{FocusState::NONE}, timeAtIdle{std::chrono::steady_clock::now()} { } -Channel::Channel(const std::string& name, const unsigned int priority) : +Channel::Channel(const std::string& name, const unsigned int priority, bool isVirtual) : m_priority{priority}, - m_state{name}, - m_observer{nullptr} { + m_isVirtual{isVirtual}, + m_state{name} { } const std::string& Channel::getName() const { @@ -44,50 +55,291 @@ unsigned int Channel::getPriority() const { return m_priority; } -bool Channel::setFocus(FocusState focus) { - if (focus == m_state.focusState) { +bool Channel::setFocus(FocusState focus, MixingBehavior behavior, bool forceUpdate) { + std::unique_lock lock(m_mutex); + + bool focusChanged = (m_state.focusState != focus); + auto primaryActivity = getPrimaryActivityLocked(); + bool mixingBehaviorChanged = primaryActivity && (primaryActivity->getMixingBehavior() != behavior); + + if (!forceUpdate && !focusChanged && !mixingBehaviorChanged) { return false; } + ACSDK_DEBUG5(LX(__func__) + .d("name", m_state.name) + .d("newfocusState", focus) + .d("prevfocusState", m_state.focusState) + .d("newMixingBehavior", behavior) + .d("forceUpdate", forceUpdate)); m_state.focusState = focus; - if (m_observer) { - m_observer->onFocusChanged(m_state.focusState); - } - - if (FocusState::NONE == m_state.focusState) { - m_observer = nullptr; + if (m_state.focusState == FocusState::NONE) { m_state.timeAtIdle = std::chrono::steady_clock::now(); } + + // Update Channel State Updates + addToChannelUpdatesLocked(m_state.interfaceName, m_state.focusState); + lock.unlock(); + + // Notify all activities of the new focus state for this channel + notifyActivities(behavior, focus); + return true; } -void Channel::setObserver(std::shared_ptr observer) { - m_observer = observer; +void Channel::setPrimaryActivity(std::shared_ptr activity) { + std::unique_lock lock(m_mutex); + + if (!activity) { + ACSDK_ERROR(LX("setPrimaryActivityFailed").m("Null Activity")); + return; + } + + ACSDK_DEBUG5(LX(__func__).d("Interface", activity->getInterface())); + if (!m_activities.empty()) { + processPolicyLocked(activity, m_activities.front()); + } + + // Establish the activity. + m_activities.push_front(activity); + updateChannelInterfaceLocked(); } -bool Channel::hasObserver() const { - return m_observer != nullptr; +bool Channel::releaseActivity(std::shared_ptr observer) { + if (observer == nullptr) { + ACSDK_ERROR(LX("releaseActivityFailed").d("reason", "observer is null.")); + return false; + } + + std::unique_lock lock(m_mutex); + for (auto it = m_activities.begin(); it != m_activities.end(); ++it) { + if ((*it)->getChannelObserver() == observer) { + bool success = removeActivityHelperLocked(it); + if (success) { + // No change in observer or activity if remove fails. + addToChannelUpdatesLocked(m_state.interfaceName, m_state.focusState); + } + + return success; + } + } + ACSDK_DEBUG0(LX("releaseActivityFailed").m("Observer not found")); + return false; +} + +bool Channel::releaseActivity(const std::string& interfaceName) { + std::unique_lock lock(m_mutex); + for (auto it = m_activities.begin(); it != m_activities.end(); ++it) { + if ((*it)->getInterface() == interfaceName) { + bool success = removeActivityHelperLocked(it); + if (success) { + // Update Channel State Updates. + addToChannelUpdatesLocked(m_state.interfaceName, m_state.focusState); + } + + return success; + } + } + return false; +} + +bool Channel::isActive() { + std::unique_lock lock(m_mutex); + return !m_activities.empty(); } bool Channel::operator>(const Channel& rhs) const { return m_priority < rhs.getPriority(); } -void Channel::setInterface(const std::string& interface) { - m_state.interfaceName = interface; -} - std::string Channel::getInterface() const { + std::lock_guard lock(m_mutex); return m_state.interfaceName; } -bool Channel::doesObserverOwnChannel(std::shared_ptr observer) const { - return observer == m_observer; -} - Channel::State Channel::getState() const { + std::lock_guard lock(m_mutex); return m_state; } +std::vector Channel::getActivityUpdates() { + std::unique_lock lock(m_mutex); + auto activityUpdatesRet = m_activityUpdates; + m_activityUpdates.clear(); + return activityUpdatesRet; +} + +std::shared_ptr Channel::getPrimaryActivity() { + std::unique_lock lock(m_mutex); + return getPrimaryActivityLocked(); +} + +std::shared_ptr Channel::getPrimaryActivityLocked() const { + if (m_activities.empty()) { + return nullptr; + } + return *(m_activities.begin()); +} + +std::shared_ptr Channel::getActivity(const std::string& interfaceName) { + std::unique_lock lock(m_mutex); + for (const auto& it : m_activities) { + if (it->getInterface() == interfaceName) return it; + } + + return nullptr; +} + +std::vector Channel::getInterfaceList() const { + std::vector listOfInterface = {}; + for (const auto& activity : m_activities) { + listOfInterface.push_back(activity->getInterface()); + } + return listOfInterface; +} + +void Channel::notifyActivities(MixingBehavior behavior, FocusState focusState) { + std::unique_lock lock(m_mutex); + if (m_activities.empty()) { + ACSDK_WARN(LX("notifyActivitiesFailed").m("No Associated Activities Found")); + return; + } + auto activitiesCopy = m_activities; + lock.unlock(); + + auto activityIt = activitiesCopy.begin(); + // inform the primary activity with the MixingBehavior + (*activityIt)->notifyObserver(focusState, behavior); + activityIt++; + + // all other secondary activities must be PAUSED + BACKGROUND + for (; activityIt != activitiesCopy.end(); activityIt++) { + (*activityIt)->notifyObserver(FocusState::BACKGROUND, MixingBehavior::MUST_PAUSE); + } +} + +bool Channel::releaseActivityLocked( + std::shared_ptr activityToRelease) { + if (!activityToRelease) { + ACSDK_ERROR(LX("releaseActivityLockedFailed").m("Null activityToRelease")); + return false; + } + + auto priorActivityIt = std::find(m_activities.begin(), m_activities.end(), activityToRelease); + if (priorActivityIt != m_activities.end()) { + return removeActivityHelperLocked(priorActivityIt); + } + return false; +} + +bool Channel::removeActivityHelperLocked( + std::list>::iterator + activityToRemoveIt) { + ACSDK_DEBUG5(LX(__func__).d("interface", (*activityToRemoveIt)->getInterface())); + + auto isRemovingPatienceReceiver = false; + if (m_patienceReceiver == *activityToRemoveIt) { + isRemovingPatienceReceiver = true; + } + + // Timer is active AND the job being removed is one of the patience Activities. + if (m_patienceTimer.isActive() && + (m_patienceInitiator == *activityToRemoveIt || m_patienceReceiver == *activityToRemoveIt)) { + m_patienceTimer.stop(); + ACSDK_DEBUG9(LX(__func__).d("status", "Patience Timer Stopped")); + m_patienceInitiator = nullptr; + m_patienceReceiver = nullptr; + } + + // If the activity to remove is the last activity, then update the time at idle for this channel + if (m_activities.size() == 1) { + m_state.timeAtIdle = std::chrono::steady_clock::now(); + } + + // No need to update Channel updates for removing patience receiver. + if (!isRemovingPatienceReceiver) { + // Add the current State and override the FocusState with NONE. + addToChannelUpdatesLocked((*activityToRemoveIt)->getInterface(), FocusState::NONE); + } + + // Report NONE only to the single Activity that is removed + (*activityToRemoveIt)->notifyObserver(FocusState::NONE, MixingBehavior::MUST_STOP); + m_activities.erase(activityToRemoveIt); + updateChannelInterfaceLocked(); + + return true; +} + +void Channel::addToChannelUpdatesLocked(const std::string& interfaceName, avsCommon::avs::FocusState focusState) { + if (m_state.interfaceName.empty()) { + return; + } + // Only add to Channel updates if channel is not virtual. + if (!m_isVirtual) { + auto state = m_state; + state.focusState = focusState; + state.interfaceName = interfaceName; + m_activityUpdates.push_back(state); + ACSDK_DEBUG0(LX(__func__).d("interface", state.interfaceName).d("focusState", state.focusState)); + } +} + +void Channel::processPolicyLocked( + std::shared_ptr incomingActivity, + std::shared_ptr currentActivity) { + if (!incomingActivity || !currentActivity) { + ACSDK_ERROR(LX("processPolicyLockedFailed").m("Null Activities")); + return; + } + + if (incomingActivity->getInterface() == currentActivity->getInterface()) { + // Both incoming and current activity has identical interface. Remove the current activity regardless of policy. + releaseActivityLocked(currentActivity); + return; + } + + if (m_patienceTimer.isActive()) { + // A new Activity is incoming. If there is an ongoing patience release, remove the receiver immediately. + // Any persistent initiator or receiver will not be removed. + m_patienceTimer.stop(); + ACSDK_DEBUG9(LX(__func__).d("status", "Patience Release Timer Stopped")); + releaseActivityLocked(m_patienceReceiver); + m_patienceReceiver = nullptr; + } + + if (incomingActivity->getPatienceDuration().count() > 0) { + // Incoming valid patience Activity + ACSDK_DEBUG9(LX(__func__).d("status", "Patience Timer Started")); + addToChannelUpdatesLocked(currentActivity->getInterface(), FocusState::NONE); + auto patienceDuration = incomingActivity->getPatienceDuration(); + m_patienceTimer.start(patienceDuration, std::bind(&Channel::patienceTimerCallback, this, currentActivity)); + m_patienceInitiator = incomingActivity; + m_patienceReceiver = currentActivity; + } else { + if (m_patienceInitiator != nullptr) { + // No valid patience duration, release the initiator. + releaseActivityLocked(m_patienceInitiator); + m_patienceInitiator = nullptr; + } + releaseActivityLocked(currentActivity); + } +} + +void Channel::patienceTimerCallback( + std::shared_ptr activity) { + std::unique_lock lock(m_mutex); + ACSDK_DEBUG9(LX(__func__).d("status", "Patience Release Timer Triggered")); + releaseActivityLocked(std::move(activity)); + // No need to modify channel updates since it was Already reported + // when patienceInitiator came into focus. +} + +void Channel::updateChannelInterfaceLocked() { + if (!m_activities.empty()) { + m_state.interfaceName = m_activities.front()->getInterface(); + } else { + m_state.interfaceName = ""; + } +} } // namespace afml } // namespace alexaClientSDK diff --git a/AFML/src/FocusManager.cpp b/AFML/src/FocusManager.cpp index 4b425327..59fa92e2 100644 --- a/AFML/src/FocusManager.cpp +++ b/AFML/src/FocusManager.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -13,6 +13,8 @@ * permissions and limitations under the License. */ +#include + #include "AFML/FocusManager.h" #include @@ -22,10 +24,20 @@ namespace afml { using namespace avsCommon::sdkInterfaces; using namespace avsCommon::utils; using namespace avsCommon::avs; +using namespace interruptModel; /// String to identify log entries originating from this file. static const std::string TAG("FocusManager"); +/// Key for @c FocusManager configurations in configuration node. +static const std::string VIRTUAL_CHANNELS_CONFIG_KEY = "virtualChannels"; + +/// Key for the name of the channel in configuration node. +static const std::string CHANNEL_NAME_KEY = "name"; + +/// Key for the priority of the channel in configuration node. +static const std::string CHANNEL_PRIORITY_KEY = "priority"; + /** * Create a LogEntry using this file's TAG and the specified event string. * @@ -34,38 +46,57 @@ static const std::string TAG("FocusManager"); #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) FocusManager::FocusManager( - const std::vector channelConfigurations, - std::shared_ptr activityTrackerInterface) : - m_activityTracker{activityTrackerInterface} { - for (auto config : channelConfigurations) { - if (doesChannelNameExist(config.name)) { - ACSDK_ERROR(LX("createChannelFailed").d("reason", "channelNameExists").d("config", config.toString())); - continue; - } - if (doesChannelPriorityExist(config.priority)) { - ACSDK_ERROR(LX("createChannelFailed").d("reason", "channelPriorityExists").d("config", config.toString())); - continue; - } - - auto channel = std::make_shared(config.name, config.priority); - m_allChannels.insert({config.name, channel}); - } + const std::vector& channelConfigurations, + std::shared_ptr activityTrackerInterface, + const std::vector& virtualChannelConfigurations, + std::shared_ptr interruptModel) : + m_activityTracker{activityTrackerInterface}, + m_interruptModel{interruptModel} { + // Read AVS channel configurations. + readChannelConfiguration(channelConfigurations, false); + // Read virtual channel configurations. + readChannelConfiguration(virtualChannelConfigurations, true); } bool FocusManager::acquireChannel( const std::string& channelName, std::shared_ptr channelObserver, - const std::string& interface) { - ACSDK_DEBUG1(LX("acquireChannel").d("channelName", channelName).d("interface", interface)); + const std::string& interfaceName) { + ACSDK_DEBUG1(LX("acquireChannel").d("channelName", channelName).d("interface", interfaceName)); std::shared_ptr channelToAcquire = getChannel(channelName); if (!channelToAcquire) { ACSDK_ERROR(LX("acquireChannelFailed").d("reason", "channelNotFound").d("channelName", channelName)); return false; } - m_executor.submit([this, channelToAcquire, channelObserver, interface]() { - acquireChannelHelper(channelToAcquire, channelObserver, interface); - }); + auto channelActivity = FocusManagerInterface::Activity::create(interfaceName, channelObserver); + if (!channelActivity) { + ACSDK_ERROR(LX("acquireChannelFailed").d("reason", "failedToCreateActivity").d("interface", interfaceName)); + return false; + } + + m_executor.submit( + [this, channelToAcquire, channelActivity]() { acquireChannelHelper(channelToAcquire, channelActivity); }); + return true; +} + +bool FocusManager::acquireChannel( + const std::string& channelName, + std::shared_ptr channelActivity) { + ACSDK_DEBUG1(LX("acquireChannel").d("channelName", channelName).d("interface", channelActivity->getInterface())); + std::shared_ptr channelToAcquire = getChannel(channelName); + if (!channelToAcquire) { + ACSDK_ERROR(LX("acquireChannelFailed").d("reason", "channelNotFound").d("channelName", channelName)); + return false; + } + + if (!channelActivity) { + ACSDK_ERROR(LX("acquireChannelFailed").d("reason", "channelActivityIsNull")); + return false; + } + + m_executor.submit( + [this, channelToAcquire, channelActivity]() { acquireChannelHelper(channelToAcquire, channelActivity); }); return true; } @@ -120,7 +151,9 @@ void FocusManager::stopAllActivities() { std::unique_lock lock(m_mutex); for (const auto& channel : m_activeChannels) { - channelOwnersCapture.insert(std::pair, std::string>(channel, channel->getInterface())); + for (const auto& interfaceName : channel->getInterfaceList()) { + channelOwnersCapture.insert(std::pair, std::string>(channel, interfaceName)); + } } lock.unlock(); @@ -138,46 +171,125 @@ void FocusManager::removeObserver(const std::shared_ptr& channel, FocusState focus) { - if (!channel->setFocus(focus)) { +void FocusManager::readChannelConfiguration( + const std::vector& channelConfigurations, + bool isVirtual) { + for (const auto& config : channelConfigurations) { + if (doesChannelNameExist(config.name)) { + ACSDK_ERROR( + LX("readChannelConfigurationFailed").d("reason", "channelNameExists").d("config", config.toString())); + continue; + } + if (doesChannelPriorityExist(config.priority)) { + ACSDK_ERROR(LX("readChannelConfigurationFailed") + .d("reason", "channelPriorityExists") + .d("config", config.toString())); + continue; + } + + auto channel = std::make_shared(config.name, config.priority, isVirtual); + m_allChannels.insert({config.name, channel}); + } +} + +void FocusManager::setChannelFocus( + const std::shared_ptr& channel, + FocusState focus, + MixingBehavior behavior, + bool forceUpdate) { + if (!channel->setFocus(focus, behavior, forceUpdate)) { return; } std::unique_lock lock(m_mutex); + // take a copy of the observers auto observers = m_observers; lock.unlock(); + + // inform copy of the observers in an unlocked content for (auto& observer : observers) { observer->onFocusChanged(channel->getName(), focus); } - m_activityUpdates.push_back(channel->getState()); +} + +MixingBehavior FocusManager::getMixingBehavior( + std::shared_ptr lowPrioChannel, + std::shared_ptr highPrioChannel) { + if (!m_interruptModel) { + ACSDK_ERROR(LX(__func__).m("Null InterruptModel")); + return MixingBehavior::UNDEFINED; + } + + if (!lowPrioChannel || !highPrioChannel) { + ACSDK_ERROR(LX("getMixingBehaviorFailed").d("reason", "nullInputChannels")); + return MixingBehavior::UNDEFINED; + } + + if (*lowPrioChannel > *highPrioChannel) { + ACSDK_ERROR(LX("getMixingBehaviorFailed") + .d("reason", "Priorities of input channels violate API contract") + .d("lowPrioChannel priority", lowPrioChannel->getPriority()) + .d("highPrioChannel priority", highPrioChannel->getPriority())); + return MixingBehavior::UNDEFINED; + } + + auto lowPrioChannelName = lowPrioChannel->getName(); + auto lowPrioChannelPrimaryActivity = lowPrioChannel->getPrimaryActivity(); + if (!lowPrioChannelPrimaryActivity) { + ACSDK_ERROR(LX("getMixingBehaviorFailed").d("No PrimaryActivity on lowPrioChannel", lowPrioChannelName)); + return MixingBehavior::UNDEFINED; + } + + auto highPrioChannelName = highPrioChannel->getName(); + auto highPrioChannelPrimaryActivity = highPrioChannel->getPrimaryActivity(); + if (!highPrioChannelPrimaryActivity) { + ACSDK_ERROR(LX("getMixingBehaviorFailed").d("No PrimaryActivity on highPrioChannel", highPrioChannelName)); + return MixingBehavior::UNDEFINED; + } + + return m_interruptModel->getMixingBehavior( + lowPrioChannelName, + lowPrioChannelPrimaryActivity->getContentType(), + highPrioChannelName, + highPrioChannelPrimaryActivity->getContentType()); } void FocusManager::acquireChannelHelper( std::shared_ptr channelToAcquire, - std::shared_ptr channelObserver, - const std::string& interface) { - // Notify the old observer, if there is one, that it lost focus. - setChannelFocus(channelToAcquire, FocusState::NONE); - + std::shared_ptr channelActivity) { // Lock here to update internal state which stopForegroundActivity may concurrently access. std::unique_lock lock(m_mutex); std::shared_ptr foregroundChannel = getHighestPriorityActiveChannelLocked(); - channelToAcquire->setInterface(interface); + // insert the incoming channel m_activeChannels.insert(channelToAcquire); lock.unlock(); - // Set the new observer. - channelToAcquire->setObserver(channelObserver); + ACSDK_DEBUG5(LX(__func__) + .d("incomingChannel", channelToAcquire->getName()) + .d("incomingInterface", channelActivity->getInterface())); + + // attach Activity to the Channel + channelToAcquire->setPrimaryActivity(std::move(channelActivity)); if (!foregroundChannel) { - setChannelFocus(channelToAcquire, FocusState::FOREGROUND); + // channelToAcquire is the only active channel + setChannelFocus(channelToAcquire, FocusState::FOREGROUND, MixingBehavior::PRIMARY); } else if (foregroundChannel == channelToAcquire) { - setChannelFocus(channelToAcquire, FocusState::FOREGROUND); + // acquireChannel request is for the same channel as the current foreground channel + // NOTE : the primaryActivity interface may change , even though focus state has not changed for the channel + setChannelFocus(channelToAcquire, FocusState::FOREGROUND, MixingBehavior::PRIMARY, true); } else if (*channelToAcquire > *foregroundChannel) { - setChannelFocus(foregroundChannel, FocusState::BACKGROUND); - setChannelFocus(channelToAcquire, FocusState::FOREGROUND); + // channelToAcquire will now become the foreground channel, other channels shall be backgrounded + // For each background channel : consult interrupt model to determine the mixability + setBackgroundChannelMixingBehavior(channelToAcquire); + + // set channelToAcquire as Foreground + setChannelFocus(channelToAcquire, FocusState::FOREGROUND, MixingBehavior::PRIMARY); } else { - setChannelFocus(channelToAcquire, FocusState::BACKGROUND); + // channelToAcquire is to be backgrounded + auto mixingBehavior = getMixingBehavior(channelToAcquire, foregroundChannel); + setChannelFocus(channelToAcquire, FocusState::BACKGROUND, mixingBehavior); } + notifyActivityTracker(); } @@ -186,23 +298,28 @@ void FocusManager::releaseChannelHelper( std::shared_ptr channelObserver, std::shared_ptr> releaseChannelSuccess, const std::string& name) { - if (!channelToRelease->doesObserverOwnChannel(channelObserver)) { - ACSDK_ERROR(LX("releaseChannelHelperFailed").d("reason", "observerDoesNotOwnChannel").d("channel", name)); - releaseChannelSuccess->set_value(false); + ACSDK_DEBUG5(LX(__func__).d("channelToRelease", channelToRelease->getName())); + + bool success = channelToRelease->releaseActivity(std::move(channelObserver)); + releaseChannelSuccess->set_value(success); + + if (!success) { + ACSDK_ERROR(LX(__func__) + .d("reason", "releaseActivityFailed") + .d("channel", channelToRelease) + .d("interface", channelToRelease->getInterface())); return; } - releaseChannelSuccess->set_value(true); - // Lock here to update internal state which stopForegroundActivity may concurrently access. - std::unique_lock lock(m_mutex); - bool wasForegrounded = isChannelForegroundedLocked(channelToRelease); - m_activeChannels.erase(channelToRelease); - lock.unlock(); - - setChannelFocus(channelToRelease, FocusState::NONE); - if (wasForegrounded) { - foregroundHighestPriorityActiveChannel(); + // Only release and set entire channel focus to NONE if there are no active Activity remaining. + if (!channelToRelease->isActive()) { + // Lock here to update internal state which stopForegroundActivity may concurrently access. + std::unique_lock lock(m_mutex); + m_activeChannels.erase(channelToRelease); + lock.unlock(); + setChannelFocus(channelToRelease, FocusState::NONE, MixingBehavior::MUST_STOP); } + foregroundHighestPriorityActiveChannel(); notifyActivityTracker(); } @@ -212,15 +329,24 @@ void FocusManager::stopForegroundActivityHelper( if (foregroundChannelInterface != foregroundChannel->getInterface()) { return; } - if (!foregroundChannel->hasObserver()) { - return; + ACSDK_DEBUG5(LX(__func__).d("interface", foregroundChannelInterface)); + bool success = foregroundChannel->releaseActivity(foregroundChannel->getInterface()); + if (!success) { + ACSDK_ERROR(LX(__func__) + .d("reason", "releaseActivityFailed") + .d("channel", foregroundChannel) + .d("interface", foregroundChannel->getInterface())); } - setChannelFocus(foregroundChannel, FocusState::NONE); - // Lock here to update internal state which stopForegroundActivity may concurrently access. - std::unique_lock lock(m_mutex); - m_activeChannels.erase(foregroundChannel); - lock.unlock(); + // Only release and set entire channel focus to NONE if there are no active Activity remaining. + if (!foregroundChannel->isActive()) { + ACSDK_DEBUG1(LX(__func__).m("Channel is not active ... releasing")); + // Lock here to update internal state which stopForegroundActivity may concurrently access. + std::unique_lock lock(m_mutex); + m_activeChannels.erase(foregroundChannel); + lock.unlock(); + setChannelFocus(foregroundChannel, FocusState::NONE, MixingBehavior::MUST_STOP); + } foregroundHighestPriorityActiveChannel(); notifyActivityTracker(); } @@ -233,22 +359,29 @@ void FocusManager::stopAllActivitiesHelper(const ChannelsToInterfaceNamesMap& ch std::unique_lock lock(m_mutex); for (const auto& channelAndInterface : channelsOwnersMap) { - if (channelAndInterface.first->getInterface() == channelAndInterface.second) { - m_activeChannels.erase(channelAndInterface.first); - channelsToClear.insert(channelAndInterface.first); - } else { - ACSDK_INFO(LX(__func__) - .d("reason", "channel has other ownership") - .d("channel", channelAndInterface.first->getName()) - .d("currentInterface", channelAndInterface.first->getInterface()) - .d("originalInterface", channelAndInterface.second)); + auto channel = channelAndInterface.first; + auto interfaceName = channelAndInterface.second; + ACSDK_DEBUG3(LX(__func__).d("channel", channel).d("interface", interfaceName)); + + bool success = channel->releaseActivity(channelAndInterface.second); + if (!success) { + ACSDK_ERROR( + LX(__func__).d("reason", "releaseActivityFailed").d("channel", channel).d("interface", interfaceName)); } + channelsToClear.insert(channel); } lock.unlock(); for (const auto& channel : channelsToClear) { - setChannelFocus(channel, FocusState::NONE); + // Only release and set entire channel focus to NONE if there are no active Activity remaining. + if (!channel->isActive()) { + // Lock here to update internal state which stopForegroundActivity may concurrently access. + std::unique_lock lock(m_mutex); + m_activeChannels.erase(channel); + lock.unlock(); + setChannelFocus(channel, FocusState::NONE, MixingBehavior::MUST_STOP); + } } foregroundHighestPriorityActiveChannel(); notifyActivityTracker(); @@ -278,26 +411,97 @@ bool FocusManager::doesChannelNameExist(const std::string& name) const { } bool FocusManager::doesChannelPriorityExist(const unsigned int priority) const { - for (auto it = m_allChannels.begin(); it != m_allChannels.end(); ++it) { - if (it->second->getPriority() == priority) { + for (const auto& m_allChannel : m_allChannels) { + if (m_allChannel.second->getPriority() == priority) { return true; } } return false; } +void FocusManager::modifyContentType( + const std::string& channelName, + const std::string& interfaceName, + ContentType contentType) { + // find the channel + auto channel = getChannel(channelName); + if (!channel) { + ACSDK_ERROR(LX("modifyContentTypeFailed").d("reason", "channelNotFound").d("channel", channelName)); + return; + } + + // find the activity associated with the interfacename in the channel + auto activity = channel->getActivity(interfaceName); + if (!activity) { + ACSDK_ERROR(LX("modifyContentTypeFailed").d("no activity found associated with interfaceName", interfaceName)); + return; + } + + if (contentType == activity->getContentType()) { + ACSDK_WARN(LX("modifyContentTypeFailed").d("no contentType to modify it is already identical: ", contentType)); + return; + } + + // modify the contentType associated with the activity + activity->setContentType(contentType); + + // reconsult the InterruptModel and set the new MixingBehaviors for all backgrounded channelobservers + std::unique_lock lock(m_mutex); + std::shared_ptr foregroundChannel = getHighestPriorityActiveChannelLocked(); + lock.unlock(); + setBackgroundChannelMixingBehavior(foregroundChannel); +} + +void FocusManager::setBackgroundChannelMixingBehavior(std::shared_ptr foregroundChannel) { + std::unique_lock lock(m_mutex); + + auto channelIter = m_activeChannels.find(foregroundChannel); + if (channelIter == m_activeChannels.end()) { + ACSDK_ERROR( + LX("setBackgroundChannelMixingBehaviorFailed").d("Could not find channel", foregroundChannel->getName())); + return; + } + // skip to the next channel in priority + channelIter++; + + for (; channelIter != m_activeChannels.end(); channelIter++) { + // determine mixingBehavior for each background channel + auto mixingBehavior = getMixingBehavior(*channelIter, foregroundChannel); + lock.unlock(); + setChannelFocus(*channelIter, FocusState::BACKGROUND, mixingBehavior); + lock.lock(); + } +} + void FocusManager::foregroundHighestPriorityActiveChannel() { std::unique_lock lock(m_mutex); std::shared_ptr channelToForeground = getHighestPriorityActiveChannelLocked(); lock.unlock(); if (channelToForeground) { - setChannelFocus(channelToForeground, FocusState::FOREGROUND); + // inform background channels of the new MixingBehavior as per the new Foreground Channel + setBackgroundChannelMixingBehavior(channelToForeground); + + // Foreground the highest priority channel + setChannelFocus(channelToForeground, FocusState::FOREGROUND, MixingBehavior::PRIMARY); } } void FocusManager::notifyActivityTracker() { - if (m_activityTracker) { + std::unique_lock lock(m_mutex); + for (const auto& channel : m_allChannels) { + auto activityUpdates = channel.second->getActivityUpdates(); + for (const auto& activity : activityUpdates) { + m_activityUpdates.push_back(activity); + ACSDK_DEBUG1(LX(__func__) + .d("name", activity.name) + .d("interfaceName", activity.interfaceName) + .d("focusState", activity.focusState)); + } + } + lock.unlock(); + + if (m_activityTracker && !m_activityUpdates.empty()) { m_activityTracker->notifyOfActivityUpdates(m_activityUpdates); } m_activityUpdates.clear(); @@ -320,5 +524,59 @@ const std::vector FocusManager::getDefaultVi return defaultVisualChannels; } +bool afml::FocusManager::ChannelConfiguration::readChannelConfiguration( + const std::string& channelTypeKey, + std::vector* virtualChannelConfigurations) { + if (!virtualChannelConfigurations) { + ACSDK_ERROR(LX("readChannelConfigurationFailed").d("reason", "nullVirtualChannelConfiguration")); + return false; + } + + auto configRoot = + alexaClientSDK::avsCommon::utils::configuration::ConfigurationNode::getRoot()[VIRTUAL_CHANNELS_CONFIG_KEY]; + if (!configRoot) { + ACSDK_DEBUG9(LX(__func__).m("noConfigurationRoot")); + return true; + } + + bool returnValue = true; + auto channelArray = configRoot.getArray(channelTypeKey); + if (!channelArray) { + ACSDK_DEBUG9(LX(__func__).d("key", channelTypeKey).m("keyNotFoundOrNotAnArray")); + } else { + for (std::size_t i = 0; i < channelArray.getArraySize(); i++) { + auto elem = channelArray[i]; + if (!elem) { + ACSDK_ERROR(LX("readChannelConfigurationFailed").d("reason", "noNameKey")); + returnValue = false; + break; + } + + std::string name; + if (!elem.getString(CHANNEL_NAME_KEY, &name)) { + ACSDK_ERROR(LX("readChannelConfigurationFailed").d("reason", "noNameKey")); + returnValue = false; + break; + } + + int priority = 0; + if (!elem.getInt(CHANNEL_PRIORITY_KEY, &priority)) { + ACSDK_ERROR(LX("readChannelConfigurationFailed").d("reason", "noPriorityKey")); + returnValue = false; + break; + } + if (priority < 0) { + ACSDK_ERROR(LX("ChannelConfigurationFailed").d("reason", "invalidPriority").d("priority", priority)); + returnValue = false; + break; + } + + afml::FocusManager::ChannelConfiguration channelConfig{name, static_cast(priority)}; + virtualChannelConfigurations->push_back(channelConfig); + } + } + return returnValue; +} + } // namespace afml } // namespace alexaClientSDK diff --git a/AFML/test/AudioActivityTrackerTest.cpp b/AFML/test/AudioActivityTrackerTest.cpp index 523285a0..0c21a268 100644 --- a/AFML/test/AudioActivityTrackerTest.cpp +++ b/AFML/test/AudioActivityTrackerTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018-2020 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. @@ -44,7 +44,7 @@ using namespace avsCommon::utils::json; using namespace ::testing; /// Plenty of time for a test to complete. -static std::chrono::milliseconds WAIT_TIMEOUT(1000); +static std::chrono::milliseconds MY_WAIT_TIMEOUT(1000); /// Namespace for AudioActivityTracke. static const std::string NAMESPACE_AUDIO_ACTIVITY_TRACKER("AudioActivityTracker"); @@ -79,6 +79,13 @@ static unsigned int CONTENT_CHANNEL_PRIORITY{300}; /// Timeout to sleep before asking for provideState(). static const std::chrono::milliseconds SHORT_TIMEOUT_MS = std::chrono::milliseconds(5); +/// MockChannelObserver for tests +class MockChannelObserver : public avsCommon::sdkInterfaces::ChannelObserverInterface { +public: + void onFocusChanged(avsCommon::avs::FocusState state, avsCommon::avs::MixingBehavior behavior) override { + } +}; + class AudioActivityTrackerTest : public ::testing::Test { public: AudioActivityTrackerTest(); @@ -139,11 +146,9 @@ void AudioActivityTrackerTest::SetUp() { ASSERT_TRUE(m_mockContextManager != nullptr); m_dialogChannel = std::make_shared(DIALOG_CHANNEL_NAME, DIALOG_CHANNEL_PRIORITY); - m_dialogChannel->setInterface(DIALOG_INTERFACE_NAME); ASSERT_TRUE(m_dialogChannel != nullptr); m_contentChannel = std::make_shared(CONTENT_CHANNEL_NAME, CONTENT_CHANNEL_PRIORITY); - m_contentChannel->setInterface(CONTENT_INTERFACE_NAME); ASSERT_TRUE(m_contentChannel != nullptr); } @@ -207,7 +212,7 @@ void AudioActivityTrackerTest::provideUpdate(const std::vector& m_audioActivityTracker->notifyOfActivityUpdates(channels); std::this_thread::sleep_for(SHORT_TIMEOUT_MS); m_audioActivityTracker->provideState(NAMESPACE_AND_NAME_STATE, PROVIDE_STATE_TOKEN_TEST); - ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(WAIT_TIMEOUT)); + ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(MY_WAIT_TIMEOUT)); } SetStateResult AudioActivityTrackerTest::wakeOnSetState() { @@ -224,7 +229,7 @@ TEST_F(AudioActivityTrackerTest, test_noActivityUpdate) { .WillOnce(InvokeWithoutArgs(this, &AudioActivityTrackerTest::wakeOnSetState)); m_audioActivityTracker->provideState(NAMESPACE_AND_NAME_STATE, PROVIDE_STATE_TOKEN_TEST); - ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(WAIT_TIMEOUT)); + ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(MY_WAIT_TIMEOUT)); } /// Test if there's an empty set of activity updates, AudioActivityTracker will return an empty context. @@ -238,13 +243,13 @@ TEST_F(AudioActivityTrackerTest, test_emptyActivityUpdate) { m_audioActivityTracker->notifyOfActivityUpdates(channels); m_audioActivityTracker->provideState(NAMESPACE_AND_NAME_STATE, PROVIDE_STATE_TOKEN_TEST); - ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(WAIT_TIMEOUT)); + ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(MY_WAIT_TIMEOUT)); } /// Test if there's an activityUpdate for one active channel, context will be reported correctly. TEST_F(AudioActivityTrackerTest, test_oneActiveChannel) { std::vector channels; - m_dialogChannel->setFocus(FocusState::FOREGROUND); + m_dialogChannel->setFocus(FocusState::FOREGROUND, MixingBehavior::PRIMARY); channels.push_back(m_dialogChannel->getState()); provideUpdate(channels); } @@ -255,8 +260,11 @@ TEST_F(AudioActivityTrackerTest, test_oneActiveChannel) { */ TEST_F(AudioActivityTrackerTest, test_oneActiveChannelWithAIPAsInterface) { std::vector channels; - m_dialogChannel->setInterface(AIP_INTERFACE_NAME); - m_dialogChannel->setFocus(FocusState::FOREGROUND); + auto mockObserver = std::make_shared(); + auto aipActivity = + avsCommon::sdkInterfaces::FocusManagerInterface::Activity::create(AIP_INTERFACE_NAME, mockObserver); + m_dialogChannel->setPrimaryActivity(aipActivity); + m_dialogChannel->setFocus(FocusState::FOREGROUND, MixingBehavior::PRIMARY); channels.push_back(m_dialogChannel->getState()); EXPECT_CALL( *(m_mockContextManager.get()), @@ -267,7 +275,7 @@ TEST_F(AudioActivityTrackerTest, test_oneActiveChannelWithAIPAsInterface) { m_audioActivityTracker->notifyOfActivityUpdates(channels); std::this_thread::sleep_for(SHORT_TIMEOUT_MS); m_audioActivityTracker->provideState(NAMESPACE_AND_NAME_STATE, PROVIDE_STATE_TOKEN_TEST); - ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(WAIT_TIMEOUT)); + ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(MY_WAIT_TIMEOUT)); } /* @@ -277,7 +285,7 @@ TEST_F(AudioActivityTrackerTest, test_oneActiveChannelWithAIPAsInterface) { */ TEST_F(AudioActivityTrackerTest, test_oneActiveChannelWithDefaultAndAIPAsInterfaces) { std::vector channels; - m_dialogChannel->setFocus(FocusState::FOREGROUND); + m_dialogChannel->setFocus(FocusState::FOREGROUND, MixingBehavior::PRIMARY); channels.push_back(m_dialogChannel->getState()); provideUpdate(channels); } @@ -285,8 +293,8 @@ TEST_F(AudioActivityTrackerTest, test_oneActiveChannelWithDefaultAndAIPAsInterfa /// Test if there's an activityUpdate for two active channels, context will be reported correctly. TEST_F(AudioActivityTrackerTest, test_twoActiveChannels) { std::vector channels; - m_dialogChannel->setFocus(FocusState::FOREGROUND); - m_contentChannel->setFocus(FocusState::BACKGROUND); + m_dialogChannel->setFocus(FocusState::FOREGROUND, MixingBehavior::PRIMARY); + m_contentChannel->setFocus(FocusState::BACKGROUND, MixingBehavior::MUST_PAUSE); channels.push_back(m_dialogChannel->getState()); channels.push_back(m_contentChannel->getState()); provideUpdate(channels); @@ -295,10 +303,10 @@ TEST_F(AudioActivityTrackerTest, test_twoActiveChannels) { /// Test if there's an activityUpdate for one active and one idle channels, context will be reported correctly. TEST_F(AudioActivityTrackerTest, test_oneActiveOneIdleChannels) { std::vector channels; - m_dialogChannel->setFocus(FocusState::FOREGROUND); - m_contentChannel->setFocus(FocusState::BACKGROUND); - m_dialogChannel->setFocus(FocusState::NONE); - m_contentChannel->setFocus(FocusState::FOREGROUND); + m_dialogChannel->setFocus(FocusState::FOREGROUND, MixingBehavior::PRIMARY); + m_contentChannel->setFocus(FocusState::BACKGROUND, MixingBehavior::MUST_PAUSE); + m_dialogChannel->setFocus(FocusState::NONE, MixingBehavior::MUST_STOP); + m_contentChannel->setFocus(FocusState::FOREGROUND, MixingBehavior::PRIMARY); channels.push_back(m_dialogChannel->getState()); channels.push_back(m_contentChannel->getState()); provideUpdate(channels); diff --git a/AFML/test/FocusManagerTest.cpp b/AFML/test/FocusManagerTest.cpp index 6798968a..0d4ecaf7 100644 --- a/AFML/test/FocusManagerTest.cpp +++ b/AFML/test/FocusManagerTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -13,13 +13,16 @@ * permissions and limitations under the License. */ +#include #include #include #include - +#include #include "AFML/FocusManager.h" +#include "InterruptModel/InterruptModel.h" + namespace alexaClientSDK { namespace afml { namespace test { @@ -27,6 +30,9 @@ namespace test { using namespace avsCommon::sdkInterfaces; using namespace avsCommon::sdkInterfaces::test; using namespace avsCommon::avs; +using namespace alexaClientSDK::avsCommon::utils::configuration; +/// Alias for JSON stream type used in @c ConfigurationNode initialization +using JSONStream = std::vector>; /// Short time out for when callbacks are expected not to occur. static const auto SHORT_TIMEOUT = std::chrono::milliseconds(50); @@ -34,18 +40,27 @@ static const auto SHORT_TIMEOUT = std::chrono::milliseconds(50); /// Long time out for the Channel observer to wait for the focus change callback (we should not reach this). static const auto DEFAULT_TIMEOUT = std::chrono::seconds(15); +/// Time out to wait for ActivityUpdates. +static const auto NO_ACTIVITY_UPDATE_TIMEOUT = std::chrono::milliseconds(250); + /// The dialog Channel name used in initializing the FocusManager. -static const std::string DIALOG_CHANNEL_NAME = "DialogChannel"; +static const std::string DIALOG_CHANNEL_NAME = "Dialog"; /// The alerts Channel name used in initializing the FocusManager. -static const std::string ALERTS_CHANNEL_NAME = "AlertsChannel"; +static const std::string ALERTS_CHANNEL_NAME = "Alert"; /// The content Channel name used in initializing the FocusManager. -static const std::string CONTENT_CHANNEL_NAME = "ContentChannel"; +static const std::string CONTENT_CHANNEL_NAME = "Content"; /// An incorrect Channel name that is never initialized as a Channel. static const std::string INCORRECT_CHANNEL_NAME = "aksdjfl;aksdjfl;akdsjf"; +/// The virtual Channel name used in initializing the FocusManager. +static const std::string VIRTUAL_CHANNEL_NAME = "VirtualChannel"; + +/// The priority of the virtual Channel used in initializing the FocusManager. +static const unsigned int VIRTUAL_CHANNEL_PRIORITY = 25; + /// The priority of the dialog Channel used in initializing the FocusManager. static const unsigned int DIALOG_CHANNEL_PRIORITY = 10; @@ -64,30 +79,206 @@ static const std::string ALERTS_INTERFACE_NAME = "alerts"; /// Sample content interface name. static const std::string CONTENT_INTERFACE_NAME = "content"; +/// Another sample content interface name. +static const std::string DIFFERENT_CONTENT_INTERFACE_NAME = "different content"; + /// Another sample dialog interface name. static const std::string DIFFERENT_DIALOG_INTERFACE_NAME = "different dialog"; +/// Sample Virtual Channel interface name. +static const std::string VIRTUAL_INTERFACE_NAME = "virtual"; + +/// The root key for the InterruptModel in the @c InterruptModelConfiguration +static const std::string INTERRUPT_MODEL_ROOT_KEY = "interruptModel"; + +/// The Json that describes the InterruptModelConfiguration +static const std::string INTERRUPT_MODEL_CONFIG_JSON = R"({ +"interruptModel" : { + "Dialog" : { + }, + "Communications" : { + "contentType": + { + "MIXABLE" : { + "incomingChannel" : { + "Dialog" : { + "incomingContentType" : { + "MIXABLE" : "MAY_DUCK" + } + } + } + }, + "NONMIXABLE" : { + "incomingChannel" : { + "Dialog" : { + "incomingContentType" : { + "MIXABLE" : "MAY_PAUSE" + } + } + } + } + } + }, + "Alert" : { + "contentType" : + { + "MIXABLE" : { + "incomingChannel" : { + "Dialog" : { + "incomingContentType" : { + "MIXABLE" : "MAY_DUCK" + } + }, + "Communications" : { + "incomingContentType" : { + "MIXABLE" : "MAY_DUCK", + "NONMIXABLE" : "MAY_DUCK" + } + } + } + } + } + }, + "VirtualChannel" : { + "contentType" : + { + "MIXABLE" : { + "incomingChannel" : { + "Dialog" : { + "incomingContentType" : { + "MIXABLE" : "MAY_DUCK" + } + }, + "Communications" : { + "incomingContentType" : { + "MIXABLE" : "MAY_DUCK", + "NONMIXABLE" : "MAY_DUCK" + } + } + } + } + } + }, + "Content" : { + "contentType" : + { + "MIXABLE" : { + "incomingChannel" : { + "Dialog" : { + "incomingContentType" : { + "MIXABLE" : "MAY_DUCK" + } + }, + "Communications" : { + "incomingContentType" : { + "MIXABLE" : "MAY_DUCK", + "NONMIXABLE" : "MUST_PAUSE" + } + }, + "Alert" : { + "incomingContentType" : { + "MIXABLE" : "MAY_DUCK" + } + }, + "VirtualChannel" : { + "incomingContentType" : { + "MIXABLE" : "MAY_DUCK" + } + } + } + }, + "NONMIXABLE" : { + "incomingChannel" : { + "Dialog" : { + "incomingContentType" : { + "MIXABLE" : "MUST_PAUSE" + } + }, + "Communications" : { + "incomingContentType" : { + "MIXABLE" : "MUST_PAUSE", + "NONMIXABLE" : "MUST_PAUSE" + } + }, + "Alert" : { + "incomingContentType" : { + "MIXABLE" : "MUST_PAUSE" + } + } + } + } + } + } + } + } +)"; + /// A test observer that mocks out the ChannelObserverInterface##onFocusChanged() call. -class TestClient : public ChannelObserverInterface { +class TestClient + : public ChannelObserverInterface + , public std::enable_shared_from_this { public: /** * Constructor. */ - TestClient() : m_focusState(FocusState::NONE), m_focusChangeOccurred(false) { + TestClient(const std::string& channelName, const std::string& interfaceName) : + m_channelName(channelName), + m_interfaceName(interfaceName), + m_focusState(FocusState::NONE), + m_mixingBehavior(MixingBehavior::UNDEFINED), + m_focusChangeCallbackInvoked(false), + m_mixingBehaviorChanged(false) { + } + + std::shared_ptr createActivity( + ContentType contentType = ContentType::NONMIXABLE, + std::chrono::milliseconds patience = std::chrono::milliseconds(0)) { + m_contentType = contentType; + return FocusManagerInterface::Activity::create(m_interfaceName, shared_from_this(), patience, contentType); + } + + const std::string& getChannelName() const { + return m_channelName; + } + + const std::string& getInterfaceName() const { + return m_interfaceName; } /** * Implementation of the ChannelObserverInterface##onFocusChanged() callback. * * @param focusState The new focus state of the Channel observer. + * @param behavior The new MixingBehavior of the Channel observer. */ - void onFocusChanged(FocusState focusState) override { + void onFocusChanged(FocusState focusState, MixingBehavior behavior) override { std::unique_lock lock(m_mutex); m_focusState = focusState; - m_focusChangeOccurred = true; + m_mixingBehaviorChanged = (m_mixingBehavior != behavior); + m_mixingBehavior = behavior; + m_focusChangeCallbackInvoked = true; m_focusChanged.notify_one(); } + struct testClientInfo { + FocusState focusState; + MixingBehavior mixingBehavior; + bool focusChanged; + bool mixingBehaviorChanged; + testClientInfo(FocusState state, MixingBehavior behavior, bool focusChg, bool mixingBehaviorChg) : + focusState{state}, + mixingBehavior{behavior}, + focusChanged{focusChg}, + mixingBehaviorChanged{mixingBehaviorChg} { + } + testClientInfo() : + focusState{FocusState::NONE}, + mixingBehavior{MixingBehavior::UNDEFINED}, + focusChanged{false}, + mixingBehaviorChanged{false} { + } + }; + /** * Waits for the ChannelObserverInterface##onFocusChanged() callback. * @@ -95,23 +286,42 @@ public: * @param focusChanged An output parameter that notifies the caller whether a callback occurred. * @return Returns @c true if the callback occured within the timeout period and @c false otherwise. */ - FocusState waitForFocusChange(std::chrono::milliseconds timeout, bool* focusChanged) { + testClientInfo waitForFocusOrMixingBehaviorChange(std::chrono::milliseconds timeout) { std::unique_lock lock(m_mutex); - bool success = m_focusChanged.wait_for(lock, timeout, [this]() { return m_focusChangeOccurred; }); + auto success = m_focusChanged.wait_for( + lock, timeout, [this]() { return m_focusChangeCallbackInvoked || m_mixingBehaviorChanged; }); + testClientInfo ret; if (!success) { - *focusChanged = false; + ret.focusChanged = false; + ret.mixingBehaviorChanged = false; } else { - m_focusChangeOccurred = false; - *focusChanged = true; + m_focusChangeCallbackInvoked = false; + ret.focusChanged = true; + ret.mixingBehaviorChanged = m_mixingBehaviorChanged; + m_mixingBehaviorChanged = false; } - return m_focusState; + + ret.focusState = m_focusState; + ret.mixingBehavior = m_mixingBehavior; + + return ret; } private: + std::string m_channelName; + + std::string m_interfaceName; + + /// The ContentType of the Activity. + ContentType m_contentType; + /// The focus state of the observer. FocusState m_focusState; + /// The MixingBehavior of the observer. + MixingBehavior m_mixingBehavior; + /// A lock to guard against focus state changes. std::mutex m_mutex; @@ -119,7 +329,10 @@ private: std::condition_variable m_focusChanged; /// A boolean flag so that we can re-use the observer even after a callback has occurred. - bool m_focusChangeOccurred; + bool m_focusChangeCallbackInvoked; + + /// A re-usable boolean flag to indicate that the MixingBehavior changed + bool m_mixingBehaviorChanged; }; /// A test observer that mocks out the ActivityTrackerInterface##notifyOfActivityUpdates() call. @@ -152,7 +365,7 @@ public: std::unique_lock lock(m_mutex); m_updatedChannels.clear(); for (auto& channel : channelStates) { - m_updatedChannels.push_back(channel); + m_updatedChannels[channel.interfaceName] = channel; } m_activityUpdatesOccurred = true; m_activityChanged.notify_one(); @@ -172,10 +385,11 @@ public: if (m_activityUpdatesOccurred) { EXPECT_EQ(m_updatedChannels.size(), expected.size()); auto count = 0; - for (auto& channel : m_updatedChannels) { - EXPECT_EQ(channel.name, expected[count].name); - EXPECT_EQ(channel.interfaceName, expected[count].interfaceName); - EXPECT_EQ(channel.focusState, expected[count].focusState); + for (auto& expectedChannel : expected) { + auto& channel = m_updatedChannels[expectedChannel.interfaceName]; + EXPECT_EQ(channel.name, expectedChannel.name); + EXPECT_EQ(channel.interfaceName, expectedChannel.interfaceName); + EXPECT_EQ(channel.focusState, expectedChannel.focusState); count++; } } @@ -188,9 +402,20 @@ public: ASSERT_TRUE(success); } + /** + * Waits for the if there's a ActivityTrackerInterface##notifyOfActivityUpdates() callback. + * + * @param timeout The amount of time to wait for the callback. + */ + bool waitForNoActivityUpdates(std::chrono::milliseconds timeout) { + std::unique_lock lock(m_mutex); + m_activityChanged.wait_for(lock, timeout); + return m_activityUpdatesOccurred; + } + private: - /// The focus state of the observer. - std::vector m_updatedChannels; + /// The focus state of the observer. The key is the interface name. + std::unordered_map m_updatedChannels; /// A lock to guard against activity changes. std::mutex m_mutex; @@ -205,17 +430,21 @@ private: /// Manages testing focus changes class FocusChangeManager { public: + TestClient::testClientInfo getWaitResult(std::shared_ptr client) { + return client->waitForFocusOrMixingBehaviorChange(DEFAULT_TIMEOUT); + } + /** - * Checks that a focus change occurred and that the focus state received is the same as the expected focus state. + * Checks that a focus change occurred and that the focus state received is the same as the expected focus + * state. * * @param client The Channel observer. * @param expectedFocusState The expected focus state. */ void assertFocusChange(std::shared_ptr client, FocusState expectedFocusState) { - bool focusChanged = false; - FocusState focusStateReceived = client->waitForFocusChange(DEFAULT_TIMEOUT, &focusChanged); - ASSERT_TRUE(focusChanged); - ASSERT_EQ(expectedFocusState, focusStateReceived); + auto waitResult = getWaitResult(client); + ASSERT_TRUE(waitResult.focusChanged); + ASSERT_EQ(expectedFocusState, waitResult.focusState); } /** @@ -224,10 +453,41 @@ public: * @param client The Channel observer. */ void assertNoFocusChange(std::shared_ptr client) { - bool focusChanged = false; // Will wait for the short timeout duration before succeeding - client->waitForFocusChange(SHORT_TIMEOUT, &focusChanged); - ASSERT_FALSE(focusChanged); + auto ret = client->waitForFocusOrMixingBehaviorChange(SHORT_TIMEOUT); + ASSERT_FALSE(ret.focusChanged); + } + + void assertMixingBehaviorChange(std::shared_ptr client, MixingBehavior behavior) { + // Will wait for the short timeout duration before succeeding + auto ret = client->waitForFocusOrMixingBehaviorChange(SHORT_TIMEOUT); + ASSERT_TRUE(ret.mixingBehaviorChanged); + ASSERT_EQ(ret.mixingBehavior, behavior); + } + + void assertNoMixingBehaviorChange(std::shared_ptr client) { + // Will wait for the short timeout duration before succeeding + auto ret = client->waitForFocusOrMixingBehaviorChange(SHORT_TIMEOUT); + ASSERT_FALSE(ret.mixingBehaviorChanged); + } + + void assertNoMixingBehaviorOrFocusChange(std::shared_ptr client) { + // Will wait for the short timeout duration before succeeding + auto ret = client->waitForFocusOrMixingBehaviorChange(SHORT_TIMEOUT); + ASSERT_FALSE(ret.mixingBehaviorChanged); + ASSERT_FALSE(ret.focusChanged); + } + + void assertMixingBehaviorAndFocusChange( + std::shared_ptr client, + FocusState expectedFocusState, + MixingBehavior behavior) { + // Will wait for the short timeout duration before succeeding + auto ret = client->waitForFocusOrMixingBehaviorChange(SHORT_TIMEOUT); + ASSERT_TRUE(ret.mixingBehaviorChanged); + ASSERT_TRUE(ret.focusChanged); + ASSERT_EQ(expectedFocusState, ret.focusState); + ASSERT_EQ(behavior, ret.mixingBehavior); } }; @@ -251,26 +511,57 @@ protected: /// A client that acquires the content Channel. std::shared_ptr contentClient; + /// A client that acquires the content Channel. + std::shared_ptr anotherContentClient; + + /// A client that acquires the virtual Channel. + std::shared_ptr virtualClient; + + /// Mock Activity Tracker std::shared_ptr m_activityTracker; + /// Interrupt Model + std::shared_ptr m_interruptModel; + + ConfigurationNode generateInterruptModelConfig() { + auto stream = std::shared_ptr(new std::istringstream(INTERRUPT_MODEL_CONFIG_JSON)); + std::vector> jsonStream({stream}); + ConfigurationNode::initialize(jsonStream); + return ConfigurationNode::getRoot(); + } + virtual void SetUp() { m_activityTracker = std::make_shared(); FocusManager::ChannelConfiguration dialogChannelConfig{DIALOG_CHANNEL_NAME, DIALOG_CHANNEL_PRIORITY}; - FocusManager::ChannelConfiguration alertsChannelConfig{ALERTS_CHANNEL_NAME, ALERTS_CHANNEL_PRIORITY}; - FocusManager::ChannelConfiguration contentChannelConfig{CONTENT_CHANNEL_NAME, CONTENT_CHANNEL_PRIORITY}; + FocusManager::ChannelConfiguration virtualChannelConfig{VIRTUAL_CHANNEL_NAME, VIRTUAL_CHANNEL_PRIORITY}; std::vector channelConfigurations{ dialogChannelConfig, alertsChannelConfig, contentChannelConfig}; - m_focusManager = std::make_shared(channelConfigurations, m_activityTracker); + dialogClient = std::make_shared(DIALOG_CHANNEL_NAME, DIALOG_INTERFACE_NAME); + alertsClient = std::make_shared(ALERTS_CHANNEL_NAME, ALERTS_INTERFACE_NAME); + contentClient = std::make_shared(CONTENT_CHANNEL_NAME, CONTENT_INTERFACE_NAME); + anotherContentClient = std::make_shared(CONTENT_CHANNEL_NAME, DIFFERENT_CONTENT_INTERFACE_NAME); + anotherDialogClient = std::make_shared(DIALOG_CHANNEL_NAME, DIFFERENT_DIALOG_INTERFACE_NAME); + virtualClient = std::make_shared(VIRTUAL_CHANNEL_NAME, VIRTUAL_INTERFACE_NAME); - dialogClient = std::make_shared(); - alertsClient = std::make_shared(); - contentClient = std::make_shared(); - anotherDialogClient = std::make_shared(); + std::vector virtualChannelConfigurations{virtualChannelConfig}; + + m_interruptModel = + interruptModel::InterruptModel::create(generateInterruptModelConfig()[INTERRUPT_MODEL_ROOT_KEY]); + m_focusManager = std::make_shared( + channelConfigurations, m_activityTracker, virtualChannelConfigurations, m_interruptModel); + } + + bool acquireChannelHelper( + std::shared_ptr client, + ContentType contentType = ContentType::NONMIXABLE, + std::chrono::milliseconds patience = std::chrono::milliseconds(0)) { + auto activity = client->createActivity(contentType, patience); + return m_focusManager->acquireChannel(client->getChannelName(), activity); } }; @@ -281,8 +572,8 @@ TEST_F(FocusManagerTest, test_acquireInvalidChannelName) { /// Tests acquireChannel, expecting to get Foreground status since no other Channels are active. TEST_F(FocusManagerTest, test_acquireChannelWithNoOtherChannelsActive) { - ASSERT_TRUE(m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, dialogClient, DIALOG_INTERFACE_NAME)); - assertFocusChange(dialogClient, FocusState::FOREGROUND); + ASSERT_TRUE(acquireChannelHelper(dialogClient, ContentType::MIXABLE)); + assertMixingBehaviorAndFocusChange(dialogClient, FocusState::FOREGROUND, MixingBehavior::PRIMARY); } /** @@ -290,10 +581,10 @@ TEST_F(FocusManagerTest, test_acquireChannelWithNoOtherChannelsActive) { * priority Channel should get Foreground focus. */ TEST_F(FocusManagerTest, test_acquireLowerPriorityChannelWithOneHigherPriorityChannelTaken) { - ASSERT_TRUE(m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, dialogClient, DIALOG_INTERFACE_NAME)); - ASSERT_TRUE(m_focusManager->acquireChannel(ALERTS_CHANNEL_NAME, alertsClient, ALERTS_INTERFACE_NAME)); - assertFocusChange(dialogClient, FocusState::FOREGROUND); - assertFocusChange(alertsClient, FocusState::BACKGROUND); + ASSERT_TRUE(acquireChannelHelper(dialogClient, ContentType::MIXABLE)); + ASSERT_TRUE(acquireChannelHelper(alertsClient, ContentType::MIXABLE)); + assertMixingBehaviorAndFocusChange(dialogClient, FocusState::FOREGROUND, MixingBehavior::PRIMARY); + assertMixingBehaviorAndFocusChange(alertsClient, FocusState::BACKGROUND, MixingBehavior::MAY_DUCK); } /** @@ -301,12 +592,28 @@ TEST_F(FocusManagerTest, test_acquireLowerPriorityChannelWithOneHigherPriorityCh * highest priority Channel should be Foreground focused. */ TEST_F(FocusManagerTest, test_aquireLowerPriorityChannelWithTwoHigherPriorityChannelsTaken) { - ASSERT_TRUE(m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, dialogClient, DIALOG_INTERFACE_NAME)); - ASSERT_TRUE(m_focusManager->acquireChannel(ALERTS_CHANNEL_NAME, alertsClient, ALERTS_INTERFACE_NAME)); - ASSERT_TRUE(m_focusManager->acquireChannel(CONTENT_CHANNEL_NAME, contentClient, CONTENT_INTERFACE_NAME)); - assertFocusChange(dialogClient, FocusState::FOREGROUND); - assertFocusChange(alertsClient, FocusState::BACKGROUND); - assertFocusChange(contentClient, FocusState::BACKGROUND); + ASSERT_TRUE(acquireChannelHelper(dialogClient, ContentType::MIXABLE)); + ASSERT_TRUE(acquireChannelHelper(alertsClient, ContentType::MIXABLE)); + ASSERT_TRUE(acquireChannelHelper(contentClient, ContentType::MIXABLE)); + assertMixingBehaviorAndFocusChange(dialogClient, FocusState::FOREGROUND, MixingBehavior::PRIMARY); + assertMixingBehaviorAndFocusChange(alertsClient, FocusState::BACKGROUND, MixingBehavior::MAY_DUCK); + assertMixingBehaviorAndFocusChange(contentClient, FocusState::BACKGROUND, MixingBehavior::MAY_DUCK); +} + +/** + * Tests acquireChannel virtual channel is working the same as other channels. We test with three Channels (one + * virtual). First we acquire a non-virtual channel and make sure it goes FOREGROUND. Then we acquire a virtual channel + * with higher prioirty and one non-virtual with lower priority and make sure the two lowest priority Channels should + * get Background focus while the highest priority Channel should be Foreground focused. + */ +TEST_F(FocusManagerTest, acquireVirtualChannelWithTwoLowerPriorityChannelsTaken) { + ASSERT_TRUE(acquireChannelHelper(dialogClient, ContentType::MIXABLE)); + assertMixingBehaviorAndFocusChange(dialogClient, FocusState::FOREGROUND, MixingBehavior::PRIMARY); + + ASSERT_TRUE(acquireChannelHelper(contentClient, ContentType::MIXABLE)); + ASSERT_TRUE(acquireChannelHelper(virtualClient, ContentType::MIXABLE)); + assertMixingBehaviorAndFocusChange(virtualClient, FocusState::BACKGROUND, MixingBehavior::MAY_DUCK); + assertMixingBehaviorAndFocusChange(contentClient, FocusState::BACKGROUND, MixingBehavior::MAY_DUCK); } /** @@ -315,12 +622,12 @@ TEST_F(FocusManagerTest, test_aquireLowerPriorityChannelWithTwoHigherPriorityCha * should be Foreground focused. */ TEST_F(FocusManagerTest, test_acquireHigherPriorityChannelWithOneLowerPriorityChannelTaken) { - ASSERT_TRUE(m_focusManager->acquireChannel(CONTENT_CHANNEL_NAME, contentClient, CONTENT_INTERFACE_NAME)); - assertFocusChange(contentClient, FocusState::FOREGROUND); + ASSERT_TRUE(acquireChannelHelper(contentClient, ContentType::MIXABLE)); + assertMixingBehaviorAndFocusChange(contentClient, FocusState::FOREGROUND, MixingBehavior::PRIMARY); - ASSERT_TRUE(m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, dialogClient, DIALOG_INTERFACE_NAME)); - assertFocusChange(contentClient, FocusState::BACKGROUND); - assertFocusChange(dialogClient, FocusState::FOREGROUND); + ASSERT_TRUE(acquireChannelHelper(dialogClient, ContentType::MIXABLE)); + assertMixingBehaviorAndFocusChange(contentClient, FocusState::BACKGROUND, MixingBehavior::MAY_DUCK); + assertMixingBehaviorAndFocusChange(dialogClient, FocusState::FOREGROUND, MixingBehavior::PRIMARY); } /** @@ -328,38 +635,37 @@ TEST_F(FocusManagerTest, test_acquireHigherPriorityChannelWithOneLowerPriorityCh * should obtain Foreground focus. */ TEST_F(FocusManagerTest, test_kickOutActivityOnSameChannel) { - ASSERT_TRUE(m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, dialogClient, DIALOG_INTERFACE_NAME)); - assertFocusChange(dialogClient, FocusState::FOREGROUND); + ASSERT_TRUE(acquireChannelHelper(dialogClient)); + assertMixingBehaviorAndFocusChange(dialogClient, FocusState::FOREGROUND, MixingBehavior::PRIMARY); - ASSERT_TRUE( - m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, anotherDialogClient, DIFFERENT_DIALOG_INTERFACE_NAME)); - assertFocusChange(dialogClient, FocusState::NONE); - assertFocusChange(anotherDialogClient, FocusState::FOREGROUND); + ASSERT_TRUE(acquireChannelHelper(anotherDialogClient)); + assertMixingBehaviorAndFocusChange(dialogClient, FocusState::NONE, MixingBehavior::MUST_STOP); + assertMixingBehaviorAndFocusChange(anotherDialogClient, FocusState::FOREGROUND, MixingBehavior::PRIMARY); } /** * Tests releaseChannel with a single Channel. The observer should be notified to stop. */ TEST_F(FocusManagerTest, test_simpleReleaseChannel) { - ASSERT_TRUE(m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, dialogClient, DIALOG_INTERFACE_NAME)); - assertFocusChange(dialogClient, FocusState::FOREGROUND); + ASSERT_TRUE(acquireChannelHelper(dialogClient)); + assertMixingBehaviorAndFocusChange(dialogClient, FocusState::FOREGROUND, MixingBehavior::PRIMARY); ASSERT_TRUE(m_focusManager->releaseChannel(DIALOG_CHANNEL_NAME, dialogClient).get()); - assertFocusChange(dialogClient, FocusState::NONE); + assertMixingBehaviorAndFocusChange(dialogClient, FocusState::NONE, MixingBehavior::MUST_STOP); } /** * Tests releaseChannel on a Channel with an incorrect observer. The client should not receive any callback. */ TEST_F(FocusManagerTest, test_simpleReleaseChannelWithIncorrectObserver) { - ASSERT_TRUE(m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, dialogClient, DIALOG_INTERFACE_NAME)); - assertFocusChange(dialogClient, FocusState::FOREGROUND); + ASSERT_TRUE(acquireChannelHelper(dialogClient)); + assertMixingBehaviorAndFocusChange(dialogClient, FocusState::FOREGROUND, MixingBehavior::PRIMARY); ASSERT_FALSE(m_focusManager->releaseChannel(CONTENT_CHANNEL_NAME, dialogClient).get()); ASSERT_FALSE(m_focusManager->releaseChannel(DIALOG_CHANNEL_NAME, contentClient).get()); - assertNoFocusChange(dialogClient); - assertNoFocusChange(contentClient); + assertNoMixingBehaviorOrFocusChange(dialogClient); + assertNoMixingBehaviorOrFocusChange(contentClient); } /** @@ -368,26 +674,27 @@ TEST_F(FocusManagerTest, test_simpleReleaseChannelWithIncorrectObserver) { * be notified to stop. */ TEST_F(FocusManagerTest, test_releaseForegroundChannelWhileBackgroundChannelTaken) { - ASSERT_TRUE(m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, dialogClient, DIALOG_INTERFACE_NAME)); - assertFocusChange(dialogClient, FocusState::FOREGROUND); + ASSERT_TRUE(acquireChannelHelper(dialogClient, ContentType::MIXABLE)); + assertMixingBehaviorAndFocusChange(dialogClient, FocusState::FOREGROUND, MixingBehavior::PRIMARY); - ASSERT_TRUE(m_focusManager->acquireChannel(CONTENT_CHANNEL_NAME, contentClient, CONTENT_INTERFACE_NAME)); - assertFocusChange(contentClient, FocusState::BACKGROUND); + ASSERT_TRUE(acquireChannelHelper(contentClient, ContentType::MIXABLE)); + assertMixingBehaviorAndFocusChange(contentClient, FocusState::BACKGROUND, MixingBehavior::MAY_DUCK); + assertNoMixingBehaviorOrFocusChange(dialogClient); ASSERT_TRUE(m_focusManager->releaseChannel(DIALOG_CHANNEL_NAME, dialogClient).get()); - assertFocusChange(dialogClient, FocusState::NONE); - assertFocusChange(contentClient, FocusState::FOREGROUND); + assertMixingBehaviorAndFocusChange(dialogClient, FocusState::NONE, MixingBehavior::MUST_STOP); + assertMixingBehaviorAndFocusChange(contentClient, FocusState::FOREGROUND, MixingBehavior::PRIMARY); } /** * Tests stopForegroundActivity with a single Channel. The observer should be notified to stop. */ TEST_F(FocusManagerTest, test_simpleNonTargetedStop) { - ASSERT_TRUE(m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, dialogClient, DIALOG_INTERFACE_NAME)); - assertFocusChange(dialogClient, FocusState::FOREGROUND); + ASSERT_TRUE(acquireChannelHelper(dialogClient, ContentType::MIXABLE)); + assertMixingBehaviorAndFocusChange(dialogClient, FocusState::FOREGROUND, MixingBehavior::PRIMARY); m_focusManager->stopForegroundActivity(); - assertFocusChange(dialogClient, FocusState::NONE); + assertMixingBehaviorAndFocusChange(dialogClient, FocusState::NONE, MixingBehavior::MUST_STOP); } /** @@ -395,25 +702,28 @@ TEST_F(FocusManagerTest, test_simpleNonTargetedStop) { * stop each time and the next highest priority background Channel should be brought to the foreground each time. */ TEST_F(FocusManagerTest, test_threeNonTargetedStopsWithThreeActivitiesHappening) { - ASSERT_TRUE(m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, dialogClient, DIALOG_INTERFACE_NAME)); - assertFocusChange(dialogClient, FocusState::FOREGROUND); + ASSERT_TRUE(acquireChannelHelper(dialogClient, ContentType::MIXABLE)); + assertMixingBehaviorAndFocusChange(dialogClient, FocusState::FOREGROUND, MixingBehavior::PRIMARY); - ASSERT_TRUE(m_focusManager->acquireChannel(ALERTS_CHANNEL_NAME, alertsClient, ALERTS_INTERFACE_NAME)); - assertFocusChange(alertsClient, FocusState::BACKGROUND); + ASSERT_TRUE(acquireChannelHelper(alertsClient, ContentType::MIXABLE)); + assertMixingBehaviorAndFocusChange(alertsClient, FocusState::BACKGROUND, MixingBehavior::MAY_DUCK); - ASSERT_TRUE(m_focusManager->acquireChannel(CONTENT_CHANNEL_NAME, contentClient, CONTENT_INTERFACE_NAME)); - assertFocusChange(contentClient, FocusState::BACKGROUND); + ASSERT_TRUE(acquireChannelHelper(contentClient, ContentType::MIXABLE)); + assertMixingBehaviorAndFocusChange(contentClient, FocusState::BACKGROUND, MixingBehavior::MAY_DUCK); + assertNoMixingBehaviorOrFocusChange(alertsClient); m_focusManager->stopForegroundActivity(); - assertFocusChange(dialogClient, FocusState::NONE); - assertFocusChange(alertsClient, FocusState::FOREGROUND); + assertMixingBehaviorAndFocusChange(dialogClient, FocusState::NONE, MixingBehavior::MUST_STOP); + assertMixingBehaviorAndFocusChange(alertsClient, FocusState::FOREGROUND, MixingBehavior::PRIMARY); + // no change in mixingbehavior or focusstate for content, no activity updates either + assertNoMixingBehaviorOrFocusChange(contentClient); m_focusManager->stopForegroundActivity(); - assertFocusChange(alertsClient, FocusState::NONE); - assertFocusChange(contentClient, FocusState::FOREGROUND); + assertMixingBehaviorAndFocusChange(alertsClient, FocusState::NONE, MixingBehavior::MUST_STOP); + assertMixingBehaviorAndFocusChange(contentClient, FocusState::FOREGROUND, MixingBehavior::PRIMARY); m_focusManager->stopForegroundActivity(); - assertFocusChange(contentClient, FocusState::NONE); + assertMixingBehaviorAndFocusChange(contentClient, FocusState::NONE, MixingBehavior::MUST_STOP); } /** @@ -421,14 +731,14 @@ TEST_F(FocusManagerTest, test_threeNonTargetedStopsWithThreeActivitiesHappening) * foreground focus. */ TEST_F(FocusManagerTest, test_stopForegroundActivityAndAcquireDifferentChannel) { - ASSERT_TRUE(m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, dialogClient, DIALOG_INTERFACE_NAME)); - assertFocusChange(dialogClient, FocusState::FOREGROUND); + ASSERT_TRUE(acquireChannelHelper(dialogClient)); + assertMixingBehaviorAndFocusChange(dialogClient, FocusState::FOREGROUND, MixingBehavior::PRIMARY); m_focusManager->stopForegroundActivity(); - assertFocusChange(dialogClient, FocusState::NONE); + assertMixingBehaviorAndFocusChange(dialogClient, FocusState::NONE, MixingBehavior::MUST_STOP); - ASSERT_TRUE(m_focusManager->acquireChannel(CONTENT_CHANNEL_NAME, contentClient, CONTENT_INTERFACE_NAME)); - assertFocusChange(contentClient, FocusState::FOREGROUND); + ASSERT_TRUE(acquireChannelHelper(contentClient)); + assertMixingBehaviorAndFocusChange(contentClient, FocusState::FOREGROUND, MixingBehavior::PRIMARY); } /** @@ -436,14 +746,14 @@ TEST_F(FocusManagerTest, test_stopForegroundActivityAndAcquireDifferentChannel) * foreground focus. */ TEST_F(FocusManagerTest, test_stopForegroundActivityAndAcquireSameChannel) { - ASSERT_TRUE(m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, dialogClient, DIALOG_INTERFACE_NAME)); - assertFocusChange(dialogClient, FocusState::FOREGROUND); + ASSERT_TRUE(acquireChannelHelper(dialogClient)); + assertMixingBehaviorAndFocusChange(dialogClient, FocusState::FOREGROUND, MixingBehavior::PRIMARY); m_focusManager->stopForegroundActivity(); - assertFocusChange(dialogClient, FocusState::NONE); + assertMixingBehaviorAndFocusChange(dialogClient, FocusState::NONE, MixingBehavior::MUST_STOP); - ASSERT_TRUE(m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, dialogClient, DIALOG_INTERFACE_NAME)); - assertFocusChange(dialogClient, FocusState::FOREGROUND); + ASSERT_TRUE(acquireChannelHelper(dialogClient)); + assertMixingBehaviorAndFocusChange(dialogClient, FocusState::FOREGROUND, MixingBehavior::PRIMARY); } /** @@ -451,17 +761,16 @@ TEST_F(FocusManagerTest, test_stopForegroundActivityAndAcquireSameChannel) { * Expect focus change only on that channel and reacquiring the same channel should resulted in foreground focus. */ TEST_F(FocusManagerTest, test_stopAllActivitiesWithSingleChannel) { - ASSERT_TRUE(m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, dialogClient, DIALOG_INTERFACE_NAME)); - assertFocusChange(dialogClient, FocusState::FOREGROUND); + ASSERT_TRUE(acquireChannelHelper(dialogClient)); + assertMixingBehaviorAndFocusChange(dialogClient, FocusState::FOREGROUND, MixingBehavior::PRIMARY); m_focusManager->stopAllActivities(); - assertFocusChange(dialogClient, FocusState::NONE); + assertMixingBehaviorAndFocusChange(dialogClient, FocusState::NONE, MixingBehavior::MUST_STOP); + assertNoMixingBehaviorOrFocusChange(contentClient); + assertNoMixingBehaviorOrFocusChange(alertsClient); - assertNoFocusChange(contentClient); - assertNoFocusChange(alertsClient); - - ASSERT_TRUE(m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, dialogClient, DIALOG_INTERFACE_NAME)); - assertFocusChange(dialogClient, FocusState::FOREGROUND); + ASSERT_TRUE(acquireChannelHelper(dialogClient)); + assertMixingBehaviorAndFocusChange(dialogClient, FocusState::FOREGROUND, MixingBehavior::PRIMARY); } /** @@ -469,23 +778,66 @@ TEST_F(FocusManagerTest, test_stopAllActivitiesWithSingleChannel) { * Expect focus change to none for all channels and a channel should resulted in foreground focus. */ TEST_F(FocusManagerTest, test_stopAllActivitiesWithThreeChannels) { - ASSERT_TRUE(m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, dialogClient, DIALOG_INTERFACE_NAME)); - assertFocusChange(dialogClient, FocusState::FOREGROUND); + ASSERT_TRUE(acquireChannelHelper(dialogClient, ContentType::MIXABLE)); + assertMixingBehaviorAndFocusChange(dialogClient, FocusState::FOREGROUND, MixingBehavior::PRIMARY); - ASSERT_TRUE(m_focusManager->acquireChannel(CONTENT_CHANNEL_NAME, contentClient, CONTENT_INTERFACE_NAME)); - assertFocusChange(contentClient, FocusState::BACKGROUND); + ASSERT_TRUE(acquireChannelHelper(contentClient)); + assertMixingBehaviorAndFocusChange(contentClient, FocusState::BACKGROUND, MixingBehavior::MUST_PAUSE); - ASSERT_TRUE(m_focusManager->acquireChannel(ALERTS_CHANNEL_NAME, alertsClient, ALERTS_INTERFACE_NAME)); - assertFocusChange(alertsClient, FocusState::BACKGROUND); + ASSERT_TRUE(acquireChannelHelper(alertsClient, ContentType::MIXABLE)); + assertMixingBehaviorAndFocusChange(alertsClient, FocusState::BACKGROUND, MixingBehavior::MAY_DUCK); m_focusManager->stopAllActivities(); - assertFocusChange(dialogClient, FocusState::NONE); - assertFocusChange(contentClient, FocusState::NONE); - assertFocusChange(alertsClient, FocusState::NONE); + assertMixingBehaviorAndFocusChange(dialogClient, FocusState::NONE, MixingBehavior::MUST_STOP); + assertMixingBehaviorAndFocusChange(contentClient, FocusState::NONE, MixingBehavior::MUST_STOP); + assertMixingBehaviorAndFocusChange(alertsClient, FocusState::NONE, MixingBehavior::MUST_STOP); - ASSERT_TRUE(m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, dialogClient, DIALOG_INTERFACE_NAME)); - assertFocusChange(dialogClient, FocusState::FOREGROUND); + ASSERT_TRUE(acquireChannelHelper(dialogClient)); + assertMixingBehaviorAndFocusChange(dialogClient, FocusState::FOREGROUND, MixingBehavior::PRIMARY); +} + +/** + * Test stopAllActivities with a single channel. + * Expect focus change only on that channel and reacquiring the same channel should result in foreground focus. + */ +TEST_F(FocusManagerTest, stopAllActivitiesWithSingleChannel) { + ASSERT_TRUE(acquireChannelHelper(dialogClient)); + assertMixingBehaviorAndFocusChange(dialogClient, FocusState::FOREGROUND, MixingBehavior::PRIMARY); + + m_focusManager->stopAllActivities(); + assertMixingBehaviorAndFocusChange(dialogClient, FocusState::NONE, MixingBehavior::MUST_STOP); + + assertNoMixingBehaviorOrFocusChange(contentClient); + assertNoMixingBehaviorOrFocusChange(alertsClient); + + ASSERT_TRUE(acquireChannelHelper(dialogClient)); + assertMixingBehaviorAndFocusChange(dialogClient, FocusState::FOREGROUND, MixingBehavior::PRIMARY); +} + +/** + * Test stopAllActivities with three channels. + * Expect focus change to none for all channels and a channel should result in foreground focus. + */ +TEST_F(FocusManagerTest, stopAllActivitiesWithThreeChannels) { + ASSERT_TRUE(acquireChannelHelper(dialogClient, ContentType::MIXABLE)); + assertMixingBehaviorAndFocusChange(dialogClient, FocusState::FOREGROUND, MixingBehavior::PRIMARY); + + ASSERT_TRUE(acquireChannelHelper(contentClient)); + assertMixingBehaviorAndFocusChange(contentClient, FocusState::BACKGROUND, MixingBehavior::MUST_PAUSE); + + ASSERT_TRUE(acquireChannelHelper(alertsClient, ContentType::MIXABLE)); + assertMixingBehaviorAndFocusChange(alertsClient, FocusState::BACKGROUND, MixingBehavior::MAY_DUCK); + assertNoMixingBehaviorOrFocusChange(contentClient); + + m_focusManager->stopAllActivities(); + + assertMixingBehaviorAndFocusChange(dialogClient, FocusState::NONE, MixingBehavior::MUST_STOP); + assertMixingBehaviorAndFocusChange(contentClient, FocusState::NONE, MixingBehavior::MUST_STOP); + assertMixingBehaviorAndFocusChange(alertsClient, FocusState::NONE, MixingBehavior::MUST_STOP); + + ASSERT_TRUE(acquireChannelHelper(dialogClient)); + assertMixingBehaviorAndFocusChange(dialogClient, FocusState::FOREGROUND, MixingBehavior::PRIMARY); } /** @@ -493,16 +845,16 @@ TEST_F(FocusManagerTest, test_stopAllActivitiesWithThreeChannels) { * should remain foregrounded while the background Channel's observer should be notified to stop. */ TEST_F(FocusManagerTest, test_releaseBackgroundChannelWhileTwoChannelsTaken) { - ASSERT_TRUE(m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, dialogClient, DIALOG_INTERFACE_NAME)); - assertFocusChange(dialogClient, FocusState::FOREGROUND); + ASSERT_TRUE(acquireChannelHelper(dialogClient, ContentType::MIXABLE)); + assertMixingBehaviorAndFocusChange(dialogClient, FocusState::FOREGROUND, MixingBehavior::PRIMARY); - ASSERT_TRUE(m_focusManager->acquireChannel(CONTENT_CHANNEL_NAME, contentClient, CONTENT_INTERFACE_NAME)); - assertFocusChange(contentClient, FocusState::BACKGROUND); + ASSERT_TRUE(acquireChannelHelper(contentClient)); + assertMixingBehaviorAndFocusChange(contentClient, FocusState::BACKGROUND, MixingBehavior::MUST_PAUSE); ASSERT_TRUE(m_focusManager->releaseChannel(CONTENT_CHANNEL_NAME, contentClient).get()); - assertFocusChange(contentClient, FocusState::NONE); + assertMixingBehaviorAndFocusChange(contentClient, FocusState::NONE, MixingBehavior::MUST_STOP); - assertNoFocusChange(dialogClient); + assertNoMixingBehaviorOrFocusChange(dialogClient); } /** @@ -511,18 +863,16 @@ TEST_F(FocusManagerTest, test_releaseBackgroundChannelWhileTwoChannelsTaken) { * Foreground focus. The originally backgrounded Channel should not change focus. */ TEST_F(FocusManagerTest, test_kickOutActivityOnSameChannelWhileOtherChannelsActive) { - ASSERT_TRUE(m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, dialogClient, DIALOG_INTERFACE_NAME)); - assertFocusChange(dialogClient, FocusState::FOREGROUND); + ASSERT_TRUE(acquireChannelHelper(dialogClient, ContentType::MIXABLE)); + assertMixingBehaviorAndFocusChange(dialogClient, FocusState::FOREGROUND, MixingBehavior::PRIMARY); - ASSERT_TRUE(m_focusManager->acquireChannel(CONTENT_CHANNEL_NAME, contentClient, CONTENT_INTERFACE_NAME)); - assertFocusChange(contentClient, FocusState::BACKGROUND); + ASSERT_TRUE(acquireChannelHelper(contentClient)); + assertMixingBehaviorAndFocusChange(contentClient, FocusState::BACKGROUND, MixingBehavior::MUST_PAUSE); - ASSERT_TRUE( - m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, anotherDialogClient, DIFFERENT_DIALOG_INTERFACE_NAME)); - assertFocusChange(dialogClient, FocusState::NONE); - assertFocusChange(anotherDialogClient, FocusState::FOREGROUND); - - assertNoFocusChange(contentClient); + ASSERT_TRUE(acquireChannelHelper(anotherDialogClient, ContentType::MIXABLE)); + assertMixingBehaviorAndFocusChange(dialogClient, FocusState::NONE, MixingBehavior::MUST_STOP); + assertMixingBehaviorAndFocusChange(anotherDialogClient, FocusState::FOREGROUND, MixingBehavior::PRIMARY); + assertNoMixingBehaviorOrFocusChange(contentClient); } /// Tests that multiple observers can be added, and that they are notified of all focus changes. @@ -540,13 +890,13 @@ TEST_F(FocusManagerTest, test_addObserver) { for (auto& observer : observers) { observer->expectFocusChange(DIALOG_CHANNEL_NAME, FocusState::FOREGROUND); } - ASSERT_TRUE(m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, dialogClient, DIALOG_INTERFACE_NAME)); + ASSERT_TRUE(acquireChannelHelper(dialogClient)); // Focus change on CONTENT channel. for (auto& observer : observers) { observer->expectFocusChange(CONTENT_CHANNEL_NAME, FocusState::BACKGROUND); } - ASSERT_TRUE(m_focusManager->acquireChannel(CONTENT_CHANNEL_NAME, contentClient, CONTENT_INTERFACE_NAME)); + ASSERT_TRUE(acquireChannelHelper(contentClient)); // Wait for all pending changes to complete. for (auto& observer : observers) { @@ -585,7 +935,7 @@ TEST_F(FocusManagerTest, test_removeObserver) { for (auto& observer : activeObservers) { observer->expectFocusChange(DIALOG_CHANNEL_NAME, FocusState::FOREGROUND); } - ASSERT_TRUE(m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, dialogClient, DIALOG_INTERFACE_NAME)); + ASSERT_TRUE(acquireChannelHelper(dialogClient)); // Wait for all pending changes to complete. for (auto& observer : allObservers) { @@ -600,7 +950,7 @@ TEST_F(FocusManagerTest, test_removeObserver) { for (auto& observer : activeObservers) { observer->expectFocusChange(CONTENT_CHANNEL_NAME, FocusState::BACKGROUND); } - ASSERT_TRUE(m_focusManager->acquireChannel(CONTENT_CHANNEL_NAME, contentClient, CONTENT_INTERFACE_NAME)); + ASSERT_TRUE(acquireChannelHelper(contentClient)); // Wait for all pending changes to complete. for (auto& observer : allObservers) { @@ -628,7 +978,7 @@ TEST_F(FocusManagerTest, test_activityTracker) { // Acquire Content channel and expect notifyOfActivityUpdates() to notify activities on the Content channel. const std::vector test1 = { {CONTENT_CHANNEL_NAME, CONTENT_INTERFACE_NAME, FocusState::FOREGROUND}}; - ASSERT_TRUE(m_focusManager->acquireChannel(CONTENT_CHANNEL_NAME, contentClient, CONTENT_INTERFACE_NAME)); + ASSERT_TRUE(acquireChannelHelper(contentClient)); m_activityTracker->waitForActivityUpdates(DEFAULT_TIMEOUT, test1); // Acquire Alert channel and expect notifyOfActivityUpdates() to notify activities to both Content and Alert @@ -636,7 +986,7 @@ TEST_F(FocusManagerTest, test_activityTracker) { const std::vector test2 = { {CONTENT_CHANNEL_NAME, CONTENT_INTERFACE_NAME, FocusState::BACKGROUND}, {ALERTS_CHANNEL_NAME, ALERTS_INTERFACE_NAME, FocusState::FOREGROUND}}; - ASSERT_TRUE(m_focusManager->acquireChannel(ALERTS_CHANNEL_NAME, alertsClient, ALERTS_INTERFACE_NAME)); + ASSERT_TRUE(acquireChannelHelper(alertsClient, ContentType::MIXABLE)); m_activityTracker->waitForActivityUpdates(DEFAULT_TIMEOUT, test2); // Acquire Dialog channel and expect notifyOfActivityUpdates() to notify activities to both Alert and Dialog @@ -644,7 +994,7 @@ TEST_F(FocusManagerTest, test_activityTracker) { const std::vector test3 = { {ALERTS_CHANNEL_NAME, ALERTS_INTERFACE_NAME, FocusState::BACKGROUND}, {DIALOG_CHANNEL_NAME, DIALOG_INTERFACE_NAME, FocusState::FOREGROUND}}; - ASSERT_TRUE(m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, dialogClient, DIALOG_INTERFACE_NAME)); + ASSERT_TRUE(acquireChannelHelper(dialogClient, ContentType::MIXABLE)); m_activityTracker->waitForActivityUpdates(DEFAULT_TIMEOUT, test3); // Release Content channel and expect notifyOfActivityUpdates() to notify activities to Content channel. @@ -658,15 +1008,14 @@ TEST_F(FocusManagerTest, test_activityTracker) { const std::vector test5 = { {DIALOG_CHANNEL_NAME, DIALOG_INTERFACE_NAME, FocusState::NONE}, {DIALOG_CHANNEL_NAME, DIFFERENT_DIALOG_INTERFACE_NAME, FocusState::FOREGROUND}}; - ASSERT_TRUE( - m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, anotherDialogClient, DIFFERENT_DIALOG_INTERFACE_NAME)); + ASSERT_TRUE(acquireChannelHelper(anotherDialogClient)); m_activityTracker->waitForActivityUpdates(DEFAULT_TIMEOUT, test5); // Release Dialog channel and expect notifyOfActivityUpdates() to notify activities to both Dialog and Alerts // channels. const std::vector test6 = { - {DIALOG_CHANNEL_NAME, DIFFERENT_DIALOG_INTERFACE_NAME, FocusState::NONE}, - {ALERTS_CHANNEL_NAME, ALERTS_INTERFACE_NAME, FocusState::FOREGROUND}}; + {ALERTS_CHANNEL_NAME, ALERTS_INTERFACE_NAME, FocusState::FOREGROUND}, + {DIALOG_CHANNEL_NAME, DIFFERENT_DIALOG_INTERFACE_NAME, FocusState::NONE}}; ASSERT_TRUE(m_focusManager->releaseChannel(DIALOG_CHANNEL_NAME, anotherDialogClient).get()); m_activityTracker->waitForActivityUpdates(DEFAULT_TIMEOUT, test6); @@ -675,6 +1024,26 @@ TEST_F(FocusManagerTest, test_activityTracker) { {ALERTS_CHANNEL_NAME, ALERTS_INTERFACE_NAME, FocusState::NONE}}; ASSERT_TRUE(m_focusManager->releaseChannel(ALERTS_CHANNEL_NAME, alertsClient).get()); m_activityTracker->waitForActivityUpdates(DEFAULT_TIMEOUT, test7); + + // acquire Virtual channel and expect no notifyOfActivityUpdates(). + ASSERT_TRUE(acquireChannelHelper(virtualClient)); + ASSERT_FALSE(m_activityTracker->waitForNoActivityUpdates(NO_ACTIVITY_UPDATE_TIMEOUT)); + + // release Virtual channel and expect no notifyOfActivityUpdates(). + ASSERT_TRUE(m_focusManager->releaseChannel(VIRTUAL_CHANNEL_NAME, virtualClient).get()); + ASSERT_FALSE(m_activityTracker->waitForNoActivityUpdates(NO_ACTIVITY_UPDATE_TIMEOUT)); + + // Acquire Content channel and expect notifyOfActivityUpdates() to notify activities on the Content channel. + const std::vector test8 = { + {CONTENT_CHANNEL_NAME, CONTENT_INTERFACE_NAME, FocusState::FOREGROUND}}; + ASSERT_TRUE(acquireChannelHelper(contentClient)); + m_activityTracker->waitForActivityUpdates(DEFAULT_TIMEOUT, test8); + + // acquire Virtual channel and expect no updates to virtual channel but Content channel to go to BACKGROUND. + const std::vector test9 = { + {CONTENT_CHANNEL_NAME, CONTENT_INTERFACE_NAME, FocusState::BACKGROUND}}; + ASSERT_TRUE(acquireChannelHelper(virtualClient)); + m_activityTracker->waitForActivityUpdates(DEFAULT_TIMEOUT, test9); } /// Test fixture for testing Channel. @@ -682,19 +1051,47 @@ class ChannelTest : public ::testing::Test , public FocusChangeManager { protected: - /// A test client that used to observe Channels. + /// observer A for the Content MultiActivity Channel std::shared_ptr clientA; - /// A test client that used to observe Channels. + /// observer B for the Content MultiActivity Channel std::shared_ptr clientB; + /// observer C for the Content MultiActivity Channel + std::shared_ptr clientC; + /// A test Channel. std::shared_ptr testChannel; + /// Content Channel. + std::shared_ptr contentChannel; + virtual void SetUp() { - clientA = std::make_shared(); - clientB = std::make_shared(); + clientA = std::make_shared(CONTENT_CHANNEL_NAME, "ClientA_Interface"); + clientB = std::make_shared(CONTENT_CHANNEL_NAME, "ClientB_Interface"); + clientC = std::make_shared(CONTENT_CHANNEL_NAME, "ClientC_Interface"); testChannel = std::make_shared(DIALOG_CHANNEL_NAME, DIALOG_CHANNEL_PRIORITY); + contentChannel = std::make_shared(CONTENT_CHANNEL_NAME, CONTENT_CHANNEL_PRIORITY); + } + + struct ActivityUpdateElem { + std::string m_interfaceName; + FocusState m_focusState; + ActivityUpdateElem(std::string interfaceName, FocusState focus) : + m_interfaceName{interfaceName}, + m_focusState{focus} { + } + }; + + void checkActivityUpdates(std::shared_ptr channel, std::vector& incoming) { + auto activityUpdates = channel->getActivityUpdates(); + ASSERT_EQ(incoming.size(), activityUpdates.size()); + for (size_t i = 0; i < incoming.size(); i++) { + ASSERT_EQ(activityUpdates.at(i).interfaceName, incoming.at(i).m_interfaceName); + ASSERT_EQ(activityUpdates.at(i).focusState, incoming.at(i).m_focusState); + } + + incoming.clear(); } }; @@ -710,18 +1107,19 @@ TEST_F(ChannelTest, test_getPriority) { /// Tests that the observer properly gets notified of focus changes. TEST_F(ChannelTest, test_setObserverThenSetFocus) { - testChannel->setObserver(clientA); + auto Activity_A = clientA->createActivity(); + testChannel->setPrimaryActivity(Activity_A); - ASSERT_TRUE(testChannel->setFocus(FocusState::FOREGROUND)); - assertFocusChange(clientA, FocusState::FOREGROUND); + ASSERT_TRUE(testChannel->setFocus(FocusState::FOREGROUND, MixingBehavior::PRIMARY)); + assertMixingBehaviorAndFocusChange(clientA, FocusState::FOREGROUND, MixingBehavior::PRIMARY); - ASSERT_TRUE(testChannel->setFocus(FocusState::BACKGROUND)); - assertFocusChange(clientA, FocusState::BACKGROUND); + ASSERT_TRUE(testChannel->setFocus(FocusState::BACKGROUND, MixingBehavior::MUST_PAUSE)); + assertMixingBehaviorAndFocusChange(clientA, FocusState::BACKGROUND, MixingBehavior::MUST_PAUSE); - ASSERT_TRUE(testChannel->setFocus(FocusState::NONE)); - assertFocusChange(clientA, FocusState::NONE); + ASSERT_TRUE(testChannel->setFocus(FocusState::NONE, MixingBehavior::MUST_STOP)); + assertMixingBehaviorAndFocusChange(clientA, FocusState::NONE, MixingBehavior::MUST_STOP); - ASSERT_FALSE(testChannel->setFocus(FocusState::NONE)); + ASSERT_FALSE(testChannel->setFocus(FocusState::NONE, MixingBehavior::MUST_STOP)); } /// Tests that Channels are compared properly @@ -732,15 +1130,28 @@ TEST_F(ChannelTest, test_priorityComparison) { ASSERT_FALSE(*lowerPriorityChannel > *testChannel); } -/// Tests that a Channel correctly reports whether it has an observer. -TEST_F(ChannelTest, test_hasObserver) { - ASSERT_FALSE(testChannel->hasObserver()); - testChannel->setObserver(clientA); - ASSERT_TRUE(testChannel->hasObserver()); - testChannel->setObserver(clientB); - ASSERT_TRUE(testChannel->hasObserver()); - testChannel->setObserver(nullptr); - ASSERT_FALSE(testChannel->hasObserver()); +/// Tests that a Channel correctly reports whether it is active. +TEST_F(ChannelTest, test_isChannelActive) { + // initially channel is not active + ASSERT_FALSE(testChannel->isActive()); + + // add Activity_A to the channel + auto Activity_A = clientA->createActivity(); + testChannel->setPrimaryActivity(Activity_A); + ASSERT_TRUE(testChannel->isActive()); + + // add Activity_B to the channel + auto Activity_B = clientB->createActivity(avsCommon::avs::ContentType::NONMIXABLE, std::chrono::seconds(2)); + testChannel->setPrimaryActivity(Activity_B); + ASSERT_TRUE(testChannel->isActive()); + + // release Activity_A + testChannel->releaseActivity(clientA); + ASSERT_TRUE(testChannel->isActive()); + + // release Activity_B + testChannel->releaseActivity(clientB); + ASSERT_FALSE(testChannel->isActive()); } /* @@ -749,19 +1160,251 @@ TEST_F(ChannelTest, test_hasObserver) { */ TEST_F(ChannelTest, test_getTimeAtIdle) { auto startTime = testChannel->getState().timeAtIdle; - ASSERT_TRUE(testChannel->setFocus(FocusState::FOREGROUND)); + ASSERT_TRUE(testChannel->setFocus(FocusState::FOREGROUND, MixingBehavior::PRIMARY)); auto afterForegroundTime = testChannel->getState().timeAtIdle; ASSERT_EQ(startTime, afterForegroundTime); - ASSERT_TRUE(testChannel->setFocus(FocusState::BACKGROUND)); + ASSERT_TRUE(testChannel->setFocus(FocusState::BACKGROUND, MixingBehavior::MUST_PAUSE)); auto afterBackgroundTime = testChannel->getState().timeAtIdle; ASSERT_EQ(afterBackgroundTime, afterForegroundTime); - ASSERT_TRUE(testChannel->setFocus(FocusState::NONE)); + ASSERT_TRUE(testChannel->setFocus(FocusState::NONE, MixingBehavior::MUST_STOP)); auto afterNoneTime = testChannel->getState().timeAtIdle; ASSERT_GT(afterNoneTime, afterBackgroundTime); } +TEST_F(ChannelTest, test_MultiActivity_NewActivityKicksExistingActivity) { + std::vector expectedUpdates; + + // client A acquires Content Channel + auto Activity_A = clientA->createActivity(); + contentChannel->setPrimaryActivity(Activity_A); + ASSERT_TRUE(contentChannel->getState().interfaceName == Activity_A->getInterface()); + contentChannel->setFocus(FocusState::FOREGROUND, MixingBehavior::PRIMARY); + expectedUpdates.push_back({ActivityUpdateElem(clientA->getInterfaceName(), FocusState::FOREGROUND)}); + checkActivityUpdates(contentChannel, expectedUpdates); + + // client B acquires Content Channel : it must kick out client A + auto Activity_B = clientB->createActivity(); + contentChannel->setPrimaryActivity(Activity_B); + contentChannel->setFocus(FocusState::FOREGROUND, MixingBehavior::PRIMARY, true); + expectedUpdates.push_back({ActivityUpdateElem(clientA->getInterfaceName(), FocusState::NONE)}); + expectedUpdates.push_back({ActivityUpdateElem(clientB->getInterfaceName(), FocusState::FOREGROUND)}); + checkActivityUpdates(contentChannel, expectedUpdates); + ASSERT_TRUE(contentChannel->getState().interfaceName == Activity_B->getInterface()); + ASSERT_EQ(contentChannel->getActivity(Activity_A->getInterface()), nullptr); + + // when client B releases the channel , activity list becomes empty + contentChannel->releaseActivity(clientB->getInterfaceName()); + contentChannel->setFocus(FocusState::NONE, MixingBehavior::MUST_STOP); + expectedUpdates.push_back({ActivityUpdateElem(clientB->getInterfaceName(), FocusState::NONE)}); + checkActivityUpdates(contentChannel, expectedUpdates); + ASSERT_EQ(contentChannel->getActivity(Activity_B->getInterface()), nullptr); +} + +TEST_F(ChannelTest, test_MultiActivity_IncomingActivityWithPatience1) { + std::vector expectedUpdates; + + // client A acquires Content Channel + auto Activity_A = clientA->createActivity(); + contentChannel->setPrimaryActivity(Activity_A); + ASSERT_TRUE(contentChannel->getState().interfaceName == Activity_A->getInterface()); + contentChannel->setFocus(FocusState::FOREGROUND, MixingBehavior::PRIMARY); + expectedUpdates.push_back({ActivityUpdateElem(clientA->getInterfaceName(), FocusState::FOREGROUND)}); + checkActivityUpdates(contentChannel, expectedUpdates); + + // client B acquires Content Channel (with a patience parameter) + // activityUpdate must show client A with FocusState::NONE + // however client A is not kicked out + auto Activity_B = clientB->createActivity(ContentType::MIXABLE, std::chrono::seconds(5)); + contentChannel->setPrimaryActivity(Activity_B); + contentChannel->setFocus(FocusState::FOREGROUND, MixingBehavior::PRIMARY, true); + expectedUpdates.push_back({ActivityUpdateElem(clientA->getInterfaceName(), FocusState::NONE)}); + expectedUpdates.push_back({ActivityUpdateElem(clientB->getInterfaceName(), FocusState::FOREGROUND)}); + checkActivityUpdates(contentChannel, expectedUpdates); + ASSERT_TRUE(contentChannel->getState().interfaceName == Activity_B->getInterface()); + ASSERT_NE(contentChannel->getActivity(Activity_A->getInterface()), nullptr); + + // when client B activity is released , activity list becomes just client A + contentChannel->releaseActivity(clientB->getInterfaceName()); + expectedUpdates.push_back({ActivityUpdateElem(clientB->getInterfaceName(), FocusState::NONE)}); + expectedUpdates.push_back({ActivityUpdateElem(clientA->getInterfaceName(), FocusState::FOREGROUND)}); + checkActivityUpdates(contentChannel, expectedUpdates); + ASSERT_EQ(contentChannel->getActivity(Activity_B->getInterface()), nullptr); + ASSERT_TRUE(contentChannel->getState().interfaceName == Activity_A->getInterface()); + + // when client A activity is released , activity list becomes empty + contentChannel->releaseActivity(clientA->getInterfaceName()); + expectedUpdates.push_back({ActivityUpdateElem(clientA->getInterfaceName(), FocusState::NONE)}); + checkActivityUpdates(contentChannel, expectedUpdates); + ASSERT_EQ(contentChannel->getActivity(Activity_A->getInterface()), nullptr); +} + +TEST_F(ChannelTest, test_MultiActivity_IncomingActivityWithPatience2) { + std::vector expectedUpdates; + + // client A acquires Content Channel + auto Activity_A = clientA->createActivity(); + contentChannel->setPrimaryActivity(Activity_A); + ASSERT_TRUE(contentChannel->getState().interfaceName == Activity_A->getInterface()); + contentChannel->setFocus(FocusState::FOREGROUND, MixingBehavior::PRIMARY); + expectedUpdates.push_back({ActivityUpdateElem(clientA->getInterfaceName(), FocusState::FOREGROUND)}); + checkActivityUpdates(contentChannel, expectedUpdates); + + // client B acquires Content Channel (with a patience parameter) + // activityUpdate must show client A with FocusState::NONE + // however client A is not kicked out + auto Activity_B = clientB->createActivity(ContentType::MIXABLE, std::chrono::seconds(5)); + contentChannel->setPrimaryActivity(Activity_B); + contentChannel->setFocus(FocusState::FOREGROUND, MixingBehavior::PRIMARY, true); + expectedUpdates.push_back({ActivityUpdateElem(clientA->getInterfaceName(), FocusState::NONE)}); + expectedUpdates.push_back({ActivityUpdateElem(clientB->getInterfaceName(), FocusState::FOREGROUND)}); + checkActivityUpdates(contentChannel, expectedUpdates); + ASSERT_TRUE(contentChannel->getState().interfaceName == Activity_B->getInterface()); + ASSERT_NE(contentChannel->getActivity(Activity_A->getInterface()), nullptr); + + // when client A activity is released , activity list becomes just client B + contentChannel->releaseActivity(clientA->getInterfaceName()); + contentChannel->setFocus(FocusState::FOREGROUND, MixingBehavior::PRIMARY); + expectedUpdates.push_back({ActivityUpdateElem(clientB->getInterfaceName(), FocusState::FOREGROUND)}); + checkActivityUpdates(contentChannel, expectedUpdates); + ASSERT_EQ(contentChannel->getActivity(Activity_A->getInterface()), nullptr); + ASSERT_TRUE(contentChannel->getState().interfaceName == Activity_B->getInterface()); + + // when client B activity is released , activity list becomes empty + contentChannel->releaseActivity(clientB->getInterfaceName()); + expectedUpdates.push_back({ActivityUpdateElem(clientB->getInterfaceName(), FocusState::NONE)}); + checkActivityUpdates(contentChannel, expectedUpdates); + ASSERT_EQ(contentChannel->getActivity(Activity_B->getInterface()), nullptr); +} + +TEST_F(ChannelTest, test_MultiActivity_IncomingActivityWithPatience3) { + std::vector expectedUpdates; + + // client A acquires Content Channel + auto Activity_A = clientA->createActivity(); + contentChannel->setPrimaryActivity(Activity_A); + ASSERT_TRUE(contentChannel->getState().interfaceName == Activity_A->getInterface()); + contentChannel->setFocus(FocusState::FOREGROUND, MixingBehavior::PRIMARY); + expectedUpdates.push_back({ActivityUpdateElem(clientA->getInterfaceName(), FocusState::FOREGROUND)}); + checkActivityUpdates(contentChannel, expectedUpdates); + + // client B acquires Content Channel (with a patience parameter) + // activityUpdate must show client A with FocusState::NONE + // however client A is not kicked out + auto Activity_B = clientB->createActivity(ContentType::MIXABLE, std::chrono::seconds(1)); + contentChannel->setPrimaryActivity(Activity_B); + contentChannel->setFocus(FocusState::FOREGROUND, MixingBehavior::PRIMARY, true); + expectedUpdates.push_back({ActivityUpdateElem(clientA->getInterfaceName(), FocusState::NONE)}); + expectedUpdates.push_back({ActivityUpdateElem(clientB->getInterfaceName(), FocusState::FOREGROUND)}); + checkActivityUpdates(contentChannel, expectedUpdates); + ASSERT_TRUE(contentChannel->getState().interfaceName == Activity_B->getInterface()); + ASSERT_NE(contentChannel->getActivity(Activity_A->getInterface()), nullptr); + + // When patience is elapsed, Activity A is released and B is the interface of the channel. + std::this_thread::sleep_for(std::chrono::seconds(2)); + ASSERT_EQ(contentChannel->getActivity(Activity_A->getInterface()), nullptr); + ASSERT_TRUE(contentChannel->getState().interfaceName == Activity_B->getInterface()); + + // when client B activity is released , activity list becomes empty + contentChannel->releaseActivity(clientB->getInterfaceName()); + expectedUpdates.push_back({ActivityUpdateElem(clientB->getInterfaceName(), FocusState::NONE)}); + checkActivityUpdates(contentChannel, expectedUpdates); + ASSERT_EQ(contentChannel->getActivity(Activity_B->getInterface()), nullptr); +} + +TEST_F(ChannelTest, test_MultiActivity_IncomingActivityWithPatience4) { + std::vector expectedUpdates; + + // client A acquires Content Channel + auto Activity_A = clientA->createActivity(); + contentChannel->setPrimaryActivity(Activity_A); + ASSERT_TRUE(contentChannel->getState().interfaceName == Activity_A->getInterface()); + contentChannel->setFocus(FocusState::FOREGROUND, MixingBehavior::PRIMARY); + expectedUpdates.push_back({ActivityUpdateElem(clientA->getInterfaceName(), FocusState::FOREGROUND)}); + checkActivityUpdates(contentChannel, expectedUpdates); + + // client B acquires Content Channel (with a patience parameter) + // activityUpdate must show client A with FocusState::NONE + // however client A is not kicked out + auto Activity_B = clientB->createActivity(ContentType::MIXABLE, std::chrono::seconds(5)); + contentChannel->setPrimaryActivity(Activity_B); + contentChannel->setFocus(FocusState::FOREGROUND, MixingBehavior::PRIMARY, true); + expectedUpdates.push_back({ActivityUpdateElem(clientA->getInterfaceName(), FocusState::NONE)}); + expectedUpdates.push_back({ActivityUpdateElem(clientB->getInterfaceName(), FocusState::FOREGROUND)}); + checkActivityUpdates(contentChannel, expectedUpdates); + ASSERT_TRUE(contentChannel->getState().interfaceName == Activity_B->getInterface()); + ASSERT_NE(contentChannel->getActivity(Activity_A->getInterface()), nullptr); + + // client C acquires Content Channel (with a patience parameter) + // activityUpdate must show client B with FocusState::NONE + // activity B is released already and already was assigned FocusState::NONE + auto Activity_C = clientC->createActivity(ContentType::MIXABLE, std::chrono::seconds(5)); + contentChannel->setPrimaryActivity(Activity_C); + contentChannel->setFocus(FocusState::FOREGROUND, MixingBehavior::PRIMARY, true); + expectedUpdates.push_back({ActivityUpdateElem(clientB->getInterfaceName(), FocusState::NONE)}); + expectedUpdates.push_back({ActivityUpdateElem(clientC->getInterfaceName(), FocusState::FOREGROUND)}); + checkActivityUpdates(contentChannel, expectedUpdates); + ASSERT_TRUE(contentChannel->getState().interfaceName == Activity_C->getInterface()); + // Activity A is released. + ASSERT_EQ(contentChannel->getActivity(Activity_A->getInterface()), nullptr); + // Activity B is not kicked out. + ASSERT_NE(contentChannel->getActivity(Activity_B->getInterface()), nullptr); + + // when client C activity is released , activity list becomes empty + contentChannel->releaseActivity(clientC->getInterfaceName()); + expectedUpdates.push_back({ActivityUpdateElem(clientC->getInterfaceName(), FocusState::NONE)}); + expectedUpdates.push_back({ActivityUpdateElem(clientB->getInterfaceName(), FocusState::FOREGROUND)}); + checkActivityUpdates(contentChannel, expectedUpdates); + // Activity A/C are released. + ASSERT_EQ(contentChannel->getActivity(Activity_A->getInterface()), nullptr); + ASSERT_EQ(contentChannel->getActivity(Activity_C->getInterface()), nullptr); + ASSERT_TRUE(contentChannel->getState().interfaceName == Activity_B->getInterface()); +} + +TEST_F(ChannelTest, test_MultiActivity_IncomingActivityWithPatience5) { + std::vector expectedUpdates; + + // client A acquires Content Channel + auto Activity_A = clientA->createActivity(); + contentChannel->setPrimaryActivity(Activity_A); + ASSERT_TRUE(contentChannel->getState().interfaceName == Activity_A->getInterface()); + contentChannel->setFocus(FocusState::FOREGROUND, MixingBehavior::PRIMARY); + expectedUpdates.push_back({ActivityUpdateElem(clientA->getInterfaceName(), FocusState::FOREGROUND)}); + checkActivityUpdates(contentChannel, expectedUpdates); + + // client B acquires Content Channel (with a patience parameter) + // activityUpdate must show client A with FocusState::NONE + // however client A is not kicked out + auto Activity_B = clientB->createActivity(ContentType::MIXABLE, std::chrono::seconds(5)); + contentChannel->setPrimaryActivity(Activity_B); + contentChannel->setFocus(FocusState::FOREGROUND, MixingBehavior::PRIMARY, true); + expectedUpdates.push_back({ActivityUpdateElem(clientA->getInterfaceName(), FocusState::NONE)}); + expectedUpdates.push_back({ActivityUpdateElem(clientB->getInterfaceName(), FocusState::FOREGROUND)}); + checkActivityUpdates(contentChannel, expectedUpdates); + ASSERT_TRUE(contentChannel->getState().interfaceName == Activity_B->getInterface()); + ASSERT_NE(contentChannel->getActivity(Activity_A->getInterface()), nullptr); + + // client C acquires Content Channel (with a no patience parameter) + // With no patience, both activity must be released. + auto Activity_C = clientC->createActivity(ContentType::MIXABLE, std::chrono::seconds(0)); + contentChannel->setPrimaryActivity(Activity_C); + contentChannel->setFocus(FocusState::FOREGROUND, MixingBehavior::PRIMARY, true); + expectedUpdates.push_back({ActivityUpdateElem(clientB->getInterfaceName(), FocusState::NONE)}); + expectedUpdates.push_back({ActivityUpdateElem(clientC->getInterfaceName(), FocusState::FOREGROUND)}); + checkActivityUpdates(contentChannel, expectedUpdates); + ASSERT_TRUE(contentChannel->getState().interfaceName == Activity_C->getInterface()); + // Activity A/B is released. + ASSERT_EQ(contentChannel->getActivity(Activity_A->getInterface()), nullptr); + ASSERT_EQ(contentChannel->getActivity(Activity_B->getInterface()), nullptr); + + // when client C activity is released , activity list becomes empty + contentChannel->releaseActivity(clientC->getInterfaceName()); + expectedUpdates.push_back({ActivityUpdateElem(clientC->getInterfaceName(), FocusState::NONE)}); + checkActivityUpdates(contentChannel, expectedUpdates); + ASSERT_EQ(contentChannel->getActivity(Activity_C->getInterface()), nullptr); +} + } // namespace test } // namespace afml } // namespace alexaClientSDK diff --git a/AFML/test/VisualActivityTrackerTest.cpp b/AFML/test/VisualActivityTrackerTest.cpp index e9780ec6..ee600bc7 100644 --- a/AFML/test/VisualActivityTrackerTest.cpp +++ b/AFML/test/VisualActivityTrackerTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018-2020 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. @@ -44,7 +44,7 @@ using namespace avsCommon::utils::json; using namespace ::testing; /// Plenty of time for a test to complete. -static std::chrono::milliseconds WAIT_TIMEOUT(1000); +static std::chrono::milliseconds MY_WAIT_TIMEOUT(1000); /// Namespace for AudioActivityTracke. static const std::string NAMESPACE_AUDIO_ACTIVITY_TRACKER("VisualActivityTracker"); @@ -133,7 +133,6 @@ void VisualActivityTrackerTest::SetUp() { ASSERT_TRUE(m_mockContextManager != nullptr); m_visualChannel = std::make_shared(VISUAL_CHANNEL_NAME, VISUAL_CHANNEL_PRIORITY); - m_visualChannel->setInterface(VISUAL_INTERFACE_NAME); ASSERT_TRUE(m_visualChannel != nullptr); } @@ -197,7 +196,7 @@ void VisualActivityTrackerTest::provideUpdate(const std::vector& m_VisualActivityTracker->notifyOfActivityUpdates(channels); std::this_thread::sleep_for(SHORT_TIMEOUT_MS); m_VisualActivityTracker->provideState(NAMESPACE_AND_NAME_STATE, PROVIDE_STATE_TOKEN_TEST); - ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(WAIT_TIMEOUT)); + ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(MY_WAIT_TIMEOUT)); } SetStateResult VisualActivityTrackerTest::wakeOnSetState() { @@ -214,7 +213,7 @@ TEST_F(VisualActivityTrackerTest, test_noActivityUpdate) { .WillOnce(InvokeWithoutArgs(this, &VisualActivityTrackerTest::wakeOnSetState)); m_VisualActivityTracker->provideState(NAMESPACE_AND_NAME_STATE, PROVIDE_STATE_TOKEN_TEST); - ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(WAIT_TIMEOUT)); + ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(MY_WAIT_TIMEOUT)); } /// Test if there's an empty vector of activity updates, VisualActivityTracker will return an empty context. @@ -226,7 +225,7 @@ TEST_F(VisualActivityTrackerTest, test_emptyActivityUpdate) { /// Test if there's an activityUpdate for one idle channel, VisualActivityTracker will return an empty context. TEST_F(VisualActivityTrackerTest, test_oneIdleChannel) { std::vector channels; - m_visualChannel->setFocus(FocusState::NONE); + m_visualChannel->setFocus(FocusState::NONE, MixingBehavior::MUST_STOP); channels.push_back(m_visualChannel->getState()); provideUpdate(channels); } @@ -234,7 +233,7 @@ TEST_F(VisualActivityTrackerTest, test_oneIdleChannel) { /// Test if there's an activityUpdate for one active channel, context will be reported correctly. TEST_F(VisualActivityTrackerTest, test_oneActiveChannel) { std::vector channels; - m_visualChannel->setFocus(FocusState::FOREGROUND); + m_visualChannel->setFocus(FocusState::FOREGROUND, MixingBehavior::PRIMARY); channels.push_back(m_visualChannel->getState()); provideUpdate(channels); } @@ -246,7 +245,7 @@ TEST_F(VisualActivityTrackerTest, test_oneActiveChannel) { TEST_F(VisualActivityTrackerTest, test_invalidChannelActivityUpdate) { std::vector channels; auto invalidChannel = std::make_shared(INVALID_CHANNEL_NAME, INVALID_CHANNEL_PRIORITY); - m_visualChannel->setFocus(FocusState::FOREGROUND); + m_visualChannel->setFocus(FocusState::FOREGROUND, MixingBehavior::PRIMARY); channels.push_back(m_visualChannel->getState()); channels.push_back(invalidChannel->getState()); provideUpdate(channels); @@ -258,9 +257,9 @@ TEST_F(VisualActivityTrackerTest, test_invalidChannelActivityUpdate) { */ TEST_F(VisualActivityTrackerTest, test_validChannelTwoActivityUpdates) { std::vector channels; - m_visualChannel->setFocus(FocusState::FOREGROUND); + m_visualChannel->setFocus(FocusState::FOREGROUND, MixingBehavior::PRIMARY); channels.push_back(m_visualChannel->getState()); - m_visualChannel->setFocus(FocusState::BACKGROUND); + m_visualChannel->setFocus(FocusState::BACKGROUND, MixingBehavior::MUST_PAUSE); channels.push_back(m_visualChannel->getState()); provideUpdate(channels); } diff --git a/AVSCommon/AVS/include/AVSCommon/AVS/AVSDirective.h b/AVSCommon/AVS/include/AVSCommon/AVS/AVSDirective.h index dde481f2..83bade7a 100644 --- a/AVSCommon/AVS/include/AVSCommon/AVS/AVSDirective.h +++ b/AVSCommon/AVS/include/AVSCommon/AVS/AVSDirective.h @@ -111,6 +111,11 @@ public: */ std::string getUnparsedDirective() const; + /** + * Returns the attachmentContextId. + */ + std::string getAttachmentContextId() const; + private: /** * Constructor. diff --git a/AVSCommon/AVS/include/AVSCommon/AVS/AVSDiscoveryEndpointAttributes.h b/AVSCommon/AVS/include/AVSCommon/AVS/AVSDiscoveryEndpointAttributes.h index 269344e4..3c6d1427 100644 --- a/AVSCommon/AVS/include/AVSCommon/AVS/AVSDiscoveryEndpointAttributes.h +++ b/AVSCommon/AVS/include/AVSCommon/AVS/AVSDiscoveryEndpointAttributes.h @@ -1,5 +1,5 @@ /* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2019-2020 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. @@ -31,7 +31,7 @@ namespace avs { * The structure representing the endpoint attributes used for discovery. * * This structure mirrors the AVS definition which is documented here: - * https://developer.amazon.com/docs/alexa-voice-service/alexa-discovery.html + * https://developer.amazon.com/docs/alexa/alexa-voice-service/alexa-discovery.html * * @note The following attributes will differ from the default endpoint, used to describe this Alexa client, to any * other endpoint controlled by this client. The differences are: @@ -85,7 +85,7 @@ struct AVSDiscoveryEndpointAttributes { /// Maximum length of each endpoint attribute: /// See format specification here: - /// https://developer.amazon.com/docs/alexa-voice-service/alexa-discovery.html#addorupdatereport + /// https://developer.amazon.com/docs/alexa/alexa-voice-service/alexa-discovery.html#addorupdatereport /// @{ static constexpr size_t MAX_ENDPOINT_IDENTIFIER_LENGTH = 256; static constexpr size_t MAX_FRIENDLY_NAME_LENGTH = 128; @@ -112,7 +112,7 @@ struct AVSDiscoveryEndpointAttributes { std::string manufacturerName; /// The display categories the device belongs to. This field should contain at least one category. See categories - /// in this document: https://developer.amazon.com/docs/device-apis/alexa-discovery.html#display-categories + /// in this document: https://developer.amazon.com/docs/alexa/device-apis/alexa-discovery.html#display-categories /// @note: This value should only include ALEXA_VOICE_ENABLED for the default endpoint. std::vector displayCategories; @@ -124,7 +124,7 @@ struct AVSDiscoveryEndpointAttributes { /// The optional connections list describing how the endpoint is connected to the internet or smart home hub. /// You can find the values available here: - /// https://developer.amazon.com/docs/alexa-voice-service/alexa-discovery.html#addorupdatereport + /// https://developer.amazon.com/docs/alexa/alexa-voice-service/alexa-discovery.html#addorupdatereport std::vector> connections; /// The optional custom key value pair used to store about the device. In the AVS documentation, this field name is diff --git a/AVSCommon/AVS/include/AVSCommon/AVS/AVSMessageEndpoint.h b/AVSCommon/AVS/include/AVSCommon/AVS/AVSMessageEndpoint.h index 05ab5d86..8e906a59 100644 --- a/AVSCommon/AVS/include/AVSCommon/AVS/AVSMessageEndpoint.h +++ b/AVSCommon/AVS/include/AVSCommon/AVS/AVSMessageEndpoint.h @@ -1,5 +1,5 @@ /* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2019-2020 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. @@ -27,7 +27,7 @@ namespace avs { /** * The structure representing the endpoint attributes that may be included in AVS Directives and Events. * - * See https://developer.amazon.com/docs/alexa-voice-service/versioning.html for more details. + * See https://developer.amazon.com/docs/alexa/alexa-voice-service/versioning.html for more details. */ struct AVSMessageEndpoint { /** diff --git a/AVSCommon/AVS/include/AVSCommon/AVS/AlexaAssetId.h b/AVSCommon/AVS/include/AVSCommon/AVS/AlexaAssetId.h index 9603f074..7f5463ae 100644 --- a/AVSCommon/AVS/include/AVSCommon/AVS/AlexaAssetId.h +++ b/AVSCommon/AVS/include/AVSCommon/AVS/AlexaAssetId.h @@ -1,5 +1,5 @@ /* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2019-2020 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. @@ -28,7 +28,7 @@ using AlexaAssetId = std::string; /** * String constants for the asset identifier. - * @see https://developer.amazon.com/docs/device-apis/resources-and-assets.html#global-alexa-catalog + * @see https://developer.amazon.com/docs/alexa/device-apis/resources-and-assets.html#global-alexa-catalog */ /// Asset identifier for device with friendly name "Shower". diff --git a/AVSCommon/AVS/include/AVSCommon/AVS/AlexaResponseType.h b/AVSCommon/AVS/include/AVSCommon/AVS/AlexaResponseType.h index 87861da6..223b7985 100644 --- a/AVSCommon/AVS/include/AVSCommon/AVS/AlexaResponseType.h +++ b/AVSCommon/AVS/include/AVSCommon/AVS/AlexaResponseType.h @@ -1,5 +1,5 @@ /* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2019-2020 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. @@ -25,7 +25,7 @@ namespace avs { /** * An enum class indicating possible response from the endpoint on a controller API call. - * Response are derived from @see https://developer.amazon.com/docs/device-apis/alexa-errorresponse.html + * Response are derived from @see https://developer.amazon.com/docs/alexa/device-apis/alexa-errorresponse.html */ enum class AlexaResponseType { /// Success diff --git a/AVSCommon/AVS/include/AVSCommon/AVS/AlexaUnitOfMeasure.h b/AVSCommon/AVS/include/AVSCommon/AVS/AlexaUnitOfMeasure.h index 9f796b06..9189885a 100644 --- a/AVSCommon/AVS/include/AVSCommon/AVS/AlexaUnitOfMeasure.h +++ b/AVSCommon/AVS/include/AVSCommon/AVS/AlexaUnitOfMeasure.h @@ -1,5 +1,5 @@ /* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2019-2020 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. @@ -28,7 +28,7 @@ using AlexaUnitOfMeasure = std::string; /** * String constants for the unit of measure. - * @see https://developer.amazon.com/docs/device-apis/alexa-property-schemas.html#units-of-measure + * @see https://developer.amazon.com/docs/alexa/device-apis/alexa-property-schemas.html#units-of-measure */ /// The Alexa unit of measure as angle degrees. diff --git a/AVSCommon/AVS/include/AVSCommon/AVS/Attachment/DefaultAttachmentReader.h b/AVSCommon/AVS/include/AVSCommon/AVS/Attachment/DefaultAttachmentReader.h new file mode 100644 index 00000000..4fd76f3b --- /dev/null +++ b/AVSCommon/AVS/include/AVSCommon/AVS/Attachment/DefaultAttachmentReader.h @@ -0,0 +1,285 @@ +/* + * Copyright 2019-2020 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 ALEXA_CLIENT_SDK_AVSCOMMON_AVS_INCLUDE_AVSCOMMON_AVS_ATTACHMENT_DEFAULTATTACHMENTREADER_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_AVS_INCLUDE_AVSCOMMON_AVS_ATTACHMENT_DEFAULTATTACHMENTREADER_H_ + +#include +#include +#include "AttachmentReader.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace avs { +namespace attachment { + +/** + * A class that provides functionality to read data from an @c Attachment. + * + * @note This class is not thread-safe beyond the thread-safety provided by the underlying SharedDataStream object. + */ +template +class DefaultAttachmentReader : public AttachmentReader { +public: + /** + * Create an AttachmentReader. + * + * @param policy The policy this reader should adhere to. + * @param sds The underlying @c SharedDataStream which this object will use. + * @param offset If being constructed from an existing @c SharedDataStream, the index indicates where to read from. + * This parameter defaults to 0, indicating no offset from the specified reference. + * @param reference The position in the stream @c offset is applied to. This parameter defaults to @c ABSOLUTE, + * indicating offset is relative to the very beginning of the Attachment. + * @param resetOnOverrun If overrun is detected on @c read, whether to close the attachment (default behavior) or + * to reset the read position to where current write position is (and skip all the bytes in between). + * @return Returns a new AttachmentReader, or nullptr if the operation failed. + */ + static std::unique_ptr create( + typename SDSType::Reader::Policy policy, + std::shared_ptr sds, + typename SDSType::Index offset = 0, + typename SDSType::Reader::Reference reference = SDSType::Reader::Reference::ABSOLUTE, + bool resetOnOverrun = false); + + /** + * Destructor. + */ + ~DefaultAttachmentReader(); + + /// @name AttachmentReader methods. + /// @{ + std::size_t read( + void* buf, + std::size_t numBytes, + ReadStatus* readStatus, + std::chrono::milliseconds timeoutMs = std::chrono::milliseconds(0)) override; + + void close(ClosePoint closePoint = ClosePoint::AFTER_DRAINING_CURRENT_BUFFER) override; + + bool seek(uint64_t offset) override; + + uint64_t getNumUnreadBytes() override; + + /// @} +private: + /** + * Constructor. + * + * @param policy The @c ReaderPolicy of this object. + * @param sds The underlying @c SharedDataStream which this object will use. + * @param resetOnOverrun If overrun is detected on @c read, whether to close the attachment (default behavior) or + * to reset the read position to where current write position is (and skip all the bytes in between). + */ + DefaultAttachmentReader(typename SDSType::Reader::Policy policy, std::shared_ptr sds, bool resetOnOverrun); + + /// Log tag + static const std::string TAG; + + /// The underlying @c SharedDataStream reader. + std::shared_ptr m_reader; + + // On @c read overrun, Whether to close the attachment, or reset it to catch up with the write + bool m_resetOnOverrun; +}; + +template +const std::string DefaultAttachmentReader::TAG = "DefaultAttachmentReader"; + +template +std::unique_ptr DefaultAttachmentReader::create( + typename SDSType::Reader::Policy policy, + std::shared_ptr sds, + typename SDSType::Index offset, + typename SDSType::Reader::Reference reference, + bool resetOnOverrun) { + auto reader = + std::unique_ptr(new DefaultAttachmentReader(policy, sds, resetOnOverrun)); + if (!reader->m_reader) { + ACSDK_ERROR(utils::logger::LogEntry(TAG, "createFailed").d("reason", "object not fully created")); + return nullptr; + } + + if (!reader->m_reader->seek(offset, reference)) { + ACSDK_ERROR(utils::logger::LogEntry(TAG, "ConstructorFailed").d("reason", "seek failed")); + return nullptr; + } + + return std::unique_ptr(reader.release()); +} + +template +DefaultAttachmentReader::~DefaultAttachmentReader() { + close(); +} + +template +std::size_t DefaultAttachmentReader::read( + void* buf, + std::size_t numBytes, + AttachmentReader::ReadStatus* readStatus, + std::chrono::milliseconds timeoutMs) { + if (!readStatus) { + ACSDK_ERROR(utils::logger::LogEntry(TAG, "readFailed").d("reason", "read status is nullptr")); + return 0; + } + + if (!buf) { + ACSDK_ERROR(utils::logger::LogEntry(TAG, "readFailed").d("reason", "buf is nullptr")); + *readStatus = ReadStatus::ERROR_INTERNAL; + return 0; + } + + if (!m_reader) { + ACSDK_INFO(utils::logger::LogEntry(TAG, "readFailed").d("reason", "closed or uninitialized SDS")); + *readStatus = ReadStatus::CLOSED; + return 0; + } + + if (timeoutMs.count() < 0) { + ACSDK_ERROR(utils::logger::LogEntry(TAG, "readFailed").d("reason", "negative timeout")); + *readStatus = ReadStatus::ERROR_INTERNAL; + return 0; + } + + *readStatus = ReadStatus::OK; + + if (0 == numBytes) { + return 0; + } + + const auto wordSize = m_reader->getWordSize(); + if (numBytes < wordSize) { + ACSDK_ERROR( + utils::logger::LogEntry(TAG, "readFailed").d("reason", "bytes requested smaller than SDS word size")); + *readStatus = ReadStatus::ERROR_BYTES_LESS_THAN_WORD_SIZE; + return 0; + } + + std::size_t bytesRead = 0; + const auto numWords = numBytes / wordSize; + + const auto readResult = m_reader->read(buf, numWords, timeoutMs); + + /* + * Convert SDS return code accordingly: + * + * < 0 : Error code. + * 0 : The underlying SDS is closed. + * > 0 : The number of bytes read. + */ + + if (readResult < 0) { + switch (readResult) { + // This means the writer has overwritten the reader. + case SDSType::Reader::Error::OVERRUN: + if (m_resetOnOverrun) { + // An attachment's read position will be reset to current writer position. + // Subsequent reads will deliver data from current writer position onward. + *readStatus = ReadStatus::OK_OVERRUN_RESET; + ACSDK_DEBUG5(utils::logger::LogEntry(TAG, "readFailed").d("reason", "memory overrun by writer")); + m_reader->seek(0, SDSType::Reader::Reference::BEFORE_WRITER); + } else { + // An attachment cannot recover from this. + *readStatus = ReadStatus::ERROR_OVERRUN; + ACSDK_ERROR(utils::logger::LogEntry(TAG, "readFailed").d("reason", "memory overrun by writer")); + close(); + } + break; + + // This means there is still an active writer, but no data. A read would block if the policy was blocking. + case SDSType::Reader::Error::WOULDBLOCK: + *readStatus = ReadStatus::OK_WOULDBLOCK; + break; + + // This means there is still an active writer, but no data. A read call timed out waiting for data. + case SDSType::Reader::Error::TIMEDOUT: + *readStatus = ReadStatus::OK_TIMEDOUT; + break; + } + + // If the status was not updated, then there's an error code from SDS we may not be handling. + if (ReadStatus::OK == *readStatus) { + ACSDK_ERROR( + utils::logger::LogEntry(TAG, "readFailed").d("reason", "unhandled error code").d("code", readResult)); + *readStatus = ReadStatus::ERROR_INTERNAL; + } + + } else if (0 == readResult) { + *readStatus = ReadStatus::CLOSED; + ACSDK_DEBUG0(utils::logger::LogEntry(TAG, "readFailed").d("reason", "SDS is closed")); + } else { + bytesRead = static_cast(readResult) * wordSize; + } + + return bytesRead; +} + +template +void DefaultAttachmentReader::close(AttachmentReader::ClosePoint closePoint) { + if (m_reader) { + switch (closePoint) { + case ClosePoint::IMMEDIATELY: + m_reader->close(); + return; + case ClosePoint::AFTER_DRAINING_CURRENT_BUFFER: + m_reader->close(0, SDSType::Reader::Reference::BEFORE_WRITER); + return; + } + } +} + +template +bool DefaultAttachmentReader::seek(uint64_t offset) { + if (m_reader) { + return m_reader->seek(offset); + } + return false; +} + +template +uint64_t DefaultAttachmentReader::getNumUnreadBytes() { + if (m_reader) { + return m_reader->tell(SDSType::Reader::Reference::BEFORE_WRITER); + } + + ACSDK_ERROR(utils::logger::LogEntry(TAG, "getNumUnreadBytesFailed").d("reason", "noReader")); + return 0; +} + +template +DefaultAttachmentReader::DefaultAttachmentReader( + typename SDSType::Reader::Policy policy, + std::shared_ptr sds, + bool resetOnOverrun) : + m_resetOnOverrun{resetOnOverrun} { + if (!sds) { + ACSDK_ERROR(utils::logger::LogEntry(TAG, "ConstructorFailed").d("reason", "SDS parameter is nullptr")); + return; + } + + m_reader = sds->createReader(policy); + + if (!m_reader) { + ACSDK_ERROR(utils::logger::LogEntry(TAG, "ConstructorFailed").d("reason", "could not create an SDS reader")); + return; + } +} + +} // namespace attachment +} // namespace avs +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_AVS_INCLUDE_AVSCOMMON_AVS_ATTACHMENT_DEFAULTATTACHMENTREADER_H_ diff --git a/AVSCommon/AVS/include/AVSCommon/AVS/Attachment/InProcessAttachmentReader.h b/AVSCommon/AVS/include/AVSCommon/AVS/Attachment/InProcessAttachmentReader.h index e024d932..5c72f2bf 100644 --- a/AVSCommon/AVS/include/AVSCommon/AVS/Attachment/InProcessAttachmentReader.h +++ b/AVSCommon/AVS/include/AVSCommon/AVS/Attachment/InProcessAttachmentReader.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-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. @@ -62,7 +62,7 @@ public: /** * Destructor. */ - ~InProcessAttachmentReader(); + ~InProcessAttachmentReader() = default; std::size_t read( void* buf, @@ -78,20 +78,14 @@ public: private: /** - * Constructor. + * Constructor * - * @param policy The @c ReaderPolicy of this object. - * @param sds The underlying @c SharedDataStream which this object will use. - * @param resetOnOverrun If overrun is detected on @c read, whether to close the attachment (default behavior) or - * to reset the read position to where current write position is (and skip all the bytes in between). + * @param delegate The reader implementation to use for in process attachment reader. */ - InProcessAttachmentReader(SDSTypeReader::Policy policy, std::shared_ptr sds, bool resetOnOverrun); + explicit InProcessAttachmentReader(std::unique_ptr delegate); - /// The underlying @c SharedDataStream reader. - std::shared_ptr m_reader; - - // On @c read overrun, Whether to close the attachment, or reset it to catch up with the write - bool m_resetOnOverrun; + // Delegate reader + std::unique_ptr m_delegate; }; } // namespace attachment diff --git a/AVSCommon/AVS/include/AVSCommon/AVS/AudioInputStream.h b/AVSCommon/AVS/include/AVSCommon/AVS/AudioInputStream.h index 93b0f135..9b5a5fa1 100644 --- a/AVSCommon/AVS/include/AVSCommon/AVS/AudioInputStream.h +++ b/AVSCommon/AVS/include/AVSCommon/AVS/AudioInputStream.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-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. @@ -16,14 +16,24 @@ #ifndef ALEXA_CLIENT_SDK_AVSCOMMON_AVS_INCLUDE_AVSCOMMON_AVS_AUDIOINPUTSTREAM_H_ #define ALEXA_CLIENT_SDK_AVSCOMMON_AVS_INCLUDE_AVSCOMMON_AVS_AUDIOINPUTSTREAM_H_ +#include "AVSCommon/Utils/SDS/SharedDataStream.h" + +#ifdef CUSTOM_SDS_TRAITS_HEADER +#include CUSTOM_SDS_TRAITS_HEADER +#else #include "AVSCommon/Utils/SDS/InProcessSDS.h" +#endif namespace alexaClientSDK { namespace avsCommon { namespace avs { /// The type used store and stream binary data. -using AudioInputStream = utils::sds::InProcessSDS; +#ifdef CUSTOM_SDS_TRAITS_CLASS +using AudioInputStream = utils::sds::SharedDataStream; +#else +using AudioInputStream = utils::sds::SharedDataStream; +#endif } // namespace avs } // namespace avsCommon diff --git a/AVSCommon/AVS/include/AVSCommon/AVS/CapabilityAgent.h b/AVSCommon/AVS/include/AVSCommon/AVS/CapabilityAgent.h index f5cc342e..e5625975 100644 --- a/AVSCommon/AVS/include/AVSCommon/AVS/CapabilityAgent.h +++ b/AVSCommon/AVS/include/AVSCommon/AVS/CapabilityAgent.h @@ -74,7 +74,7 @@ public: void onDeregistered() override; - void onFocusChanged(FocusState newFocus) override; + void onFocusChanged(FocusState newFocus, MixingBehavior behavior) override; protected: /** diff --git a/AVSCommon/AVS/include/AVSCommon/AVS/CapabilityResources.h b/AVSCommon/AVS/include/AVSCommon/AVS/CapabilityResources.h index 028117f1..e00b66ac 100644 --- a/AVSCommon/AVS/include/AVSCommon/AVS/CapabilityResources.h +++ b/AVSCommon/AVS/include/AVSCommon/AVS/CapabilityResources.h @@ -1,5 +1,5 @@ /* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2019-2020 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. @@ -29,7 +29,7 @@ namespace avs { /** * This class represents the resources used by a Capability, communicated as friendly names to AVS. - * @see https://developer.amazon.com/docs/device-apis/resources-and-assets.html#capability-resources + * @see https://developer.amazon.com/docs/alexa/device-apis/resources-and-assets.html#capability-resources */ class CapabilityResources { public: diff --git a/AVSCommon/AVS/include/AVSCommon/AVS/ContentType.h b/AVSCommon/AVS/include/AVSCommon/AVS/ContentType.h new file mode 100644 index 00000000..4173eef1 --- /dev/null +++ b/AVSCommon/AVS/include/AVSCommon/AVS/ContentType.h @@ -0,0 +1,74 @@ +/* + * Copyright 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 ALEXA_CLIENT_SDK_AVSCOMMON_AVS_INCLUDE_AVSCOMMON_AVS_CONTENTTYPE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_AVS_INCLUDE_AVSCOMMON_AVS_CONTENTTYPE_H_ + +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace avs { + +enum class ContentType { + /// Indicates that the corresponding Activity is mixable with other channels + /// Such Activities may duck upon receiving FocusState::BACKGROUND focus + MIXABLE, + + /// Indicates that the corresponding Activity is not mixable with other channels + /// Such Activities must pause upon receiving FocusState::BACKGROUND focus + NONMIXABLE, + + /// Indicates that the corresponding ContentType was undefined/unitialized + UNDEFINED, + + /// Indicates the Number of @c ContentType enumerations + NUM_CONTENT_TYPE +}; + +/** + * This function converts the provided @c ContentType to a string. + * + * @param contentType The @c ContentType to convert to a string. + * @return The string conversion of @c contentType. + */ +inline std::string contentTypeToString(ContentType contentType) { + switch (contentType) { + case ContentType::MIXABLE: + return "MIXABLE"; + case ContentType::NONMIXABLE: + return "NONMIXABLE"; + case ContentType::UNDEFINED: + return "UNDEFINED"; + default: + return "UNDEFINED"; + } +} + +/** + * Write a @c ContentType value to an @c ostream as a string. + * + * @param stream The stream to write the value to. + * @param contenType The @c ContentType value to write to the @c ostream as a string. + * @return The @c ostream that was passed in and written to. + */ +inline std::ostream& operator<<(std::ostream& stream, const ContentType& contentType) { + return stream << contentTypeToString(contentType); +} +} // namespace avs +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_AVS_INCLUDE_AVSCOMMON_AVS_CONTENTTYPE_H_ diff --git a/AVSCommon/AVS/include/AVSCommon/AVS/DialogUXStateAggregator.h b/AVSCommon/AVS/include/AVSCommon/AVS/DialogUXStateAggregator.h index abdd5eb5..89bf0e37 100644 --- a/AVSCommon/AVS/include/AVSCommon/AVS/DialogUXStateAggregator.h +++ b/AVSCommon/AVS/include/AVSCommon/AVS/DialogUXStateAggregator.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -26,7 +26,7 @@ #include "AVSCommon/SDKInterfaces/InteractionModelRequestProcessingObserverInterface.h" #include "AVSCommon/SDKInterfaces/MessageObserverInterface.h" #include "AVSCommon/SDKInterfaces/SpeechSynthesizerObserverInterface.h" - +#include #include #include @@ -52,10 +52,12 @@ public: * arrive from AVS. * @param timeoutForListeningToIdle This timeout will be used to time out from the LISTENING state in case the * Request Processing Started (RPS) directive is not received from AVS. + * @param metricRecorder The metric recorder. */ DialogUXStateAggregator( std::chrono::milliseconds timeoutForThinkingToIdle = std::chrono::seconds{8}, - std::chrono::milliseconds timeoutForListeningToIdle = std::chrono::seconds{8}); + std::chrono::milliseconds timeoutForListeningToIdle = std::chrono::seconds{8}, + std::shared_ptr metricRecorder = nullptr); /** * Adds an observer to be notified of UX state changes. @@ -82,7 +84,10 @@ public: void onStateChanged(sdkInterfaces::AudioInputProcessorObserverInterface::State state) override; - void onStateChanged(sdkInterfaces::SpeechSynthesizerObserverInterface::SpeechSynthesizerState state) override; + void onStateChanged( + sdkInterfaces::SpeechSynthesizerObserverInterface::SpeechSynthesizerState state, + const avsCommon::utils::mediaPlayer::MediaPlayerInterface::SourceId mediaSourceId, + const avsCommon::utils::Optional& mediaPlayerState) override; void receive(const std::string& contextId, const std::string& message) override; @@ -143,6 +148,9 @@ private: */ /// @{ + /// The metric recorder. + std::shared_ptr m_metricRecorder; + /// The @c UXObserverInterface to notify any time the Alexa Voice Service UX state needs to change. std::unordered_set> m_observers; diff --git a/AVSCommon/AVS/include/AVSCommon/AVS/FocusState.h b/AVSCommon/AVS/include/AVSCommon/AVS/FocusState.h index 5b0124bc..b057d5ce 100644 --- a/AVSCommon/AVS/include/AVSCommon/AVS/FocusState.h +++ b/AVSCommon/AVS/include/AVSCommon/AVS/FocusState.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -16,6 +16,7 @@ #ifndef ALEXA_CLIENT_SDK_AVSCOMMON_AVS_INCLUDE_AVSCOMMON_AVS_FOCUSSTATE_H_ #define ALEXA_CLIENT_SDK_AVSCOMMON_AVS_INCLUDE_AVSCOMMON_AVS_FOCUSSTATE_H_ +#include #include namespace alexaClientSDK { diff --git a/AVSCommon/AVS/include/AVSCommon/AVS/IndicatorState.h b/AVSCommon/AVS/include/AVSCommon/AVS/IndicatorState.h index 52042a4f..3bfb199f 100644 --- a/AVSCommon/AVS/include/AVSCommon/AVS/IndicatorState.h +++ b/AVSCommon/AVS/include/AVSCommon/AVS/IndicatorState.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-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. @@ -45,7 +45,7 @@ inline int indicatorStateToInt(IndicatorState state) { * @param stateNum The int to convert. * @return The IndicatorState representation of stateNum or nullptr if stateNum is invalid. */ -inline const IndicatorState intToIndicatorState(int stateNum) { +inline IndicatorState intToIndicatorState(int stateNum) { if (stateNum < 0 || stateNum >= static_cast(IndicatorState::UNDEFINED)) { return IndicatorState::UNDEFINED; } diff --git a/AVSCommon/AVS/include/AVSCommon/AVS/Initialization/AlexaClientSDKInit.h b/AVSCommon/AVS/include/AVSCommon/AVS/Initialization/AlexaClientSDKInit.h index 86a5789f..b559f8e4 100644 --- a/AVSCommon/AVS/include/AVSCommon/AVS/Initialization/AlexaClientSDKInit.h +++ b/AVSCommon/AVS/include/AVSCommon/AVS/Initialization/AlexaClientSDKInit.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. diff --git a/AVSCommon/AVS/include/AVSCommon/AVS/MixingBehavior.h b/AVSCommon/AVS/include/AVSCommon/AVS/MixingBehavior.h new file mode 100644 index 00000000..eeae32e8 --- /dev/null +++ b/AVSCommon/AVS/include/AVSCommon/AVS/MixingBehavior.h @@ -0,0 +1,102 @@ +/* + * Copyright 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 ALEXA_CLIENT_SDK_AVSCOMMON_AVS_INCLUDE_AVSCOMMON_AVS_MIXINGBEHAVIOR_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_AVS_INCLUDE_AVSCOMMON_AVS_MIXINGBEHAVIOR_H_ + +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace avs { + +enum class MixingBehavior { + /// Indicates that the corresponding Activity is the primary Activity on the AFML Channel + PRIMARY, + + /// Indicates that the corresponding Activity may duck + /// If ducking is not possible, the Activity must pause instead + MAY_DUCK, + + /// Indicates that the corresponding Activity must pause + MUST_PAUSE, + + /// Indicates that the corresponding Activity must stop + MUST_STOP, + + /// Indicates that the corresponding Activity may adopt any one of the above behaviors + UNDEFINED +}; + +/** + * This function converts the provided @c MixingBehavior to a string. + * + * @param behavior The @c MixingBehavior to convert to a string. + * @return The string conversion of @c behavior. + */ +inline std::string mixingBehaviorToString(MixingBehavior behavior) { + switch (behavior) { + case MixingBehavior::PRIMARY: + return "PRIMARY"; + case MixingBehavior::MAY_DUCK: + return "MAY_DUCK"; + case MixingBehavior::MUST_PAUSE: + return "MUST_PAUSE"; + case MixingBehavior::MUST_STOP: + return "MUST_STOP"; + case MixingBehavior::UNDEFINED: + return "UNDEFINED"; + } + return "UNDEFINED"; +} + +/** + * This function reverse maps the provided string to corresponding MixingBehavior Implementation as specified by + * mixingBehaviorToString + * @param input string to convert to corresponding MixingBehavior + * @return @c MixingBehavior that corresponds to the input string. In case of error + * the API returns MixingBehavior::UNDEFINED + */ +inline MixingBehavior getMixingBehavior(const std::string& input) { + MixingBehavior behavior = MixingBehavior::UNDEFINED; + if (mixingBehaviorToString(MixingBehavior::PRIMARY) == input) { + behavior = MixingBehavior::PRIMARY; + } else if (mixingBehaviorToString(MixingBehavior::MAY_DUCK) == input) { + behavior = MixingBehavior::MAY_DUCK; + } else if (mixingBehaviorToString(MixingBehavior::MUST_PAUSE) == input) { + behavior = MixingBehavior::MUST_PAUSE; + } else if (mixingBehaviorToString(MixingBehavior::MUST_STOP) == input) { + behavior = MixingBehavior::MUST_STOP; + } + + return behavior; +} + +/** + * Write a @c MixingBehavior value to an @c ostream as a string. + * + * @param stream The stream to write the value to. + * @param behavior The @c MixingBehavior value to write to the @c ostream as a string. + * @return The @c ostream that was passed in and written to. + */ +inline std::ostream& operator<<(std::ostream& stream, const MixingBehavior& behavior) { + return stream << mixingBehaviorToString(behavior); +} + +} // namespace avs +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_AVS_INCLUDE_AVSCOMMON_AVS_MIXINGBEHAVIOR_H_ diff --git a/AVSCommon/AVS/include/AVSCommon/AVS/PlayRequestor.h b/AVSCommon/AVS/include/AVSCommon/AVS/PlayRequestor.h index b1da4a5c..c2af660a 100644 --- a/AVSCommon/AVS/include/AVSCommon/AVS/PlayRequestor.h +++ b/AVSCommon/AVS/include/AVSCommon/AVS/PlayRequestor.h @@ -1,5 +1,5 @@ /* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2019-2020 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. @@ -35,6 +35,10 @@ struct PlayRequestor { std::string id; }; +inline bool operator==(const PlayRequestor& playRequestorA, const PlayRequestor& playRequestorB) { + return playRequestorA.type == playRequestorB.type && playRequestorA.id == playRequestorB.id; +} + } // namespace avs } // namespace avsCommon } // namespace alexaClientSDK diff --git a/AVSCommon/AVS/include/AVSCommon/AVS/StateRefreshPolicy.h b/AVSCommon/AVS/include/AVSCommon/AVS/StateRefreshPolicy.h index d7068aed..0427e84a 100644 --- a/AVSCommon/AVS/include/AVSCommon/AVS/StateRefreshPolicy.h +++ b/AVSCommon/AVS/include/AVSCommon/AVS/StateRefreshPolicy.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -23,6 +23,10 @@ namespace avs { /** * An enum class used to specify the refresh policy for the state information provided by a @c stateProviderInterface. * The @c stateProviderInterface must specify the refresh policy when it updates its state via @c setState. + * + * Note: When a @c stateProviderInterface provides an empty state, the behavior is as follows: + * - For @c StateRefreshPolicy @c ALWAYS and @c NEVER, the empty state is included in the context. + * - For @c StateRefreshPolicy @c SOMETIMES, the empty state is NOT included in the context. */ enum class StateRefreshPolicy { /** diff --git a/AVSCommon/AVS/src/AVSContext.cpp b/AVSCommon/AVS/src/AVSContext.cpp index 3be86b22..d5543aca 100644 --- a/AVSCommon/AVS/src/AVSContext.cpp +++ b/AVSCommon/AVS/src/AVSContext.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2019-2020 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. @@ -35,7 +35,7 @@ static const std::string VALUE_KEY_STRING = "value"; static const std::string TIME_OF_SAMPLE_KEY_STRING = "timeOfSample"; /// Key used to identify an the uncertainty in milliseconds related to the time of sample. For more information: -/// https://developer.amazon.com/docs/alexa-voice-service/reportable-state-properties.html#property-object +/// https://developer.amazon.com/docs/alexa/alexa-voice-service/reportable-state-properties.html#property-object static const std::string UNCERTAINTY_KEY_STRING = "uncertaintyInMilliseconds"; /// String to identify log entries originating from this file. diff --git a/AVSCommon/AVS/src/AVSDirective.cpp b/AVSCommon/AVS/src/AVSDirective.cpp index 425f6f31..ce0f1992 100644 --- a/AVSCommon/AVS/src/AVSDirective.cpp +++ b/AVSCommon/AVS/src/AVSDirective.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -272,9 +272,9 @@ std::pair, AVSDirective::ParseStatus> AVSDirective std::unique_ptr AVSDirective::create( const std::string& unparsedDirective, - std::shared_ptr avsMessageHeader, + const std::shared_ptr avsMessageHeader, const std::string& payload, - std::shared_ptr attachmentManager, + const std::shared_ptr attachmentManager, const std::string& attachmentContextId, const utils::Optional& endpoint) { if (!avsMessageHeader) { @@ -313,6 +313,10 @@ std::string AVSDirective::getUnparsedDirective() const { return m_unparsedDirective; } +std::string AVSDirective::getAttachmentContextId() const { + return m_attachmentContextId; +} + } // namespace avs } // namespace avsCommon } // namespace alexaClientSDK diff --git a/AVSCommon/AVS/src/AlexaClientSDKInit.cpp b/AVSCommon/AVS/src/AlexaClientSDKInit.cpp index e43658ec..f15a66c4 100644 --- a/AVSCommon/AVS/src/AlexaClientSDKInit.cpp +++ b/AVSCommon/AVS/src/AlexaClientSDKInit.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. diff --git a/AVSCommon/AVS/src/Attachment/InProcessAttachmentReader.cpp b/AVSCommon/AVS/src/Attachment/InProcessAttachmentReader.cpp index 8d697420..f35019d9 100644 --- a/AVSCommon/AVS/src/Attachment/InProcessAttachmentReader.cpp +++ b/AVSCommon/AVS/src/Attachment/InProcessAttachmentReader.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-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. @@ -13,68 +13,30 @@ * permissions and limitations under the License. */ +#include "AVSCommon/AVS/Attachment/DefaultAttachmentReader.h" #include "AVSCommon/AVS/Attachment/InProcessAttachmentReader.h" -#include "AVSCommon/Utils/Logger/Logger.h" - -using namespace alexaClientSDK::avsCommon::utils; namespace alexaClientSDK { namespace avsCommon { namespace avs { namespace attachment { -/// String to identify log entries originating from this file. -static const std::string TAG("InProcessAttachmentReader"); - -/** - * 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) - std::unique_ptr InProcessAttachmentReader::create( SDSTypeReader::Policy policy, std::shared_ptr sds, SDSTypeIndex offset, SDSTypeReader::Reference reference, bool resetOnOverrun) { - auto reader = - std::unique_ptr(new InProcessAttachmentReader(policy, sds, resetOnOverrun)); - - if (!reader->m_reader) { - ACSDK_ERROR(LX("createFailed").d("reason", "object not fully created")); + auto readerImpl = + DefaultAttachmentReader::create(policy, std::move(sds), offset, reference, resetOnOverrun); + if (!readerImpl) { return nullptr; } - - if (!reader->m_reader->seek(offset, reference)) { - ACSDK_ERROR(LX("ConstructorFailed").d("reason", "seek failed")); - return nullptr; - } - - return reader; + return std::unique_ptr(new InProcessAttachmentReader(std::move(readerImpl))); } -InProcessAttachmentReader::InProcessAttachmentReader( - SDSTypeReader::Policy policy, - std::shared_ptr sds, - bool resetOnOverrun) : - m_resetOnOverrun{resetOnOverrun} { - if (!sds) { - ACSDK_ERROR(LX("ConstructorFailed").d("reason", "SDS parameter is nullptr")); - return; - } - - m_reader = sds->createReader(policy); - - if (!m_reader) { - ACSDK_ERROR(LX("ConstructorFailed").d("reason", "could not create an SDS reader")); - return; - } -} - -InProcessAttachmentReader::~InProcessAttachmentReader() { - close(); +InProcessAttachmentReader::InProcessAttachmentReader(std::unique_ptr reader) : + m_delegate(std::move(reader)) { } std::size_t InProcessAttachmentReader::read( @@ -82,121 +44,19 @@ std::size_t InProcessAttachmentReader::read( std::size_t numBytes, ReadStatus* readStatus, std::chrono::milliseconds timeoutMs) { - if (!readStatus) { - ACSDK_ERROR(LX("readFailed").d("reason", "read status is nullptr")); - return 0; - } - - if (!m_reader) { - ACSDK_INFO(LX("readFailed").d("reason", "closed or uninitialized SDS")); - *readStatus = ReadStatus::CLOSED; - return 0; - } - - if (timeoutMs.count() < 0) { - ACSDK_ERROR(LX("readFailed").d("reason", "negative timeout")); - *readStatus = ReadStatus::ERROR_INTERNAL; - return 0; - } - - *readStatus = ReadStatus::OK; - - if (0 == numBytes) { - return 0; - } - - auto wordSize = m_reader->getWordSize(); - if (numBytes < wordSize) { - ACSDK_ERROR(LX("readFailed").d("reason", "bytes requested smaller than SDS word size")); - *readStatus = ReadStatus::ERROR_BYTES_LESS_THAN_WORD_SIZE; - return 0; - } - - std::size_t bytesRead = 0; - auto numWords = numBytes / wordSize; - - auto readResult = m_reader->read(buf, numWords, timeoutMs); - - /* - * Convert SDS return code accordingly: - * - * < 0 : Error code. - * 0 : The underlying SDS is closed. - * > 0 : The number of bytes read. - */ - - if (readResult < 0) { - switch (readResult) { - // This means the writer has overwritten the reader. - case SDSType::Reader::Error::OVERRUN: - if (m_resetOnOverrun) { - // An attachment's read position will be reset to current writer position. - // Subsequent reads will deliver data from current writer position onward. - *readStatus = ReadStatus::OK_OVERRUN_RESET; - ACSDK_DEBUG5(LX("readFailed").d("reason", "memory overrun by writer")); - m_reader->seek(0, SDSTypeReader::Reference::BEFORE_WRITER); - } else { - // An attachment cannot recover from this. - *readStatus = ReadStatus::ERROR_OVERRUN; - ACSDK_ERROR(LX("readFailed").d("reason", "memory overrun by writer")); - close(); - } - break; - - // This means there is still an active writer, but no data. A read would block if the policy was blocking. - case SDSType::Reader::Error::WOULDBLOCK: - *readStatus = ReadStatus::OK_WOULDBLOCK; - break; - - // This means there is still an active writer, but no data. A read call timed out waiting for data. - case SDSType::Reader::Error::TIMEDOUT: - *readStatus = ReadStatus::OK_TIMEDOUT; - break; - } - - // If the status was not updated, then there's an error code from SDS we may not be handling. - if (ReadStatus::OK == *readStatus) { - ACSDK_ERROR(LX("readFailed").d("reason", "unhandled error code").d("code", readResult)); - *readStatus = ReadStatus::ERROR_INTERNAL; - } - - } else if (0 == readResult) { - *readStatus = ReadStatus::CLOSED; - ACSDK_DEBUG0(LX("readFailed").d("reason", "SDS is closed")); - } else { - bytesRead = static_cast(readResult) * wordSize; - } - - return bytesRead; + return m_delegate->read(buf, numBytes, readStatus, timeoutMs); } void InProcessAttachmentReader::close(ClosePoint closePoint) { - if (m_reader) { - switch (closePoint) { - case ClosePoint::IMMEDIATELY: - m_reader->close(); - return; - case ClosePoint::AFTER_DRAINING_CURRENT_BUFFER: - m_reader->close(0, SDSType::Reader::Reference::BEFORE_WRITER); - return; - } - } + m_delegate->close(closePoint); } bool InProcessAttachmentReader::seek(uint64_t offset) { - if (m_reader) { - return m_reader->seek(offset); - } - return false; + return m_delegate->seek(offset); } uint64_t InProcessAttachmentReader::getNumUnreadBytes() { - if (m_reader) { - return m_reader->tell(utils::sds::InProcessSDS::Reader::Reference::BEFORE_WRITER); - } - - ACSDK_ERROR(LX("getNumUnreadBytesFailed").d("reason", "noReader")); - return 0; + return m_delegate->getNumUnreadBytes(); } } // namespace attachment diff --git a/AVSCommon/AVS/src/CapabilityAgent.cpp b/AVSCommon/AVS/src/CapabilityAgent.cpp index 5ddb3617..c8324e77 100644 --- a/AVSCommon/AVS/src/CapabilityAgent.cpp +++ b/AVSCommon/AVS/src/CapabilityAgent.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -141,7 +141,7 @@ void CapabilityAgent::removeDirective(const std::string& messageId) { m_directiveInfoMap.erase(messageId); } -void CapabilityAgent::onFocusChanged(FocusState) { +void CapabilityAgent::onFocusChanged(FocusState, MixingBehavior) { // default no-op } diff --git a/AVSCommon/AVS/src/DialogUXStateAggregator.cpp b/AVSCommon/AVS/src/DialogUXStateAggregator.cpp index 54d564eb..241941da 100644 --- a/AVSCommon/AVS/src/DialogUXStateAggregator.cpp +++ b/AVSCommon/AVS/src/DialogUXStateAggregator.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -12,6 +12,8 @@ * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ +#include +#include #include "AVSCommon/AVS/DialogUXStateAggregator.h" @@ -20,6 +22,7 @@ namespace avsCommon { namespace avs { using namespace sdkInterfaces; +using namespace avsCommon::utils::metrics; /// String to identify log entries originating from this file. static const std::string TAG("DialogUXStateAggregator"); @@ -36,9 +39,42 @@ static const std::string TAG("DialogUXStateAggregator"); */ static const std::chrono::milliseconds SHORT_TIMEOUT{200}; +/// Custom Metrics prefix used by DialogUXStateAggregator. +static const std::string CUSTOM_METRIC_PREFIX = "CUSTOM-"; + +/// error metric for Listening timeout expires +static const std::string LISTENING_TIMEOUT_EXPIRES = "LISTENING_TIMEOUT_EXPIRES"; + +/// error metric for Thinking timeout expires +static const std::string THINKING_TIMEOUT_EXPIRES = "THINKING_TIMEOUT_EXPIRES"; + +/** + * Submits a metric of given event name + * @param metricRecorder The @c MetricRecorderInterface which records Metric events + * @param eventName The name of the metric event + */ +static void submitMetric(const std::shared_ptr& metricRecorder, const std::string& eventName) { + if (!metricRecorder) { + return; + } + + auto metricEvent = MetricEventBuilder{} + .setActivityName(CUSTOM_METRIC_PREFIX + eventName) + .addDataPoint(DataPointCounterBuilder{}.setName(eventName).increment(1).build()) + .build(); + + if (metricEvent == nullptr) { + ACSDK_ERROR(LX("Error creating metric.")); + return; + } + recordMetric(metricRecorder, metricEvent); +} + DialogUXStateAggregator::DialogUXStateAggregator( std::chrono::milliseconds timeoutForThinkingToIdle, - std::chrono::milliseconds timeoutForListeningToIdle) : + std::chrono::milliseconds timeoutForListeningToIdle, + std::shared_ptr metricRecorder) : + m_metricRecorder{metricRecorder}, m_currentState{DialogUXStateObserverInterface::DialogUXState::IDLE}, m_timeoutForThinkingToIdle{timeoutForThinkingToIdle}, m_timeoutForListeningToIdle{timeoutForListeningToIdle}, @@ -96,7 +132,10 @@ void DialogUXStateAggregator::onStateChanged(AudioInputProcessorObserverInterfac }); } -void DialogUXStateAggregator::onStateChanged(SpeechSynthesizerObserverInterface::SpeechSynthesizerState state) { +void DialogUXStateAggregator::onStateChanged( + SpeechSynthesizerObserverInterface::SpeechSynthesizerState state, + const avsCommon::utils::mediaPlayer::MediaPlayerInterface::SourceId mediaSourceId, + const avsCommon::utils::Optional& mediaPlayerState) { m_speechSynthesizerState = state; m_executor.submit([this, state]() { @@ -139,7 +178,7 @@ void DialogUXStateAggregator::receive(const std::string& contextId, const std::s void DialogUXStateAggregator::onConnectionStatusChanged( const ConnectionStatusObserverInterface::Status status, const ConnectionStatusObserverInterface::ChangedReason reason) { - m_executor.submit([this, &status]() { + m_executor.submit([this, status]() { if (status != avsCommon::sdkInterfaces::ConnectionStatusObserverInterface::Status::CONNECTED) { setState(DialogUXStateObserverInterface::DialogUXState::IDLE); } @@ -201,6 +240,8 @@ void DialogUXStateAggregator::transitionFromThinkingTimedOut() { if (DialogUXStateObserverInterface::DialogUXState::THINKING == m_currentState) { ACSDK_DEBUG(LX("transitionFromThinkingTimedOut")); setState(DialogUXStateObserverInterface::DialogUXState::IDLE); + + submitMetric(m_metricRecorder, THINKING_TIMEOUT_EXPIRES); } }); } @@ -210,6 +251,8 @@ void DialogUXStateAggregator::transitionFromListeningTimedOut() { if (DialogUXStateObserverInterface::DialogUXState::LISTENING == m_currentState) { ACSDK_DEBUG(LX("transitionFromListeningTimedOut")); setState(DialogUXStateObserverInterface::DialogUXState::IDLE); + + submitMetric(m_metricRecorder, LISTENING_TIMEOUT_EXPIRES); } }); } diff --git a/AVSCommon/AVS/src/MessageRequest.cpp b/AVSCommon/AVS/src/MessageRequest.cpp index 1d58b453..41b61184 100644 --- a/AVSCommon/AVS/src/MessageRequest.cpp +++ b/AVSCommon/AVS/src/MessageRequest.cpp @@ -116,8 +116,6 @@ void MessageRequest::removeObserver( m_observers.erase(observer); } -using namespace avsCommon::sdkInterfaces; - } // namespace avs } // namespace avsCommon } // namespace alexaClientSDK diff --git a/AVSCommon/AVS/test/Attachment/AttachmentUtilsTest.cpp b/AVSCommon/AVS/test/Attachment/AttachmentUtilsTest.cpp index 49dfac04..0dd02865 100644 --- a/AVSCommon/AVS/test/Attachment/AttachmentUtilsTest.cpp +++ b/AVSCommon/AVS/test/Attachment/AttachmentUtilsTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018-2020 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. @@ -14,6 +14,7 @@ */ #include +#include #include #include @@ -51,8 +52,9 @@ void AttachmentUtilsTest::SetUp() { * Test read until end of buffer */ TEST_F(AttachmentUtilsTest, test_readCompleteBuffer) { - char dstBuffer[sampleBuffer.length() + 10]; - memset(dstBuffer, 0, sampleBuffer.length() + 10); + std::vector dstBufferVec(sampleBuffer.length() + 10); + std::fill(dstBufferVec.begin(), dstBufferVec.end(), 0); + char* dstBuffer = dstBufferVec.data(); AttachmentReader::ReadStatus status; size_t bytesRead = m_attachmentReader->read(dstBuffer, sampleBuffer.length(), &status); diff --git a/AVSCommon/AVS/test/Attachment/Common/Common.cpp b/AVSCommon/AVS/test/Attachment/Common/Common.cpp index 1c275080..83cc4c27 100644 --- a/AVSCommon/AVS/test/Attachment/Common/Common.cpp +++ b/AVSCommon/AVS/test/Attachment/Common/Common.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -34,11 +34,15 @@ std::unique_ptr createSDS(int desiredSize) { } std::vector createTestPattern(int patternSize) { - std::vector vec(patternSize); - std::independent_bits_engine engine; - std::generate(begin(vec), end(vec), std::ref(engine)); + std::vector ret(patternSize); + std::vector vec(patternSize); + std::independent_bits_engine engine; - return vec; + std::generate(begin(vec), end(vec), std::ref(engine)); + for (size_t i = 0; i < vec.size(); i++) { + ret[i] = static_cast(vec[i]); + } + return ret; } } // namespace test diff --git a/AVSCommon/AVS/test/CMakeLists.txt b/AVSCommon/AVS/test/CMakeLists.txt index 53bb7409..374c81bf 100644 --- a/AVSCommon/AVS/test/CMakeLists.txt +++ b/AVSCommon/AVS/test/CMakeLists.txt @@ -2,5 +2,6 @@ add_subdirectory("Attachment") set(INCLUDE_PATH "${AVSCommon_INCLUDE_DIRS}" "${AVSCommon_SOURCE_DIR}/AVS/test" - "${AVSCommon_SOURCE_DIR}/SDKInterfaces/test") + "${AVSCommon_SOURCE_DIR}/SDKInterfaces/test" + "${MetricRecorder_INCLUDE_DIRS}") discover_unit_tests("${INCLUDE_PATH}" "AVSCommon;AttachmentCommonTestLib") diff --git a/AVSCommon/AVS/test/DialogUXStateAggregatorTest.cpp b/AVSCommon/AVS/test/DialogUXStateAggregatorTest.cpp index 4c5e4396..e0c563ab 100644 --- a/AVSCommon/AVS/test/DialogUXStateAggregatorTest.cpp +++ b/AVSCommon/AVS/test/DialogUXStateAggregatorTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -23,8 +23,8 @@ namespace alexaClientSDK { namespace avsCommon { namespace test { -using namespace avsCommon::sdkInterfaces; using namespace avsCommon::avs; +using namespace avsCommon::sdkInterfaces; /// Long time out for observers to wait for the state change callback (we should not reach this). static const auto DEFAULT_TIMEOUT = std::chrono::seconds(5); @@ -36,6 +36,9 @@ static const auto SHORT_TIMEOUT = std::chrono::milliseconds(50); // This needs to be longer than the values passed into the DialogUXStateAggregator. static const auto TRANSITION_TIMEOUT = std::chrono::milliseconds(300); +/// Dummy value for a media player source id +static const avsCommon::utils::mediaPlayer::MediaPlayerInterface::SourceId TEST_SOURCE_ID = -1; + /// A test observer that mocks out the DialogUXStateObserverInterface##onDialogUXStateChanged() call. class TestObserver : public DialogUXStateObserverInterface { public: @@ -145,6 +148,9 @@ protected: /// Another test observer std::shared_ptr m_anotherTestObserver; + /// A MediaPlayerState object passed to onStateChange by SpeechSynthesizer + avsCommon::utils::mediaPlayer::MediaPlayerState m_testMediaPlayerState; + virtual void SetUp() { m_aggregator = std::make_shared(); ASSERT_TRUE(m_aggregator); @@ -298,7 +304,8 @@ TEST_F(DialogUXAggregatorTest, test_listeningThenReceiveThenSpeakGoesToSpeakButN m_aggregator->receive("", ""); - m_aggregator->onStateChanged(sdkInterfaces::SpeechSynthesizerObserverInterface::SpeechSynthesizerState::PLAYING); + m_aggregator->onStateChanged( + SpeechSynthesizerObserverInterface::SpeechSynthesizerState::PLAYING, TEST_SOURCE_ID, m_testMediaPlayerState); assertStateChange(m_testObserver, DialogUXStateObserverInterface::DialogUXState::SPEAKING); @@ -316,12 +323,14 @@ TEST_F(DialogUXAggregatorTest, test_speakingAndRecognizingFinishedGoesToIdle) { m_aggregator->receive("", ""); - m_aggregator->onStateChanged(sdkInterfaces::SpeechSynthesizerObserverInterface::SpeechSynthesizerState::PLAYING); + m_aggregator->onStateChanged( + SpeechSynthesizerObserverInterface::SpeechSynthesizerState::PLAYING, TEST_SOURCE_ID, m_testMediaPlayerState); assertStateChange(m_testObserver, DialogUXStateObserverInterface::DialogUXState::SPEAKING); m_aggregator->onStateChanged(AudioInputProcessorObserverInterface::State::IDLE); - m_aggregator->onStateChanged(sdkInterfaces::SpeechSynthesizerObserverInterface::SpeechSynthesizerState::FINISHED); + m_aggregator->onStateChanged( + SpeechSynthesizerObserverInterface::SpeechSynthesizerState::FINISHED, TEST_SOURCE_ID, m_testMediaPlayerState); assertStateChange(m_testObserver, DialogUXStateObserverInterface::DialogUXState::IDLE); } @@ -332,20 +341,24 @@ TEST_F(DialogUXAggregatorTest, test_nonIdleObservantsPreventsIdle) { // AIP is active, SS is not. Expected: non idle m_aggregator->onStateChanged(AudioInputProcessorObserverInterface::State::BUSY); - m_aggregator->onStateChanged(sdkInterfaces::SpeechSynthesizerObserverInterface::SpeechSynthesizerState::FINISHED); + m_aggregator->onStateChanged( + SpeechSynthesizerObserverInterface::SpeechSynthesizerState::FINISHED, TEST_SOURCE_ID, m_testMediaPlayerState); assertStateChange(m_testObserver, DialogUXStateObserverInterface::DialogUXState::LISTENING); // Both AIP and SS are inactive. Expected: idle m_aggregator->onStateChanged(AudioInputProcessorObserverInterface::State::IDLE); - m_aggregator->onStateChanged(sdkInterfaces::SpeechSynthesizerObserverInterface::SpeechSynthesizerState::FINISHED); + m_aggregator->onStateChanged( + SpeechSynthesizerObserverInterface::SpeechSynthesizerState::FINISHED, TEST_SOURCE_ID, m_testMediaPlayerState); assertStateChange(m_testObserver, DialogUXStateObserverInterface::DialogUXState::IDLE); // AIP is inactive, SS is active. Expected: non-idle - m_aggregator->onStateChanged(sdkInterfaces::SpeechSynthesizerObserverInterface::SpeechSynthesizerState::PLAYING); + m_aggregator->onStateChanged( + SpeechSynthesizerObserverInterface::SpeechSynthesizerState::PLAYING, TEST_SOURCE_ID, m_testMediaPlayerState); assertStateChange(m_testObserver, DialogUXStateObserverInterface::DialogUXState::SPEAKING); // AIP is inactive, SS is inactive: Expected: idle - m_aggregator->onStateChanged(sdkInterfaces::SpeechSynthesizerObserverInterface::SpeechSynthesizerState::FINISHED); + m_aggregator->onStateChanged( + SpeechSynthesizerObserverInterface::SpeechSynthesizerState::FINISHED, TEST_SOURCE_ID, m_testMediaPlayerState); assertStateChange(m_testObserver, DialogUXStateObserverInterface::DialogUXState::IDLE); } @@ -360,11 +373,13 @@ TEST_F(DialogUXAggregatorTest, test_speakingFinishedDoesNotGoesToIdleImmediately m_aggregator->receive("", ""); - m_aggregator->onStateChanged(sdkInterfaces::SpeechSynthesizerObserverInterface::SpeechSynthesizerState::PLAYING); + m_aggregator->onStateChanged( + SpeechSynthesizerObserverInterface::SpeechSynthesizerState::PLAYING, TEST_SOURCE_ID, m_testMediaPlayerState); assertStateChange(m_testObserver, DialogUXStateObserverInterface::DialogUXState::SPEAKING); - m_aggregator->onStateChanged(sdkInterfaces::SpeechSynthesizerObserverInterface::SpeechSynthesizerState::FINISHED); + m_aggregator->onStateChanged( + SpeechSynthesizerObserverInterface::SpeechSynthesizerState::FINISHED, TEST_SOURCE_ID, m_testMediaPlayerState); assertNoStateChange(m_testObserver); } @@ -377,7 +392,8 @@ TEST_F(DialogUXAggregatorTest, test_simpleReceiveDoesNothing) { assertNoStateChange(m_testObserver); - m_aggregator->onStateChanged(sdkInterfaces::SpeechSynthesizerObserverInterface::SpeechSynthesizerState::PLAYING); + m_aggregator->onStateChanged( + SpeechSynthesizerObserverInterface::SpeechSynthesizerState::PLAYING, TEST_SOURCE_ID, m_testMediaPlayerState); assertStateChange(m_testObserver, DialogUXStateObserverInterface::DialogUXState::SPEAKING); @@ -399,7 +415,9 @@ TEST_F(DialogUXAggregatorTest, test_thinkingThenReceiveRemainsInThinkingIfSpeech m_aggregator->receive("", ""); m_aggregator->onStateChanged( - sdkInterfaces::SpeechSynthesizerObserverInterface::SpeechSynthesizerState::GAINING_FOCUS); + SpeechSynthesizerObserverInterface::SpeechSynthesizerState::GAINING_FOCUS, + TEST_SOURCE_ID, + m_testMediaPlayerState); // Make sure after SpeechSynthesizer reports GAINING_FOCUS, that it would stay in THINKING state m_aggregator->receive("", ""); @@ -422,14 +440,16 @@ TEST_F(DialogUXAggregatorTest, test_validStatesForRPSToThinking) { m_aggregator->receive("", ""); assertStateChange(m_testObserver, DialogUXStateObserverInterface::DialogUXState::IDLE); - m_aggregator->onStateChanged(sdkInterfaces::SpeechSynthesizerObserverInterface::SpeechSynthesizerState::PLAYING); + m_aggregator->onStateChanged( + SpeechSynthesizerObserverInterface::SpeechSynthesizerState::PLAYING, TEST_SOURCE_ID, m_testMediaPlayerState); assertStateChange(m_testObserver, DialogUXStateObserverInterface::DialogUXState::SPEAKING); m_aggregator->onRequestProcessingStarted(); assertNoStateChange(m_testObserver); // Reset to IDLE m_aggregator->onStateChanged(AudioInputProcessorObserverInterface::State::IDLE); - m_aggregator->onStateChanged(sdkInterfaces::SpeechSynthesizerObserverInterface::SpeechSynthesizerState::FINISHED); + m_aggregator->onStateChanged( + SpeechSynthesizerObserverInterface::SpeechSynthesizerState::FINISHED, TEST_SOURCE_ID, m_testMediaPlayerState); assertStateChange(m_testObserver, DialogUXStateObserverInterface::DialogUXState::IDLE); m_aggregator->onStateChanged(AudioInputProcessorObserverInterface::State::EXPECTING_SPEECH); assertStateChange(m_testObserver, DialogUXStateObserverInterface::DialogUXState::EXPECTING); diff --git a/AVSCommon/CMakeLists.txt b/AVSCommon/CMakeLists.txt index d10b8ccd..b785ca67 100644 --- a/AVSCommon/CMakeLists.txt +++ b/AVSCommon/CMakeLists.txt @@ -23,8 +23,8 @@ add_library(AVSCommon SHARED AVS/src/Attachment/InProcessAttachment.cpp AVS/src/Attachment/InProcessAttachmentReader.cpp AVS/src/Attachment/InProcessAttachmentWriter.cpp - AVS/src/CapabilityConfiguration.cpp AVS/src/CapabilityAgent.cpp + AVS/src/CapabilityConfiguration.cpp AVS/src/CapabilityTag.cpp AVS/src/DialogUXStateAggregator.cpp AVS/src/DirectiveRoutingRule.cpp @@ -72,14 +72,14 @@ add_library(AVSCommon SHARED Utils/src/Logger/ModuleLogger.cpp Utils/src/Logger/ThreadMoniker.cpp Utils/src/MacAddressString.cpp - Utils/src/Metrics/DataPoint.cpp - Utils/src/Metrics/DataPointStringBuilder.cpp - Utils/src/Metrics/DataPointCounterBuilder.cpp - Utils/src/Metrics/DataPointDurationBuilder.cpp - Utils/src/Metrics/MetricEvent.cpp - Utils/src/Metrics/MetricEventBuilder.cpp Utils/src/MediaPlayer/PooledMediaPlayerFactory.cpp Utils/src/Metrics.cpp + Utils/src/Metrics/DataPoint.cpp + Utils/src/Metrics/DataPointCounterBuilder.cpp + Utils/src/Metrics/DataPointDurationBuilder.cpp + Utils/src/Metrics/DataPointStringBuilder.cpp + Utils/src/Metrics/MetricEvent.cpp + Utils/src/Metrics/MetricEventBuilder.cpp Utils/src/MultiTimer.cpp Utils/src/Network/InternetConnectionMonitor.cpp Utils/src/RequiresShutdown.cpp @@ -104,6 +104,13 @@ target_include_directories(AVSCommon PUBLIC "${MultipartParser_SOURCE_DIR}" ${CURL_INCLUDE_DIRS}) +if (CUSTOM_AUDIO_INPUT_STREAM_TRAITS) +target_include_directories(AVSCommon PUBLIC + ${CUSTOMSDSTRAITS_LIB_DIR}/include) + +target_link_libraries(AVSCommon ${CUSTOMSDSTRAITS_LIB_NAME}) +endif () + target_link_libraries(AVSCommon ${CURL_LIBRARIES}) diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/AVSGatewayManagerInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/AVSGatewayManagerInterface.h index db04ea1b..4001404e 100644 --- a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/AVSGatewayManagerInterface.h +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/AVSGatewayManagerInterface.h @@ -1,5 +1,5 @@ /* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2019-2020 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. @@ -20,6 +20,7 @@ #include #include +#include #include namespace alexaClientSDK { @@ -53,6 +54,20 @@ public: * @return True if successful, else false. */ virtual bool setGatewayURL(const std::string& avsGatewayURL) = 0; + + /** + * Adds an observer. + * + * @param observer The @c AVSGatewayObserver + */ + virtual void addObserver(std::shared_ptr observer) = 0; + + /** + * Removes an observer. + * + * @param observer The @c AVSGatewayObserver. + */ + virtual void removeObserver(std::shared_ptr observer) = 0; }; } // namespace sdkInterfaces diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/AVSGatewayObserverInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/AVSGatewayObserverInterface.h new file mode 100644 index 00000000..edf57c71 --- /dev/null +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/AVSGatewayObserverInterface.h @@ -0,0 +1,47 @@ +/* + * Copyright 2020 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 ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_AVSGATEWAYOBSERVERINTERFACE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_AVSGATEWAYOBSERVERINTERFACE_H_ + +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace sdkInterfaces { + +/** + * An interface for setting AVS gateway. + */ +class AVSGatewayObserverInterface { +public: + /** + * Destructor. + */ + virtual ~AVSGatewayObserverInterface() = default; + + /** + * Observer method to be called when the AVS Gateway is changed. + * + * @param avsGateway The AVS Gateway the device should be connected to. + */ + virtual void onAVSGatewayChanged(const std::string& avsGateway) = 0; +}; + +} // namespace sdkInterfaces +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_AVSGATEWAYOBSERVERINTERFACE_H_ diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Audio/EqualizerControllerListenerInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Audio/EqualizerControllerListenerInterface.h index f9d1bab8..36c68b05 100644 --- a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Audio/EqualizerControllerListenerInterface.h +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Audio/EqualizerControllerListenerInterface.h @@ -1,5 +1,5 @@ /* - * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018-2020 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. @@ -39,6 +39,14 @@ public: * @param newState New state of the @c EqualizerController. */ virtual void onEqualizerStateChanged(const EqualizerState& newState) = 0; + + /** + * Receives the same state of the @c EqualizerController when equalizer setting is changed but to an identical state + * to the current state. This callback is called after all changes has been applied. + * + * @param newState New state of the @c EqualizerController. + */ + virtual void onEqualizerSameStateChanged(const EqualizerState& newState) = 0; }; } // namespace audio diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/AudioInputProcessorObserverInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/AudioInputProcessorObserverInterface.h index 2e669f49..40b968d9 100644 --- a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/AudioInputProcessorObserverInterface.h +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/AudioInputProcessorObserverInterface.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -16,6 +16,7 @@ #ifndef ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_AUDIOINPUTPROCESSOROBSERVERINTERFACE_H_ #define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_AUDIOINPUTPROCESSOROBSERVERINTERFACE_H_ +#include #include namespace alexaClientSDK { diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Bluetooth/BluetoothDeviceConnectionRuleInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Bluetooth/BluetoothDeviceConnectionRuleInterface.h new file mode 100644 index 00000000..f8e700da --- /dev/null +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Bluetooth/BluetoothDeviceConnectionRuleInterface.h @@ -0,0 +1,83 @@ +/* + * Copyright 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 ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_BLUETOOTH_BLUETOOTHDEVICECONNECTIONRULEINTERFACE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_BLUETOOTH_BLUETOOTHDEVICECONNECTIONRULEINTERFACE_H_ + +#include +#include + +#include +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace sdkInterfaces { +namespace bluetooth { + +/** + * This interface defines the connection rule the Bluetooth device needs to follow. + */ +class BluetoothDeviceConnectionRuleInterface { +public: + /** + * Destructor. + */ + virtual ~BluetoothDeviceConnectionRuleInterface() = default; + + /** + * The rule to explicitly connect the Bluetooth device after pair. + * + * @return true if the caller needs to handle connect logic. + */ + virtual bool shouldExplicitlyConnect() = 0; + + /** + * The rule to explicitly disconnect the Bluetooth device before unpair. + * + * @return true if the caller needs to handle disconnect logic. + */ + virtual bool shouldExplicitlyDisconnect() = 0; + + /** + * The rule to get a set of Bluetooth devices needed to disconnect when the Bluetooth device connects. + * + * @param connectedDevices the current connected devices. + * @return the set of Bluetooth devices needed to disconnect. + */ + virtual std::set> devicesToDisconnect( + std::map>> connectedDevices) = 0; + + /** + * Get the set of device categories using the connection rule. + * + * @return The set of @c DeviceCategory of the connection rule. + */ + virtual std::set getDeviceCategories() = 0; + + /** + * Get the set of profile uuids which support those device categories defined in the connection rule. + * + * @return The set of profile uuids. + */ + virtual std::set getDependentProfiles() = 0; +}; + +} // namespace bluetooth +} // namespace sdkInterfaces +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_BLUETOOTH_BLUETOOTHDEVICECONNECTIONRULEINTERFACE_H_ diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Bluetooth/BluetoothDeviceInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Bluetooth/BluetoothDeviceInterface.h index 24a428d4..42a6e3b1 100644 --- a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Bluetooth/BluetoothDeviceInterface.h +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Bluetooth/BluetoothDeviceInterface.h @@ -1,5 +1,5 @@ /* - * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018-2020 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. @@ -22,12 +22,10 @@ #include #include -#include -#include -#include -#include #include #include +#include +#include namespace alexaClientSDK { namespace avsCommon { @@ -101,6 +99,41 @@ inline std::ostream& operator<<(std::ostream& stream, const DeviceState state) { /// Represents a Bluetooth Device. class BluetoothDeviceInterface { public: + /** + * Struct to represent a Bluetooth device meta data. + */ + struct MetaData { + /// The value of undefined class of the Bluetooth device. + static const int UNDEFINED_CLASS_VALUE = 0; + + utils::Optional vendorId; + utils::Optional productId; + int classOfDevice; + utils::Optional vendorDeviceSigId; + utils::Optional vendorDeviceId; + + /** + * Constructor + * @param vendorId The vendor id. + * @param productId The product id. + * @param classOfDevice The class of device. + * @param vendorDeviceSigId The vendor device SIG id. + * @param vendorDeviceId The vendor device id. + */ + MetaData( + utils::Optional vendorId, + utils::Optional productId, + int classOfDevice, + utils::Optional vendorDeviceSigId, + utils::Optional vendorDeviceId) : + vendorId(vendorId), + productId(productId), + classOfDevice(classOfDevice), + vendorDeviceSigId(vendorDeviceSigId), + vendorDeviceId(vendorDeviceId) { + } + }; + /// Destructor virtual ~BluetoothDeviceInterface() = default; @@ -125,6 +158,13 @@ public: */ virtual DeviceState getDeviceState() = 0; + /** + * Getter for the Bluetooth device metadata. + * + * @return the meta data of the Bluetooth device. + */ + virtual MetaData getDeviceMetaData() = 0; + /** * Getter for the paired state of the device. This should return * the state after any pending state changes have been resolved. @@ -172,18 +212,27 @@ public: /// @return The Bluetooth Services that this device supports. virtual std::vector> getSupportedServices() = 0; - // TODO : Generic getService method. - /// @return A pointer to an instance of the @c A2DPSourceInterface if supported, else a nullptr. - virtual std::shared_ptr getA2DPSource() = 0; + /** + * Get the Bluetooth service that this device supports. + * + * @param uuid the uuid of the Bluetooth Service. + * @return A pointer to an instance of the @c BluetoothServiceInterface if supported, else a nullptr. + */ + virtual std::shared_ptr getService(std::string uuid) = 0; - /// @return A pointer to an instance of the @c A2DPSinkInterface if supported, else a nullptr. - virtual std::shared_ptr getA2DPSink() = 0; + /// @return The current media streaming state of the BluetoothDevice if the device supports A2DP streaming. + virtual utils::bluetooth::MediaStreamingState getStreamingState() = 0; - /// @return A pointer to an instance of the @c AVRCPTargetInterface if supported, else a nullptr. - virtual std::shared_ptr getAVRCPTarget() = 0; - - /// @return A pointer to an instance of the @c AVRCPControllerInterface if supported, else a nullptr. - virtual std::shared_ptr getAVRCPController() = 0; + /** + * Toggle the profile of a device, which restricts the future connection/disconnection. + * + * @param enabled True if need to connect the certain profile, false to disconnect. + * @param service The target profile to toggle. + * @return A bool indicating success. + */ + virtual bool toggleServiceConnection( + bool enabled, + std::shared_ptr service) = 0; }; } // namespace bluetooth diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Bluetooth/BluetoothHostControllerInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Bluetooth/BluetoothHostControllerInterface.h index 76dd841d..b22e7244 100644 --- a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Bluetooth/BluetoothHostControllerInterface.h +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Bluetooth/BluetoothHostControllerInterface.h @@ -1,5 +1,5 @@ /* - * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018-2020 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. diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Bluetooth/Services/AVRCPTargetInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Bluetooth/Services/AVRCPTargetInterface.h index c190137c..979c6ba7 100644 --- a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Bluetooth/Services/AVRCPTargetInterface.h +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Bluetooth/Services/AVRCPTargetInterface.h @@ -1,5 +1,5 @@ /* - * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * 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. @@ -28,8 +28,8 @@ namespace bluetooth { namespace services { // TODO: Move to own enum file. -/// An Enum representing AVRCP commands. -enum class AVRCPCommand { +/// An Enum representing Media commands. +enum class MediaCommand { /// A Play command. PLAY, @@ -40,38 +40,43 @@ enum class AVRCPCommand { NEXT, /// A Previous command. If issued at the beginning of a song, the previous track will be selected. - PREVIOUS + PREVIOUS, + + /// A Play/Pause command. + PLAY_PAUSE }; /** - * Converts the @c AVRCPCommand enum to a string. + * Converts the @c MediaCommand enum to a string. * - * @param cmd The @c AVRCPCommand to convert. - * @return A string representation of the @c AVRCPCommand. + * @param cmd The @c MediaCommand to convert. + * @return A string representation of the @c MediaCommand. */ -inline std::string commandToString(AVRCPCommand cmd) { +inline std::string commandToString(MediaCommand cmd) { switch (cmd) { - case AVRCPCommand::PLAY: + case MediaCommand::PLAY: return "PLAY"; - case AVRCPCommand::PAUSE: + case MediaCommand::PAUSE: return "PAUSE"; - case AVRCPCommand::NEXT: + case MediaCommand::NEXT: return "NEXT"; - case AVRCPCommand::PREVIOUS: + case MediaCommand::PREVIOUS: return "PREVIOUS"; + case MediaCommand::PLAY_PAUSE: + return "PLAY_PAUSE"; } return "UNKNOWN"; } /** - * Overload for the @c AVRCPCommand enum. This will write the @c AVRCPCommand as a string to the provided stream. + * Overload for the @c MediaCommand enum. This will write the @c MediaCommand as a string to the provided stream. * * @param stream An ostream to send the DeviceState as a string. - * @param cmd The @c AVRCPCommand to convert. + * @param cmd The @c MediaCommand to convert. * @return The stream. */ -inline std::ostream& operator<<(std::ostream& stream, const AVRCPCommand cmd) { +inline std::ostream& operator<<(std::ostream& stream, const MediaCommand cmd) { return stream << commandToString(cmd); } diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Bluetooth/Services/BluetoothServiceInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Bluetooth/Services/BluetoothServiceInterface.h index 69c9af4b..8c9fda58 100644 --- a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Bluetooth/Services/BluetoothServiceInterface.h +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Bluetooth/Services/BluetoothServiceInterface.h @@ -1,5 +1,5 @@ /* - * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * 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. @@ -26,7 +26,11 @@ namespace sdkInterfaces { namespace bluetooth { namespace services { -/// Interface representing a BluetoothService. +/** + * Interface representing a Bluetooth Service. + * More Bluetooth Service information(e.g, UUID, NAME) could be found at + * https://www.bluetooth.com/specifications/assigned-numbers/service-discovery/ + */ class BluetoothServiceInterface { public: /** diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Bluetooth/Services/HFPInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Bluetooth/Services/HFPInterface.h new file mode 100644 index 00000000..c24d309e --- /dev/null +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Bluetooth/Services/HFPInterface.h @@ -0,0 +1,45 @@ +/* + * Copyright 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 ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_BLUETOOTH_SERVICES_HFPINTERFACE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_BLUETOOTH_SERVICES_HFPINTERFACE_H_ + +#include "AVSCommon/SDKInterfaces/Bluetooth/Services/BluetoothServiceInterface.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace sdkInterfaces { +namespace bluetooth { +namespace services { + +/** + * Interface to support hands-free profile. + */ +class HFPInterface : public BluetoothServiceInterface { +public: + /// The Service UUID. + static constexpr const char* UUID = "0000111e-0000-1000-8000-00805f9b34fb"; + + /// The Service Name. + static constexpr const char* NAME = "Handsfree"; +}; + +} // namespace services +} // namespace bluetooth +} // namespace sdkInterfaces +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_BLUETOOTH_SERVICES_HFPINTERFACE_H_ \ No newline at end of file diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Bluetooth/Services/HIDInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Bluetooth/Services/HIDInterface.h new file mode 100644 index 00000000..00e8f54e --- /dev/null +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Bluetooth/Services/HIDInterface.h @@ -0,0 +1,45 @@ +/* + * Copyright 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 ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_BLUETOOTH_SERVICES_HIDINTERFACE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_BLUETOOTH_SERVICES_HIDINTERFACE_H_ + +#include "AVSCommon/SDKInterfaces/Bluetooth/Services/BluetoothServiceInterface.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace sdkInterfaces { +namespace bluetooth { +namespace services { + +/** + * Interface to support human interface device(such as keyboards, mics) profile. + */ +class HIDInterface : public BluetoothServiceInterface { +public: + /// The Service UUID. + static constexpr const char* UUID = "00001124-0000-1000-8000-00805f9b34fb"; + + /// The Service Name. + static constexpr const char* NAME = "HumanInterfaceDeviceService"; +}; + +} // namespace services +} // namespace bluetooth +} // namespace sdkInterfaces +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_BLUETOOTH_SERVICES_HIDINTERFACE_H_ \ No newline at end of file diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Bluetooth/Services/SPPInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Bluetooth/Services/SPPInterface.h new file mode 100644 index 00000000..e40cb118 --- /dev/null +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Bluetooth/Services/SPPInterface.h @@ -0,0 +1,45 @@ +/* + * Copyright 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 ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_BLUETOOTH_SERVICES_SPPINTERFACE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_BLUETOOTH_SERVICES_SPPINTERFACE_H_ + +#include "AVSCommon/SDKInterfaces/Bluetooth/Services/BluetoothServiceInterface.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace sdkInterfaces { +namespace bluetooth { +namespace services { + +/** + * Interface to support serial port profile. + */ +class SPPInterface : public BluetoothServiceInterface { +public: + /// The Service UUID. + static constexpr const char* UUID = "00001101-0000-1000-8000-00805f9b34fb"; + + /// The Service Name. + static constexpr const char* NAME = "SerialPort"; +}; + +} // namespace services +} // namespace bluetooth +} // namespace sdkInterfaces +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_BLUETOOTH_SERVICES_SPPINTERFACE_H_ \ No newline at end of file diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/CallManagerInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/CallManagerInterface.h index 8d45e9e2..08e9d368 100644 --- a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/CallManagerInterface.h +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/CallManagerInterface.h @@ -100,6 +100,23 @@ public: * Stops the call. */ virtual void stopCall() = 0; + + /** + * Mute self during the call. + */ + virtual void muteSelf() = 0; + + /** + * Unmute self during the call. + */ + virtual void unmuteSelf() = 0; + + /** + * Check if the call is muted. + * + * @return Whether the call is muted. + */ + virtual bool isSelfMuted() const = 0; }; inline CallManagerInterface::CallManagerInterface( diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/CallStateObserverInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/CallStateObserverInterface.h index fc8e7cd8..60f74649 100644 --- a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/CallStateObserverInterface.h +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/CallStateObserverInterface.h @@ -1,5 +1,5 @@ /* - * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * 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. @@ -52,6 +52,17 @@ public: * @param state The new CallState. */ virtual void onCallStateChange(CallState state) = 0; + + /** + * Checks the state of the provided call state to determine if a call is in an "active" state + * Active states are: CONNECTING + * INBOUND_RINGING + * CALL_CONNETED + * + * @param state The new CallState. + * @return True on states that are considered "active", false otherwise. + */ + static bool isStateActive(const CallStateObserverInterface::CallState& state); }; /** @@ -82,6 +93,19 @@ inline std::ostream& operator<<(std::ostream& stream, const CallStateObserverInt return stream << "UNKNOWN STATE"; } +inline bool CallStateObserverInterface::isStateActive(const CallStateObserverInterface::CallState& state) { + switch (state) { + case CallStateObserverInterface::CallState::CONNECTING: + case CallStateObserverInterface::CallState::INBOUND_RINGING: + case CallStateObserverInterface::CallState::CALL_CONNECTED: + return true; + case CallStateObserverInterface::CallState::CALL_DISCONNECTED: + case CallStateObserverInterface::CallState::NONE: + return false; + } + return false; +} + } // namespace sdkInterfaces } // namespace avsCommon } // namespace alexaClientSDK diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/CapabilitiesDelegateInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/CapabilitiesDelegateInterface.h index b3100556..7ccb32af 100644 --- a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/CapabilitiesDelegateInterface.h +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/CapabilitiesDelegateInterface.h @@ -1,5 +1,5 @@ /* - * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018-2020 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. @@ -21,9 +21,10 @@ #include #include +#include +#include #include #include -#include #include namespace alexaClientSDK { @@ -34,7 +35,9 @@ namespace sdkInterfaces { * CapabilitiesDelegateInterface is an interface with methods that provide clients a way to register endpoints and their * capabilities and publish them so that Alexa is aware of the device's capabilities. */ -class CapabilitiesDelegateInterface : public avsCommon::sdkInterfaces::AlexaEventProcessedObserverInterface { +class CapabilitiesDelegateInterface + : public avsCommon::sdkInterfaces::AlexaEventProcessedObserverInterface + , public avsCommon::sdkInterfaces::AVSGatewayObserverInterface { public: /** * Destructor diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/ChannelObserverInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/ChannelObserverInterface.h index f0f04b07..09ec23ff 100644 --- a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/ChannelObserverInterface.h +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/ChannelObserverInterface.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -17,6 +17,7 @@ #define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_CHANNELOBSERVERINTERFACE_H_ #include "AVSCommon/AVS/FocusState.h" +#include "AVSCommon/AVS/MixingBehavior.h" namespace alexaClientSDK { namespace avsCommon { @@ -42,8 +43,11 @@ public: * Channel. * * @param newFocus The new Focus of the channel. + * @param behavior The mixingBehavior for the ChannelObserver to take as per the interrupt model + * @note when newFocus is FocusState::FOREGROUND, the MixingBehavior shall be guaranteed to be PRIMARY + * when newFocus is FocusState::NONE, the MixingBehavior shall be guaranteed to be MUST_STOP */ - virtual void onFocusChanged(avs::FocusState newFocus) = 0; + virtual void onFocusChanged(avs::FocusState newFocus, avs::MixingBehavior behavior) = 0; }; } // namespace sdkInterfaces diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/ContextManagerInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/ContextManagerInterface.h index 5bb0063d..af1ef6f3 100644 --- a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/ContextManagerInterface.h +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/ContextManagerInterface.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -56,7 +56,7 @@ enum class SetStateResult { * Interface to get the context and set the state. * State refers to the client component's state. Context is a container used to communicate the state * of the client components to AVS. - * @see https://developer.amazon.com/docs/alexa-voice-service/context.html. + * @see https://developer.amazon.com/docs/alexa/alexa-voice-service/context.html. * * @note Implementations must be thread-safe. */ diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/ExternalMediaAdapterInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/ExternalMediaAdapterInterface.h index d100866c..88a95b8b 100644 --- a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/ExternalMediaAdapterInterface.h +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/ExternalMediaAdapterInterface.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -16,12 +16,12 @@ #ifndef ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_EXTERNALMEDIAADAPTERINTERFACE_H_ #define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_EXTERNALMEDIAADAPTERINTERFACE_H_ -#include "AVSCommon/Utils/RequiresShutdown.h" - #include #include #include +#include "AVSCommon/Utils/RequiresShutdown.h" + namespace alexaClientSDK { namespace avsCommon { namespace sdkInterfaces { @@ -139,7 +139,7 @@ enum class SupportedPlaybackOperation { /// Previous PREVIOUS, - /// Starover a track from the beginning + /// Start over a track from the beginning START_OVER, /// Fast-forward @@ -193,7 +193,7 @@ enum class ChangeCauseType { /// Change was triggered by a rule. RULE_TRIGGER, - /// Change was triggerd by periodic polling. + /// Change was triggered by periodic polling. PERIODIC_POLL }; @@ -391,9 +391,9 @@ public: /** * ExternalMediaAdapterInterface constructor. * - * @param adapaterName The name of the adapter. + * @param adapterName The name of the adapter. */ - ExternalMediaAdapterInterface(const std::string& adapaterName); + explicit ExternalMediaAdapterInterface(const std::string& adapterName); /** * Destructor. diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/ExternalMediaPlayerObserverInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/ExternalMediaPlayerObserverInterface.h index 59ee8f00..276cc1ec 100644 --- a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/ExternalMediaPlayerObserverInterface.h +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/ExternalMediaPlayerObserverInterface.h @@ -1,5 +1,5 @@ /* - * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018-2020 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. @@ -34,8 +34,9 @@ struct ObservableSessionProperties { /** * Constructor - * @param pLoggedIn - * @param pUserName + * + * @param loggedIn Flag that identifies if a users is logged in or not. + * @param userName The user name of the currently logged in user. */ ObservableSessionProperties(bool loggedIn, const std::string& userName); @@ -72,8 +73,9 @@ struct ObservablePlaybackStateProperties { /** * Constructor - * @param pState - * @param pTrackName + * + * @param state The state of the player. State values are "IDLE", "PLAYING", "PAUSED", "STOPPED", "FINISHED". + * @param trackName The display name for the playing track. */ ObservablePlaybackStateProperties(const std::string& state, const std::string& trackName); diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/FocusManagerInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/FocusManagerInterface.h index fc550ebc..91676c83 100644 --- a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/FocusManagerInterface.h +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/FocusManagerInterface.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -18,8 +18,11 @@ #include #include +#include #include +#include +#include #include "ChannelObserverInterface.h" #include "FocusManagerObserverInterface.h" @@ -34,6 +37,8 @@ namespace sdkInterfaces { * * acquire Channel - clients should call the acquireChannel() method, passing in the name of the Channel they wish to * acquire, a pointer to the observer that they want to be notified once they get focus, and a unique interface name. + * clients could alternatively construct an @c Activity object and pass that along with the channel name to acquire the + * channel. * * release Channel - clients should call the releaseChannel() method, passing in the name of the Channel and the * observer of the Channel they wish to release. @@ -48,6 +53,132 @@ namespace sdkInterfaces { */ class FocusManagerInterface { public: + /** + * An activity representation of an entity that includes details of policy and patience duration that can acquire + * a channel. + * + * If activity A has a patience duration greater than 0, and pushes the current activity B to background, + * activity B is eligible to be reinstated as foreground if activity A releases the channel before the duration + * of the patience has lapsed. + */ + class Activity { + public: + /** + * Constructs a new Activity object. + * + * @param interfaceName The Activity's interface. + * @param channelObserver The Activity's Channel Observer. + * @param patienceDuration The Activity's Patience Duration. + * @param contentType The Activity's Content Type. + */ + static std::shared_ptr create( + const std::string& interfaceName, + const std::shared_ptr& channelObserver, + const std::chrono::milliseconds& patienceDuration = std::chrono::milliseconds::zero(), + const avsCommon::avs::ContentType contentType = avsCommon::avs::ContentType::NONMIXABLE); + + bool operator==(const Activity& rhs) { + return this->m_interface == rhs.m_interface; + } + + /** + * Returns the name of Activity's AVS interface. + * + * @return The name of the AVS interface. + */ + const std::string getInterface() const; + + /** + * Returns the patience duration in milliseconds of Activity. After the release duration, the + * backgrounded Activity due to the forgrounding of the Activity with patience will be kicked + * out of the stack and will be set to NONE FocusState. + * + * @return The patience duration in milliseconds of Activity. + */ + std::chrono::milliseconds getPatienceDuration() const; + + /** + * Returns the @c ContentType associated with the @c Activity. + * + * @return The @c ContentType associated with this @c Activity. + */ + avsCommon::avs::ContentType getContentType() const; + + /** + * Sets the @c ContentType associated with this @c Activity. + * + * @param contentType The @c ContentType associated with this @c Activity. + */ + void setContentType(avsCommon::avs::ContentType contentType); + + /** + * Gets the last @c MixingBehavior set for this Activity. + * + * @param The @c MixingBehavior to be set for this @c Activity + */ + avsCommon::avs::MixingBehavior getMixingBehavior() const; + + /** + * Returns the channel observer of Activity. + * + * @return The channel observer of Activity. + */ + std::shared_ptr getChannelObserver() const; + + /** + * Notifies the channel Observer of focus of Channel and Channel owner interface. + * + * @return @c true if observer was notified, else @c false. + */ + bool notifyObserver(avs::FocusState focus, avsCommon::avs::MixingBehavior behavior); + + private: + /** + * Constructs a new Activity object. + * + * @param interfaceName The Activity's interface. + * @param patience The Activity's Patience Duration. + * @param channelObserver The Activity's Channel Observer. + * @param contentType The Activity's Content Type. + */ + Activity( + const std::string& interfaceName, + const std::chrono::milliseconds& patienceDuration, + const std::shared_ptr& channelObserver, + const avsCommon::avs::ContentType contentType) : + m_interface{interfaceName}, + m_patienceDuration{patienceDuration}, + m_channelObserver{channelObserver}, + m_contentType{contentType}, + m_mixingBehavior{avsCommon::avs::MixingBehavior::UNDEFINED} { + } + + /** + * Sets the @c MixingBehavior for this @c Activity + * + * @param behavior The @c MixingBehavior to be set for this @c Activity. + */ + void setMixingBehavior(avsCommon::avs::MixingBehavior behavior); + + // The mutex that synchronizes all operations within the activity + mutable std::mutex m_mutex; + + // The interface name of the Activity. + const std::string m_interface; + + // The duration of patience in milliseconds of the Activity. + const std::chrono::milliseconds m_patienceDuration; + + // The channel observer of the Activity. + const std::shared_ptr m_channelObserver; + + // The ContentType associated with this Activity + avsCommon::avs::ContentType m_contentType; + + // Last MixingBehavior associated with this activity + avsCommon::avs::MixingBehavior m_mixingBehavior; + }; + /// The default Dialog Channel name. static constexpr const char* DIALOG_CHANNEL_NAME = "Dialog"; @@ -84,13 +215,12 @@ public: /** * This method will acquire the channel and grant the appropriate focus to it and other channels if needed. The * caller will be notified via an ChannelObserverInterface##onFocusChanged() call to the @c channelObserver when - * it can start the activity. If the Channel was already held by a different observer, the observer will be - * notified via ChannelObserverInterface##onFocusChanged() to stop before letting the new observer start. + * it can start the activity. * * @param channelName The name of the Channel to acquire. * @param channelObserver The observer that will be acquiring the Channel and be notified of focus changes. - * @param interface The name of the AVS interface occupying the Channel. This should be unique and represents the - * name of the AVS interface using the Channel. The name of the AVS interface is used by the ActivityTracker to + * @param interfaceName The name of the AVS interface occupying the Channel. This should be unique and represents + * the name of the AVS interface using the Channel. The name of the AVS interface is used by the ActivityTracker to * send Context to AVS. * * @return Returns @c true if the Channel can be acquired and @c false otherwise. @@ -98,7 +228,21 @@ public: virtual bool acquireChannel( const std::string& channelName, std::shared_ptr channelObserver, - const std::string& interface) = 0; + const std::string& interfaceName) = 0; + + /** + * This method will acquire the channel and grant the appropriate focus to it and other channels if needed. The + * caller will be notified via an ChannelObserverInterface##onFocusChanged() call to the @c channelObserver when + * it can start the activity. + * + * @param channelName The name of the Channel to acquire. + * @param channelActivity Activity object associated with the Channel. + * + * @return Returns @c true if the Channel can be acquired and @c false otherwise. + */ + virtual bool acquireChannel( + const std::string& channelName, + std::shared_ptr channelActivity) = 0; /** * This method will release the Channel and notify the observer of the Channel, if the observer is the same as the @@ -143,8 +287,88 @@ public: */ virtual void removeObserver( const std::shared_ptr& observer) = 0; + + /** + * This function allows ChannelObservers to modify the ContentType rendering on their associated Activity + * This will cause the focus manager to reconsult the interruptModel in order to determine the new MixingBehavior + * for all backgrounded channels. + * + * @param channelName the channel associated with the ChannelObserver + * @param interfaceName the interface name associated with the ChannelObserver + * @param contentType the new content type + */ + virtual void modifyContentType( + const std::string& channelName, + const std::string& interfaceName, + avsCommon::avs::ContentType contentType) = 0; }; +inline std::shared_ptr FocusManagerInterface::Activity::create( + const std::string& interfaceName, + const std::shared_ptr& channelObserver, + const std::chrono::milliseconds& patienceDuration, + const avsCommon::avs::ContentType contentType) { + if (interfaceName.empty() || patienceDuration.count() < 0 || channelObserver == nullptr) { + return nullptr; + } + + auto activity = std::shared_ptr( + new FocusManagerInterface::Activity(interfaceName, patienceDuration, channelObserver, contentType)); + return activity; +} + +inline const std::string FocusManagerInterface::Activity::getInterface() const { + return m_interface; +} + +inline std::chrono::milliseconds FocusManagerInterface::Activity::getPatienceDuration() const { + return m_patienceDuration; +} + +inline avsCommon::avs::ContentType FocusManagerInterface::Activity::getContentType() const { + std::unique_lock lock(m_mutex); + return m_contentType; +} + +inline void FocusManagerInterface::Activity::setContentType(avsCommon::avs::ContentType contentType) { + std::unique_lock lock(m_mutex); + m_contentType = contentType; +} + +inline avsCommon::avs::MixingBehavior FocusManagerInterface::Activity::getMixingBehavior() const { + std::lock_guard lock(m_mutex); + return m_mixingBehavior; +} + +inline std::shared_ptr FocusManagerInterface::Activity:: + getChannelObserver() const { + return m_channelObserver; +} + +inline bool FocusManagerInterface::Activity::notifyObserver( + avs::FocusState focus, + avsCommon::avs::MixingBehavior behavior) { + if (m_channelObserver) { + avsCommon::avs::MixingBehavior overrideBehavior{behavior}; + // If the activity/channelObserver is already paused , do not duck + if ((avsCommon::avs::MixingBehavior::MUST_PAUSE == getMixingBehavior()) && + (avsCommon::avs::MixingBehavior::MAY_DUCK == behavior)) { + overrideBehavior = avsCommon::avs::MixingBehavior::MUST_PAUSE; + } + + m_channelObserver->onFocusChanged(focus, overrideBehavior); + // Set the current mixing behavior that the observer received. + setMixingBehavior(overrideBehavior); + return true; + } + return false; +} + +inline void FocusManagerInterface::Activity::setMixingBehavior(avsCommon::avs::MixingBehavior behavior) { + std::unique_lock lock(m_mutex); + m_mixingBehavior = behavior; +} + } // namespace sdkInterfaces } // namespace avsCommon } // namespace alexaClientSDK diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/ModeController/ModeControllerAttributeBuilderInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/ModeController/ModeControllerAttributeBuilderInterface.h index 74038506..9b4f4e0b 100644 --- a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/ModeController/ModeControllerAttributeBuilderInterface.h +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/ModeController/ModeControllerAttributeBuilderInterface.h @@ -1,5 +1,5 @@ /* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2019-2020 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. @@ -74,7 +74,7 @@ public: * * @note The order here means how the modes are organized in the mode controller. * By setting this to @c true, you enable the Alexa to send the @c adjustMode directive. - * @see https://developer.amazon.com/docs/alexa-voice-service/alexa-modecontroller.html#capability-assertion + * @see https://developer.amazon.com/docs/alexa/alexa-voice-service/alexa-modecontroller.html#capability-assertion * * @note Calling this again will overrite the previous provided value. * diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/ModeController/ModeControllerAttributes.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/ModeController/ModeControllerAttributes.h index ea3ab428..229eca87 100644 --- a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/ModeController/ModeControllerAttributes.h +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/ModeController/ModeControllerAttributes.h @@ -1,5 +1,5 @@ /* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2019-2020 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. @@ -32,7 +32,7 @@ using ModeResources = avsCommon::avs::CapabilityResources; * Struct to hold the Mode Controller attributes required for * Capability Agent discovery. * - * @see https://developer.amazon.com/docs/alexa-voice-service/alexa-modecontroller.html#capability-assertion + * @see https://developer.amazon.com/docs/alexa/alexa-voice-service/alexa-modecontroller.html#capability-assertion */ struct ModeControllerAttributes { /** @@ -64,7 +64,11 @@ struct ModeControllerAttributes { const bool ordered; }; -inline ModeControllerAttributes::ModeControllerAttributes() : ordered{false} { +inline ModeControllerAttributes::ModeControllerAttributes() : + ModeControllerAttributes( + avsCommon::avs::CapabilityResources(), + std::unordered_map(), + false) { } inline ModeControllerAttributes::ModeControllerAttributes( diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/PowerResourceManagerInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/PowerResourceManagerInterface.h index 56a1f12d..c1885eb9 100644 --- a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/PowerResourceManagerInterface.h +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/PowerResourceManagerInterface.h @@ -1,5 +1,5 @@ /* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2019-2020 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. diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/RangeController/RangeControllerAttributes.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/RangeController/RangeControllerAttributes.h index 0b37e76e..d5abf5e0 100644 --- a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/RangeController/RangeControllerAttributes.h +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/RangeController/RangeControllerAttributes.h @@ -1,5 +1,5 @@ /* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2019-2020 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. @@ -32,7 +32,7 @@ using PresetResources = avsCommon::avs::CapabilityResources; * Struct to hold the Range Controller attributes required for * Capability Agent discovery. * - * @see https://developer.amazon.com/docs/alexa-voice-service/alexa-rangecontroller.html#capability-assertion + * @see https://developer.amazon.com/docs/alexa/alexa-voice-service/alexa-rangecontroller.html#capability-assertion */ struct RangeControllerAttributes { /** diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/SpeakerInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/SpeakerInterface.h index 2ec689ea..2d2c0889 100644 --- a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/SpeakerInterface.h +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/SpeakerInterface.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/SpeakerManagerInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/SpeakerManagerInterface.h index 29d7b2a9..92ba2aae 100644 --- a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/SpeakerManagerInterface.h +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/SpeakerManagerInterface.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -82,6 +82,20 @@ public: bool forceNoNotifications = false, SpeakerManagerObserverInterface::Source source = SpeakerManagerObserverInterface::Source::LOCAL_API) = 0; +#ifdef ENABLE_MAXVOLUME_SETTING + /** + * Sets maximum volume limit. This function should be called to handle @c setMaximumVolumeLimit + * directive from AVS. + * + * @param maximumVolumeLimit The maximum volume level speakers in this system can reach. + * @note Upon success, previous volume exceeding the new limit will be decreased to be complied with the new limit. + * @return A future to be set with the operation's result. The future must be checked for validity + * before attempting to obtain the shared state. An invalid future indicates an internal error, + * and the caller should not assume the operation was successful. + */ + virtual std::future setMaximumVolumeLimit(const int8_t maximumVolumeLimit) = 0; +#endif + /** * Gets the speaker settings. * diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/SpeechSynthesizerObserverInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/SpeechSynthesizerObserverInterface.h index c5ea5b1d..ec8fa82e 100644 --- a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/SpeechSynthesizerObserverInterface.h +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/SpeechSynthesizerObserverInterface.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -18,6 +18,8 @@ #include +#include + namespace alexaClientSDK { namespace avsCommon { namespace sdkInterfaces { @@ -55,8 +57,14 @@ public: /** * Notification that the @c SpeechSynthesizer state has changed. Callback functions must return as soon as possible. * @param state The new state of the @c speechSynthesizer. + * @param mediaSourceId The current media source id for SpeechSynthesizer + * @param mediaPlayerState Optional state of the media player as of this state change. The Optional is blank + * if the state is unavailable. */ - virtual void onStateChanged(SpeechSynthesizerState state) = 0; + virtual void onStateChanged( + SpeechSynthesizerState state, + const avsCommon::utils::mediaPlayer::MediaPlayerInterface::SourceId mediaSourceId, + const avsCommon::utils::Optional& mediaPlayerState) = 0; }; /** diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/ToggleController/ToggleControllerAttributes.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/ToggleController/ToggleControllerAttributes.h index ef24b081..db6ef141 100644 --- a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/ToggleController/ToggleControllerAttributes.h +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/ToggleController/ToggleControllerAttributes.h @@ -1,5 +1,5 @@ /* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2019-2020 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. @@ -27,7 +27,7 @@ namespace toggleController { * The toggle controller attribute containing the capability resources required for * Capability Agent discovery. * - * @see https://developer.amazon.com/docs/alexa-voice-service/alexa-togglecontroller.html#capability-assertion + * @see https://developer.amazon.com/docs/alexa/alexa-voice-service/alexa-togglecontroller.html#capability-assertion */ using ToggleControllerAttributes = avsCommon::avs::CapabilityResources; diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/UserInactivityMonitorInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/UserInactivityMonitorInterface.h index e9b05789..04433c46 100644 --- a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/UserInactivityMonitorInterface.h +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/UserInactivityMonitorInterface.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -30,7 +30,7 @@ namespace sdkInterfaces { * (e.g. SpeechStarted). * * This interface should also send the System.UserInactivityReport Event as defined here: - * https://developer.amazon.com/docs/alexa-voice-service/system.html#userinactivityreport + * https://developer.amazon.com/docs/alexa/alexa-voice-service/system.html#userinactivityreport * and notify its observers when this occurs. */ class UserInactivityMonitorInterface { diff --git a/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/Audio/MockEqualizerControllerListenerInterface.h b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/Audio/MockEqualizerControllerListenerInterface.h index 01b18959..bcfa357a 100644 --- a/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/Audio/MockEqualizerControllerListenerInterface.h +++ b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/Audio/MockEqualizerControllerListenerInterface.h @@ -1,5 +1,5 @@ /* - * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018-2020 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. @@ -32,6 +32,7 @@ namespace test { class MockEqualizerControllerListenerInterface : public EqualizerControllerListenerInterface { public: MOCK_METHOD1(onEqualizerStateChanged, void(const EqualizerState&)); + MOCK_METHOD1(onEqualizerSameStateChanged, void(const EqualizerState&)); }; } // namespace test diff --git a/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/Bluetooth/MockBluetoothDevice.h b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/Bluetooth/MockBluetoothDevice.h new file mode 100644 index 00000000..2776ff46 --- /dev/null +++ b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/Bluetooth/MockBluetoothDevice.h @@ -0,0 +1,182 @@ +/* + * Copyright 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 ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_BLUETOOTH_MOCKBLUETOOTHDEVICE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_BLUETOOTH_MOCKBLUETOOTHDEVICE_H_ + +#include +#include + +#include + +#include +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace sdkInterfaces { +namespace bluetooth { +namespace test { + +using namespace ::testing; +/** + * Mock class that implements BluetoothDeviceInterface. + * Please note that MockBluetoothDevice doesn't support sending any @c BluetoothEvent to @c BluetoothEventBus, + * any @c BluetoothEvent based logic will need to be tested separately. + */ +class MockBluetoothDevice : public BluetoothDeviceInterface { +public: + std::string getMac() const override; + std::string getFriendlyName() const override; + DeviceState getDeviceState() override; + MockBluetoothDevice::MetaData getDeviceMetaData() override; + bool isPaired() override; + std::future pair() override; + std::future unpair() override; + bool isConnected() override; + std::future connect() override; + std::future disconnect() override; + std::vector> getSupportedServices() override; + std::shared_ptr getService(std::string uuid) override; + utils::bluetooth::MediaStreamingState getStreamingState() override; + bool toggleServiceConnection(bool enabled, std::shared_ptr service) override; + + /// Constructor. + MockBluetoothDevice( + const std::string& mac, + const std::string friendlyName, + MetaData metaData, + std::vector> supportedServices); + +protected: + /// Represent the pair status. + bool m_isPaired; + /// Represent the connection status. + bool m_isConnected; + /// Represent the Bluetooth device mac address. + const std::string m_mac; + /// Represent the Bluetooth device friendly name. + const std::string m_friendlyName; + /// Represent the Bluetooth device meta data. + MockBluetoothDevice::MetaData m_metaData; + /// Represent the Bluetooth device supported Services. + std::unordered_map> m_supportedServices; + /// Represent the Bluetooth device state. + DeviceState m_deviceState; +}; + +inline std::string MockBluetoothDevice::getMac() const { + return m_mac; +} + +inline std::string MockBluetoothDevice::getFriendlyName() const { + return m_friendlyName; +} + +inline DeviceState MockBluetoothDevice::getDeviceState() { + return m_deviceState; +} + +inline MockBluetoothDevice::MetaData MockBluetoothDevice::getDeviceMetaData() { + return m_metaData; +} + +inline bool MockBluetoothDevice::isPaired() { + return m_isPaired; +} + +inline std::future MockBluetoothDevice::pair() { + std::promise pairPromise; + pairPromise.set_value(true); + m_isPaired = true; + m_deviceState = DeviceState::PAIRED; + return pairPromise.get_future(); +} + +inline std::future MockBluetoothDevice::unpair() { + std::promise pairPromise; + pairPromise.set_value(true); + m_isPaired = false; + m_deviceState = DeviceState::UNPAIRED; + return pairPromise.get_future(); +} + +inline bool MockBluetoothDevice::isConnected() { + return m_isConnected; +} + +inline std::future MockBluetoothDevice::connect() { + std::promise connectionPromise; + connectionPromise.set_value(true); + m_isConnected = true; + m_deviceState = DeviceState::CONNECTED; + return connectionPromise.get_future(); +} + +inline std::future MockBluetoothDevice::disconnect() { + std::promise connectionPromise; + connectionPromise.set_value(true); + m_isConnected = false; + m_deviceState = DeviceState::DISCONNECTED; + return connectionPromise.get_future(); +} + +inline std::vector> MockBluetoothDevice::getSupportedServices() { + std::vector> services; + for (auto service : m_supportedServices) { + services.push_back(service.second->getRecord()); + } + return services; +} + +inline std::shared_ptr MockBluetoothDevice::getService(std::string uuid) { + auto serviceIt = m_supportedServices.find(uuid); + if (serviceIt != m_supportedServices.end()) { + return serviceIt->second; + } + return nullptr; +} + +inline utils::bluetooth::MediaStreamingState MockBluetoothDevice::getStreamingState() { + return alexaClientSDK::avsCommon::utils::bluetooth::MediaStreamingState::IDLE; +} + +inline bool MockBluetoothDevice::toggleServiceConnection( + bool enabled, + std::shared_ptr service) { + // No-op. + return false; +} + +MockBluetoothDevice::MockBluetoothDevice( + const std::string& mac, + const std::string friendlyName, + alexaClientSDK::avsCommon::sdkInterfaces::bluetooth::BluetoothDeviceInterface::MetaData metaData, + std::vector> supportedServices) : + m_mac{mac}, + m_friendlyName{friendlyName}, + m_metaData{metaData} { + for (unsigned int i = 0; i < supportedServices.size(); ++i) { + auto service = supportedServices[i]; + m_supportedServices.insert({service->getRecord()->getUuid(), service}); + } +} + +} // namespace test +} // namespace bluetooth +} // namespace sdkInterfaces +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_BLUETOOTH_MOCKBLUETOOTHDEVICE_H_ \ No newline at end of file diff --git a/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/Bluetooth/MockBluetoothDeviceConnectionRule.h b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/Bluetooth/MockBluetoothDeviceConnectionRule.h new file mode 100644 index 00000000..3810f04b --- /dev/null +++ b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/Bluetooth/MockBluetoothDeviceConnectionRule.h @@ -0,0 +1,123 @@ +/* + * Copyright 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 ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_BLUETOOTH_MOCKBLUETOOTHDEVICECONNECTIONRULE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_BLUETOOTH_MOCKBLUETOOTHDEVICECONNECTIONRULE_H_ + +#include + +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace sdkInterfaces { +namespace bluetooth { +namespace test { + +/** + * Mock class that implements BluetoothDeviceConnectionRuleInterface. + */ +class MockBluetoothDeviceConnectionRule : public BluetoothDeviceConnectionRuleInterface { +public: + MockBluetoothDeviceConnectionRule(std::set categories, std::set profiles); + bool shouldExplicitlyConnect() override; + bool shouldExplicitlyDisconnect() override; + std::set> devicesToDisconnect( + std::map>> connectedDevices) override; + std::set getDeviceCategories() override; + std::set getDependentProfiles() override; + + /** + * Helper function to test shouldExplicitlyConnect() method. + * + * @param explicitlyConnect A bool indicating if a device needs to explicitly connect. + */ + void setExplicitlyConnect(bool explicitlyConnect); + + /** + * Helper function to test shouldExplicitlyDisconnect() method. + * + * @param explicitlyDisconnect A bool indicating if a device needs to explicitly connect. + */ + void setExplicitlyDisconnect(bool explicitlyDisconnect); + + /** + * Helper function to test devicesToDisconnect(Map>) method. + * + * @param devices a set of devices needed to disconnect. + */ + void setDevicesToDisconnect(std::set> devices); + +protected: + std::set m_categories; + + std::set m_profiles; + + bool m_explicitlyConnect; + + bool m_explicitlyDisconnect; + + std::set> m_disconnectedDevices; +}; + +MockBluetoothDeviceConnectionRule::MockBluetoothDeviceConnectionRule( + std::set categories, + std::set profiles) : + m_categories{categories}, + m_profiles{profiles}, + m_explicitlyConnect{false}, + m_explicitlyDisconnect{false} { +} + +std::set MockBluetoothDeviceConnectionRule::getDeviceCategories() { + return m_categories; +} + +std::set MockBluetoothDeviceConnectionRule::getDependentProfiles() { + return m_profiles; +} + +bool MockBluetoothDeviceConnectionRule::shouldExplicitlyConnect() { + return m_explicitlyConnect; +} + +bool MockBluetoothDeviceConnectionRule::shouldExplicitlyDisconnect() { + return m_explicitlyDisconnect; +} + +std::set> MockBluetoothDeviceConnectionRule::devicesToDisconnect( + std::map>> connectedDevices) { + return m_disconnectedDevices; +} + +void MockBluetoothDeviceConnectionRule::setExplicitlyConnect(bool explicitlyConnect) { + m_explicitlyConnect = explicitlyConnect; +} + +void MockBluetoothDeviceConnectionRule::setExplicitlyDisconnect(bool explicitlyDisconnect) { + m_explicitlyDisconnect = explicitlyDisconnect; +} + +void MockBluetoothDeviceConnectionRule::setDevicesToDisconnect( + std::set> devices) { + m_disconnectedDevices = devices; +} + +} // namespace test +} // namespace bluetooth +} // namespace sdkInterfaces +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_BLUETOOTH_MOCKBLUETOOTHDEVICECONNECTIONRULE_H_ \ No newline at end of file diff --git a/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/Bluetooth/MockBluetoothDeviceManager.h b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/Bluetooth/MockBluetoothDeviceManager.h new file mode 100644 index 00000000..7a67a605 --- /dev/null +++ b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/Bluetooth/MockBluetoothDeviceManager.h @@ -0,0 +1,88 @@ +/* + * Copyright 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 ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_BLUETOOTH_MOCKBLUETOOTHDEVICEMANAGER_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_BLUETOOTH_MOCKBLUETOOTHDEVICEMANAGER_H_ + +#include + +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace sdkInterfaces { +namespace bluetooth { +namespace test { + +using namespace ::testing; + +/** + * Mock class that implements the BluetoothDeviceManagerInterface. + */ +class MockBluetoothDeviceManager : public BluetoothDeviceManagerInterface { +public: + std::shared_ptr getHostController() override; + std::list> getDiscoveredDevices() + override; + std::shared_ptr getEventBus() override; + + /** + * Constructor + * @param hostcontroller @c BluetoothHostController + * @param discoveredDevices a list of discovered devices. + * @param eventBus @c BluetoothEventBus. + */ + MockBluetoothDeviceManager( + std::shared_ptr hostcontroller, + std::list> discoveredDevices, + std::shared_ptr eventBus); + +protected: + std::shared_ptr m_hostController; + std::list> m_discoveredDevices; + std::shared_ptr m_eventBus; +}; + +inline std::shared_ptr +MockBluetoothDeviceManager::getHostController() { + return m_hostController; +} + +inline std::list> +MockBluetoothDeviceManager::getDiscoveredDevices() { + return m_discoveredDevices; +} + +inline std::shared_ptr MockBluetoothDeviceManager::getEventBus() { + return m_eventBus; +} + +inline MockBluetoothDeviceManager::MockBluetoothDeviceManager( + std::shared_ptr + hostcontroller, + std::list> + discoveredDevices, + std::shared_ptr eventBus) : + m_hostController{hostcontroller}, + m_discoveredDevices(discoveredDevices), + m_eventBus(eventBus) { +} + +} // namespace test +} // namespace bluetooth +} // namespace sdkInterfaces +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_BLUETOOTH_MOCKBLUETOOTHDEVICEMANAGER_H_ diff --git a/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/Bluetooth/MockBluetoothDeviceObserver.h b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/Bluetooth/MockBluetoothDeviceObserver.h new file mode 100644 index 00000000..96b92b35 --- /dev/null +++ b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/Bluetooth/MockBluetoothDeviceObserver.h @@ -0,0 +1,43 @@ +/* + * Copyright 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 ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_BLUETOOTH_MOCKBLUETOOTHDEVICEOBSERVER_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_BLUETOOTH_MOCKBLUETOOTHDEVICEOBSERVER_H_ + +#include + +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace sdkInterfaces { +namespace bluetooth { +namespace test { + +/** + * Mock class that implements BluetoothDeviceInterface + */ +class MockBluetoothDeviceObserver : public BluetoothDeviceObserverInterface { +public: + MOCK_METHOD1(onActiveDeviceConnected, void(const DeviceAttributes& attributes)); + MOCK_METHOD1(onActiveDeviceDisconnected, void(const DeviceAttributes& attributes)); +}; + +} // namespace test +} // namespace bluetooth +} // namespace sdkInterfaces +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_BLUETOOTH_MOCKBLUETOOTHDEVICEOBSERVER_H_ \ No newline at end of file diff --git a/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/Bluetooth/MockBluetoothHostController.h b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/Bluetooth/MockBluetoothHostController.h new file mode 100644 index 00000000..aadbdc6f --- /dev/null +++ b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/Bluetooth/MockBluetoothHostController.h @@ -0,0 +1,100 @@ +/* + * Copyright 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 ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_BLUETOOTH_MOCKBLUETOOTHHOSTCONTROLLER_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_BLUETOOTH_MOCKBLUETOOTHHOSTCONTROLLER_H_ + +#include + +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace sdkInterfaces { +namespace bluetooth { +namespace test { + +static const std::string MOCK_MAC_ADDRESS = "XX:XX:XX:XX"; +static const std::string MOCK_FRIENDLY_NAME = "MockBluetoothHostControllerName"; + +/** + * Mock class that implements BluetoothHostControllerInterface + */ +class MockBluetoothHostController : public BluetoothHostControllerInterface { +public: + std::string getMac() const override; + std::string getFriendlyName() const override; + bool isDiscoverable() const override; + bool isScanning() const override; + std::future startScan() override; + std::future stopScan() override; + std::future enterDiscoverableMode() override; + std::future exitDiscoverableMode() override; + +protected: + bool m_isDiscoverable; + bool m_isScanning; +}; + +inline std::future MockBluetoothHostController::startScan() { + std::promise scanPromise; + scanPromise.set_value(true); + m_isScanning = true; + return scanPromise.get_future(); +} + +inline std::future MockBluetoothHostController::stopScan() { + std::promise scanPromise; + scanPromise.set_value(true); + m_isScanning = false; + return scanPromise.get_future(); +} + +std::future MockBluetoothHostController::enterDiscoverableMode() { + std::promise discoverPromise; + discoverPromise.set_value(true); + m_isDiscoverable = true; + return discoverPromise.get_future(); +} + +std::future MockBluetoothHostController::exitDiscoverableMode() { + std::promise discoverPromise; + discoverPromise.set_value(true); + m_isDiscoverable = false; + return discoverPromise.get_future(); +} + +bool MockBluetoothHostController::isScanning() const { + return m_isScanning; +} + +bool MockBluetoothHostController::isDiscoverable() const { + return m_isDiscoverable; +} + +std::string MockBluetoothHostController::getMac() const { + return MOCK_MAC_ADDRESS; +} + +std::string MockBluetoothHostController::getFriendlyName() const { + return MOCK_FRIENDLY_NAME; +} + +} // namespace test +} // namespace bluetooth +} // namespace sdkInterfaces +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_BLUETOOTH_MOCKBLUETOOTHHOSTCONTROLLER_H_ \ No newline at end of file diff --git a/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/Bluetooth/Services/MockBluetoothService.h b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/Bluetooth/Services/MockBluetoothService.h new file mode 100644 index 00000000..57a1d979 --- /dev/null +++ b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/Bluetooth/Services/MockBluetoothService.h @@ -0,0 +1,67 @@ +/* + * Copyright 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 ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_BLUETOOTH_SERVICES_MOCKBLUETOOTHSERVICE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_BLUETOOTH_SERVICES_MOCKBLUETOOTHSERVICE_H_ + +#include + +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace sdkInterfaces { +namespace bluetooth { +namespace services { +namespace test { + +/** + * Mock class that implements BluetoothServiceInterface + */ +class MockBluetoothService : public BluetoothServiceInterface { +public: + void setup() override; + void cleanup() override; + std::shared_ptr getRecord() override; + + /// Constructor. + MockBluetoothService(std::shared_ptr record); + +protected: + std::shared_ptr m_record; +}; + +inline std::shared_ptr MockBluetoothService::getRecord() { + return m_record; +} + +MockBluetoothService::MockBluetoothService(std::shared_ptr record) : m_record{record} { +} + +void MockBluetoothService::setup() { + // no-op +} + +void MockBluetoothService::cleanup() { + // no op +} + +} // namespace test +} // namespace services +} // namespace bluetooth +} // namespace sdkInterfaces +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_BLUETOOTH_SERVICES_MOCKBLUETOOTHSERVICE_H_ \ No newline at end of file diff --git a/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockAVSGatewayManager.h b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockAVSGatewayManager.h index 19065e88..2e74db15 100644 --- a/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockAVSGatewayManager.h +++ b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockAVSGatewayManager.h @@ -1,5 +1,5 @@ /* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2019-2020 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. @@ -36,6 +36,8 @@ public: MOCK_METHOD1( setPostConnectMesasgeSender, bool(std::shared_ptr postConnectMessageSender)); + MOCK_METHOD1(addObserver, void(std::shared_ptr observer)); + MOCK_METHOD1(removeObserver, void(std::shared_ptr observer)); }; } // namespace test diff --git a/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockAVSGatewayObserver.h b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockAVSGatewayObserver.h new file mode 100644 index 00000000..62b5d046 --- /dev/null +++ b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockAVSGatewayObserver.h @@ -0,0 +1,40 @@ +/* + * Copyright 2020 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 ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_MOCKAVSGATEWAYOBSERVER_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_MOCKAVSGATEWAYOBSERVER_H_ + +#include "AVSCommon/SDKInterfaces/AVSGatewayObserverInterface.h" +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace sdkInterfaces { +namespace test { + +/* + * Mock class that implements @c AVSGatewayObserverInterface. + */ +class MockAVSGatewayObserver : public AVSGatewayObserverInterface { +public: + MOCK_METHOD1(onAVSGatewayChanged, void(const std::string& avsGateway)); +}; + +} // namespace test +} // namespace sdkInterfaces +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_MOCKAVSGATEWAYOBSERVER_H_ diff --git a/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockCapabilitiesDelegate.h b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockCapabilitiesDelegate.h index 92b54e8d..ea577e3b 100644 --- a/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockCapabilitiesDelegate.h +++ b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockCapabilitiesDelegate.h @@ -1,5 +1,5 @@ /* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2019-2020 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. @@ -16,9 +16,10 @@ #ifndef ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_MOCKCAPABILITIESDELEGATE_H_ #define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_MOCKCAPABILITIESDELEGATE_H_ -#include "AVSCommon/SDKInterfaces/CapabilitiesDelegateInterface.h" #include +#include + namespace alexaClientSDK { namespace avsCommon { namespace sdkInterfaces { @@ -40,6 +41,7 @@ public: void(std::shared_ptr observer)); MOCK_METHOD0(invalidateCapabilities, void()); MOCK_METHOD1(onAlexaEventProcessedReceived, void(const std::string& eventCorrelationToken)); + MOCK_METHOD1(onAVSGatewayChanged, void(const std::string& avsGateway)); }; } // namespace test diff --git a/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockFocusManager.h b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockFocusManager.h index c2404fe9..12dadce6 100644 --- a/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockFocusManager.h +++ b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockFocusManager.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -32,7 +32,12 @@ public: bool( const std::string& channelName, std::shared_ptr channelObserver, - const std::string& interface)); + const std::string& interfaceName)); + MOCK_METHOD2( + acquireChannel, + bool( + const std::string& channelName, + std::shared_ptr channelActivity)); MOCK_METHOD2( releaseChannel, std::future( @@ -46,6 +51,7 @@ public: removeObserver, void(const std::shared_ptr& observer)); MOCK_METHOD0(stopAllActivities, void()); + MOCK_METHOD3(modifyContentType, void(const std::string&, const std::string&, avsCommon::avs::ContentType)); }; } // namespace test diff --git a/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockSpeakerManager.h b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockSpeakerManager.h index 72151ec2..769e8ed2 100644 --- a/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockSpeakerManager.h +++ b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockSpeakerManager.h @@ -1,5 +1,5 @@ /* - * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018-2020 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. @@ -51,6 +51,10 @@ public: bool forceNoNotifications, avsCommon::sdkInterfaces::SpeakerManagerObserverInterface::Source source)); +#ifdef ENABLE_MAXVOLUME_SETTING + MOCK_METHOD1(setMaximumVolumeLimit, std::future(const int8_t maximumVolumeLimit)); +#endif + MOCK_METHOD2( getSpeakerSettings, std::future( diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/Bluetooth/A2DPRole.h b/AVSCommon/Utils/include/AVSCommon/Utils/Bluetooth/A2DPRole.h index 2833b4c4..5938c9c5 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/Bluetooth/A2DPRole.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/Bluetooth/A2DPRole.h @@ -1,5 +1,5 @@ /* - * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * 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. @@ -23,12 +23,44 @@ namespace bluetooth { /// An Enum representing the current A2DP role. enum class A2DPRole { + /// Does not support A2DP. + NONE, /// AVS device acting as an A2DPSink. SINK, /// AVS device acting as an A2DPSource. SOURCE }; +/** + * Converts the @c A2DPRole enum to a string. + * + * @param The @c A2DPRole to convert. + * @return A string representation of the @c A2DPRole. + */ +inline std::string a2DPRoleToString(A2DPRole value) { + switch (value) { + case A2DPRole::NONE: + return "NONE"; + case A2DPRole::SINK: + return "SINK"; + case A2DPRole::SOURCE: + return "SOURCE"; + default: + return "UNKNOWN"; + } +} + +/** + * Overload for the @c A2DPRole enum. This will write the @c A2DPRole as a string to the provided stream. + * + * @param An ostream to send the @c A2DPRole as a string. + * @param The @c A2DPRole to convert. + * @return The stream. + */ +inline std::ostream& operator<<(std::ostream& stream, const A2DPRole a2DPRole) { + return stream << a2DPRoleToString(a2DPRole); +} + } // namespace bluetooth } // namespace utils } // namespace avsCommon diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/Bluetooth/BluetoothEvents.h b/AVSCommon/Utils/include/AVSCommon/Utils/Bluetooth/BluetoothEvents.h index 7fe46421..e266af88 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/Bluetooth/BluetoothEvents.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/Bluetooth/BluetoothEvents.h @@ -20,6 +20,7 @@ #include "AVSCommon/SDKInterfaces/Bluetooth/Services/A2DPSourceInterface.h" #include "AVSCommon/SDKInterfaces/Bluetooth/Services/AVRCPTargetInterface.h" #include "AVSCommon/Utils/Bluetooth/A2DPRole.h" +#include "AVSCommon/Utils/Bluetooth/MediaStreamingState.h" namespace alexaClientSDK { namespace avsCommon { @@ -40,11 +41,17 @@ enum class BluetoothEventType { /// Represents when the A2DP streaming state changes. STREAMING_STATE_CHANGED, - /// Represents when an AVRCP command has been received. - AVRCP_COMMAND_RECEIVED, + /// Represents when a media command has been received. + MEDIA_COMMAND_RECEIVED, /// When the BluetoothDeviceManager has initialized. - BLUETOOTH_DEVICE_MANAGER_INITIALIZED + BLUETOOTH_DEVICE_MANAGER_INITIALIZED, + + /// When the scanning state of the host has changed. + SCANNING_STATE_CHANGED, + + /// Request to connect/disconnect a certain profile + TOGGLE_A2DP_PROFILE_STATE_CHANGED }; /// Helper struct to allow enum class to be a key in collections @@ -55,16 +62,6 @@ struct BluetoothEventTypeHash { } }; -/// An Enum representing the current state of the stream. -enum class MediaStreamingState { - /// Currently not streaming. - IDLE, - /// Currently acquiring the stream. - PENDING, - /// Currently streaming. - ACTIVE -}; - /// Base class for a @c BluetoothEvent. class BluetoothEvent { public: @@ -104,10 +101,24 @@ public: std::shared_ptr getA2DPRole() const; /** - * Get @c AVRCPCommand associated with the event - * @return @c AVRCPCommand associated with the event or null if not applicable + * Get @c MediaCommand associated with the event + * @return @c MediaCommand associated with the event or null if not applicable */ - std::shared_ptr getAVRCPCommand() const; + std::shared_ptr getMediaCommand() const; + + /** + * Get the current scanning state of the host device. + * + * @return Whether the device is currently scanning for other devices. + */ + bool isScanning() const; + + /** + * Get the current enable/disable state of the A2DP profiles. + * + * @return Whether the event requires to enable the A2DP profiles. + */ + bool isA2DPEnabled() const; protected: /** @@ -117,7 +128,7 @@ protected: * @param deviceState @c DeviceState associated with the event * @param mediaStreamingState @c MediaStreamingState associated with the event * @param role The A2DP role the AVS device is acting as - * @param avrcpCommand The received AVRCPCommand if applicable + * @param mediaCommand The received MediaCommand if applicable */ BluetoothEvent( BluetoothEventType type, @@ -126,7 +137,9 @@ protected: avsCommon::sdkInterfaces::bluetooth::DeviceState::IDLE, MediaStreamingState mediaStreamingState = MediaStreamingState::IDLE, std::shared_ptr role = nullptr, - std::shared_ptr avrcpCommand = nullptr); + std::shared_ptr mediaCommand = nullptr, + bool scanningState = false, + bool a2dpEnable = false); private: /// Event type @@ -144,8 +157,14 @@ private: /// @c A2DPRole associated with the event std::shared_ptr m_a2dpRole; - /// @C AVRCPCommand that is received - std::shared_ptr m_avrcpCommand; + /// @C MediaCommand that is received + std::shared_ptr m_mediaCommand; + + /// The scanning state of the host device + bool m_scanningState; + + /// The enable state of the A2DP profiles + bool m_a2dpEnable; }; inline BluetoothEvent::BluetoothEvent( @@ -154,13 +173,17 @@ inline BluetoothEvent::BluetoothEvent( avsCommon::sdkInterfaces::bluetooth::DeviceState deviceState, MediaStreamingState mediaStreamingState, std::shared_ptr a2dpRole, - std::shared_ptr avrcpCommand) : + std::shared_ptr mediaCommand, + bool scanningState, + bool a2dpEnable) : m_type{type}, m_device{device}, m_deviceState{deviceState}, m_mediaStreamingState{mediaStreamingState}, m_a2dpRole{a2dpRole}, - m_avrcpCommand{avrcpCommand} { + m_mediaCommand{mediaCommand}, + m_scanningState{scanningState}, + m_a2dpEnable{a2dpEnable} { } inline BluetoothEventType BluetoothEvent::getType() const { @@ -184,9 +207,17 @@ inline std::shared_ptr BluetoothEvent::getA2DPRole() const { return m_a2dpRole; } -inline std::shared_ptr BluetoothEvent::getAVRCPCommand() +inline std::shared_ptr BluetoothEvent::getMediaCommand() const { - return m_avrcpCommand; + return m_mediaCommand; +} + +inline bool BluetoothEvent::isScanning() const { + return m_scanningState; +} + +inline bool BluetoothEvent::isA2DPEnabled() const { + return m_a2dpEnable; } /** @@ -291,20 +322,20 @@ inline MediaStreamingStateChangedEvent::MediaStreamingStateChangedEvent( /** * Event indicating that an AVRCP command was received. */ -class AVRCPCommandReceivedEvent : public BluetoothEvent { +class MediaCommandReceivedEvent : public BluetoothEvent { public: - explicit AVRCPCommandReceivedEvent(avsCommon::sdkInterfaces::bluetooth::services::AVRCPCommand command); + explicit MediaCommandReceivedEvent(avsCommon::sdkInterfaces::bluetooth::services::MediaCommand command); }; -inline AVRCPCommandReceivedEvent::AVRCPCommandReceivedEvent( - avsCommon::sdkInterfaces::bluetooth::services::AVRCPCommand command) : +inline MediaCommandReceivedEvent::MediaCommandReceivedEvent( + avsCommon::sdkInterfaces::bluetooth::services::MediaCommand command) : BluetoothEvent( - BluetoothEventType::AVRCP_COMMAND_RECEIVED, + BluetoothEventType::MEDIA_COMMAND_RECEIVED, nullptr, avsCommon::sdkInterfaces::bluetooth::DeviceState::IDLE, MediaStreamingState::IDLE, nullptr, - std::make_shared(command)) { + std::make_shared(command)) { } /** @@ -319,6 +350,45 @@ inline BluetoothDeviceManagerInitializedEvent::BluetoothDeviceManagerInitialized BluetoothEvent(BluetoothEventType::BLUETOOTH_DEVICE_MANAGER_INITIALIZED) { } +/** + * Event indicating that the scanning state on the host device has changed. + */ +class ScanningStateChangedEvent : public BluetoothEvent { +public: + explicit ScanningStateChangedEvent(bool isScanning); +}; + +inline ScanningStateChangedEvent::ScanningStateChangedEvent(bool isScanning) : + BluetoothEvent( + BluetoothEventType::SCANNING_STATE_CHANGED, + nullptr, + avsCommon::sdkInterfaces::bluetooth::DeviceState::IDLE, + MediaStreamingState::IDLE, + nullptr, + nullptr, + isScanning) { +} + +/** + * Event indicating that the request to disconnect/connect certain profiles on devices + */ +class ToggleA2DPProfileStateChangedEvent : public BluetoothEvent { +public: + explicit ToggleA2DPProfileStateChangedEvent(bool a2dpEnable); +}; + +inline ToggleA2DPProfileStateChangedEvent::ToggleA2DPProfileStateChangedEvent(bool a2dpEnable) : + BluetoothEvent( + BluetoothEventType::TOGGLE_A2DP_PROFILE_STATE_CHANGED, + nullptr, + avsCommon::sdkInterfaces::bluetooth::DeviceState::IDLE, + MediaStreamingState::IDLE, + nullptr, + nullptr, + false, + a2dpEnable) { +} + } // namespace bluetooth } // namespace utils } // namespace avsCommon diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/Bluetooth/DeviceCategory.h b/AVSCommon/Utils/include/AVSCommon/Utils/Bluetooth/DeviceCategory.h new file mode 100644 index 00000000..14504ae5 --- /dev/null +++ b/AVSCommon/Utils/include/AVSCommon/Utils/Bluetooth/DeviceCategory.h @@ -0,0 +1,92 @@ +/* + * Copyright 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 ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_BLUETOOTH_DEVICECATEGORY_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_BLUETOOTH_DEVICECATEGORY_H_ + +#include +#include + +/** + * Represent the paired device category. + */ +enum class DeviceCategory { + // the paired device belongs to peripheral family + REMOTE_CONTROL, + // the paired device belongs to gadget family + GADGET, + // the paired device is a audio/video major class. + AUDIO_VIDEO, + // the paired device is a telephony device. + PHONE, + // the paired device falls into none of the above category. + OTHER, + // the paired device has no information. + UNKNOWN +}; + +/** + * Converts the @c DeviceCategory enum to a string. + * + * @param The @c DeviceCategory to convert. + * @return A string representation of the @c DeviceCategory. + */ +inline std::string deviceCategoryToString(const DeviceCategory& category) { + switch (category) { + case DeviceCategory::REMOTE_CONTROL: + return "REMOTE_CONTROL"; + case DeviceCategory::GADGET: + return "GADGET"; + case DeviceCategory::AUDIO_VIDEO: + return "AUDIO_VIDEO"; + case DeviceCategory::PHONE: + return "PHONE"; + case DeviceCategory::OTHER: + return "OTHER"; + case DeviceCategory::UNKNOWN: + return "UNKNOWN"; + } + + return "UNKNOWN"; +} + +/** + * Converts the string to @c DeviceCategory enum. + * + * @param A string representation of the @c DeviceCategory. + * @return The @c DeviceCategory. + */ +inline DeviceCategory stringToDeviceCategory(const std::string& category) { + if (category == "REMOTE_CONTROL") return DeviceCategory::REMOTE_CONTROL; + if (category == "GADGET") return DeviceCategory::GADGET; + if (category == "AUDIO_VIDEO") return DeviceCategory::AUDIO_VIDEO; + if (category == "PHONE") return DeviceCategory::PHONE; + if (category == "OTHER") return DeviceCategory::OTHER; + if (category == "UNKNOWN") return DeviceCategory::UNKNOWN; + + return DeviceCategory::UNKNOWN; +} + +/** + * Overload for the @c DeviceCategory enum. This will write the @c DeviceCategory as a string to the provided stream. + * + * @param An ostream to send the @c DeviceCategory as a string. + * @param The @c DeviceCategory to convert. + * @return The stream. + */ +inline std::ostream& operator<<(std::ostream& stream, const DeviceCategory category) { + return stream << deviceCategoryToString(category); +} + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_BLUETOOTH_DEVICECATEGORY_H_ \ No newline at end of file diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/Bluetooth/MediaStreamingState.h b/AVSCommon/Utils/include/AVSCommon/Utils/Bluetooth/MediaStreamingState.h new file mode 100644 index 00000000..ed424cc4 --- /dev/null +++ b/AVSCommon/Utils/include/AVSCommon/Utils/Bluetooth/MediaStreamingState.h @@ -0,0 +1,70 @@ +/* + * 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 ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_BLUETOOTH_MEDIASTREAMINGSTATE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_BLUETOOTH_MEDIASTREAMINGSTATE_H_ + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace bluetooth { + +/// An Enum representing the current state of the stream. +enum class MediaStreamingState { + /// Currently not streaming. + IDLE, + /// Currently acquiring the stream. + PENDING, + /// Currently streaming. + ACTIVE +}; + +/** + * Converts the @c MediaStreamingState enum to a string. + * + * @param The @c MediaStreamingState to convert. + * @return A string representation of the @c MediaStreamingState. + */ +inline std::string mediaStreamingStateToString(MediaStreamingState value) { + switch (value) { + case MediaStreamingState::IDLE: + return "IDLE"; + case MediaStreamingState::PENDING: + return "PENDING"; + case MediaStreamingState::ACTIVE: + return "ACTIVE"; + default: + return "UNKNOWN"; + } +} + +/** + * Overload for the @c MediaStreamingState enum. This will write the @c MediaStreamingState as a string to the + * provided stream. + * + * @param An ostream to send the @c MediaStreamingState as a string. + * @param The @c MediaStreamingState to convert. + * @return The stream. + */ +inline std::ostream& operator<<(std::ostream& stream, const MediaStreamingState state) { + return stream << mediaStreamingStateToString(state); +} + +} // namespace bluetooth +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_BLUETOOTH_MEDIASTREAMINGSTATE_H_ diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/Bluetooth/SDPRecords.h b/AVSCommon/Utils/include/AVSCommon/Utils/Bluetooth/SDPRecords.h index 215fbd24..c709d056 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/Bluetooth/SDPRecords.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/Bluetooth/SDPRecords.h @@ -1,5 +1,5 @@ /* - * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * 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. @@ -109,6 +109,39 @@ public: AVRCPControllerRecord(const std::string& version); }; +/// A SDP record representing HFP. +class HFPRecord : public SDPRecord { +public: + /** + * Constructor + * + * @param version The version of the service. + */ + HFPRecord(const std::string& version); +}; + +/// A SDP record representing HID. +class HIDRecord : public SDPRecord { +public: + /** + * Constructor + * + * @param version The version of the service. + */ + HIDRecord(const std::string& version); +}; + +/// A SDP record representing SPP. +class SPPRecord : public SDPRecord { +public: + /** + * Constructor + * + * @param version The version of the service. + */ + SPPRecord(const std::string& version); +}; + } // namespace bluetooth } // namespace utils } // namespace avsCommon diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/DeviceInfo.h b/AVSCommon/Utils/include/AVSCommon/Utils/DeviceInfo.h index 76da5b97..6aedc653 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/DeviceInfo.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/DeviceInfo.h @@ -47,14 +47,19 @@ public: * @param deviceSerialNumber DSN. * @param manufacturerName The manufacturer name. * @param description The description of the device. + * @param friendlyName AVS friendly name for device + * @param deviceType The device type. * @return If successful, returns a new DeviceInfo, otherwise @c nullptr. */ static std::unique_ptr create( const std::string& clientId, const std::string& productId, const std::string& deviceSerialNumber, - const std::string& manufacturerName = "", - const std::string& description = ""); + const std::string& manufacturerName, + const std::string& description, + const std::string& friendlyName = "", + const std::string& deviceType = "", + const avsCommon::sdkInterfaces::endpoints::EndpointIdentifier& endpointId = ""); /** * Gets the client Id. @@ -91,6 +96,20 @@ public: */ std::string getDeviceDescription() const; + /** + * Gets the device's AVS friendly name. + * + * @return AVS friendly name. + */ + std::string getFriendlyName() const; + + /** + * Gets the deviceType. + * + * @return device Type. + */ + std::string getDeviceType() const; + /** * Gets the device default endpoint id. * @@ -123,13 +142,17 @@ private: * @param deviceSerialNumber DSN. * @param manufacturerName The manufacturer name. * @param description The description of the device. + * @param endpointId The device endpoint id. */ DeviceInfo( const std::string& clientId, const std::string& productId, const std::string& deviceSerialNumber, const std::string& manufacturerName, - const std::string& description); + const std::string& description, + const std::string& friendlyName, + const std::string& deviceType, + const avsCommon::sdkInterfaces::endpoints::EndpointIdentifier& endpointId); /// Client ID std::string m_clientId; @@ -145,6 +168,15 @@ private: /// The device description. std::string m_deviceDescription; + + /// AVS Friendly name for the device + std::string m_friendlyName; + + /// Device Type + std::string m_deviceType; + + /// Device default endpoint id. + sdkInterfaces::endpoints::EndpointIdentifier m_defaultEndpointId; }; inline std::string DeviceInfo::getManufacturerName() const { @@ -155,6 +187,10 @@ inline std::string DeviceInfo::getDeviceDescription() const { return m_deviceDescription; } +inline sdkInterfaces::endpoints::EndpointIdentifier DeviceInfo::getDefaultEndpointId() const { + return m_defaultEndpointId; +} + } // namespace utils } // namespace avsCommon } // namespace alexaClientSDK diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/HTTP/HttpResponseCode.h b/AVSCommon/Utils/include/AVSCommon/Utils/HTTP/HttpResponseCode.h index e08bb1c5..38e8e60b 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/HTTP/HttpResponseCode.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/HTTP/HttpResponseCode.h @@ -1,5 +1,5 @@ /* - * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018-2020 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. @@ -57,6 +57,8 @@ enum HTTPResponseCode { /// HTTP code for internal error by server which didn't fulfill the request. SERVER_ERROR_INTERNAL = 500, + /// HTTP code for internal error by server for not supporting the facility requested. + SERVER_ERROR_NOT_IMPLEMENTED = 501, /// First success code SUCCESS_START_CODE = SUCCESS_OK, @@ -180,6 +182,8 @@ inline std::string responseCodeToString(HTTPResponseCode responseCode) { return "CLIENT_ERROR_FORBIDDEN"; case HTTPResponseCode::SERVER_ERROR_INTERNAL: return "SERVER_ERROR_INTERNAL"; + case HTTPResponseCode::SERVER_ERROR_NOT_IMPLEMENTED: + return "SERVER_ERROR_NOT_IMPLEMENTED"; } logger::acsdkError(logger::LogEntry("HttpResponseCodes", __func__) .d("longValue", static_cast(responseCode)) diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/HTTP2/HTTP2ConnectionInterface.h b/AVSCommon/Utils/include/AVSCommon/Utils/HTTP2/HTTP2ConnectionInterface.h index a5bd265a..77ff38ca 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/HTTP2/HTTP2ConnectionInterface.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/HTTP2/HTTP2ConnectionInterface.h @@ -1,5 +1,5 @@ /* - * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * 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. @@ -21,6 +21,7 @@ #include "AVSCommon/Utils/HTTP2/HTTP2RequestConfig.h" #include "AVSCommon/Utils/HTTP2/HTTP2RequestInterface.h" #include "AVSCommon/Utils/HTTP2/HTTP2RequestType.h" +#include "AVSCommon/Utils/HTTP2/HTTP2ConnectionObserverInterface.h" namespace alexaClientSDK { namespace avsCommon { @@ -49,6 +50,20 @@ public: * Terminate the HTTP2 connection, forcing immediate termination of any active requests. */ virtual void disconnect() = 0; + + /** + * Add a new connection observer object. + * + * @param observer Object to be notified of any registration event. + */ + virtual void addObserver(std::shared_ptr observer) = 0; + + /** + * Remove the given observer object. + * + * @param observer Object to be removed from observers set. + */ + virtual void removeObserver(std::shared_ptr observer) = 0; }; } // namespace http2 diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/HTTP2/HTTP2ConnectionObserverInterface.h b/AVSCommon/Utils/include/AVSCommon/Utils/HTTP2/HTTP2ConnectionObserverInterface.h new file mode 100644 index 00000000..a134146b --- /dev/null +++ b/AVSCommon/Utils/include/AVSCommon/Utils/HTTP2/HTTP2ConnectionObserverInterface.h @@ -0,0 +1,45 @@ +/* + * 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 ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_HTTP2_HTTP2CONNECTIONOBSERVERINTERFACE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_HTTP2_HTTP2CONNECTIONOBSERVERINTERFACE_H_ + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace http2 { + +/** + * Interface for listening to changes to an HTTP2 connection. + */ +class HTTP2ConnectionObserverInterface { +public: + /** + * Destructor. + */ + virtual ~HTTP2ConnectionObserverInterface() = default; + + /** + * Notify that a GOAWAY frame has been received. + */ + virtual void onGoawayReceived() = 0; +}; + +} // namespace http2 +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_HTTP2_HTTP2CONNECTIONOBSERVERINTERFACE_H_ diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/LibcurlUtils/HttpPost.h b/AVSCommon/Utils/include/AVSCommon/Utils/LibcurlUtils/HttpPost.h index aceb6e0c..534c7019 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/LibcurlUtils/HttpPost.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/LibcurlUtils/HttpPost.h @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2016-2020 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. @@ -82,13 +82,22 @@ private: */ HttpPost() = default; + HTTPResponse doPostInternal( + CurlEasyHandleWrapper& curl, + const std::string& url, + const std::vector headerLines, + const std::string& data, + std::chrono::seconds timeout); + /** * Build POST data from a vector of key value pairs. * + * @param curl Curl handle with which to make the request. * @param data Vector of key, value pairs from which to build the POST data. - * @return + * @return The generated POST data. */ - std::string buildPostData(const std::vector>& data) const; + std::string buildPostData(CurlEasyHandleWrapper& curl, const std::vector>& data) + const; /** * Callback function used to accumulate the body of the HTTP Post response @@ -102,12 +111,6 @@ private: */ static size_t staticWriteCallbackLocked(char* ptr, size_t size, size_t nmemb, void* userdata); - /// Mutex to serialize access to @c m_curl and @c m_response. - std::mutex m_mutex; - - /// CURL handle with which to make requests - CurlEasyHandleWrapper m_curl; - /// String used to accumulate the response body. std::string m_bodyAccumulator; }; diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/LibcurlUtils/LibcurlHTTP2Connection.h b/AVSCommon/Utils/include/AVSCommon/Utils/LibcurlUtils/LibcurlHTTP2Connection.h index 15f62551..b809be67 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/LibcurlUtils/LibcurlHTTP2Connection.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/LibcurlUtils/LibcurlHTTP2Connection.h @@ -1,5 +1,5 @@ /* - * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * 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. @@ -51,6 +51,8 @@ public: std::shared_ptr createAndSendRequest( const http2::HTTP2RequestConfig& config) override; void disconnect() override; + void addObserver(std::shared_ptr observer) override; + void removeObserver(std::shared_ptr observer) override; /// @} protected: @@ -146,6 +148,11 @@ private: */ void processNextRequest(); + /** + * Notify observers that a GOAWAY frame has been received. + */ + void notifyObserversOfGoawayReceived(); + /// Main thread for this class. std::thread m_networkThread; @@ -166,6 +173,12 @@ private: /// Queue of requests send. Serialized by @c m_mutex. std::deque> m_requestQueue; + /// Mutex for observers. + std::mutex m_observersMutex; + + /// Observers + std::unordered_set> m_observers; + /// Set to true when we want to exit the network loop. bool m_isStopping; }; diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/Logger/Level.h b/AVSCommon/Utils/include/AVSCommon/Utils/Logger/Level.h index 4aa3cb4c..aed7b290 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/Logger/Level.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/Logger/Level.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -16,6 +16,7 @@ #ifndef ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_LOGGER_LEVEL_H_ #define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_LOGGER_LEVEL_H_ +#include #include namespace alexaClientSDK { diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/Logger/LogEntry.h b/AVSCommon/Utils/include/AVSCommon/Utils/Logger/LogEntry.h index 633d5283..a63f6872 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/Logger/LogEntry.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/Logger/LogEntry.h @@ -16,6 +16,8 @@ #ifndef ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_LOGGER_LOGENTRY_H_ #define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_LOGGER_LOGENTRY_H_ +#include +#include #include #include @@ -128,6 +130,18 @@ public: template inline LogEntry& sensitive(const char* key, const ValueType& value); + /** + * Add data in the form of a @c key, @c value pair to the metadata of this log entry. + * If the value includes a privacy blacklist entry, the portion after that will be obfuscated. + * This is done in a distinct method (instead of m or d) to avoid the cost of always checking + * against the blacklist. + * + * @param key The key identifying the value to add to this LogEntry. + * @param value The value to add to this LogEntry, obfuscated if needed. + * @return This instance to facilitate adding more information to this log entry. + */ + inline LogEntry& obfuscatePrivateData(const char* key, const std::string& value); + /** * Add an arbitrary message to the end of the text of this LogEntry. Once this has been called no other * additions should be made to this LogEntry. @@ -161,6 +175,12 @@ private: /// Add the appropriate prefix for an arbitrary message that is about to be appended to the text of this LogEntry. void prefixMessage(); + /// Return a list of labels we will obfuscate if sent to obfuscatePrivateData + static std::vector getPrivateLabelBlacklist() { + static std::vector privateLabelBlacklist = {"ssid"}; + return privateLabelBlacklist; + } + /** * Append an escaped string to m_stream. * Our metadata and subsequent optional message is of the form: @@ -202,6 +222,36 @@ LogEntry& LogEntry::sensitive(const char*, const ValueType&) { } #endif +LogEntry& LogEntry::obfuscatePrivateData(const char* key, const std::string& value) { + // if value contains any private label, obfuscate the section after the label + // since it can (but shouldn't) contain multiple, obfuscate from the earliest one found onward + auto firstPosition = value.length(); + + for (auto privateLabel : getPrivateLabelBlacklist()) { + auto it = std::search( + value.begin(), + value.end(), + privateLabel.begin(), + privateLabel.end(), + [](char valueChar, char blackListChar) { return std::tolower(valueChar) == std::tolower(blackListChar); }); + if (it != value.end()) { + // capture the least value + auto thisPosition = std::distance(value.begin(), it) + privateLabel.length(); + if (thisPosition < firstPosition) { + firstPosition = thisPosition; + } + } + } + + if (firstPosition <= value.length()) { + // hash everything after the label itself + auto labelPart = value.substr(0, firstPosition); + auto obfuscatedPart = std::to_string(std::hash{}(value.substr(firstPosition))); + return d(key, labelPart + obfuscatedPart); + } + return d(key, value); +} + } // namespace logger } // namespace utils } // namespace avsCommon diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/MacAddressString.h b/AVSCommon/Utils/include/AVSCommon/Utils/MacAddressString.h index 4888de3e..8e47e5dc 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/MacAddressString.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/MacAddressString.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -57,7 +57,7 @@ public: private: /// The constructor will only be called with a legal macAddress input. We don't check here because this function is /// private and is only called from the public create(...) factory method. - MacAddressString(const std::string& macAddress); + explicit MacAddressString(const std::string& macAddress); /// a well formed MAC address string const std::string m_macAddress; diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/MediaPlayer/MediaPlayerInterface.h b/AVSCommon/Utils/include/AVSCommon/Utils/MediaPlayer/MediaPlayerInterface.h index 83bcd2cc..3c8e8475 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/MediaPlayer/MediaPlayerInterface.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/MediaPlayer/MediaPlayerInterface.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -23,7 +23,12 @@ #include "AVSCommon/AVS/Attachment/AttachmentReader.h" #include "AVSCommon/Utils/AudioFormat.h" +#include "AVSCommon/Utils/Optional.h" +#include "AVSCommon/Utils/MediaPlayer/MediaPlayerState.h" +#include "AVSCommon/Utils/MediaPlayer/PlaybackAttributes.h" +#include "AVSCommon/Utils/MediaPlayer/PlaybackReport.h" #include "AVSCommon/Utils/MediaPlayer/SourceConfig.h" +#include "AVSCommon/Utils/Optional.h" namespace alexaClientSDK { namespace avsCommon { @@ -53,7 +58,7 @@ class MediaPlayerObserverInterface; * set if the previous source was in a non-stopped state. * * @c note A @c MediaPlayerInterface implementation must be able to support the various audio formats listed at: - * https://developer.amazon.com/docs/alexa-voice-service/recommended-media-support.html. + * https://developer.amazon.com/docs/alexa/alexa-voice-service/recommended-media-support.html. */ class MediaPlayerInterface { public: @@ -226,6 +231,8 @@ public: * * @return If the specified source is playing, the offset in milliseconds that the source has been playing * will be returned. If the specified source is not playing, the last offset it played will be returned. + * + * @deprecated Use @c getMediaPlayerState instead, which contains the offset */ virtual std::chrono::milliseconds getOffset(SourceId id) = 0; @@ -236,6 +243,17 @@ public: */ virtual uint64_t getNumBytesBuffered() = 0; + /** + * Returns the current state of the media player source, including + * the id and offset. + * + * @param id The unique id of the source for the desired state. + * + * @return Optional state including the offset. A blank Optional is returned + * if retrieving this information fails. + */ + virtual utils::Optional getMediaPlayerState(SourceId id) = 0; + /** * Adds an observer to be notified when playback state changes. * @@ -251,8 +269,34 @@ public: */ virtual void removeObserver( std::shared_ptr playerObserver) = 0; + + /** + * Get @c PlaybackAttributes for the current stream being played. + * This method only needs to be implemented if your mediaplayer supports Premium Audio. + * + * @return playbackAttributes The playback attributes for the current stream if premium audio is supported + * and it has an active source; otherwise, an empty object. + */ + virtual utils::Optional getPlaybackAttributes(); + + /** + * Get list of @c PlaybackReports for current track. + * This method only needs to be implemented if your mediaplayer supports Premium Audio. + * + * @return The list of @c PlaybackReport for current track if premium audio is supported and it has an + * active source; otherwise, an empty list. + */ + virtual std::vector getPlaybackReports(); }; +inline utils::Optional MediaPlayerInterface::getPlaybackAttributes() { + return utils::Optional(); +} + +inline std::vector MediaPlayerInterface::getPlaybackReports() { + return {}; +} + } // namespace mediaPlayer } // namespace utils } // namespace avsCommon diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/MediaPlayer/MediaPlayerObserverInterface.h b/AVSCommon/Utils/include/AVSCommon/Utils/MediaPlayer/MediaPlayerObserverInterface.h index 10cf05cb..18485c00 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/MediaPlayer/MediaPlayerObserverInterface.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/MediaPlayer/MediaPlayerObserverInterface.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -23,6 +23,7 @@ #include #include "MediaPlayerInterface.h" +#include "MediaPlayerState.h" namespace alexaClientSDK { namespace avsCommon { @@ -52,10 +53,6 @@ public: /** * Structure to hold the key, value and type of tag that is found. - * - * @param key Key of the stream tag - * @param value Value of the stream tag - * @param type Type of the stream tag. */ struct TagKeyValueType { /// Key extracted from the stream tag. @@ -73,14 +70,25 @@ public: */ virtual ~MediaPlayerObserverInterface() = default; + /** + * This is an indication to the observer that the @c MediaPlayer has read its first byte of data. + * + * @param id The id of the source to which this callback corresponds to. + * @param state Metadata about the media player state + */ + virtual void onFirstByteRead(SourceId id, const MediaPlayerState& state) = 0; + /** * This is an indication to the observer that the @c MediaPlayer has started playing the source specified by * the id. * * @note The observer must quickly return to quickly from this callback. Failure to do so could block the @c * MediaPlayer from further processing. + * + * @param id The id of the source to which this callback corresponds to. + * @param state Metadata about the media player state */ - virtual void onPlaybackStarted(SourceId id) = 0; + virtual void onPlaybackStarted(SourceId id, const MediaPlayerState& state) = 0; /** * This is an indication to the observer that the @c MediaPlayer finished the source. @@ -89,8 +97,9 @@ public: * MediaPlayer from further processing. * * @param id The id of the source to which this callback corresponds to. + * @param state Metadata about the media player state */ - virtual void onPlaybackFinished(SourceId id) = 0; + virtual void onPlaybackFinished(SourceId id, const MediaPlayerState& state) = 0; /** * This is an indication to the observer that the @c MediaPlayer encountered an error. Errors can occur during @@ -102,8 +111,13 @@ public: * @param id The id of the source to which this callback corresponds to. * @param type The type of error encountered by the @c MediaPlayerInterface. * @param error The error encountered by the @c MediaPlayerInterface. + * @param state Metadata about the media player state */ - virtual void onPlaybackError(SourceId id, const ErrorType& type, std::string error) = 0; + virtual void onPlaybackError( + SourceId id, + const ErrorType& type, + std::string error, + const MediaPlayerState& state) = 0; /** * This is an indication to the observer that the @c MediaPlayer has paused playing the source. @@ -112,8 +126,9 @@ public: * further processing. * @param id The id of the source to which this callback corresponds to. + * @param state Metadata about the media player state */ - virtual void onPlaybackPaused(SourceId id){}; + virtual void onPlaybackPaused(SourceId id, const MediaPlayerState& state){}; /** * This is an indication to the observer that the @c MediaPlayer has resumed playing the source. @@ -122,8 +137,9 @@ public: * further processing. * * @param id The id of the source to which this callback corresponds to. + * @param state Metadata about the media player state */ - virtual void onPlaybackResumed(SourceId id){}; + virtual void onPlaybackResumed(SourceId id, const MediaPlayerState& state){}; /** * This is an indication to the observer that the @c MediaPlayer has stopped the source. @@ -132,8 +148,9 @@ public: * further processing. * * @param id The id of the source to which this callback corresponds to. + * @param state Metadata about the media player state */ - virtual void onPlaybackStopped(SourceId id){}; + virtual void onPlaybackStopped(SourceId id, const MediaPlayerState& state){}; /** * This is an indication to the observer that the @c MediaPlayer is experiencing a buffer underrun. @@ -143,8 +160,9 @@ public: * further processing. * * @param id The id of the source to which this callback corresponds to. + * @param state Metadata about the media player state */ - virtual void onBufferUnderrun(SourceId id) { + virtual void onBufferUnderrun(SourceId id, const MediaPlayerState& state) { } /** @@ -155,8 +173,9 @@ public: * further processing. * * @param id The id of the source to which this callback corresponds to. + * @param state Metadata about the media player state */ - virtual void onBufferRefilled(SourceId id) { + virtual void onBufferRefilled(SourceId id, const MediaPlayerState& state) { } /** @@ -169,8 +188,9 @@ public: * further processing. * * @param id The id of the source to which this callback corresponds to. + * @param state Metadata about the media player state */ - virtual void onBufferingComplete(SourceId id) { + virtual void onBufferingComplete(SourceId id, const MediaPlayerState& state) { } /** @@ -184,10 +204,22 @@ public: * * @param id The id of the source to which this callback corresponds to. * @param vectorOfTags The vector containing stream tags. + * @param state Metadata about the media player state */ - virtual void onTags(SourceId id, std::unique_ptr vectorOfTags){}; + virtual void onTags(SourceId id, std::unique_ptr vectorOfTags, const MediaPlayerState& state){}; }; +/** + * Write a @c MediaPlayerState to an @c ostream as a string. + * + * @param stream The stream to write the value to. + * @param state The state value to write to teh @c ostream as a string + * @return The @c ostream that was passed in and written to. + */ +inline std::ostream& operator<<(std::ostream& stream, const MediaPlayerState& state) { + return stream << "MediaPlayerState: offsetInMilliseconds=" << state.offset.count(); +} + } // namespace mediaPlayer } // namespace utils } // namespace avsCommon diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/MediaPlayer/MediaPlayerState.h b/AVSCommon/Utils/include/AVSCommon/Utils/MediaPlayer/MediaPlayerState.h new file mode 100644 index 00000000..8fb60e82 --- /dev/null +++ b/AVSCommon/Utils/include/AVSCommon/Utils/MediaPlayer/MediaPlayerState.h @@ -0,0 +1,61 @@ +/* + * Copyright 2020 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 ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_MEDIAPLAYER_MEDIAPLAYERSTATE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_MEDIAPLAYER_MEDIAPLAYERSTATE_H_ + +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace mediaPlayer { + +/** + * Structure to hold metadata about the MediaPlayerState + */ +struct MediaPlayerState { + /** + * Default Constructor, initializes the offset to zero. + */ + MediaPlayerState() : offset(std::chrono::milliseconds::zero()) { + } + + /** + * Constructor. + */ + MediaPlayerState(std::chrono::milliseconds offsetInMs) : offset(offsetInMs) { + } + + /// Offset in milliseconds + std::chrono::milliseconds offset; + + /** + * Overload the == operator for equality checks + * + * @param other The other @c MediaPlayerState to compare + * @return Whether @c this is equivalent to @c other + */ + bool operator==(const MediaPlayerState& other) const { + return offset == other.offset; + } +}; + +} // namespace mediaPlayer +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_MEDIAPLAYER_MEDIAPLAYERSTATE_H_ diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/MediaPlayer/PlaybackAttributes.h b/AVSCommon/Utils/include/AVSCommon/Utils/MediaPlayer/PlaybackAttributes.h new file mode 100644 index 00000000..95f7d180 --- /dev/null +++ b/AVSCommon/Utils/include/AVSCommon/Utils/MediaPlayer/PlaybackAttributes.h @@ -0,0 +1,55 @@ +/* + * Copyright 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 ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_MEDIAPLAYER_PLAYBACKATTRIBUTES_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_MEDIAPLAYER_PLAYBACKATTRIBUTES_H_ + +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace mediaPlayer { + +/** + * Struct to hold playback attributes of a track. This struct is used for + * both reporting playback options (as part of an adaptive bitrate scheme + * such as MPEG-DASH) for Premium Audio, and reporting which playback option the device + * chose. + */ +struct PlaybackAttributes { + /// Identifier for the stream currently being played from the manifest. For DASH, this will be pulled from a + /// SupplementalProperty under each representation, with the schemeIdURI="tag:amazon.com,2019:dash:StreamId" + std::string name; + + /// Encoding of the stream being played. For DASH, this will be pulled from the "representation" tag in an + /// Adaptation Set. + std::string codec; + + /// Audio sampling rate of the stream being played in Hertz. For DASH, this will be pulled from the "representation" + /// tag in an Adaptation Set. + long samplingRateInHertz; + + /// Bitrate of the stream being played in bits per second. For DASH, this will be pulled from the "representation" + /// tag in an Adaptation Set. + long dataRateInBitsPerSecond; +}; + +} // namespace mediaPlayer +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_MEDIAPLAYER_PLAYBACKATTRIBUTES_H_ diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/MediaPlayer/PlaybackReport.h b/AVSCommon/Utils/include/AVSCommon/Utils/MediaPlayer/PlaybackReport.h new file mode 100644 index 00000000..85dd9c65 --- /dev/null +++ b/AVSCommon/Utils/include/AVSCommon/Utils/MediaPlayer/PlaybackReport.h @@ -0,0 +1,50 @@ +/* + * Copyright 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 ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_MEDIAPLAYER_PLAYBACKREPORT_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_MEDIAPLAYER_PLAYBACKREPORT_H_ + +#include + +#include "AVSCommon/Utils/MediaPlayer/PlaybackAttributes.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace mediaPlayer { + +/** + * Struct to hold playback reports for adaptive bitrate schemes such as MPEG-DASH. + * This struct is used for reporting the various playback options (that is + * part of an adaptive bitrate scheme such as MPEG-DASH manifest) for Premium Audio. For MPEG-DASH, the + * PlaybackReport represents a subset of the data in an AdaptationSet. + */ +struct PlaybackReport { + /// Start offset for playback being reported. + std::chrono::milliseconds startOffset; + + /// End offset for playback being reported. + std::chrono::milliseconds endOffset; + + /// @c PlaybackAttributes for the stream being reported. + PlaybackAttributes playbackAttributes; +}; + +} // namespace mediaPlayer +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_MEDIAPLAYER_PLAYBACKREPORT_H_ diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/MediaPlayer/SourceConfig.h b/AVSCommon/Utils/include/AVSCommon/Utils/MediaPlayer/SourceConfig.h index d1f34c24..eef379e1 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/MediaPlayer/SourceConfig.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/MediaPlayer/SourceConfig.h @@ -1,5 +1,5 @@ /* - * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018-2020 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. @@ -68,10 +68,23 @@ struct SourceConfig { const std::chrono::milliseconds& duration); }; +/** + * Builds a Source Config object with fade in disabled. + * @return a Source Config object without fade in + */ inline SourceConfig emptySourceConfig() { return SourceConfig{{100, 100, std::chrono::milliseconds::zero(), false}}; } +/** + * Builds a Source Config object with fade in enabled and with the provided valid values. + * + * @param startGain The starting percentage volume when the media starts playing (range 0-100) Will become valid. + * @param endGain The ending percentage volume when the media played to the fade-in duration (range 0-100). Will become + * valid. + * @param duration The fade-in duration time. + * @return a Source Config object with fade in set for the given valid values + */ inline SourceConfig SourceConfig::createWithFadeIn( short startGain, short endGain, diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/Metrics/DataPoint.h b/AVSCommon/Utils/include/AVSCommon/Utils/Metrics/DataPoint.h index 146f5d25..938fcb9d 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/Metrics/DataPoint.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/Metrics/DataPoint.h @@ -1,5 +1,5 @@ /* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2019-2020 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. @@ -30,7 +30,7 @@ namespace metrics { class DataPoint { public: /** - * Default constructor + * Default constructor. Creates a invalid metric point with empty values. */ DataPoint(); diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/Metrics/MetricSinkInterface.h b/AVSCommon/Utils/include/AVSCommon/Utils/Metrics/MetricSinkInterface.h index 95626791..6280f5de 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/Metrics/MetricSinkInterface.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/Metrics/MetricSinkInterface.h @@ -1,5 +1,5 @@ /* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2019-2020 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. @@ -12,6 +12,7 @@ * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ + #ifndef ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_METRICS_METRICSINKINTERFACE_H_ #define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_METRICS_METRICSINKINTERFACE_H_ @@ -30,14 +31,14 @@ namespace metrics { class MetricSinkInterface { public: /** - * Destructor + * Destructor. */ virtual ~MetricSinkInterface() = default; /** * This function allows consumption of a MetricEvent. * - * @param metricEvent is the metricEvent to be consumed + * @param metricEvent is the metricEvent object to be consumed * @return true if metric sink consumes metric event successfully * false otherwise */ diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/PlatformDefinitions.h b/AVSCommon/Utils/include/AVSCommon/Utils/PlatformDefinitions.h new file mode 100644 index 00000000..11519c01 --- /dev/null +++ b/AVSCommon/Utils/include/AVSCommon/Utils/PlatformDefinitions.h @@ -0,0 +1,24 @@ +/* + * Copyright 2020 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 ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_PLATFORMDEFINITIONS_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_PLATFORMDEFINITIONS_H_ + +#if defined(_MSC_VER) +#include +typedef SSIZE_T ssize_t; +#endif + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_PLATFORMDEFINITIONS_H_ diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/SDKVersion.h b/AVSCommon/Utils/include/AVSCommon/Utils/SDKVersion.h index 050d77a8..e3364d60 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/SDKVersion.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/SDKVersion.h @@ -29,7 +29,7 @@ namespace utils { namespace sdkVersion { inline static std::string getCurrentVersion() { - return "1.17.0"; + return "1.18.0"; } inline static int getMajorVersion() { @@ -37,7 +37,7 @@ inline static int getMajorVersion() { } inline static int getMinorVersion() { - return 17; + return 18; } inline static int getPatchVersion() { diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/SDS/BufferLayout.h b/AVSCommon/Utils/include/AVSCommon/Utils/SDS/BufferLayout.h index 003bcf8f..8f7cb84b 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/SDS/BufferLayout.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/SDS/BufferLayout.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -582,10 +582,12 @@ void SharedDataStream::BufferLayout::detach() { } auto header = getHeader(); - std::lock_guard lock(header->attachMutex); - --header->referenceCount; - if (header->referenceCount > 0) { - return; + { + std::lock_guard lock(header->attachMutex); + --header->referenceCount; + if (header->referenceCount > 0) { + return; + } } // Destruction of reader arrays. diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/SDS/Reader.h b/AVSCommon/Utils/include/AVSCommon/Utils/SDS/Reader.h index 1d26384f..033931c2 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/SDS/Reader.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/SDS/Reader.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -24,6 +24,7 @@ #include #include "AVSCommon/Utils/Logger/LoggerUtils.h" +#include "AVSCommon/Utils/PlatformDefinitions.h" #include "SharedDataStream.h" #include "ReaderPolicy.h" @@ -409,6 +410,7 @@ bool SharedDataStream::Reader::seek(Index offset, Reference reference) { *m_readerCursor = absolute; if (backward) { + header->oldestUnconsumedCursor = 0; m_bufferLayout->updateOldestUnconsumedCursorLocked(); lock.unlock(); } else { diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/SDS/Writer.h b/AVSCommon/Utils/include/AVSCommon/Utils/SDS/Writer.h index e8820f6b..300c55b5 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/SDS/Writer.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/SDS/Writer.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -16,12 +16,12 @@ #ifndef ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_SDS_WRITER_H_ #define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_SDS_WRITER_H_ -#include #include -#include -#include -#include +#include #include +#include +#include +#include #include "AVSCommon/Utils/Logger/LoggerUtils.h" diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/Threading/TaskThread.h b/AVSCommon/Utils/include/AVSCommon/Utils/Threading/TaskThread.h index bef91158..f1fe8b88 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/Threading/TaskThread.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/Threading/TaskThread.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -35,8 +35,6 @@ class TaskThread { public: /** * Constructs a TaskThread to read from the given TaskQueue. This does not start the thread. - * - * @params taskQueue A TaskQueue to take tasks from to execute. */ TaskThread(); diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/Timing/Timer.h b/AVSCommon/Utils/include/AVSCommon/Utils/Timing/Timer.h index 9f6382ab..325755cc 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/Timing/Timer.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/Timing/Timer.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -70,6 +70,16 @@ public: */ ~Timer(); + /// Static member function to get FOREVER. + static size_t getForever() { + return FOREVER; + } + + /// Static member function to get TAG. + static std::string getTag() { + return "Timer"; + } + /** * Submits a callable type (function, lambda expression, bind expression, or another function object) to be * executed after an initial delay, and then called repeatedly on a fixed time schedule. A @c Timer instance @@ -86,7 +96,7 @@ public: * @param period The non-negative time to wait between subsequent task calls. Negative values will cause this * function to return false. * @param periodType The type of period to use when making subsequent task calls. - * @param maxCount The desired number of times to call task. @c Timer::FOREVER means to call forever until + * @param maxCount The desired number of times to call task. @c Timer::getForever() means to call forever until * @c stop() is called. Note that fewer than @c maxCount calls may occur if @c periodType is * @c PeriodType::ABSOLUTE and the task runtime exceeds @c period. * @param task A callable type representing a task. @@ -116,7 +126,7 @@ public: * @param period The non-negative time to wait before each @c task call. Negative values will cause this * function to return false. * @param periodType The type of period to use when making subsequent task calls. - * @param maxCount The desired number of times to call task. @c Timer::FOREVER means to call forever until + * @param maxCount The desired number of times to call task. @c Timer::getForever() means to call forever until * @c stop() is called. Note that fewer than @c maxCount calls may occur if @c periodType is * @c PeriodType::ABSOLUTE and the task runtime exceeds @c period. * @param task A callable type representing a task. @@ -189,7 +199,7 @@ private: * @param delay The non-negative time to wait before making the first @c task call. * @param period The non-negative time to wait between subsequent @c task calls. * @param periodType The type of period to use when making subsequent task calls. - * @param maxCount The desired number of times to call task. @c Timer::FOREVER means to call forever until + * @param maxCount The desired number of times to call task. @c Timer::getForever() means to call forever until * @c stop() is called. Note that fewer than @c maxCount calls may occur if @c periodType is * @c PeriodType::ABSOLUTE and the task runtime exceeds @c period. * @param task A callable type representing a task. @@ -202,11 +212,6 @@ private: size_t maxCount, std::function task); - /** - * The tag associated with log entries from this class. - */ - static const std::string TAG; - /// The condition variable used to wait for @c stop() or period timeouts. std::condition_variable m_waitCondition; @@ -235,17 +240,17 @@ bool Timer::start( Task task, Args&&... args) { if (delay < std::chrono::duration::zero()) { - logger::acsdkError(logger::LogEntry(TAG, "startFailed").d("reason", "negativeDelay")); + logger::acsdkError(logger::LogEntry(Timer::getTag(), "startFailed").d("reason", "negativeDelay")); return false; } if (period < std::chrono::duration::zero()) { - logger::acsdkError(logger::LogEntry(TAG, "startFailed").d("reason", "negativePeriod")); + logger::acsdkError(logger::LogEntry(Timer::getTag(), "startFailed").d("reason", "negativePeriod")); return false; } // Can't start if already running. if (!activate()) { - logger::acsdkError(logger::LogEntry(TAG, "startFailed").d("reason", "timerAlreadyActive")); + logger::acsdkError(logger::LogEntry(Timer::getTag(), "startFailed").d("reason", "timerAlreadyActive")); return false; } @@ -283,7 +288,7 @@ auto Timer::start(const std::chrono::duration& delay, Task task, Ar -> std::future { // Can't start if already running. if (!activate()) { - logger::acsdkError(logger::LogEntry(TAG, "startFailed").d("reason", "timerAlreadyActive")); + logger::acsdkError(logger::LogEntry(Timer::getTag(), "startFailed").d("reason", "timerAlreadyActive")); using FutureType = decltype(task(args...)); return std::future(); } diff --git a/AVSCommon/Utils/src/Bluetooth/SDPRecords.cpp b/AVSCommon/Utils/src/Bluetooth/SDPRecords.cpp index 97c750e7..d147ef3b 100644 --- a/AVSCommon/Utils/src/Bluetooth/SDPRecords.cpp +++ b/AVSCommon/Utils/src/Bluetooth/SDPRecords.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * 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. @@ -16,6 +16,9 @@ #include "AVSCommon/SDKInterfaces/Bluetooth/Services/A2DPSourceInterface.h" #include "AVSCommon/SDKInterfaces/Bluetooth/Services/AVRCPControllerInterface.h" #include "AVSCommon/SDKInterfaces/Bluetooth/Services/AVRCPTargetInterface.h" +#include "AVSCommon/SDKInterfaces/Bluetooth/Services/HFPInterface.h" +#include "AVSCommon/SDKInterfaces/Bluetooth/Services/HIDInterface.h" +#include "AVSCommon/SDKInterfaces/Bluetooth/Services/SPPInterface.h" #include "AVSCommon/Utils/Bluetooth/SDPRecords.h" @@ -60,6 +63,15 @@ AVRCPControllerRecord::AVRCPControllerRecord(const std::string& version) : SDPRecord{AVRCPControllerInterface::NAME, AVRCPControllerInterface::UUID, version} { } +HFPRecord::HFPRecord(const std::string& version) : SDPRecord{HFPInterface::NAME, HFPInterface::UUID, version} { +} + +HIDRecord::HIDRecord(const std::string& version) : SDPRecord(HIDInterface::NAME, HIDInterface::UUID, version) { +} + +SPPRecord::SPPRecord(const std::string& version) : SDPRecord(SPPInterface::NAME, SPPInterface::UUID, version) { +} + } // namespace bluetooth } // namespace utils } // namespace avsCommon diff --git a/AVSCommon/Utils/src/DeviceInfo.cpp b/AVSCommon/Utils/src/DeviceInfo.cpp index 0e784bef..54ede610 100644 --- a/AVSCommon/Utils/src/DeviceInfo.cpp +++ b/AVSCommon/Utils/src/DeviceInfo.cpp @@ -32,6 +32,8 @@ static const std::string TAG("DeviceInfo"); */ #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) +using namespace avsCommon::sdkInterfaces::endpoints; + /// Name of @c ConfigurationNode for DeviceInfo const std::string CONFIG_KEY_DEVICE_INFO = "deviceInfo"; /// Name of clientId value in DeviceInfo's @c ConfigurationNode. @@ -40,6 +42,10 @@ const std::string CONFIG_KEY_CLIENT_ID = "clientId"; const std::string CONFIG_KEY_PRODUCT_ID = "productId"; /// Name of deviceSerialNumber value in DeviceInfo's @c ConfigurationNode. const std::string CONFIG_KEY_DSN = "deviceSerialNumber"; +/// DeviceType in DeviceInfo's @c ConfigurationNode. +const std::string CONFIG_KEY_DEVICE_TYPE = "deviceType"; +/// Name of friendlyName value in DeviceInfo's @c ConfigurationNode. +const std::string CONFIG_KEY_FRIENDLY_NAME = "friendlyName"; /// Key for the device manufacturer name in DeviceInfo's @c ConfigurationNode. const std::string CONFIG_KEY_MANUFACTURER_NAME = "manufacturerName"; /// Key for the device description in DeviceInfo's @c ConfigurationNode. @@ -47,6 +53,22 @@ const std::string CONFIG_KEY_DESCRIPTION = "description"; /// String used to join attributes in the generation of the default endpoint id. const std::string ENDPOINT_ID_CONCAT = "::"; +/** + * Generate an endpoint identifier to the device per public documentation: + * @see https://developer.amazon.com/en-US/docs/alexa/alexa-voice-service/alexa-discovery.html + * + * @param clientId The client ID of the device maintaining the HTTP/2 connection, generated during product registration. + * @param productId The product ID of the device maintaining the HTTP/2 connection. + * @param deviceSerialNumber The serial number of the device maintaining the HTTP/2 connection. + * @return The default endpoint identifier for this client. + */ +inline EndpointIdentifier generateEndpointId( + const std::string& clientId, + const std::string& productId, + const std::string& deviceSerialNumber) { + return clientId + ENDPOINT_ID_CONCAT + productId + ENDPOINT_ID_CONCAT + deviceSerialNumber; +} + std::unique_ptr DeviceInfo::create( const avsCommon::utils::configuration::ConfigurationNode& configurationRoot) { std::string clientId; @@ -54,6 +76,8 @@ std::unique_ptr DeviceInfo::create( std::string deviceSerialNumber; std::string manufacturerName; std::string description; + std::string friendlyName; + std::string deviceType; const std::string errorEvent = "createFailed"; const std::string errorReasonKey = "reason"; const std::string errorValueKey = "key"; @@ -81,6 +105,14 @@ std::unique_ptr DeviceInfo::create( return nullptr; } + if (!deviceInfoConfiguration.getString(CONFIG_KEY_DEVICE_TYPE, &deviceType)) { + ACSDK_INFO(LX(__func__).d("result", "skipDeviceType").d(errorValueKey, CONFIG_KEY_DEVICE_TYPE)); + } + + if (!deviceInfoConfiguration.getString(CONFIG_KEY_FRIENDLY_NAME, &friendlyName)) { + ACSDK_INFO(LX(__func__).d("result", "skipFriendlyName").d(errorValueKey, CONFIG_KEY_FRIENDLY_NAME)); + } + if (!deviceInfoConfiguration.getString(CONFIG_KEY_MANUFACTURER_NAME, &manufacturerName)) { ACSDK_ERROR( LX(errorEvent).d(errorReasonKey, "missingManufacturerName").d(errorValueKey, CONFIG_KEY_MANUFACTURER_NAME)); @@ -92,7 +124,7 @@ std::unique_ptr DeviceInfo::create( return nullptr; } - return create(clientId, productId, deviceSerialNumber, manufacturerName, description); + return create(clientId, productId, deviceSerialNumber, manufacturerName, description, friendlyName, deviceType); } std::unique_ptr DeviceInfo::create( @@ -100,7 +132,10 @@ std::unique_ptr DeviceInfo::create( const std::string& productId, const std::string& deviceSerialNumber, const std::string& manufacturerName, - const std::string& description) { + const std::string& description, + const std::string& friendlyName, + const std::string& deviceType, + const EndpointIdentifier& endpointId) { const std::string errorEvent = "createFailed"; const std::string errorReasonKey = "reason"; const std::string errorValueKey = "key"; @@ -131,8 +166,18 @@ std::unique_ptr DeviceInfo::create( return nullptr; } - std::unique_ptr instance( - new DeviceInfo(clientId, productId, deviceSerialNumber, manufacturerName, description)); + auto defaultEndpointId = + endpointId.empty() ? generateEndpointId(clientId, productId, deviceSerialNumber) : endpointId; + + std::unique_ptr instance(new DeviceInfo( + clientId, + productId, + deviceSerialNumber, + manufacturerName, + description, + friendlyName, + deviceType, + defaultEndpointId)); return instance; } @@ -149,22 +194,33 @@ std::string DeviceInfo::getDeviceSerialNumber() const { return m_deviceSerialNumber; } -sdkInterfaces::endpoints::EndpointIdentifier DeviceInfo::getDefaultEndpointId() const { - return m_clientId + ENDPOINT_ID_CONCAT + m_productId + ENDPOINT_ID_CONCAT + m_deviceSerialNumber; +std::string DeviceInfo::getFriendlyName() const { + return m_friendlyName; +} + +std::string DeviceInfo::getDeviceType() const { + return m_deviceType; } bool DeviceInfo::operator==(const DeviceInfo& rhs) { - if (getClientId() != rhs.getClientId()) { - return false; - } - if (getProductId() != rhs.getProductId()) { - return false; - } - if (getDeviceSerialNumber() != rhs.getDeviceSerialNumber()) { - return false; - } - - return true; + return std::tie( + m_clientId, + m_productId, + m_deviceSerialNumber, + m_manufacturer, + m_deviceDescription, + m_friendlyName, + m_deviceType, + m_defaultEndpointId) == + std::tie( + rhs.m_clientId, + rhs.m_productId, + rhs.m_deviceSerialNumber, + rhs.m_manufacturer, + rhs.m_deviceDescription, + rhs.m_friendlyName, + rhs.m_deviceType, + rhs.m_defaultEndpointId); } bool DeviceInfo::operator!=(const DeviceInfo& rhs) { @@ -176,12 +232,18 @@ DeviceInfo::DeviceInfo( const std::string& productId, const std::string& deviceSerialNumber, const std::string& manufacturerName, - const std::string& description) : + const std::string& description, + const std::string& friendlyName, + const std::string& deviceType, + const EndpointIdentifier& endpointId) : m_clientId{clientId}, m_productId{productId}, m_deviceSerialNumber{deviceSerialNumber}, m_manufacturer{manufacturerName}, - m_deviceDescription{description} { + m_deviceDescription{description}, + m_friendlyName{friendlyName}, + m_deviceType{deviceType}, + m_defaultEndpointId{endpointId} { } } // namespace utils diff --git a/AVSCommon/Utils/src/LibcurlUtils/HttpPost.cpp b/AVSCommon/Utils/src/LibcurlUtils/HttpPost.cpp index c39ac371..e4b5d613 100644 --- a/AVSCommon/Utils/src/LibcurlUtils/HttpPost.cpp +++ b/AVSCommon/Utils/src/LibcurlUtils/HttpPost.cpp @@ -38,11 +38,7 @@ static const std::string TAG("HttpPost"); #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) std::unique_ptr HttpPost::create() { - std::unique_ptr httpPost(new HttpPost()); - if (httpPost->m_curl.isValid()) { - return httpPost; - } - return nullptr; + return std::unique_ptr(new HttpPost()); } long HttpPost::doPost( @@ -50,7 +46,8 @@ long HttpPost::doPost( const std::string& data, std::chrono::seconds timeout, std::string& body) { - auto response = doPost(url, {}, data, timeout); + CurlEasyHandleWrapper curl; + auto response = doPostInternal(curl, url, {}, data, timeout); body = response.body; return response.code; } @@ -60,8 +57,9 @@ HTTPResponse HttpPost::doPost( const std::vector headerLines, const std::vector>& data, std::chrono::seconds timeout) { - auto encodedData = buildPostData(data); - return doPost(url, headerLines, encodedData, timeout); + CurlEasyHandleWrapper curl; + auto encodedData = buildPostData(curl, data); + return doPostInternal(curl, url, headerLines, encodedData, timeout); } HTTPResponse HttpPost::doPost( @@ -69,23 +67,31 @@ HTTPResponse HttpPost::doPost( const std::vector headerLines, const std::string& data, std::chrono::seconds timeout) { - std::lock_guard lock(m_mutex); + CurlEasyHandleWrapper curl; + return doPostInternal(curl, url, headerLines, data, timeout); +} +HTTPResponse HttpPost::doPostInternal( + CurlEasyHandleWrapper& curl, + const std::string& url, + const std::vector headerLines, + const std::string& data, + std::chrono::seconds timeout) { HTTPResponse response; - if (!m_curl.reset() || !m_curl.setTransferTimeout(static_cast(timeout.count())) || !m_curl.setURL(url) || - !m_curl.setPostData(data) || !m_curl.setWriteCallback(staticWriteCallbackLocked, &response.body)) { + if (!curl.isValid() || !curl.setTransferTimeout(static_cast(timeout.count())) || !curl.setURL(url) || + !curl.setPostData(data) || !curl.setWriteCallback(staticWriteCallbackLocked, &response.body)) { return HTTPResponse(); } for (auto line : headerLines) { - if (!m_curl.addHTTPHeader(line)) { + if (!curl.addHTTPHeader(line)) { ACSDK_ERROR(LX("doPostFailed").d("reason", "unableToAddHttpHeader")); return HTTPResponse(); } } - auto curlHandle = m_curl.getCurlHandle(); + auto curlHandle = curl.getCurlHandle(); auto result = curl_easy_perform(curlHandle); if (result != CURLE_OK) { @@ -110,14 +116,16 @@ HTTPResponse HttpPost::doPost( } } -std::string HttpPost::buildPostData(const std::vector>& data) const { +std::string HttpPost::buildPostData( + CurlEasyHandleWrapper& curl, + const std::vector>& data) const { std::stringstream dataStream; for (auto ix = data.begin(); ix != data.end(); ix++) { if (ix != data.begin()) { dataStream << '&'; } - dataStream << m_curl.urlEncode(ix->first) << '=' << m_curl.urlEncode(ix->second); + dataStream << curl.urlEncode(ix->first) << '=' << curl.urlEncode(ix->second); } return dataStream.str(); diff --git a/AVSCommon/Utils/src/LibcurlUtils/LibcurlHTTP2Connection.cpp b/AVSCommon/Utils/src/LibcurlUtils/LibcurlHTTP2Connection.cpp index c47b0c8a..4e343a63 100644 --- a/AVSCommon/Utils/src/LibcurlUtils/LibcurlHTTP2Connection.cpp +++ b/AVSCommon/Utils/src/LibcurlUtils/LibcurlHTTP2Connection.cpp @@ -12,7 +12,6 @@ * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ - #include #include @@ -279,6 +278,33 @@ void LibcurlHTTP2Connection::disconnect() { } } +void LibcurlHTTP2Connection::addObserver(std::shared_ptr observer) { + std::lock_guard lock{m_observersMutex}; + if (observer != nullptr) { + m_observers.insert(observer); + } +} +void LibcurlHTTP2Connection::removeObserver(std::shared_ptr observer) { + std::lock_guard lock{m_observersMutex}; + if (observer != nullptr) { + m_observers.erase(observer); + } +} + +void LibcurlHTTP2Connection::notifyObserversOfGoawayReceived() { + std::vector> observers; + { + std::lock_guard lock{m_observersMutex}; + for (auto& observer : m_observers) { + observers.push_back(observer); + } + } + + for (auto& observer : observers) { + observer->onGoawayReceived(); + } +} + bool LibcurlHTTP2Connection::addStream(std::shared_ptr stream) { if (!stream) { ACSDK_ERROR(LX("addStream").d("failed", "null stream")); diff --git a/AVSCommon/Utils/src/LibcurlUtils/LibcurlUtils.cpp b/AVSCommon/Utils/src/LibcurlUtils/LibcurlUtils.cpp index 67c16d7a..b993b538 100644 --- a/AVSCommon/Utils/src/LibcurlUtils/LibcurlUtils.cpp +++ b/AVSCommon/Utils/src/LibcurlUtils/LibcurlUtils.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -16,6 +16,8 @@ #include #include +#include + #include "AVSCommon/Utils/Configuration/ConfigurationNode.h" #include "AVSCommon/Utils/LibcurlUtils/LibcurlUtils.h" #include "AVSCommon/Utils/Logger/Logger.h" diff --git a/AVSCommon/Utils/src/Logger/LogStringFormatter.cpp b/AVSCommon/Utils/src/Logger/LogStringFormatter.cpp index b851942e..e5aa495d 100644 --- a/AVSCommon/Utils/src/Logger/LogStringFormatter.cpp +++ b/AVSCommon/Utils/src/Logger/LogStringFormatter.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * 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. @@ -71,7 +71,7 @@ std::string LogStringFormatter::format( std::chrono::duration_cast(time.time_since_epoch()).count() % MILLISECONDS_PER_SECOND); char millisString[MILLIS_STRING_SIZE]; - if (std::snprintf(millisString, sizeof(millisString), MILLIS_FORMAT_STRING, timeMillisPart) < 0) { + if (snprintf(millisString, sizeof(millisString), MILLIS_FORMAT_STRING, timeMillisPart) < 0) { millisecondFailure = true; } diff --git a/AVSCommon/Utils/src/MacAddressString.cpp b/AVSCommon/Utils/src/MacAddressString.cpp index 7557f927..542781b8 100644 --- a/AVSCommon/Utils/src/MacAddressString.cpp +++ b/AVSCommon/Utils/src/MacAddressString.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -13,7 +13,6 @@ * permissions and limitations under the License. */ -#include #include #include diff --git a/AVSCommon/Utils/src/Metrics/DataPoint.cpp b/AVSCommon/Utils/src/Metrics/DataPoint.cpp index 56955ddb..9f3bc223 100644 --- a/AVSCommon/Utils/src/Metrics/DataPoint.cpp +++ b/AVSCommon/Utils/src/Metrics/DataPoint.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2019-2020 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. @@ -12,10 +12,11 @@ * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ -#include "AVSCommon/Utils/Metrics/DataPoint.h" #include +#include "AVSCommon/Utils/Metrics/DataPoint.h" + namespace alexaClientSDK { namespace avsCommon { namespace utils { diff --git a/AVSCommon/Utils/src/Network/InternetConnectionMonitor.cpp b/AVSCommon/Utils/src/Network/InternetConnectionMonitor.cpp index 1404bda6..6ddf2d22 100644 --- a/AVSCommon/Utils/src/Network/InternetConnectionMonitor.cpp +++ b/AVSCommon/Utils/src/Network/InternetConnectionMonitor.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018-2020 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. @@ -114,7 +114,7 @@ void InternetConnectionMonitor::startMonitoring() { std::chrono::seconds(0), m_period, Timer::PeriodType::RELATIVE, - Timer::FOREVER, + Timer::getForever(), std::bind(&InternetConnectionMonitor::testConnection, this)); } diff --git a/AVSCommon/Utils/src/RetryTimer.cpp b/AVSCommon/Utils/src/RetryTimer.cpp index 38236018..53248ffb 100644 --- a/AVSCommon/Utils/src/RetryTimer.cpp +++ b/AVSCommon/Utils/src/RetryTimer.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -13,6 +13,8 @@ * permissions and limitations under the License. */ +#include +#include #include #include "AVSCommon/Utils/RetryTimer.h" diff --git a/AVSCommon/Utils/src/TaskThread.cpp b/AVSCommon/Utils/src/TaskThread.cpp index 6e6302ed..b15cf08a 100644 --- a/AVSCommon/Utils/src/TaskThread.cpp +++ b/AVSCommon/Utils/src/TaskThread.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. diff --git a/AVSCommon/Utils/src/Timer.cpp b/AVSCommon/Utils/src/Timer.cpp index 020b8cbe..ee0c1718 100644 --- a/AVSCommon/Utils/src/Timer.cpp +++ b/AVSCommon/Utils/src/Timer.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -20,8 +20,6 @@ namespace avsCommon { namespace utils { namespace timing { -const std::string Timer::TAG = "Timer"; - Timer::Timer() : m_running(false), m_stopping(false) { } diff --git a/AVSCommon/Utils/src/UUIDGeneration.cpp b/AVSCommon/Utils/src/UUIDGeneration.cpp index 63d58ace..3d49a751 100644 --- a/AVSCommon/Utils/src/UUIDGeneration.cpp +++ b/AVSCommon/Utils/src/UUIDGeneration.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -91,10 +91,10 @@ void setSalt(const std::string& newSalt) { * @return A hex string of length @c numDigits. */ static const std::string generateHexWithReplacement( - std::independent_bits_engine& ibe, + std::independent_bits_engine& ibe, unsigned int numDigits, uint8_t replacementBits, - unsigned short numReplacementBits) { + uint16_t numReplacementBits) { if (numReplacementBits > MAX_NUM_REPLACEMENT_BITS) { ACSDK_ERROR(LX("generateHexWithReplacementFailed") .d("reason", "replacingMoreBitsThanProvided") @@ -111,10 +111,17 @@ static const std::string generateHexWithReplacement( return ""; } - // Makes assumption that 1 digit = 4 bits. - std::vector bytes(ceil(numDigits / 2.0)); - std::generate(bytes.begin(), bytes.end(), std::ref(ibe)); + const size_t arrayLen = (numDigits + 1) / 2; + std::vector shorts(arrayLen); + std::generate(shorts.begin(), shorts.end(), std::ref(ibe)); + + // Makes assumption that 1 digit = 4 bits. + std::vector bytes(arrayLen); + + for (size_t i = 0; i < arrayLen; i++) { + bytes[i] = static_cast(shorts[i]); + } // Replace the specified number of bits from the first byte. bytes.at(0) &= (0xff >> numReplacementBits); replacementBits &= (0xff << (MAX_NUM_REPLACEMENT_BITS - numReplacementBits)); @@ -141,13 +148,13 @@ static const std::string generateHexWithReplacement( * @return A hex string of length @c numDigits. */ static const std::string generateHex( - std::independent_bits_engine& ibe, + std::independent_bits_engine& ibe, unsigned int numDigits) { return generateHexWithReplacement(ibe, numDigits, 0, 0); } const std::string generateUUID() { - static std::independent_bits_engine ibe; + static std::independent_bits_engine ibe; std::unique_lock lock(g_mutex); static int consistentEntropyReports = 0; diff --git a/AVSCommon/Utils/test/AVSCommon/Utils/MediaPlayer/MockMediaPlayer.h b/AVSCommon/Utils/test/AVSCommon/Utils/MediaPlayer/MockMediaPlayer.h index 48671ee0..869024fa 100644 --- a/AVSCommon/Utils/test/AVSCommon/Utils/MediaPlayer/MockMediaPlayer.h +++ b/AVSCommon/Utils/test/AVSCommon/Utils/MediaPlayer/MockMediaPlayer.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -21,9 +21,10 @@ #include #include "AVSCommon/Utils/MediaPlayer/MediaPlayerInterface.h" +#include "AVSCommon/Utils/MediaPlayer/MediaPlayerObserverInterface.h" #include "AVSCommon/Utils/MediaPlayer/SourceConfig.h" #include "AVSCommon/Utils/RequiresShutdown.h" -#include +#include "AVSCommon/Utils/Timing/Stopwatch.h" namespace alexaClientSDK { namespace avsCommon { @@ -73,6 +74,7 @@ class MockMediaPlayer , public RequiresShutdown { public: using observer = avsCommon::utils::mediaPlayer::MediaPlayerObserverInterface; + using MediaPlayerState = avsCommon::utils::mediaPlayer::MediaPlayerState; /** * Creates an instance of the @c MockMediaPlayer. @@ -120,6 +122,9 @@ public: MOCK_METHOD1(stop, bool(SourceId)); MOCK_METHOD1(getOffset, std::chrono::milliseconds(SourceId)); MOCK_METHOD0(getNumBytesBuffered, uint64_t()); + MOCK_METHOD1( + getMediaPlayerState, + avsCommon::utils::Optional(SourceId)); /// @name RequiresShutdown overrides /// @{ @@ -193,6 +198,11 @@ public: */ std::chrono::milliseconds mockGetOffset(SourceId id); + /** + * This is a mock method which gives the @c sourceId and the offset through @c getOffset() + */ + avsCommon::utils::Optional mockGetState(SourceId id); + /** * Reset the timer used in the following waitXXX methods. * Used to test resuming @@ -315,7 +325,7 @@ private: SourceState( Source* source, const std::string& name, - std::function, SourceId)> notifyFunction); + std::function, SourceId, const MediaPlayerState&)> notifyFunction); /** * Destructor. If necessary, wakes and joins m_thread (cancelling any non-started notification) @@ -361,7 +371,7 @@ private: std::string m_name; /// The function to use to deliver this notification. - std::function, SourceId)> m_notifyFunction; + std::function, SourceId, const MediaPlayerState&)> m_notifyFunction; /// Used to serialize access to some data members. std::mutex m_mutex; diff --git a/AVSCommon/Utils/test/AVSCommon/Utils/Metrics/MockMetricRecorder.h b/AVSCommon/Utils/test/AVSCommon/Utils/Metrics/MockMetricRecorder.h new file mode 100644 index 00000000..9d44ccae --- /dev/null +++ b/AVSCommon/Utils/test/AVSCommon/Utils/Metrics/MockMetricRecorder.h @@ -0,0 +1,38 @@ +/* + * Copyright 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 ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_TEST_AVSCOMMON_UTILS_METRICS_MOCKMETRICRECORDER_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_TEST_AVSCOMMON_UTILS_METRICS_MOCKMETRICRECORDER_H_ + +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace metrics { +namespace test { + +class MockMetricRecorder : public avsCommon::utils::metrics::MetricRecorderInterface { +public: + MOCK_METHOD1(recordMetric, void(std::shared_ptr)); +}; + +} // namespace test +} // namespace metrics +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_TEST_AVSCOMMON_UTILS_METRICS_MOCKMETRICRECORDER_H_ diff --git a/AVSCommon/Utils/test/AVSCommon/Utils/OptionalTest.cpp b/AVSCommon/Utils/test/AVSCommon/Utils/OptionalTest.cpp index a87cac7b..b679745a 100644 --- a/AVSCommon/Utils/test/AVSCommon/Utils/OptionalTest.cpp +++ b/AVSCommon/Utils/test/AVSCommon/Utils/OptionalTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2019-2020 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. @@ -108,10 +108,10 @@ TEST(OptionalTest, test_createOptionalWithValue) { } TEST(OptionalTest, test_getValueOfOptionalWithValue) { - Optional dummy{Dummy{.m_name = "EXPECTED_NAME"}}; + Optional dummy{Dummy{"EXPECTED_NAME"}}; ASSERT_TRUE(dummy.hasValue()); - auto name = dummy.valueOr(Dummy{.m_name = "OTHER_NAME"}).m_name; + auto name = dummy.valueOr(Dummy{"OTHER_NAME"}).m_name; EXPECT_EQ(name, "EXPECTED_NAME"); name = dummy.value().m_name; @@ -122,7 +122,7 @@ TEST(OptionalTest, test_getValueOfEmptyOptional) { Optional dummy; ASSERT_FALSE(dummy.hasValue()); - auto name = dummy.valueOr(Dummy{.m_name = "OTHER_NAME"}).m_name; + auto name = dummy.valueOr(Dummy{"OTHER_NAME"}).m_name; EXPECT_EQ(name, "OTHER_NAME"); name = dummy.value().m_name; @@ -136,14 +136,14 @@ TEST(OptionalTest, test_functionWithEmptyOptionalReturn) { } TEST(OptionalTest, test_functionWithNonEmptyOptionalReturn) { - auto function = []() -> Optional { return Optional{Dummy{.m_name = "EXPECTED_NAME"}}; }; + auto function = []() -> Optional { return Optional{Dummy{"EXPECTED_NAME"}}; }; auto dummy = function(); ASSERT_TRUE(dummy.hasValue()); ASSERT_EQ(dummy.value().m_name, "EXPECTED_NAME"); } TEST(OptionalTest, test_copyOptionalWithValue) { - Optional dummy1{Dummy{.m_name = "EXPECTED_NAME"}}; + Optional dummy1{Dummy{"EXPECTED_NAME"}}; ASSERT_TRUE(dummy1.hasValue()); Optional dummy2{dummy1}; @@ -160,7 +160,7 @@ TEST(OptionalTest, test_copyEmptyOptional) { } TEST(OptionalTest, test_setNewValueForEmptyOptional) { - Dummy dummy{.m_name = "EXPECTED_NAME"}; + Dummy dummy{"EXPECTED_NAME"}; Optional optionalDummy; optionalDummy.set(dummy); @@ -169,10 +169,10 @@ TEST(OptionalTest, test_setNewValueForEmptyOptional) { } TEST(OptionalTest, test_setNewValueForNonEmptyOptional) { - Optional optionalDummy{Dummy{.m_name = "OLD_NAME"}}; + Optional optionalDummy{Dummy{"OLD_NAME"}}; ASSERT_TRUE(optionalDummy.hasValue()); - optionalDummy.set(Dummy{.m_name = "EXPECTED_NAME"}); + optionalDummy.set(Dummy{"EXPECTED_NAME"}); EXPECT_TRUE(optionalDummy.hasValue()); EXPECT_EQ(optionalDummy.value().m_name, "EXPECTED_NAME"); @@ -187,7 +187,7 @@ TEST(OptionalTest, test_resetEmptyOptional) { } TEST(OptionalTest, test_resetNonEmptyOptional) { - Optional optionalDummy{Dummy{.m_name = "OLD_NAME"}}; + Optional optionalDummy{Dummy{"OLD_NAME"}}; ASSERT_TRUE(optionalDummy.hasValue()); optionalDummy.reset(); diff --git a/AVSCommon/Utils/test/Common/Common.cpp b/AVSCommon/Utils/test/Common/Common.cpp index bd80bae8..a7178e46 100644 --- a/AVSCommon/Utils/test/Common/Common.cpp +++ b/AVSCommon/Utils/test/Common/Common.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018-2020 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. @@ -34,21 +34,22 @@ namespace utils { std::string createRandomAlphabetString(int stringSize) { // First, let's efficiently generate random numbers of the appropriate size. - std::vector vec(stringSize); - std::independent_bits_engine engine; + std::vector vec(stringSize); + std::independent_bits_engine engine; std::random_device rd; engine.seed( rd() + std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()) .count()); std::generate(begin(vec), end(vec), std::ref(engine)); + std::vector vec8(stringSize); // Now perform a modulo, bounding them within [a,z]. for (size_t i = 0; i < vec.size(); ++i) { - vec[i] = static_cast('a' + (vec[i] % 26)); + vec8[i] = static_cast('a' + (vec[i] % 26)); } /// Convert the data into a std::string. - char* dataBegin = reinterpret_cast(&vec[0]); + char* dataBegin = reinterpret_cast(&vec8[0]); return std::string(dataBegin, stringSize); } diff --git a/AVSCommon/Utils/test/Common/MockMediaPlayer.cpp b/AVSCommon/Utils/test/Common/MockMediaPlayer.cpp index e000aac1..6ad5c898 100644 --- a/AVSCommon/Utils/test/Common/MockMediaPlayer.cpp +++ b/AVSCommon/Utils/test/Common/MockMediaPlayer.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -45,6 +45,7 @@ std::shared_ptr> MockMediaPlayer::create() { ON_CALL(*result.get(), pause(_)).WillByDefault(Invoke(result.get(), &MockMediaPlayer::mockPause)); ON_CALL(*result.get(), resume(_)).WillByDefault(Invoke(result.get(), &MockMediaPlayer::mockResume)); ON_CALL(*result.get(), getOffset(_)).WillByDefault(Invoke(result.get(), &MockMediaPlayer::mockGetOffset)); + ON_CALL(*result.get(), getMediaPlayerState(_)).WillByDefault(Invoke(result.get(), &MockMediaPlayer::mockGetState)); return result; } @@ -220,6 +221,16 @@ std::chrono::milliseconds MockMediaPlayer::mockGetOffset(SourceId sourceId) { return source->stopwatch.getElapsed() + source->offset; } +Optional MockMediaPlayer::mockGetState(SourceId sourceId) { + auto offset = mockGetOffset(sourceId); + if (offset == MEDIA_PLAYER_INVALID_OFFSET) { + return Optional(); + } + auto state = MediaPlayerState(); + state.offset = offset; + return Optional(state); +} + void MockMediaPlayer::resetWaitTimer() { auto source = getCurrentSource(getCurrentSourceId()); if (!source) { @@ -305,7 +316,7 @@ MediaPlayerInterface::SourceId MockMediaPlayer::getLatestSourceId() { MockMediaPlayer::SourceState::SourceState( Source* source, const std::string& name, - std::function, SourceId)> notifyFunction) : + std::function, SourceId, const MediaPlayerState&)> notifyFunction) : m_source{source}, m_name{name}, m_notifyFunction{notifyFunction}, @@ -341,14 +352,15 @@ void MockMediaPlayer::SourceState::notify( std::unique_lock lock(m_mutex); auto timedOut = !m_wake.wait_for(lock, timeout, [this]() { return (m_stateReached || m_shutdown); }); lock.unlock(); + const MediaPlayerState state = {timeout}; for (const auto& observer : observers) { if (timedOut) { observer->onPlaybackError( - m_source->sourceId, ErrorType::MEDIA_ERROR_UNKNOWN, m_name + ": wait to notify timed out"); + m_source->sourceId, ErrorType::MEDIA_ERROR_UNKNOWN, m_name + ": wait to notify timed out", state); return; } - m_notifyFunction(observer, m_source->sourceId); + m_notifyFunction(observer, m_source->sourceId, state); } } @@ -370,38 +382,44 @@ void MockMediaPlayer::SourceState::resetStateReached() { static void notifyPlaybackStarted( std::shared_ptr observer, - MediaPlayerInterface::SourceId sourceId) { - observer->onPlaybackStarted(sourceId); + MediaPlayerInterface::SourceId sourceId, + const MediaPlayerState& state) { + observer->onPlaybackStarted(sourceId, state); } static void notifyPlaybackPaused( std::shared_ptr observer, - MediaPlayerInterface::SourceId sourceId) { - observer->onPlaybackPaused(sourceId); + MediaPlayerInterface::SourceId sourceId, + const MediaPlayerState& state) { + observer->onPlaybackPaused(sourceId, state); } static void notifyPlaybackResumed( std::shared_ptr observer, - MediaPlayerInterface::SourceId sourceId) { - observer->onPlaybackResumed(sourceId); + MediaPlayerInterface::SourceId sourceId, + const MediaPlayerState& state) { + observer->onPlaybackResumed(sourceId, state); } static void notifyPlaybackStopped( std::shared_ptr observer, - MediaPlayerInterface::SourceId sourceId) { - observer->onPlaybackStopped(sourceId); + MediaPlayerInterface::SourceId sourceId, + const MediaPlayerState& state) { + observer->onPlaybackStopped(sourceId, state); } static void notifyPlaybackFinished( std::shared_ptr observer, - MediaPlayerInterface::SourceId sourceId) { - observer->onPlaybackFinished(sourceId); + MediaPlayerInterface::SourceId sourceId, + const MediaPlayerState& state) { + observer->onPlaybackFinished(sourceId, state); } static void notifyPlaybackError( std::shared_ptr observer, - MediaPlayerInterface::SourceId sourceId) { - observer->onPlaybackError(sourceId, ErrorType::MEDIA_ERROR_INTERNAL_SERVER_ERROR, "mock error"); + MediaPlayerInterface::SourceId sourceId, + const MediaPlayerState& state) { + observer->onPlaybackError(sourceId, ErrorType::MEDIA_ERROR_INTERNAL_SERVER_ERROR, "mock error", state); } MockMediaPlayer::Source::Source(MockMediaPlayer* player, SourceId id) : diff --git a/AVSCommon/Utils/test/LoggerTest.cpp b/AVSCommon/Utils/test/LoggerTest.cpp index 8e430a0a..5b489bba 100644 --- a/AVSCommon/Utils/test/LoggerTest.cpp +++ b/AVSCommon/Utils/test/LoggerTest.cpp @@ -600,6 +600,36 @@ TEST_F(LoggerTest, test_sensitiveDataSuppressed) { #endif } +TEST_F(LoggerTest, test_obfuscatedDataIsMangled) { + ACSDK_GET_LOGGER_FUNCTION().setLevel(Level::INFO); + EXPECT_CALL(*(g_log.get()), emit(Level::INFO, _, _, _)).Times(3); + + // generic, not private labels should still show up in logs + ACSDK_INFO(LX("testing metadata obfuscation").obfuscatePrivateData(METADATA_KEY, TEST_MESSAGE_STRING)); + auto presentInOriginalForm = g_log->m_lastText.find(TEST_MESSAGE_STRING) != std::string::npos; + ASSERT_TRUE(presentInOriginalForm); + + // a private value should not appear, the blacklist word itself should + ACSDK_INFO(LX("testing metadata obfuscation") + .obfuscatePrivateData(METADATA_KEY, "{\"ESSID\"\\:\"I_NAME_MY_SSID_AFTER_MY_CREDIT_CARD_NUMBER\"}")); + auto lastLineAfterPrivateSubmission = g_log->m_lastText; + auto privateDataWasHidden = + lastLineAfterPrivateSubmission.find("I_NAME_MY_SSID_AFTER_MY_CREDIT_CARD_NUMBER") == std::string::npos; + ASSERT_TRUE(privateDataWasHidden); + auto privateLabelWasPresent = lastLineAfterPrivateSubmission.find("ESSID") != std::string::npos; + ASSERT_TRUE(privateLabelWasPresent); + + // key itself should be present + auto keyIsPresent = g_log->m_lastText.find(METADATA_KEY KEY_VALUE_SEPARATOR) != std::string::npos; + ASSERT_TRUE(keyIsPresent); + + // two of the same input should result in same output + ACSDK_INFO(LX("testing metadata obfuscation") + .obfuscatePrivateData(METADATA_KEY, "{\"ESSID\"\\:\"I_NAME_MY_SSID_AFTER_MY_CREDIT_CARD_NUMBER\"}")); + auto lastLineAfterSecondSubmission = g_log->m_lastText; + ASSERT_EQ(lastLineAfterSecondSubmission, lastLineAfterPrivateSubmission); +} + /** * Test observer mechanism in the MockModuleLogger. Expects that when the logLevel changes for the sink, the * callback of the MockModuleLogger is triggered. Also make sure any changes to sink's logLevel is ignored diff --git a/AVSCommon/Utils/test/MIMEParserTest.cpp b/AVSCommon/Utils/test/MIMEParserTest.cpp index 5fe51b2e..5d5228e6 100644 --- a/AVSCommon/Utils/test/MIMEParserTest.cpp +++ b/AVSCommon/Utils/test/MIMEParserTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018-2020 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. @@ -204,7 +204,8 @@ TEST_F(MIMEParserTest, test_encodingSanity) { /// characters array to store the output, size chosen to be more than /// the size calculated above - char buf[encodedPayload.size() * 2]; + std::vector bufferVec(encodedPayload.size() * 2); + char* buf = bufferVec.data(); size_t index{0}; size_t lastSize{bufferSize}; HTTP2SendDataResult result{0}; diff --git a/AVSCommon/Utils/test/MacAddressStringTest.cpp b/AVSCommon/Utils/test/MacAddressStringTest.cpp index 5089f2b4..4d028631 100644 --- a/AVSCommon/Utils/test/MacAddressStringTest.cpp +++ b/AVSCommon/Utils/test/MacAddressStringTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2019-2020 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. @@ -73,6 +73,15 @@ TEST_F(MacAddressStringTest, test_createWithValidMacAddress) { ASSERT_EQ(macAddressString->getTruncatedString(), truncatedMacAddress); } +/** + * Test @c MacAddressString create method with truncated Mac address is invalid. + */ +TEST_F(MacAddressStringTest, test_createWithTruncatedMacAddress) { + ASSERT_EQ(MacAddressString::create("XX:XX:XX:XX:cd:ef"), nullptr); + ASSERT_EQ(MacAddressString::create("XX:23:45:ab:cd:ef"), nullptr); + ASSERT_EQ(MacAddressString::create("01:23:45:ab:cd:XX"), nullptr); +} + } // namespace test } // namespace utils } // namespace avsCommon diff --git a/AVSCommon/Utils/test/SharedDataStreamTest.cpp b/AVSCommon/Utils/test/SharedDataStreamTest.cpp index 42d99ae6..3add8066 100644 --- a/AVSCommon/Utils/test/SharedDataStreamTest.cpp +++ b/AVSCommon/Utils/test/SharedDataStreamTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -75,7 +75,7 @@ struct MinimalTraits2 { }; /// An @c AtomicIndex type with the minimum functionality required by SDS. -class MinimalTraits::AtomicIndex : private InProcessSDS::AtomicIndex { +class MinimalTraits::AtomicIndex : public InProcessSDS::AtomicIndex { public: /// Conversion to @c Index. operator InProcessSDS::Index() { @@ -98,7 +98,7 @@ public: }; /// An @c AtomicBool type with the minimum functionality required by SDS. -class MinimalTraits::AtomicBool : private InProcessSDS::AtomicBool { +class MinimalTraits::AtomicBool : public InProcessSDS::AtomicBool { public: /// Conversion to bool. operator bool() { @@ -213,7 +213,7 @@ std::future Source::run( bool started = m_timer.start( period, timing::Timer::PeriodType::RELATIVE, - timing::Timer::FOREVER, + timing::Timer::getForever(), [this, writer, blockSizeWords, maxWords, wordSize] { std::vector block(blockSizeWords * writer->getWordSize()); size_t wordsToWrite = 0; @@ -294,7 +294,7 @@ std::future Sink::run( bool started = m_timer.start( period, timing::Timer::PeriodType::RELATIVE, - timing::Timer::FOREVER, + timing::Timer::getForever(), [this, reader, blockSizeWords, maxWords, wordSize] { std::vector block(blockSizeWords * wordSize); ssize_t nWords = reader->read(block.data(), block.size() / wordSize); @@ -614,8 +614,9 @@ TEST_F(SharedDataStreamTest, test_createReaderWhileWriting) { writerThread.join(); } +// Disabled due to ACSDK-3414 /// This tests @c SharedDataStream::Reader::read(). -TEST_F(SharedDataStreamTest, test_readerRead) { +TEST_F(SharedDataStreamTest, DISABLED_test_readerRead) { static const size_t WORDSIZE = 2; static const size_t WORDCOUNT = 2; static const size_t MAXREADERS = 2; @@ -1131,8 +1132,9 @@ TEST_F(SharedDataStreamTest, test_writerGetWordSize) { } } +// Disabled test due to ACSDK-3414 /// This tests a nonblockable, slow @c Writer streaming concurrently to two fast @c Readers (one of each type). -TEST_F(SharedDataStreamTest, testTimer_concurrencyNonblockableWriterDualReader) { +TEST_F(SharedDataStreamTest, DISABLED_testTimer_concurrencyNonblockableWriterDualReader) { static const size_t WORDSIZE = 2; static const size_t WRITE_FREQUENCY_HZ = 1000; static const size_t READ_FREQUENCY_HZ = 0; diff --git a/AVSCommon/Utils/test/StreamFunctionsTest.cpp b/AVSCommon/Utils/test/StreamFunctionsTest.cpp index bf9b8bca..c9aabd1f 100644 --- a/AVSCommon/Utils/test/StreamFunctionsTest.cpp +++ b/AVSCommon/Utils/test/StreamFunctionsTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -75,9 +75,9 @@ TEST_F(StreamFunctionsTest, test_dataContainsUnprintableChars) { * Verify that empty datasets work */ TEST_F(StreamFunctionsTest, test_emptyVector) { - const unsigned char empty[] = {}; - auto stream = stream::streamFromData(empty, sizeof(empty)); - ASSERT_TRUE(streamAndDataAreEqual(*stream, empty, sizeof(empty))); + std::vector empty(0); + auto stream = stream::streamFromData(empty.data(), 0); + ASSERT_TRUE(streamAndDataAreEqual(*stream, empty.data(), 0)); } /** diff --git a/AVSCommon/Utils/test/TaskThreadTest.cpp b/AVSCommon/Utils/test/TaskThreadTest.cpp index 861ef101..6b4f3851 100644 --- a/AVSCommon/Utils/test/TaskThreadTest.cpp +++ b/AVSCommon/Utils/test/TaskThreadTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -26,8 +26,7 @@ namespace threading { namespace test { /// Timeout used while waiting for synchronization events. -/// We picked 2s to avoid failure in slower systems (e.g.: valgrind and emulators). -const std::chrono::milliseconds WAIT_TIMEOUT{100}; +const std::chrono::milliseconds MY_WAIT_TIMEOUT{100}; using namespace logger; @@ -96,7 +95,7 @@ TEST(TaskThreadTest, test_startNewJob) { TaskThread taskThread; EXPECT_TRUE(taskThread.start(increment)); - ASSERT_TRUE(waitEvent.wait(WAIT_TIMEOUT)); + ASSERT_TRUE(waitEvent.wait(MY_WAIT_TIMEOUT)); EXPECT_TRUE(taskThread.start(decrement)); } @@ -104,8 +103,8 @@ TEST(TaskThreadTest, test_startNewJob) { TEST(TaskThreadTest, test_startFailDueTooManyThreads) { WaitEvent waitEnqueue, waitStart; auto simpleJob = [&waitEnqueue, &waitStart] { - waitStart.wakeUp(); // Job has started. - waitEnqueue.wait(WAIT_TIMEOUT); // Wait till job should finish. + waitStart.wakeUp(); // Job has started. + waitEnqueue.wait(MY_WAIT_TIMEOUT); // Wait till job should finish. return false; }; @@ -113,7 +112,7 @@ TEST(TaskThreadTest, test_startFailDueTooManyThreads) { EXPECT_TRUE(taskThread.start(simpleJob)); // Wait until first job has started. - waitStart.wait(WAIT_TIMEOUT); + waitStart.wait(MY_WAIT_TIMEOUT); EXPECT_TRUE(taskThread.start([] { return false; })); // This should fail since the task thread is starting. @@ -140,10 +139,10 @@ TEST(TaskThreadTest, DISABLED_test_moniker) { TaskThread taskThread; EXPECT_TRUE(taskThread.start(getMoniker)); - waitGetMoniker.wait(WAIT_TIMEOUT); + waitGetMoniker.wait(MY_WAIT_TIMEOUT); EXPECT_TRUE(taskThread.start(validateMoniker)); - waitValidateMoniker.wait(WAIT_TIMEOUT); + waitValidateMoniker.wait(MY_WAIT_TIMEOUT); } /// Test that threads from different @c TaskThreads will have different monikers. @@ -164,11 +163,11 @@ TEST(TaskThreadTest, test_monikerDifferentObjects) { TaskThread taskThread1; EXPECT_TRUE(taskThread1.start(getMoniker)); - waitGetMoniker.wait(WAIT_TIMEOUT); + waitGetMoniker.wait(MY_WAIT_TIMEOUT); TaskThread taskThread2; EXPECT_TRUE(taskThread2.start(validateMoniker)); - waitValidateMoniker.wait(WAIT_TIMEOUT); + waitValidateMoniker.wait(MY_WAIT_TIMEOUT); } } // namespace test diff --git a/AVSGatewayManager/include/AVSGatewayManager/AVSGatewayManager.h b/AVSGatewayManager/include/AVSGatewayManager/AVSGatewayManager.h index 5c4f4968..6aa40f52 100644 --- a/AVSGatewayManager/include/AVSGatewayManager/AVSGatewayManager.h +++ b/AVSGatewayManager/include/AVSGatewayManager/AVSGatewayManager.h @@ -1,5 +1,5 @@ /* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2019-2020 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. @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -46,11 +47,6 @@ class AVSGatewayManager , public avsCommon::sdkInterfaces::PostConnectOperationProviderInterface , public registrationManager::CustomerDataHandler { public: - /** - * Creates an instance of the @c AVSGatewayManager - * @return - */ - /** * Creates an instance of the @c AVSGatewayManager. * @@ -69,6 +65,8 @@ public: bool setAVSGatewayAssigner( std::shared_ptr avsGatewayAssigner) override; bool setGatewayURL(const std::string& avsGatewayURL) override; + void addObserver(std::shared_ptr observer) override; + void removeObserver(std::shared_ptr observer) override; /// @} /// @name PostConnectOperationProviderInterface Functions @@ -123,7 +121,7 @@ private: /// The AVS Gateway Assigner. std::shared_ptr m_avsGatewayAssigner; - /// The mutex to synchronize access to @c PostConnectVerifyGatewaySender. + /// The mutex to synchronize access to members. std::mutex m_mutex; /// The current @c PostConnectVerifyGateway sender used to send the verify gateway event. @@ -131,6 +129,9 @@ private: /// The current AVS Gateway Verification state. GatewayVerifyState m_currentState; + + /// The set of @c AVSGatewayObservers. Access is synchronized using m_mutex. + std::unordered_set> m_observers; }; } // namespace avsGatewayManager diff --git a/AVSGatewayManager/src/AVSGatewayManager.cpp b/AVSGatewayManager/src/AVSGatewayManager.cpp index 3e02d9e8..d52a0012 100644 --- a/AVSGatewayManager/src/AVSGatewayManager.cpp +++ b/AVSGatewayManager/src/AVSGatewayManager.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2019-2020 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. @@ -133,11 +133,20 @@ bool AVSGatewayManager::setGatewayURL(const std::string& avsGatewayURL) { }; if (avsGatewayURL != m_currentState.avsGatewayURL) { + std::unordered_set> observersCopy; { std::lock_guard lock{m_mutex}; m_currentState = GatewayVerifyState{avsGatewayURL, false}; saveStateLocked(); + observersCopy = m_observers; } + + if (!observersCopy.empty()) { + for (auto& observer : observersCopy) { + observer->onAVSGatewayChanged(avsGatewayURL); + } + } + if (m_avsGatewayAssigner) { m_avsGatewayAssigner->setAVSGateway(avsGatewayURL); } else { @@ -170,6 +179,38 @@ bool AVSGatewayManager::saveStateLocked() { return true; } +void AVSGatewayManager::addObserver(std::shared_ptr observer) { + ACSDK_DEBUG5(LX(__func__)); + if (!observer) { + ACSDK_ERROR(LX("addObserverFailed").d("reason", "nullObserver")); + return; + } + + { + std::lock_guard lock{m_mutex}; + if (!m_observers.insert(observer).second) { + ACSDK_ERROR(LX("addObserverFailed").d("reason", "observer already added!")); + return; + } + } +} + +void AVSGatewayManager::removeObserver(std::shared_ptr observer) { + ACSDK_DEBUG5(LX(__func__)); + if (!observer) { + ACSDK_ERROR(LX("addObserverFailed").d("reason", "nullObserver")); + return; + } + + { + std::lock_guard lock{m_mutex}; + if (!m_observers.erase(observer)) { + ACSDK_ERROR(LX("removeObserverFailed").d("reason", "observer not found")); + return; + } + } +} + void AVSGatewayManager::clearData() { ACSDK_DEBUG5(LX(__func__)); m_avsGatewayStorage->clear(); diff --git a/AVSGatewayManager/src/CMakeLists.txt b/AVSGatewayManager/src/CMakeLists.txt index c2c8e6ab..458ab686 100644 --- a/AVSGatewayManager/src/CMakeLists.txt +++ b/AVSGatewayManager/src/CMakeLists.txt @@ -5,8 +5,12 @@ add_library(AVSGatewayManager SHARED PostConnectVerifyGatewaySender.cpp) target_include_directories(AVSGatewayManager PUBLIC - "${AVSCommon_SOURCE_DIR}" - "${AVSGatewayManager_SOURCE_DIR}/include") + "$" + "$" + "$" + "$" + "$" + "$") target_link_libraries(AVSGatewayManager AVSCommon RegistrationManager) diff --git a/AVSGatewayManager/src/Storage/AVSGatewayManagerStorage.cpp b/AVSGatewayManager/src/Storage/AVSGatewayManagerStorage.cpp index 9615fed3..cde37182 100644 --- a/AVSGatewayManager/src/Storage/AVSGatewayManagerStorage.cpp +++ b/AVSGatewayManager/src/Storage/AVSGatewayManagerStorage.cpp @@ -17,7 +17,7 @@ #include #include -#include +#include namespace alexaClientSDK { namespace avsGatewayManager { diff --git a/AVSGatewayManager/test/AVSGatewayManagerTest.cpp b/AVSGatewayManager/test/AVSGatewayManagerTest.cpp index dd292d6b..94b80c72 100644 --- a/AVSGatewayManager/test/AVSGatewayManagerTest.cpp +++ b/AVSGatewayManager/test/AVSGatewayManagerTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2019-2020 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. @@ -20,6 +20,7 @@ #include #include +#include #include #include @@ -99,7 +100,10 @@ protected: std::shared_ptr m_customerDataManager; /// The mock @c AVSGatewayAssigner. - std::shared_ptr m_mockAvsGatewayAssigner; + std::shared_ptr m_mockAVSGatewayAssigner; + + /// The mock @c AVSGatewayObserver. + std::shared_ptr m_mockAVSGatewayObserver; /// The mock @c AVSGatewayManagerStorageInterface. std::shared_ptr m_mockAVSGatewayManagerStorage; @@ -110,7 +114,8 @@ protected: void AVSGatewayManagerTest::SetUp() { m_mockAVSGatewayManagerStorage = std::make_shared>(); - m_mockAvsGatewayAssigner = std::make_shared>(); + m_mockAVSGatewayAssigner = std::make_shared>(); + m_mockAVSGatewayObserver = std::make_shared>(); m_customerDataManager = std::make_shared(); } @@ -150,10 +155,10 @@ TEST_F(AVSGatewayManagerTest, test_defaultAVSGatewayFromConfigFile) { initializeConfigRoot(AVS_GATEWAY_MANAGER_JSON); createAVSGatewayManager(); - EXPECT_CALL(*m_mockAvsGatewayAssigner, setAVSGateway(_)).WillOnce(Invoke([](const std::string& gatewayURL) { + EXPECT_CALL(*m_mockAVSGatewayAssigner, setAVSGateway(_)).WillOnce(Invoke([](const std::string& gatewayURL) { ASSERT_EQ(gatewayURL, TEST_AVS_GATEWAY); })); - m_avsGatewayManager->setAVSGatewayAssigner(m_mockAvsGatewayAssigner); + m_avsGatewayManager->setAVSGatewayAssigner(m_mockAVSGatewayAssigner); } /** @@ -163,10 +168,10 @@ TEST_F(AVSGatewayManagerTest, test_defaultAVSGatewayFromConfigFileWithNoGateway) initializeConfigRoot(AVS_GATEWAY_MANAGER_JSON_NO_GATEWAY); createAVSGatewayManager(); - EXPECT_CALL(*m_mockAvsGatewayAssigner, setAVSGateway(_)).WillOnce(Invoke([](const std::string& gatewayURL) { + EXPECT_CALL(*m_mockAVSGatewayAssigner, setAVSGateway(_)).WillOnce(Invoke([](const std::string& gatewayURL) { ASSERT_EQ(gatewayURL, DEFAULT_AVS_GATEWAY); })); - m_avsGatewayManager->setAVSGatewayAssigner(m_mockAvsGatewayAssigner); + m_avsGatewayManager->setAVSGatewayAssigner(m_mockAVSGatewayAssigner); } /** @@ -176,10 +181,10 @@ TEST_F(AVSGatewayManagerTest, test_defaultAVSGatewayFromConfigFileWithEmptyGatew initializeConfigRoot(AVS_GATEWAY_MANAGER_JSON_EMPTY_GATEWAY); createAVSGatewayManager(); - EXPECT_CALL(*m_mockAvsGatewayAssigner, setAVSGateway(_)).WillOnce(Invoke([](const std::string& gatewayURL) { + EXPECT_CALL(*m_mockAVSGatewayAssigner, setAVSGateway(_)).WillOnce(Invoke([](const std::string& gatewayURL) { ASSERT_EQ(gatewayURL, DEFAULT_AVS_GATEWAY); })); - m_avsGatewayManager->setAVSGatewayAssigner(m_mockAvsGatewayAssigner); + m_avsGatewayManager->setAVSGatewayAssigner(m_mockAVSGatewayAssigner); } /** @@ -199,40 +204,93 @@ TEST_F(AVSGatewayManagerTest, test_avsGatewayFromStorage) { ASSERT_NE(m_avsGatewayManager, nullptr); - EXPECT_CALL(*m_mockAvsGatewayAssigner, setAVSGateway(_)).WillOnce(Invoke([](const std::string& gatewayURL) { + EXPECT_CALL(*m_mockAVSGatewayAssigner, setAVSGateway(_)).WillOnce(Invoke([](const std::string& gatewayURL) { ASSERT_EQ(gatewayURL, STORED_AVS_GATEWAY); })); - m_avsGatewayManager->setAVSGatewayAssigner(m_mockAvsGatewayAssigner); + m_avsGatewayManager->setAVSGatewayAssigner(m_mockAVSGatewayAssigner); } /** - * Test if a call to setGatewayURL() with a new URL calls the setAVSGateway() method on @c AVSGatewayAssigner and calls - * storeState() on @c AVSGatewayManagerStorage. + * Test if a call to setGatewayURL() with a new URL calls + * - calls the setAVSGateway() method on @c AVSGatewayAssigner + * - calls the storeState() method on @c AVSGatewayManagerStorage + * - calls the onGatewayChanged() method on @c AVSGatewayObserver. */ TEST_F(AVSGatewayManagerTest, test_setAVSGatewayURLWithNewURL) { initializeConfigRoot(AVS_GATEWAY_MANAGER_JSON); createAVSGatewayManager(); - EXPECT_CALL(*m_mockAvsGatewayAssigner, setAVSGateway(_)).Times(2); + EXPECT_CALL(*m_mockAVSGatewayAssigner, setAVSGateway(_)).Times(2); EXPECT_CALL(*m_mockAVSGatewayManagerStorage, saveState(_)).Times(1); + EXPECT_CALL(*m_mockAVSGatewayObserver, onAVSGatewayChanged(_)).Times(1); - m_avsGatewayManager->setAVSGatewayAssigner(m_mockAvsGatewayAssigner); + m_avsGatewayManager->addObserver(m_mockAVSGatewayObserver); + m_avsGatewayManager->setAVSGatewayAssigner(m_mockAVSGatewayAssigner); m_avsGatewayManager->setGatewayURL(DEFAULT_AVS_GATEWAY); } /** - * Test if a call to setGatewayURL with the same URL does not trigger calls to @c AVSGatewayAssigner and @c - * AVSGatewayManagerStorage. + * Test if a call to setGatewayURL with the same URL does not trigger calls to @c AVSGatewayAssigner, + * @c AVSGatewayObserver and @c AVSGatewayManagerStorage. */ TEST_F(AVSGatewayManagerTest, test_setAVSGatewayURLWithSameURL) { initializeConfigRoot(AVS_GATEWAY_MANAGER_JSON); createAVSGatewayManager(); - EXPECT_CALL(*m_mockAvsGatewayAssigner, setAVSGateway(_)).Times(1); + EXPECT_CALL(*m_mockAVSGatewayAssigner, setAVSGateway(_)).Times(1); EXPECT_CALL(*m_mockAVSGatewayManagerStorage, saveState(_)).Times(0); - m_avsGatewayManager->setAVSGatewayAssigner(m_mockAvsGatewayAssigner); + EXPECT_CALL(*m_mockAVSGatewayObserver, onAVSGatewayChanged(_)).Times(0); + m_avsGatewayManager->addObserver(m_mockAVSGatewayObserver); + m_avsGatewayManager->setAVSGatewayAssigner(m_mockAVSGatewayAssigner); m_avsGatewayManager->setGatewayURL(TEST_AVS_GATEWAY); } +/** + * Test if AVSGatewayManager gracefully handles adding a nullObserver. + */ +TEST_F(AVSGatewayManagerTest, test_addNullObserver) { + initializeConfigRoot(AVS_GATEWAY_MANAGER_JSON); + createAVSGatewayManager(); + m_avsGatewayManager->addObserver(nullptr); +} + +/** + * Test if AVSGatewayManager gracefully handles removing a nullObserver. + */ +TEST_F(AVSGatewayManagerTest, test_removeNullObserver) { + initializeConfigRoot(AVS_GATEWAY_MANAGER_JSON); + createAVSGatewayManager(); + m_avsGatewayManager->removeObserver(nullptr); +} + +/** + * Test removing a previously added observer. + */ +TEST_F(AVSGatewayManagerTest, test_removeAddedObserver) { + initializeConfigRoot(AVS_GATEWAY_MANAGER_JSON); + createAVSGatewayManager(); + + EXPECT_CALL(*m_mockAVSGatewayObserver, onAVSGatewayChanged(_)).Times(1); + m_avsGatewayManager->addObserver(m_mockAVSGatewayObserver); + m_avsGatewayManager->setGatewayURL(DEFAULT_AVS_GATEWAY); + + EXPECT_CALL(*m_mockAVSGatewayObserver, onAVSGatewayChanged(_)).Times(0); + m_avsGatewayManager->removeObserver(m_mockAVSGatewayObserver); + m_avsGatewayManager->setGatewayURL(TEST_AVS_GATEWAY); +} + +/** + * Test removing an observer that is not previously added is handled gracefully. + */ +TEST_F(AVSGatewayManagerTest, test_removeObserverNotAddedPreviously) { + initializeConfigRoot(AVS_GATEWAY_MANAGER_JSON); + createAVSGatewayManager(); + + EXPECT_CALL(*m_mockAVSGatewayObserver, onAVSGatewayChanged(_)).Times(0); + m_avsGatewayManager->setGatewayURL(DEFAULT_AVS_GATEWAY); + + m_avsGatewayManager->removeObserver(m_mockAVSGatewayObserver); +} + /** * Test if a call to clearData() invokes call to clear() on the storage. */ diff --git a/AVSGatewayManager/test/PostConnectVerifyGatewaySenderTest.cpp b/AVSGatewayManager/test/PostConnectVerifyGatewaySenderTest.cpp index d116a793..37bf4257 100644 --- a/AVSGatewayManager/test/PostConnectVerifyGatewaySenderTest.cpp +++ b/AVSGatewayManager/test/PostConnectVerifyGatewaySenderTest.cpp @@ -21,7 +21,7 @@ #include #include -#include +#include #include namespace alexaClientSDK { diff --git a/ApplicationUtilities/AndroidUtilities/include/AndroidUtilities/AndroidLogger.h b/ApplicationUtilities/AndroidUtilities/include/AndroidUtilities/AndroidLogger.h index 0167f92c..2bd1dba8 100644 --- a/ApplicationUtilities/AndroidUtilities/include/AndroidUtilities/AndroidLogger.h +++ b/ApplicationUtilities/AndroidUtilities/include/AndroidUtilities/AndroidLogger.h @@ -1,5 +1,5 @@ /* - * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * 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. @@ -19,6 +19,8 @@ #include #include +#include + namespace alexaClientSDK { namespace applicationUtilities { namespace androidUtilities { @@ -38,6 +40,17 @@ public: * @param level The lowest severity level of logs to be emitted by this Logger. */ AndroidLogger(alexaClientSDK::avsCommon::utils::logger::Level level); + + /** + * Constructor. + * + * @param tag to be includes as a prefix for every log message. + * @param level The lowest severity level of logs to be emitted by this Logger. + */ + AndroidLogger(const std::string& tag, alexaClientSDK::avsCommon::utils::logger::Level level); + +private: + std::string m_tag; }; } // namespace androidUtilities diff --git a/ApplicationUtilities/AndroidUtilities/src/AndroidLogger.cpp b/ApplicationUtilities/AndroidUtilities/src/AndroidLogger.cpp index 618c2695..6cfc3559 100644 --- a/ApplicationUtilities/AndroidUtilities/src/AndroidLogger.cpp +++ b/ApplicationUtilities/AndroidUtilities/src/AndroidLogger.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * 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. @@ -60,7 +60,10 @@ static android_LogPriority convertToAndroidLevel(Level level) { } } -AndroidLogger::AndroidLogger(Level level) : Logger{level} { +AndroidLogger::AndroidLogger(Level level) : Logger{level}, m_tag{TAG} { +} + +AndroidLogger::AndroidLogger(const std::string& tag, Level level) : Logger{level}, m_tag{tag} { } void AndroidLogger::emit( @@ -69,7 +72,7 @@ void AndroidLogger::emit( const char* threadMoniker, const char* text) { __android_log_print( - convertToAndroidLevel(level), TAG, "[%s] %c %s", threadMoniker, convertLevelToChar(level), text); + convertToAndroidLevel(level), m_tag.c_str(), "[%s] %c %s", threadMoniker, convertLevelToChar(level), text); } } // namespace androidUtilities diff --git a/ApplicationUtilities/DefaultClient/include/DefaultClient/DefaultClient.h b/ApplicationUtilities/DefaultClient/include/DefaultClient/DefaultClient.h index 7a14fb10..1d9eb50a 100644 --- a/ApplicationUtilities/DefaultClient/include/DefaultClient/DefaultClient.h +++ b/ApplicationUtilities/DefaultClient/include/DefaultClient/DefaultClient.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -41,6 +41,7 @@ #include #include #include +#include #include #include #include @@ -59,7 +60,7 @@ #include #include #include -#include +#include #include #include #include @@ -142,7 +143,6 @@ public: * @param bluetoothMediaPlayer The media player to play bluetooth content. * @param ringtoneMediaPlayer The media player to play Comms ringtones. * @param systemSoundMediaPlayer The media player to play system sounds. - * @param metricSinkInterface The metric sink interface to be moved into MetricRecorder * @param speakSpeaker The speaker to control volume of Alexa speech. * @param audioSpeaker The speaker to control volume of Alexa audio content. * @param alertsSpeaker The speaker to control volume of alerts. @@ -174,13 +174,16 @@ public: * @param contextManager The @c ContextManager which will provide the context for various components. * @param transportFactory The object passed in here will be used whenever a new transport object * for AVS communication is needed. + * @param avsGatewayManager The @c AVSGatewayManager instance used to create the ApiGateway CA. * @param localeAssetsManager The device locale assets manager. + * @param enabledConnectionRules The set of @c BluetoothDeviceConnectionRuleInterface instances used to + * create the Bluetooth CA. * @param systemTimezone Optional object used to set the system timezone. * @param firmwareVersion The firmware version to report to @c AVS or @c INVALID_FIRMWARE_VERSION. * @param sendSoftwareInfoOnConnected Whether to send SoftwareInfo upon connecting to @c AVS. * @param softwareInfoSenderObserver Object to receive notifications about sending SoftwareInfo. * @param bluetoothDeviceManager The @c BluetoothDeviceManager instance used to create the Bluetooth CA. - * @param avsGatewayManager The @c AVSGatewayManager instance used to create the ApiGateway CA. + * @param metricRecorder The metric recorder object used to capture metrics. * @param powerResourceManager Object to manage power resource. * @return A @c std::unique_ptr to a DefaultClient if all went well or @c nullptr otherwise. * @@ -201,7 +204,6 @@ public: std::shared_ptr bluetoothMediaPlayer, std::shared_ptr ringtoneMediaPlayer, std::shared_ptr systemSoundMediaPlayer, - std::unique_ptr metricSinkInterface, std::shared_ptr speakSpeaker, std::shared_ptr audioSpeaker, std::shared_ptr alertsSpeaker, @@ -242,7 +244,11 @@ public: std::shared_ptr capabilitiesDelegate, std::shared_ptr contextManager, std::shared_ptr transportFactory, + std::shared_ptr avsGatewayManager, std::shared_ptr localeAssetsManager, + std::unordered_set> + enabledConnectionRules = std::unordered_set< + std::shared_ptr>(), std::shared_ptr systemTimezone = nullptr, avsCommon::sdkInterfaces::softwareInfo::FirmwareVersion firmwareVersion = avsCommon::sdkInterfaces::softwareInfo::INVALID_FIRMWARE_VERSION, @@ -251,7 +257,7 @@ public: nullptr, std::unique_ptr bluetoothDeviceManager = nullptr, - std::shared_ptr avsGatewayManager = nullptr, + std::shared_ptr metricRecorder = nullptr, std::shared_ptr powerResourceManager = nullptr); /** @@ -491,6 +497,20 @@ public: */ std::shared_ptr getSpeakerManager(); + /** + * Adds a SpeechSynthesizerObserver to be alerted on SpeechSynthesizer state changes + * @param observer The observer to be notified upon the state change + */ + void addSpeechSynthesizerObserver( + std::shared_ptr observer); + + /** + * Removes a SpeechSynthesizerObserver from being alerted on SpeechSynthesizer state changes + * @param observer The observer to be removed + */ + void removeSpeechSynthesizerObserver( + std::shared_ptr observer); + /** * Get a shared_ptr to the RegistrationManager. * @@ -622,6 +642,23 @@ public: */ void stopCommsCall(); + /** + * Check if the Comms call is muted. + * + * @return Whether the comms call is muted. + */ + bool isCommsCallMuted(); + + /** + * Mute comms Call. + */ + void muteCommsCall(); + + /** + * Unmute Comms call. + */ + void unmuteCommsCall(); + /** * Destructor. */ @@ -653,7 +690,6 @@ private: * @param bluetoothMediaPlayer The media player to play bluetooth content. * @param ringtoneMediaPlayer The media player to play Comms ringtones. * @param systemSoundPlayer The media player to play system sounds. - * @param metricSinkInterface The metric sink interface to be moved into MetricRecorder * @param speakSpeaker The speaker to control volume of Alexa speech. * @param audioSpeaker The speaker to control volume of Alexa audio content. * @param alertsSpeaker The speaker to control volume of alerts. @@ -680,13 +716,16 @@ private: * @param contextManager The @c ContextManager which will provide the context for various components. * @param transportFactory The object passed in here will be used whenever a new transport object * for AVS communication is needed. + * @param avsGatewayManager The @c AVSGatewayManager instance used to create the ApiGateway CA. * @param localeAssetsManager The device locale assets manager. + * @param enabledConnectionRules The set of @c BluetoothDeviceConnectionRuleInterface instances used to + * create the Bluetooth CA. * @param systemTimezone Optional object used to set the system timezone. * @param firmwareVersion The firmware version to report to @c AVS or @c INVALID_FIRMWARE_VERSION. * @param sendSoftwareInfoOnConnected Whether to send SoftwareInfo upon connecting to @c AVS. * @param softwareInfoSenderObserver Object to receive notifications about sending SoftwareInfo. * @param bluetoothDeviceManager The @c BluetoothDeviceManager instance used to create the Bluetooth CA. - * @param avsGatewayManager The @c AVSGatewayManager instance used to create the ApiGateway CA. + * @param metricRecorder The optional metric recorder object used to capture metrics. * @param powerResourceManager Object to manage power resource. * @return Whether the SDK was initialized properly. */ @@ -704,7 +743,6 @@ private: std::shared_ptr bluetoothMediaPlayer, std::shared_ptr ringtoneMediaPlayer, std::shared_ptr systemSoundMediaPlayer, - std::unique_ptr metricSinkInterface, std::shared_ptr speakSpeaker, std::shared_ptr audioSpeaker, std::shared_ptr alertsSpeaker, @@ -745,13 +783,16 @@ private: std::shared_ptr capabilitiesDelegate, std::shared_ptr contextManager, std::shared_ptr transportFactory, + std::shared_ptr avsGatewayManager, std::shared_ptr localeAssetsManager, + std::unordered_set> + enabledConnectionRules, std::shared_ptr systemTimezone, avsCommon::sdkInterfaces::softwareInfo::FirmwareVersion firmwareVersion, bool sendSoftwareInfoOnConnected, std::shared_ptr softwareInfoSenderObserver, std::unique_ptr bluetoothDeviceManager, - std::shared_ptr avsGatewayManager, + std::shared_ptr metricRecorder, std::shared_ptr powerResourceManager); /// The directive sequencer. diff --git a/ApplicationUtilities/DefaultClient/include/DefaultClient/DeviceSettingsManagerBuilder.h b/ApplicationUtilities/DefaultClient/include/DefaultClient/DeviceSettingsManagerBuilder.h index af410910..8c0ff10f 100644 --- a/ApplicationUtilities/DefaultClient/include/DefaultClient/DeviceSettingsManagerBuilder.h +++ b/ApplicationUtilities/DefaultClient/include/DefaultClient/DeviceSettingsManagerBuilder.h @@ -1,5 +1,5 @@ /* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2019-2020 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. @@ -12,6 +12,7 @@ * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ + #ifndef ALEXA_CLIENT_SDK_APPLICATIONUTILITIES_DEFAULTCLIENT_INCLUDE_DEFAULTCLIENT_DEVICESETTINGSMANAGERBUILDER_H_ #define ALEXA_CLIENT_SDK_APPLICATIONUTILITIES_DEFAULTCLIENT_INCLUDE_DEFAULTCLIENT_DEVICESETTINGSMANAGERBUILDER_H_ @@ -118,6 +119,13 @@ public: DeviceSettingsManagerBuilder& withLocaleAndWakeWordsSettings( std::shared_ptr localeAssetsManager); + /** + * Configures network info setting. + * + * @return This builder to allow nested calls. + */ + DeviceSettingsManagerBuilder& withNetworkInfoSetting(); + /** * Gets the setting for the given @c index. * diff --git a/ApplicationUtilities/DefaultClient/src/CMakeLists.txt b/ApplicationUtilities/DefaultClient/src/CMakeLists.txt index 1cc7c699..9f639038 100644 --- a/ApplicationUtilities/DefaultClient/src/CMakeLists.txt +++ b/ApplicationUtilities/DefaultClient/src/CMakeLists.txt @@ -33,19 +33,20 @@ target_link_libraries(DefaultClient Bluetooth Captions ContextManager + DeviceSettings DoNotDisturbCA Endpoints Equalizer ExternalMediaPlayer InteractionModel + InterruptModel Notifications PlaybackController RegistrationManager SpeakerManager SpeechSynthesizer - DeviceSettings - TemplateRuntime SystemSoundPlayer + TemplateRuntime ) if (COMMS) diff --git a/ApplicationUtilities/DefaultClient/src/DefaultClient.cpp b/ApplicationUtilities/DefaultClient/src/DefaultClient.cpp index 1b29073a..c5c3f8b1 100644 --- a/ApplicationUtilities/DefaultClient/src/DefaultClient.cpp +++ b/ApplicationUtilities/DefaultClient/src/DefaultClient.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -18,12 +18,8 @@ #include #include #include -#include #include - -#ifdef ACSDK_ENABLE_METRICS_RECORDING -#include -#endif +#include #include @@ -68,12 +64,12 @@ #include #include #include +#include #ifdef BLUETOOTH_BLUEZ #include -#include +#include #endif - #include "DefaultClient/DefaultClient.h" #include "DefaultClient/DeviceSettingsManagerBuilder.h" @@ -85,6 +81,15 @@ using namespace avsCommon::sdkInterfaces; using namespace avsCommon::sdkInterfaces::endpoints; using namespace alexaClientSDK::endpoints; +/// Key for audio channel array configurations in configuration node. +static const std::string AUDIO_CHANNEL_CONFIG_KEY = "audioChannels"; + +/// Key for visual channel array configurations in configuration node. +static const std::string VISUAL_CHANNEL_CONFIG_KEY = "visualChannels"; + +/// Key for the interrupt model configuration +static const std::string INTERRUPT_MODEL_CONFIG_KEY = "interruptModel"; + /// String to identify log entries originating from this file. static const std::string TAG("DefaultClient"); @@ -110,7 +115,6 @@ std::unique_ptr DefaultClient::create( std::shared_ptr bluetoothMediaPlayer, std::shared_ptr ringtoneMediaPlayer, std::shared_ptr systemSoundMediaPlayer, - std::unique_ptr metricSinkInterface, std::shared_ptr speakSpeaker, std::shared_ptr audioSpeaker, std::shared_ptr alertsSpeaker, @@ -151,13 +155,16 @@ std::unique_ptr DefaultClient::create( std::shared_ptr capabilitiesDelegate, std::shared_ptr contextManager, std::shared_ptr transportFactory, + std::shared_ptr avsGatewayManager, std::shared_ptr localeAssetsManager, + std::unordered_set> + enabledConnectionRules, std::shared_ptr systemTimezone, avsCommon::sdkInterfaces::softwareInfo::FirmwareVersion firmwareVersion, bool sendSoftwareInfoOnConnected, std::shared_ptr softwareInfoSenderObserver, std::unique_ptr bluetoothDeviceManager, - std::shared_ptr avsGatewayManager, + std::shared_ptr metricRecorder, std::shared_ptr powerResourceManager) { if (!deviceInfo) { ACSDK_ERROR(LX("initializeFailed").d("reason", "nullDeviceInfo")); @@ -177,7 +184,6 @@ std::unique_ptr DefaultClient::create( bluetoothMediaPlayer, ringtoneMediaPlayer, systemSoundMediaPlayer, - std::move(metricSinkInterface), speakSpeaker, audioSpeaker, alertsSpeaker, @@ -216,13 +222,15 @@ std::unique_ptr DefaultClient::create( capabilitiesDelegate, contextManager, transportFactory, + avsGatewayManager, localeAssetsManager, + enabledConnectionRules, systemTimezone, firmwareVersion, sendSoftwareInfoOnConnected, softwareInfoSenderObserver, std::move(bluetoothDeviceManager), - avsGatewayManager, + std::move(metricRecorder), powerResourceManager)) { return nullptr; } @@ -247,7 +255,6 @@ bool DefaultClient::initialize( std::shared_ptr bluetoothMediaPlayer, std::shared_ptr ringtoneMediaPlayer, std::shared_ptr systemSoundMediaPlayer, - std::unique_ptr metricSinkInterface, std::shared_ptr speakSpeaker, std::shared_ptr audioSpeaker, std::shared_ptr alertsSpeaker, @@ -288,13 +295,16 @@ bool DefaultClient::initialize( std::shared_ptr capabilitiesDelegate, std::shared_ptr contextManager, std::shared_ptr transportFactory, + std::shared_ptr avsGatewayManager, std::shared_ptr localeAssetsManager, + std::unordered_set> + enabledConnectionRules, std::shared_ptr systemTimezone, avsCommon::sdkInterfaces::softwareInfo::FirmwareVersion firmwareVersion, bool sendSoftwareInfoOnConnected, std::shared_ptr softwareInfoSenderObserver, std::unique_ptr bluetoothDeviceManager, - std::shared_ptr avsGatewayManager, + std::shared_ptr metricRecorder, std::shared_ptr powerResourceManager) { if (!audioFactory) { ACSDK_ERROR(LX("initializeFailed").d("reason", "nullAudioFactory")); @@ -471,6 +481,7 @@ bool DefaultClient::initialize( // Create endpoint related objects. m_contextManager = contextManager; m_capabilitiesDelegate = std::move(capabilitiesDelegate); + m_avsGatewayManager->addObserver(m_capabilitiesDelegate); m_endpointManager = EndpointRegistrationManager::create(m_directiveSequencer, m_capabilitiesDelegate); if (!m_endpointManager) { ACSDK_ERROR(LX("initializeFailed").d("reason", "endpointRegistrationManagerCreateFailed")); @@ -504,7 +515,8 @@ bool DefaultClient::initialize( .withAlarmVolumeRampSetting() .withWakeWordConfirmationSetting() .withSpeechConfirmationSetting() - .withTimeZoneSetting(systemTimezone); + .withTimeZoneSetting(systemTimezone) + .withNetworkInfoSetting(); if (localeAssetsManager->getDefaultSupportedWakeWords().empty()) { settingsManagerBuilder.withLocaleSetting(localeAssetsManager); @@ -525,6 +537,20 @@ bool DefaultClient::initialize( */ m_audioActivityTracker = afml::AudioActivityTracker::create(contextManager); + /** + * Interrupt Model object + */ + auto interruptModel = alexaClientSDK::afml::interruptModel::InterruptModel::create( + alexaClientSDK::avsCommon::utils::configuration::ConfigurationNode::getRoot()[INTERRUPT_MODEL_CONFIG_KEY]); + + // Read audioChannels configuration from config file + std::vector audioVirtualChannelConfiguration; + if (!afml::FocusManager::ChannelConfiguration::readChannelConfiguration( + AUDIO_CHANNEL_CONFIG_KEY, &audioVirtualChannelConfiguration)) { + ACSDK_ERROR(LX("initializeFailed").d("reason", "unableToReadAudioChannelConfiguration")); + return false; + } + /* * Creating the Focus Manager - This component deals with the management of * layered audio focus across various @@ -535,8 +561,11 @@ bool DefaultClient::initialize( * Capability Agent will require the Focus Manager in order to request access * to the Channel it wishes to play on. */ - m_audioFocusManager = - std::make_shared(afml::FocusManager::getDefaultAudioChannels(), m_audioActivityTracker); + m_audioFocusManager = std::make_shared( + afml::FocusManager::getDefaultAudioChannels(), + m_audioActivityTracker, + audioVirtualChannelConfiguration, + interruptModel); #ifdef ENABLE_CAPTIONS /* @@ -566,6 +595,7 @@ bool DefaultClient::initialize( auto speechConfirmationSetting = settingsManagerBuilder.getSetting(); auto wakeWordsSetting = settingsManagerBuilder.getSetting(); + /* * Creating the Audio Input Processor - This component is the Capability Agent * that implements the SpeechRecognizer interface of AVS. @@ -586,7 +616,8 @@ bool DefaultClient::initialize( wakeWordsSetting, std::make_shared(std::make_shared()), capabilityAgents::aip::AudioProvider::null(), - powerResourceManager); + powerResourceManager, + metricRecorder); #else m_audioInputProcessor = capabilityAgents::aip::AudioInputProcessor::create( m_directiveSequencer, @@ -603,7 +634,8 @@ bool DefaultClient::initialize( wakeWordsSetting, nullptr, capabilityAgents::aip::AudioProvider::null(), - powerResourceManager); + powerResourceManager, + metricRecorder); #endif if (!m_audioInputProcessor) { @@ -613,13 +645,6 @@ bool DefaultClient::initialize( m_audioInputProcessor->addObserver(m_dialogUXStateAggregator); - std::shared_ptr metricRecorder; -#ifdef ACSDK_ENABLE_METRICS_RECORDING - auto recorderImpl = std::make_shared(); - recorderImpl->addSink(std::move(metricSinkInterface)); - metricRecorder = recorderImpl; -#endif - /* * Creating the Speech Synthesizer - This component is the Capability Agent * that implements the SpeechSynthesizer @@ -679,13 +704,13 @@ bool DefaultClient::initialize( return false; } -// clang-format off + // clang-format off /* * Creating the Audio Player - This component is the Capability Agent that * implements the AudioPlayer * interface of AVS. */ -// clang-format on + // clang-format on #ifdef ENABLE_CAPTIONS m_audioPlayer = capabilityAgents::audioPlayer::AudioPlayer::create( std::move(audioMediaPlayerFactory), @@ -735,14 +760,13 @@ bool DefaultClient::initialize( * Speaker interface of AVS. */ m_speakerManager = capabilityAgents::speakerManager::SpeakerManager::create( - allSpeakers, contextManager, m_connectionManager, m_exceptionSender); + allSpeakers, metricRecorder, contextManager, m_connectionManager, m_exceptionSender); if (!m_speakerManager) { ACSDK_ERROR(LX("initializeFailed").d("reason", "unableToCreateSpeakerManager")); return false; } - auto alertRenderer = - capabilityAgents::alerts::renderer::Renderer::create(alertsMediaPlayer, m_deviceSettingsManager); + auto alertRenderer = capabilityAgents::alerts::renderer::Renderer::create(alertsMediaPlayer); if (!alertRenderer) { ACSDK_ERROR(LX("initializeFailed").d("reason", "unableToCreateAlarmRenderer")); return false; @@ -765,7 +789,10 @@ bool DefaultClient::initialize( audioFactory->alerts(), alertRenderer, customerDataManager, - settingsManagerBuilder.getSetting()); + settingsManagerBuilder.getSetting(), + m_deviceSettingsManager, + metricRecorder); + if (!m_alertsCapabilityAgent) { ACSDK_ERROR(LX("initializeFailed").d("reason", "unableToCreateAlertsCapabilityAgent")); return false; @@ -790,7 +817,8 @@ bool DefaultClient::initialize( contextManager, m_exceptionSender, audioFactory->notifications(), - customerDataManager); + customerDataManager, + metricRecorder); if (!m_notificationsCapabilityAgent) { ACSDK_ERROR(LX("initializeFailed").d("reason", "unableToCreateNotificationsCapabilityAgent")); return false; @@ -896,13 +924,12 @@ bool DefaultClient::initialize( return false; } +// clang-format off + /* + * Creating the MRM (Multi-Room-Music) Capability Agent. + */ +// clang-format on #ifdef ENABLE_MRM - // clang-format off - /* - * Creating the MRM (Multi-Room-Music) Capability Agent. - */ - // clang-format on - #ifdef ENABLE_MRM_STANDALONE_APP auto mrmHandler = capabilityAgents::mrm::mrmHandler::MRMHandlerProxy::create( m_connectionManager, @@ -953,6 +980,14 @@ bool DefaultClient::initialize( */ m_visualActivityTracker = afml::VisualActivityTracker::create(contextManager); + // Read visualVirtualChannels from config file + std::vector visualVirtualChannelConfiguration; + if (!afml::FocusManager::ChannelConfiguration::readChannelConfiguration( + VISUAL_CHANNEL_CONFIG_KEY, &visualVirtualChannelConfiguration)) { + ACSDK_ERROR(LX("initializeFailed").d("reason", "unableToReadVisualChannels")); + return false; + } + /* * Creating the Visual Focus Manager - This component deals with the * management of visual focus across various @@ -965,7 +1000,10 @@ bool DefaultClient::initialize( * on. */ m_visualFocusManager = std::make_shared( - afml::FocusManager::getDefaultVisualChannels(), m_visualActivityTracker); + afml::FocusManager::getDefaultVisualChannels(), + m_visualActivityTracker, + visualVirtualChannelConfiguration, + interruptModel); std::unordered_set> renderPlayerInfoCardsProviders = {m_audioPlayer, m_externalMediaPlayer}; @@ -992,7 +1030,6 @@ bool DefaultClient::initialize( * Creating the Equalizer Capability Agent and related implementations if * enabled */ - m_equalizerRuntimeSetup = equalizerRuntimeSetup; if (nullptr != m_equalizerRuntimeSetup) { auto equalizerController = equalizer::EqualizerController::create( @@ -1125,8 +1162,8 @@ bool DefaultClient::initialize( // the unique ptr for bluetoothDeviceManager can be moved. auto eventBus = bluetoothDeviceManager->getEventBus(); - auto bluetoothAVRCPTransformer = - capabilityAgents::bluetooth::BluetoothAVRCPTransformer::create(eventBus, m_playbackRouter); + auto bluetoothMediaInputTransformer = + capabilityAgents::bluetooth::BluetoothMediaInputTransformer::create(eventBus, m_playbackRouter); /* * Creating the Bluetooth Capability Agent - This component is responsible @@ -1143,7 +1180,8 @@ bool DefaultClient::initialize( std::move(eventBus), bluetoothMediaPlayer, customerDataManager, - bluetoothAVRCPTransformer); + enabledConnectionRules, + bluetoothMediaInputTransformer); } else { ACSDK_DEBUG5(LX("bluetoothCapabilityAgentDisabled").d("reason", "nullBluetoothDeviceManager")); } @@ -1152,7 +1190,6 @@ bool DefaultClient::initialize( capabilityAgents::apiGateway::ApiGatewayCapabilityAgent::create(m_avsGatewayManager, m_exceptionSender); if (!m_apiGatewayCapabilityAgent) { ACSDK_ERROR(LX("initializeFailed").d("reason", "unableToCreateApiGatewayCapabilityAgent")); - return false; } /* @@ -1196,6 +1233,7 @@ bool DefaultClient::initialize( m_defaultEndpointBuilder->withCapability( m_alexaCapabilityAgent->getCapabilityConfiguration(), m_alexaCapabilityAgent); m_defaultEndpointBuilder->withCapabilityConfiguration(m_audioActivityTracker); + m_defaultEndpointBuilder->withCapabilityConfiguration(m_playbackController); #ifdef ENABLE_PCC if (m_phoneCallControllerCapabilityAgent) { m_defaultEndpointBuilder->withCapability( @@ -1265,7 +1303,7 @@ bool DefaultClient::initialize( void DefaultClient::connect() { if (m_defaultEndpointBuilder) { - if (!m_defaultEndpointBuilder->build().hasValue()) { + if (!m_defaultEndpointBuilder->buildDefaultEndpoint()) { ACSDK_CRITICAL(LX("connectFailed").d("reason", "couldNotBuildDefaultEndpoint")); return; } @@ -1479,6 +1517,16 @@ std::shared_ptr DefaultClient return m_speakerManager; } +void DefaultClient::addSpeechSynthesizerObserver( + std::shared_ptr observer) { + m_speechSynthesizer->addObserver(observer); +} + +void DefaultClient::removeSpeechSynthesizerObserver( + std::shared_ptr observer) { + m_speechSynthesizer->removeObserver(observer); +} + bool DefaultClient::setFirmwareVersion(avsCommon::sdkInterfaces::softwareInfo::FirmwareVersion firmwareVersion) { { std::lock_guard lock(m_softwareInfoSenderMutex); @@ -1604,6 +1652,25 @@ void DefaultClient::stopCommsCall() { } } +bool DefaultClient::isCommsCallMuted() { + if (m_callManager) { + return m_callManager->isSelfMuted(); + } + return false; +} + +void DefaultClient::muteCommsCall() { + if (m_callManager) { + m_callManager->muteSelf(); + } +} + +void DefaultClient::unmuteCommsCall() { + if (m_callManager) { + m_callManager->unmuteSelf(); + } +} + DefaultClient::~DefaultClient() { if (m_directiveSequencer) { ACSDK_DEBUG5(LX("DirectiveSequencerShutdown")); diff --git a/ApplicationUtilities/DefaultClient/src/DeviceSettingsManagerBuilder.cpp b/ApplicationUtilities/DefaultClient/src/DeviceSettingsManagerBuilder.cpp index 905f6eee..aa5bc7ed 100644 --- a/ApplicationUtilities/DefaultClient/src/DeviceSettingsManagerBuilder.cpp +++ b/ApplicationUtilities/DefaultClient/src/DeviceSettingsManagerBuilder.cpp @@ -49,6 +49,12 @@ static const std::string SETTINGS_CONFIGURATION_ROOT_KEY = "deviceSettings"; /// The key to find the default timezone configuration. static const std::string DEFAULT_TIMEZONE_CONFIGURATION_KEY = "defaultTimezone"; +/// Network info setting events metadata. +static const SettingEventMetadata NETWORK_INFO_METADATA = {"System", + "NetworkInfoChanged", + "NetworkInfoReport", + "networkInfo"}; + template static inline bool checkPointer(const std::shared_ptr& pointer, const std::string& variableName) { if (!pointer) { @@ -182,6 +188,11 @@ DeviceSettingsManagerBuilder& DeviceSettingsManagerBuilder::withLocaleAndWakeWor return *this; } +DeviceSettingsManagerBuilder& DeviceSettingsManagerBuilder::withNetworkInfoSetting() { + return withSynchronizedSetting( + NETWORK_INFO_METADATA, types::NetworkInfo()); +} + DeviceSettingsManagerBuilder& DeviceSettingsManagerBuilder::withDoNotDisturbSetting( const std::shared_ptr& dndCA) { if (!dndCA) { diff --git a/ApplicationUtilities/Resources/Audio/include/Audio/Data/create_header.bash b/ApplicationUtilities/Resources/Audio/include/Audio/Data/create_header.bash index d7a69b34..52f2f4e0 100644 --- a/ApplicationUtilities/Resources/Audio/include/Audio/Data/create_header.bash +++ b/ApplicationUtilities/Resources/Audio/include/Audio/Data/create_header.bash @@ -1,7 +1,7 @@ #!/bin/bash # This is used to create a header file that contains binary data from a file that can be used in this library. The -# files are downloaded from https://developer.amazon.com/docs/alexa-voice-service/ux-design-overview.html#sounds. +# files are downloaded from https://developer.amazon.com/docs/alexa/alexa-voice-service/ux-design-overview.html#sounds. if [ "$#" -ne 2 ] || ! [ -f "${1}" ] || [ -f "${2}" ]; then echo "Usage: $0 " >&2 diff --git a/ApplicationUtilities/SystemSoundPlayer/include/SystemSoundPlayer/SystemSoundPlayer.h b/ApplicationUtilities/SystemSoundPlayer/include/SystemSoundPlayer/SystemSoundPlayer.h index c574c846..d8591989 100644 --- a/ApplicationUtilities/SystemSoundPlayer/include/SystemSoundPlayer/SystemSoundPlayer.h +++ b/ApplicationUtilities/SystemSoundPlayer/include/SystemSoundPlayer/SystemSoundPlayer.h @@ -1,5 +1,5 @@ /* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2019-2020 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. @@ -55,9 +55,14 @@ public: /// @name MediaPlayerObserverInterface Functions /// @{ - void onPlaybackFinished(SourceId id) override; - void onPlaybackError(SourceId id, const avsCommon::utils::mediaPlayer::ErrorType& type, std::string error) override; - void onPlaybackStarted(SourceId id) override; + void onPlaybackFinished(SourceId id, const avsCommon::utils::mediaPlayer::MediaPlayerState& state) override; + void onPlaybackError( + SourceId id, + const avsCommon::utils::mediaPlayer::ErrorType& type, + std::string error, + const avsCommon::utils::mediaPlayer::MediaPlayerState& state) override; + void onPlaybackStarted(SourceId id, const avsCommon::utils::mediaPlayer::MediaPlayerState& state) override; + void onFirstByteRead(SourceId id, const avsCommon::utils::mediaPlayer::MediaPlayerState& state) override; /// @} private: diff --git a/ApplicationUtilities/SystemSoundPlayer/src/SystemSoundPlayer.cpp b/ApplicationUtilities/SystemSoundPlayer/src/SystemSoundPlayer.cpp index 25f2a6b0..bcf71fd4 100644 --- a/ApplicationUtilities/SystemSoundPlayer/src/SystemSoundPlayer.cpp +++ b/ApplicationUtilities/SystemSoundPlayer/src/SystemSoundPlayer.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2019-2020 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. @@ -21,6 +21,7 @@ namespace applicationUtilities { namespace systemSoundPlayer { using namespace avsCommon::utils::logger; +using MediaPlayerState = avsCommon::utils::mediaPlayer::MediaPlayerState; /// String to identify log entries originating from this file. static const std::string TAG("SystemSoundPlayer"); @@ -90,11 +91,11 @@ std::shared_future SystemSoundPlayer::playTone(Tone tone) { return m_sharedFuture; } -void SystemSoundPlayer::onPlaybackStarted(SourceId id) { +void SystemSoundPlayer::onPlaybackStarted(SourceId id, const MediaPlayerState&) { ACSDK_DEBUG5(LX(__func__).d("SourceId", id)); } -void SystemSoundPlayer::onPlaybackFinished(SourceId id) { +void SystemSoundPlayer::onPlaybackFinished(SourceId id, const MediaPlayerState&) { std::lock_guard lock(m_mutex); ACSDK_DEBUG5(LX(__func__).d("SourceId", id)); @@ -108,7 +109,8 @@ void SystemSoundPlayer::onPlaybackFinished(SourceId id) { void SystemSoundPlayer::onPlaybackError( SourceId id, const avsCommon::utils::mediaPlayer::ErrorType& type, - std::string error) { + std::string error, + const MediaPlayerState&) { std::lock_guard lock(m_mutex); ACSDK_ERROR(LX(__func__).d("SourceId", id).d("error", error)); @@ -121,6 +123,10 @@ void SystemSoundPlayer::onPlaybackError( finishPlayTone(false); } +void SystemSoundPlayer::onFirstByteRead(SourceId id, const MediaPlayerState&) { + // TODO : add metrics +} + void SystemSoundPlayer::finishPlayTone(bool result) { m_playTonePromise.set_value(result); @@ -137,4 +143,4 @@ SystemSoundPlayer::SystemSoundPlayer( } // namespace systemSoundPlayer } // namespace applicationUtilities -} // namespace alexaClientSDK \ No newline at end of file +} // namespace alexaClientSDK diff --git a/BluetoothImplementations/BlueZ/include/BlueZ/BlueZBluetoothDevice.h b/BluetoothImplementations/BlueZ/include/BlueZ/BlueZBluetoothDevice.h index 6c965505..6b5f73d3 100644 --- a/BluetoothImplementations/BlueZ/include/BlueZ/BlueZBluetoothDevice.h +++ b/BluetoothImplementations/BlueZ/include/BlueZ/BlueZBluetoothDevice.h @@ -70,6 +70,7 @@ public: std::string getMac() const override; std::string getFriendlyName() const override; avsCommon::sdkInterfaces::bluetooth::DeviceState getDeviceState() override; + MetaData getDeviceMetaData() override; bool isPaired() override; std::future pair() override; @@ -81,11 +82,12 @@ public: std::vector> getSupportedServices() override; - std::shared_ptr getA2DPSink() override; - std::shared_ptr getA2DPSource() override; - std::shared_ptr getAVRCPTarget() override; - std::shared_ptr getAVRCPController() - override; + std::shared_ptr getService( + std::string uuid) override; + avsCommon::utils::bluetooth::MediaStreamingState getStreamingState() override; + bool toggleServiceConnection( + bool enabled, + std::shared_ptr service) override; /// @} /** @@ -206,6 +208,16 @@ private: */ bool executeIsConnected(); + /** + * Helper function to toggle a profile, which restricts the future connection/disconnection. + * @param enabled true if need to connect. + * @param service the Bluetooth profile needed to toggle. + * @return A bool indicating success. + */ + bool executeToggleServiceConnection( + bool enabled, + std::shared_ptr service); + /** * Queries BlueZ for the value of the property as reported by the adapter. * @@ -273,6 +285,15 @@ private: template std::shared_ptr getService(); + /** + * Helper function to initialize an existing service. + * + * @tparam BlueZServiceType the type of the @c BluetoothServiceInterface. + * @return bool Indicates whether the service initialization was successful. + */ + template + bool initializeService(); + /// Proxy to interact with the org.bluez.Device1 interface. std::shared_ptr m_deviceProxy; @@ -300,6 +321,9 @@ private: /// The current state of the device. BlueZDeviceState m_deviceState; + /// Used to store device metadata. + std::unique_ptr m_metaData; + /// The associated @c BlueZDeviceManager. std::shared_ptr m_deviceManager; diff --git a/BluetoothImplementations/BlueZ/include/BlueZ/BlueZDeviceManager.h b/BluetoothImplementations/BlueZ/include/BlueZ/BlueZDeviceManager.h index 2b0de6c0..3b651f01 100644 --- a/BluetoothImplementations/BlueZ/include/BlueZ/BlueZDeviceManager.h +++ b/BluetoothImplementations/BlueZ/include/BlueZ/BlueZDeviceManager.h @@ -92,6 +92,12 @@ public: */ std::string getAdapterPath() const; + /** + * Get the current media streaming state. + * @return @c MediaStreamingState + */ + avsCommon::utils::bluetooth::MediaStreamingState getMediaStreamingState(); + private: /** * A constructor diff --git a/BluetoothImplementations/BlueZ/include/BlueZ/BlueZHFP.h b/BluetoothImplementations/BlueZ/include/BlueZ/BlueZHFP.h new file mode 100644 index 00000000..9cd7c45c --- /dev/null +++ b/BluetoothImplementations/BlueZ/include/BlueZ/BlueZHFP.h @@ -0,0 +1,61 @@ +/* + * Copyright 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 ALEXA_CLIENT_SDK_BLUETOOTHIMPLEMENTATIONS_BLUEZ_INCLUDE_BLUEZ_BLUEZHFP_H_ +#define ALEXA_CLIENT_SDK_BLUETOOTHIMPLEMENTATIONS_BLUEZ_INCLUDE_BLUEZ_BLUEZHFP_H_ + +#include + +#include +#include + +namespace alexaClientSDK { +namespace bluetoothImplementations { +namespace blueZ { + +/** + * BlueZ implementation of @c HFPInterface interface + */ +class BlueZHFP : public avsCommon::sdkInterfaces::bluetooth::services::HFPInterface { +public: + /** + * Factory method to create a new instance of @c BlueZHFP + * + * @return A new instance of @c BlueZHFP, nullptr if there was an error creating it. + */ + static std::shared_ptr create(); + + /// @name BluetoothServiceInterface functions. + /// @{ + std::shared_ptr getRecord() override; + void setup() override; + void cleanup() override; + /// @} + +private: + /** + * Private constructor + */ + BlueZHFP(); + + /// Bluetooth service's SDP record containing the common service information. + std::shared_ptr m_record; +}; + +} // namespace blueZ +} // namespace bluetoothImplementations +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_BLUETOOTHIMPLEMENTATIONS_BLUEZ_INCLUDE_BLUEZ_BLUEZHFP_H_App \ No newline at end of file diff --git a/BluetoothImplementations/BlueZ/include/BlueZ/BlueZHID.h b/BluetoothImplementations/BlueZ/include/BlueZ/BlueZHID.h new file mode 100644 index 00000000..465d9a0b --- /dev/null +++ b/BluetoothImplementations/BlueZ/include/BlueZ/BlueZHID.h @@ -0,0 +1,61 @@ +/* + * Copyright 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 ALEXA_CLIENT_SDK_BLUETOOTHIMPLEMENTATIONS_BLUEZ_INCLUDE_BLUEZ_BLUEZHID_H_ +#define ALEXA_CLIENT_SDK_BLUETOOTHIMPLEMENTATIONS_BLUEZ_INCLUDE_BLUEZ_BLUEZHID_H_ + +#include + +#include +#include + +namespace alexaClientSDK { +namespace bluetoothImplementations { +namespace blueZ { + +/** + * BlueZ implementation of @c HIDInterface interface + */ +class BlueZHID : public avsCommon::sdkInterfaces::bluetooth::services::HIDInterface { +public: + /** + * Factory method to create a new instance of @c BlueZHID + * + * @return A new instance of @c BlueZHID, nullptr if there was an error creating it. + */ + static std::shared_ptr create(); + + /// @name BluetoothServiceInterface functions. + /// @{ + std::shared_ptr getRecord() override; + void setup() override; + void cleanup() override; + /// @} + +private: + /** + * Private constructor + */ + BlueZHID(); + + /// Bluetooth service's SDP record containing the common service information. + std::shared_ptr m_record; +}; + +} // namespace blueZ +} // namespace bluetoothImplementations +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_BLUETOOTHIMPLEMENTATIONS_BLUEZ_INCLUDE_BLUEZ_BLUEZHID_H_App \ No newline at end of file diff --git a/BluetoothImplementations/BlueZ/include/BlueZ/BlueZSPP.h b/BluetoothImplementations/BlueZ/include/BlueZ/BlueZSPP.h new file mode 100644 index 00000000..298d48b0 --- /dev/null +++ b/BluetoothImplementations/BlueZ/include/BlueZ/BlueZSPP.h @@ -0,0 +1,61 @@ +/* + * Copyright 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 ALEXA_CLIENT_SDK_BLUETOOTHIMPLEMENTATIONS_BLUEZ_INCLUDE_BLUEZ_BLUEZSPP_H_ +#define ALEXA_CLIENT_SDK_BLUETOOTHIMPLEMENTATIONS_BLUEZ_INCLUDE_BLUEZ_BLUEZSPP_H_ + +#include + +#include +#include + +namespace alexaClientSDK { +namespace bluetoothImplementations { +namespace blueZ { + +/** + * BlueZ implementation of @c SPPInterface interface + */ +class BlueZSPP : public avsCommon::sdkInterfaces::bluetooth::services::SPPInterface { +public: + /** + * Factory method to create a new instance of @c BlueZSPP + * + * @return A new instance of @c BlueZSPP, nullptr if there was an error creating it. + */ + static std::shared_ptr create(); + + /// @name BluetoothServiceInterface functions. + /// @{ + std::shared_ptr getRecord() override; + void setup() override; + void cleanup() override; + /// @} + +private: + /** + * Private constructor + */ + BlueZSPP(); + + /// Bluetooth service's SDP record containing the common service information. + std::shared_ptr m_record; +}; + +} // namespace blueZ +} // namespace bluetoothImplementations +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_BLUETOOTHIMPLEMENTATIONS_BLUEZ_INCLUDE_BLUEZ_BLUEZSPP_H_App \ No newline at end of file diff --git a/BluetoothImplementations/BlueZ/include/BlueZ/MPRISPlayer.h b/BluetoothImplementations/BlueZ/include/BlueZ/MPRISPlayer.h index 73c372a0..59e90cab 100644 --- a/BluetoothImplementations/BlueZ/include/BlueZ/MPRISPlayer.h +++ b/BluetoothImplementations/BlueZ/include/BlueZ/MPRISPlayer.h @@ -1,5 +1,5 @@ /* - * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018-2020 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. @@ -67,7 +67,7 @@ private: * * @param connection A DBusConnection object. * @param media The org.bluez.Media1 object. - * @param eventBus The eventbus to notify of the @c AVRCPCommand. + * @param eventBus The eventbus to notify of the @c MediaCommand. * @param playerPath The object to create the player at. */ MPRISPlayer( @@ -79,7 +79,7 @@ private: /** * Performs initialization. * - * @return Whether the operation was succesful or not. + * @return Whether the operation was successful or not. */ bool init(); @@ -106,19 +106,19 @@ private: void unsupportedMethod(GVariant* arguments, GDBusMethodInvocation* invocation); /** - * Converts the callback to the corresponding @c AVRCPCommandReceived event. + * Converts the callback to the corresponding @c MediaCommandReceived event. * * @param arguments The arguments which this DBus method was called with. * @invocation A struct containing data about the method invocation. */ - void toAVRCPCommand(GVariant* arguments, GDBusMethodInvocation* invocation); + void toMediaCommand(GVariant* arguments, GDBusMethodInvocation* invocation); /** - * Sends the @c AVRCPCommandRecieved event to @c m_eventBus. + * Sends the @c MediaCommandReceived event to @c m_eventBus. * * @param command The command to send. */ - void sendEvent(const avsCommon::sdkInterfaces::bluetooth::services::AVRCPCommand& command); + void sendEvent(const avsCommon::sdkInterfaces::bluetooth::services::MediaCommand& command); /// The DBus object path of the player. const std::string m_playerPath; @@ -126,7 +126,7 @@ private: /// A Proxy for the Media object. std::shared_ptr m_media; - /// The event bus on which to send the @c AVRCPCommand. + /// The event bus on which to send the @c MediaCommand. std::shared_ptr m_eventBus; }; diff --git a/BluetoothImplementations/BlueZ/src/BlueZBluetoothDevice.cpp b/BluetoothImplementations/BlueZ/src/BlueZBluetoothDevice.cpp index 208a479f..d00fe138 100644 --- a/BluetoothImplementations/BlueZ/src/BlueZBluetoothDevice.cpp +++ b/BluetoothImplementations/BlueZ/src/BlueZBluetoothDevice.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018-2020 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. @@ -12,13 +12,17 @@ * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ - #include #include +#include + #include "BlueZ/BlueZA2DPSink.h" #include "BlueZ/BlueZA2DPSource.h" #include "BlueZ/BlueZAVRCPController.h" #include "BlueZ/BlueZAVRCPTarget.h" +#include "BlueZ/BlueZHFP.h" +#include "BlueZ/BlueZHID.h" +#include "BlueZ/BlueZSPP.h" #include "BlueZ/BlueZConstants.h" #include "BlueZ/BlueZDeviceManager.h" @@ -64,12 +68,21 @@ static const std::string BLUEZ_DEVICE_METHOD_CONNECT = "Connect"; /// BlueZ org.bluez.Device1 method to disconnect. static const std::string BLUEZ_DEVICE_METHOD_DISCONNECT = "Disconnect"; +/// BlueZ org.bluez.Device1 method to connect profile. +static const std::string BLUZ_DEVICE_METHOD_CONNECT_PROFILE = "ConnectProfile"; + +/// BlueZ org.bluez.Device1 method to disconnect profile. +static const std::string BLUZ_DEVICE_METHOD_DISCONNECT_PROFILE = "DisconnectProfile"; + /// BlueZ org.bluez.Device1 paired property. static const std::string BLUEZ_DEVICE_PROPERTY_PAIRED = "Paired"; /// BlueZ org.bluez.Device1 connected property. static const std::string BLUEZ_DEVICE_PROPERTY_CONNECTED = "Connected"; +/// BlueZ org.bluez.Device1 class property. +static const std::string BLUEZ_DEVICE_PROPERTY_CLASS = "Class"; + /// BlueZ org.bluez.Adapter1 method to remove device. static const std::string BLUEZ_ADAPTER_REMOVE_DEVICE = "RemoveDevice"; @@ -180,6 +193,38 @@ bool BlueZBluetoothDevice::init() { transitionToState(BlueZDeviceState::CONNECTED, true); } + // Get device MetaData + int defaultUndefinedClass = BlueZBluetoothDevice::MetaData::UNDEFINED_CLASS_VALUE; + m_metaData = alexaClientSDK::avsCommon::utils::memory::make_unique( + Optional(), Optional(), defaultUndefinedClass, Optional(), Optional()); + + ManagedGVariant classOfDevice; + if (m_propertiesProxy->getVariantProperty( + BlueZConstants::BLUEZ_DEVICE_INTERFACE, BLUEZ_DEVICE_PROPERTY_CLASS, &classOfDevice)) { + GVariantTupleReader tupleReader(classOfDevice); + ManagedGVariant unboxed = tupleReader.getVariant(0).unbox(); + if (g_variant_is_of_type(unboxed.get(), G_VARIANT_TYPE_UINT32) == TRUE) { + m_metaData->classOfDevice = g_variant_get_uint32(unboxed.get()); + ACSDK_DEBUG5(LX(__func__).d("ClassOfDevice", g_variant_get_uint32(unboxed.get()))); + } + } + + return true; +} + +template +bool BlueZBluetoothDevice::initializeService() { + ACSDK_DEBUG5(LX(__func__).d("supports", BlueZServiceType::NAME)); + std::shared_ptr service = BlueZServiceType::create(); + + if (!service) { + ACSDK_ERROR(LX(__func__).d("createBlueZServiceFailed", BlueZServiceType::NAME)); + return false; + } else { + service->setup(); + insertService(service); + } + return true; } @@ -217,24 +262,24 @@ bool BlueZBluetoothDevice::initializeServices(const std::unordered_set()) { return false; - } else { - a2dpSink->setup(); - insertService(a2dpSink); } } else if (AVRCPControllerInterface::UUID == uuid && !serviceExists(uuid)) { - ACSDK_DEBUG5(LX(__func__).d("supports", AVRCPControllerInterface::NAME)); - auto avrcpController = BlueZAVRCPController::create(); - if (!avrcpController) { - ACSDK_ERROR(LX(__func__).d("reason", "createAVRCPControllerFailed")); + if (!initializeService()) { + return false; + } + } else if (HFPInterface::UUID == uuid && !serviceExists(uuid)) { + if (!initializeService()) { + return false; + } + } else if (HIDInterface::UUID == uuid && !serviceExists(uuid)) { + if (!initializeService()) { + return false; + } + } else if (SPPInterface::UUID == uuid && !serviceExists(uuid)) { + if (!initializeService()) { return false; - } else { - avrcpController->setup(); - insertService(avrcpController); } } } @@ -310,6 +355,9 @@ bool BlueZBluetoothDevice::executeUnpair() { ACSDK_ERROR(LX(__func__).d("error", errorMsg)); return false; } + + transitionToState(BlueZDeviceState::UNPAIRED, true); + transitionToState(BlueZDeviceState::FOUND, true); return true; } @@ -450,6 +498,9 @@ bool BlueZBluetoothDevice::executeDisconnect() { return false; } + transitionToState(BlueZDeviceState::DISCONNECTED, true); + transitionToState(BlueZDeviceState::IDLE, true); + return true; } @@ -523,27 +574,25 @@ std::shared_ptr BlueZBluetoothDevice::getService() { return service; } -std::shared_ptr BlueZBluetoothDevice::getA2DPSink() { - ACSDK_DEBUG5(LX(__func__)); +std::shared_ptr BlueZBluetoothDevice:: + getService(std::string uuid) { + if (A2DPSourceInterface::UUID == uuid) { + return getService(); + } else if (A2DPSinkInterface::UUID == uuid) { + return getService(); + } else if (AVRCPTargetInterface::UUID == uuid) { + return getService(); + } else if (AVRCPControllerInterface::UUID == uuid) { + return getService(); + } else if (HFPInterface::UUID == uuid) { + return getService(); + } else if (HIDInterface::UUID == uuid) { + return getService(); + } else if (SPPInterface::UUID == uuid) { + return getService(); + } - return getService(); -} - -std::shared_ptr BlueZBluetoothDevice::getA2DPSource() { - ACSDK_DEBUG5(LX(__func__)); - return getService(); -} - -std::shared_ptr BlueZBluetoothDevice::getAVRCPTarget() { - ACSDK_DEBUG5(LX(__func__)); - - return getService(); -} - -std::shared_ptr BlueZBluetoothDevice::getAVRCPController() { - ACSDK_DEBUG5(LX(__func__)); - - return getService(); + return nullptr; } DeviceState BlueZBluetoothDevice::getDeviceState() { @@ -552,6 +601,49 @@ DeviceState BlueZBluetoothDevice::getDeviceState() { return m_executor.submit([this] { return convertToDeviceState(m_deviceState); }).get(); } +BlueZBluetoothDevice::MetaData BlueZBluetoothDevice::getDeviceMetaData() { + return *m_metaData; +} + +MediaStreamingState BlueZBluetoothDevice::getStreamingState() { + return m_executor + .submit([this] { + if (!m_deviceManager) { + ACSDK_ERROR(LX(__func__).d("reason", "nullDeviceManager")); + return MediaStreamingState::IDLE; + } + + return m_deviceManager->getMediaStreamingState(); + }) + .get(); +} + +bool BlueZBluetoothDevice::toggleServiceConnection(bool enabled, std::shared_ptr service) { + ACSDK_DEBUG5(LX(__func__)); + + return m_executor.submit([this, enabled, service] { return executeToggleServiceConnection(enabled, service); }) + .get(); +} + +bool BlueZBluetoothDevice::executeToggleServiceConnection( + bool enabled, + std::shared_ptr service) { + std::string uuid = service->getRecord()->getUuid(); + ManagedGError error; + if (enabled) { + m_deviceProxy->callMethod( + BLUZ_DEVICE_METHOD_CONNECT_PROFILE, g_variant_new_string(uuid.c_str()), error.toOutputParameter()); + } else { + m_deviceProxy->callMethod( + BLUZ_DEVICE_METHOD_DISCONNECT_PROFILE, g_variant_new_string(uuid.c_str()), error.toOutputParameter()); + } + if (error.hasError()) { + ACSDK_ERROR(LX(__func__).d("reason", error.getMessage())); + return false; + } + return true; +} + avsCommon::sdkInterfaces::bluetooth::DeviceState BlueZBluetoothDevice::convertToDeviceState( BlueZDeviceState bluezDeviceState) { switch (bluezDeviceState) { @@ -615,7 +707,8 @@ bool BlueZBluetoothDevice::executeIsConnectedToRelevantServices() { bool isPaired = false; bool isConnected = false; - bool relevantServiceDiscovered = getA2DPSink() != nullptr || getA2DPSource() != nullptr; + bool relevantServiceDiscovered = + getService(A2DPSinkInterface::UUID) != nullptr || getService(A2DPSourceInterface::UUID) != nullptr; return relevantServiceDiscovered && queryDeviceProperty(BLUEZ_DEVICE_PROPERTY_PAIRED, &isPaired) && isPaired && queryDeviceProperty(BLUEZ_DEVICE_PROPERTY_CONNECTED, &isConnected) && isConnected; diff --git a/BluetoothImplementations/BlueZ/src/BlueZDeviceManager.cpp b/BluetoothImplementations/BlueZ/src/BlueZDeviceManager.cpp index dde8eca8..60940391 100644 --- a/BluetoothImplementations/BlueZ/src/BlueZDeviceManager.cpp +++ b/BluetoothImplementations/BlueZ/src/BlueZDeviceManager.cpp @@ -18,6 +18,7 @@ #include #include +#include #include #include #include @@ -290,7 +291,7 @@ void BlueZDeviceManager::onMediaStreamPropertyChanged(const std::string& path, c } if (A2DPSourceInterface::UUID == uuid) { - auto sink = device->getA2DPSink(); + auto sink = device->getService(A2DPSinkInterface::UUID); if (!sink) { ACSDK_ERROR(LX(__func__).d("reason", "nullSink")); return; @@ -346,6 +347,10 @@ std::string BlueZDeviceManager::getAdapterPath() const { return m_adapterPath; } +avsCommon::utils::bluetooth::MediaStreamingState BlueZDeviceManager::getMediaStreamingState() { + return m_streamingState; +} + void BlueZDeviceManager::interfacesAddedCallback( GDBusConnection* conn, const gchar* sender_name, diff --git a/BluetoothImplementations/BlueZ/src/BlueZHFP.cpp b/BluetoothImplementations/BlueZ/src/BlueZHFP.cpp new file mode 100644 index 00000000..e85a1bdd --- /dev/null +++ b/BluetoothImplementations/BlueZ/src/BlueZHFP.cpp @@ -0,0 +1,57 @@ +/* + * Copyright 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 + +#include "BlueZ/BlueZHFP.h" +#include "BlueZ/BlueZDeviceManager.h" + +namespace alexaClientSDK { +namespace bluetoothImplementations { +namespace blueZ { + +using namespace avsCommon::utils; +using namespace avsCommon::sdkInterfaces::bluetooth::services; + +/// String to identify log entries originating from this file. +static const std::string TAG{"BlueZHFP"}; + +/** + * 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) + +std::shared_ptr BlueZHFP::create() { + return std::shared_ptr(new BlueZHFP()); +} + +void BlueZHFP::setup() { +} + +void BlueZHFP::cleanup() { +} + +std::shared_ptr BlueZHFP::getRecord() { + return m_record; +} + +BlueZHFP::BlueZHFP() : m_record{std::make_shared("")} { +} + +} // namespace blueZ +} // namespace bluetoothImplementations +} // namespace alexaClientSDK diff --git a/BluetoothImplementations/BlueZ/src/BlueZHID.cpp b/BluetoothImplementations/BlueZ/src/BlueZHID.cpp new file mode 100644 index 00000000..8a0a4762 --- /dev/null +++ b/BluetoothImplementations/BlueZ/src/BlueZHID.cpp @@ -0,0 +1,57 @@ +/* + * Copyright 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 + +#include "BlueZ/BlueZHID.h" +#include "BlueZ/BlueZDeviceManager.h" + +namespace alexaClientSDK { +namespace bluetoothImplementations { +namespace blueZ { + +using namespace avsCommon::utils; +using namespace avsCommon::sdkInterfaces::bluetooth::services; + +/// String to identify log entries originating from this file. +static const std::string TAG{"BlueZHID"}; + +/** + * 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) + +std::shared_ptr BlueZHID::create() { + return std::shared_ptr(new BlueZHID()); +} + +void BlueZHID::setup() { +} + +void BlueZHID::cleanup() { +} + +std::shared_ptr BlueZHID::getRecord() { + return m_record; +} + +BlueZHID::BlueZHID() : m_record{std::make_shared("")} { +} + +} // namespace blueZ +} // namespace bluetoothImplementations +} // namespace alexaClientSDK diff --git a/BluetoothImplementations/BlueZ/src/BlueZSPP.cpp b/BluetoothImplementations/BlueZ/src/BlueZSPP.cpp new file mode 100644 index 00000000..57faa23b --- /dev/null +++ b/BluetoothImplementations/BlueZ/src/BlueZSPP.cpp @@ -0,0 +1,57 @@ +/* + * Copyright 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 + +#include "BlueZ/BlueZSPP.h" +#include "BlueZ/BlueZDeviceManager.h" + +namespace alexaClientSDK { +namespace bluetoothImplementations { +namespace blueZ { + +using namespace avsCommon::utils; +using namespace avsCommon::sdkInterfaces::bluetooth::services; + +/// String to identify log entries originating from this file. +static const std::string TAG{"BlueZSPP"}; + +/** + * 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) + +std::shared_ptr BlueZSPP::create() { + return std::shared_ptr(new BlueZSPP()); +} + +void BlueZSPP::setup() { +} + +void BlueZSPP::cleanup() { +} + +std::shared_ptr BlueZSPP::getRecord() { + return m_record; +} + +BlueZSPP::BlueZSPP() : m_record{std::make_shared("")} { +} + +} // namespace blueZ +} // namespace bluetoothImplementations +} // namespace alexaClientSDK \ No newline at end of file diff --git a/BluetoothImplementations/BlueZ/src/CMakeLists.txt b/BluetoothImplementations/BlueZ/src/CMakeLists.txt index b03d905f..154c17d4 100644 --- a/BluetoothImplementations/BlueZ/src/CMakeLists.txt +++ b/BluetoothImplementations/BlueZ/src/CMakeLists.txt @@ -8,7 +8,10 @@ add_library(BluetoothImplementationsBlueZ SHARED BlueZBluetoothDevice.cpp BlueZBluetoothDeviceManager.cpp BlueZDeviceManager.cpp + BlueZHFP.cpp + BlueZHID.cpp BlueZHostController.cpp + BlueZSPP.cpp DBusConnection.cpp DBusObjectBase.cpp DBusPropertiesProxy.cpp diff --git a/BluetoothImplementations/BlueZ/src/MPRISPlayer.cpp b/BluetoothImplementations/BlueZ/src/MPRISPlayer.cpp index 13b7dc70..c79421ca 100644 --- a/BluetoothImplementations/BlueZ/src/MPRISPlayer.cpp +++ b/BluetoothImplementations/BlueZ/src/MPRISPlayer.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * 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. @@ -153,10 +153,10 @@ MPRISPlayer::MPRISPlayer( INTROSPECT_XML, playerPath, { - {NEXT, &MPRISPlayer::toAVRCPCommand}, - {PREVIOUS, &MPRISPlayer::toAVRCPCommand}, - {PAUSE, &MPRISPlayer::toAVRCPCommand}, - {PLAY, &MPRISPlayer::toAVRCPCommand}, + {NEXT, &MPRISPlayer::toMediaCommand}, + {PREVIOUS, &MPRISPlayer::toMediaCommand}, + {PAUSE, &MPRISPlayer::toMediaCommand}, + {PLAY, &MPRISPlayer::toMediaCommand}, {PLAY_PAUSE, &MPRISPlayer::unsupportedMethod}, {STOP, &MPRISPlayer::unsupportedMethod}, {SEEK, &MPRISPlayer::unsupportedMethod}, @@ -188,19 +188,19 @@ void MPRISPlayer::unsupportedMethod(GVariant* arguments, GDBusMethodInvocation* g_dbus_method_invocation_return_value(invocation, nullptr); } -void MPRISPlayer::toAVRCPCommand(GVariant* arguments, GDBusMethodInvocation* invocation) { +void MPRISPlayer::toMediaCommand(GVariant* arguments, GDBusMethodInvocation* invocation) { const char* method = g_dbus_method_invocation_get_method_name(invocation); ACSDK_DEBUG5(LX(__func__).d("method", method)); if (PLAY == method) { - sendEvent(AVRCPCommand::PLAY); + sendEvent(MediaCommand::PLAY); } else if (PAUSE == method) { - sendEvent(AVRCPCommand::PAUSE); + sendEvent(MediaCommand::PAUSE); } else if (NEXT == method) { - sendEvent(AVRCPCommand::NEXT); + sendEvent(MediaCommand::NEXT); } else if (PREVIOUS == method) { - sendEvent(AVRCPCommand::PREVIOUS); + sendEvent(MediaCommand::PREVIOUS); } else { ACSDK_ERROR(LX(__func__).d("reason", "unsupported").d("method", method)); } @@ -208,10 +208,10 @@ void MPRISPlayer::toAVRCPCommand(GVariant* arguments, GDBusMethodInvocation* inv g_dbus_method_invocation_return_value(invocation, nullptr); } -void MPRISPlayer::sendEvent(const AVRCPCommand& command) { +void MPRISPlayer::sendEvent(const MediaCommand& command) { ACSDK_DEBUG5(LX(__func__).d("command", command)); - AVRCPCommandReceivedEvent event(command); + MediaCommandReceivedEvent event(command); m_eventBus->sendEvent(event); } @@ -222,7 +222,7 @@ bool MPRISPlayer::registerPlayer() { GVariantBuilder builder; g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}")); - // Subset of properties used for AVRCP Commands. + // Subset of properties used for Media Commands. g_variant_builder_add(&builder, "{sv}", CAN_GO_NEXT.c_str(), g_variant_new("b", TRUE)); g_variant_builder_add(&builder, "{sv}", CAN_GO_PREVIOUS.c_str(), g_variant_new("b", TRUE)); g_variant_builder_add(&builder, "{sv}", CAN_PLAY.c_str(), g_variant_new("b", TRUE)); diff --git a/BluetoothImplementations/BlueZ/test/BlueZHFPTest.cpp b/BluetoothImplementations/BlueZ/test/BlueZHFPTest.cpp new file mode 100644 index 00000000..eb41bba0 --- /dev/null +++ b/BluetoothImplementations/BlueZ/test/BlueZHFPTest.cpp @@ -0,0 +1,43 @@ +/* + * Copyright 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 +#include + +#include "BlueZ/BlueZHFP.h" + +namespace alexaClientSDK { +namespace bluetoothImplementations { +namespace blueZ { +namespace test { + +using namespace ::testing; + +/// Test that a valid object is returned. +TEST(BlueZHFPTest, test_createSucceeds) { + ASSERT_THAT(BlueZHFP::create(), NotNull()); +} + +/// Test the correct SDP record is returned. +TEST(BlueZHFPTest, test_checkSDPRecord) { + auto sdp = BlueZHFP::create()->getRecord(); + ASSERT_EQ(sdp->getUuid(), std::string(avsCommon::sdkInterfaces::bluetooth::services::HFPInterface::UUID)); + ASSERT_EQ(sdp->getName(), std::string(avsCommon::sdkInterfaces::bluetooth::services::HFPInterface::NAME)); +} + +} // namespace test +} // namespace blueZ +} // namespace bluetoothImplementations +} // namespace alexaClientSDK diff --git a/BluetoothImplementations/BlueZ/test/BlueZHIDTest.cpp b/BluetoothImplementations/BlueZ/test/BlueZHIDTest.cpp new file mode 100644 index 00000000..51bd6b3a --- /dev/null +++ b/BluetoothImplementations/BlueZ/test/BlueZHIDTest.cpp @@ -0,0 +1,43 @@ +/* + * Copyright 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 +#include + +#include "BlueZ/BlueZHID.h" + +namespace alexaClientSDK { +namespace bluetoothImplementations { +namespace blueZ { +namespace test { + +using namespace ::testing; + +/// Test that a valid object is returned. +TEST(BlueZHIDTest, test_createSucceeds) { + ASSERT_THAT(BlueZHID::create(), NotNull()); +} + +/// Test the correct SDP record is returned. +TEST(BlueZHIDTest, test_checkSDPRecord) { + auto sdp = BlueZHID::create()->getRecord(); + ASSERT_EQ(sdp->getUuid(), std::string(avsCommon::sdkInterfaces::bluetooth::services::HIDInterface::UUID)); + ASSERT_EQ(sdp->getName(), std::string(avsCommon::sdkInterfaces::bluetooth::services::HIDInterface::NAME)); +} + +} // namespace test +} // namespace blueZ +} // namespace bluetoothImplementations +} // namespace alexaClientSDK \ No newline at end of file diff --git a/BluetoothImplementations/BlueZ/test/BlueZSPPTest.cpp b/BluetoothImplementations/BlueZ/test/BlueZSPPTest.cpp new file mode 100644 index 00000000..1dea84c4 --- /dev/null +++ b/BluetoothImplementations/BlueZ/test/BlueZSPPTest.cpp @@ -0,0 +1,43 @@ +/* + * Copyright 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 +#include + +#include "BlueZ/BlueZSPP.h" + +namespace alexaClientSDK { +namespace bluetoothImplementations { +namespace blueZ { +namespace test { + +using namespace ::testing; + +/// Test that a valid object is returned. +TEST(BlueZSPPTest, test_createSucceeds) { + ASSERT_THAT(BlueZSPP::create(), NotNull()); +} + +/// Test the correct SDP record is returned. +TEST(BlueZSPPTest, test_checkSDPRecord) { + auto sdp = BlueZSPP::create()->getRecord(); + ASSERT_EQ(sdp->getUuid(), std::string(avsCommon::sdkInterfaces::bluetooth::services::SPPInterface::UUID)); + ASSERT_EQ(sdp->getName(), std::string(avsCommon::sdkInterfaces::bluetooth::services::SPPInterface::NAME)); +} + +} // namespace test +} // namespace blueZ +} // namespace bluetoothImplementations +} // namespace alexaClientSDK \ No newline at end of file diff --git a/BluetoothImplementations/BlueZ/test/MPRISPlayerTest.cpp b/BluetoothImplementations/BlueZ/test/MPRISPlayerTest.cpp index e515916c..5e48949a 100644 --- a/BluetoothImplementations/BlueZ/test/MPRISPlayerTest.cpp +++ b/BluetoothImplementations/BlueZ/test/MPRISPlayerTest.cpp @@ -101,13 +101,13 @@ MPRISPlayerTest::MPRISPlayerTest() { m_mockMedia = std::make_shared>(); m_mockListener = std::shared_ptr(new MockListener()); m_eventBus = std::make_shared(); - m_eventBus->addListener({avsCommon::utils::bluetooth::BluetoothEventType::AVRCP_COMMAND_RECEIVED}, m_mockListener); + m_eventBus->addListener({avsCommon::utils::bluetooth::BluetoothEventType::MEDIA_COMMAND_RECEIVED}, m_mockListener); } MPRISPlayerTest::~MPRISPlayerTest() { m_player.reset(); m_eventBus->removeListener( - {avsCommon::utils::bluetooth::BluetoothEventType::AVRCP_COMMAND_RECEIVED}, m_mockListener); + {avsCommon::utils::bluetooth::BluetoothEventType::MEDIA_COMMAND_RECEIVED}, m_mockListener); m_eventBus.reset(); } @@ -161,43 +161,43 @@ TEST_F(MPRISPlayerTest, test_playerRegistration) { * The following tests are for the org.mpris.MediaPlayer2.Player methods that the SDK MPRIS player supports. */ -/// Parameterized test fixture for supported org.mpris.MediaPlayer2.Player DBus AVRCP Methods. -class MPRISPlayerSupportedAVRCPTest +/// Parameterized test fixture for supported org.mpris.MediaPlayer2.Player DBus Media Methods. +class MPRISPlayerSupportedMediaTest : public MPRISPlayerTest - , public ::testing::WithParamInterface> {}; + , public ::testing::WithParamInterface> {}; INSTANTIATE_TEST_CASE_P( Parameterized, - MPRISPlayerSupportedAVRCPTest, + MPRISPlayerSupportedMediaTest, ::testing::Values( - std::make_pair("Play", AVRCPCommand::PLAY), - std::make_pair("Pause", AVRCPCommand::PAUSE), - std::make_pair("Next", AVRCPCommand::NEXT), - std::make_pair("Previous", AVRCPCommand::PREVIOUS)), + std::make_pair("Play", MediaCommand::PLAY), + std::make_pair("Pause", MediaCommand::PAUSE), + std::make_pair("Next", MediaCommand::NEXT), + std::make_pair("Previous", MediaCommand::PREVIOUS)), // Append method name to the test case name. - [](testing::TestParamInfo> info) { return info.param.first; }); + [](testing::TestParamInfo> info) { return info.param.first; }); /** - * Custom matcher for AVRCPCommand expectations. This matcher is only used for @c BluetoothEvent objects. + * Custom matcher for MediaCommand expectations. This matcher is only used for @c BluetoothEvent objects. * - * Returns true if the event is of type @c AVRCP_COMMAND_RECEIVED and contains the expected @c AVRCPCommand. + * Returns true if the event is of type @c MEDIA_COMMAND_RECEIVED and contains the expected @c MediaCommand. */ -MATCHER_P(ContainsAVRCPCommand, /* AVRCPCommand */ cmd, "") { +MATCHER_P(ContainsMediaCommand, /* MediaCommand */ cmd, "") { // Throw obvious compile error if not a BluetoothEvent. const BluetoothEvent* event = &arg; - return BluetoothEventType::AVRCP_COMMAND_RECEIVED == event->getType() && nullptr != event->getAVRCPCommand() && - cmd == *(event->getAVRCPCommand()); + return BluetoothEventType::MEDIA_COMMAND_RECEIVED == event->getType() && nullptr != event->getMediaCommand() && + cmd == *(event->getMediaCommand()); } -/// Test that a supported DBus method sends the correct corresponding AVRCPCommand. -TEST_P(MPRISPlayerSupportedAVRCPTest, test_avrcpCommand) { +/// Test that a supported DBus method sends the correct corresponding MediaCommand. +TEST_P(MPRISPlayerSupportedMediaTest, test_mediaCommand) { std::string dbusMethodName; - AVRCPCommand command; + MediaCommand command; std::tie(dbusMethodName, command) = GetParam(); - EXPECT_CALL(*m_mockListener, onEventFired(ContainsAVRCPCommand(command))).Times(1); + EXPECT_CALL(*m_mockListener, onEventFired(ContainsMediaCommand(command))).Times(1); auto eventThread = std::thread(&MPRISPlayerTest::gMainLoop, this); ASSERT_NE(m_nameAcquiredPromise.get_future().wait_for(std::chrono::seconds(1)), std::future_status::timeout); @@ -222,7 +222,7 @@ TEST_P(MPRISPlayerSupportedAVRCPTest, test_avrcpCommand) { * The following tests are for the org.mpris.MediaPlayer2.Player methods that the SDK MPRIS player does not support. */ -/// Parameterized test fixture for unsupported DBus AVRCP Methods. +/// Parameterized test fixture for unsupported DBus Media Methods. class MPRISPlayerUnsupportedTest : public MPRISPlayerTest , public ::testing::WithParamInterface {}; diff --git a/CHANGELOG.md b/CHANGELOG.md index c7137bd3..2ad2bd7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,105 +1,108 @@ ## ChangeLog -### Version 1.17.0 - December 10 2019 +### Version 1.18.0 - February 19 2020 -**Enhancements** +**Enhancements**  -- Added support for [captions for TTS](https://developer.amazon.com/docs/alexa/avs-device-sdk/features.html#captions). This enhancement allows you to print on-screen captions for Alexa voice responses. -- Added support for [SpeechSynthesizer Interface 1.3](https://developer.amazon.com/docs/alexa/alexa-voice-service/speechsynthesizer.html). This interface supports the new `captions` parameter. -- Added support for [AudioPlayer Interface 1.3](https://developer.amazon.com/docs/alexa/alexa-voice-service/audioplayer.html). This interface supports the new `captions` parameter. -- Added support for [Interaction Model 1.2](https://developer.amazon.com/docs/alexa/alexa-voice-service/interactionmodel-interface.html). -- Added support for [System 2.0](https://developer.amazon.com/docs/alexa/alexa-voice-service/system.html). -- Added support for Alarms 1.4 -- Added support for Alarm Volume Ramp (Ascending Alarms in the Companion App). This feature lets you fade in alarms for a more pleasant experience. You enable Alarm Volume Ramp in the Sample App through the settings menu. -- Added support for URI path extensions to CertifiedSender. This change allows you to specify the URI path extension when sending messages with [`CertifiedSender::sendJSONMessage`](https://alexa.github.io/avs-device-sdk/classalexa_client_s_d_k_1_1certified_sender_1_1_certified_sender.html#a4c0706d79717b226ba77d1a9c3280fe6). -- Added new [`Metrics`](https://alexa.github.io/avs-device-sdk/classalexa_client_s_d_k_1_1avs_common_1_1utils_1_1_metrics.html) interfaces and helper classes. These additions help you create and consume [`Metrics`](https://alexa.github.io/avs-device-sdk/classalexa_client_s_d_k_1_1avs_common_1_1utils_1_1_metrics.html) events. - - **Interfaces** - `MetricRecorderInterface`, `MetricSinkInterface`. - - **Helper Classes** - `DataPointStringBuilder`, `DataPointCounterBuilder`, `DataPointDurationBuilder`, `MetricEventBuilder`. - -- Added support for the following AVS [endpoint](https://developer.amazon.com/docs/alexa/avs-device-sdk/endpoints.html) controller capabilities: - - [Alexa.ModeController](https://developer.amazon.com/docs/alexa/alexa-voice-service/alexa-modecontroller.html) - - [Alexa.RangeController](https://developer.amazon.com/docs/alexa/alexa-voice-service/alexa-rangecontroller.html) - - [Alexa.PowerController](https://developer.amazon.com/docs/alexa/alexa-voice-service/alexa-powercontroller.html) - - [Alexa.ToggleController](https://developer.amazon.com/docs/alexa/alexa-voice-service/alexa-togglecontroller.html) - -- Added `PowerResourceManagerInterface`. This interface allows the SDK to control power resource levels for components such as the `AudioInputProcessor` and `SpeechSynthesizer`. -- Added `AlexaInterfaceCapabilityAgent`. This Capability Agent handles common directives and endpoint controller capabilities support by [`Alexa.AlexaInterface`](../alexa-voice-service/alexa.html). -- Added `AlexaInterfaceMessageSenderInterface`. This interface is required to send common events defined by the `Alexa.AlexaInterface` interface. -- Added `BufferingComplete` to [`MediaPlayerObserverInterface`](https://alexa.github.io/avs-device-sdk/classalexa_client_s_d_k_1_1avs_common_1_1utils_1_1media_player_1_1_media_player_observer_interface.html). This method helps improve performance in poor networking conditions by making sure `MediaPlayer` pre-buffers correctly. -- Added `SendDTMF` to `CallManagerInterface`. This method allows you to send DTMF tones during calls. - -**New build options** - -- CAPTIONS - - **ADDED** [`CAPTIONS`](https://developer.amazon.com/docs/alexa/avs-device-sdk/cmake-parameters.html#captions) - - **ADDED** [`LIBWEBVTT_LIB_PATH`](https://developer.amazon.com/docs/alexa/avs-device-sdk/cmake-parameters.html#captions) - - **ADDED** [`LIBWEBVTT_INCLUDE_DIR`](https://developer.amazon.com/docs/alexa/avs-device-sdk/cmake-parameters.html#captions) -- METRICS - - **ADDED** [`METRICS`](https://developer.amazon.com/docs/alexa/avs-device-sdk/cmake-parameters.html#metrics) -- ENDPONTS - - **ADDED** [`ENABLE_ALL_ENDPOINT_CONTROLLERS`](https://developer.amazon.com/docs/alexa/avs-device-sdk/cmake-parameters.html#endpoints) - - **ADDED** [`ENABLE_POWER_CONTROLLER`](https://developer.amazon.com/docs/alexa/avs-device-sdk/cmake-parameters.html#endpoints) - - **ADDED** [`ENABLE_TOGGLE_CONTROLLER`](https://developer.amazon.com/docs/alexa/avs-device-sdk/cmake-parameters.html#endpoints) - - **ADDED** [`ENABLE_RANGE_CONTROLLER`](https://developer.amazon.com/docs/alexa/avs-device-sdk/cmake-parameters.html#endpoints) - - **ADDED** [`ENABLE_MODE_CONTROLLER`](https://developer.amazon.com/docs/alexa/avs-device-sdk/cmake-parameters.html#endpoints) - -**New dependencies** - -- To use captions, you must install a [new dependency](https://developer.amazon.com/docs/alexa/avs-device-sdk/dependencies.html) – the [libwebvtt parsing library](https://github.com/alexa/webvtt). Webvtt is a C/C++ library for interpreting and authoring conformant WebVTT content. WebVTT is a caption and subtitle format designed for use with HTML5 audio and video elements. +* Added support for [Bluetooth Interface 2.0](https://developer.amazon.com/docs/alexa/alexa-voice-service/bluetooth.html). This interface adds support for multiple simultaneous connections to Bluetooth peripherals. +* Added support for [Audio Focus Manager Library (AFML) Multi Activity](https://developer.amazon.com/docs/alexa/avs-device-sdk/sdk-interaction-model.html). This interface enhances the behavior of a device so it can handle more than one Activity per Channel. +* Added the `obfuscatePrivateData` logging method to help remove certain data from logs. +* Updated `MediaPlayerObserverInterface` to include metadata about playback states. +* Added SDK extension point. You can integrate CMake projects into the SDK without cloning those projects into a subdirectory. **Bug fixes** -- Fixed [`MimeResponseSink::onReceiveNonMimeData`](https://alexa.github.io/avs-device-sdk/classalexa_client_s_d_k_1_1acl_1_1_mime_response_sink.html) [data issue](https://github.com/alexa/avs-device-sdk/issues/1519) that returned invalid data. -- Fixed [data type issue](https://github.com/alexa/avs-device-sdk/issues/1519) that incorrectly used `finalResponseCode` instead of [`FinalResponseCodeId`](https://github.com/alexa/avs-device-sdk/blob/master/AVSCommon/Utils/src/LibcurlUtils/LibCurlHttpContentFetcher.cpp#L370). -- Fixed [`UrlContentToAttachmentConverter`](https://alexa.github.io/avs-device-sdk/classalexa_client_s_d_k_1_1playlist_parser_1_1_url_content_to_attachment_converter.html) issue that used the incorrect range parameter. -- Fixed `FinallyGuard` [linking issue](https://github.com/alexa/avs-device-sdk/issues/1517) that caused problems compiling the SDK on iOS. -- Fixed [issue](https://github.com/alexa/avs-device-sdk/issues/1566) that caused a listening state after speaking the Wake Word "Alexa" twice rapidly. - +* Fixed Mac/OSX issue that caused an unresponsive Sample App when not connected to the internet. +* Fixed issue that prevented sample app from exiting various states. +* Fixed `UIManager` issue that caused an error in the logs when the device with built without the wake word enabled. +* Fixed volume issue that caused timers to ascend in volume when setting up ascending alarms. +* Fixed alert volume issue that caused any changes to the alert volume to notify observers. +* Fixed EQ issue where changes to the EQ band levels didn't notify observers. +* Fixed Bluetooth bug that caused short notification sounds from a connected phone to stop audio playback on the device. + **Known Issues** +* Build errors can occur on the Raspberry Pi due to incorrect linking of the atomic library. A suggested workaround is to add the following `set(CMAKE_CXX_LINK_FLAGS "${CMAKE_CXX_LINK_FLAGS} -latomic")` to the top most CMake file. * The WebVTT dependency required for `captions` isn't supported for Windows/Android. -* AudioInputProcessor unit tests don't build on Windows when -DCMAKE_BUILD_TYPE=DEBUG. +* Exiting from the setting option takes you back to the Options Menu directly. It doesn't provide a message to indicate that you're back in the main menu. +* Failing Unit Tests and AIP Unit tests are disabled on Windows +* `AudioInputProcessor` unit tests don't build on Windows when with the `-DCMAKE_BUILD_TYPE=DEBUG` cmake parameter. * Music playback history isn't displayed in the Alexa app for certain account and device types. * When using Gnu Compiler Collection 8+ (GCC 8+), `-Wclass-memaccess` triggers warnings. You can ignore these, they don't cause the build to fail. * Android error `libDefaultClient.so not found` might occur. Resolve this by upgrading to ADB version 1.0.40. -* If a device loses a network connection, the lost connection status isn't returned via local TTS. +* If a device loses a network connection, the lost connection status isn't returned though local TTS. * ACL encounters issues if it receives audio attachments but doesn't consume them. -* `SpeechSynthesizerState` uses `GAINING_FOCUS` and `LOSING_FOCUS` as a workaround for handling intermediate states. -* Media steamed through Bluetooth might abruptly stop. To restart playback, resume the media in the source application or toggle next/previous. +* Media streamed through Bluetooth might abruptly stop. To restart playback, resume the media in the source application or toggle next/previous. * If a connected Bluetooth device is inactive, the Alexa app might indicates that audio is playing. * The Bluetooth agent assumes that the Bluetooth adapter is always connected to a power source. Disconnecting from a power source during operation isn't yet supported. * When using some products, interrupted Bluetooth playback might not resume if other content is locally streamed. * `make integration` isn't available for Android. To run Android integration tests, manually upload the test binary and input file and run ADB. -* Alexa might truncate the beginning of speech when responding to text-to-speech (TTS) user events. This only impacts Raspberry Pi devices running Android Things with HDMI output audio. +* Alexa might truncate the beginning of speech when responding to text-to-speech (TTS) user events. This only impacts Raspberry Pi devices running Android Things with HDMI output audio. * A reminder TTS message doesn't play if the sample app restarts and loses a network connection. Instead, the default alarm tone plays twice. * `ServerDisconnectIntegratonTest` tests are disabled until they are updated to reflect new service behavior. * The `DirectiveSequencerTest.test_handleBlockingThenImmediatelyThenNonBockingOnSameDialogId` test fails intermittently. -* On some devices, Alexa gets stuck in a listening state. Pressing `t` and `h` in the Sample App doesn't exit the listening state. -* Exiting the settings menu doesn't provide a message to indicate that you're back in the main menu. - -### Version 1.16.0 - October 25 2019 +### Version 1.17.0 - December 10 2019 **Enhancements**  -- Added support for [SpeechSynthesizer version 1.2](https://github.com/alexa/avs-device-sdk/wiki/SpeechSynthesizer-Interface-v1.2) which includes the new `playBehaviour` directive. -- Added support for pre-buffering in the [`AudioPlayer`](https://alexa.github.io/avs-device-sdk/classalexa_client_s_d_k_1_1capability_agents_1_1audio_player_1_1_audio_player.html) Capability Agent. You can optionally choose the number of instances [`MediaPlayer`](https://alexa.github.io/avs-device-sdk/namespacealexa_client_s_d_k_1_1avs_common_1_1utils_1_1media_player.html) uses in the [AlexaClientSDKconfig.json](https://github.com/alexa/avs-device-sdk/blob/master/Integration/AlexaClientSDKConfig.json). **Important:** the contract for [`MediaPlayerInterface`](https://alexa.github.io/avs-device-sdk/classalexa_client_s_d_k_1_1avs_common_1_1utils_1_1media_player_1_1_media_player_interface.html)has changed. You must now make sure that the `SourceId` value returned by `setSource()` is unique across all instances. -- The [`AudioPlayer`](https://alexa.github.io/avs-device-sdk/classalexa_client_s_d_k_1_1capability_agents_1_1audio_player_1_1_audio_player.html) Capability Agent is now licensed under the Amazon Software License instead of the Apache Software License. +* Added support for [captions for TTS](https://developer.amazon.com/docs/alexa/avs-device-sdk/features.html#captions). This enhancement allows you to print on-screen captions for Alexa voice responses. +* Added support for [SpeechSynthesizer Interface 1.3](https://developer.amazon.com/docs/alexa/alexa-voice-service/speechsynthesizer.html). This interface supports the new `captions` parameter. +* Added support for [AudioPlayer Interface 1.3](https://developer.amazon.com/docs/alexa/alexa-voice-service/audioplayer.html). This interface supports the new `captions` parameter. +* Added support for [Interaction Model 1.2](https://developer.amazon.com/docs/alexa/alexa-voice-service/interactionmodel-interface.html). +* Added support for [System 2.0](https://developer.amazon.com/docs/alexa/alexa-voice-service/system.html). +* Added support for Alerts Interface 1.4. + Added support for Alarm Volume Ramp (Ascending Alarms in the Companion App). This feature lets the user enable alarm fade in. You enable this feature in the sample app through the settings menu. +* Added support to use certified senders for URI path extensions. This change allows you to specify the URI path extension when sending messages with [`CertifiedSender::sendJSONMessage`](https://alexa.github.io/avs-device-sdk/classalexa_client_s_d_k_1_1certified_sender_1_1_certified_sender.html#a4c0706d79717b226ba77d1a9c3280fe6) +* Added new [`Metrics`](https://alexa.github.io/avs-device-sdk/classalexa_client_s_d_k_1_1avs_common_1_1utils_1_1_metrics.html) interfaces and helper classes. These additions help you create and consume [`Metrics`](https://alexa.github.io/avs-device-sdk/classalexa_client_s_d_k_1_1avs_common_1_1utils_1_1_metrics.html) events. + * **Interfaces** - `MetricRecorderInterface`, `MetricSinkInterface`. + * **Helper Classes** - `DataPointStringBuilder`, `DataPointCounterBuilder`, `DataPointDurationBuilder`, `MetricEventBuilder`. +* Added support for the following AVS [endpoint](https://developer.amazon.com/docs/alexa/avs-device-sdk/endpoints.html) controller capabilities: + * [Alexa.ModeController](https://developer.amazon.com/docs/alexa/alexa-voice-service/alexa-modecontroller.html) + * [Alexa.RangeController](https://developer.amazon.com/docs/alexa/alexa-voice-service/alexa-rangecontroller.html) + * [Alexa.PowerController](https://developer.amazon.com/docs/alexa/alexa-voice-service/alexa-powercontroller.html) + * [Alexa.ToggleController](https://developer.amazon.com/docs/alexa/alexa-voice-service/alexa-togglecontroller.html) +* Added `PowerResourceManagerInterface`. This interface allows the SDK to control power resource levels for components such as the `AudioInputProcessor` and `SpeechSynthesizer`. +* Added `AlexaInterfaceCapabilityAgent`. This Capability Agent handles common directives and endpoint controller capabilities support by [`Alexa.AlexaInterface`](https://developer.amazon.com/docs/alexa/alexa-voice-service/alexa.html). +* Added `AlexaInterfaceMessageSenderInterface`. Use this interface to send common events defined by the `Alexa.AlexaInterface` interface. +* Added `BufferingComplete` to [`MediaPlayerObserverInterface`](https://alexa.github.io/avs-device-sdk/classalexa_client_s_d_k_1_1avs_common_1_1utils_1_1media_player_1_1_media_player_observer_interface.html). This method helps improve performance in poor networking conditions by making sure `MediaPlayer` pre-buffers correctly. +* Added `SendDTMF` to `CallManagerInterface`. This method allows you to send DTMF tones during calls. -**Bug Fixes** +**New build options** -- Fixed Android issue that caused the build script to ignore `PKG_CONFIG_PATH`. This sometimes caused the build to use a preinstalled dependency instead of the specific version downloaded by the Android script. For example openssl). -- Fixed Android issue that prevented the Sample app from running at the same time as other applications using the microphone. Android doesn't inherently allow two applications to use the microphone. Pressing the mute button now temporarily stops Alexa from accessing the microphone. -- Added 'quit' (– q) to the settings sub menu. -- Fixed outdated dependencies issue in the Windows install script. -- Fixed reminders issue that caused Notification LEDs to stay on, even after dismissing the alert. +* CAPTIONS + * **ADDED** [`CAPTIONS`](https://developer.amazon.com/docs/alexa/avs-device-sdk/cmake-parameters.html#captions) + * **ADDED** [`LIBWEBVTT_LIB_PATH`](https://developer.amazon.com/docs/alexa/avs-device-sdk/cmake-parameters.html#captions) + * **ADDED** [`LIBWEBVTT_INCLUDE_DIR`](https://developer.amazon.com/docs/alexa/avs-device-sdk/cmake-parameters.html#captions) +* METRICS + * **ADDED** [`METRICS`](https://developer.amazon.com/docs/alexa/avs-device-sdk/cmake-parameters.html#metrics) +* ENDPONTS + * **ADDED** [`ENABLE_ALL_ENDPOINT_CONTROLLERS`](https://developer.amazon.com/docs/alexa/avs-device-sdk/cmake-parameters.html#endpoints) + * **ADDED** [`ENABLE_POWER_CONTROLLER`](https://developer.amazon.com/docs/alexa/avs-device-sdk/cmake-parameters.html#endpoints) + * **ADDED** [`ENABLE_TOGGLE_CONTROLLER`](https://developer.amazon.com/docs/alexa/avs-device-sdk/cmake-parameters.html#endpoints) + * **ADDED** [`ENABLE_RANGE_CONTROLLER`](https://developer.amazon.com/docs/alexa/avs-device-sdk/cmake-parameters.html#endpoints) + * **ADDED** [`ENABLE_MODE_CONTROLLER`](https://developer.amazon.com/docs/alexa/avs-device-sdk/cmake-parameters.html#endpoints) + +**New dependencies** + +* To use captions, you must install a [new dependency](https://developer.amazon.com/docs/alexa/avs-device-sdk/dependencies.html) – the [libwebvtt parsing library](https://github.com/alexa/webvtt). WebVTT is a C/C++ library for interpreting and authoring WebVTT content. WebVTT is a caption and subtitle format designed for use with HTML5 audio and video elements. + +**Bug fixes** + +* Fixed [`MimeResponseSink::onReceiveNonMimeData`](https://alexa.github.io/avs-device-sdk/classalexa_client_s_d_k_1_1acl_1_1_mime_response_sink.html) [data issue](https://github.com/alexa/avs-device-sdk/issues/1519) that returned invalid data. +* Fixed [data type issue](https://github.com/alexa/avs-device-sdk/issues/1519) that incorrectly used `finalResponseCode` instead of [`FinalResponseCodeId`](https://github.com/alexa/avs-device-sdk/blob/master/AVSCommon/Utils/src/LibcurlUtils/LibCurlHttpContentFetcher.cpp#L370) +* Fixed [`UrlContentToAttachmentConverter`](https://alexa.github.io/avs-device-sdk/classalexa_client_s_d_k_1_1playlist_parser_1_1_url_content_to_attachment_converter.html) issue that used the incorrect range parameter. +* Fixed `FinallyGuard` [linking issue](https://github.com/alexa/avs-device-sdk/issues/1517) that caused problems compiling the SDK on iOS. +* Fixed bug when you spoke the Wake Word "Alexa" twice rapidly. **Known Issues** +* The WebVTT dependency required for `captions` isn't supported for Windows/Android. +* `AudioInputProcessor` unit tests don't build on Windows when with the `-DCMAKE_BUILD_TYPE=DEBUG` cmake parameter. * Music playback history isn't displayed in the Alexa app for certain account and device types. * When using Gnu Compiler Collection 8+ (GCC 8+), `-Wclass-memaccess` triggers warnings. You can ignore these, they don't cause the build to fail. * Android error `libDefaultClient.so not found` might occur. Resolve this by upgrading to ADB version 1.0.40. -* If a device loses a network connection, the lost connection status isn't returned via local TTS. +* If a device loses a network connection, the lost connection status isn't returned through local TTS. * ACL encounters issues if it receives audio attachments but doesn't consume them. * `SpeechSynthesizerState` uses `GAINING_FOCUS` and `LOSING_FOCUS` as a workaround for handling intermediate states. * Media streamed through Bluetooth might abruptly stop. To restart playback, resume the media in the source application or toggle next/previous. @@ -107,14 +110,29 @@ * The Bluetooth agent assumes that the Bluetooth adapter is always connected to a power source. Disconnecting from a power source during operation isn't yet supported. * When using some products, interrupted Bluetooth playback might not resume if other content is locally streamed. * `make integration` isn't available for Android. To run Android integration tests, manually upload the test binary and input file and run ADB. -* Alexa might truncate the beginning of speech when responding to text-to-speech (TTS) user events. This only impacts Raspberry Pi devices running Android Things with HDMI output audio. +* Alexa might truncate the beginning of speech when responding to text-to-speech (TTS) user events. This only impacts Raspberry Pi devices running Android Things with HDMI output audio. * A reminder TTS message doesn't play if the sample app restarts and loses a network connection. Instead, the default alarm tone plays twice. * `ServerDisconnectIntegratonTest` tests are disabled until they are updated to reflect new service behavior. -* Bluetooth initialization must complete before connecting devices, otherwise devices are ignored. * The `DirectiveSequencerTest.test_handleBlockingThenImmediatelyThenNonBockingOnSameDialogId` test fails intermittently. -* On some devices, Alexa gets stuck in an listening state. Pressing `t` and `h` in the Sample App doesn't exit the listening state. +* On some devices, Pressing `t` and `h` in the Sample App doesn't exit the assigned state. * Exiting the settings menu doesn't provide a message to indicate that you're back in the main menu. +### Version 1.16.0 - October 25 2019 + +**Enhancements**  + +- Added support for [SpeechSynthesizer version 1.2](https://github.com/alexa/avs-device-sdk/wiki/SpeechSynthesizer-Interface-v1.2) which includes the new `playBehaviour` directive. +- Added support for pre-buffering in the [`AudioPlayer`](https://alexa.github.io/avs-device-sdk/classalexa_client_s_d_k_1_1capability_agents_1_1audio_player_1_1_audio_player.html) Capability Agent. You can optionally choose the number of instances [`MediaPlayer`](https://alexa.github.io/avs-device-sdk/namespacealexa_client_s_d_k_1_1avs_common_1_1utils_1_1media_player.html) uses in the [AlexaClientSDKconfig.json](https://github.com/alexa/avs-device-sdk/blob/master/Integration/AlexaClientSDKConfig.json). **Important:** the contract for [`MediaPlayerInterface`](https://alexa.github.io/avs-device-sdk/classalexa_client_s_d_k_1_1avs_common_1_1utils_1_1media_player_1_1_media_player_interface.html) has changed. You must now make sure that the `SourceId` value returned by `setSource()` is unique across all instances. +- The [`AudioPlayer`](https://alexa.github.io/avs-device-sdk/classalexa_client_s_d_k_1_1capability_agents_1_1audio_player_1_1_audio_player.html) Capability Agent is now licensed under the Amazon Software License instead of the Apache Software License. + +**Bug Fixes** + +- Fixed Android issue that caused the build script to ignore `PKG_CONFIG_PATH`. This sometimes caused the build to use a preinstalled dependency instead of the specific version downloaded by the Android script. For example openssl). +- Fixed Android issue that prevented the Sample app from running at the same time as other applications using the microphone. Android doesn't inherently allow two applications to use the microphone. Pressing the mute button now temporarily stops Alexa from accessing the microphone. +- Added 'quit' (– q) to the settings sub menu. +- Fixed outdated dependencies issue in the Windows install script. +- Fixed reminders issue that caused Notification LEDs to stay on, even after dismissing the alert. + ### v1.15.0 released 09/25/2019: **Enhancements** @@ -377,7 +395,7 @@ * Fixed a bug in which `ExternalMediaPlayer` adapter playback wasn't being recognized by AVS. * [Issue 973](https://github.com/alexa/avs-device-sdk/issues/973) - Fixed issues related to `AudioPlayer` where progress reports were being sent out of order or with incorrect offsets. * An `EXPECTING`, state has been added to `DialogUXState` in order to handle `EXPECT_SPEECH` state for hold-to-talk devices. -* [Issue 948](https://github.com/alexa/avs-device-sdk/issues/948) - Fixed a bug in which the sample app was stuck in a listening state. +* [Issue 948](https://github.com/alexa/avs-device-sdk/issues/948) - Fixed a bug in which the sample app gets stuck various states. * Fixed a bug where there was a delay between receiving a `DeleteAlert` directive, and deleting the alert. * [Issue 839](https://github.com/alexa/avs-device-sdk/issues/839) - Fixed an issue where speech was being truncated due to the `DialogUXStateAggregator` transitioning between a `THINKING` and `IDLE` state. * Fixed a bug in which the `AudioPlayer` attempted to play when it wasn't in the `FOREGROUND` focus. @@ -738,7 +756,7 @@ * Implemented PlaylistParser. * **Bug fixes**: - * AIP getting stuck in `LISTENING` or `THINKING` and refusing user input on network outage. + * AIP getting stuck in various states and refusing user input on network outage. * SampleApp crashing if running for 5 minutes after network disconnect. * Issue where on repeated user barge-ins, `AudioPlayer` would not pause. Specifically, the third attempt to “Play iHeartRadio” would not result in currently-playing music pausing. * Utterances being ignored after particularly long TTS. @@ -760,7 +778,7 @@ * Alexa's responses are cut off by about half a second when asking "What's up" or barging into an active alarm to ask the time. * Switching from Kindle to Amazon Music after pausing and resuming Kindle doesn't work. * Pause/resume on Amazon Music causes entire song to start over. -* Stuck in listening state if `ExpectSpeech` comes in when the microphone has been turned off. +* Stuck in various states if `ExpectSpeech` comes in when the microphone has been turned off. * Pausing and resuming Pandora causes stuttering, looped audio. * Audible features are not fully supported. * `Recognize` event after regaining network connection and during an alarm going off can cause client to get stuck in `Recognizing` state. @@ -794,7 +812,7 @@ * Fixed an issue with `AudioPlayer` barge-in which was preventing subsequent audio from playing. * Changed `Mediaplayer` buffering to reduce stuttering. * Known Issues: - * Connection loss during listening keeps the app in that state even after connection is regained. Pressing ‘s’ unsticks the state. + * Connection loss during various states keeps the app in that state even after connection is regained. Pressing ‘s’ unsticks the state. * Play/Pause media restarts it from the beginning. * `SpeechSynthesizer` shows wrong UX state during a burst of Speaks. * Quitting the sample app while `AudioPlayer` is playing something causes a segmentation fault. @@ -826,7 +844,7 @@ * `AlertsCapabilityAgent` * Satisfies the [AVS specification](https://developer.amazon.com/public/solutions/alexa/alexa-voice-service/reference/timers-and-alarms-conceptual-overview) except for sending retrospective Events. For example, sending `AlertStarted` Event for an Alert which rendered when there was no internet connection. * `Sample App`: - * Any connection loss during the `Listening` state keeps the app stuck in that state, unless the ongoing interaction is manually stopped by the user. + * Any connection loss during various states keeps the app stuck in that state, unless the ongoing interaction is manually stopped by the user. * The user must wait several seconds after starting up the sample app before the sample app is properly usable. * `Tests`: * `SpeechSynthesizer` unit tests hang on some older versions of GCC due to a tear down issue in the test suite @@ -855,7 +873,7 @@ * Alerts do not play in the background when Alexa is speaking, but will continue playing after Alexa stops speaking. * `Sample App`: * Without the refresh token being filled out in the JSON file, the sample app crashes on start up. - * Any connection loss during the `Listening` state keeps the app stuck in that state, unless the ongoing interaction is manually stopped by the user. + * Any connection loss during various states keeps the app stuck in that state, unless the ongoing interaction is manually stopped by the user. * At the end of a shopping list with more than 5 items, the interaction in which Alexa asks the user if he/she would like to hear more does not finish properly. * `Tests`: * `SpeechSynthesizer` unit tests hang on some older versions of GCC due to a tear down issue in the test suite @@ -962,4 +980,3 @@ ### [0.1.0] - 2017-02-10 * Initial release of the `Alexa Communications Library` (ACL), a component which manages network connectivity with AVS, and `AuthDelegate`, a component which handles user authorization with AVS. - diff --git a/CMakeLists.txt b/CMakeLists.txt index 7b2de617..9deb2602 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,12 +1,15 @@ cmake_minimum_required(VERSION 3.1 FATAL_ERROR) # Set project information -project(AlexaClientSDK VERSION 1.17.0 LANGUAGES CXX) +project(AlexaClientSDK VERSION 1.18.0 LANGUAGES CXX) set(PROJECT_BRIEF "A cross-platform, modular SDK for interacting with the Alexa Voice Service") if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/CapabilityAgents/ExternalMediaPlayer/src/ExternalMediaPlayerAdapters") set(HAS_EXTERNAL_MEDIA_PLAYER_ADAPTERS ON) endif() +# This variable should be used by extension packages to include cmake files from this project. +get_filename_component(AVS_CORE . ABSOLUTE) + include(build/BuildDefaults.cmake) include(tools/Testing.cmake) @@ -23,6 +26,7 @@ configure_file ( # Alexa Client SDK targets. add_subdirectory("ThirdParty") add_subdirectory("AVSCommon") +add_subdirectory("Metrics") add_subdirectory("ACL") add_subdirectory("ADSL") add_subdirectory("AFML") @@ -35,6 +39,7 @@ add_subdirectory("BluetoothImplementations") add_subdirectory("EqualizerImplementations") add_subdirectory("ContextManager") add_subdirectory("CapabilitiesDelegate") +add_subdirectory("InterruptModel") add_subdirectory("PlaylistParser") add_subdirectory("KWD") add_subdirectory("CapabilityAgents") @@ -47,7 +52,9 @@ add_subdirectory("SpeechEncoder") add_subdirectory("Storage") add_subdirectory("SynchronizeStateSender") add_subdirectory("doc") -add_subdirectory("Metrics") + +include(build/cmake/ExtensionPath.cmake) +add_extension_projects() # Create .pc pkg-config file include(build/cmake/GeneratePkgConfig.cmake) diff --git a/CapabilitiesDelegate/include/CapabilitiesDelegate/CapabilitiesDelegate.h b/CapabilitiesDelegate/include/CapabilitiesDelegate/CapabilitiesDelegate.h index 6c9093ec..19075688 100644 --- a/CapabilitiesDelegate/include/CapabilitiesDelegate/CapabilitiesDelegate.h +++ b/CapabilitiesDelegate/include/CapabilitiesDelegate/CapabilitiesDelegate.h @@ -1,5 +1,5 @@ /* - * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018-2020 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. @@ -98,6 +98,11 @@ public: void onDiscoveryFailure(avsCommon::sdkInterfaces::MessageRequestObserverInterface::Status status) override; /// @} + /// @name AVSGatewayManagerInterface methods + /// @{ + void onAVSGatewayChanged(const std::string& avsGateway) override; + /// @} + /// @name CustomerDataHandler Functions /// @{ void clearData() override; @@ -190,9 +195,6 @@ private: /// A map from endpoint ID to endpoint configuration. std::unordered_map m_endpointIdToConfigMap; - /// A map from endpoint ID to endpoint configuration of previously published capabilities. - std::unordered_map m_previousEndpointIdToConfigMap; - /// The mutex to synchronize access to the current @c DiscoveryEventSender std::mutex m_discoveryEventSenderMutex; diff --git a/CapabilitiesDelegate/src/CapabilitiesDelegate.cpp b/CapabilitiesDelegate/src/CapabilitiesDelegate.cpp index 65e94a30..e9d4fd52 100644 --- a/CapabilitiesDelegate/src/CapabilitiesDelegate.cpp +++ b/CapabilitiesDelegate/src/CapabilitiesDelegate.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018-2020 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. @@ -105,15 +105,6 @@ bool CapabilitiesDelegate::init() { } } - { - std::lock_guard lock{m_endpointMapMutex}; - if (!m_capabilitiesDelegateStorage->load(&m_previousEndpointIdToConfigMap)) { - ACSDK_ERROR(LX("initFailed").m("Could not load from database.")); - return false; - } - ACSDK_DEBUG5(LX(__func__).d("num endpoints stored", m_previousEndpointIdToConfigMap.size())); - } - return true; } @@ -167,6 +158,7 @@ void CapabilitiesDelegate::setCapabilitiesState( } void CapabilitiesDelegate::invalidateCapabilities() { + ACSDK_DEBUG5(LX(__func__)); if (!m_capabilitiesDelegateStorage->clearDatabase()) { ACSDK_ERROR(LX("invalidateCapabilitiesFailed").d("reason", "unable to clear database")); } @@ -227,27 +219,38 @@ std::shared_ptr CapabilitiesDelegate::createPostC std::unordered_map deletedEndpointIdToConfigPairs; ACSDK_DEBUG5(LX(__func__).d("num of endpoints registered", m_endpointIdToConfigMap.size())); + std::unordered_map storedEndpointConfig; + if (!m_capabilitiesDelegateStorage->load(&storedEndpointConfig)) { + ACSDK_ERROR(LX("createPostConnectOperationFailed").m("Could not load previous config from database.")); + return nullptr; + } + ACSDK_DEBUG5(LX(__func__).d("num endpoints stored", storedEndpointConfig.size())); + { std::lock_guard lock{m_endpointMapMutex}; - std::unordered_map storedEndpointConfigCopy = m_previousEndpointIdToConfigMap; /// Find the endpoints whose configuration has changed. for (auto& endpointIdToConfigPair : m_endpointIdToConfigMap) { - auto storedEndpointConfigIt = storedEndpointConfigCopy.find(endpointIdToConfigPair.first); - if (storedEndpointConfigIt != storedEndpointConfigCopy.end()) { + auto storedEndpointConfigIt = storedEndpointConfig.find(endpointIdToConfigPair.first); + if (storedEndpointConfigIt != storedEndpointConfig.end()) { if (endpointIdToConfigPair.second != storedEndpointConfigIt->second) { /// Endpoint configuration has been updated. + ACSDK_DEBUG5(LX(__func__) + .d("step", "endpointUpdated") + .sensitive("configuration", endpointIdToConfigPair.second)); addOrUpdateEndpointIdToConfigPairs.insert(endpointIdToConfigPair); } /// Remove this endpoint from the stored endpoint list. - storedEndpointConfigCopy.erase(endpointIdToConfigPair.first); + storedEndpointConfig.erase(endpointIdToConfigPair.first); } else { /// Endpoint has been freshly added. + ACSDK_DEBUG5( + LX(__func__).d("step", "newEndpoint").sensitive("configuration", endpointIdToConfigPair.second)); addOrUpdateEndpointIdToConfigPairs.insert(endpointIdToConfigPair); } } /// The remaining items in the stored list are the endpoints that need to be deleted. - for (auto& it : storedEndpointConfigCopy) { + for (auto& it : storedEndpointConfig) { deletedEndpointIdToConfigPairs.insert({it.first, getDeleteReportEndpointConfigJson(it.first)}); } } @@ -356,5 +359,10 @@ void CapabilitiesDelegate::resetDiscoveryEventSender() { m_currentDiscoveryEventSender.reset(); } +void CapabilitiesDelegate::onAVSGatewayChanged(const std::string& avsGateway) { + ACSDK_DEBUG5(LX(__func__)); + invalidateCapabilities(); +} + } // namespace capabilitiesDelegate } // namespace alexaClientSDK diff --git a/CapabilitiesDelegate/test/CapabilitiesDelegateTest.cpp b/CapabilitiesDelegate/test/CapabilitiesDelegateTest.cpp index 11c86841..2949da31 100644 --- a/CapabilitiesDelegate/test/CapabilitiesDelegateTest.cpp +++ b/CapabilitiesDelegate/test/CapabilitiesDelegateTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018-2020 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. @@ -110,7 +110,6 @@ void CapabilitiesDelegateTest::SetUp() { /// Expect calls to storage. EXPECT_CALL(*m_mockCapabilitiesStorage, open()).WillOnce(Return(true)); - EXPECT_CALL(*m_mockCapabilitiesStorage, load(_)).WillOnce(Return(true)); m_capabilitiesDelegate = CapabilitiesDelegate::create(m_mockAuthDelegate, m_mockCapabilitiesStorage, m_dataManager); ASSERT_NE(m_capabilitiesDelegate, nullptr); @@ -144,7 +143,6 @@ TEST_F(CapabilitiesDelegateTest, test_createMethodWithInvalidParameters) { ASSERT_EQ(instance, nullptr); EXPECT_CALL(*m_mockCapabilitiesStorage, open()).WillOnce(Return(true)); - EXPECT_CALL(*m_mockCapabilitiesStorage, load(_)).WillOnce(Return(true)); instance = CapabilitiesDelegate::create(m_mockAuthDelegate, m_mockCapabilitiesStorage, m_dataManager); ASSERT_NE(instance, nullptr); instance->shutdown(); @@ -160,17 +158,9 @@ TEST_F(CapabilitiesDelegateTest, test_init) { auto instance = CapabilitiesDelegate::create(m_mockAuthDelegate, m_mockCapabilitiesStorage, m_dataManager); ASSERT_EQ(instance, nullptr); - /// Test if load fails create method returns nullptr. - EXPECT_CALL(*m_mockCapabilitiesStorage, open()).WillOnce(Return(false)); - EXPECT_CALL(*m_mockCapabilitiesStorage, createDatabase()).WillOnce(Return(true)); - EXPECT_CALL(*m_mockCapabilitiesStorage, load(_)).WillOnce(Return(false)); - instance = CapabilitiesDelegate::create(m_mockAuthDelegate, m_mockCapabilitiesStorage, m_dataManager); - ASSERT_EQ(instance, nullptr); - /// Happy path. EXPECT_CALL(*m_mockCapabilitiesStorage, open()).WillOnce(Return(false)); EXPECT_CALL(*m_mockCapabilitiesStorage, createDatabase()).WillOnce(Return(true)); - EXPECT_CALL(*m_mockCapabilitiesStorage, load(_)).WillOnce(Return(true)); instance = CapabilitiesDelegate::create(m_mockAuthDelegate, m_mockCapabilitiesStorage, m_dataManager); ASSERT_NE(instance, nullptr); @@ -413,6 +403,14 @@ TEST_F(CapabilitiesDelegateTest, test_createPostConnectOperationWithSameEndpoint ASSERT_EQ(publisher, nullptr); } +/** + * Test if the CapabilitiesDelegate calls the clearDatabase() method when the onAVSGatewayChanged() method is called. + */ +TEST_F(CapabilitiesDelegateTest, test_onAVSGatewayChangedNotification) { + EXPECT_CALL(*m_mockCapabilitiesStorage, clearDatabase()).WillOnce(Return(true)); + m_capabilitiesDelegate->onAVSGatewayChanged("TEST_GATEWAY"); +} + } // namespace test } // namespace capabilitiesDelegate } // namespace alexaClientSDK diff --git a/CapabilityAgents/AIP/CMakeLists.txt b/CapabilityAgents/AIP/CMakeLists.txt index f0e546c4..041e63ee 100644 --- a/CapabilityAgents/AIP/CMakeLists.txt +++ b/CapabilityAgents/AIP/CMakeLists.txt @@ -1,7 +1,10 @@ cmake_minimum_required(VERSION 3.1 FATAL_ERROR) project(AIP LANGUAGES CXX) -include(../../build/BuildDefaults.cmake) +include(${AVS_CORE}/build/BuildDefaults.cmake) add_subdirectory("src") -add_subdirectory("test") +# Disabling unit test for Windows according to ACSDK-3414 +if(NOT WIN32) + add_subdirectory("test") +endif() diff --git a/CapabilityAgents/AIP/include/AIP/AudioInputProcessor.h b/CapabilityAgents/AIP/include/AIP/AudioInputProcessor.h index 25ae1819..97e6910c 100644 --- a/CapabilityAgents/AIP/include/AIP/AudioInputProcessor.h +++ b/CapabilityAgents/AIP/include/AIP/AudioInputProcessor.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -42,6 +42,7 @@ #include #include #include +#include #include #include #include @@ -107,6 +108,7 @@ public: * provider is not readable (@c avsCommon::AudioProvider::alwaysReadable). This parameter is optional and * defaults to an invalid @c avsCommon::AudioProvider. * @param powerResourceManager Power Resource Manager. + * @param metricRecorder The metric recorder. * @return A @c std::shared_ptr to the new @c AudioInputProcessor instance. */ static std::shared_ptr create( @@ -124,7 +126,8 @@ public: std::shared_ptr wakeWordsSetting = nullptr, std::shared_ptr speechEncoder = nullptr, AudioProvider defaultAudioProvider = AudioProvider::null(), - std::shared_ptr powerResourceManager = nullptr); + std::shared_ptr powerResourceManager = nullptr, + std::shared_ptr metricRecorder = nullptr); /** * Adds an observer to be notified of AudioInputProcessor state changes. @@ -186,6 +189,7 @@ public: * accepted by AVS for keyword is "ALEXA". See * https://developer.amazon.com/public/solutions/alexa/alexa-voice-service/reference/context#recognizerstate * @param KWDMetadata Wake word engine metadata. + * @param initiatorToken An optional opaque string associated with the interaction. * @return A future which is @c true if the Recognize Event was started successfully, else @c false. */ std::future recognize( @@ -195,7 +199,8 @@ public: avsCommon::avs::AudioInputStream::Index begin = INVALID_INDEX, avsCommon::avs::AudioInputStream::Index keywordEnd = INVALID_INDEX, std::string keyword = "", - std::shared_ptr> KWDMetadata = nullptr); + std::shared_ptr> KWDMetadata = nullptr, + const std::string& initiatorToken = ""); /** * This function asks the @c AudioInputProcessor to stop streaming audio and end an ongoing Recognize Event, which @@ -239,7 +244,7 @@ public: /// @name ChannelObserverInterface Functions /// @{ - void onFocusChanged(avsCommon::avs::FocusState newFocus) override; + void onFocusChanged(avsCommon::avs::FocusState newFocus, avsCommon::avs::MixingBehavior behavior) override; /// @} /// @name DialogUXStateObserverInterface Functions @@ -296,6 +301,7 @@ private: * device supports wake words. * @param capabilitiesConfiguration The SpeechRecognizer capabilities configuration. * @param powerResourceManager Power Resource Manager. + * @param metricRecorder The metric recorder. * * @note This constructor is private so that users are forced to use the @c create() factory function. The primary * reason for this is to ensure that a @c std::shared_ptr to the instance exists, which is a requirement for @@ -315,7 +321,8 @@ private: std::shared_ptr speechConfirmation, std::shared_ptr wakeWordsSetting, std::shared_ptr capabilitiesConfiguration, - std::shared_ptr powerResourceManager); + std::shared_ptr powerResourceManager, + std::shared_ptr metricRecorder); /// @name RequiresShutdown Functions /// @{ @@ -352,6 +359,18 @@ private: */ void handleSetEndOfSpeechOffsetDirective(std::shared_ptr info); + /** + * Performs operations to handle the failure of processing a directive. + * + * @param errorMessage A string containing the error message. + * @param info The @c DirectiveInfo containing the @c AVSDirective and the @c DirectiveHandlerResultInterface. + * @param errorType The type of exception encountered. + */ + void handleDirectiveFailure( + const std::string& errorMessage, + std::shared_ptr info, + avsCommon::avs::ExceptionErrorType errorType); + /** * @name Executor Thread Functions * @@ -377,6 +396,7 @@ private: * @param keywordEnd The @c Index in @c audioProvider.stream where the wakeword ends. * @param keyword The text of the keyword which was recognized. * @param KWDMetadata Wake word engine metadata. + * @param initiatorToken An optional opaque string associated with the interaction. * @return @c true if the Recognize Event was started successfully, else @c false. */ bool executeRecognize( @@ -386,7 +406,8 @@ private: avsCommon::avs::AudioInputStream::Index begin, avsCommon::avs::AudioInputStream::Index keywordEnd, const std::string& keyword, - std::shared_ptr> KWDMetadata); + std::shared_ptr> KWDMetadata, + const std::string& initiatorToken); /** * This function builds and sends a @c Recognize event. This version of the function expects a pre-built string @@ -404,11 +425,14 @@ private: * stream audio starting at the time of the @c recognize() call. If the @c initiator is @c WAKEWORD, and this * and @c keywordEnd are specified, streaming will begin between 0 and 500ms prior to the @c Index specified by * this parameter to attempt false wakeword validation. + * @param end The @c Index in @c audioProvider.stream where the wakeword ends. * @param keyword The text of the keyword which was recognized. This parameter is optional, and defaults to an * empty string. This parameter is ignored if initiator is not @c WAKEWORD. The only value currently * accepted by AVS for keyword is "ALEXA". See * https://developer.amazon.com/public/solutions/alexa/alexa-voice-service/reference/context#recognizerstate * @param KWDMetadata Wake word engine metadata. + * @param initiatedByWakeword Whether the Initiator was Wakeword; false by default. + * @param falseWakewordDetection Whether false Wakeword detection was enabled; false by default. * @return @c true if the Recognize Event was started successfully, else @c false. */ bool executeRecognize( @@ -416,8 +440,11 @@ private: const std::string& initiatorJson, std::chrono::steady_clock::time_point startOfSpeechTimestamp = std::chrono::steady_clock::now(), avsCommon::avs::AudioInputStream::Index begin = INVALID_INDEX, + avsCommon::avs::AudioInputStream::Index end = INVALID_INDEX, const std::string& keyword = "", - std::shared_ptr> KWDMetadata = nullptr); + std::shared_ptr> KWDMetadata = nullptr, + bool initiatedByWakeword = false, + bool falseWakewordDetection = false); /** * This function receives the full system context from @c ContextManager. Context requests are initiated by @@ -553,6 +580,9 @@ private: */ void managePowerResource(ObserverInterface::State newState); + /// The metric recorder. + std::shared_ptr m_metricRecorder; + /// The Directive Sequencer to register with for receiving directives. std::shared_ptr m_directiveSequencer; @@ -602,7 +632,7 @@ private: * valid during the @c RECOGNIZING state, and is retained by @c AudioInputProcessor so that it can close the * stream from @c executeStopCapture(). */ - std::shared_ptr m_reader; + std::shared_ptr m_reader; /** * The attachment reader used for the wake word engine metadata. It's is populated by a call to @c @@ -656,6 +686,12 @@ private: */ bool m_localStopCapturePerformed; + /** + * This flag indicates if a new dialogRequestId should be generated (e.g. the Recognize event is not part of a + * multi-turn interaction). + */ + bool m_shouldGenerateDialogRequestId; + /// The system sound player. std::shared_ptr m_systemSoundPlayer; @@ -686,6 +722,9 @@ private: /// The power resource manager std::shared_ptr m_powerResourceManager; + /// StopCapture received time + std::chrono::steady_clock::time_point m_stopCaptureReceivedTime; + /** * @c Executor which queues up operations from asynchronous API calls. * diff --git a/CapabilityAgents/AIP/src/AudioInputProcessor.cpp b/CapabilityAgents/AIP/src/AudioInputProcessor.cpp index 679c146f..8b0e9f0c 100644 --- a/CapabilityAgents/AIP/src/AudioInputProcessor.cpp +++ b/CapabilityAgents/AIP/src/AudioInputProcessor.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -26,6 +26,10 @@ #include #include #include +#include +#include +#include +#include #include #include #include @@ -33,6 +37,7 @@ #include #include #include +#include #include "AIP/AudioInputProcessor.h" @@ -42,6 +47,9 @@ namespace aip { using namespace avsCommon::avs; using namespace avsCommon::utils; using namespace avsCommon::utils::logger; +using namespace avsCommon::utils::metrics; +using namespace avsCommon::sdkInterfaces; +using namespace std::chrono; /// SpeechRecognizer capability constants /// SpeechRecognizer interface type @@ -80,7 +88,7 @@ static const std::string TAG("AudioInputProcessor"); #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) /// The name of the @c FocusManager channel used by @c AudioInputProvider. -static const std::string CHANNEL_NAME = avsCommon::sdkInterfaces::FocusManagerInterface::DIALOG_CHANNEL_NAME; +static const std::string CHANNEL_NAME = FocusManagerInterface::DIALOG_CHANNEL_NAME; /// The namespace for this capability agent. static const std::string NAMESPACE = "SpeechRecognizer"; @@ -94,9 +102,6 @@ static const avsCommon::avs::NamespaceAndName EXPECT_SPEECH{NAMESPACE, "ExpectSp /// The SetEndOfSpeechOffset directive signature. static const avsCommon::avs::NamespaceAndName SET_END_OF_SPEECH_OFFSET{NAMESPACE, "SetEndOfSpeechOffset"}; -/// The SpeechRecognizer context state signature. -static const avsCommon::avs::NamespaceAndName RECOGNIZER_STATE{NAMESPACE, "RecognizerState"}; - /// The SetWakeWordConfirmation directive signature. static const avsCommon::avs::NamespaceAndName SET_WAKE_WORD_CONFIRMATION{NAMESPACE, "SetWakeWordConfirmation"}; @@ -118,6 +123,9 @@ static const std::string FORMAT_KEY = "format"; /// The field identifying the initiator's type. static const std::string TYPE_KEY = "type"; +/// The field identifying the initiator's token. +static const std::string TOKEN_KEY = "token"; + /// The field identifying the initiator's payload. static const std::string PAYLOAD_KEY = "payload"; @@ -170,6 +178,124 @@ static const std::string WAKE_WORDS_PAYLOAD_KEY = "wakeWords"; /// The component name of power management static const std::string POWER_RESOURCE_COMPONENT_NAME = "AudioInputProcessor"; +/// Metric Activity Name Prefix for AIP metric source +static const std::string METRIC_ACTIVITY_NAME_PREFIX_AIP = "AIP-"; + +/// Start of Utterance Activity Name for AIP metric source +static const std::string START_OF_UTTERANCE = "START_OF_UTTERANCE"; +static const std::string START_OF_UTTERANCE_ACTIVITY_NAME = METRIC_ACTIVITY_NAME_PREFIX_AIP + START_OF_UTTERANCE; + +/// Wakeword Activity Name for AIP metric source +static const std::string START_OF_STREAM_TIMESTAMP = "START_OF_STREAM_TIMESTAMP"; +static const std::string WW_DURATION = "WW_DURATION"; +static const std::string WW_DURATION_ACTIVITY_NAME = METRIC_ACTIVITY_NAME_PREFIX_AIP + WW_DURATION; + +/// Stop Capture Received Activity Name for AIP metric source +static const std::string STOP_CAPTURE_RECEIVED = "STOP_CAPTURE"; +static const std::string STOP_CAPTURE_RECEIVED_ACTIVITY_NAME = METRIC_ACTIVITY_NAME_PREFIX_AIP + STOP_CAPTURE_RECEIVED; + +/// End of Speech Offset Received Activity Name for AIP metric source +static const std::string END_OF_SPEECH_OFFSET_RECEIVED = "END_OF_SPEECH_OFFSET"; +static const std::string END_OF_SPEECH_OFFSET_RECEIVED_ACTIVITY_NAME = + METRIC_ACTIVITY_NAME_PREFIX_AIP + END_OF_SPEECH_OFFSET_RECEIVED; + +/// The duration metric for short time out +static const std::string STOP_CAPTURE_TO_END_OF_SPEECH_METRIC_NAME = "STOP_CAPTURE_TO_END_OF_SPEECH"; +static const std::string STOP_CAPTURE_TO_END_OF_SPEECH_ACTIVITY_NAME = + METRIC_ACTIVITY_NAME_PREFIX_AIP + STOP_CAPTURE_TO_END_OF_SPEECH_METRIC_NAME; + +/// Preroll duration is a fixed 500ms. +static const std::chrono::milliseconds PREROLL_DURATION = std::chrono::milliseconds(500); + +static const int MILLISECONDS_PER_SECOND = 1000; + +/** + * Handles a Metric event by creating and recording it. Failure to create or record the event results + * in an early return. + * + * @param metricRecorder The @c MetricRecorderInterface which records Metric events. + * @param activityName The activityName of the Metric event. + * @param dataPoint The @c DataPoint of this Metric event (e.g. duration). + * @param dialogRequestId The dialogRequestId associated with this Metric event; default is empty string. + */ +static void submitMetric( + const std::shared_ptr& metricRecorder, + const std::string& activityName, + const DataPoint& dataPoint, + const std::string& dialogRequestId = "") { + auto metricEventBuilder = + MetricEventBuilder{} + .setActivityName(activityName) + .addDataPoint(dataPoint) + .addDataPoint(DataPointStringBuilder{}.setName("DIALOG_REQUEST_ID").setValue(dialogRequestId).build()); + + auto metricEvent = metricEventBuilder.build(); + + if (metricEvent == nullptr) { + ACSDK_ERROR(LX("Error creating metric with explicit dialogRequestId")); + return; + } + recordMetric(metricRecorder, metricEvent); +} + +/** + * Handles a Metric event by creating and recording it. Failure to create or record the event results + * in an early return. + * + * @param metricRecorder The @c MetricRecorderInterface which records Metric events. + * @param metricEventBuilder The @c MetricEventBuilder. + * @param dialogRequestId The dialogRequestId associated with this Metric event; default is empty string. + */ +static void submitMetric( + const std::shared_ptr& metricRecorder, + MetricEventBuilder& metricEventBuilder, + const std::string& dialogRequestId = "") { + metricEventBuilder.addDataPoint( + DataPointStringBuilder{}.setName("DIALOG_REQUEST_ID").setValue(dialogRequestId).build()); + + auto metricEvent = metricEventBuilder.build(); + + if (metricEvent == nullptr) { + ACSDK_ERROR(LX("Error creating metric with explicit dialogRequestId")); + return; + } + recordMetric(metricRecorder, metricEvent); +} + +/** + * Handles a Metric event by creating and recording it. Failure to create or record the event results + * in an early return. + * + * @param metricRecorder The @c MetricRecorderInterface which records Metric events. + * @param activityName The activityName of the Metric event. + * @param dataPoint The @c DataPoint of this Metric event (e.g. duration). + * @param directive The @c AVSDirective associated with this Metric event; default is nullptr. + */ +static void submitMetric( + const std::shared_ptr metricRecorder, + const std::string& activityName, + const DataPoint& dataPoint, + const std::shared_ptr& directive = nullptr) { + auto metricEventBuilder = MetricEventBuilder{}.setActivityName(activityName).addDataPoint(dataPoint); + + if (directive != nullptr) { + metricEventBuilder.addDataPoint( + DataPointStringBuilder{}.setName("HTTP2_STREAM").setValue(directive->getAttachmentContextId()).build()); + metricEventBuilder.addDataPoint( + DataPointStringBuilder{}.setName("DIRECTIVE_MESSAGE_ID").setValue(directive->getMessageId()).build()); + metricEventBuilder.addDataPoint( + DataPointStringBuilder{}.setName("DIALOG_REQUEST_ID").setValue(directive->getDialogRequestId()).build()); + } + + auto metricEvent = metricEventBuilder.build(); + + if (metricEvent == nullptr) { + ACSDK_ERROR(LX("Error creating metric from directive")); + return; + } + recordMetric(metricRecorder, metricEvent); +} + /** * Creates the SpeechRecognizer capability configuration. * @@ -177,24 +303,38 @@ static const std::string POWER_RESOURCE_COMPONENT_NAME = "AudioInputProcessor"; * @return The SpeechRecognizer capability configuration. */ static std::shared_ptr getSpeechRecognizerCapabilityConfiguration( - const avsCommon::sdkInterfaces::LocaleAssetsManagerInterface& assetsManager); + const LocaleAssetsManagerInterface& assetsManager); + +/** + * Checks whether a new dialogRequestId should be generated. + * + * @param state The current state. + * @return Whether a new dialogRequestId should be generated. + */ +static bool shouldGenerateDialogRequestId(AudioInputProcessor::ObserverInterface::State state) { + if (AudioInputProcessor::ObserverInterface::State::IDLE == state) { + return true; + } + return false; +} std::shared_ptr AudioInputProcessor::create( - std::shared_ptr directiveSequencer, - std::shared_ptr messageSender, - std::shared_ptr contextManager, - std::shared_ptr focusManager, + std::shared_ptr directiveSequencer, + std::shared_ptr messageSender, + std::shared_ptr contextManager, + std::shared_ptr focusManager, std::shared_ptr dialogUXStateAggregator, - std::shared_ptr exceptionEncounteredSender, - std::shared_ptr userInactivityMonitor, - std::shared_ptr systemSoundPlayer, - const std::shared_ptr& assetsManager, + std::shared_ptr exceptionEncounteredSender, + std::shared_ptr userInactivityMonitor, + std::shared_ptr systemSoundPlayer, + const std::shared_ptr& assetsManager, std::shared_ptr wakeWordConfirmation, std::shared_ptr speechConfirmation, std::shared_ptr wakeWordsSetting, std::shared_ptr speechEncoder, AudioProvider defaultAudioProvider, - std::shared_ptr powerResourceManager) { + std::shared_ptr powerResourceManager, + std::shared_ptr metricRecorder) { if (!directiveSequencer) { ACSDK_ERROR(LX("createFailed").d("reason", "nullDirectiveSequencer")); return nullptr; @@ -253,7 +393,8 @@ std::shared_ptr AudioInputProcessor::create( speechConfirmation, wakeWordsSetting, capabilitiesConfiguration, - powerResourceManager)); + powerResourceManager, + std::move(metricRecorder))); if (aip) { dialogUXStateAggregator->addObserver(aip); @@ -292,11 +433,12 @@ void AudioInputProcessor::removeObserver(std::shared_ptr obse std::future AudioInputProcessor::recognize( AudioProvider audioProvider, Initiator initiator, - std::chrono::steady_clock::time_point startOfSpeechTimestamp, + steady_clock::time_point startOfSpeechTimestamp, avsCommon::avs::AudioInputStream::Index begin, avsCommon::avs::AudioInputStream::Index keywordEnd, std::string keyword, - std::shared_ptr> KWDMetadata) { + std::shared_ptr> KWDMetadata, + const std::string& initiatorToken) { ACSDK_METRIC_IDS(TAG, "Recognize", "", "", Metrics::Location::AIP_RECEIVE); std::string upperCaseKeyword = keyword; @@ -323,11 +465,18 @@ std::future AudioInputProcessor::recognize( begin = reader->tell(); } - return m_executor.submit( - [this, audioProvider, initiator, startOfSpeechTimestamp, begin, keywordEnd, keyword, KWDMetadata]() { - return executeRecognize( - audioProvider, initiator, startOfSpeechTimestamp, begin, keywordEnd, keyword, KWDMetadata); - }); + return m_executor.submit([this, + audioProvider, + initiator, + startOfSpeechTimestamp, + begin, + keywordEnd, + keyword, + KWDMetadata, + initiatorToken]() { + return executeRecognize( + audioProvider, initiator, startOfSpeechTimestamp, begin, keywordEnd, keyword, KWDMetadata, initiatorToken); + }); } std::future AudioInputProcessor::stopCapture() { @@ -342,7 +491,7 @@ void AudioInputProcessor::onContextAvailable(const std::string& jsonContext) { m_executor.submit([this, jsonContext]() { executeOnContextAvailable(jsonContext); }); } -void AudioInputProcessor::onContextFailure(const avsCommon::sdkInterfaces::ContextRequestError error) { +void AudioInputProcessor::onContextFailure(const ContextRequestError error) { m_executor.submit([this, error]() { executeOnContextFailure(error); }); } @@ -366,6 +515,11 @@ void AudioInputProcessor::handleDirective(std::shared_ptr info) { if (info->directive->getName() == STOP_CAPTURE.name) { ACSDK_METRIC_MSG(TAG, info->directive, Metrics::Location::AIP_RECEIVE); + submitMetric( + m_metricRecorder, + STOP_CAPTURE_RECEIVED_ACTIVITY_NAME, + DataPointCounterBuilder{}.setName(STOP_CAPTURE_RECEIVED).increment(1).build(), + info->directive); handleStopCaptureDirective(info); } else if (info->directive->getName() == EXPECT_SPEECH.name) { handleExpectSpeechDirective(info); @@ -403,33 +557,34 @@ void AudioInputProcessor::onDeregistered() { resetState(); } -void AudioInputProcessor::onFocusChanged(avsCommon::avs::FocusState newFocus) { - ACSDK_DEBUG9(LX("onFocusChanged").d("newFocus", newFocus)); +void AudioInputProcessor::onFocusChanged(avsCommon::avs::FocusState newFocus, avsCommon::avs::MixingBehavior behavior) { + ACSDK_DEBUG9(LX("onFocusChanged").d("newFocus", newFocus).d("MixingBehavior", behavior)); m_executor.submit([this, newFocus]() { executeOnFocusChanged(newFocus); }); } -void AudioInputProcessor::onDialogUXStateChanged( - avsCommon::sdkInterfaces::DialogUXStateObserverInterface::DialogUXState newState) { +void AudioInputProcessor::onDialogUXStateChanged(DialogUXStateObserverInterface::DialogUXState newState) { m_executor.submit([this, newState]() { executeOnDialogUXStateChanged(newState); }); } AudioInputProcessor::AudioInputProcessor( - std::shared_ptr directiveSequencer, - std::shared_ptr messageSender, - std::shared_ptr contextManager, - std::shared_ptr focusManager, - std::shared_ptr exceptionEncounteredSender, - std::shared_ptr userInactivityMonitor, - std::shared_ptr systemSoundPlayer, + std::shared_ptr directiveSequencer, + std::shared_ptr messageSender, + std::shared_ptr contextManager, + std::shared_ptr focusManager, + std::shared_ptr exceptionEncounteredSender, + std::shared_ptr userInactivityMonitor, + std::shared_ptr systemSoundPlayer, std::shared_ptr speechEncoder, AudioProvider defaultAudioProvider, std::shared_ptr wakeWordConfirmation, std::shared_ptr speechConfirmation, std::shared_ptr wakeWordsSetting, std::shared_ptr capabilitiesConfiguration, - std::shared_ptr powerResourceManager) : + std::shared_ptr powerResourceManager, + std::shared_ptr metricRecorder) : CapabilityAgent{NAMESPACE, exceptionEncounteredSender}, RequiresShutdown{"AudioInputProcessor"}, + m_metricRecorder{metricRecorder}, m_directiveSequencer{directiveSequencer}, m_messageSender{messageSender}, m_contextManager{contextManager}, @@ -444,6 +599,7 @@ AudioInputProcessor::AudioInputProcessor( m_preparingToSend{false}, m_initialDialogUXStateReceived{false}, m_localStopCapturePerformed{false}, + m_shouldGenerateDialogRequestId{true}, m_systemSoundPlayer{systemSoundPlayer}, m_precedingExpectSpeechInitiator{nullptr}, m_wakeWordConfirmation{wakeWordConfirmation}, @@ -462,7 +618,7 @@ AudioInputProcessor::AudioInputProcessor( */ std::string generateSupportedWakeWordsJson( const std::string& scope, - const avsCommon::sdkInterfaces::LocaleAssetsManagerInterface::WakeWordsSets& wakeWordsCombination) { + const LocaleAssetsManagerInterface::WakeWordsSets& wakeWordsCombination) { json::JsonGenerator generator; generator.addStringArray(CAPABILITY_INTERFACE_SCOPES_KEY, std::list({scope})); generator.addCollectionOfStringArray(CAPABILITY_INTERFACE_VALUES_KEY, wakeWordsCombination); @@ -470,7 +626,7 @@ std::string generateSupportedWakeWordsJson( } std::shared_ptr getSpeechRecognizerCapabilityConfiguration( - const avsCommon::sdkInterfaces::LocaleAssetsManagerInterface& assetsManager) { + const LocaleAssetsManagerInterface& assetsManager) { std::unordered_map configMap; configMap.insert({CAPABILITY_INTERFACE_TYPE_KEY, SPEECHRECOGNIZER_CAPABILITY_INTERFACE_TYPE}); configMap.insert({CAPABILITY_INTERFACE_NAME_KEY, SPEECHRECOGNIZER_CAPABILITY_INTERFACE_NAME}); @@ -519,6 +675,7 @@ std::future AudioInputProcessor::expectSpeechTimedOut() { } void AudioInputProcessor::handleStopCaptureDirective(std::shared_ptr info) { + m_stopCaptureReceivedTime = steady_clock::now(); m_executor.submit([this, info]() { bool stopImmediately = true; executeStopCapture(stopImmediately, info); @@ -546,10 +703,20 @@ void AudioInputProcessor::handleExpectSpeechDirective(std::shared_ptr info) { + // The duration from StopCapture to SetEndOfSpeechOffset. END_OF_SPEECH_OFFSET_RECEIVED starts from Recognize, + // it is not helpful for figuring out the best SHORT timeout. + submitMetric( + m_metricRecorder, + STOP_CAPTURE_TO_END_OF_SPEECH_ACTIVITY_NAME, + DataPointDurationBuilder{duration_cast(steady_clock::now() - m_stopCaptureReceivedTime)} + .setName(STOP_CAPTURE_TO_END_OF_SPEECH_METRIC_NAME) + .build(), + info->directive); + auto payload = info->directive->getPayload(); int64_t endOfSpeechOffset = 0; int64_t startOfSpeechTimestamp = 0; @@ -559,6 +726,12 @@ void AudioInputProcessor::handleSetEndOfSpeechOffsetDirective(std::shared_ptrdirective); std::istringstream iss{startOfSpeechTimeStampInString}; iss >> startOfSpeechTimestamp; @@ -585,11 +758,12 @@ void AudioInputProcessor::handleSetEndOfSpeechOffsetDirective(std::shared_ptr> KWDMetadata) { + std::shared_ptr> KWDMetadata, + const std::string& initiatorToken) { // Make sure we have a keyword if this is a wakeword initiator. if (Initiator::WAKEWORD == initiator && keyword.empty()) { ACSDK_ERROR(LX("executeRecognizeFailed").d("reason", "emptyKeywordWithWakewordInitiator")); @@ -622,16 +796,34 @@ bool AudioInputProcessor::executeRecognize( generator.addMember(WAKE_WORD_KEY, string::stringToUpperCase(keyword)); } - return executeRecognize(provider, generator.toString(), startOfSpeechTimestamp, begin, keyword, KWDMetadata); + if (!initiatorToken.empty()) { + generator.addMember(TOKEN_KEY, initiatorToken); + } + + bool initiatedByWakeword = (Initiator::WAKEWORD == initiator) ? true : false; + + return executeRecognize( + provider, + generator.toString(), + startOfSpeechTimestamp, + begin, + end, + keyword, + KWDMetadata, + initiatedByWakeword, + falseWakewordDetection); } bool AudioInputProcessor::executeRecognize( AudioProvider provider, const std::string& initiatorJson, - std::chrono::steady_clock::time_point startOfSpeechTimestamp, + steady_clock::time_point startOfSpeechTimestamp, avsCommon::avs::AudioInputStream::Index begin, + avsCommon::avs::AudioInputStream::Index end, const std::string& keyword, - std::shared_ptr> KWDMetadata) { + std::shared_ptr> KWDMetadata, + bool initiatedByWakeword, + bool falseWakewordDetection) { if (!provider.stream) { ACSDK_ERROR(LX("executeRecognizeFailed").d("reason", "nullAudioInputStream")); return false; @@ -715,8 +907,8 @@ bool AudioInputProcessor::executeRecognize( } if (settings::WakeWordConfirmationSettingType::TONE == m_wakeWordConfirmation->get()) { - m_systemSoundPlayer->playTone( - avsCommon::sdkInterfaces::SystemSoundPlayerInterface::Tone::WAKEWORD_NOTIFICATION); + m_executor.submit( + [this]() { m_systemSoundPlayer->playTone(SystemSoundPlayerInterface::Tone::WAKEWORD_NOTIFICATION); }); } // Check if SpeechEncoder is available @@ -746,11 +938,10 @@ bool AudioInputProcessor::executeRecognize( // Set up an attachment reader for the event. avsCommon::avs::attachment::InProcessAttachmentReader::SDSTypeIndex offset = 0; - avsCommon::avs::attachment::InProcessAttachmentReader::SDSTypeReader::Reference reference = - avsCommon::avs::attachment::InProcessAttachmentReader::SDSTypeReader::Reference::BEFORE_WRITER; + auto reference = AudioInputStream::Reader::Reference::BEFORE_WRITER; if (INVALID_INDEX != begin) { offset = begin; - reference = avsCommon::avs::attachment::InProcessAttachmentReader::SDSTypeReader::Reference::ABSOLUTE; + reference = AudioInputStream::Reader::Reference::ABSOLUTE; } // Set up the speech encoder @@ -761,12 +952,12 @@ bool AudioInputProcessor::executeRecognize( return false; } offset = 0; - reference = avsCommon::avs::attachment::InProcessAttachmentReader::SDSTypeReader::Reference::BEFORE_WRITER; + reference = AudioInputStream::Reader::Reference::BEFORE_WRITER; } else { ACSDK_DEBUG(LX("notEncodingAudio")); } - m_reader = avsCommon::avs::attachment::InProcessAttachmentReader::create( + m_reader = attachment::DefaultAttachmentReader::create( sds::ReaderPolicy::NONBLOCKING, shouldBeEncoded ? m_encoder->getEncodedStream() : provider.stream, offset, @@ -785,9 +976,9 @@ bool AudioInputProcessor::executeRecognize( // Code below this point changes the state of AIP. Formally update state now, and don't error out without calling // executeResetState() after this point. - setState(ObserverInterface::State::RECOGNIZING); + m_shouldGenerateDialogRequestId = shouldGenerateDialogRequestId(m_state); - m_directiveSequencer->setDialogRequestId(uuidGeneration::generateUUID()); + setState(ObserverInterface::State::RECOGNIZING); // Note that we're preparing to send a Recognize event. m_preparingToSend = true; @@ -810,6 +1001,34 @@ bool AudioInputProcessor::executeRecognize( // We can't assemble the MessageRequest until we receive the context. m_recognizeRequest.reset(); + // Handle metrics for this event. + submitMetric( + m_metricRecorder, + START_OF_UTTERANCE_ACTIVITY_NAME, + DataPointCounterBuilder{}.setName(START_OF_UTTERANCE).increment(1).build(), + m_directiveSequencer->getDialogRequestId()); + + if (initiatedByWakeword) { + auto duration = milliseconds((end - begin) * MILLISECONDS_PER_SECOND / provider.format.sampleRateHz); + + auto startOfStreamTimestamp = startOfSpeechTimestamp; + if (falseWakewordDetection) { + startOfStreamTimestamp -= PREROLL_DURATION; + } + + submitMetric( + m_metricRecorder, + MetricEventBuilder{} + .setActivityName(WW_DURATION_ACTIVITY_NAME) + .addDataPoint(DataPointDurationBuilder{duration}.setName(WW_DURATION).build()) + .addDataPoint( + DataPointDurationBuilder{duration_cast(startOfStreamTimestamp.time_since_epoch())} + .setName(START_OF_STREAM_TIMESTAMP) + .build()), + m_directiveSequencer->getDialogRequestId()); + ACSDK_DEBUG(LX(__func__).d("WW_DURATION(ms)", duration.count())); + } + return true; } @@ -838,13 +1057,19 @@ void AudioInputProcessor::executeOnContextAvailable(const std::string jsonContex // Start acquiring the channel right away; we'll service the callback after assembling our Recognize event. if (m_focusState != avsCommon::avs::FocusState::FOREGROUND) { - if (!m_focusManager->acquireChannel(CHANNEL_NAME, shared_from_this(), NAMESPACE)) { + auto activity = FocusManagerInterface::Activity::create( + NAMESPACE, shared_from_this(), std::chrono::milliseconds::zero(), avsCommon::avs::ContentType::MIXABLE); + if (!m_focusManager->acquireChannel(CHANNEL_NAME, activity)) { ACSDK_ERROR(LX("executeOnContextAvailableFailed").d("reason", "Unable to acquire channel")); executeResetState(); return; } } + if (m_shouldGenerateDialogRequestId) { + m_directiveSequencer->setDialogRequestId(uuidGeneration::generateUUID()); + } + // Assemble the MessageRequest. It will be sent by executeOnFocusChanged when we acquire the channel. auto msgIdAndJsonEvent = buildJsonEventString("Recognize", m_directiveSequencer->getDialogRequestId(), m_recognizePayload, jsonContext); @@ -864,7 +1089,7 @@ void AudioInputProcessor::executeOnContextAvailable(const std::string jsonContex } } -void AudioInputProcessor::executeOnContextFailure(const avsCommon::sdkInterfaces::ContextRequestError error) { +void AudioInputProcessor::executeOnContextFailure(const ContextRequestError error) { ACSDK_ERROR(LX("executeOnContextFailure").d("error", error)); executeResetState(); } @@ -956,7 +1181,7 @@ bool AudioInputProcessor::executeStopCapture(bool stopImmediately, std::shared_p } if (m_speechConfirmation->get() == settings::SpeechConfirmationSettingType::TONE) { - m_systemSoundPlayer->playTone(avsCommon::sdkInterfaces::SystemSoundPlayerInterface::Tone::END_SPEECH); + m_systemSoundPlayer->playTone(SystemSoundPlayerInterface::Tone::END_SPEECH); } }; @@ -998,7 +1223,7 @@ void AudioInputProcessor::executeResetState() { setState(ObserverInterface::State::IDLE); } -bool AudioInputProcessor::executeExpectSpeech(std::chrono::milliseconds timeout, std::shared_ptr info) { +bool AudioInputProcessor::executeExpectSpeech(milliseconds timeout, std::shared_ptr info) { if (info && info->isCancelled) { ACSDK_DEBUG(LX("expectSpeechIgnored").d("reason", "isCancelled")); return true; @@ -1069,15 +1294,14 @@ bool AudioInputProcessor::executeExpectSpeechTimedOut() { return true; } -void AudioInputProcessor::executeOnDialogUXStateChanged( - avsCommon::sdkInterfaces::DialogUXStateObserverInterface::DialogUXState newState) { +void AudioInputProcessor::executeOnDialogUXStateChanged(DialogUXStateObserverInterface::DialogUXState newState) { if (!m_initialDialogUXStateReceived) { // The initial dialog UX state change call comes from simply registering as an observer; it is not a deliberate // change to the dialog state which should interrupt a recognize event. m_initialDialogUXStateReceived = true; return; } - if (newState != avsCommon::sdkInterfaces::DialogUXStateObserverInterface::DialogUXState::IDLE) { + if (newState != DialogUXStateObserverInterface::DialogUXState::IDLE) { return; } if (m_focusState != avsCommon::avs::FocusState::NONE) { @@ -1139,11 +1363,11 @@ void AudioInputProcessor::onExceptionReceived(const std::string& exceptionMessag resetState(); } -void AudioInputProcessor::onSendCompleted(avsCommon::sdkInterfaces::MessageRequestObserverInterface::Status status) { +void AudioInputProcessor::onSendCompleted(MessageRequestObserverInterface::Status status) { ACSDK_DEBUG(LX("onSendCompleted").d("status", status)); - if (status == avsCommon::sdkInterfaces::MessageRequestObserverInterface::Status::SUCCESS || - status == avsCommon::sdkInterfaces::MessageRequestObserverInterface::Status::PENDING) { + if (status == MessageRequestObserverInterface::Status::SUCCESS || + status == MessageRequestObserverInterface::Status::PENDING) { // Stop listening from audio input source when the recognize event steam is closed. ACSDK_DEBUG5(LX("stopCapture").d("reason", "streamClosed")); stopCapture(); @@ -1159,6 +1383,22 @@ std::unordered_set> Aud return m_capabilityConfigurations; } +void AudioInputProcessor::handleDirectiveFailure( + const std::string& errorMessage, + std::shared_ptr info, + avsCommon::avs::ExceptionErrorType errorType) { + m_exceptionEncounteredSender->sendExceptionEncountered( + info->directive->getUnparsedDirective(), errorType, errorMessage); + + if (info->result) { + info->result->setFailed(errorMessage); + } + + removeDirective(info); + + ACSDK_ERROR(LX("handleDirectiveFailed").d("error", errorMessage)); +} + settings::SettingEventMetadata AudioInputProcessor::getWakeWordConfirmationMetadata() { return settings::SettingEventMetadata{NAMESPACE, WAKE_WORD_CONFIRMATION_CHANGED_EVENT_NAME, diff --git a/CapabilityAgents/AIP/test/AudioInputProcessorTest.cpp b/CapabilityAgents/AIP/test/AudioInputProcessorTest.cpp index 4a4e68fb..db9bd2b5 100644 --- a/CapabilityAgents/AIP/test/AudioInputProcessorTest.cpp +++ b/CapabilityAgents/AIP/test/AudioInputProcessorTest.cpp @@ -43,6 +43,7 @@ #include #include #include +#include #include #include #include @@ -801,9 +802,10 @@ protected: * Function to call @c onFocusChanged() and verify that @c AudioInputProcessor responds correctly. * * @param state The focus state to test with. + * @param behavior The MixingBehavior to test with. * @return @c true if the @c AudioInputProcessor responds as expected, else @c false. */ - bool testFocusChange(avsCommon::avs::FocusState state); + bool testFocusChange(avsCommon::avs::FocusState state, avsCommon::avs::MixingBehavior behavior); /** * Performs a test to check the AIP correctly transitions to a state after getting notified that the recognize event @@ -821,6 +823,9 @@ protected: AudioInputProcessorObserverInterface::State expectedAIPFinalState, bool expectFocusReleased); + /// The metric recorder. + std::shared_ptr m_metricRecorder; + /// The mock @c DirectiveSequencerInterface. std::shared_ptr m_mockDirectiveSequencer; @@ -885,6 +890,7 @@ protected: }; void AudioInputProcessorTest::SetUp() { + m_metricRecorder = std::make_shared>(); m_mockDirectiveSequencer = std::make_shared(); m_mockMessageSender = std::make_shared(); m_mockContextManager = std::make_shared(); @@ -916,7 +922,9 @@ void AudioInputProcessorTest::SetUp() { avsCommon::utils::AudioFormat::Endianness::LITTLE, SAMPLE_RATE_HZ, SAMPLE_SIZE_IN_BITS, - NUM_CHANNELS}; + NUM_CHANNELS, + false, + avsCommon::utils::AudioFormat::Layout::NON_INTERLEAVED}; m_audioProvider = avsCommon::utils::memory::make_unique( std::move(stream), format, ASRProfile::NEAR_FIELD, ALWAYS_READABLE, CAN_OVERRIDE, CAN_BE_OVERRIDDEN); @@ -944,7 +952,8 @@ void AudioInputProcessorTest::SetUp() { m_mockWakeWordSetting, nullptr, *m_audioProvider, - m_mockPowerResourceManager); + m_mockPowerResourceManager, + m_metricRecorder); ASSERT_NE(m_audioInputProcessor, nullptr); m_audioInputProcessor->addObserver(m_dialogUXStateAggregator); // Note: StrictMock here so that we fail on unexpected AIP state changes @@ -1036,15 +1045,14 @@ bool AudioInputProcessorTest::testRecognizeSucceeds( if (!bargeIn) { EXPECT_CALL(*m_mockUserInactivityMonitor, onUserActive()).Times(2); EXPECT_CALL(*m_mockObserver, onStateChanged(AudioInputProcessorObserverInterface::State::RECOGNIZING)); - EXPECT_CALL(*m_mockFocusManager, acquireChannel(CHANNEL_NAME, _, NAMESPACE)) - .WillOnce(InvokeWithoutArgs([this, stopPoint] { - m_audioInputProcessor->onFocusChanged(avsCommon::avs::FocusState::FOREGROUND); - if (RecognizeStopPoint::AFTER_FOCUS == stopPoint) { - EXPECT_TRUE(m_audioInputProcessor->stopCapture().valid()); - m_dialogUXStateAggregator->onRequestProcessingStarted(); - } - return true; - })); + EXPECT_CALL(*m_mockFocusManager, acquireChannel(CHANNEL_NAME, _)).WillOnce(InvokeWithoutArgs([this, stopPoint] { + m_audioInputProcessor->onFocusChanged(avsCommon::avs::FocusState::FOREGROUND, MixingBehavior::PRIMARY); + if (RecognizeStopPoint::AFTER_FOCUS == stopPoint) { + EXPECT_TRUE(m_audioInputProcessor->stopCapture().valid()); + m_dialogUXStateAggregator->onRequestProcessingStarted(); + } + return true; + })); EXPECT_CALL(*m_mockPowerResourceManager, acquirePowerResource(COMPONENT_NAME, PowerResourceLevel::ACTIVE_HIGH)) .Times(AtLeast(1)); } @@ -1381,7 +1389,7 @@ bool AudioInputProcessorTest::testRecognizeWithExpectSpeechInitiator(bool withIn directiveHandler->preHandleDirective(avsDirective, std::move(result)); EXPECT_TRUE(directiveHandler->handleDirective(avsDirective->getMessageId())); EXPECT_EQ(std::string(""), m_mockDirectiveSequencer->getDialogRequestId()); - m_audioInputProcessor->onFocusChanged(avsCommon::avs::FocusState::FOREGROUND); + m_audioInputProcessor->onFocusChanged(avsCommon::avs::FocusState::FOREGROUND, MixingBehavior::PRIMARY); m_audioInputProcessor->onContextAvailable(contextJson); std::unique_lock lock(mutex); @@ -1487,7 +1495,8 @@ void AudioInputProcessorTest::removeDefaultAudioProvider() { m_mockWakeWordSetting, nullptr, AudioProvider::null(), - m_mockPowerResourceManager); + m_mockPowerResourceManager, + m_metricRecorder); EXPECT_NE(m_audioInputProcessor, nullptr); m_audioInputProcessor->addObserver(m_mockObserver); m_audioInputProcessor->addObserver(m_dialogUXStateAggregator); @@ -1511,13 +1520,16 @@ void AudioInputProcessorTest::makeDefaultAudioProviderNotAlwaysReadable() { m_mockWakeWordSetting, nullptr, *m_audioProvider, - m_mockPowerResourceManager); + m_mockPowerResourceManager, + m_metricRecorder); EXPECT_NE(m_audioInputProcessor, nullptr); m_audioInputProcessor->addObserver(m_mockObserver); m_audioInputProcessor->addObserver(m_dialogUXStateAggregator); } -bool AudioInputProcessorTest::testFocusChange(avsCommon::avs::FocusState state) { +bool AudioInputProcessorTest::testFocusChange( + avsCommon::avs::FocusState state, + avsCommon::avs::MixingBehavior behavior) { std::mutex mutex; std::condition_variable conditionVariable; bool done = false; @@ -1536,7 +1548,7 @@ bool AudioInputProcessorTest::testFocusChange(avsCommon::avs::FocusState state) done = true; conditionVariable.notify_one(); })); - m_audioInputProcessor->onFocusChanged(state); + m_audioInputProcessor->onFocusChanged(state, behavior); std::unique_lock lock(mutex); return conditionVariable.wait_for(lock, TEST_TIMEOUT, [&done] { return done; }); @@ -1591,7 +1603,8 @@ TEST_F(AudioInputProcessorTest, test_createWithoutDirectiveSequencer) { m_mockWakeWordSetting, nullptr, *m_audioProvider, - m_mockPowerResourceManager); + m_mockPowerResourceManager, + m_metricRecorder); EXPECT_EQ(m_audioInputProcessor, nullptr); } @@ -1613,7 +1626,8 @@ TEST_F(AudioInputProcessorTest, test_createWithoutMessageSender) { m_mockWakeWordSetting, nullptr, *m_audioProvider, - m_mockPowerResourceManager); + m_mockPowerResourceManager, + m_metricRecorder); EXPECT_EQ(m_audioInputProcessor, nullptr); } @@ -1635,7 +1649,8 @@ TEST_F(AudioInputProcessorTest, test_createWithoutContextManager) { m_mockWakeWordSetting, nullptr, *m_audioProvider, - m_mockPowerResourceManager); + m_mockPowerResourceManager, + m_metricRecorder); EXPECT_EQ(m_audioInputProcessor, nullptr); } @@ -1657,7 +1672,8 @@ TEST_F(AudioInputProcessorTest, test_createWithoutFocusManager) { m_mockWakeWordSetting, nullptr, *m_audioProvider, - m_mockPowerResourceManager); + m_mockPowerResourceManager, + m_metricRecorder); EXPECT_EQ(m_audioInputProcessor, nullptr); } @@ -1679,7 +1695,8 @@ TEST_F(AudioInputProcessorTest, test_createWithoutStateAggregator) { m_mockWakeWordSetting, nullptr, *m_audioProvider, - m_mockPowerResourceManager); + m_mockPowerResourceManager, + m_metricRecorder); EXPECT_EQ(m_audioInputProcessor, nullptr); } @@ -1704,7 +1721,8 @@ TEST_F(AudioInputProcessorTest, test_createWithoutExceptionSender) { m_mockWakeWordSetting, nullptr, *m_audioProvider, - m_mockPowerResourceManager); + m_mockPowerResourceManager, + m_metricRecorder); EXPECT_EQ(m_audioInputProcessor, nullptr); } @@ -1729,7 +1747,8 @@ TEST_F(AudioInputProcessorTest, test_createWithoutUserInactivityMonitor) { m_mockWakeWordSetting, nullptr, *m_audioProvider, - m_mockPowerResourceManager); + m_mockPowerResourceManager, + m_metricRecorder); EXPECT_EQ(m_audioInputProcessor, nullptr); } @@ -1751,7 +1770,8 @@ TEST_F(AudioInputProcessorTest, test_createWithoutAudioProvider) { m_mockWakeWordSetting, nullptr, AudioProvider::null(), - m_mockPowerResourceManager); + m_mockPowerResourceManager, + m_metricRecorder); EXPECT_NE(m_audioInputProcessor, nullptr); } @@ -1773,6 +1793,30 @@ TEST_F(AudioInputProcessorTest, test_createWithoutPowerResourceManager) { m_mockWakeWordSetting, nullptr, AudioProvider::null(), + nullptr, + m_metricRecorder); + EXPECT_NE(m_audioInputProcessor, nullptr); +} + +/// Function to verify that @c AudioInputProcessor::create() succeeds with a null @c MetricRecorderInterface. +TEST_F(AudioInputProcessorTest, test_createWithoutMetricRecorder) { + m_audioInputProcessor->removeObserver(m_dialogUXStateAggregator); + m_audioInputProcessor = AudioInputProcessor::create( + m_mockDirectiveSequencer, + m_mockMessageSender, + m_mockContextManager, + m_mockFocusManager, + m_dialogUXStateAggregator, + m_mockExceptionEncounteredSender, + m_mockUserInactivityMonitor, + m_mockSystemSoundPlayer, + m_mockAssetsManager, + m_mockWakeWordConfirmation, + m_mockSpeechConfirmation, + m_mockWakeWordSetting, + nullptr, + AudioProvider::null(), + m_mockPowerResourceManager, nullptr); EXPECT_NE(m_audioInputProcessor, nullptr); } @@ -2251,13 +2295,13 @@ TEST_F(AudioInputProcessorTest, test_expectSpeechWithInitiatorTimedOut) { /// This function verifies that a focus change to @c FocusState::BACKGROUND causes the @c AudioInputProcessor to /// release the channel and go back to @c State::IDLE. TEST_F(AudioInputProcessorTest, test_focusChangedBackground) { - ASSERT_TRUE(testFocusChange(avsCommon::avs::FocusState::BACKGROUND)); + ASSERT_TRUE(testFocusChange(avsCommon::avs::FocusState::BACKGROUND, avsCommon::avs::MixingBehavior::MUST_PAUSE)); } /// This function verifies that a focus change to @c FocusState::NONE causes the @c AudioInputProcessor to /// release the channel and go back to @c State::IDLE. TEST_F(AudioInputProcessorTest, test_focusChangedNone) { - ASSERT_TRUE(testFocusChange(avsCommon::avs::FocusState::NONE)); + ASSERT_TRUE(testFocusChange(avsCommon::avs::FocusState::NONE, avsCommon::avs::MixingBehavior::MUST_STOP)); } /// Test that the @c AudioInputProcessor correctly transitions to @c State::IDLE diff --git a/CapabilityAgents/Alerts/CMakeLists.txt b/CapabilityAgents/Alerts/CMakeLists.txt index 2db2cd00..c5c3f2ed 100644 --- a/CapabilityAgents/Alerts/CMakeLists.txt +++ b/CapabilityAgents/Alerts/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.0 FATAL_ERROR) project(Alerts LANGUAGES CXX) -include(../../build/BuildDefaults.cmake) +include(${AVS_CORE}/build/BuildDefaults.cmake) add_subdirectory("src") add_subdirectory("test") diff --git a/CapabilityAgents/Alerts/include/Alerts/Alarm.h b/CapabilityAgents/Alerts/include/Alerts/Alarm.h index 7c18d6f4..4dc45a35 100644 --- a/CapabilityAgents/Alerts/include/Alerts/Alarm.h +++ b/CapabilityAgents/Alerts/include/Alerts/Alarm.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -16,6 +16,8 @@ #ifndef ALEXA_CLIENT_SDK_CAPABILITYAGENTS_ALERTS_INCLUDE_ALERTS_ALARM_H_ #define ALEXA_CLIENT_SDK_CAPABILITYAGENTS_ALERTS_INCLUDE_ALERTS_ALARM_H_ +#include + #include "Alerts/Alert.h" namespace alexaClientSDK { @@ -31,14 +33,17 @@ namespace alerts { */ class Alarm : public Alert { public: - /// String representation of this type. - static const std::string TYPE_NAME; - Alarm( std::function()> defaultAudioFactory, - std::function()> shortAudioFactory); + std::function()> shortAudioFactory, + std::shared_ptr settingsManager); std::string getTypeName() const override; + + /// Static member function to get the string representation of this type. + static std::string getTypeNameStatic() { + return "ALARM"; + } }; } // namespace alerts diff --git a/CapabilityAgents/Alerts/include/Alerts/Alert.h b/CapabilityAgents/Alerts/include/Alerts/Alert.h index 3fba645f..d8294a66 100644 --- a/CapabilityAgents/Alerts/include/Alerts/Alert.h +++ b/CapabilityAgents/Alerts/include/Alerts/Alert.h @@ -24,6 +24,8 @@ #include #include +#include + #include #include #include @@ -248,7 +250,8 @@ public: */ Alert( std::function()> defaultAudioFactory, - std::function()> shortAudioFactory); + std::function()> shortAudioFactory, + std::shared_ptr settingsManager); /** * Returns a string to identify the type of the class. Required for persistent storage. @@ -494,6 +497,8 @@ private: const std::function()> m_defaultAudioFactory; /// This is the factory that provides a short audio stream. const std::function()> m_shortAudioFactory; + /// The settings manager used to retrieve the value of alarm volume ramp setting. + std::shared_ptr m_settingsManager; }; /** diff --git a/CapabilityAgents/Alerts/include/Alerts/AlertScheduler.h b/CapabilityAgents/Alerts/include/Alerts/AlertScheduler.h index 8a7f09ca..af21d7ad 100644 --- a/CapabilityAgents/Alerts/include/Alerts/AlertScheduler.h +++ b/CapabilityAgents/Alerts/include/Alerts/AlertScheduler.h @@ -16,14 +16,15 @@ #ifndef ALEXA_CLIENT_SDK_CAPABILITYAGENTS_ALERTS_INCLUDE_ALERTS_ALERTSCHEDULER_H_ #define ALEXA_CLIENT_SDK_CAPABILITYAGENTS_ALERTS_INCLUDE_ALERTS_ALERTSCHEDULER_H_ -#include "Alerts/Storage/AlertStorageInterface.h" #include "Alerts/AlertObserverInterface.h" +#include "Alerts/Storage/AlertStorageInterface.h" #include +#include +#include #include #include -#include namespace alexaClientSDK { namespace capabilityAgents { @@ -70,9 +71,12 @@ public: * @note This function must be called before other use of an object this class. * * @param observer An observer which we will notify of all alert state changes. + * @param m_settingsManager A settingsManager object that manages alarm volume ramp setting. * @return Whether initialization was successful. */ - bool initialize(std::shared_ptr observer); + bool initialize( + std::shared_ptr observer, + std::shared_ptr settingsManager); /** * Schedule an alert for rendering. @@ -274,6 +278,9 @@ private: */ std::shared_ptr m_observer; + /// The settings manager used to retrieve the value of alarm volume ramp setting. + std::shared_ptr m_settingsManager; + /// Mutex for accessing all variables besides the observer. std::mutex m_mutex; diff --git a/CapabilityAgents/Alerts/include/Alerts/AlertsCapabilityAgent.h b/CapabilityAgents/Alerts/include/Alerts/AlertsCapabilityAgent.h index 8c7b9cf4..afab251c 100644 --- a/CapabilityAgents/Alerts/include/Alerts/AlertsCapabilityAgent.h +++ b/CapabilityAgents/Alerts/include/Alerts/AlertsCapabilityAgent.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -33,10 +33,13 @@ #include #include #include +#include #include #include #include #include +#include +#include #include #include @@ -80,6 +83,8 @@ public: * @param alertRenderer An alert renderer, which Alerts will use to generate user-perceivable effects when active. * @param dataManager A dataManager object that will track the CustomerDataHandler. * @param alarmVolumeRampSetting The alarm volume ramp setting. + * @param settingsManager A settingsManager object that manages alarm volume ramp setting. + * @param metricRecorder The metric recorder. * @return A pointer to an object of this type, or nullptr if there were problems during construction. */ static std::shared_ptr create( @@ -94,7 +99,9 @@ public: std::shared_ptr alertsAudioFactory, std::shared_ptr alertRenderer, std::shared_ptr dataManager, - std::shared_ptr alarmVolumeRampSetting); + std::shared_ptr alarmVolumeRampSetting, + std::shared_ptr settingsManager, + std::shared_ptr metricRecorder); /// @name CapabilityAgent Functions /// @{ @@ -112,7 +119,7 @@ public: void onConnectionStatusChanged(const Status status, const ChangedReason reason) override; - void onFocusChanged(avsCommon::avs::FocusState focusState) override; + void onFocusChanged(avsCommon::avs::FocusState focusState, avsCommon::avs::MixingBehavior behavior) override; void onAlertStateChange( const std::string& token, @@ -193,6 +200,8 @@ private: * @param alertRenderer An alert renderer, which Alerts will use to generate user-perceivable effects when active. * @param dataManager A dataManager object that will track the CustomerDataHandler. * @param alarmVolumeRampSetting The alarm volume ramp setting. + * @param settingsManager A settingsManager object that manages alarm volume ramp setting. + * @param metricRecorder The metric recorder. */ AlertsCapabilityAgent( std::shared_ptr messageSender, @@ -205,7 +214,9 @@ private: std::shared_ptr alertsAudioFactory, std::shared_ptr alertRenderer, std::shared_ptr dataManager, - std::shared_ptr alarmVolumeRampSetting); + std::shared_ptr alarmVolumeRampSetting, + std::shared_ptr settingsManager, + std::shared_ptr metricRecorder); void doShutdown() override; @@ -326,8 +337,6 @@ private: */ void executeOnLocalStop(); - /// @} - /** * A helper function to handle the SetAlert directive. * @@ -494,6 +503,8 @@ private: */ /// @{ + /// The metric recorder. + std::shared_ptr m_metricRecorder; /// The regular MessageSender object. std::shared_ptr m_messageSender; /// The CertifiedSender object. @@ -546,10 +557,13 @@ private: * The alarm volume ramp setting. */ std::shared_ptr m_alarmVolumeRampSetting; + + /// The settings manager used to retrieve the value of alarm volume ramp setting. + std::shared_ptr m_settingsManager; }; } // namespace alerts } // namespace capabilityAgents } // namespace alexaClientSDK -#endif // ALEXA_CLIENT_SDK_CAPABILITYAGENTS_ALERTS_INCLUDE_ALERTS_ALERTSCAPABILITYAGENT_H_ +#endif // ALEXA_CLIENT_SDK_CAPABILITYAGENTS_ALERTS_INCLUDE_ALERTS_ALERTSCAPABILITYAGENT_H_ \ No newline at end of file diff --git a/CapabilityAgents/Alerts/include/Alerts/Reminder.h b/CapabilityAgents/Alerts/include/Alerts/Reminder.h index a552bcca..ef94b516 100644 --- a/CapabilityAgents/Alerts/include/Alerts/Reminder.h +++ b/CapabilityAgents/Alerts/include/Alerts/Reminder.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -35,14 +35,17 @@ namespace alerts { */ class Reminder : public Alert { public: - /// String representation of this type. - static const std::string TYPE_NAME; - Reminder( std::function()> defaultAudioFactory, - std::function()> shortAudioFactory); + std::function()> shortAudioFactory, + std::shared_ptr settingsManager); std::string getTypeName() const override; + + /// Static member function to get the string representation of this type. + static std::string getTypeNameStatic() { + return "REMINDER"; + } }; } // namespace alerts diff --git a/CapabilityAgents/Alerts/include/Alerts/Renderer/Renderer.h b/CapabilityAgents/Alerts/include/Alerts/Renderer/Renderer.h index d72afcee..457fc99c 100644 --- a/CapabilityAgents/Alerts/include/Alerts/Renderer/Renderer.h +++ b/CapabilityAgents/Alerts/include/Alerts/Renderer/Renderer.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -19,11 +19,11 @@ #include "Alerts/Renderer/RendererInterface.h" #include "Alerts/Renderer/RendererObserverInterface.h" +#include #include #include #include #include -#include #include #include @@ -47,16 +47,15 @@ public: * Creates a @c Renderer. * * @param mediaPlayer the @c MediaPlayerInterface that the @c Renderer object will interact with. - * @param settingsManager A settingsManager object that manages alarm volume ramp setting. * @return The @c Renderer object. */ static std::shared_ptr create( - std::shared_ptr mediaPlayer, - std::shared_ptr settingsManager); + std::shared_ptr mediaPlayer); void start( std::shared_ptr observer, std::function()> audioFactory, + bool volumeRampEnabled, const std::vector& urls = std::vector(), int loopCount = 0, std::chrono::milliseconds loopPause = std::chrono::milliseconds{0}, @@ -64,14 +63,19 @@ public: void stop() override; - void onPlaybackStarted(SourceId sourceId) override; + void onFirstByteRead(SourceId sourceId, const avsCommon::utils::mediaPlayer::MediaPlayerState& state) override; - void onPlaybackStopped(SourceId sourceId) override; + void onPlaybackStarted(SourceId sourceId, const avsCommon::utils::mediaPlayer::MediaPlayerState& state) override; - void onPlaybackFinished(SourceId sourceId) override; + void onPlaybackStopped(SourceId sourceId, const avsCommon::utils::mediaPlayer::MediaPlayerState& state) override; - void onPlaybackError(SourceId sourceId, const avsCommon::utils::mediaPlayer::ErrorType& type, std::string error) - override; + void onPlaybackFinished(SourceId sourceId, const avsCommon::utils::mediaPlayer::MediaPlayerState& state) override; + + void onPlaybackError( + SourceId sourceId, + const avsCommon::utils::mediaPlayer::ErrorType& type, + std::string error, + const avsCommon::utils::mediaPlayer::MediaPlayerState& state) override; private: /// A type that identifies which source is currently being operated on. @@ -81,11 +85,8 @@ private: * Constructor. * * @param mediaPlayer The @c MediaPlayerInterface, which will render audio for an alert. - * @param settingsManager A settingsManager object that manages alarm volume ramp setting. */ - Renderer( - std::shared_ptr mediaPlayer, - std::shared_ptr settingsManager); + Renderer(std::shared_ptr mediaPlayer); /** * @name Executor Thread Functions @@ -102,6 +103,7 @@ private: * @param observer The observer that will receive renderer events. * @param audioFactory A function that produces a stream of audio that is used for the default if nothing * else is available. + * @param volumeRampEnabled whether this media should ramp * @param urls A container of urls to be rendered per the above description. * @param loopCount The number of times the urls should be rendered. * @param loopPause The duration which must expire between the beginning of rendering of any loop of audio. @@ -114,6 +116,7 @@ private: void executeStart( std::shared_ptr observer, std::function()> audioFactory, + bool volumeRampEnabled, const std::vector& urls, int loopCount, std::chrono::milliseconds loopPause, @@ -227,9 +230,9 @@ private: void play(); /** - * Generate the media player configuration for the current alarm rendering including volume gain. + * Generate the media player configuration for the current media rendering including volume gain. * - * @return The media player configuration for the current alarm rendering. + * @return The media player configuration for the current media rendering. */ avsCommon::utils::mediaPlayer::SourceConfig generateMediaConfiguration(); @@ -311,11 +314,8 @@ private: /// The id associated with the media that our MediaPlayer is currently handling. SourceId m_currentSourceId; - /// Whether the alarm volume ramp property was enabled when the alarm started playing. - bool m_alarmVolumeRampEnabled; - - /// The settings manager used to retrieve the value of alarm volume ramp setting. - std::shared_ptr m_settingsManager; + /// Whether the volume ramp property was enabled when the media started playing. + bool m_volumeRampEnabled; /// @} diff --git a/CapabilityAgents/Alerts/include/Alerts/Renderer/RendererInterface.h b/CapabilityAgents/Alerts/include/Alerts/Renderer/RendererInterface.h index 9addac07..42b49131 100644 --- a/CapabilityAgents/Alerts/include/Alerts/Renderer/RendererInterface.h +++ b/CapabilityAgents/Alerts/include/Alerts/Renderer/RendererInterface.h @@ -55,6 +55,7 @@ public: * @param observer The observer that will receive renderer events. * @param audioFactory A function that produces a unique stream of audio that is used for the default if nothing * else is available. + * @param volumeRampEnabled whether this media should ramp * @param urls A container of urls to be rendered per the above description. * @param loopCount The number of times the urls should be rendered. * @param loopPause The duration which must expire between the beginning of rendering of any loop of audio. @@ -67,6 +68,7 @@ public: virtual void start( std::shared_ptr observer, std::function()> audioFactory, + bool volumeRampEnabled, const std::vector& urls = std::vector(), int loopCount = 0, std::chrono::milliseconds loopPause = std::chrono::milliseconds{0}, diff --git a/CapabilityAgents/Alerts/include/Alerts/Storage/AlertStorageInterface.h b/CapabilityAgents/Alerts/include/Alerts/Storage/AlertStorageInterface.h index a7d7aa24..8edd43aa 100644 --- a/CapabilityAgents/Alerts/include/Alerts/Storage/AlertStorageInterface.h +++ b/CapabilityAgents/Alerts/include/Alerts/Storage/AlertStorageInterface.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-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. @@ -18,6 +18,8 @@ #include "Alerts/Alert.h" +#include + #include #include #include @@ -75,9 +77,12 @@ public: * Loads all alerts in the database. * * @param[out] alertContainer The container where alerts should be stored. + * @param settingsManager A settingsManager object that manages alarm volume ramp setting. * @return Whether the @c Alerts were successfully loaded. */ - virtual bool load(std::vector>* alertContainer) = 0; + virtual bool load( + std::vector>* alertContainer, + std::shared_ptr settingsManager) = 0; /** * Updates a database record of the @c Alert parameter. diff --git a/CapabilityAgents/Alerts/include/Alerts/Storage/SQLiteAlertStorage.h b/CapabilityAgents/Alerts/include/Alerts/Storage/SQLiteAlertStorage.h index 62ea3f27..836ab469 100644 --- a/CapabilityAgents/Alerts/include/Alerts/Storage/SQLiteAlertStorage.h +++ b/CapabilityAgents/Alerts/include/Alerts/Storage/SQLiteAlertStorage.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-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. @@ -61,7 +61,9 @@ public: bool store(std::shared_ptr alert) override; - bool load(std::vector>* alertContainer) override; + bool load( + std::vector>* alertContainer, + std::shared_ptr settingsManager) override; bool modify(std::shared_ptr alert) override; @@ -118,9 +120,13 @@ private: * * @param dbVersion The version of the database we wish to load from. * @param[out] alertContainer The container where alerts should be stored. + * @param settingsManager A settingsManager object that manages alarm volume ramp setting. * @return Whether the alerts were loaded ok. */ - bool loadHelper(int dbVersion, std::vector>* alertContainer); + bool loadHelper( + int dbVersion, + std::vector>* alertContainer, + std::shared_ptr settingsManager); /** * Query whether an alert is currently stored with the given token. diff --git a/CapabilityAgents/Alerts/include/Alerts/Timer.h b/CapabilityAgents/Alerts/include/Alerts/Timer.h index 6cb65ccb..a21a8c55 100644 --- a/CapabilityAgents/Alerts/include/Alerts/Timer.h +++ b/CapabilityAgents/Alerts/include/Alerts/Timer.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -38,14 +38,17 @@ namespace alerts { */ class Timer : public Alert { public: - /// String representation of this type. - static const std::string TYPE_NAME; - Timer( std::function()> defaultAudioFactory, - std::function()> shortAudioFactory); + std::function()> shortAudioFactory, + std::shared_ptr settingsManager); std::string getTypeName() const override; + + /// Static member function to get the string representation of this type. + static std::string getTypeNameStatic() { + return "TIMER"; + } }; } // namespace alerts diff --git a/CapabilityAgents/Alerts/src/Alarm.cpp b/CapabilityAgents/Alerts/src/Alarm.cpp index 043ecdd3..0c2b28a3 100644 --- a/CapabilityAgents/Alerts/src/Alarm.cpp +++ b/CapabilityAgents/Alerts/src/Alarm.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -19,17 +19,15 @@ namespace alexaClientSDK { namespace capabilityAgents { namespace alerts { -/// Definition for static class data member. -const std::string Alarm::TYPE_NAME = "ALARM"; - Alarm::Alarm( std::function()> defaultAudioFactory, - std::function()> shortAudioFactory) : - Alert(defaultAudioFactory, shortAudioFactory) { + std::function()> shortAudioFactory, + std::shared_ptr settingsManager) : + Alert(defaultAudioFactory, shortAudioFactory, settingsManager) { } std::string Alarm::getTypeName() const { - return TYPE_NAME; + return Alarm::getTypeNameStatic(); } } // namespace alerts diff --git a/CapabilityAgents/Alerts/src/Alert.cpp b/CapabilityAgents/Alerts/src/Alert.cpp index 4d83946f..0d67db8b 100644 --- a/CapabilityAgents/Alerts/src/Alert.cpp +++ b/CapabilityAgents/Alerts/src/Alert.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -13,6 +13,7 @@ * permissions and limitations under the License. */ +#include "Alerts/Alarm.h" #include "Alerts/Alert.h" #include @@ -76,13 +77,15 @@ static const std::string TAG("Alert"); Alert::Alert( std::function()> defaultAudioFactory, - std::function()> shortAudioFactory) : + std::function()> shortAudioFactory, + std::shared_ptr settingsManager) : m_stopReason{StopReason::UNSET}, m_focusState{avsCommon::avs::FocusState::NONE}, m_hasTimerExpired{false}, m_observer{nullptr}, m_defaultAudioFactory{defaultAudioFactory}, - m_shortAudioFactory{shortAudioFactory} { + m_shortAudioFactory{shortAudioFactory}, + m_settingsManager{settingsManager} { } /** @@ -592,7 +595,19 @@ void Alert::startRendererLocked() { } } - m_renderer->start(shared_from_this(), audioFactory, urls, loopCount, loopPause, startWithPause); + auto alarmVolumeRampEnabled = false; + if (m_settingsManager) { + // When Alert starts, check the current volume ramp setting so the alert renders with the most current setting + auto alarmVolumeRampSetting = m_settingsManager + ->getValue( + settings::types::getAlarmVolumeRampDefault()) + .second; + alarmVolumeRampEnabled = + settings::types::isEnabled(alarmVolumeRampSetting) && (getTypeName() == Alarm::getTypeNameStatic()); + } + + m_renderer->start( + shared_from_this(), audioFactory, alarmVolumeRampEnabled, urls, loopCount, loopPause, startWithPause); } void Alert::onMaxTimerExpiration() { diff --git a/CapabilityAgents/Alerts/src/AlertScheduler.cpp b/CapabilityAgents/Alerts/src/AlertScheduler.cpp index 2b3c5a41..ff27745c 100644 --- a/CapabilityAgents/Alerts/src/AlertScheduler.cpp +++ b/CapabilityAgents/Alerts/src/AlertScheduler.cpp @@ -63,7 +63,9 @@ void AlertScheduler::onAlertStateChange( }); } -bool AlertScheduler::initialize(std::shared_ptr observer) { +bool AlertScheduler::initialize( + std::shared_ptr observer, + std::shared_ptr settingsManager) { if (!observer) { ACSDK_ERROR(LX("initializeFailed").m("observer was nullptr.")); return false; @@ -88,7 +90,7 @@ bool AlertScheduler::initialize(std::shared_ptr observer std::vector> alerts; std::unique_lock lock(m_mutex); - m_alertStorage->load(&alerts); + m_alertStorage->load(&alerts, settingsManager); for (auto& alert : alerts) { if (alert->isPastDue(unixEpochNow, m_alertPastDueTimeLimit)) { diff --git a/CapabilityAgents/Alerts/src/AlertsCapabilityAgent.cpp b/CapabilityAgents/Alerts/src/AlertsCapabilityAgent.cpp index 37f35357..37cc17c5 100644 --- a/CapabilityAgents/Alerts/src/AlertsCapabilityAgent.cpp +++ b/CapabilityAgents/Alerts/src/AlertsCapabilityAgent.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -17,13 +17,14 @@ #include "Alerts/Alarm.h" #include "Alerts/Reminder.h" -#include "Alerts/Storage/SQLiteAlertStorage.h" #include "Alerts/Timer.h" #include "AVSCommon/AVS/CapabilityConfiguration.h" #include #include #include #include +#include +#include #include #include #include @@ -48,6 +49,7 @@ using namespace avsCommon::utils::configuration; using namespace avsCommon::utils::file; using namespace avsCommon::utils::json::jsonUtils; using namespace avsCommon::utils::logger; +using namespace avsCommon::utils::metrics; using namespace avsCommon::utils::timing; using namespace avsCommon::sdkInterfaces; using namespace certifiedSender; @@ -170,6 +172,14 @@ static const avsCommon::avs::NamespaceAndName SET_ALARM_VOLUME_RAMP{NAMESPACE, D /// String to identify log entries originating from this file. static const std::string TAG("AlertsCapabilityAgent"); +/// Metric Activity Name Prefix for ALERT metric source +static const std::string ALERT_METRIC_SOURCE_PREFIX = "ALERT-"; + +/// Metric constants related to Alerts +static const std::string FAILED_SNOOZE_ALERT = "failedToSnoozeAlert"; +static const std::string FAILED_SCHEDULE_ALERT = "failedToScheduleAlert"; +static const std::string INVALID_PAYLOAD_FOR_SET_ALARM_VOLUME = "invalidPayloadToSetAlarmRamping"; +static const std::string INVALID_PAYLOAD_FOR_CHANGE_ALARM_VOLUME = "invalidPayloadToChangeAlarmVolume"; /** * Create a LogEntry using this file's TAG and the specified event string. * @@ -237,24 +247,57 @@ static rapidjson::Value buildActiveAlertsContext( return alertArray; } +/** + * Submits a metric for a given count and name + * @param metricRecorder The @c MetricRecorderInterface which records Metric events + * @param eventName The name of the metric event + * @param count The count for metric event + */ +static void submitMetric( + const std::shared_ptr& metricRecorder, + const std::string& eventName, + int count) { + if (!metricRecorder) { + return; + } + + auto metricEvent = MetricEventBuilder{} + .setActivityName(ALERT_METRIC_SOURCE_PREFIX + eventName) + .addDataPoint(DataPointCounterBuilder{}.setName(eventName).increment(count).build()) + .build(); + + if (metricEvent == nullptr) { + ACSDK_ERROR(LX("Error creating metric.")); + return; + } + recordMetric(metricRecorder, metricEvent); +} + std::shared_ptr AlertsCapabilityAgent::create( - std::shared_ptr messageSender, - std::shared_ptr connectionManager, + std::shared_ptr messageSender, + std::shared_ptr connectionManager, std::shared_ptr certifiedMessageSender, - std::shared_ptr focusManager, - std::shared_ptr speakerManager, - std::shared_ptr contextManager, - std::shared_ptr exceptionEncounteredSender, + std::shared_ptr focusManager, + std::shared_ptr speakerManager, + std::shared_ptr contextManager, + std::shared_ptr exceptionEncounteredSender, std::shared_ptr alertStorage, - std::shared_ptr alertsAudioFactory, + std::shared_ptr alertsAudioFactory, std::shared_ptr alertRenderer, std::shared_ptr dataManager, - std::shared_ptr alarmVolumeRampSetting) { + std::shared_ptr alarmVolumeRampSetting, + std::shared_ptr settingsManager, + std::shared_ptr metricRecorder = nullptr) { if (!alarmVolumeRampSetting) { ACSDK_ERROR(LX("createFailed").d("reason", "nullAlarmVolumeRampSetting")); return nullptr; } + if (!settingsManager) { + ACSDK_ERROR(LX("createFailed").d("reason", "nullSettingsManager")); + return nullptr; + } + auto alertsCA = std::shared_ptr(new AlertsCapabilityAgent( messageSender, certifiedMessageSender, @@ -266,7 +309,9 @@ std::shared_ptr AlertsCapabilityAgent::create( alertsAudioFactory, alertRenderer, dataManager, - alarmVolumeRampSetting)); + alarmVolumeRampSetting, + settingsManager, + metricRecorder)); if (!alertsCA->initialize()) { ACSDK_ERROR(LX("createFailed").d("reason", "Initialization error.")); @@ -325,7 +370,9 @@ void AlertsCapabilityAgent::onConnectionStatusChanged(const Status status, const m_executor.submit([this, status, reason]() { executeOnConnectionStatusChanged(status, reason); }); } -void AlertsCapabilityAgent::onFocusChanged(avsCommon::avs::FocusState focusState) { +void AlertsCapabilityAgent::onFocusChanged( + avsCommon::avs::FocusState focusState, + avsCommon::avs::MixingBehavior behavior) { ACSDK_DEBUG9(LX("onFocusChanged").d("focusState", focusState)); m_executor.submit([this, focusState]() { executeOnFocusChanged(focusState); }); } @@ -377,20 +424,23 @@ void AlertsCapabilityAgent::onLocalStop() { } AlertsCapabilityAgent::AlertsCapabilityAgent( - std::shared_ptr messageSender, + std::shared_ptr messageSender, std::shared_ptr certifiedMessageSender, - std::shared_ptr focusManager, - std::shared_ptr speakerManager, - std::shared_ptr contextManager, - std::shared_ptr exceptionEncounteredSender, + std::shared_ptr focusManager, + std::shared_ptr speakerManager, + std::shared_ptr contextManager, + std::shared_ptr exceptionEncounteredSender, std::shared_ptr alertStorage, - std::shared_ptr alertsAudioFactory, + std::shared_ptr alertsAudioFactory, std::shared_ptr alertRenderer, std::shared_ptr dataManager, - std::shared_ptr alarmVolumeRampSetting) : + std::shared_ptr alarmVolumeRampSetting, + std::shared_ptr settingsManager, + std::shared_ptr metricRecorder) : CapabilityAgent("Alerts", exceptionEncounteredSender), RequiresShutdown("AlertsCapabilityAgent"), CustomerDataHandler(dataManager), + m_metricRecorder{metricRecorder}, m_messageSender{messageSender}, m_certifiedSender{certifiedMessageSender}, m_focusManager{focusManager}, @@ -402,7 +452,8 @@ AlertsCapabilityAgent::AlertsCapabilityAgent( m_contentChannelIsActive{false}, m_commsChannelIsActive{false}, m_alertIsSounding{false}, - m_alarmVolumeRampSetting{alarmVolumeRampSetting} { + m_alarmVolumeRampSetting{alarmVolumeRampSetting}, + m_settingsManager{settingsManager} { m_capabilityConfigurations.insert(getAlertsCapabilityConfiguration()); } @@ -443,7 +494,7 @@ bool AlertsCapabilityAgent::initialize() { } bool AlertsCapabilityAgent::initializeAlerts() { - return m_alertScheduler.initialize(shared_from_this()); + return m_alertScheduler.initialize(shared_from_this(), m_settingsManager); } settings::SettingEventMetadata AlertsCapabilityAgent::getAlarmVolumeRampMetadata() { @@ -470,13 +521,15 @@ bool AlertsCapabilityAgent::handleSetAlert( std::shared_ptr parsedAlert; - if (Alarm::TYPE_NAME == alertType) { - parsedAlert = std::make_shared(m_alertsAudioFactory->alarmDefault(), m_alertsAudioFactory->alarmShort()); - } else if (Timer::TYPE_NAME == alertType) { - parsedAlert = std::make_shared(m_alertsAudioFactory->timerDefault(), m_alertsAudioFactory->timerShort()); - } else if (Reminder::TYPE_NAME == alertType) { - parsedAlert = - std::make_shared(m_alertsAudioFactory->reminderDefault(), m_alertsAudioFactory->reminderShort()); + if (Alarm::getTypeNameStatic() == alertType) { + parsedAlert = std::make_shared( + m_alertsAudioFactory->alarmDefault(), m_alertsAudioFactory->alarmShort(), m_settingsManager); + } else if (Timer::getTypeNameStatic() == alertType) { + parsedAlert = std::make_shared( + m_alertsAudioFactory->timerDefault(), m_alertsAudioFactory->timerShort(), m_settingsManager); + } else if (Reminder::getTypeNameStatic() == alertType) { + parsedAlert = std::make_shared( + m_alertsAudioFactory->reminderDefault(), m_alertsAudioFactory->reminderShort(), m_settingsManager); } if (!parsedAlert) { @@ -500,6 +553,7 @@ bool AlertsCapabilityAgent::handleSetAlert( if (m_alertScheduler.isAlertActive(parsedAlert)) { if (!m_alertScheduler.snoozeAlert(parsedAlert->getToken(), parsedAlert->getScheduledTime_ISO_8601())) { ACSDK_ERROR(LX("handleSetAlertFailed").d("reason", "failed to snooze alert")); + submitMetric(m_metricRecorder, FAILED_SNOOZE_ALERT, 1); return false; } @@ -509,12 +563,16 @@ bool AlertsCapabilityAgent::handleSetAlert( parsedAlert->getTypeName(), State::SCHEDULED_FOR_LATER, parsedAlert->getScheduledTime_ISO_8601()); + submitMetric(m_metricRecorder, FAILED_SNOOZE_ALERT, 0); + submitMetric(m_metricRecorder, "alarmSnoozeCount", 1); return true; } if (!m_alertScheduler.scheduleAlert(parsedAlert)) { + submitMetric(m_metricRecorder, FAILED_SCHEDULE_ALERT, 1); return false; } + submitMetric(m_metricRecorder, FAILED_SCHEDULE_ALERT, 0); executeNotifyObservers( parsedAlert->getToken(), @@ -538,9 +596,11 @@ bool AlertsCapabilityAgent::handleDeleteAlert( } if (!m_alertScheduler.deleteAlert(*alertToken)) { + submitMetric(m_metricRecorder, "failedToDeleteAlert", 1); return false; } + submitMetric(m_metricRecorder, "failedToDeleteAlert", 0); updateContextManager(); return true; @@ -594,9 +654,11 @@ bool AlertsCapabilityAgent::handleSetVolume( int64_t volumeValue = 0; if (!retrieveValue(payload, DIRECTIVE_PAYLOAD_VOLUME, &volumeValue)) { ACSDK_ERROR(LX("handleSetVolumeFailed").m("Could not find volume in the payload.")); + submitMetric(m_metricRecorder, INVALID_PAYLOAD_FOR_CHANGE_ALARM_VOLUME, 1); return false; } + submitMetric(m_metricRecorder, INVALID_PAYLOAD_FOR_CHANGE_ALARM_VOLUME, 0); setNextAlertVolume(volumeValue); return true; @@ -609,8 +671,10 @@ bool AlertsCapabilityAgent::handleAdjustVolume( int64_t adjustValue = 0; if (!retrieveValue(payload, DIRECTIVE_PAYLOAD_VOLUME, &adjustValue)) { ACSDK_ERROR(LX("handleAdjustVolumeFailed").m("Could not find volume in the payload.")); + submitMetric(m_metricRecorder, INVALID_PAYLOAD_FOR_CHANGE_ALARM_VOLUME, 1); return false; } + submitMetric(m_metricRecorder, INVALID_PAYLOAD_FOR_CHANGE_ALARM_VOLUME, 0); SpeakerInterface::SpeakerSettings speakerSettings; if (!m_speakerManager->getSpeakerSettings(SpeakerInterface::Type::AVS_ALERTS_VOLUME, &speakerSettings).get()) { @@ -633,21 +697,26 @@ bool AlertsCapabilityAgent::handleSetAlarmVolumeRamp( DIRECTIVE_PAYLOAD_ALARM_VOLUME_RAMP + " not specified for " + DIRECTIVE_NAME_SET_ALARM_VOLUME_RAMP; ACSDK_ERROR(LX("handleSetAlarmVolumeRampFailed").m(errorMessage)); sendProcessingDirectiveException(directive, errorMessage); + submitMetric(m_metricRecorder, INVALID_PAYLOAD_FOR_SET_ALARM_VOLUME, 1); return false; } + submitMetric(m_metricRecorder, INVALID_PAYLOAD_FOR_SET_ALARM_VOLUME, 0); auto value = getAlarmVolumeRampDefault(); std::stringstream ss{jsonValue}; ss >> value; if (ss.fail()) { ACSDK_ERROR(LX(__func__).d("error", "invalid").d("value", jsonValue)); + submitMetric(m_metricRecorder, INVALID_PAYLOAD_FOR_CHANGE_ALARM_VOLUME, 1); return false; } + submitMetric(m_metricRecorder, INVALID_PAYLOAD_FOR_CHANGE_ALARM_VOLUME, 0); return m_alarmVolumeRampSetting->setAvsChange(value); } void AlertsCapabilityAgent::sendEvent(const std::string& eventName, const std::string& alertToken, bool isCertified) { + submitMetric(m_metricRecorder, eventName, 1); rapidjson::Document payload(kObjectType); rapidjson::Document::AllocatorType& alloc = payload.GetAllocator(); @@ -679,6 +748,7 @@ void AlertsCapabilityAgent::sendBulkEvent( const std::string& eventName, const std::list& tokenList, bool isCertified) { + submitMetric(m_metricRecorder, eventName, 1); rapidjson::Document payload(kObjectType); rapidjson::Document::AllocatorType& alloc = payload.GetAllocator(); @@ -752,7 +822,9 @@ void AlertsCapabilityAgent::sendProcessingDirectiveException( void AlertsCapabilityAgent::acquireChannel() { ACSDK_DEBUG9(LX("acquireChannel")); - m_focusManager->acquireChannel(FocusManagerInterface::ALERT_CHANNEL_NAME, shared_from_this(), NAMESPACE); + auto activity = FocusManagerInterface::Activity::create( + NAMESPACE, shared_from_this(), std::chrono::milliseconds::zero(), avsCommon::avs::ContentType::MIXABLE); + m_focusManager->acquireChannel(FocusManagerInterface::ALERT_CHANNEL_NAME, activity); } void AlertsCapabilityAgent::releaseChannel() { @@ -839,7 +911,10 @@ void AlertsCapabilityAgent::executeOnFocusManagerFocusChanged( // Alert is sounding with volume higher than Base Volume. Assume that it was adjusted because of // content being played and reset it to the base one. Keep lower values, though. m_speakerManager->setVolume( - SpeakerInterface::Type::AVS_ALERTS_VOLUME, m_lastReportedSpeakerSettings.volume); + SpeakerInterface::Type::AVS_ALERTS_VOLUME, + m_lastReportedSpeakerSettings.volume, + false, + SpeakerManagerObserverInterface::Source::DIRECTIVE); } } } @@ -915,7 +990,10 @@ void AlertsCapabilityAgent::executeOnAlertStateChange( if (m_lastReportedSpeakerSettings.volume < contentSpeakerSettings.volume) { // Adjust alerts volume to be at least as loud as content volume m_speakerManager->setVolume( - SpeakerInterface::Type::AVS_ALERTS_VOLUME, contentSpeakerSettings.volume); + SpeakerInterface::Type::AVS_ALERTS_VOLUME, + m_lastReportedSpeakerSettings.volume, + false, + SpeakerManagerObserverInterface::Source::DIRECTIVE); } } } @@ -927,7 +1005,7 @@ void AlertsCapabilityAgent::executeOnAlertStateChange( // Reset Active Alerts Volume volume to the Base Alerts Volume when alert stops m_alertIsSounding = false; m_speakerManager->setVolume( - SpeakerInterface::Type::AVS_ALERTS_VOLUME, m_lastReportedSpeakerSettings.volume); + SpeakerInterface::Type::AVS_ALERTS_VOLUME, m_lastReportedSpeakerSettings.volume, true); } } @@ -1023,8 +1101,7 @@ void AlertsCapabilityAgent::onSpeakerSettingsChanged( m_executor.submit([this, settings, type]() { executeOnSpeakerSettingsChanged(type, settings); }); } -bool AlertsCapabilityAgent::getAlertVolumeSettings( - avsCommon::sdkInterfaces::SpeakerInterface::SpeakerSettings* speakerSettings) { +bool AlertsCapabilityAgent::getAlertVolumeSettings(SpeakerInterface::SpeakerSettings* speakerSettings) { if (!m_speakerManager->getSpeakerSettings(SpeakerInterface::Type::AVS_ALERTS_VOLUME, speakerSettings).get()) { ACSDK_ERROR(LX("getAlertSpeakerSettingsFailed").d("reason", "Failed to get speaker settings")); return false; @@ -1032,8 +1109,7 @@ bool AlertsCapabilityAgent::getAlertVolumeSettings( return true; } -bool AlertsCapabilityAgent::getSpeakerVolumeSettings( - avsCommon::sdkInterfaces::SpeakerInterface::SpeakerSettings* speakerSettings) { +bool AlertsCapabilityAgent::getSpeakerVolumeSettings(SpeakerInterface::SpeakerSettings* speakerSettings) { if (!m_speakerManager->getSpeakerSettings(SpeakerInterface::Type::AVS_SPEAKER_VOLUME, speakerSettings).get()) { ACSDK_ERROR(LX("getContentSpeakerSettingsFailed").d("reason", "Failed to get speaker settings")); return false; @@ -1068,7 +1144,7 @@ void AlertsCapabilityAgent::executeOnSpeakerSettingsChanged( const SpeakerInterface::Type& type, const SpeakerInterface::SpeakerSettings& speakerSettings) { if (SpeakerInterface::Type::AVS_ALERTS_VOLUME == type && !m_alertIsSounding) { - updateAVSWithLocalVolumeChanges(speakerSettings.volume, false); + updateAVSWithLocalVolumeChanges(speakerSettings.volume, true); } } diff --git a/CapabilityAgents/Alerts/src/CMakeLists.txt b/CapabilityAgents/Alerts/src/CMakeLists.txt index 200c5992..e37db4af 100644 --- a/CapabilityAgents/Alerts/src/CMakeLists.txt +++ b/CapabilityAgents/Alerts/src/CMakeLists.txt @@ -18,7 +18,7 @@ target_include_directories(Alerts PUBLIC "${RegistrationManager_SOURCE_DIR}/include" "${DeviceSettings_SOURCE_DIR}/include") -target_link_libraries(Alerts AudioResources AVSCommon CertifiedSender SQLiteStorage RegistrationManager DeviceSettings) +target_link_libraries(Alerts AudioResources AVSCommon CertifiedSender SQLiteStorage RegistrationManager DeviceSettings SpeakerManager) # install target asdk_install() diff --git a/CapabilityAgents/Alerts/src/Reminder.cpp b/CapabilityAgents/Alerts/src/Reminder.cpp index 9340aee1..8df8c41e 100644 --- a/CapabilityAgents/Alerts/src/Reminder.cpp +++ b/CapabilityAgents/Alerts/src/Reminder.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -19,17 +19,15 @@ namespace alexaClientSDK { namespace capabilityAgents { namespace alerts { -/// Definition for static class data member. -const std::string Reminder::TYPE_NAME = "REMINDER"; - Reminder::Reminder( std::function()> defaultAudioFactory, - std::function()> shortAudioFactory) : - Alert(defaultAudioFactory, shortAudioFactory) { + std::function()> shortAudioFactory, + std::shared_ptr settingsManager) : + Alert(defaultAudioFactory, shortAudioFactory, settingsManager) { } std::string Reminder::getTypeName() const { - return TYPE_NAME; + return Reminder::getTypeNameStatic(); } } // namespace alerts diff --git a/CapabilityAgents/Alerts/src/Renderer/Renderer.cpp b/CapabilityAgents/Alerts/src/Renderer/Renderer.cpp index 6e7b4d5c..1f0f2bc3 100644 --- a/CapabilityAgents/Alerts/src/Renderer/Renderer.cpp +++ b/CapabilityAgents/Alerts/src/Renderer/Renderer.cpp @@ -17,6 +17,7 @@ #include #include +#include #include #include "Alerts/Renderer/Renderer.h" @@ -26,6 +27,7 @@ namespace capabilityAgents { namespace alerts { namespace renderer { +using namespace avsCommon::sdkInterfaces; using namespace avsCommon::utils::logger; using namespace avsCommon::utils::mediaPlayer; @@ -52,20 +54,13 @@ static bool isSourceIdOk(MediaPlayerInterface::SourceId sourceId) { return sourceId != MediaPlayerInterface::ERROR; } -std::shared_ptr Renderer::create( - std::shared_ptr mediaPlayer, - std::shared_ptr settingsManager) { +std::shared_ptr Renderer::create(std::shared_ptr mediaPlayer) { if (!mediaPlayer) { ACSDK_ERROR(LX("createFailed").m("mediaPlayer parameter was nullptr.")); return nullptr; } - if (!settingsManager) { - ACSDK_ERROR(LX("createFailed").m("settingsManager parameter was nullptr.")); - return nullptr; - } - - auto renderer = std::shared_ptr(new Renderer{mediaPlayer, settingsManager}); + auto renderer = std::shared_ptr(new Renderer{mediaPlayer}); mediaPlayer->addObserver(renderer); return renderer; } @@ -73,6 +68,7 @@ std::shared_ptr Renderer::create( void Renderer::start( std::shared_ptr observer, std::function()> audioFactory, + bool volumeRampEnabled, const std::vector& urls, int loopCount, std::chrono::milliseconds loopPause, @@ -95,8 +91,8 @@ void Renderer::start( loopPause = std::chrono::milliseconds{0}; } - m_executor.submit([this, observer, audioFactory, urls, loopCount, loopPause, startWithPause]() { - executeStart(observer, audioFactory, urls, loopCount, loopPause, startWithPause); + m_executor.submit([this, observer, audioFactory, volumeRampEnabled, urls, loopCount, loopPause, startWithPause]() { + executeStart(observer, audioFactory, volumeRampEnabled, urls, loopCount, loopPause, startWithPause); }); } @@ -109,28 +105,31 @@ void Renderer::stop() { m_executor.submit([this]() { executeStop(); }); } -void Renderer::onPlaybackStarted(SourceId sourceId) { +void Renderer::onFirstByteRead(SourceId id, const MediaPlayerState&) { + ACSDK_DEBUG(LX(__func__).d("id", id)); +} + +void Renderer::onPlaybackStarted(SourceId sourceId, const MediaPlayerState&) { m_executor.submit([this, sourceId]() { executeOnPlaybackStarted(sourceId); }); } -void Renderer::onPlaybackStopped(SourceId sourceId) { +void Renderer::onPlaybackStopped(SourceId sourceId, const MediaPlayerState&) { m_executor.submit([this, sourceId]() { executeOnPlaybackStopped(sourceId); }); } -void Renderer::onPlaybackFinished(SourceId sourceId) { +void Renderer::onPlaybackFinished(SourceId sourceId, const MediaPlayerState&) { m_executor.submit([this, sourceId]() { executeOnPlaybackFinished(sourceId); }); } void Renderer::onPlaybackError( SourceId sourceId, const avsCommon::utils::mediaPlayer::ErrorType& type, - std::string error) { + std::string error, + const MediaPlayerState&) { m_executor.submit([this, sourceId, type, error]() { executeOnPlaybackError(sourceId, type, error); }); } -Renderer::Renderer( - std::shared_ptr mediaPlayer, - std::shared_ptr settingsManager) : +Renderer::Renderer(std::shared_ptr mediaPlayer) : m_mediaPlayer{mediaPlayer}, m_observer{nullptr}, m_numberOfStreamsRenderedThisLoop{0}, @@ -140,8 +139,7 @@ Renderer::Renderer( m_shouldPauseBeforeRender{false}, m_isStopping{false}, m_isStartPending{false}, - m_alarmVolumeRampEnabled{false}, - m_settingsManager{settingsManager} { + m_volumeRampEnabled{false} { resetSourceId(); } @@ -213,7 +211,7 @@ bool Renderer::pause(std::chrono::milliseconds duration) { } SourceConfig Renderer::generateMediaConfiguration() { - if (!m_alarmVolumeRampEnabled) { + if (!m_volumeRampEnabled) { return emptySourceConfig(); } @@ -233,8 +231,7 @@ SourceConfig Renderer::generateMediaConfiguration() { void Renderer::play() { auto mediaConfig = generateMediaConfiguration(); - ACSDK_DEBUG9( - LX(__func__).d("fadeEnabled", m_alarmVolumeRampEnabled).d("startGain", mediaConfig.fadeInConfig.startGain)); + ACSDK_DEBUG9(LX(__func__).d("fadeEnabled", m_volumeRampEnabled).d("startGain", mediaConfig.fadeInConfig.startGain)); m_isStartPending = false; @@ -270,11 +267,13 @@ void Renderer::play() { void Renderer::executeStart( std::shared_ptr observer, std::function()> audioFactory, + bool volumeRampEnabled, const std::vector& urls, int loopCount, std::chrono::milliseconds loopPause, bool startWithPause) { ACSDK_DEBUG1(LX(__func__) + .d("rampEnabled", volumeRampEnabled) .d("urls.size", urls.size()) .d("loopCount", loopCount) .d("loopPause (ms)", std::chrono::duration_cast(loopPause).count()) @@ -288,12 +287,7 @@ void Renderer::executeStart( m_loopPause = loopPause; m_shouldPauseBeforeRender = startWithPause; m_defaultAudioFactory = audioFactory; - - auto alarmVolumeRampSetting = - m_settingsManager - ->getValue(settings::types::getAlarmVolumeRampDefault()) - .second; - m_alarmVolumeRampEnabled = settings::types::isEnabled(alarmVolumeRampSetting); + m_volumeRampEnabled = volumeRampEnabled; m_numberOfStreamsRenderedThisLoop = 0; ACSDK_DEBUG9( diff --git a/CapabilityAgents/Alerts/src/Storage/SQLiteAlertStorage.cpp b/CapabilityAgents/Alerts/src/Storage/SQLiteAlertStorage.cpp index 5895c9dd..ca0c97ca 100644 --- a/CapabilityAgents/Alerts/src/Storage/SQLiteAlertStorage.cpp +++ b/CapabilityAgents/Alerts/src/Storage/SQLiteAlertStorage.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-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. @@ -419,7 +419,7 @@ bool SQLiteAlertStorage::migrateAlertsDbFromV1ToV2() { // the migration is fine. if (m_db.tableExists(ALERTS_TABLE_NAME)) { std::vector> alertContainer; - if (!loadHelper(ALERTS_DATABASE_VERSION_ONE, &alertContainer)) { + if (!loadHelper(ALERTS_DATABASE_VERSION_ONE, &alertContainer, nullptr)) { ACSDK_ERROR(LX("migrateAlertsDbFromV1ToV2Failed").m("Could not load V1 alert records.")); return false; } @@ -675,7 +675,7 @@ bool SQLiteAlertStorage::store(std::shared_ptr alert) { staticData.dbId = id; if (!alert->setAlertData(&staticData, nullptr)) { - ACSDK_ERROR(LX("loadHelperFailed").m("Could not set alert data.")); + ACSDK_ERROR(LX("storeFailed").m("Could not set alert data.")); return false; } @@ -792,7 +792,10 @@ static bool loadAlertAssetPlayOrderItems( return true; } -bool SQLiteAlertStorage::loadHelper(int dbVersion, std::vector>* alertContainer) { +bool SQLiteAlertStorage::loadHelper( + int dbVersion, + std::vector>* alertContainer, + std::shared_ptr settingsManager) { if (!alertContainer) { ACSDK_ERROR(LX("loadHelperFailed").m("Alert container parameter is nullptr.")); return false; @@ -874,12 +877,14 @@ bool SQLiteAlertStorage::loadHelper(int dbVersion, std::vector alert; if (ALERT_EVENT_TYPE_ALARM == type) { - alert = std::make_shared(m_alertsAudioFactory->alarmDefault(), m_alertsAudioFactory->alarmShort()); + alert = std::make_shared( + m_alertsAudioFactory->alarmDefault(), m_alertsAudioFactory->alarmShort(), settingsManager); } else if (ALERT_EVENT_TYPE_TIMER == type) { - alert = std::make_shared(m_alertsAudioFactory->timerDefault(), m_alertsAudioFactory->timerShort()); + alert = std::make_shared( + m_alertsAudioFactory->timerDefault(), m_alertsAudioFactory->timerShort(), settingsManager); } else if (ALERT_EVENT_TYPE_REMINDER == type) { alert = std::make_shared( - m_alertsAudioFactory->reminderDefault(), m_alertsAudioFactory->reminderShort()); + m_alertsAudioFactory->reminderDefault(), m_alertsAudioFactory->reminderShort(), settingsManager); } else { ACSDK_ERROR( LX("loadHelperFailed").m("Could not instantiate an alert object.").d("type read from database", type)); @@ -931,8 +936,10 @@ bool SQLiteAlertStorage::loadHelper(int dbVersion, std::vector>* alertContainer) { - return loadHelper(ALERTS_DATABASE_VERSION_TWO, alertContainer); +bool SQLiteAlertStorage::load( + std::vector>* alertContainer, + std::shared_ptr settingsManager) { + return loadHelper(ALERTS_DATABASE_VERSION_TWO, alertContainer, settingsManager); } bool SQLiteAlertStorage::modify(std::shared_ptr alert) { @@ -1199,7 +1206,7 @@ static void printAlertsSummary( void SQLiteAlertStorage::printStats(StatLevel level) { std::vector> alerts; - load(&alerts); + load(&alerts, nullptr); switch (level) { case StatLevel::ONE_LINE: printOneLineSummary(&m_db); diff --git a/CapabilityAgents/Alerts/src/Timer.cpp b/CapabilityAgents/Alerts/src/Timer.cpp index e09a5d60..e7eea5b1 100644 --- a/CapabilityAgents/Alerts/src/Timer.cpp +++ b/CapabilityAgents/Alerts/src/Timer.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -19,17 +19,15 @@ namespace alexaClientSDK { namespace capabilityAgents { namespace alerts { -/// Definition for static class data member. -const std::string Timer::TYPE_NAME = "TIMER"; - Timer::Timer( std::function()> defaultAudioFactory, - std::function()> shortAudioFactory) : - Alert(defaultAudioFactory, shortAudioFactory) { + std::function()> shortAudioFactory, + std::shared_ptr settingsManager) : + Alert(defaultAudioFactory, shortAudioFactory, settingsManager) { } std::string Timer::getTypeName() const { - return TYPE_NAME; + return Timer::getTypeNameStatic(); } } // namespace alerts diff --git a/CapabilityAgents/Alerts/test/AlarmAlertTest.cpp b/CapabilityAgents/Alerts/test/AlarmAlertTest.cpp index ff72b3ec..83d03d1f 100644 --- a/CapabilityAgents/Alerts/test/AlarmAlertTest.cpp +++ b/CapabilityAgents/Alerts/test/AlarmAlertTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -40,7 +40,7 @@ public: std::shared_ptr m_alarm; }; -AlarmAlertTest::AlarmAlertTest() : m_alarm{std::make_shared(alarmDefaultFactory, alarmShortFactory)} { +AlarmAlertTest::AlarmAlertTest() : m_alarm{std::make_shared(alarmDefaultFactory, alarmShortFactory, nullptr)} { } TEST_F(AlarmAlertTest, test_defaultAudio) { @@ -58,7 +58,7 @@ TEST_F(AlarmAlertTest, test_shortAudio) { } TEST_F(AlarmAlertTest, test_getTypeName) { - ASSERT_EQ(m_alarm->getTypeName(), Alarm::TYPE_NAME); + ASSERT_EQ(m_alarm->getTypeName(), Alarm::getTypeNameStatic()); } } // namespace test diff --git a/CapabilityAgents/Alerts/test/AlertSchedulerTest.cpp b/CapabilityAgents/Alerts/test/AlertSchedulerTest.cpp index 057bd0de..78a4f7cf 100644 --- a/CapabilityAgents/Alerts/test/AlertSchedulerTest.cpp +++ b/CapabilityAgents/Alerts/test/AlertSchedulerTest.cpp @@ -18,6 +18,8 @@ #include #include +#include +#include #include "Alerts/AlertScheduler.h" @@ -51,11 +53,12 @@ static const std::chrono::seconds ALERT_PAST_DUE_TIME_LIMIT{10}; class MockRenderer : public renderer::RendererInterface { public: - MOCK_METHOD6( + MOCK_METHOD7( start, void( std::shared_ptr observer, std::function()> audioFactory, + bool alarmVolumeRampEnabled, const std::vector& urls, int loopCount, std::chrono::milliseconds loopPause, @@ -66,14 +69,14 @@ public: class TestAlert : public Alert { public: TestAlert() : - Alert(defaultAudioFactory, shortAudioFactory), + Alert(defaultAudioFactory, shortAudioFactory, nullptr), m_alertType{ALERT_TYPE}, m_renderer{std::make_shared()} { this->setRenderer(m_renderer); } TestAlert(const std::string& token, const std::string& schedTime) : - Alert(defaultAudioFactory, shortAudioFactory), + Alert(defaultAudioFactory, shortAudioFactory, nullptr), m_alertType{ALERT_TYPE}, m_renderer{std::make_shared()} { this->setRenderer(m_renderer); @@ -140,7 +143,9 @@ public: bool store(std::shared_ptr alert) { return m_storeRetVal; } - bool load(std::vector>* alertContainer) { + bool load( + std::vector>* alertContainer, + std::shared_ptr settingsManager) { if (m_loadRetVal) { alertContainer->clear(); for (std::shared_ptr alertToAdd : m_alertsInStorage) { @@ -231,6 +236,7 @@ protected: std::chrono::seconds m_alertPastDueTimeLimit; std::shared_ptr m_alertScheduler; std::shared_ptr m_testAlertObserver; + std::shared_ptr m_settingsManager; }; static std::string getFutureInstant(int yearsPlus) { @@ -257,6 +263,8 @@ AlertSchedulerTest::AlertSchedulerTest() : void AlertSchedulerTest::SetUp() { m_alertStorage->setOpenRetVal(true); + m_settingsManager = + std::make_shared(std::make_shared()); } /** @@ -276,11 +284,12 @@ std::shared_ptr AlertSchedulerTest::doSimpleTestSetup(bool activateAl m_alertStorage->setAlerts(alertToAdd); if (initWithAlertObserver) { - m_alertScheduler->initialize(m_testAlertObserver); + m_alertScheduler->initialize(m_testAlertObserver, m_settingsManager); + ; } else { std::shared_ptr alertSchedulerObs{ std::make_shared(m_alertStorage, m_alertRenderer, m_alertPastDueTimeLimit)}; - m_alertScheduler->initialize(alertSchedulerObs); + m_alertScheduler->initialize(alertSchedulerObs, m_settingsManager); } if (activateAlert) { @@ -296,12 +305,13 @@ std::shared_ptr AlertSchedulerTest::doSimpleTestSetup(bool activateAl */ TEST_F(AlertSchedulerTest, test_initialize) { /// check if init fails if scheduler is not available - ASSERT_FALSE(m_alertScheduler->initialize(nullptr)); + ASSERT_FALSE(m_alertScheduler->initialize(nullptr, nullptr)); + ASSERT_FALSE(m_alertScheduler->initialize(nullptr, m_settingsManager)); /// check if init fails if a database for alerts cant be created m_alertStorage->setOpenRetVal(false); m_alertStorage->setCreateDatabaseRetVal(false); - ASSERT_FALSE(m_alertScheduler->initialize(m_alertScheduler)); + ASSERT_FALSE(m_alertScheduler->initialize(m_alertScheduler, m_settingsManager)); /// check if init succeeds. Pass in 3 alerts of which 1 is expired. Only 2 should actually remain in the end. std::shared_ptr alertSchedulerObs{ @@ -330,7 +340,7 @@ TEST_F(AlertSchedulerTest, test_initialize) { /// active alert should get modified EXPECT_CALL(*(m_alertStorage.get()), modify(testing::_)).Times(1); - ASSERT_TRUE(m_alertScheduler->initialize(alertSchedulerObs)); + ASSERT_TRUE(m_alertScheduler->initialize(alertSchedulerObs, m_settingsManager)); const unsigned int expectedRemainingAlerts = 2; @@ -455,7 +465,7 @@ TEST_F(AlertSchedulerTest, test_deleteAlertSingle) { std::shared_ptr alert1 = std::make_shared(ALERT1_TOKEN, getFutureInstant(1)); alertsToAdd.push_back(alert1); m_alertStorage->setAlerts(alertsToAdd); - m_alertScheduler->initialize(alertSchedulerObs); + m_alertScheduler->initialize(alertSchedulerObs, m_settingsManager); m_alertScheduler->updateFocus(avsCommon::avs::FocusState::BACKGROUND); // if active alert and the token matches, ensure that we dont delete it (we deactivate the alert actually) @@ -469,7 +479,7 @@ TEST_F(AlertSchedulerTest, test_deleteAlertSingle) { std::shared_ptr alert2 = std::make_shared(ALERT2_TOKEN, getFutureInstant(1)); alertsToAdd.push_back(alert2); m_alertStorage->setAlerts(alertsToAdd); - m_alertScheduler->initialize(alertSchedulerObs); + m_alertScheduler->initialize(alertSchedulerObs, m_settingsManager); EXPECT_CALL(*(m_alertStorage.get()), erase(testing::_)).Times(1); ASSERT_TRUE(m_alertScheduler->deleteAlert(ALERT2_TOKEN)); } @@ -489,7 +499,7 @@ TEST_F(AlertSchedulerTest, test_bulkDeleteAlertsSingle) { alertsToAdd.push_back(alert1); alertsToAdd.push_back(alert2); m_alertStorage->setAlerts(alertsToAdd); - m_alertScheduler->initialize(alertSchedulerObs); + m_alertScheduler->initialize(alertSchedulerObs, m_settingsManager); // Delete one existing ASSERT_EQ(m_alertScheduler->getAllAlerts().size(), 2u); @@ -520,7 +530,7 @@ TEST_F(AlertSchedulerTest, test_bulkDeleteAlertsMultipleExisting) { alertsToAdd.push_back(alert1); alertsToAdd.push_back(alert2); m_alertStorage->setAlerts(alertsToAdd); - m_alertScheduler->initialize(alertSchedulerObs); + m_alertScheduler->initialize(alertSchedulerObs, m_settingsManager); // Delete multiple existing EXPECT_TRUE(m_alertScheduler->deleteAlerts({ALERT1_TOKEN, ALERT2_TOKEN})); @@ -542,7 +552,7 @@ TEST_F(AlertSchedulerTest, test_bulkDeleteAlertsMultipleMixed) { alertsToAdd.push_back(alert1); alertsToAdd.push_back(alert2); m_alertStorage->setAlerts(alertsToAdd); - m_alertScheduler->initialize(alertSchedulerObs); + m_alertScheduler->initialize(alertSchedulerObs, m_settingsManager); // Delete multiple mixed EXPECT_TRUE(m_alertScheduler->deleteAlerts({ALERT1_TOKEN, ALERT3_TOKEN})); @@ -564,7 +574,7 @@ TEST_F(AlertSchedulerTest, test_bulkDeleteAlertsMultipleMissing) { alertsToAdd.push_back(alert1); alertsToAdd.push_back(alert2); m_alertStorage->setAlerts(alertsToAdd); - m_alertScheduler->initialize(alertSchedulerObs); + m_alertScheduler->initialize(alertSchedulerObs, m_settingsManager); // Delete multiple non-existing EXPECT_TRUE(m_alertScheduler->deleteAlerts({ALERT3_TOKEN, ALERT4_TOKEN})); @@ -586,7 +596,7 @@ TEST_F(AlertSchedulerTest, test_bulkDeleteAlertsMultipleSame) { alertsToAdd.push_back(alert1); alertsToAdd.push_back(alert2); m_alertStorage->setAlerts(alertsToAdd); - m_alertScheduler->initialize(alertSchedulerObs); + m_alertScheduler->initialize(alertSchedulerObs, m_settingsManager); // Delete same multiple times EXPECT_TRUE(m_alertScheduler->deleteAlerts({ALERT1_TOKEN, ALERT1_TOKEN})); @@ -608,7 +618,7 @@ TEST_F(AlertSchedulerTest, test_bulkDeleteAlertsMultipleEmpty) { alertsToAdd.push_back(alert1); alertsToAdd.push_back(alert2); m_alertStorage->setAlerts(alertsToAdd); - m_alertScheduler->initialize(alertSchedulerObs); + m_alertScheduler->initialize(alertSchedulerObs, m_settingsManager); // Delete empty EXPECT_TRUE(m_alertScheduler->deleteAlerts({})); @@ -627,7 +637,7 @@ TEST_F(AlertSchedulerTest, test_isAlertActive) { std::shared_ptr alert1 = std::make_shared(ALERT1_TOKEN, getFutureInstant(1)); alertsToAdd.push_back(alert1); m_alertStorage->setAlerts(alertsToAdd); - m_alertScheduler->initialize(alertSchedulerObs); + m_alertScheduler->initialize(alertSchedulerObs, m_settingsManager); m_alertScheduler->updateFocus(avsCommon::avs::FocusState::BACKGROUND); /// inactive alert @@ -656,7 +666,7 @@ TEST_F(AlertSchedulerTest, test_getContextInfo) { std::shared_ptr alert2 = std::make_shared(ALERT2_TOKEN, getFutureInstant(1)); alertsToAdd.push_back(alert2); m_alertStorage->setAlerts(alertsToAdd); - m_alertScheduler->initialize(alertSchedulerObs); + m_alertScheduler->initialize(alertSchedulerObs, m_settingsManager); m_alertScheduler->updateFocus(avsCommon::avs::FocusState::BACKGROUND); AlertScheduler::AlertsContextInfo resultContextInfo = m_alertScheduler->getContextInfo(); diff --git a/CapabilityAgents/Alerts/test/AlertTest.cpp b/CapabilityAgents/Alerts/test/AlertTest.cpp index 72136a7d..63b4d2d5 100644 --- a/CapabilityAgents/Alerts/test/AlertTest.cpp +++ b/CapabilityAgents/Alerts/test/AlertTest.cpp @@ -57,7 +57,7 @@ static const std::string SHORT_AUDIO{"short audio"}; class MockAlert : public Alert { public: - MockAlert() : Alert(defaultAudioFactory, shortAudioFactory) { + MockAlert() : Alert(defaultAudioFactory, shortAudioFactory, nullptr) { } std::string getTypeName() const override; @@ -80,6 +80,7 @@ public: void start( std::shared_ptr observer, std::function()> audioFactory, + bool alarmVolumeRampEnabled, const std::vector& urls = std::vector(), int loopCount = 0, std::chrono::milliseconds loopPause = std::chrono::milliseconds{0}, diff --git a/CapabilityAgents/Alerts/test/AlertsCapabilityAgentTest.cpp b/CapabilityAgents/Alerts/test/AlertsCapabilityAgentTest.cpp index cb883260..7bf1412f 100644 --- a/CapabilityAgents/Alerts/test/AlertsCapabilityAgentTest.cpp +++ b/CapabilityAgents/Alerts/test/AlertsCapabilityAgentTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018-2020 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. @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -57,7 +58,7 @@ using namespace settings; using namespace settings::test; using namespace testing; -/// Maximum time to wait for operaiton result. +/// Maximum time to wait for operation result. constexpr int MAX_WAIT_TIME_MS = 200; /// Alerts.SetVolume Directive name. @@ -143,19 +144,19 @@ public: void close() override { } - bool store(std::shared_ptr alert) override { + bool store(std::shared_ptr) override { return true; } - bool load(std::vector>* alertContainer) override { + bool load(std::vector>*, std::shared_ptr) override { return true; } - bool modify(std::shared_ptr alert) override { + bool modify(std::shared_ptr) override { return true; } - bool erase(std::shared_ptr alert) override { + bool erase(std::shared_ptr) override { return true; } @@ -163,7 +164,7 @@ public: return true; } - bool bulkErase(const std::list>& alertList) override { + bool bulkErase(const std::list>&) override { return true; } }; @@ -176,11 +177,11 @@ public: MOCK_METHOD0(createDatabase, bool()); MOCK_METHOD0(open, bool()); MOCK_METHOD0(close, void()); - MOCK_METHOD1(store, bool(std::shared_ptr alert)); - MOCK_METHOD1(load, bool(std::vector>* alertContainer)); - MOCK_METHOD1(modify, bool(std::shared_ptr alert)); - MOCK_METHOD1(erase, bool(std::shared_ptr alert)); - MOCK_METHOD1(bulkErase, bool(const std::list>& alertList)); + MOCK_METHOD1(store, bool(std::shared_ptr)); + MOCK_METHOD2(load, bool(std::vector>*, std::shared_ptr)); + MOCK_METHOD1(modify, bool(std::shared_ptr)); + MOCK_METHOD1(erase, bool(std::shared_ptr)); + MOCK_METHOD1(bulkErase, bool(const std::list>&)); MOCK_METHOD0(clearDatabase, bool()); }; @@ -188,6 +189,7 @@ class StubRenderer : public RendererInterface { void start( std::shared_ptr observer, std::function()> audioFactory, + bool alarmVolumeRampEnabled, const std::vector& urls, int loopCount, std::chrono::milliseconds loopPause, @@ -200,15 +202,16 @@ class StubRenderer : public RendererInterface { */ class MockRenderer : public RendererInterface { public: - MOCK_METHOD6( + MOCK_METHOD7( start, void( - std::shared_ptr ovserver, + std::shared_ptr observer, std::function()>, + bool, const std::vector&, - int loopcount, + int, std::chrono::milliseconds, - bool startWithPause)); + bool)); MOCK_METHOD0(stop, void()); }; @@ -312,11 +315,14 @@ protected: std::shared_ptr> m_mockAlarmVolumeRampSetting; std::shared_ptr m_customerDataManager; std::unique_ptr> m_mockDirectiveHandlerResult; + std::shared_ptr m_settingsManager; + std::shared_ptr m_metricRecorder; std::mutex m_mutex; }; void AlertsCapabilityAgentTest::SetUp() { + m_metricRecorder = std::make_shared>(); m_mockMessageSender = std::make_shared(); m_mockAVSConnectionManager = std::make_shared>(); m_mockFocusManager = std::make_shared>(); @@ -329,8 +335,11 @@ void AlertsCapabilityAgentTest::SetUp() { m_customerDataManager = std::make_shared(); m_messageStorage = std::make_shared(); m_mockDirectiveHandlerResult = make_unique>(); + m_settingsManager = + std::make_shared(std::make_shared()); m_mockAlarmVolumeRampSetting = - std::make_shared>(types::AlarmVolumeRampTypes::NONE); + std::make_shared>(settings::types::getAlarmVolumeRampDefault()); + ASSERT_TRUE(m_settingsManager->addSetting(m_mockAlarmVolumeRampSetting)); ON_CALL(*(m_speakerManager.get()), getSpeakerSettings(_, _)) .WillByDefault(Invoke([](SpeakerInterface::Type, SpeakerInterface::SpeakerSettings*) { @@ -349,7 +358,7 @@ void AlertsCapabilityAgentTest::SetUp() { ON_CALL(*(m_alertStorage), createDatabase()).WillByDefault(Return(true)); ON_CALL(*(m_alertStorage), open()).WillByDefault(Return(true)); ON_CALL(*(m_alertStorage), store(_)).WillByDefault(Return(true)); - ON_CALL(*(m_alertStorage), load(_)).WillByDefault(Return(true)); + ON_CALL(*(m_alertStorage), load(_, _)).WillByDefault(Return(true)); ON_CALL(*(m_alertStorage), modify(_)).WillByDefault(Return(true)); ON_CALL(*(m_alertStorage), erase(_)).WillByDefault(Return(true)); ON_CALL(*(m_alertStorage), bulkErase(_)).WillByDefault(Return(true)); @@ -370,7 +379,9 @@ void AlertsCapabilityAgentTest::SetUp() { m_alertsAudioFactory, m_renderer, m_customerDataManager, - m_mockAlarmVolumeRampSetting); + m_mockAlarmVolumeRampSetting, + m_settingsManager, + m_metricRecorder); std::static_pointer_cast(m_certifiedSender) ->onConnectionStatusChanged( diff --git a/CapabilityAgents/Alerts/test/ReminderAlertTest.cpp b/CapabilityAgents/Alerts/test/ReminderAlertTest.cpp index dea8885f..6b515cf7 100644 --- a/CapabilityAgents/Alerts/test/ReminderAlertTest.cpp +++ b/CapabilityAgents/Alerts/test/ReminderAlertTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -40,7 +40,7 @@ public: }; ReminderAlertTest::ReminderAlertTest() : - m_reminder{std::make_shared(reminderDefaultFactory, reminderShortFactory)} { + m_reminder{std::make_shared(reminderDefaultFactory, reminderShortFactory, nullptr)} { } TEST_F(ReminderAlertTest, test_defaultAudio) { @@ -58,7 +58,7 @@ TEST_F(ReminderAlertTest, test_shortAudio) { } TEST_F(ReminderAlertTest, test_getTypeName) { - ASSERT_EQ(m_reminder->getTypeName(), Reminder::TYPE_NAME); + ASSERT_EQ(m_reminder->getTypeName(), Reminder::getTypeNameStatic()); } } // namespace test diff --git a/CapabilityAgents/Alerts/test/Renderer/RendererTest.cpp b/CapabilityAgents/Alerts/test/Renderer/RendererTest.cpp index 17b913ed..667b5a2c 100644 --- a/CapabilityAgents/Alerts/test/Renderer/RendererTest.cpp +++ b/CapabilityAgents/Alerts/test/Renderer/RendererTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -16,6 +16,7 @@ #include #include +#include #include #include #include @@ -30,13 +31,19 @@ namespace alerts { namespace renderer { namespace test { +using namespace avsCommon::sdkInterfaces::test; using namespace avsCommon::utils::mediaPlayer::test; using namespace settings::types; using namespace settings::test; +using namespace testing; +using MediaPlayerState = avsCommon::utils::mediaPlayer::MediaPlayerState; /// Amount of time that the renderer observer should wait for a task to finish. static const std::chrono::milliseconds TEST_TIMEOUT{100}; +/// Default media player state to report for all playback events +static const MediaPlayerState DEFAULT_MEDIA_PLAYER_STATE = {std::chrono::milliseconds(0)}; + /// Test source Id that exists for the tests static const avsCommon::utils::mediaPlayer::MediaPlayerInterface::SourceId TEST_SOURCE_ID_GOOD = 1234; @@ -59,6 +66,8 @@ static const auto TEST_BACKGROUND_LOOP_PAUSE = std::chrono::seconds(1); /// Amount of time that the renderer observer should wait for a task to finish. static const auto TEST_BACKGROUND_TIMEOUT = std::chrono::seconds(5); +static const std::string ALARM_NAME = "ALARM"; + class MockRendererObserver : public RendererObserverInterface { public: bool waitFor(RendererObserverInterface::State newState) { @@ -182,7 +191,6 @@ public: protected: std::shared_ptr m_observer; std::shared_ptr m_mediaPlayer; - std::shared_ptr m_settingsManager; std::shared_ptr m_renderer; static std::unique_ptr audioFactoryFunc() { @@ -193,9 +201,7 @@ protected: RendererTest::RendererTest() : m_observer{std::make_shared()}, m_mediaPlayer{TestMediaPlayer::create()}, - m_settingsManager{std::make_shared( - std::make_shared())}, - m_renderer{Renderer::create(m_mediaPlayer, m_settingsManager)} { + m_renderer{Renderer::create(m_mediaPlayer)} { } RendererTest::~RendererTest() { @@ -205,7 +211,7 @@ RendererTest::~RendererTest() { void RendererTest::SetUpTest() { std::function()> audioFactory = RendererTest::audioFactoryFunc; std::vector urls = {TEST_URL1, TEST_URL2}; - m_renderer->start(m_observer, audioFactory, urls, TEST_LOOP_COUNT, TEST_LOOP_PAUSE); + m_renderer->start(m_observer, audioFactory, true, urls, TEST_LOOP_COUNT, TEST_LOOP_PAUSE); } void RendererTest::TearDown() { @@ -222,10 +228,7 @@ TEST_F(RendererTest, test_create) { ASSERT_NE(m_renderer, nullptr); /// confirm we return a nullptr if a nullptr was passed in - ASSERT_EQ(Renderer::create(nullptr, m_settingsManager), nullptr); - - /// confirm we return a nullptr if a nullptr was passed in - ASSERT_EQ(Renderer::create(m_mediaPlayer, nullptr), nullptr); + ASSERT_EQ(Renderer::create(nullptr), nullptr); } /** @@ -255,7 +258,7 @@ TEST_F(RendererTest, test_stop) { */ TEST_F(RendererTest, test_stopError) { SetUpTest(); - m_renderer->onPlaybackStarted(TEST_SOURCE_ID_GOOD); + m_renderer->onPlaybackStarted(TEST_SOURCE_ID_GOOD, DEFAULT_MEDIA_PLAYER_STATE); ASSERT_TRUE(m_observer->waitFor(RendererObserverInterface::State::STARTED)); m_mediaPlayer->setStopRetVal(false); @@ -272,11 +275,11 @@ TEST_F(RendererTest, test_onPlaybackStarted) { SetUpTest(); /// shouldn't start if the source is bad - m_renderer->onPlaybackStarted(TEST_SOURCE_ID_BAD); + m_renderer->onPlaybackStarted(TEST_SOURCE_ID_BAD, DEFAULT_MEDIA_PLAYER_STATE); ASSERT_FALSE(m_observer->waitFor(RendererObserverInterface::State::STARTED)); /// should start if the source is good - m_renderer->onPlaybackStarted(TEST_SOURCE_ID_GOOD); + m_renderer->onPlaybackStarted(TEST_SOURCE_ID_GOOD, DEFAULT_MEDIA_PLAYER_STATE); ASSERT_TRUE(m_observer->waitFor(RendererObserverInterface::State::STARTED)); } @@ -287,11 +290,11 @@ TEST_F(RendererTest, test_onPlaybackStopped) { SetUpTest(); /// shouldn't stop if the source is bad - m_renderer->onPlaybackStopped(TEST_SOURCE_ID_BAD); + m_renderer->onPlaybackStopped(TEST_SOURCE_ID_BAD, DEFAULT_MEDIA_PLAYER_STATE); ASSERT_FALSE(m_observer->waitFor(RendererObserverInterface::State::STOPPED)); /// should stop if the source is good - m_renderer->onPlaybackStopped(TEST_SOURCE_ID_GOOD); + m_renderer->onPlaybackStopped(TEST_SOURCE_ID_GOOD, DEFAULT_MEDIA_PLAYER_STATE); ASSERT_TRUE(m_observer->waitFor(RendererObserverInterface::State::STOPPED)); } @@ -303,13 +306,13 @@ TEST_F(RendererTest, test_onPlaybackFinishedError) { /// shouldn't finish even if the source is good, if the media player is errored out m_mediaPlayer->setSourceRetVal(avsCommon::utils::mediaPlayer::MediaPlayerInterface::ERROR); - m_renderer->onPlaybackFinished(TEST_SOURCE_ID_GOOD); + m_renderer->onPlaybackFinished(TEST_SOURCE_ID_GOOD, DEFAULT_MEDIA_PLAYER_STATE); ASSERT_FALSE(m_observer->waitFor(RendererObserverInterface::State::STOPPED)); /// shouldn't finish even if the source is good, if the media player can't play it m_mediaPlayer->setSourceRetVal(TEST_SOURCE_ID_GOOD); m_mediaPlayer->setPlayRetVal(false); - m_renderer->onPlaybackFinished(TEST_SOURCE_ID_GOOD); + m_renderer->onPlaybackFinished(TEST_SOURCE_ID_GOOD, DEFAULT_MEDIA_PLAYER_STATE); ASSERT_FALSE(m_observer->waitFor(RendererObserverInterface::State::STOPPED)); } @@ -324,11 +327,11 @@ TEST_F(RendererTest, test_onPlaybackError) { SetUpTest(); /// shouldn't respond with errors if the source is bad - m_renderer->onPlaybackError(TEST_SOURCE_ID_BAD, errorType, errorMsg); + m_renderer->onPlaybackError(TEST_SOURCE_ID_BAD, errorType, errorMsg, DEFAULT_MEDIA_PLAYER_STATE); ASSERT_FALSE(m_observer->waitFor(RendererObserverInterface::State::ERROR)); /// shouldn't respond with errors if the source is good - m_renderer->onPlaybackError(TEST_SOURCE_ID_GOOD, errorType, errorMsg); + m_renderer->onPlaybackError(TEST_SOURCE_ID_GOOD, errorType, errorMsg, DEFAULT_MEDIA_PLAYER_STATE); ASSERT_TRUE(m_observer->waitFor(RendererObserverInterface::State::ERROR)); } @@ -342,10 +345,10 @@ TEST_F(RendererTest, testTimer_emptyURLNonZeroLoopPause) { // pass empty URLS with 10s pause and no loop count // this simulates playing a default alarm audio on background // it is expected to renderer to play the alert sound continuously at loop pause intervals - m_renderer->start(m_observer, audioFactory, urls, TEST_LOOP_COUNT, TEST_BACKGROUND_LOOP_PAUSE); + m_renderer->start(m_observer, audioFactory, true, urls, TEST_LOOP_COUNT, TEST_BACKGROUND_LOOP_PAUSE); // mediaplayer starts playing the alarm audio, in this case audio is of 0 length - m_renderer->onPlaybackStarted(TEST_SOURCE_ID_GOOD); + m_renderer->onPlaybackStarted(TEST_SOURCE_ID_GOOD, DEFAULT_MEDIA_PLAYER_STATE); // record the time audio starts playing auto now = std::chrono::high_resolution_clock::now(); @@ -354,13 +357,13 @@ TEST_F(RendererTest, testTimer_emptyURLNonZeroLoopPause) { ASSERT_TRUE(m_observer->waitFor(RendererObserverInterface::State::STARTED, TEST_BACKGROUND_TIMEOUT)); // mediaplayer finishes playing the alarm audio - m_renderer->onPlaybackFinished(TEST_SOURCE_ID_GOOD); + m_renderer->onPlaybackFinished(TEST_SOURCE_ID_GOOD, DEFAULT_MEDIA_PLAYER_STATE); // mediaplayer starts playing the alarm audio, in this case audio is of 0 length - m_renderer->onPlaybackStarted(TEST_SOURCE_ID_GOOD); + m_renderer->onPlaybackStarted(TEST_SOURCE_ID_GOOD, DEFAULT_MEDIA_PLAYER_STATE); // mediaplayer finishes playing the alarm audio - m_renderer->onPlaybackFinished(TEST_SOURCE_ID_GOOD); + m_renderer->onPlaybackFinished(TEST_SOURCE_ID_GOOD, DEFAULT_MEDIA_PLAYER_STATE); ASSERT_TRUE(m_observer->waitFor(RendererObserverInterface::State::STARTED, TEST_BACKGROUND_TIMEOUT)); @@ -384,10 +387,6 @@ TEST_F(RendererTest, test_alarmVolumeRampRendering) { // Pause interval for this test. const auto loopPause = std::chrono::seconds(1); - // Create a mock setting for alarm volume ramp with enabled value. - auto setting = std::make_shared>(toAlarmRamp(true)); - m_settingsManager->addSetting(setting); - // Create a thread that will observe the FadeIn config that is set to the MediaPlayer; std::thread sourceConfigObserver([this, loopPause]() { avsCommon::utils::mediaPlayer::SourceConfig config; @@ -397,22 +396,22 @@ TEST_F(RendererTest, test_alarmVolumeRampRendering) { std::tie(ok, config) = m_mediaPlayer->waitForSourceConfig(6 * loopPause); ASSERT_TRUE(ok); ASSERT_EQ(config.fadeInConfig.startGain, 0); - m_renderer->onPlaybackStarted(TEST_SOURCE_ID_GOOD); - m_renderer->onPlaybackFinished(TEST_SOURCE_ID_GOOD); + m_renderer->onPlaybackStarted(TEST_SOURCE_ID_GOOD, DEFAULT_MEDIA_PLAYER_STATE); + m_renderer->onPlaybackFinished(TEST_SOURCE_ID_GOOD, DEFAULT_MEDIA_PLAYER_STATE); // Check that the gain increases at each repetition. std::tie(ok, config) = m_mediaPlayer->waitForSourceConfig(6 * loopPause); ASSERT_TRUE(ok); ASSERT_GT(config.fadeInConfig.startGain, 0); - m_renderer->onPlaybackStarted(TEST_SOURCE_ID_GOOD); - m_renderer->onPlaybackFinished(TEST_SOURCE_ID_GOOD); + m_renderer->onPlaybackStarted(TEST_SOURCE_ID_GOOD, DEFAULT_MEDIA_PLAYER_STATE); + m_renderer->onPlaybackFinished(TEST_SOURCE_ID_GOOD, DEFAULT_MEDIA_PLAYER_STATE); }); // pass empty URLS with 1s pause // this simulates playing a default alarm audio on background // it is expected to renderer to play the alert sound continuously at loop pause intervals constexpr int testLoopCount = 2; - m_renderer->start(m_observer, audioFactory, urls, testLoopCount, loopPause); + m_renderer->start(m_observer, audioFactory, true, urls, testLoopCount, loopPause); sourceConfigObserver.join(); } diff --git a/CapabilityAgents/Alerts/test/TimerAlertTest.cpp b/CapabilityAgents/Alerts/test/TimerAlertTest.cpp index 896c7e9d..562f230d 100644 --- a/CapabilityAgents/Alerts/test/TimerAlertTest.cpp +++ b/CapabilityAgents/Alerts/test/TimerAlertTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -40,7 +40,7 @@ public: std::shared_ptr m_timer; }; -TimerAlertTest::TimerAlertTest() : m_timer{std::make_shared(timerDefaultFactory, timerShortFactory)} { +TimerAlertTest::TimerAlertTest() : m_timer{std::make_shared(timerDefaultFactory, timerShortFactory, nullptr)} { } TEST_F(TimerAlertTest, test_defaultAudio) { @@ -58,7 +58,7 @@ TEST_F(TimerAlertTest, test_shortAudio) { } TEST_F(TimerAlertTest, test_getTypeName) { - ASSERT_EQ(m_timer->getTypeName(), Timer::TYPE_NAME); + ASSERT_EQ(m_timer->getTypeName(), Timer::getTypeNameStatic()); } } // namespace test } // namespace alerts diff --git a/CapabilityAgents/Alexa/CMakeLists.txt b/CapabilityAgents/Alexa/CMakeLists.txt index f7298c8b..df5dcd28 100644 --- a/CapabilityAgents/Alexa/CMakeLists.txt +++ b/CapabilityAgents/Alexa/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.0) project(Alexa LANGUAGES CXX) -include(../../build/BuildDefaults.cmake) +include(${AVS_CORE}/build/BuildDefaults.cmake) add_subdirectory("src") add_subdirectory("test") diff --git a/CapabilityAgents/Alexa/test/AlexaInterfaceMessageSenderTest.cpp b/CapabilityAgents/Alexa/test/AlexaInterfaceMessageSenderTest.cpp index 2cc2f80f..dc67a110 100644 --- a/CapabilityAgents/Alexa/test/AlexaInterfaceMessageSenderTest.cpp +++ b/CapabilityAgents/Alexa/test/AlexaInterfaceMessageSenderTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2019-2020 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. @@ -47,7 +47,7 @@ using namespace avsCommon::utils::timing; using namespace rapidjson; /// Amount of time for the test to wait for event to be sent. -static const std::chrono::seconds WAIT_TIMEOUT(2); +static const std::chrono::seconds MY_WAIT_TIMEOUT(2); /// Name for PowerController. static const std::string NAME_POWER_CONTROLLER("PowerController"); @@ -528,11 +528,11 @@ bool AlexaInterfaceMessageSenderTest::expectEventSent( triggerOperation(); - EXPECT_TRUE(contextPromise.get_future().wait_for(WAIT_TIMEOUT) == std::future_status::ready); + EXPECT_TRUE(contextPromise.get_future().wait_for(MY_WAIT_TIMEOUT) == std::future_status::ready); auto sendFuture = eventPromise.get_future(); bool isSendFutureReady = false; - isSendFutureReady = sendFuture.wait_for(WAIT_TIMEOUT) == std::future_status::ready; + isSendFutureReady = sendFuture.wait_for(MY_WAIT_TIMEOUT) == std::future_status::ready; // Workaround GTEST issue where expectations can hold a reference to a shared_ptr even after we wait for the future. EXPECT_TRUE(Mock::VerifyAndClearExpectations(m_messageSender.get())); @@ -577,11 +577,11 @@ bool AlexaInterfaceMessageSenderTest::expectEventSentOnInvalidContext( triggerOperation(); - EXPECT_TRUE(contextPromise.get_future().wait_for(WAIT_TIMEOUT) == std::future_status::ready); + EXPECT_TRUE(contextPromise.get_future().wait_for(MY_WAIT_TIMEOUT) == std::future_status::ready); auto sendFuture = eventPromise.get_future(); bool isSendFutureReady = false; - isSendFutureReady = sendFuture.wait_for(WAIT_TIMEOUT) == std::future_status::ready; + isSendFutureReady = sendFuture.wait_for(MY_WAIT_TIMEOUT) == std::future_status::ready; // Workaround GTEST issue where expectations can hold a reference to a shared_ptr even after we wait for the future. EXPECT_TRUE(Mock::VerifyAndClearExpectations(m_messageSender.get())); @@ -620,7 +620,7 @@ bool AlexaInterfaceMessageSenderTest::expectEventSentWithoutContext( auto sendFuture = eventPromise.get_future(); bool isSendFutureReady = false; - isSendFutureReady = sendFuture.wait_for(WAIT_TIMEOUT) == std::future_status::ready; + isSendFutureReady = sendFuture.wait_for(MY_WAIT_TIMEOUT) == std::future_status::ready; // Workaround GTEST issue where expectations can hold a reference to a shared_ptr even after we wait for the future. EXPECT_TRUE(Mock::VerifyAndClearExpectations(m_messageSender.get())); @@ -652,11 +652,11 @@ bool AlexaInterfaceMessageSenderTest::expectEventNotSentOnInvalidContext( triggerOperation(); - EXPECT_TRUE(contextPromise.get_future().wait_for(WAIT_TIMEOUT) == std::future_status::ready); + EXPECT_TRUE(contextPromise.get_future().wait_for(MY_WAIT_TIMEOUT) == std::future_status::ready); auto sendFuture = eventPromise.get_future(); bool isSendFutureReady = false; - isSendFutureReady = sendFuture.wait_for(WAIT_TIMEOUT) == std::future_status::ready; + isSendFutureReady = sendFuture.wait_for(MY_WAIT_TIMEOUT) == std::future_status::ready; // Workaround GTEST issue where expectations can hold a reference to a shared_ptr even after we wait for the future. EXPECT_TRUE(Mock::VerifyAndClearExpectations(m_messageSender.get())); diff --git a/CapabilityAgents/ApiGateway/CMakeLists.txt b/CapabilityAgents/ApiGateway/CMakeLists.txt index 8884ea12..c92dd9de 100644 --- a/CapabilityAgents/ApiGateway/CMakeLists.txt +++ b/CapabilityAgents/ApiGateway/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.0) project(ApiGateway LANGUAGES CXX) -include(../../build/BuildDefaults.cmake) +include(${AVS_CORE}/build/BuildDefaults.cmake) add_subdirectory("src") add_subdirectory("test") diff --git a/CapabilityAgents/AudioPlayer/CMakeLists.txt b/CapabilityAgents/AudioPlayer/CMakeLists.txt index fc3f89ea..b5841456 100644 --- a/CapabilityAgents/AudioPlayer/CMakeLists.txt +++ b/CapabilityAgents/AudioPlayer/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.1 FATAL_ERROR) project(AudioPlayer LANGUAGES CXX) -include(../../build/BuildDefaults.cmake) +include(${AVS_CORE}/build/BuildDefaults.cmake) add_subdirectory("src") add_subdirectory("test") diff --git a/CapabilityAgents/AudioPlayer/include/AudioPlayer/AudioItem.h b/CapabilityAgents/AudioPlayer/include/AudioPlayer/AudioItem.h index 84eeac42..419de76c 100644 --- a/CapabilityAgents/AudioPlayer/include/AudioPlayer/AudioItem.h +++ b/CapabilityAgents/AudioPlayer/include/AudioPlayer/AudioItem.h @@ -21,6 +21,7 @@ #include #include +#include #include #include "StreamFormat.h" @@ -95,6 +96,12 @@ struct AudioItem { /// The caption content that goes with the audio. captions::CaptionData captionData; + + /// Metadata cache for duplicate removal + alexaClientSDK::avsCommon::utils::mediaPlayer::MediaPlayerObserverInterface::VectorOfTags cachedMetadata; + + /// Time of last Metadata event (used to rate limit metadata events) + std::chrono::time_point lastMetadataEvent; }; } // namespace audioPlayer diff --git a/CapabilityAgents/AudioPlayer/include/AudioPlayer/AudioPlayer.h b/CapabilityAgents/AudioPlayer/include/AudioPlayer/AudioPlayer.h index 49df3c9e..803e18d4 100644 --- a/CapabilityAgents/AudioPlayer/include/AudioPlayer/AudioPlayer.h +++ b/CapabilityAgents/AudioPlayer/include/AudioPlayer/AudioPlayer.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -70,6 +70,11 @@ class AudioPlayer , public avsCommon::utils::RequiresShutdown , public std::enable_shared_from_this { public: + /** + * Destructor. + */ + virtual ~AudioPlayer() = default; + /** * Creates a new @c AudioPlayer instance. * @@ -110,21 +115,29 @@ public: /// @name ChannelObserverInterface Functions /// @{ - void onFocusChanged(avsCommon::avs::FocusState newFocus) override; + void onFocusChanged(avsCommon::avs::FocusState newFocus, avsCommon::avs::MixingBehavior behavior) override; /// @} /// @name MediaPlayerObserverInterface Functions /// @{ - void onPlaybackStarted(SourceId id) override; - void onPlaybackStopped(SourceId id) override; - void onPlaybackFinished(SourceId id) override; - void onPlaybackError(SourceId id, const avsCommon::utils::mediaPlayer::ErrorType& type, std::string error) override; - void onPlaybackPaused(SourceId id) override; - void onPlaybackResumed(SourceId id) override; - void onBufferUnderrun(SourceId id) override; - void onBufferRefilled(SourceId id) override; - void onBufferingComplete(SourceId id) override; - void onTags(SourceId id, std::unique_ptr vectorOfTags) override; + void onFirstByteRead(SourceId id, const avsCommon::utils::mediaPlayer::MediaPlayerState& state) override; + void onPlaybackStarted(SourceId id, const avsCommon::utils::mediaPlayer::MediaPlayerState& state) override; + void onPlaybackStopped(SourceId id, const avsCommon::utils::mediaPlayer::MediaPlayerState& state) override; + void onPlaybackFinished(SourceId id, const avsCommon::utils::mediaPlayer::MediaPlayerState& state) override; + void onPlaybackError( + SourceId id, + const avsCommon::utils::mediaPlayer::ErrorType& type, + std::string error, + const avsCommon::utils::mediaPlayer::MediaPlayerState& state) override; + void onPlaybackPaused(SourceId id, const avsCommon::utils::mediaPlayer::MediaPlayerState& state) override; + void onPlaybackResumed(SourceId id, const avsCommon::utils::mediaPlayer::MediaPlayerState& state) override; + void onBufferUnderrun(SourceId id, const avsCommon::utils::mediaPlayer::MediaPlayerState& state) override; + void onBufferRefilled(SourceId id, const avsCommon::utils::mediaPlayer::MediaPlayerState& state) override; + void onBufferingComplete(SourceId id, const avsCommon::utils::mediaPlayer::MediaPlayerState& state) override; + void onTags( + SourceId id, + std::unique_ptr vectorOfTags, + const avsCommon::utils::mediaPlayer::MediaPlayerState& state) override; /// @} /// @name MediaPlayerFactoryObserverInterface Functions @@ -318,36 +331,41 @@ private: * @li If focus changes to @c NONE, all playback will be stopped. * * @param newFocus The focus state to change to. + * @param behavior The mixing behavior to change to. */ - void executeOnFocusChanged(avsCommon::avs::FocusState newFocus); + void executeOnFocusChanged(avsCommon::avs::FocusState newFocus, avsCommon::avs::MixingBehavior behavior); /** * Executes onPlaybackStarted callback function * * @param id The id of the source to which this executed callback corresponds to. + * @param state Metadata about the media player state */ - void executeOnPlaybackStarted(SourceId id); + void executeOnPlaybackStarted(SourceId id, const avsCommon::utils::mediaPlayer::MediaPlayerState& state); /** * Executes onPlaybackStopped callback function * * @param id The id of the source to which this executed callback corresponds to. + * @param state Metadata about the media player state */ - void executeOnPlaybackStopped(SourceId id); + void executeOnPlaybackStopped(SourceId id, const avsCommon::utils::mediaPlayer::MediaPlayerState& state); /** * Executes onPlaybackFinished callback function * * @param id The id of the source to which this executed callback corresponds to. + * @param state Metadata about the media player state */ - void executeOnPlaybackFinished(SourceId id); + void executeOnPlaybackFinished(SourceId id, const avsCommon::utils::mediaPlayer::MediaPlayerState& state); /** * Executes onBufferingComplete callback function * * @param id The id of the source to which this executed callback corresponds to. + * @param state Metadata about the media player state */ - void executeOnBufferingComplete(SourceId id); + void executeOnBufferingComplete(SourceId id, const avsCommon::utils::mediaPlayer::MediaPlayerState& state); /// Performs necessary cleanup when playback has finished/stopped. void handlePlaybackCompleted(); @@ -370,44 +388,59 @@ private: * Executes onPlaybackError callback function * * @param id The id of the source to which this executed callback corresponds to. + * @param type Error type + * @param error Error in string format + * @param state Metadata about the media player state */ - void executeOnPlaybackError(SourceId id, const avsCommon::utils::mediaPlayer::ErrorType& type, std::string error); + void executeOnPlaybackError( + SourceId id, + const avsCommon::utils::mediaPlayer::ErrorType& type, + std::string error, + const avsCommon::utils::mediaPlayer::MediaPlayerState& state); /** * Executes onPlaybackPaused callback function * * @param id The id of the source to which this executed callback corresponds to. + * @param state Metadata about the media player state */ - void executeOnPlaybackPaused(SourceId id); + void executeOnPlaybackPaused(SourceId id, const avsCommon::utils::mediaPlayer::MediaPlayerState& state); /** * Executes onPlaybackResumed callback function * * @param id The id of the source to which this executed callback corresponds to. + * @param state Metadata about the media player state */ - void executeOnPlaybackResumed(SourceId id); + void executeOnPlaybackResumed(SourceId id, const avsCommon::utils::mediaPlayer::MediaPlayerState& state); /** * Executes onBufferUnderrun callback function * * @param id The id of the source to which this executed callback corresponds to. + * @param state Metadata about the media player state */ - void executeOnBufferUnderrun(SourceId id); + void executeOnBufferUnderrun(SourceId id, const avsCommon::utils::mediaPlayer::MediaPlayerState& state); /** * Executes onBufferRefilled callback function * * @param id The id of the source to which this executed callback corresponds to. + * @param state Metadata about the media player state */ - void executeOnBufferRefilled(SourceId id); + void executeOnBufferRefilled(SourceId id, const avsCommon::utils::mediaPlayer::MediaPlayerState& state); /** * Executes onTags callback function * * @param id The id of the source to which this callback corresponds to. * @param vectorOfTags The vector containing stream tags. + * @param state Metadata about the media player state */ - void executeOnTags(SourceId id, std::shared_ptr vectorOfTags); + void executeOnTags( + SourceId id, + std::shared_ptr vectorOfTags, + const avsCommon::utils::mediaPlayer::MediaPlayerState& state); /** * Executes onReadyToProvideNextPlayer callback function @@ -464,51 +497,91 @@ private: const std::string& eventName, std::chrono::milliseconds offset = avsCommon::utils::mediaPlayer::MEDIA_PLAYER_INVALID_OFFSET); - /// Send a @c PlaybackStarted event. - void sendPlaybackStartedEvent(); - - /// Send a @c PlaybackNearlyFinished event. - void sendPlaybackNearlyFinishedEvent(); - - /// Send a @c PlaybackStutterStarted event. - void sendPlaybackStutterStartedEvent(); - - /// Send a @c PlaybackStutterFinished event. - void sendPlaybackStutterFinishedEvent(); - - /// Send a @c PlaybackFinished event. - void sendPlaybackFinishedEvent(); + /** + * Send a @c PlaybackStarted event with given state + * + * @param state Metadata about the media player state + */ + void sendPlaybackStartedEvent(const avsCommon::utils::mediaPlayer::MediaPlayerState& state); /** - * Send a @c PlaybackFailed event. + * Send a @c PlaybackNearlyFinished event with given state + * + * @param state Metadata about the media player state + */ + void sendPlaybackNearlyFinishedEvent(const avsCommon::utils::mediaPlayer::MediaPlayerState& state); + + /** + * Send a @c PlaybackStutterStarted event with given state + * + * @param state Metadata about the media player state + */ + void sendPlaybackStutterStartedEvent(const avsCommon::utils::mediaPlayer::MediaPlayerState& state); + + /** + * Send a @c PlaybackStutterFinished event with given state + * + * @param state Metadata about the media player state + */ + void sendPlaybackStutterFinishedEvent(const avsCommon::utils::mediaPlayer::MediaPlayerState& state); + + /** + * Send a @c PlaybackFinished event with given state + * + * @param state Metadata about the media player state + */ + void sendPlaybackFinishedEvent(const avsCommon::utils::mediaPlayer::MediaPlayerState& state); + + /** + * Send a @c PlaybackFailed event with given state * * @param failingToken The token of the playback item that failed. * @param errorType The cause of the failure. * @param message A message describing the failure. + * @param state Metadata about the media player state */ void sendPlaybackFailedEvent( const std::string& failingToken, avsCommon::utils::mediaPlayer::ErrorType errorType, - const std::string& message); + const std::string& message, + const avsCommon::utils::mediaPlayer::MediaPlayerState& state); - /// Send a @c PlaybackStopped event. - void sendPlaybackStoppedEvent(); + /** + * Send a @c PlaybackStop event with given state + * + * @param state Metadata about the media player state + */ + ; + void sendPlaybackStoppedEvent(const avsCommon::utils::mediaPlayer::MediaPlayerState& state); - /// Send a @c PlaybackPaused event. - void sendPlaybackPausedEvent(); + /** + * Send a @c PlaybackPaused event with given state + * + * @param state Metadata about the media player state + */ + void sendPlaybackPausedEvent(const avsCommon::utils::mediaPlayer::MediaPlayerState& state); - /// Send a @c PlaybackResumed event. - void sendPlaybackResumedEvent(); + /** + * Send a @c PlaybackResumed event with given state + * + * @param state Metadata about the media player state + */ + void sendPlaybackResumedEvent(const avsCommon::utils::mediaPlayer::MediaPlayerState& state); /// Send a @c PlaybackQueueCleared event. void sendPlaybackQueueClearedEvent(); /** - * Send a @c StreamMetadataExtracted event. + * Send a @c StreamMetadataExtracted event with given state * + * @param audioItem item associated with the metadata * @param vectorOfTags Pointer to vector of tags that should be sent to AVS. + * @param state Metadata about the media player state */ - void sendStreamMetadataExtractedEvent(const std::string& token, std::shared_ptr vectorOfTags); + void sendStreamMetadataExtractedEvent( + AudioItem& audioItem, + std::shared_ptr vectorOfTags, + const avsCommon::utils::mediaPlayer::MediaPlayerState& state); /// Notify AudioPlayerObservers of state changes. void notifyObserver(); @@ -526,9 +599,26 @@ private: std::chrono::milliseconds getOffset(); /** - * Clears the Play Queue, releasing all players first. + * Get a media player state with the given offset + * + * @return Media player state with current offset */ - void clearPlayQueue(); + avsCommon::utils::mediaPlayer::MediaPlayerState getMediaPlayerState(); + + /** + * Clears the Play Queue, releasing all players first. + * + * @param stopCurrentPlayer Whether or not to stop the current media player + */ + void clearPlayQueue(const bool stopCurrentPlayer); + + /** + * Stop and clean-up MediaPlayer information in a PlayDirectiveInfo, and return it to the + * Factory + * + * @param playbackItem PlayDirectiveInfo holding the MediaPlayer + */ + void stopAndReleaseMediaPlayer(std::shared_ptr playbackItem); /** * Clean-up MediaPlayer information in a PlayDirectiveInfo, and return it to the Factory @@ -551,7 +641,7 @@ private: /// This is used to safely access the time utilities. avsCommon::utils::timing::TimeUtils m_timeUtils; - /// MediaPlayerFactoryInterface instance ised to generate Players used to play tracks. + /// MediaPlayerFactoryInterface instance is used to generate Players used to play tracks. std::unique_ptr m_mediaPlayerFactory; /// The object to use for sending events. @@ -653,6 +743,12 @@ private: /// Set of capability configurations that will get published using the Capabilities API std::unordered_set> m_capabilityConfigurations; + /// Current ContentType Rendering in the AudioPlayer + avsCommon::avs::ContentType m_currentMixability; + + /// Current MixingBehavior for the AudioPlayer. + avsCommon::avs::MixingBehavior m_mixingBehavior; + /** * @c Executor which queues up operations from asynchronous API calls. * diff --git a/CapabilityAgents/AudioPlayer/include/AudioPlayer/ProgressTimer.h b/CapabilityAgents/AudioPlayer/include/AudioPlayer/ProgressTimer.h index 07b82b75..cc4b3893 100644 --- a/CapabilityAgents/AudioPlayer/include/AudioPlayer/ProgressTimer.h +++ b/CapabilityAgents/AudioPlayer/include/AudioPlayer/ProgressTimer.h @@ -1,5 +1,5 @@ /* - * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018-2020 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. @@ -61,11 +61,15 @@ public: virtual ~ContextInterface() = default; }; - /// Delay value for no ProgressReportDelayElapsed notifications. - static const std::chrono::milliseconds NO_DELAY; + /// Static function member to get delay value for no ProgressReportDelayElapsed notifications.. + static std::chrono::milliseconds getNoDelay() { + return std::chrono::milliseconds::max(); + } - /// Interval value for no ProgressReportIntervalElapsed notifications. - static const std::chrono::milliseconds NO_INTERVAL; + /// Static function member to get interval value for no ProgressReportIntervalElapsed notifications. + static std::chrono::milliseconds getNoInterval() { + return std::chrono::milliseconds::max(); + } /** * Constructor. @@ -84,10 +88,10 @@ public: * * @param context The context within which to operate. * @param delay The offset (in milliseconds from the start of the track) at which to send the - * @c ProgressReportDelayElapsed event. If delay is @c NO_DELAY, no @c ProgressReportDelayElapsed + * @c ProgressReportDelayElapsed event. If delay is @c ProgressTimer::getNoDelay(), no @c ProgressReportDelayElapsed * notifications will be sent. * @param interval The interval (in milliseconds from the start of the track) at which to send - * @c ProgressReportIntervalElapsed events. If interval is @c NO_INTERVAL, no + * @c ProgressReportIntervalElapsed events. If interval is @c ProgressTimer::getNoInterval(), no * @c ProgressReportIntervalElapsed notifications will be sent. * @param offset The offset (in milliseconds from the start of the track) at which playback of * the track will start. diff --git a/CapabilityAgents/AudioPlayer/src/AudioPlayer.cpp b/CapabilityAgents/AudioPlayer/src/AudioPlayer.cpp index 340abe32..253c07af 100644 --- a/CapabilityAgents/AudioPlayer/src/AudioPlayer.cpp +++ b/CapabilityAgents/AudioPlayer/src/AudioPlayer.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2019-2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * SPDX-License-Identifier: LicenseRef-.amazon.com.-ASL-1.0 * Licensed under the Amazon Software License (the "License"). @@ -16,6 +16,7 @@ /// @file AudioPlayer.cpp #include +#include #include "AudioPlayer/AudioPlayer.h" @@ -38,6 +39,7 @@ using namespace avsCommon::utils; using namespace avsCommon::utils::json; using namespace avsCommon::utils::logger; using namespace avsCommon::utils::mediaPlayer; +using MediaPlayerState = avsCommon::utils::mediaPlayer::MediaPlayerState; /// AudioPlayer capability constants /// AudioPlayer interface type @@ -61,7 +63,7 @@ static const AudioPlayer::SourceId ERROR_SOURCE_ID = MediaPlayerInterface::ERROR #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) /// The name of the @c FocusManager channel used by @c AudioPlayer. -static const std::string CHANNEL_NAME = avsCommon::sdkInterfaces::FocusManagerInterface::CONTENT_CHANNEL_NAME; +static const std::string CHANNEL_NAME = FocusManagerInterface::CONTENT_CHANNEL_NAME; /// The namespace for this capability agent. static const std::string NAMESPACE = "AudioPlayer"; @@ -108,6 +110,13 @@ static const std::chrono::seconds TIMEOUT{2}; /// default mixing behavior static const audio::MixingBehavior DEFAULT_MIXING_BEHAVIOR = audio::MixingBehavior::BEHAVIOR_PAUSE; +/// whitelisted metadata to send to server. This is done to avoid excessive traffic +/// must be all lower-case +static const std::vector METADATA_WHITELIST = {"title"}; + +/// Min time between metadata events +static const std::chrono::seconds METADATA_EVENT_RATE{1}; + /** * Compare two URLs up to the 'query' portion, if one exists. * @@ -259,7 +268,7 @@ void AudioPlayer::cancelDirective(std::shared_ptr info) { for (auto it = m_audioPlayQueue.begin(); it != m_audioPlayQueue.end(); it++) { if (messageId == (*it)->messageId) { if (it->get()->mediaPlayer) { - releaseMediaPlayer(*it); + stopAndReleaseMediaPlayer(*it); } m_audioPlayQueue.erase(it); break; @@ -274,7 +283,7 @@ void AudioPlayer::onDeregistered() { ACSDK_DEBUG(LX("onDeregistered")); m_executor.submit([this] { executeStop(); - m_audioPlayQueue.clear(); + clearPlayQueue(false); }); } @@ -288,9 +297,9 @@ DirectiveHandlerConfiguration AudioPlayer::getConfiguration() const { return configuration; } -void AudioPlayer::onFocusChanged(FocusState newFocus) { - ACSDK_DEBUG(LX("onFocusChanged").d("newFocus", newFocus)); - m_executor.submit([this, newFocus] { executeOnFocusChanged(newFocus); }); +void AudioPlayer::onFocusChanged(FocusState newFocus, MixingBehavior behavior) { + ACSDK_DEBUG(LX("onFocusChanged").d("newFocus", newFocus).d("MixingBehavior", behavior)); + m_executor.submit([this, newFocus, behavior] { executeOnFocusChanged(newFocus, behavior); }); switch (newFocus) { case FocusState::FOREGROUND: @@ -354,59 +363,67 @@ void AudioPlayer::onFocusChanged(FocusState newFocus) { ACSDK_ERROR(LX("onFocusChangedFailed").d("reason", "unexpectedFocusState").d("newFocus", newFocus)); } -void AudioPlayer::onPlaybackStarted(SourceId id) { - ACSDK_DEBUG(LX("onPlaybackStarted").d("id", id)); - m_executor.submit([this, id] { executeOnPlaybackStarted(id); }); +void AudioPlayer::onFirstByteRead(SourceId id, const MediaPlayerState& state) { + ACSDK_DEBUG(LX(__func__).d("id", id).d("state", state)); } -void AudioPlayer::onPlaybackStopped(SourceId id) { - ACSDK_DEBUG(LX("onPlaybackStopped").d("id", id)); - m_executor.submit([this, id] { executeOnPlaybackStopped(id); }); +void AudioPlayer::onPlaybackStarted(SourceId id, const MediaPlayerState& state) { + ACSDK_DEBUG(LX("onPlaybackStarted").d("id", id).d("state", state)); + m_executor.submit([this, id, state] { executeOnPlaybackStarted(id, state); }); } -void AudioPlayer::onPlaybackFinished(SourceId id) { - ACSDK_DEBUG(LX("onPlaybackFinished").d("id", id)); - m_executor.submit([this, id] { executeOnPlaybackFinished(id); }); +void AudioPlayer::onPlaybackStopped(SourceId id, const MediaPlayerState& state) { + ACSDK_DEBUG(LX("onPlaybackStopped").d("id", id).d("state", state)); + m_executor.submit([this, id, state] { executeOnPlaybackStopped(id, state); }); } -void AudioPlayer::onPlaybackError(SourceId id, const ErrorType& type, std::string error) { - ACSDK_DEBUG(LX("onPlaybackError").d("type", type).d("error", error).d("id", id)); - m_executor.submit([this, id, type, error] { executeOnPlaybackError(id, type, error); }); +void AudioPlayer::onPlaybackFinished(SourceId id, const MediaPlayerState& state) { + ACSDK_DEBUG(LX("onPlaybackFinished").d("id", id).d("state", state)); + m_executor.submit([this, id, state] { executeOnPlaybackFinished(id, state); }); } -void AudioPlayer::onPlaybackPaused(SourceId id) { - ACSDK_DEBUG(LX("onPlaybackPaused").d("id", id)); - m_executor.submit([this, id] { executeOnPlaybackPaused(id); }); +void AudioPlayer::onPlaybackError( + SourceId id, + const ErrorType& type, + std::string error, + const MediaPlayerState& state) { + ACSDK_DEBUG(LX("onPlaybackError").d("type", type).d("error", error).d("id", id).d("state", state)); + m_executor.submit([this, id, type, error, state] { executeOnPlaybackError(id, type, error, state); }); } -void AudioPlayer::onPlaybackResumed(SourceId id) { - ACSDK_DEBUG(LX("onPlaybackResumed").d("id", id)); - m_executor.submit([this, id] { executeOnPlaybackResumed(id); }); +void AudioPlayer::onPlaybackPaused(SourceId id, const MediaPlayerState& state) { + ACSDK_DEBUG(LX("onPlaybackPaused").d("id", id).d("state", state)); + m_executor.submit([this, id, state] { executeOnPlaybackPaused(id, state); }); } -void AudioPlayer::onBufferUnderrun(SourceId id) { - ACSDK_DEBUG(LX("onBufferUnderrun").d("id", id)); - m_executor.submit([this, id] { executeOnBufferUnderrun(id); }); +void AudioPlayer::onPlaybackResumed(SourceId id, const MediaPlayerState& state) { + ACSDK_DEBUG(LX("onPlaybackResumed").d("id", id).d("state", state)); + m_executor.submit([this, id, state] { executeOnPlaybackResumed(id, state); }); } -void AudioPlayer::onBufferRefilled(SourceId id) { - ACSDK_DEBUG(LX("onBufferRefilled").d("id", id)); - m_executor.submit([this, id] { executeOnBufferRefilled(id); }); +void AudioPlayer::onBufferUnderrun(SourceId id, const MediaPlayerState& state) { + ACSDK_DEBUG(LX("onBufferUnderrun").d("id", id).d("state", state)); + m_executor.submit([this, id, state] { executeOnBufferUnderrun(id, state); }); } -void AudioPlayer::onTags(SourceId id, std::unique_ptr vectorOfTags) { - ACSDK_DEBUG(LX("onTags").d("id", id)); +void AudioPlayer::onBufferRefilled(SourceId id, const MediaPlayerState& state) { + ACSDK_DEBUG(LX("onBufferRefilled").d("id", id).d("state", state)); + m_executor.submit([this, id, state] { executeOnBufferRefilled(id, state); }); +} + +void AudioPlayer::onTags(SourceId id, std::unique_ptr vectorOfTags, const MediaPlayerState& state) { + ACSDK_DEBUG(LX("onTags").d("id", id).d("state", state)); if (nullptr == vectorOfTags || vectorOfTags->empty()) { ACSDK_ERROR(LX("onTagsFailed").d("reason", "noTags")); return; } std::shared_ptr sharedVectorOfTags(std::move(vectorOfTags)); - m_executor.submit([this, id, sharedVectorOfTags] { executeOnTags(id, sharedVectorOfTags); }); + m_executor.submit([this, id, sharedVectorOfTags, state] { executeOnTags(id, sharedVectorOfTags, state); }); } -void AudioPlayer::onBufferingComplete(SourceId id) { - ACSDK_DEBUG(LX("onBufferingComplete").d("id", id)); - m_executor.submit([this, id] { executeOnBufferingComplete(id); }); +void AudioPlayer::onBufferingComplete(SourceId id, const MediaPlayerState& state) { + ACSDK_DEBUG(LX("onBufferingComplete").d("id", id).d("state", state)); + m_executor.submit([this, id, state] { executeOnBufferingComplete(id, state); }); } void AudioPlayer::onReadyToProvideNextPlayer() { @@ -432,7 +449,7 @@ void AudioPlayer::requestProgress() { }); } -void AudioPlayer::addObserver(std::shared_ptr observer) { +void AudioPlayer::addObserver(std::shared_ptr observer) { ACSDK_DEBUG1(LX("addObserver")); if (!observer) { ACSDK_ERROR(LX("addObserver").m("Observer is null.")); @@ -445,7 +462,7 @@ void AudioPlayer::addObserver(std::shared_ptr observer) { +void AudioPlayer::removeObserver(std::shared_ptr observer) { ACSDK_DEBUG1(LX("removeObserver")); if (!observer) { ACSDK_ERROR(LX("removeObserver").m("Observer is null.")); @@ -458,8 +475,7 @@ void AudioPlayer::removeObserver(std::shared_ptr observer) { +void AudioPlayer::setObserver(std::shared_ptr observer) { ACSDK_DEBUG1(LX(__func__)); m_executor.submit([this, observer] { m_renderPlayerObserver = observer; }); } @@ -491,7 +507,9 @@ AudioPlayer::AudioPlayer( m_currentlyPlaying(std::make_shared("")), m_offset{std::chrono::milliseconds{std::chrono::milliseconds::zero()}}, m_isStopCalled{false}, - m_okToRequestNextTrack{false} { + m_okToRequestNextTrack{false}, + m_currentMixability{avsCommon::avs::ContentType::UNDEFINED}, + m_mixingBehavior{avsCommon::avs::MixingBehavior::UNDEFINED} { m_capabilityConfigurations.insert(getAudioPlayerCapabilityConfiguration()); } @@ -515,7 +533,7 @@ void AudioPlayer::doShutdown() { m_focusManager.reset(); m_contextManager->setStateProvider(STATE, nullptr); m_contextManager.reset(); - m_audioPlayQueue.clear(); + clearPlayQueue(true); m_playbackRouter.reset(); m_captionManager.reset(); } @@ -644,8 +662,8 @@ void AudioPlayer::preHandlePlayDirective(std::shared_ptr info) { } rapidjson::Value::ConstMemberIterator progressReport; - audioItem.stream.progressReport.delay = ProgressTimer::NO_DELAY; - audioItem.stream.progressReport.interval = ProgressTimer::NO_INTERVAL; + audioItem.stream.progressReport.delay = ProgressTimer::getNoDelay(); + audioItem.stream.progressReport.interval = ProgressTimer::getNoInterval(); if (!jsonUtils::findNode(stream->value, "progressReport", &progressReport)) { progressReport = stream->value.MemberEnd(); } else { @@ -812,13 +830,15 @@ void AudioPlayer::executeProvideState(bool sendToken, unsigned int stateRequestT } } -void AudioPlayer::executeOnFocusChanged(FocusState newFocus) { +void AudioPlayer::executeOnFocusChanged(FocusState newFocus, MixingBehavior behavior) { ACSDK_DEBUG1( LX("executeOnFocusChanged").d("from", m_focus).d("to", newFocus).d("m_currentActivity", m_currentActivity)); - if (m_focus == newFocus) { + + if ((m_focus == newFocus) && (m_mixingBehavior == behavior)) { return; } m_focus = newFocus; + m_mixingBehavior = behavior; switch (newFocus) { case FocusState::FOREGROUND: @@ -848,7 +868,8 @@ void AudioPlayer::executeOnFocusChanged(FocusState newFocus) { sendPlaybackFailedEvent( m_currentlyPlaying->audioItem.stream.token, ErrorType::MEDIA_ERROR_INTERNAL_DEVICE_ERROR, - "failed to resume media player"); + "failed to resume media player", + getMediaPlayerState()); ACSDK_ERROR(LX("executeOnFocusChangedFailed").d("reason", "resumeFailed")); m_focusManager->releaseChannel(CHANNEL_NAME, shared_from_this()); return; @@ -871,15 +892,15 @@ void AudioPlayer::executeOnFocusChanged(FocusState newFocus) { return; } - // We can also end up here with an empty queue if we've asked MediaPlayer to play, but playback - // hasn't started yet, so we fall through to call @c pause() here as well. + // We can also end up here with an empty queue if we've asked MediaPlayer to play, but playback + // hasn't started yet, so we fall through to call @c pause() here as well. case PlayerActivity::FINISHED: case PlayerActivity::IDLE: - // Note: can be in FINISHED or IDLE while waiting for MediaPlayer to start playing, so we fall - // through to call @c pause() here as well. + // Note: can be in FINISHED or IDLE while waiting for MediaPlayer to start playing, so we fall + // through to call @c pause() here as well. case PlayerActivity::PAUSED: - // Note: can be in PAUSED while we're trying to resume, in which case we still want to pause, so we - // fall through to call @c pause() here as well. + // Note: can be in PAUSED while we're trying to resume, in which case we still want to pause, so we + // fall through to call @c pause() here as well. case PlayerActivity::PLAYING: case PlayerActivity::BUFFER_UNDERRUN: { // If we get pushed into the background while playing or buffering, pause the current song. @@ -889,6 +910,11 @@ void AudioPlayer::executeOnFocusChanged(FocusState newFocus) { // to be reporting errors in those cases. // TODO: Consider expanding the states to track the transition to PLAYING so that we don't call // pause when we're genuinely IDLE/STOPPED/FINISHED (ACSDK-734). + if (MixingBehavior::MUST_PAUSE != behavior) { + ACSDK_WARN(LX(__func__).d("Unhandled MixingBehavior", behavior)); + } + + // Pause By Default Upon Receiving Background Focus if (m_currentlyPlaying->mediaPlayer) { m_currentlyPlaying->mediaPlayer->pause(m_currentlyPlaying->sourceId); } @@ -901,15 +927,15 @@ void AudioPlayer::executeOnFocusChanged(FocusState newFocus) { case PlayerActivity::IDLE: case PlayerActivity::STOPPED: case PlayerActivity::FINISHED: - // Nothing to more to do if we're already not playing; we got here because the act of stopping - // caused the channel to be released, which in turn caused this callback. + // Nothing to more to do if we're already not playing; we got here because the act of + // stopping caused the channel to be released, which in turn caused this callback. return; case PlayerActivity::PLAYING: case PlayerActivity::PAUSED: case PlayerActivity::BUFFER_UNDERRUN: // If the focus change came in while we were in a 'playing' state, we need to stop because we are // yielding the channel. - m_audioPlayQueue.clear(); + clearPlayQueue(false); ACSDK_DEBUG1(LX("executeOnFocusChanged").d("action", "executeStop")); executeStop(); return; @@ -919,13 +945,14 @@ void AudioPlayer::executeOnFocusChanged(FocusState newFocus) { ACSDK_WARN(LX("unexpectedExecuteOnFocusChanged").d("newFocus", newFocus).d("m_currentActivity", m_currentActivity)); } -void AudioPlayer::executeOnPlaybackStarted(SourceId id) { - ACSDK_DEBUG1(LX("executeOnPlaybackStarted").d("id", id)); +void AudioPlayer::executeOnPlaybackStarted(SourceId id, const MediaPlayerState& state) { + ACSDK_DEBUG1(LX("executeOnPlaybackStarted").d("id", id).d("state", state)); if (id != m_currentlyPlaying->sourceId) { ACSDK_ERROR(LX("executeOnPlaybackStartedFailed") .d("reason", "invalidSourceId") .d("id", id) + .d("state", state) .d("m_sourceId", m_currentlyPlaying->sourceId)); return; } @@ -941,7 +968,7 @@ void AudioPlayer::executeOnPlaybackStarted(SourceId id) { // Note: Send playback started event before changeActivity below to send the offset closer to when onPlaybackStarted // was actually emitted - sendPlaybackStartedEvent(); + sendPlaybackStartedEvent(state); /* * When @c AudioPlayer is the active player, @c PlaybackController which is @@ -953,14 +980,14 @@ void AudioPlayer::executeOnPlaybackStarted(SourceId id) { m_progressTimer.start(); if (m_mediaPlayerFactory->isMediaPlayerAvailable() && m_currentlyPlaying->isBuffered) { - sendPlaybackNearlyFinishedEvent(); + sendPlaybackNearlyFinishedEvent(state); } else { m_okToRequestNextTrack = true; } } -void AudioPlayer::executeOnBufferingComplete(SourceId id) { - ACSDK_DEBUG(LX("executeOnBufferingComplete").d("id", id)); +void AudioPlayer::executeOnBufferingComplete(SourceId id, const MediaPlayerState& state) { + ACSDK_DEBUG(LX("executeOnBufferingComplete").d("id", id).d("state", state)); if (id != m_currentlyPlaying->sourceId) { // See if SourceId identifies a loading track @@ -969,35 +996,37 @@ void AudioPlayer::executeOnBufferingComplete(SourceId id) { if (it->sourceId == id) { // Mark as buffered, so nearly finished can be send when started. it->isBuffered = true; - ACSDK_DEBUG2(LX(__func__).m("FullyBufferedBeforeStarted").d("id", id)); + ACSDK_DEBUG2(LX(__func__).m("FullyBufferedBeforeStarted").d("id", id).d("state", state)); return; } } } - ACSDK_ERROR(LX("executeOnBufferingComplete") - .d("reason", "invalidSourceId") + ACSDK_DEBUG(LX("executeOnBufferingComplete") + .d("reason", "sourceIdDoesNotMatchCurrentTrack") .d("id", id) + .d("state", state) .d("m_sourceId", m_currentlyPlaying->sourceId)); return; } if (m_mediaPlayerFactory->isMediaPlayerAvailable() && m_okToRequestNextTrack) { - sendPlaybackNearlyFinishedEvent(); + sendPlaybackNearlyFinishedEvent(state); m_okToRequestNextTrack = false; } else { m_currentlyPlaying->isBuffered = true; - ACSDK_DEBUG2(LX(__func__).m("FullyBufferedBeforeStarted").d("id", id)); + ACSDK_DEBUG2(LX(__func__).m("FullyBufferedBeforeStarted").d("id", id).d("state", state)); } } -void AudioPlayer::executeOnPlaybackStopped(SourceId id) { - ACSDK_DEBUG1(LX("executeOnPlaybackStopped").d("id", id)); +void AudioPlayer::executeOnPlaybackStopped(SourceId id, const MediaPlayerState& state) { + ACSDK_DEBUG1(LX("executeOnPlaybackStopped").d("id", id).d("state", state)); if (id != m_currentlyPlaying->sourceId) { - ACSDK_ERROR(LX("executeOnPlaybackStoppedFailed") - .d("reason", "invalidSourceId") + ACSDK_DEBUG(LX("executeOnPlaybackStopped") + .d("reason", "sourceIdDoesNotMatchCurrentTrack") .d("id", id) + .d("state", state) .d("m_sourceId", m_currentlyPlaying->sourceId)); return; } @@ -1008,7 +1037,7 @@ void AudioPlayer::executeOnPlaybackStopped(SourceId id) { case PlayerActivity::BUFFER_UNDERRUN: changeActivity(PlayerActivity::STOPPED); m_progressTimer.stop(); - sendPlaybackStoppedEvent(); + sendPlaybackStoppedEvent(state); m_okToRequestNextTrack = false; m_isStopCalled = false; if (!m_playNextItemAfterStopped || m_audioPlayQueue.empty()) { @@ -1038,13 +1067,14 @@ void AudioPlayer::executeOnPlaybackStopped(SourceId id) { .d("m_currentActivity", m_currentActivity)); } -void AudioPlayer::executeOnPlaybackFinished(SourceId id) { - ACSDK_DEBUG1(LX("executeOnPlaybackFinished").d("id", id)); +void AudioPlayer::executeOnPlaybackFinished(SourceId id, const MediaPlayerState& state) { + ACSDK_DEBUG1(LX("executeOnPlaybackFinished").d("id", id).d("state", state)); if (id != m_currentlyPlaying->sourceId) { ACSDK_ERROR(LX("executeOnPlaybackFinishedFailed") .d("reason", "invalidSourceId") .d("id", id) + .d("state", state) .d("m_sourceId", m_currentlyPlaying->sourceId)); return; } @@ -1059,11 +1089,11 @@ void AudioPlayer::executeOnPlaybackFinished(SourceId id) { releaseMediaPlayer(m_currentlyPlaying); } - m_executor.submit([this] { + m_executor.submit([this, state] { // Sending this in an executor in case the 'releaseMediaPlayer()' call above // resulted in a 'playbackNearlyFinished' event (which would be done on the executor, not // synchronously). If it did, this needs to be sent afterward. - sendPlaybackFinishedEvent(); + sendPlaybackFinishedEvent(state); m_okToRequestNextTrack = false; if (m_audioPlayQueue.empty()) { handlePlaybackCompleted(); @@ -1095,8 +1125,12 @@ void AudioPlayer::handlePlaybackCompleted() { } } -void AudioPlayer::executeOnPlaybackError(SourceId id, const ErrorType& type, std::string error) { - ACSDK_DEBUG1(LX(__func__).d("id", id).d("type", type).d("error", error)); +void AudioPlayer::executeOnPlaybackError( + SourceId id, + const ErrorType& type, + std::string error, + const MediaPlayerState& state) { + ACSDK_DEBUG1(LX(__func__).d("id", id).d("state", state).d("type", type).d("error", error)); if (id != m_currentlyPlaying->sourceId) { // See if SourceId identifies a loading track @@ -1105,14 +1139,17 @@ void AudioPlayer::executeOnPlaybackError(SourceId id, const ErrorType& type, std if (it->sourceId == id) { it->errorMsg = std::move(error); it->errorType = type; - ACSDK_WARN(LX(__func__).m("ErrorWhileBuffering").d("id", id)); + ACSDK_WARN(LX(__func__).m("ErrorWhileBuffering").d("id", id).d("state", state)); return; } } } - ACSDK_ERROR( - LX(__func__).d("reason", "invalidSourceId").d("id", id).d("m_sourceId", m_currentlyPlaying->sourceId)); + ACSDK_ERROR(LX(__func__) + .d("reason", "invalidSourceId") + .d("id", id) + .d("state", state) + .d("m_sourceId", m_currentlyPlaying->sourceId)); return; } @@ -1125,41 +1162,43 @@ void AudioPlayer::executeOnPlaybackError(SourceId id, const ErrorType& type, std auto offsetDelta = getOffset() - m_currentlyPlaying->initialOffset; if (offsetDelta < std::chrono::milliseconds(500) || (m_currentActivity != PlayerActivity::STOPPED && m_currentActivity != PlayerActivity::FINISHED)) { - sendPlaybackFailedEvent(m_currentlyPlaying->audioItem.stream.token, type, error); + sendPlaybackFailedEvent(m_currentlyPlaying->audioItem.stream.token, type, error, state); } } /* - * There's no need to call stop() here as the MediaPlayer has already stopped due to the playback error. Instead, - * call executeOnPlaybackStopped() so that the states in AudioPlayer are reset properly. + * There's no need to call stop() here as the MediaPlayer has already stopped due to the playback error. + * Instead, call executeOnPlaybackStopped() so that the states in AudioPlayer are reset properly. */ - executeOnPlaybackStopped(m_currentlyPlaying->sourceId); + executeOnPlaybackStopped(m_currentlyPlaying->sourceId, state); } -void AudioPlayer::executeOnPlaybackPaused(SourceId id) { - ACSDK_DEBUG1(LX("executeOnPlaybackPaused").d("id", id)); +void AudioPlayer::executeOnPlaybackPaused(SourceId id, const MediaPlayerState& state) { + ACSDK_DEBUG1(LX("executeOnPlaybackPaused").d("id", id).d("state", state)); if (id != m_currentlyPlaying->sourceId) { ACSDK_ERROR(LX("executeOnPlaybackPausedFailed") .d("reason", "invalidSourceId") .d("id", id) + .d("state", state) .d("m_sourceId", m_currentlyPlaying->sourceId)); return; } m_progressTimer.pause(); // TODO: AVS recommends sending this after a recognize event to reduce latency (ACSDK-371). - sendPlaybackPausedEvent(); + sendPlaybackPausedEvent(state); changeActivity(PlayerActivity::PAUSED); } -void AudioPlayer::executeOnPlaybackResumed(SourceId id) { - ACSDK_DEBUG1(LX("executeOnPlaybackResumed").d("id", id)); +void AudioPlayer::executeOnPlaybackResumed(SourceId id, const MediaPlayerState& state) { + ACSDK_DEBUG1(LX("executeOnPlaybackResumed").d("id", id).d("state", state)); if (id != m_currentlyPlaying->sourceId) { ACSDK_ERROR(LX("executeOnPlaybackResumedFailed") .d("reason", "invalidSourceId") .d("id", id) + .d("state", state) .d("m_sourceId", m_currentlyPlaying->sourceId)); return; } @@ -1169,18 +1208,19 @@ void AudioPlayer::executeOnPlaybackResumed(SourceId id) { return; } - sendPlaybackResumedEvent(); + sendPlaybackResumedEvent(state); m_progressTimer.resume(); changeActivity(PlayerActivity::PLAYING); } -void AudioPlayer::executeOnBufferUnderrun(SourceId id) { - ACSDK_DEBUG1(LX("executeOnBufferUnderrun").d("id", id)); +void AudioPlayer::executeOnBufferUnderrun(SourceId id, const MediaPlayerState& state) { + ACSDK_DEBUG1(LX("executeOnBufferUnderrun").d("id", id).d("state", state)); if (id != m_currentlyPlaying->sourceId) { ACSDK_ERROR(LX("executeOnBufferUnderrunFailed") .d("reason", "invalidSourceId") .d("id", id) + .d("state", state) .d("m_sourceId", m_currentlyPlaying->sourceId)); return; } @@ -1191,35 +1231,39 @@ void AudioPlayer::executeOnBufferUnderrun(SourceId id) { } m_bufferUnderrunTimestamp = std::chrono::steady_clock::now(); - sendPlaybackStutterStartedEvent(); + sendPlaybackStutterStartedEvent(state); m_progressTimer.pause(); changeActivity(PlayerActivity::BUFFER_UNDERRUN); } -void AudioPlayer::executeOnBufferRefilled(SourceId id) { - ACSDK_DEBUG1(LX("executeOnBufferRefilled").d("id", id)); +void AudioPlayer::executeOnBufferRefilled(SourceId id, const MediaPlayerState& state) { + ACSDK_DEBUG1(LX("executeOnBufferRefilled").d("id", id).d("state", state)); if (id != m_currentlyPlaying->sourceId) { ACSDK_ERROR(LX("executeOnBufferRefilledFailed") .d("reason", "invalidSourceId") .d("id", id) + .d("state", state) .d("m_sourceId", m_currentlyPlaying->sourceId)); return; } - sendPlaybackStutterFinishedEvent(); + sendPlaybackStutterFinishedEvent(state); m_progressTimer.resume(); changeActivity(PlayerActivity::PLAYING); } -void AudioPlayer::executeOnTags(SourceId id, std::shared_ptr vectorOfTags) { - ACSDK_DEBUG1(LX("executeOnTags").d("id", id)); +void AudioPlayer::executeOnTags( + SourceId id, + std::shared_ptr vectorOfTags, + const MediaPlayerState& state) { + ACSDK_DEBUG1(LX("executeOnTags").d("id", id).d("state", state)); if (id != m_currentlyPlaying->sourceId) { // See if SourceId identifies a loading track for (const auto& it : m_audioPlayQueue) { if (it->sourceId == id) { - sendStreamMetadataExtractedEvent(it->audioItem.stream.token, vectorOfTags); + sendStreamMetadataExtractedEvent(it->audioItem, vectorOfTags, state); return; } } @@ -1227,23 +1271,35 @@ void AudioPlayer::executeOnTags(SourceId id, std::shared_ptr ACSDK_ERROR(LX("executeOnTags") .d("reason", "invalidSourceId") .d("id", id) + .d("state", state) .d("m_sourceId", m_currentlyPlaying->sourceId)); return; } - sendStreamMetadataExtractedEvent(m_currentlyPlaying->audioItem.stream.token, vectorOfTags); + sendStreamMetadataExtractedEvent(m_currentlyPlaying->audioItem, vectorOfTags, state); } -void AudioPlayer::clearPlayQueue() { +void AudioPlayer::clearPlayQueue(const bool stopCurrentPlayer) { // release all MediaPlayers on the play queue for (auto& it : m_audioPlayQueue) { if (it->mediaPlayer) { - releaseMediaPlayer(it); + if (!stopCurrentPlayer && it->sourceId == m_currentlyPlaying->sourceId) { + releaseMediaPlayer(it); + continue; + } + stopAndReleaseMediaPlayer(it); } } m_audioPlayQueue.clear(); } +void AudioPlayer::stopAndReleaseMediaPlayer(std::shared_ptr playbackItem) { + if (playbackItem->mediaPlayer) { + playbackItem->mediaPlayer->stop(playbackItem->sourceId); + } + releaseMediaPlayer(playbackItem); +} + void AudioPlayer::releaseMediaPlayer(std::shared_ptr playbackItem) { if (playbackItem->mediaPlayer) { ACSDK_DEBUG5(LX(__func__).d("sourceId", playbackItem->sourceId)); @@ -1283,7 +1339,8 @@ bool AudioPlayer::configureMediaPlayer(std::shared_ptr& playb sendPlaybackFailedEvent( playbackItem->audioItem.stream.token, ErrorType::MEDIA_ERROR_INTERNAL_DEVICE_ERROR, - "failed to set attachment media source"); + "failed to set attachment media source", + getMediaPlayerState()); ACSDK_ERROR(LX("configureMediaPlayerFailed").d("reason", "setSourceFailed").d("type", "attachment")); return false; } @@ -1295,7 +1352,8 @@ bool AudioPlayer::configureMediaPlayer(std::shared_ptr& playb sendPlaybackFailedEvent( playbackItem->audioItem.stream.token, ErrorType::MEDIA_ERROR_INTERNAL_DEVICE_ERROR, - "failed to set URL media source"); + "failed to set URL media source", + getMediaPlayerState()); ACSDK_ERROR(LX("configureMediaPlayerFailed").d("reason", "setSourceFailed").d("type", "URL")); return false; } @@ -1317,7 +1375,7 @@ void AudioPlayer::executeOnReadyToProvideNextPlayer() { } if (m_audioPlayQueue.empty() && m_okToRequestNextTrack) { - sendPlaybackNearlyFinishedEvent(); + sendPlaybackNearlyFinishedEvent(getMediaPlayerState()); m_okToRequestNextTrack = false; } else { // If something is on the queue, and doesn't have a player, @@ -1409,7 +1467,7 @@ void AudioPlayer::executePrePlay(std::shared_ptr info) { // handled in executePlay() // FALL-THROUGH case PlayBehavior::REPLACE_ENQUEUED: - clearPlayQueue(); + clearPlayQueue(false); // FALL-THROUGH case PlayBehavior::ENQUEUE: m_audioPlayQueue.push_back(info); @@ -1420,14 +1478,12 @@ void AudioPlayer::executePrePlay(std::shared_ptr info) { void AudioPlayer::executePlay(const std::string& messageId) { ACSDK_DEBUG1(LX(__func__)); - auto playItem = m_audioPlayQueue.front(); - if (m_audioPlayQueue.empty()) { - ACSDK_ERROR( - LX("executePlayFailed").d("reason", "unhandledPlayBehavior").d("playBehavior", playItem->playBehavior)); + ACSDK_ERROR(LX("executePlayFailed").d("reason", "emptyPlayQueue")); return; } + auto playItem = m_audioPlayQueue.front(); if (playItem->playBehavior != PlayBehavior::ENQUEUE && playItem->messageId != messageId) { ACSDK_ERROR(LX("executePlayFailed").d("reason", "TrackNotHeadOfQueue")); return; @@ -1437,30 +1493,42 @@ void AudioPlayer::executePlay(const std::string& messageId) { executeStop(true); } + // Determine Mixability of the item we are about to play. + auto mixability = playItem->mixingBehavior == audio::MixingBehavior::BEHAVIOR_PAUSE + ? avsCommon::avs::ContentType::NONMIXABLE + : avsCommon::avs::ContentType::MIXABLE; + // Initiate playback if not already playing. switch (m_currentActivity) { case PlayerActivity::IDLE: case PlayerActivity::STOPPED: case PlayerActivity::FINISHED: if (FocusState::NONE == m_focus) { + auto activity = FocusManagerInterface::Activity::create( + NAMESPACE, shared_from_this(), std::chrono::milliseconds::zero(), mixability); // If we don't currently have focus, acquire it now; playback will start when focus changes to // FOREGROUND. - if (!m_focusManager->acquireChannel(CHANNEL_NAME, shared_from_this(), NAMESPACE)) { + if (m_focusManager->acquireChannel(CHANNEL_NAME, activity)) { + ACSDK_INFO(LX("executePlay").d("acquiring Channel", CHANNEL_NAME)); + m_currentMixability = mixability; + } else { ACSDK_ERROR(LX("executePlayFailed").d("reason", "CouldNotAcquireChannel")); m_progressTimer.stop(); sendPlaybackFailedEvent( playItem->audioItem.stream.token, ErrorType::MEDIA_ERROR_INTERNAL_DEVICE_ERROR, - std::string("Could not acquire ") + CHANNEL_NAME + " for " + NAMESPACE); + std::string("Could not acquire ") + CHANNEL_NAME + " for " + NAMESPACE, + getMediaPlayerState()); return; } } return; + case PlayerActivity::PLAYING: case PlayerActivity::PAUSED: case PlayerActivity::BUFFER_UNDERRUN: - // If we're already 'playing', the new song should have been enqueued above and there's nothing more to do - // here. + // If we're already 'playing', the new song should have been enqueued above and there's nothing more to + // do here. return; } ACSDK_ERROR(LX("executePlayFailed").d("reason", "unexpectedActivity").d("m_currentActivity", m_currentActivity)); @@ -1472,7 +1540,10 @@ void AudioPlayer::playNextItem() { m_progressTimer.stop(); if (m_audioPlayQueue.empty()) { sendPlaybackFailedEvent( - m_currentlyPlaying->audioItem.stream.token, ErrorType::MEDIA_ERROR_INTERNAL_DEVICE_ERROR, "queue is empty"); + m_currentlyPlaying->audioItem.stream.token, + ErrorType::MEDIA_ERROR_INTERNAL_DEVICE_ERROR, + "queue is empty", + getMediaPlayerState()); ACSDK_ERROR(LX("playNextItemFailed").d("reason", "emptyQueue")); executeStop(); return; @@ -1487,6 +1558,16 @@ void AudioPlayer::playNextItem() { m_currentlyPlaying->initialOffset = m_currentlyPlaying->audioItem.stream.offset; m_offset = m_currentlyPlaying->initialOffset; + // AudioPlayer must notify of its mixability as it is based on content. + auto mixability = m_currentlyPlaying->mixingBehavior == audio::MixingBehavior::BEHAVIOR_PAUSE + ? avsCommon::avs::ContentType::NONMIXABLE + : avsCommon::avs::ContentType::MIXABLE; + + if (m_currentMixability != mixability) { + m_currentMixability = mixability; + m_focusManager->modifyContentType(CHANNEL_NAME, NAMESPACE, mixability); + } + if (!m_currentlyPlaying->mediaPlayer) { if (m_mediaPlayerFactory->isMediaPlayerAvailable()) { if (!configureMediaPlayer(m_currentlyPlaying)) { @@ -1500,21 +1581,28 @@ void AudioPlayer::playNextItem() { sendPlaybackFailedEvent( m_currentlyPlaying->audioItem.stream.token, ErrorType::MEDIA_ERROR_INTERNAL_DEVICE_ERROR, - "player not configured"); + "player not configured", + getMediaPlayerState()); return; } if (!m_currentlyPlaying->errorMsg.empty()) { ACSDK_ERROR(LX("playNextItemFailed").m("reportingPrebufferedError")); executeOnPlaybackError( - m_currentlyPlaying->sourceId, m_currentlyPlaying->errorType, m_currentlyPlaying->errorMsg); + m_currentlyPlaying->sourceId, + m_currentlyPlaying->errorType, + m_currentlyPlaying->errorMsg, + getMediaPlayerState()); return; } ACSDK_DEBUG1(LX(__func__).d("playingSourceId", m_currentlyPlaying->sourceId)); if (!m_currentlyPlaying->mediaPlayer->play(m_currentlyPlaying->sourceId)) { executeOnPlaybackError( - m_currentlyPlaying->sourceId, ErrorType::MEDIA_ERROR_INTERNAL_DEVICE_ERROR, "playFailed"); + m_currentlyPlaying->sourceId, + ErrorType::MEDIA_ERROR_INTERNAL_DEVICE_ERROR, + "playFailed", + getMediaPlayerState()); return; } @@ -1565,21 +1653,20 @@ void AudioPlayer::executeClearQueue(ClearBehavior clearBehavior) { switch (clearBehavior) { case ClearBehavior::CLEAR_ALL: executeStop(); - // FALL-THROUGH + clearPlayQueue(false); + break; case ClearBehavior::CLEAR_ENQUEUED: - clearPlayQueue(); - sendPlaybackQueueClearedEvent(); - return; + clearPlayQueue(true); + break; } - ACSDK_ERROR(LX("executeClearQueueFailed").d("reason", "unexpectedClearBehavior").d("clearBehavior", clearBehavior)); + sendPlaybackQueueClearedEvent(); } void AudioPlayer::changeActivity(PlayerActivity activity) { ACSDK_DEBUG(LX("changeActivity").d("from", m_currentActivity).d("to", activity)); - { - std::lock_guard lock(m_currentActivityMutex); - m_currentActivity = activity; - } + std::unique_lock lock(m_currentActivityMutex); + m_currentActivity = activity; + lock.unlock(); m_currentActivityConditionVariable.notify_all(); executeProvideState(); notifyObserver(); @@ -1588,8 +1675,8 @@ void AudioPlayer::changeActivity(PlayerActivity activity) { void AudioPlayer::sendEventWithTokenAndOffset(const std::string& eventName, std::chrono::milliseconds offset) { rapidjson::Document payload(rapidjson::kObjectType); payload.AddMember(TOKEN_KEY, m_currentlyPlaying->audioItem.stream.token, payload.GetAllocator()); - // Note: offset is an optional parameter, which defaults to MEDIA_PLAYER_INVALID_OFFSET. Per documentation, this - // function will use the current MediaPlayer offset is a valid offset was not provided. + // Note: offset is an optional parameter, which defaults to MEDIA_PLAYER_INVALID_OFFSET. Per documentation, + // this function will use the current MediaPlayer offset is a valid offset was not provided. if (MEDIA_PLAYER_INVALID_OFFSET == offset) { offset = getOffset(); } @@ -1611,24 +1698,24 @@ void AudioPlayer::sendEventWithTokenAndOffset(const std::string& eventName, std: m_messageSender->sendMessage(request); } -void AudioPlayer::sendPlaybackStartedEvent() { +void AudioPlayer::sendPlaybackStartedEvent(const MediaPlayerState& state) { sendEventWithTokenAndOffset("PlaybackStarted", m_currentlyPlaying->initialOffset); } -void AudioPlayer::sendPlaybackNearlyFinishedEvent() { - sendEventWithTokenAndOffset("PlaybackNearlyFinished"); +void AudioPlayer::sendPlaybackNearlyFinishedEvent(const MediaPlayerState& state) { + sendEventWithTokenAndOffset("PlaybackNearlyFinished", state.offset); } -void AudioPlayer::sendPlaybackStutterStartedEvent() { - sendEventWithTokenAndOffset("PlaybackStutterStarted"); +void AudioPlayer::sendPlaybackStutterStartedEvent(const MediaPlayerState& state) { + sendEventWithTokenAndOffset("PlaybackStutterStarted", state.offset); } -void AudioPlayer::sendPlaybackStutterFinishedEvent() { +void AudioPlayer::sendPlaybackStutterFinishedEvent(const MediaPlayerState& state) { rapidjson::Document payload(rapidjson::kObjectType); payload.AddMember(TOKEN_KEY, m_currentlyPlaying->audioItem.stream.token, payload.GetAllocator()); payload.AddMember( OFFSET_KEY, - (int64_t)std::chrono::duration_cast(getOffset()).count(), + (int64_t)std::chrono::duration_cast(state.offset).count(), payload.GetAllocator()); auto stutterDuration = std::chrono::steady_clock::now() - m_bufferUnderrunTimestamp; payload.AddMember( @@ -1648,14 +1735,15 @@ void AudioPlayer::sendPlaybackStutterFinishedEvent() { m_messageSender->sendMessage(request); } -void AudioPlayer::sendPlaybackFinishedEvent() { - sendEventWithTokenAndOffset("PlaybackFinished"); +void AudioPlayer::sendPlaybackFinishedEvent(const MediaPlayerState& state) { + sendEventWithTokenAndOffset("PlaybackFinished", state.offset); } void AudioPlayer::sendPlaybackFailedEvent( const std::string& failingToken, ErrorType errorType, - const std::string& message) { + const std::string& message, + const MediaPlayerState& state) { rapidjson::Document payload(rapidjson::kObjectType); payload.AddMember(TOKEN_KEY, failingToken, payload.GetAllocator()); @@ -1663,7 +1751,7 @@ void AudioPlayer::sendPlaybackFailedEvent( currentPlaybackState.AddMember(TOKEN_KEY, m_currentlyPlaying->audioItem.stream.token, payload.GetAllocator()); currentPlaybackState.AddMember( OFFSET_KEY, - (int64_t)std::chrono::duration_cast(getOffset()).count(), + (int64_t)std::chrono::duration_cast(state.offset).count(), payload.GetAllocator()); currentPlaybackState.AddMember(ACTIVITY_KEY, playerActivityToString(m_currentActivity), payload.GetAllocator()); @@ -1687,16 +1775,16 @@ void AudioPlayer::sendPlaybackFailedEvent( m_messageSender->sendMessage(request); } -void AudioPlayer::sendPlaybackStoppedEvent() { - sendEventWithTokenAndOffset("PlaybackStopped"); +void AudioPlayer::sendPlaybackStoppedEvent(const MediaPlayerState& state) { + sendEventWithTokenAndOffset("PlaybackStopped", state.offset); } -void AudioPlayer::sendPlaybackPausedEvent() { - sendEventWithTokenAndOffset("PlaybackPaused"); +void AudioPlayer::sendPlaybackPausedEvent(const MediaPlayerState& state) { + sendEventWithTokenAndOffset("PlaybackPaused", state.offset); } -void AudioPlayer::sendPlaybackResumedEvent() { - sendEventWithTokenAndOffset("PlaybackResumed"); +void AudioPlayer::sendPlaybackResumedEvent(const MediaPlayerState& state) { + sendEventWithTokenAndOffset("PlaybackResumed", state.offset); } void AudioPlayer::sendPlaybackQueueClearedEvent() { @@ -1706,25 +1794,74 @@ void AudioPlayer::sendPlaybackQueueClearedEvent() { } void AudioPlayer::sendStreamMetadataExtractedEvent( - const std::string& token, - std::shared_ptr vectorOfTags) { + AudioItem& audioItem, + std::shared_ptr vectorOfTags, + const MediaPlayerState&) { + const std::string& token = audioItem.stream.token; rapidjson::Document payload(rapidjson::kObjectType); payload.AddMember(TOKEN_KEY, token, payload.GetAllocator()); rapidjson::Value metadata(rapidjson::kObjectType); + bool passFilter = false; + // Make a copy of the duplicate cache + VectorOfTags newCache(audioItem.cachedMetadata.begin(), audioItem.cachedMetadata.end()); for (auto& tag : *vectorOfTags) { - rapidjson::Value tagKey(tag.key.c_str(), payload.GetAllocator()); - if (TagType::BOOLEAN == tag.type) { - if (!tag.value.compare("true")) { - metadata.AddMember(tagKey, true, payload.GetAllocator()); - } else { - metadata.AddMember(tagKey, false, payload.GetAllocator()); + // Filter metadata against whitelist + std::string lowerTag = tag.key; + std::transform(lowerTag.begin(), lowerTag.end(), lowerTag.begin(), ::tolower); + if (std::find(std::begin(METADATA_WHITELIST), std::end(METADATA_WHITELIST), lowerTag) != + std::end(METADATA_WHITELIST) && + !tag.value.empty()) { + // Check for duplicates + bool dupFound = false; + for (auto iter = newCache.begin(); iter != newCache.end(); ++iter) { + if (iter->key == tag.key) { + if (iter->value == tag.value && iter->type == tag.type) { + // duplicate, don't send + dupFound = true; + } else { + newCache.erase(iter); + } + break; + } + } + if (dupFound) { + continue; + } + passFilter = true; + newCache.push_back(tag); + rapidjson::Value tagKey(tag.key.c_str(), payload.GetAllocator()); + if (TagType::BOOLEAN == tag.type) { + std::string value = tag.value; + std::transform(value.begin(), value.end(), value.begin(), ::tolower); + if (value == "true") { + metadata.AddMember(tagKey, true, payload.GetAllocator()); + } else { + metadata.AddMember(tagKey, false, payload.GetAllocator()); + } + } else { + rapidjson::Value tagValue(tag.value.c_str(), payload.GetAllocator()); + metadata.AddMember(tagKey, tagValue, payload.GetAllocator()); } - } else { - rapidjson::Value tagValue(tag.value.c_str(), payload.GetAllocator()); - metadata.AddMember(tagKey, tagValue, payload.GetAllocator()); } } + + if (!passFilter) { + ACSDK_DEBUG(LX("sendStreamMetadataExtractedEvent").d("eventNotSent", "noWhitelistedData")); + return; + } + + std::chrono::time_point now = std::chrono::steady_clock::now(); + if (audioItem.lastMetadataEvent.time_since_epoch().count() == 0 || + now - audioItem.lastMetadataEvent > METADATA_EVENT_RATE) { + audioItem.lastMetadataEvent = now; + // Only keep the updated cache if we actually send the event. + audioItem.cachedMetadata = newCache; + } else { + ACSDK_DEBUG(LX("sendStreamMetadataExtractedEvent").d("eventNotSent", "tooFrequent")); + return; + } + payload.AddMember("metadata", metadata, payload.GetAllocator()); rapidjson::StringBuffer buffer; @@ -1740,7 +1877,7 @@ void AudioPlayer::sendStreamMetadataExtractedEvent( } void AudioPlayer::notifyObserver() { - avsCommon::sdkInterfaces::AudioPlayerObserverInterface::Context context; + AudioPlayerObserverInterface::Context context; context.audioItemId = m_currentlyPlaying->audioItem.id; context.offset = getOffset(); context.playRequestor = m_currentlyPlaying->playRequestor; @@ -1752,7 +1889,7 @@ void AudioPlayer::notifyObserver() { } if (m_renderPlayerObserver) { - avsCommon::sdkInterfaces::RenderPlayerInfoCardsObserverInterface::Context renderPlayerInfoContext; + RenderPlayerInfoCardsObserverInterface::Context renderPlayerInfoContext; renderPlayerInfoContext.audioItemId = m_currentlyPlaying->audioItem.id; renderPlayerInfoContext.offset = getOffset(); renderPlayerInfoContext.mediaProperties = shared_from_this(); @@ -1771,6 +1908,11 @@ std::chrono::milliseconds AudioPlayer::getOffset() { return m_offset; } +MediaPlayerState AudioPlayer::getMediaPlayerState() { + MediaPlayerState state = MediaPlayerState(getOffset()); + return state; +} + std::unordered_set> AudioPlayer:: getCapabilityConfigurations() { return m_capabilityConfigurations; diff --git a/CapabilityAgents/AudioPlayer/src/ProgressTimer.cpp b/CapabilityAgents/AudioPlayer/src/ProgressTimer.cpp index 043b6594..d6cb4dde 100644 --- a/CapabilityAgents/AudioPlayer/src/ProgressTimer.cpp +++ b/CapabilityAgents/AudioPlayer/src/ProgressTimer.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018-2020 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. @@ -26,9 +26,6 @@ namespace audioPlayer { using namespace avsCommon::utils::timing; -const std::chrono::milliseconds ProgressTimer::NO_DELAY{std::chrono::milliseconds::max()}; -const std::chrono::milliseconds ProgressTimer::NO_INTERVAL{std::chrono::milliseconds::max()}; - /// String to identify log entries originating from this file. static const std::string TAG("ProgressTimer"); @@ -41,8 +38,8 @@ static const std::string TAG("ProgressTimer"); ProgressTimer::ProgressTimer() : m_state{State::IDLE}, - m_delay{NO_DELAY}, - m_interval{NO_INTERVAL}, + m_delay{ProgressTimer::getNoDelay()}, + m_interval{ProgressTimer::getNoInterval()}, m_target{std::chrono::milliseconds::zero()}, m_gotProgress{false}, m_progress{std::chrono::milliseconds::zero()} { @@ -101,7 +98,7 @@ void ProgressTimer::init( m_context = context; m_interval = interval; - m_delay = delay >= offset ? delay : NO_DELAY; + m_delay = delay >= offset ? delay : ProgressTimer::getNoDelay(); m_offset = offset; m_progress = offset; } @@ -116,14 +113,14 @@ void ProgressTimer::start() { return; } - if (m_delay != NO_DELAY) { - if (m_interval != NO_INTERVAL) { + if (m_delay != ProgressTimer::getNoDelay()) { + if (m_interval != ProgressTimer::getNoInterval()) { m_target = std::min(m_delay, m_interval * ((m_offset / m_interval) + 1)); } else { m_target = m_delay; } } else { - if (m_interval != NO_INTERVAL) { + if (m_interval != ProgressTimer::getNoInterval()) { m_target = m_interval * ((m_offset / m_interval) + 1); } else { ACSDK_DEBUG5(LX("startNotStartingThread").d("reason", "noTarget")); @@ -192,8 +189,8 @@ void ProgressTimer::stop() { } m_context.reset(); - m_delay = NO_DELAY; - m_interval = NO_INTERVAL; + m_delay = ProgressTimer::getNoDelay(); + m_interval = ProgressTimer::getNoInterval(); m_target = std::chrono::milliseconds::zero(); } @@ -251,7 +248,7 @@ void ProgressTimer::mainLoop() { std::unique_lock stateLock(m_stateMutex); - if (NO_DELAY == m_delay && NO_INTERVAL == m_interval) { + if (ProgressTimer::getNoDelay() == m_delay && ProgressTimer::getNoInterval() == m_interval) { ACSDK_DEBUG5(LX("mainLoopExiting").d("reason", "noDelayOrInterval")); return; } @@ -272,7 +269,7 @@ void ProgressTimer::mainLoop() { if (m_target == m_delay) { m_context->onProgressReportDelayElapsed(); // If delay and interval coincide, send both notifications. - if (m_interval != NO_INTERVAL && (m_target.count() % m_interval.count()) == 0) { + if (m_interval != ProgressTimer::getNoInterval() && (m_target.count() % m_interval.count()) == 0) { m_context->onProgressReportIntervalElapsed(); } } else { @@ -300,7 +297,7 @@ bool ProgressTimer::updateTargetLocked() { // progress reports to send. The rules for interpreting the delay and interval values are // explained in progressReportDelayElapsed event and progressReportIntervalElapsed event // sections of the AudioPlayer interface documentation: - // https://developer.amazon.com/docs/alexa-voice-service/audioplayer.html + // https://developer.amazon.com/docs/alexa/alexa-voice-service/audioplayer.html // Haven't reached the target yet, so no need to update it. if (m_progress < m_target) { @@ -308,9 +305,9 @@ bool ProgressTimer::updateTargetLocked() { } // No reporting after an initial delay. - if (NO_DELAY == m_delay) { + if (ProgressTimer::getNoDelay() == m_delay) { // If no periodic reports, either, there will be no progress reports, and so, no target. - if (NO_INTERVAL == m_interval) { + if (ProgressTimer::getNoInterval() == m_interval) { ACSDK_DEBUG9(LX("noTarget")); return false; } @@ -322,12 +319,12 @@ bool ProgressTimer::updateTargetLocked() { } // Handle reporting progress after an initial delay, and without reporting periodic progress. - if (NO_INTERVAL == m_interval) { + if (ProgressTimer::getNoInterval() == m_interval) { // If progress has already reached the initial delay and there is no interval, there is // no more progress to report and mainLoop() will exit. Reset m_delay before returning // so that a pesky call to resume() won't trigger more progress reports. if (m_target == m_delay) { - m_delay = NO_DELAY; + m_delay = ProgressTimer::getNoDelay(); return false; } diff --git a/CapabilityAgents/AudioPlayer/test/AudioPlayerTest.cpp b/CapabilityAgents/AudioPlayer/test/AudioPlayerTest.cpp index 4a2d1f4b..99c3ce8a 100644 --- a/CapabilityAgents/AudioPlayer/test/AudioPlayerTest.cpp +++ b/CapabilityAgents/AudioPlayer/test/AudioPlayerTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -17,8 +17,8 @@ #include #include -#include #include +#include #include #include @@ -30,10 +30,10 @@ #include #include -#include #include -#include #include +#include +#include #include #include #include @@ -52,6 +52,7 @@ namespace capabilityAgents { namespace audioPlayer { namespace test { +using namespace ::testing; using namespace avsCommon::utils::json; using namespace avsCommon::utils; using namespace avsCommon; @@ -63,11 +64,14 @@ using namespace avsCommon::utils::mediaPlayer; using namespace avsCommon::utils::memory; using namespace captions::test; using namespace avsCommon::utils::mediaPlayer::test; -using namespace ::testing; using namespace rapidjson; +using MediaPlayerState = avsCommon::utils::mediaPlayer::MediaPlayerState; /// Plenty of time for a test to complete. -static std::chrono::milliseconds WAIT_TIMEOUT(1000); +static std::chrono::milliseconds MY_WAIT_TIMEOUT(1000); + +/// Default media player state for reporting all playback offsets +static const MediaPlayerState DEFAULT_MEDIA_PLAYER_STATE = {std::chrono::milliseconds(0)}; // Delay to let events happen / threads catch up static std::chrono::milliseconds EVENT_PROCESS_DELAY(20); @@ -178,6 +182,9 @@ static const long OFFSET_IN_MILLISECONDS_AFTER_PROGRESS_REPORT_INTERVAL{PROGRESS static const std::chrono::milliseconds TIME_FOR_TWO_AND_A_HALF_INTERVAL_PERIODS{ std::chrono::milliseconds((2 * PROGRESS_REPORT_INTERVAL) + (PROGRESS_REPORT_INTERVAL / 2))}; +/// The time to wait before sending 'onTags()' after the last send. +static const long METADATA_EVENT_DELAY{1001}; + static const std::string CAPTION_CONTENT_SAMPLE = "WEBVTT\\n" "\\n" @@ -328,9 +335,15 @@ static const std::string MESSAGE_METADATA_KEY = "metadata"; /// JSON key for "string" type field in metadata section of StreamMetadataExtracted event. static const std::string MESSAGE_METADATA_STRING_KEY = "StringKey"; +/// JSON key for "string" type field in metadata section of StreamMetadataExtracted event. On whitelist +static const std::string MESSAGE_METADATA_STRING_KEY_WL = "Title"; + /// JSON value for "string" type field in metadata section of StreamMetadataExtracted event. static const std::string MESSAGE_METADATA_STRING_VALUE = "StringValue"; +/// JSON value for alternate "string" type field in metadata section of StreamMetadataExtracted event. +static const std::string MESSAGE_METADATA_STRING_VALUE_ALT = "StringValue2"; + /// JSON key for "uint" type field in metadata section of StreamMetadataExtracted event. static const std::string MESSAGE_METADATA_UINT_KEY = "UintKey"; @@ -607,12 +620,13 @@ public: */ void verifyTags( std::shared_ptr request, - std::map* expectedMessages); + std::map* expectedMessages, + bool validateBoolean = true); /** * Run through test of playing, enqueuing, finish, play */ - void testPlayEnqueFinishPlay(); + void testPlayEnqueueFinishPlay(); /// General purpose mutex. std::mutex m_mutex; @@ -739,22 +753,22 @@ void AudioPlayerTest::sendPlayDirective(long offsetInMilliseconds) { std::shared_ptr playDirective = AVSDirective::create( "", avsMessageHeader, createEnqueuePayloadTest(offsetInMilliseconds), m_attachmentManager, CONTEXT_ID_TEST); - EXPECT_CALL(*(m_mockFocusManager.get()), acquireChannel(CHANNEL_NAME, _, NAMESPACE_AUDIO_PLAYER)) + EXPECT_CALL(*(m_mockFocusManager.get()), acquireChannel(CHANNEL_NAME, _)) .Times(1) .WillOnce(InvokeWithoutArgs(this, &AudioPlayerTest::wakeOnAcquireChannel)); EXPECT_CALL(*m_mockDirectiveHandlerResult, setCompleted()); m_audioPlayer->CapabilityAgent::preHandleDirective(playDirective, std::move(m_mockDirectiveHandlerResult)); - m_mockMediaPlayer->waitUntilNextSetSource(WAIT_TIMEOUT); - m_audioPlayer->onBufferingComplete(m_mockMediaPlayer->getLatestSourceId()); + m_mockMediaPlayer->waitUntilNextSetSource(MY_WAIT_TIMEOUT); + m_audioPlayer->onBufferingComplete(m_mockMediaPlayer->getLatestSourceId(), DEFAULT_MEDIA_PLAYER_STATE); m_audioPlayer->CapabilityAgent::handleDirective(MESSAGE_ID_TEST); - ASSERT_EQ(std::future_status::ready, m_wakeAcquireChannelFuture.wait_for(WAIT_TIMEOUT)); + ASSERT_EQ(std::future_status::ready, m_wakeAcquireChannelFuture.wait_for(MY_WAIT_TIMEOUT)); - m_audioPlayer->onFocusChanged(FocusState::FOREGROUND); + m_audioPlayer->onFocusChanged(FocusState::FOREGROUND, avs::MixingBehavior::PRIMARY); - ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PLAYING, WAIT_TIMEOUT)); + ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PLAYING, MY_WAIT_TIMEOUT)); } void AudioPlayerTest::sendStopDirective() { @@ -833,7 +847,8 @@ void AudioPlayerTest::verifyState(const std::string& providedState, const std::s void AudioPlayerTest::verifyTags( std::shared_ptr request, - std::map* expectedMessages) { + std::map* expectedMessages, + bool validateBoolean) { rapidjson::Document document; document.Parse(request->getJsonContent().c_str()); EXPECT_FALSE(document.HasParseError()) @@ -857,7 +872,9 @@ void AudioPlayerTest::verifyTags( EXPECT_NE(payload, event->value.MemberEnd()); auto metadata = payload->value.FindMember(MESSAGE_METADATA_KEY); - EXPECT_NE(metadata, payload->value.MemberEnd()); + if (metadata == payload->value.MemberEnd()) { + return; + } std::string metadata_string_value; jsonUtils::retrieveValue(metadata->value, MESSAGE_METADATA_STRING_KEY, &metadata_string_value); @@ -866,6 +883,13 @@ void AudioPlayerTest::verifyTags( expectedMessages->at(metadata_string_value) = expectedMessages->at(metadata_string_value) + 1; } + metadata_string_value = ""; + jsonUtils::retrieveValue(metadata->value, MESSAGE_METADATA_STRING_KEY_WL, &metadata_string_value); + + if (expectedMessages->find(metadata_string_value) != expectedMessages->end()) { + expectedMessages->at(metadata_string_value) = expectedMessages->at(metadata_string_value) + 1; + } + std::string metadata_uint_value; jsonUtils::retrieveValue(metadata->value, MESSAGE_METADATA_UINT_KEY, &metadata_uint_value); @@ -887,9 +911,11 @@ void AudioPlayerTest::verifyTags( expectedMessages->at(metadata_double_value) = expectedMessages->at(metadata_double_value) + 1; } - bool metadata_boolean_value = false; - jsonUtils::retrieveValue(metadata->value, MESSAGE_METADATA_BOOLEAN_KEY, &metadata_boolean_value); - ASSERT_TRUE(metadata_boolean_value); + if (validateBoolean) { + bool metadata_boolean_value = false; + jsonUtils::retrieveValue(metadata->value, MESSAGE_METADATA_BOOLEAN_KEY, &metadata_boolean_value); + ASSERT_TRUE(metadata_boolean_value); + } } /** @@ -987,7 +1013,7 @@ TEST_F(AudioPlayerTest, test_transitionFromPlayingToStopped) { // now send Stop directive sendStopDirective(); - ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::STOPPED, WAIT_TIMEOUT)); + ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::STOPPED, MY_WAIT_TIMEOUT)); } /** @@ -1001,7 +1027,7 @@ TEST_F(AudioPlayerTest, test_transitionFromPlayingToStoppedWithClear) { sendClearQueueDirective(); - ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::STOPPED, WAIT_TIMEOUT)); + ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::STOPPED, MY_WAIT_TIMEOUT)); } /** @@ -1013,15 +1039,13 @@ TEST_F(AudioPlayerTest, test_transitionFromStoppedToPlaying) { EXPECT_CALL(*(m_mockMediaPlayer.get()), stop(_)).Times(AtLeast(1)); sendClearQueueDirective(); - ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::STOPPED, WAIT_TIMEOUT)); - m_audioPlayer->onFocusChanged(FocusState::NONE); + ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::STOPPED, MY_WAIT_TIMEOUT)); + m_audioPlayer->onFocusChanged(FocusState::NONE, MixingBehavior::MUST_STOP); // Verify previous media player unused EXPECT_CALL(*(m_mockMediaPlayer.get()), play(_)).Times(0); EXPECT_CALL(*(m_mockMediaPlayerTrack2.get()), play(_)).Times(AtLeast(1)); - EXPECT_CALL(*(m_mockFocusManager.get()), acquireChannel(CHANNEL_NAME, _, NAMESPACE_AUDIO_PLAYER)) - .Times(1) - .WillOnce(Return(true)); + EXPECT_CALL(*(m_mockFocusManager.get()), acquireChannel(CHANNEL_NAME, _)).Times(1).WillOnce(Return(true)); auto avsMessageHeader = std::make_shared(NAMESPACE_AUDIO_PLAYER, NAME_PLAY, MESSAGE_ID_TEST_2); std::shared_ptr playDirective = AVSDirective::create( "", @@ -1031,8 +1055,8 @@ TEST_F(AudioPlayerTest, test_transitionFromStoppedToPlaying) { CONTEXT_ID_TEST_2); m_audioPlayer->CapabilityAgent::preHandleDirective(playDirective, std::move(m_mockDirectiveHandlerResult)); m_audioPlayer->CapabilityAgent::handleDirective(MESSAGE_ID_TEST_2); - m_audioPlayer->onFocusChanged(FocusState::FOREGROUND); - ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PLAYING, WAIT_TIMEOUT)); + m_audioPlayer->onFocusChanged(FocusState::FOREGROUND, MixingBehavior::PRIMARY); + ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PLAYING, MY_WAIT_TIMEOUT)); } /** @@ -1047,17 +1071,15 @@ TEST_F(AudioPlayerTest, testTransitionFromStoppedToResumePlaying) { EXPECT_CALL(*(m_mockMediaPlayer.get()), stop(_)).Times(AtLeast(1)); sendClearQueueDirective(); - ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::STOPPED, WAIT_TIMEOUT)); - m_audioPlayer->onFocusChanged(FocusState::NONE); + ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::STOPPED, MY_WAIT_TIMEOUT)); + m_audioPlayer->onFocusChanged(FocusState::NONE, MixingBehavior::MUST_STOP); // re-init attachemnt manager - resuming is not normally done with attachments... m_attachmentManager = std::make_shared(AttachmentManager::AttachmentType::IN_PROCESS); m_mockMediaPlayer->resetWaitTimer(); EXPECT_CALL(*(m_mockMediaPlayerTrack2.get()), play(_)).Times(1); - EXPECT_CALL(*(m_mockFocusManager.get()), acquireChannel(CHANNEL_NAME, _, NAMESPACE_AUDIO_PLAYER)) - .Times(1) - .WillOnce(Return(true)); + EXPECT_CALL(*(m_mockFocusManager.get()), acquireChannel(CHANNEL_NAME, _)).Times(1).WillOnce(Return(true)); // Enqueue next track auto avsMessageHeader = std::make_shared(NAMESPACE_AUDIO_PLAYER, NAME_PLAY, MESSAGE_ID_TEST_2); @@ -1070,8 +1092,8 @@ TEST_F(AudioPlayerTest, testTransitionFromStoppedToResumePlaying) { CONTEXT_ID_TEST); m_audioPlayer->CapabilityAgent::preHandleDirective(playDirective, std::move(m_mockDirectiveHandlerResult)); m_audioPlayer->CapabilityAgent::handleDirective(MESSAGE_ID_TEST_2); - m_audioPlayer->onFocusChanged(FocusState::FOREGROUND); - ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PLAYING, WAIT_TIMEOUT)); + m_audioPlayer->onFocusChanged(FocusState::FOREGROUND, MixingBehavior::PRIMARY); + ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PLAYING, MY_WAIT_TIMEOUT)); } /** @@ -1081,7 +1103,7 @@ TEST_F(AudioPlayerTest, testTransitionFromStoppedToResumePlaying) { TEST_F(AudioPlayerTest, testTransitionFromPlayingToPlayingNextEnqueuedTrack) { // send a play directive sendPlayDirective(); - ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PLAYING, WAIT_TIMEOUT)); + ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PLAYING, MY_WAIT_TIMEOUT)); std::this_thread::sleep_for(std::chrono::milliseconds(EVENT_PROCESS_DELAY)); @@ -1111,8 +1133,8 @@ TEST_F(AudioPlayerTest, testTransitionFromPlayingToPlayingNextEnqueuedTrack) { m_audioPlayer->CapabilityAgent::preHandleDirective(playDirective, std::move(m_mockDirectiveHandlerResult)); m_audioPlayer->CapabilityAgent::handleDirective(MESSAGE_ID_TEST_3); - ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::STOPPED, WAIT_TIMEOUT)); - ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PLAYING, WAIT_TIMEOUT)); + ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::STOPPED, MY_WAIT_TIMEOUT)); + ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PLAYING, MY_WAIT_TIMEOUT)); } /** @@ -1125,9 +1147,8 @@ TEST_F(AudioPlayerTest, test_transitionFromPlayingToPaused) { EXPECT_CALL(*(m_mockMediaPlayer.get()), pause(_)).Times(AtLeast(1)); // simulate focus change - m_audioPlayer->onFocusChanged(FocusState::BACKGROUND); - - ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PAUSED, WAIT_TIMEOUT)); + m_audioPlayer->onFocusChanged(FocusState::BACKGROUND, MixingBehavior::MUST_PAUSE); + ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PAUSED, MY_WAIT_TIMEOUT)); } /** @@ -1139,13 +1160,12 @@ TEST_F(AudioPlayerTest, test_transitionFromPausedToStopped) { EXPECT_CALL(*(m_mockMediaPlayer.get()), stop(_)).Times(AtLeast(1)); // simulate focus change in order to pause - m_audioPlayer->onFocusChanged(FocusState::BACKGROUND); - - ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PAUSED, WAIT_TIMEOUT)); + m_audioPlayer->onFocusChanged(FocusState::BACKGROUND, MixingBehavior::MUST_PAUSE); + ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PAUSED, MY_WAIT_TIMEOUT)); + // clear queue directive must stop music sendClearQueueDirective(); - - ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::STOPPED, WAIT_TIMEOUT)); + ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::STOPPED, MY_WAIT_TIMEOUT)); } /** @@ -1158,15 +1178,13 @@ TEST_F(AudioPlayerTest, test_resumeAfterPaused) { EXPECT_CALL(*(m_mockMediaPlayer.get()), stop(_)).Times(AtLeast(1)); // simulate focus change in order to pause - m_audioPlayer->onFocusChanged(FocusState::BACKGROUND); - - ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PAUSED, WAIT_TIMEOUT)); + m_audioPlayer->onFocusChanged(FocusState::BACKGROUND, MixingBehavior::MUST_PAUSE); + ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PAUSED, MY_WAIT_TIMEOUT)); + // verify mediaplayer resumes on foreground EXPECT_CALL(*(m_mockMediaPlayer.get()), resume(_)).Times(AtLeast(1)); - - m_audioPlayer->onFocusChanged(FocusState::FOREGROUND); - - ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PLAYING, WAIT_TIMEOUT)); + m_audioPlayer->onFocusChanged(FocusState::FOREGROUND, MixingBehavior::PRIMARY); + ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PLAYING, MY_WAIT_TIMEOUT)); } /** @@ -1188,7 +1206,7 @@ TEST_F(AudioPlayerTest, test_callingProvideStateWhenIdle) { InvokeWithoutArgs(this, &AudioPlayerTest::wakeOnSetState))); m_audioPlayer->provideState(NAMESPACE_AND_NAME_PLAYBACK_STATE, PROVIDE_STATE_TOKEN_TEST); - ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(WAIT_TIMEOUT)); + ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(MY_WAIT_TIMEOUT)); } /** @@ -1211,13 +1229,16 @@ TEST_F(AudioPlayerTest, test_onPlaybackError) { sendPlayDirective(); m_audioPlayer->onPlaybackError( - m_mockMediaPlayer->getCurrentSourceId(), ErrorType::MEDIA_ERROR_UNKNOWN, "TEST_ERROR"); + m_mockMediaPlayer->getCurrentSourceId(), + ErrorType::MEDIA_ERROR_UNKNOWN, + "TEST_ERROR", + DEFAULT_MEDIA_PLAYER_STATE); std::unique_lock lock(m_mutex); bool result; - result = m_messageSentTrigger.wait_for(lock, WAIT_TIMEOUT, [this] { + result = m_messageSentTrigger.wait_for(lock, MY_WAIT_TIMEOUT, [this] { for (auto messageStatus : m_expectedMessages) { if (messageStatus.second == 0) { return false; @@ -1225,7 +1246,7 @@ TEST_F(AudioPlayerTest, test_onPlaybackError) { } return true; }); - ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::STOPPED, WAIT_TIMEOUT)); + ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::STOPPED, MY_WAIT_TIMEOUT)); ASSERT_TRUE(result); } @@ -1248,19 +1269,22 @@ TEST_F(AudioPlayerTest, test_onPlaybackError_Stopped) { })); sendPlayDirective(); - ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PLAYING, WAIT_TIMEOUT)); + ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PLAYING, MY_WAIT_TIMEOUT)); sendStopDirective(); - ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::STOPPED, WAIT_TIMEOUT)); + ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::STOPPED, MY_WAIT_TIMEOUT)); m_audioPlayer->onPlaybackError( - m_mockMediaPlayer->getCurrentSourceId(), ErrorType::MEDIA_ERROR_UNKNOWN, "TEST_ERROR"); + m_mockMediaPlayer->getCurrentSourceId(), + ErrorType::MEDIA_ERROR_UNKNOWN, + "TEST_ERROR", + DEFAULT_MEDIA_PLAYER_STATE); std::unique_lock lock(m_mutex); bool result; - result = m_messageSentTrigger.wait_for(lock, WAIT_TIMEOUT, [this] { + result = m_messageSentTrigger.wait_for(lock, MY_WAIT_TIMEOUT, [this] { for (auto messageStatus : m_expectedMessages) { if (messageStatus.second == 0) { return false; @@ -1287,7 +1311,7 @@ TEST_F(AudioPlayerTest, testPrebufferOnPlaybackError) { })); sendPlayDirective(); - ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PLAYING, WAIT_TIMEOUT)); + ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PLAYING, MY_WAIT_TIMEOUT)); bool result; @@ -1311,7 +1335,10 @@ TEST_F(AudioPlayerTest, testPrebufferOnPlaybackError) { // Send error for track 2 while track 1 is playing m_audioPlayer->onPlaybackError( - m_mockMediaPlayerTrack2->getSourceId(), ErrorType::MEDIA_ERROR_UNKNOWN, "TEST_ERROR"); + m_mockMediaPlayerTrack2->getSourceId(), + ErrorType::MEDIA_ERROR_UNKNOWN, + "TEST_ERROR", + DEFAULT_MEDIA_PLAYER_STATE); // now 'play' track 2 m_audioPlayer->CapabilityAgent::handleDirective(MESSAGE_ID_TEST_2); @@ -1321,7 +1348,7 @@ TEST_F(AudioPlayerTest, testPrebufferOnPlaybackError) { // verify error not sent std::unique_lock lock(m_mutex); - result = m_messageSentTrigger.wait_for(lock, WAIT_TIMEOUT, [this] { + result = m_messageSentTrigger.wait_for(lock, MY_WAIT_TIMEOUT, [this] { for (auto messageStatus : m_expectedMessages) { if (messageStatus.second == 0) { return false; @@ -1335,9 +1362,9 @@ TEST_F(AudioPlayerTest, testPrebufferOnPlaybackError) { // verify error sent std::unique_lock lock(m_mutex); // Second track enqueue, but had an error loading. now advance by finishing playing - m_audioPlayer->onPlaybackFinished(m_mockMediaPlayer->getSourceId()); + m_audioPlayer->onPlaybackFinished(m_mockMediaPlayer->getSourceId(), DEFAULT_MEDIA_PLAYER_STATE); - result = m_messageSentTrigger.wait_for(lock, WAIT_TIMEOUT, [this] { + result = m_messageSentTrigger.wait_for(lock, MY_WAIT_TIMEOUT, [this] { for (auto messageStatus : m_expectedMessages) { if (messageStatus.second == 0) { return false; @@ -1366,15 +1393,12 @@ TEST_F(AudioPlayerTest, test_onPlaybackPaused) { sendPlayDirective(); - m_audioPlayer->onFocusChanged(FocusState::BACKGROUND); - - ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PAUSED, WAIT_TIMEOUT)); + m_audioPlayer->onFocusChanged(FocusState::BACKGROUND, MixingBehavior::MUST_PAUSE); + ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PAUSED, MY_WAIT_TIMEOUT)); std::unique_lock lock(m_mutex); - bool result; - - result = m_messageSentTrigger.wait_for(lock, WAIT_TIMEOUT, [this] { + result = m_messageSentTrigger.wait_for(lock, MY_WAIT_TIMEOUT, [this] { for (auto messageStatus : m_expectedMessages) { if (messageStatus.second == 0) { return false; @@ -1403,15 +1427,15 @@ TEST_F(AudioPlayerTest, test_onPlaybackResumed) { })); sendPlayDirective(); - ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PLAYING, WAIT_TIMEOUT)); + ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PLAYING, MY_WAIT_TIMEOUT)); bool result; - m_audioPlayer->onPlaybackResumed(m_mockMediaPlayer->getCurrentSourceId()); + m_audioPlayer->onPlaybackResumed(m_mockMediaPlayer->getCurrentSourceId(), DEFAULT_MEDIA_PLAYER_STATE); std::unique_lock lock(m_mutex); - result = m_messageSentTrigger.wait_for(lock, WAIT_TIMEOUT, [this] { + result = m_messageSentTrigger.wait_for(lock, MY_WAIT_TIMEOUT, [this] { for (auto messageStatus : m_expectedMessages) { if (messageStatus.second == 0) { return false; @@ -1419,6 +1443,7 @@ TEST_F(AudioPlayerTest, test_onPlaybackResumed) { } return true; }); + ASSERT_TRUE(result); } @@ -1439,14 +1464,14 @@ TEST_F(AudioPlayerTest, test_onPlaybackFinished_bufferCompleteAfterStarted) { })); sendPlayDirective(); - ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PLAYING, WAIT_TIMEOUT)); + ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PLAYING, MY_WAIT_TIMEOUT)); - m_audioPlayer->onPlaybackFinished(m_mockMediaPlayer->getCurrentSourceId()); + m_audioPlayer->onPlaybackFinished(m_mockMediaPlayer->getCurrentSourceId(), DEFAULT_MEDIA_PLAYER_STATE); std::unique_lock lock(m_mutex); bool result; - result = m_messageSentTrigger.wait_for(lock, WAIT_TIMEOUT, [this] { + result = m_messageSentTrigger.wait_for(lock, MY_WAIT_TIMEOUT, [this] { for (auto messageStatus : m_expectedMessages) { if (messageStatus.second == 0) { return false; @@ -1483,7 +1508,7 @@ TEST_F(AudioPlayerTest, test_onPlaybackFinished_bufferCompleteBeforeStarted) { createEnqueuePayloadTest(OFFSET_IN_MILLISECONDS_TEST), m_attachmentManager, CONTEXT_ID_TEST); - EXPECT_CALL(*(m_mockFocusManager.get()), acquireChannel(CHANNEL_NAME, _, NAMESPACE_AUDIO_PLAYER)) + EXPECT_CALL(*(m_mockFocusManager.get()), acquireChannel(CHANNEL_NAME, _)) .Times(1) .WillOnce(InvokeWithoutArgs(this, &AudioPlayerTest::wakeOnAcquireChannel)); @@ -1492,19 +1517,19 @@ TEST_F(AudioPlayerTest, test_onPlaybackFinished_bufferCompleteBeforeStarted) { m_audioPlayer->CapabilityAgent::preHandleDirective(playDirective, std::move(m_mockDirectiveHandlerResult)); m_audioPlayer->CapabilityAgent::handleDirective(MESSAGE_ID_TEST); - ASSERT_EQ(std::future_status::ready, m_wakeAcquireChannelFuture.wait_for(WAIT_TIMEOUT)); + ASSERT_EQ(std::future_status::ready, m_wakeAcquireChannelFuture.wait_for(MY_WAIT_TIMEOUT)); - m_audioPlayer->onFocusChanged(FocusState::FOREGROUND); + m_audioPlayer->onFocusChanged(FocusState::FOREGROUND, MixingBehavior::PRIMARY); - ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PLAYING, WAIT_TIMEOUT)); + ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PLAYING, MY_WAIT_TIMEOUT)); - m_audioPlayer->onBufferingComplete(m_mockMediaPlayer->getCurrentSourceId()); - m_audioPlayer->onPlaybackFinished(m_mockMediaPlayer->getCurrentSourceId()); + m_audioPlayer->onBufferingComplete(m_mockMediaPlayer->getCurrentSourceId(), DEFAULT_MEDIA_PLAYER_STATE); + m_audioPlayer->onPlaybackFinished(m_mockMediaPlayer->getCurrentSourceId(), DEFAULT_MEDIA_PLAYER_STATE); bool result; std::unique_lock lock(m_mutex); - result = m_messageSentTrigger.wait_for(lock, WAIT_TIMEOUT, [this] { + result = m_messageSentTrigger.wait_for(lock, MY_WAIT_TIMEOUT, [this] { for (auto messageStatus : m_expectedMessages) { if (messageStatus.second == 0) { return false; @@ -1534,13 +1559,13 @@ TEST_F(AudioPlayerTest, test_onBufferUnderrun) { sendPlayDirective(); - m_audioPlayer->onBufferUnderrun(m_mockMediaPlayer->getCurrentSourceId()); + m_audioPlayer->onBufferUnderrun(m_mockMediaPlayer->getCurrentSourceId(), DEFAULT_MEDIA_PLAYER_STATE); std::unique_lock lock(m_mutex); bool result; - result = m_messageSentTrigger.wait_for(lock, WAIT_TIMEOUT, [this] { + result = m_messageSentTrigger.wait_for(lock, MY_WAIT_TIMEOUT, [this] { for (auto messageStatus : m_expectedMessages) { if (messageStatus.second == 0) { return false; @@ -1570,13 +1595,13 @@ TEST_F(AudioPlayerTest, testTimer_onBufferRefilled) { sendPlayDirective(); - m_audioPlayer->onBufferRefilled(m_mockMediaPlayer->getCurrentSourceId()); + m_audioPlayer->onBufferRefilled(m_mockMediaPlayer->getCurrentSourceId(), DEFAULT_MEDIA_PLAYER_STATE); std::unique_lock lock(m_mutex); bool result; - result = m_messageSentTrigger.wait_for(lock, WAIT_TIMEOUT, [this] { + result = m_messageSentTrigger.wait_for(lock, MY_WAIT_TIMEOUT, [this] { for (auto messageStatus : m_expectedMessages) { if (messageStatus.second == 0) { return false; @@ -1593,16 +1618,18 @@ TEST_F(AudioPlayerTest, testTimer_onBufferRefilled) { * Build a vector of tags and pass to Observer (onTags). * Observer will use the vector of tags and build a valid JSON object * "StreamMetadataExtracted Event". This JSON object is verified in verifyTags. + * Verify that metadata not on whitelist is removed, and not sent */ -TEST_F(AudioPlayerTest, test_onTags) { +TEST_F(AudioPlayerTest, test_onTags_filteredOut) { + sendPlayDirective(); + m_expectedMessages.insert({STREAM_METADATA_EXTRACTED_NAME, 0}); m_expectedMessages.insert({MESSAGE_METADATA_STRING_VALUE, 0}); m_expectedMessages.insert({MESSAGE_METADATA_UINT_VALUE, 0}); m_expectedMessages.insert({MESSAGE_METADATA_DOUBLE_VALUE, 0}); EXPECT_CALL(*(m_mockMessageSender.get()), sendMessage(_)) - .Times(AtLeast(1)) .WillRepeatedly(Invoke([this](std::shared_ptr request) { if (!m_mockMediaPlayer->waitUntilPlaybackStopped(std::chrono::milliseconds(0))) { std::lock_guard lock(m_mutex); @@ -1641,11 +1668,83 @@ TEST_F(AudioPlayerTest, test_onTags) { booleanTag.type = AudioPlayer::TagType::BOOLEAN; vectorOfTags->push_back(booleanTag); - m_audioPlayer->onTags(m_mockMediaPlayer->getCurrentSourceId(), std::move(ptrToVectorOfTags)); + m_audioPlayer->onTags( + m_mockMediaPlayer->getCurrentSourceId(), std::move(ptrToVectorOfTags), DEFAULT_MEDIA_PLAYER_STATE); std::unique_lock lock(m_mutex); - auto result = m_messageSentTrigger.wait_for(lock, WAIT_TIMEOUT, [this] { + auto result = m_messageSentTrigger.wait_for(lock, MY_WAIT_TIMEOUT, [this] { + for (auto messageStatus : m_expectedMessages) { + if (messageStatus.second == 0) { + return false; + } + } + return true; + }); + ASSERT_FALSE(result); +} + +/** + * Test @c onTags and expect valid JSON. + * Build a vector of tags and pass to Observer (onTags). + * Observer will use the vector of tags and build a valid JSON object + * "StreamMetadataExtracted Event". This JSON object is verified in verifyTags. + * Send data on whitelist + */ + +TEST_F(AudioPlayerTest, test_onTags_filteredIn) { + sendPlayDirective(); + m_expectedMessages.insert({STREAM_METADATA_EXTRACTED_NAME, 0}); + m_expectedMessages.insert({MESSAGE_METADATA_STRING_VALUE, 0}); + m_expectedMessages.insert({MESSAGE_METADATA_UINT_VALUE, -1}); + m_expectedMessages.insert({MESSAGE_METADATA_DOUBLE_VALUE, -1}); + + EXPECT_CALL(*(m_mockMessageSender.get()), sendMessage(_)) + .Times(AtLeast(1)) + .WillRepeatedly(Invoke([this](std::shared_ptr request) { + if (!m_mockMediaPlayer->waitUntilPlaybackStopped(std::chrono::milliseconds(0))) { + std::lock_guard lock(m_mutex); + verifyTags(request, &m_expectedMessages, false); + m_messageSentTrigger.notify_one(); + } + })); + + std::unique_ptr ptrToVectorOfTags = make_unique(); + auto vectorOfTags = ptrToVectorOfTags.get(); + + // Populate vector with dummy tags + AudioPlayer::TagKeyValueType stringTag, uintTag, intTag, doubleTag, booleanTag; + stringTag.key = std::string(MESSAGE_METADATA_STRING_KEY_WL); + stringTag.value = std::string(MESSAGE_METADATA_STRING_VALUE); + stringTag.type = AudioPlayer::TagType::STRING; + vectorOfTags->push_back(stringTag); + + uintTag.key = std::string(MESSAGE_METADATA_UINT_KEY); + uintTag.value = std::string(MESSAGE_METADATA_UINT_VALUE); + uintTag.type = AudioPlayer::TagType::UINT; + vectorOfTags->push_back(uintTag); + + intTag.key = std::string(MESSAGE_METADATA_INT_KEY); + intTag.value = std::string(MESSAGE_METADATA_INT_VALUE); + intTag.type = AudioPlayer::TagType::INT; + vectorOfTags->push_back(intTag); + + doubleTag.key = std::string(MESSAGE_METADATA_DOUBLE_KEY); + doubleTag.value = std::string(MESSAGE_METADATA_DOUBLE_VALUE); + doubleTag.type = AudioPlayer::TagType::DOUBLE; + vectorOfTags->push_back(doubleTag); + + booleanTag.key = std::string(MESSAGE_METADATA_BOOLEAN_KEY); + booleanTag.value = std::string(MESSAGE_METADATA_BOOLEAN_VALUE); + booleanTag.type = AudioPlayer::TagType::BOOLEAN; + vectorOfTags->push_back(booleanTag); + + m_audioPlayer->onTags( + m_mockMediaPlayer->getCurrentSourceId(), std::move(ptrToVectorOfTags), DEFAULT_MEDIA_PLAYER_STATE); + + std::unique_lock lock(m_mutex); + + auto result = m_messageSentTrigger.wait_for(lock, MY_WAIT_TIMEOUT, [this] { for (auto messageStatus : m_expectedMessages) { if (messageStatus.second == 0) { return false; @@ -1656,6 +1755,272 @@ TEST_F(AudioPlayerTest, test_onTags) { ASSERT_TRUE(result); } +/** + * Test @c onTags and expect valid JSON. + * Build a vector of tags and pass to Observer (onTags). + * Observer will use the vector of tags and build a valid JSON object + * "StreamMetadataExtracted Event". This JSON object is verified in verifyTags. + * Send data on whitelist + * make sure event not sent too fast + */ + +TEST_F(AudioPlayerTest, test_onTags_filteredIn_rateCheck) { + sendPlayDirective(); + + m_expectedMessages.insert({STREAM_METADATA_EXTRACTED_NAME, 0}); + m_expectedMessages.insert({MESSAGE_METADATA_STRING_VALUE, 0}); + m_expectedMessages.insert({MESSAGE_METADATA_UINT_VALUE, -1}); + m_expectedMessages.insert({MESSAGE_METADATA_DOUBLE_VALUE, -1}); + + EXPECT_CALL(*(m_mockMessageSender.get()), sendMessage(_)) + .Times(AtLeast(1)) + .WillRepeatedly(Invoke([this](std::shared_ptr request) { + if (!m_mockMediaPlayer->waitUntilPlaybackStopped(std::chrono::milliseconds(0))) { + std::lock_guard lock(m_mutex); + verifyTags(request, &m_expectedMessages, false); + m_messageSentTrigger.notify_one(); + } + })); + + std::unique_ptr ptrToVectorOfTags = make_unique(); + auto vectorOfTags = ptrToVectorOfTags.get(); + + // Populate vector with dummy tags + AudioPlayer::TagKeyValueType stringTag, uintTag, intTag, doubleTag, booleanTag; + stringTag.key = std::string(MESSAGE_METADATA_STRING_KEY_WL); + stringTag.value = std::string(MESSAGE_METADATA_STRING_VALUE); + stringTag.type = AudioPlayer::TagType::STRING; + vectorOfTags->push_back(stringTag); + + uintTag.key = std::string(MESSAGE_METADATA_UINT_KEY); + uintTag.value = std::string(MESSAGE_METADATA_UINT_VALUE); + uintTag.type = AudioPlayer::TagType::UINT; + vectorOfTags->push_back(uintTag); + + intTag.key = std::string(MESSAGE_METADATA_INT_KEY); + intTag.value = std::string(MESSAGE_METADATA_INT_VALUE); + intTag.type = AudioPlayer::TagType::INT; + vectorOfTags->push_back(intTag); + + doubleTag.key = std::string(MESSAGE_METADATA_DOUBLE_KEY); + doubleTag.value = std::string(MESSAGE_METADATA_DOUBLE_VALUE); + doubleTag.type = AudioPlayer::TagType::DOUBLE; + vectorOfTags->push_back(doubleTag); + + booleanTag.key = std::string(MESSAGE_METADATA_BOOLEAN_KEY); + booleanTag.value = std::string(MESSAGE_METADATA_BOOLEAN_VALUE); + booleanTag.type = AudioPlayer::TagType::BOOLEAN; + vectorOfTags->push_back(booleanTag); + + std::unique_ptr ptrToVectorOfTags1 = + make_unique(vectorOfTags->begin(), vectorOfTags->end()); + m_audioPlayer->onTags( + m_mockMediaPlayer->getCurrentSourceId(), std::move(ptrToVectorOfTags1), DEFAULT_MEDIA_PLAYER_STATE); + + { + std::unique_lock lock(m_mutex); + + auto result = m_messageSentTrigger.wait_for(lock, MY_WAIT_TIMEOUT, [this] { + for (auto messageStatus : m_expectedMessages) { + if (messageStatus.second == 0) { + return false; + } + } + return true; + }); + + ASSERT_TRUE(result); + } + m_expectedMessages.clear(); + m_expectedMessages.insert({STREAM_METADATA_EXTRACTED_NAME, 0}); + m_expectedMessages.insert({MESSAGE_METADATA_STRING_VALUE_ALT, 0}); + m_expectedMessages.insert({MESSAGE_METADATA_UINT_VALUE, -1}); + m_expectedMessages.insert({MESSAGE_METADATA_DOUBLE_VALUE, -1}); + + for (auto iter = vectorOfTags->begin(); iter != vectorOfTags->end(); ++iter) { + if (iter->key == MESSAGE_METADATA_STRING_KEY_WL) { + iter->value = MESSAGE_METADATA_STRING_VALUE_ALT; + break; + } + } + + std::unique_ptr ptrToVectorOfTags2 = + make_unique(vectorOfTags->begin(), vectorOfTags->end()); + m_audioPlayer->onTags( + m_mockMediaPlayer->getCurrentSourceId(), std::move(ptrToVectorOfTags2), DEFAULT_MEDIA_PLAYER_STATE); + + { + std::unique_lock lock(m_mutex); + + auto result = m_messageSentTrigger.wait_for(lock, MY_WAIT_TIMEOUT, [this] { + for (auto messageStatus : m_expectedMessages) { + if (messageStatus.second == 0) { + return false; + } + } + return true; + }); + + ASSERT_FALSE(result); + } + std::this_thread::sleep_for(std::chrono::milliseconds(METADATA_EVENT_DELAY)); + + m_audioPlayer->onTags( + m_mockMediaPlayer->getCurrentSourceId(), std::move(ptrToVectorOfTags), DEFAULT_MEDIA_PLAYER_STATE); + + { + std::unique_lock lock(m_mutex); + + auto result = m_messageSentTrigger.wait_for(lock, MY_WAIT_TIMEOUT, [this] { + for (auto messageStatus : m_expectedMessages) { + if (messageStatus.second == 0) { + return false; + } + } + return true; + }); + + ASSERT_TRUE(result); + } +} + +/** + * Test @c onTags and expect valid JSON. + * Build a vector of tags and pass to Observer (onTags). + * Observer will use the vector of tags and build a valid JSON object + * "StreamMetadataExtracted Event". This JSON object is verified in verifyTags. + * Send data on whitelist + * make sure duplicate not sent + */ + +TEST_F(AudioPlayerTest, test_onTags_filteredIn_duplicateCheck) { + sendPlayDirective(); + + m_expectedMessages.insert({STREAM_METADATA_EXTRACTED_NAME, 0}); + m_expectedMessages.insert({MESSAGE_METADATA_STRING_VALUE, 0}); + m_expectedMessages.insert({MESSAGE_METADATA_UINT_VALUE, -1}); + m_expectedMessages.insert({MESSAGE_METADATA_DOUBLE_VALUE, -1}); + + EXPECT_CALL(*(m_mockMessageSender.get()), sendMessage(_)) + .Times(AtLeast(1)) + .WillRepeatedly(Invoke([this](std::shared_ptr request) { + if (!m_mockMediaPlayer->waitUntilPlaybackStopped(std::chrono::milliseconds(0))) { + std::lock_guard lock(m_mutex); + verifyTags(request, &m_expectedMessages, false); + m_messageSentTrigger.notify_one(); + } + })); + + std::unique_ptr ptrToVectorOfTags = make_unique(); + auto vectorOfTags = ptrToVectorOfTags.get(); + + // Populate vector with dummy tags + AudioPlayer::TagKeyValueType stringTag, uintTag, intTag, doubleTag, booleanTag; + stringTag.key = std::string(MESSAGE_METADATA_STRING_KEY_WL); + stringTag.value = std::string(MESSAGE_METADATA_STRING_VALUE); + stringTag.type = AudioPlayer::TagType::STRING; + vectorOfTags->push_back(stringTag); + + uintTag.key = std::string(MESSAGE_METADATA_UINT_KEY); + uintTag.value = std::string(MESSAGE_METADATA_UINT_VALUE); + uintTag.type = AudioPlayer::TagType::UINT; + vectorOfTags->push_back(uintTag); + + intTag.key = std::string(MESSAGE_METADATA_INT_KEY); + intTag.value = std::string(MESSAGE_METADATA_INT_VALUE); + intTag.type = AudioPlayer::TagType::INT; + vectorOfTags->push_back(intTag); + + doubleTag.key = std::string(MESSAGE_METADATA_DOUBLE_KEY); + doubleTag.value = std::string(MESSAGE_METADATA_DOUBLE_VALUE); + doubleTag.type = AudioPlayer::TagType::DOUBLE; + vectorOfTags->push_back(doubleTag); + + booleanTag.key = std::string(MESSAGE_METADATA_BOOLEAN_KEY); + booleanTag.value = std::string(MESSAGE_METADATA_BOOLEAN_VALUE); + booleanTag.type = AudioPlayer::TagType::BOOLEAN; + vectorOfTags->push_back(booleanTag); + + std::unique_ptr ptrToVectorOfTags1 = + make_unique(vectorOfTags->begin(), vectorOfTags->end()); + m_audioPlayer->onTags( + m_mockMediaPlayer->getCurrentSourceId(), std::move(ptrToVectorOfTags1), DEFAULT_MEDIA_PLAYER_STATE); + + { + std::unique_lock lock(m_mutex); + + auto result = m_messageSentTrigger.wait_for(lock, MY_WAIT_TIMEOUT, [this] { + for (auto messageStatus : m_expectedMessages) { + if (messageStatus.second == 0) { + return false; + } + } + return true; + }); + + ASSERT_TRUE(result); + } + + std::this_thread::sleep_for(std::chrono::milliseconds(METADATA_EVENT_DELAY)); + + m_expectedMessages.clear(); + m_expectedMessages.insert({STREAM_METADATA_EXTRACTED_NAME, 0}); + m_expectedMessages.insert({MESSAGE_METADATA_STRING_VALUE, 0}); + m_expectedMessages.insert({MESSAGE_METADATA_UINT_VALUE, -1}); + m_expectedMessages.insert({MESSAGE_METADATA_DOUBLE_VALUE, -1}); + + std::unique_ptr ptrToVectorOfTags2 = + make_unique(vectorOfTags->begin(), vectorOfTags->end()); + m_audioPlayer->onTags( + m_mockMediaPlayer->getCurrentSourceId(), std::move(ptrToVectorOfTags2), DEFAULT_MEDIA_PLAYER_STATE); + + { + std::unique_lock lock(m_mutex); + + auto result = m_messageSentTrigger.wait_for(lock, MY_WAIT_TIMEOUT, [this] { + for (auto messageStatus : m_expectedMessages) { + if (messageStatus.second == 0) { + return false; + } + } + return true; + }); + + ASSERT_FALSE(result); + } + std::this_thread::sleep_for(std::chrono::milliseconds(METADATA_EVENT_DELAY)); + + m_expectedMessages.clear(); + m_expectedMessages.insert({STREAM_METADATA_EXTRACTED_NAME, 0}); + m_expectedMessages.insert({MESSAGE_METADATA_STRING_VALUE_ALT, 0}); + m_expectedMessages.insert({MESSAGE_METADATA_UINT_VALUE, -1}); + m_expectedMessages.insert({MESSAGE_METADATA_DOUBLE_VALUE, -1}); + + for (auto iter = vectorOfTags->begin(); iter != vectorOfTags->end(); ++iter) { + if (iter->key == MESSAGE_METADATA_STRING_KEY_WL) { + iter->value = MESSAGE_METADATA_STRING_VALUE_ALT; + break; + } + } + m_audioPlayer->onTags( + m_mockMediaPlayer->getCurrentSourceId(), std::move(ptrToVectorOfTags), DEFAULT_MEDIA_PLAYER_STATE); + + { + std::unique_lock lock(m_mutex); + + auto result = m_messageSentTrigger.wait_for(lock, MY_WAIT_TIMEOUT, [this] { + for (auto messageStatus : m_expectedMessages) { + if (messageStatus.second == 0) { + return false; + } + } + return true; + }); + + ASSERT_TRUE(result); + } +} + /** * Test @c cancelDirective * Expect the @c handleDirective call to the cancelled directive returns false @@ -1676,8 +2041,8 @@ TEST_F(AudioPlayerTest, test_cancelDirective) { TEST_F(AudioPlayerTest, test_focusChangeToNoneInIdleState) { // switching to FocusState::NONE should cause no change - m_audioPlayer->onFocusChanged(FocusState::NONE); - ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::IDLE, WAIT_TIMEOUT)); + m_audioPlayer->onFocusChanged(FocusState::NONE, MixingBehavior::MUST_STOP); + ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::IDLE, MY_WAIT_TIMEOUT)); } /** @@ -1687,12 +2052,13 @@ TEST_F(AudioPlayerTest, test_focusChangeToNoneInIdleState) { */ TEST_F(AudioPlayerTest, test_focusChangeFromForegroundToBackgroundInIdleState) { - m_audioPlayer->onFocusChanged(FocusState::FOREGROUND); + m_audioPlayer->onFocusChanged(FocusState::FOREGROUND, MixingBehavior::PRIMARY); + // ensure AudioPlayer is IDLE + ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::IDLE, MY_WAIT_TIMEOUT)); + m_audioPlayer->onFocusChanged(FocusState::BACKGROUND, MixingBehavior::MUST_PAUSE); // ensure AudioPlayer is still IDLE - ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::IDLE, WAIT_TIMEOUT)); - - m_audioPlayer->onFocusChanged(FocusState::BACKGROUND); + ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::IDLE, MY_WAIT_TIMEOUT)); } /** @@ -1701,7 +2067,7 @@ TEST_F(AudioPlayerTest, test_focusChangeFromForegroundToBackgroundInIdleState) { */ TEST_F(AudioPlayerTest, test_focusChangeFromNoneToBackgroundInIdleState) { - m_audioPlayer->onFocusChanged(FocusState::BACKGROUND); + m_audioPlayer->onFocusChanged(FocusState::BACKGROUND, MixingBehavior::MUST_PAUSE); } /** @@ -1713,23 +2079,23 @@ TEST_F(AudioPlayerTest, test_focusChangesInPlayingState) { sendPlayDirective(); // already in FOREGROUND, expect no change - m_audioPlayer->onFocusChanged(FocusState::FOREGROUND); - ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PLAYING, WAIT_TIMEOUT)); + m_audioPlayer->onFocusChanged(FocusState::FOREGROUND, MixingBehavior::PRIMARY); + ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PLAYING, MY_WAIT_TIMEOUT)); // expect to pause in BACKGROUND EXPECT_CALL(*(m_mockMediaPlayer.get()), pause(_)).Times(1); - m_audioPlayer->onFocusChanged(FocusState::BACKGROUND); - ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PAUSED, WAIT_TIMEOUT)); + m_audioPlayer->onFocusChanged(FocusState::BACKGROUND, MixingBehavior::MUST_PAUSE); + ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PAUSED, MY_WAIT_TIMEOUT)); // expect to resume when switching back to FOREGROUND EXPECT_CALL(*(m_mockMediaPlayer.get()), resume(_)).Times(1); - m_audioPlayer->onFocusChanged(FocusState::FOREGROUND); - ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PLAYING, WAIT_TIMEOUT)); + m_audioPlayer->onFocusChanged(FocusState::FOREGROUND, MixingBehavior::PRIMARY); + ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PLAYING, MY_WAIT_TIMEOUT)); // expect to stop when changing focus to NONE EXPECT_CALL(*(m_mockMediaPlayer.get()), stop(_)).Times(1); - m_audioPlayer->onFocusChanged(FocusState::NONE); - ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::STOPPED, WAIT_TIMEOUT)); + m_audioPlayer->onFocusChanged(FocusState::NONE, MixingBehavior::MUST_STOP); + ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::STOPPED, MY_WAIT_TIMEOUT)); } /** @@ -1743,15 +2109,15 @@ TEST_F(AudioPlayerTest, test_focusChangesInStoppedState) { // push AudioPlayer into stopped state EXPECT_CALL(*(m_mockMediaPlayer.get()), stop(_)).Times(AtLeast(1)); - m_audioPlayer->onFocusChanged(FocusState::NONE); - ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::STOPPED, WAIT_TIMEOUT)); + m_audioPlayer->onFocusChanged(FocusState::NONE, MixingBehavior::MUST_STOP); + ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::STOPPED, MY_WAIT_TIMEOUT)); - m_audioPlayer->onFocusChanged(FocusState::FOREGROUND); - ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::STOPPED, WAIT_TIMEOUT)); + m_audioPlayer->onFocusChanged(FocusState::FOREGROUND, MixingBehavior::PRIMARY); + ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::STOPPED, MY_WAIT_TIMEOUT)); EXPECT_CALL(*(m_mockMediaPlayer.get()), pause(_)).Times(1); - m_audioPlayer->onFocusChanged(FocusState::BACKGROUND); - ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PAUSED, WAIT_TIMEOUT)); + m_audioPlayer->onFocusChanged(FocusState::BACKGROUND, MixingBehavior::MUST_PAUSE); + ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PAUSED, MY_WAIT_TIMEOUT)); } /** @@ -1764,27 +2130,27 @@ TEST_F(AudioPlayerTest, test_focusChangesInPausedState) { // push AudioPlayer into paused state EXPECT_CALL(*(m_mockMediaPlayer.get()), pause(_)).Times(1); - m_audioPlayer->onFocusChanged(FocusState::BACKGROUND); - ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PAUSED, WAIT_TIMEOUT)); + m_audioPlayer->onFocusChanged(FocusState::BACKGROUND, MixingBehavior::MUST_PAUSE); + ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PAUSED, MY_WAIT_TIMEOUT)); // expect a resume when switching back to FOREGROUND EXPECT_CALL(*(m_mockMediaPlayer.get()), resume(_)).Times(1); - m_audioPlayer->onFocusChanged(FocusState::FOREGROUND); - ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PLAYING, WAIT_TIMEOUT)); + m_audioPlayer->onFocusChanged(FocusState::FOREGROUND, MixingBehavior::PRIMARY); + ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PLAYING, MY_WAIT_TIMEOUT)); // return to paused state EXPECT_CALL(*(m_mockMediaPlayer.get()), pause(_)).Times(1); - m_audioPlayer->onFocusChanged(FocusState::BACKGROUND); - ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PAUSED, WAIT_TIMEOUT)); + m_audioPlayer->onFocusChanged(FocusState::BACKGROUND, MixingBehavior::MUST_PAUSE); + ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PAUSED, MY_WAIT_TIMEOUT)); // expect nothing to happen when switching to BACKGROUND from BACKGROUND - m_audioPlayer->onFocusChanged(FocusState::BACKGROUND); - ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PAUSED, WAIT_TIMEOUT)); + m_audioPlayer->onFocusChanged(FocusState::BACKGROUND, MixingBehavior::MUST_PAUSE); + ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PAUSED, MY_WAIT_TIMEOUT)); // expect stop when switching to NONE focus EXPECT_CALL(*(m_mockMediaPlayer.get()), stop(_)).Times(1); - m_audioPlayer->onFocusChanged(FocusState::NONE); - ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::STOPPED, WAIT_TIMEOUT)); + m_audioPlayer->onFocusChanged(FocusState::NONE, MixingBehavior::MUST_STOP); + ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::STOPPED, MY_WAIT_TIMEOUT)); } /** @@ -1797,29 +2163,29 @@ TEST_F(AudioPlayerTest, test_focusChangesInBufferUnderrunState) { sendPlayDirective(); // push AudioPlayer into buffer underrun state - m_audioPlayer->onBufferUnderrun(m_mockMediaPlayer->getCurrentSourceId()); - ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::BUFFER_UNDERRUN, WAIT_TIMEOUT)); + m_audioPlayer->onBufferUnderrun(m_mockMediaPlayer->getCurrentSourceId(), DEFAULT_MEDIA_PLAYER_STATE); + ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::BUFFER_UNDERRUN, MY_WAIT_TIMEOUT)); // nothing happens, AudioPlayer already in FOREGROUND - m_audioPlayer->onFocusChanged(FocusState::FOREGROUND); - ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::BUFFER_UNDERRUN, WAIT_TIMEOUT)); + m_audioPlayer->onFocusChanged(FocusState::FOREGROUND, MixingBehavior::PRIMARY); + ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::BUFFER_UNDERRUN, MY_WAIT_TIMEOUT)); // expect to pause if pushed to BACKGROUND EXPECT_CALL(*(m_mockMediaPlayer.get()), pause(_)).Times(1); - m_audioPlayer->onFocusChanged(FocusState::BACKGROUND); - ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PAUSED, WAIT_TIMEOUT)); + m_audioPlayer->onFocusChanged(FocusState::BACKGROUND, MixingBehavior::MUST_PAUSE); + ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PAUSED, MY_WAIT_TIMEOUT)); // back to FOREGROUND and buffer underrun state EXPECT_CALL(*(m_mockMediaPlayer.get()), resume(_)).Times(1); - m_audioPlayer->onFocusChanged(FocusState::FOREGROUND); - ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PLAYING, WAIT_TIMEOUT)); - m_audioPlayer->onBufferUnderrun(m_mockMediaPlayer->getCurrentSourceId()); - ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::BUFFER_UNDERRUN, WAIT_TIMEOUT)); + m_audioPlayer->onFocusChanged(FocusState::FOREGROUND, MixingBehavior::PRIMARY); + ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PLAYING, MY_WAIT_TIMEOUT)); + m_audioPlayer->onBufferUnderrun(m_mockMediaPlayer->getCurrentSourceId(), DEFAULT_MEDIA_PLAYER_STATE); + ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::BUFFER_UNDERRUN, MY_WAIT_TIMEOUT)); // expect stop when switching to NONE focus EXPECT_CALL(*(m_mockMediaPlayer.get()), stop(_)).Times(1); - m_audioPlayer->onFocusChanged(FocusState::NONE); - ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::STOPPED, WAIT_TIMEOUT)); + m_audioPlayer->onFocusChanged(FocusState::NONE, MixingBehavior::MUST_STOP); + ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::STOPPED, MY_WAIT_TIMEOUT)); } /** @@ -1836,13 +2202,11 @@ TEST_F(AudioPlayerTest, test_focusChangeToBackgroundBeforeOnPlaybackStarted) { sendClearQueueDirective(); - ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::STOPPED, WAIT_TIMEOUT)); - m_audioPlayer->onFocusChanged(FocusState::NONE); + ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::STOPPED, MY_WAIT_TIMEOUT)); + m_audioPlayer->onFocusChanged(FocusState::NONE, MixingBehavior::MUST_STOP); // send a second Play directive and move to foreground - EXPECT_CALL(*(m_mockFocusManager.get()), acquireChannel(CHANNEL_NAME, _, NAMESPACE_AUDIO_PLAYER)) - .Times(1) - .WillOnce(Return(true)); + EXPECT_CALL(*(m_mockFocusManager.get()), acquireChannel(CHANNEL_NAME, _)).Times(1).WillOnce(Return(true)); EXPECT_CALL(*(m_mockMediaPlayerTrack2.get()), play(_)).Times(1); auto avsMessageHeader = std::make_shared(NAMESPACE_AUDIO_PLAYER, NAME_PLAY, MESSAGE_ID_TEST_2); @@ -1856,11 +2220,11 @@ TEST_F(AudioPlayerTest, test_focusChangeToBackgroundBeforeOnPlaybackStarted) { m_audioPlayer->CapabilityAgent::preHandleDirective(playDirective, std::move(m_mockDirectiveHandlerResult)); m_audioPlayer->CapabilityAgent::handleDirective(MESSAGE_ID_TEST_2); - m_audioPlayer->onFocusChanged(FocusState::FOREGROUND); - ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PLAYING, WAIT_TIMEOUT)); + m_audioPlayer->onFocusChanged(FocusState::FOREGROUND, MixingBehavior::PRIMARY); + ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PLAYING, MY_WAIT_TIMEOUT)); - m_audioPlayer->onFocusChanged(FocusState::BACKGROUND); - ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PAUSED, WAIT_TIMEOUT)); + m_audioPlayer->onFocusChanged(FocusState::BACKGROUND, MixingBehavior::MUST_PAUSE); + ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PAUSED, MY_WAIT_TIMEOUT)); } /** @@ -1872,15 +2236,18 @@ TEST_F(AudioPlayerTest, test_playAfterOnPlaybackError) { EXPECT_CALL(*(m_mockMediaPlayer.get()), getOffset(_)) .WillRepeatedly(Return(m_mockMediaPlayer->getOffset(m_mockMediaPlayer->getCurrentSourceId()))); sendPlayDirective(); - ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PLAYING, WAIT_TIMEOUT)); + ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PLAYING, MY_WAIT_TIMEOUT)); EXPECT_CALL(*(m_mockFocusManager.get()), releaseChannel(CHANNEL_NAME, _)) .Times(1) .WillOnce(InvokeWithoutArgs(this, &AudioPlayerTest::wakeOnReleaseChannel)); m_audioPlayer->onPlaybackError( - m_mockMediaPlayer->getCurrentSourceId(), ErrorType::MEDIA_ERROR_UNKNOWN, "TEST_ERROR"); - ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::STOPPED, WAIT_TIMEOUT)); - ASSERT_EQ(std::future_status::ready, m_wakeReleaseChannelFuture.wait_for(WAIT_TIMEOUT)); - m_audioPlayer->onFocusChanged(FocusState::NONE); + m_mockMediaPlayer->getCurrentSourceId(), + ErrorType::MEDIA_ERROR_UNKNOWN, + "TEST_ERROR", + DEFAULT_MEDIA_PLAYER_STATE); + ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::STOPPED, MY_WAIT_TIMEOUT)); + ASSERT_EQ(std::future_status::ready, m_wakeReleaseChannelFuture.wait_for(MY_WAIT_TIMEOUT)); + m_audioPlayer->onFocusChanged(FocusState::NONE, MixingBehavior::MUST_STOP); // send a REPLACE_ALL Play directive to see if AudioPlayer can still play the new item EXPECT_CALL(*(m_mockMediaPlayerTrack2.get()), play(_)).Times(1); @@ -1891,14 +2258,14 @@ TEST_F(AudioPlayerTest, test_playAfterOnPlaybackError) { m_wakeAcquireChannelPromise = std::promise(); m_wakeAcquireChannelFuture = m_wakeAcquireChannelPromise.get_future(); - EXPECT_CALL(*(m_mockFocusManager.get()), acquireChannel(CHANNEL_NAME, _, NAMESPACE_AUDIO_PLAYER)) + EXPECT_CALL(*(m_mockFocusManager.get()), acquireChannel(CHANNEL_NAME, _)) .Times(1) .WillOnce(InvokeWithoutArgs(this, &AudioPlayerTest::wakeOnAcquireChannel)); m_audioPlayer->CapabilityAgent::preHandleDirective(playDirective, std::move(m_mockDirectiveHandlerResult)); m_audioPlayer->CapabilityAgent::handleDirective(MESSAGE_ID_TEST_2); - ASSERT_EQ(std::future_status::ready, m_wakeAcquireChannelFuture.wait_for(WAIT_TIMEOUT)); - m_audioPlayer->onFocusChanged(FocusState::FOREGROUND); - ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PLAYING, WAIT_TIMEOUT)); + ASSERT_EQ(std::future_status::ready, m_wakeAcquireChannelFuture.wait_for(MY_WAIT_TIMEOUT)); + m_audioPlayer->onFocusChanged(FocusState::FOREGROUND, MixingBehavior::PRIMARY); + ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PLAYING, MY_WAIT_TIMEOUT)); } /** @@ -1946,7 +2313,7 @@ TEST_F(AudioPlayerTest, test_progressReportDelayElapsed) { std::this_thread::sleep_for(std::chrono::milliseconds(PROGRESS_REPORT_DELAY)); std::unique_lock lock(m_mutex); - auto result = m_messageSentTrigger.wait_for(lock, WAIT_TIMEOUT, [this] { + auto result = m_messageSentTrigger.wait_for(lock, MY_WAIT_TIMEOUT, [this] { for (auto messageStatus : m_expectedMessages) { if (messageStatus.second != 1) { return false; @@ -1977,7 +2344,7 @@ TEST_F(AudioPlayerTest, test_progressReportDelayElapsedDelayLessThanOffset) { std::this_thread::sleep_for(std::chrono::milliseconds(PROGRESS_REPORT_DELAY)); std::unique_lock lock(m_mutex); - auto result = m_messageSentTrigger.wait_for(lock, WAIT_TIMEOUT, [this] { + auto result = m_messageSentTrigger.wait_for(lock, MY_WAIT_TIMEOUT, [this] { for (auto messageStatus : m_expectedMessages) { if (messageStatus.second != 0) { return false; @@ -2008,7 +2375,7 @@ TEST_F(AudioPlayerTest, testTimer_progressReportIntervalElapsed) { std::this_thread::sleep_for(TIME_FOR_TWO_AND_A_HALF_INTERVAL_PERIODS); std::unique_lock lock(m_mutex); - auto result = m_messageSentTrigger.wait_for(lock, WAIT_TIMEOUT, [this] { + auto result = m_messageSentTrigger.wait_for(lock, MY_WAIT_TIMEOUT, [this] { for (auto messageStatus : m_expectedMessages) { if (messageStatus.second != 3) { return false; @@ -2039,7 +2406,7 @@ TEST_F(AudioPlayerTest, test_progressReportIntervalElapsedIntervalLessThanOffset std::this_thread::sleep_for(TIME_FOR_TWO_AND_A_HALF_INTERVAL_PERIODS); std::unique_lock lock(m_mutex); - auto result = m_messageSentTrigger.wait_for(lock, WAIT_TIMEOUT, [this] { + auto result = m_messageSentTrigger.wait_for(lock, MY_WAIT_TIMEOUT, [this] { for (auto messageStatus : m_expectedMessages) { if (messageStatus.second != 2) { return false; @@ -2060,10 +2427,10 @@ TEST_F(AudioPlayerTest, testSlow_playOnlyAfterForegroundFocus) { EXPECT_CALL(*(m_mockMediaPlayer.get()), getOffset(_)) .WillRepeatedly(Return(m_mockMediaPlayer->getOffset(m_mockMediaPlayer->getCurrentSourceId()))); sendPlayDirective(); - ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PLAYING, WAIT_TIMEOUT)); - m_audioPlayer->onPlaybackStarted(m_mockMediaPlayer->getCurrentSourceId()); - m_audioPlayer->onFocusChanged(FocusState::BACKGROUND); - ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PAUSED, WAIT_TIMEOUT)); + ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PLAYING, MY_WAIT_TIMEOUT)); + m_audioPlayer->onPlaybackStarted(m_mockMediaPlayer->getCurrentSourceId(), DEFAULT_MEDIA_PLAYER_STATE); + m_audioPlayer->onFocusChanged(FocusState::BACKGROUND, MixingBehavior::MUST_PAUSE); + ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PAUSED, MY_WAIT_TIMEOUT)); // send a REPLACE_ALL Play directive auto avsMessageHeader = std::make_shared(NAMESPACE_AUDIO_PLAYER, NAME_PLAY, MESSAGE_ID_TEST_2); @@ -2075,12 +2442,12 @@ TEST_F(AudioPlayerTest, testSlow_playOnlyAfterForegroundFocus) { m_audioPlayer->CapabilityAgent::preHandleDirective(playDirective, std::move(m_mockDirectiveHandlerResult)); EXPECT_CALL(*(m_mockMediaPlayerTrack2.get()), play(_)).Times(0); m_audioPlayer->CapabilityAgent::handleDirective(MESSAGE_ID_TEST_2); - ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::STOPPED, WAIT_TIMEOUT)); + ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::STOPPED, MY_WAIT_TIMEOUT)); // Now check play() is only called when focus to back to FOREGROUND EXPECT_CALL(*(m_mockMediaPlayerTrack2.get()), play(_)).Times(1); - m_audioPlayer->onFocusChanged(FocusState::FOREGROUND); - ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PLAYING, WAIT_TIMEOUT)); + m_audioPlayer->onFocusChanged(FocusState::FOREGROUND, MixingBehavior::PRIMARY); + ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PLAYING, MY_WAIT_TIMEOUT)); } /** @@ -2100,7 +2467,7 @@ TEST_F(AudioPlayerTest, testTimer_playbackStartedCallbackAfterFocusLost) { createEnqueuePayloadTest(OFFSET_IN_MILLISECONDS_TEST), m_attachmentManager, CONTEXT_ID_TEST); - EXPECT_CALL(*(m_mockFocusManager.get()), acquireChannel(CHANNEL_NAME, _, NAMESPACE_AUDIO_PLAYER)) + EXPECT_CALL(*(m_mockFocusManager.get()), acquireChannel(CHANNEL_NAME, _)) .Times(1) .WillOnce(InvokeWithoutArgs(this, &AudioPlayerTest::wakeOnAcquireChannel)); @@ -2124,16 +2491,16 @@ TEST_F(AudioPlayerTest, testTimer_playbackStartedCallbackAfterFocusLost) { EXPECT_CALL(*m_mockMediaPlayer, stop(_)).Times(1); // Wait for acquireFocus(). - ASSERT_EQ(std::future_status::ready, m_wakeAcquireChannelFuture.wait_for(WAIT_TIMEOUT)); - m_audioPlayer->onFocusChanged(FocusState::FOREGROUND); + ASSERT_EQ(std::future_status::ready, m_wakeAcquireChannelFuture.wait_for(MY_WAIT_TIMEOUT)); + m_audioPlayer->onFocusChanged(FocusState::FOREGROUND, MixingBehavior::PRIMARY); - ASSERT_THAT(playCalled.wait_for(WAIT_TIMEOUT), Ne(std::future_status::timeout)); + ASSERT_THAT(playCalled.wait_for(MY_WAIT_TIMEOUT), Ne(std::future_status::timeout)); - m_audioPlayer->onFocusChanged(FocusState::NONE); - m_audioPlayer->onPlaybackStarted(m_mockMediaPlayer->getSourceId()); + m_audioPlayer->onFocusChanged(FocusState::NONE, MixingBehavior::MUST_STOP); + m_audioPlayer->onPlaybackStarted(m_mockMediaPlayer->getSourceId(), DEFAULT_MEDIA_PLAYER_STATE); // Make sure stop() will be called, but STOPPED state is already true - ASSERT_FALSE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PLAYING, WAIT_TIMEOUT)); + ASSERT_FALSE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PLAYING, MY_WAIT_TIMEOUT)); } } @@ -2143,10 +2510,10 @@ TEST_F(AudioPlayerTest, testTimer_playbackStartedCallbackAfterFocusLost) { * It is called from tests that set up different numbers of MediaPlayers in a Factory Pool * to ensure everything works smoothly. */ -void AudioPlayerTest::testPlayEnqueFinishPlay() { +void AudioPlayerTest::testPlayEnqueueFinishPlay() { // send a play directive sendPlayDirective(); - ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PLAYING, WAIT_TIMEOUT)); + ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PLAYING, MY_WAIT_TIMEOUT)); std::this_thread::sleep_for(std::chrono::milliseconds(EVENT_PROCESS_DELAY)); @@ -2164,44 +2531,44 @@ void AudioPlayerTest::testPlayEnqueFinishPlay() { m_audioPlayer->CapabilityAgent::handleDirective(msgId); } - m_audioPlayer->onPlaybackFinished(m_mockMediaPlayer->getCurrentSourceId()); + m_audioPlayer->onPlaybackFinished(m_mockMediaPlayer->getCurrentSourceId(), DEFAULT_MEDIA_PLAYER_STATE); - ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::FINISHED, WAIT_TIMEOUT)); - ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PLAYING, WAIT_TIMEOUT)); + ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::FINISHED, MY_WAIT_TIMEOUT)); + ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PLAYING, MY_WAIT_TIMEOUT)); - m_audioPlayer->onPlaybackFinished(m_mockMediaPlayer->getCurrentSourceId()); + m_audioPlayer->onPlaybackFinished(m_mockMediaPlayer->getCurrentSourceId(), DEFAULT_MEDIA_PLAYER_STATE); - ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::FINISHED, WAIT_TIMEOUT)); - ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PLAYING, WAIT_TIMEOUT)); + ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::FINISHED, MY_WAIT_TIMEOUT)); + ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PLAYING, MY_WAIT_TIMEOUT)); - m_audioPlayer->onPlaybackFinished(m_mockMediaPlayer->getCurrentSourceId()); + m_audioPlayer->onPlaybackFinished(m_mockMediaPlayer->getCurrentSourceId(), DEFAULT_MEDIA_PLAYER_STATE); - ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::FINISHED, WAIT_TIMEOUT)); - ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PLAYING, WAIT_TIMEOUT)); + ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::FINISHED, MY_WAIT_TIMEOUT)); + ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PLAYING, MY_WAIT_TIMEOUT)); - m_audioPlayer->onPlaybackFinished(m_mockMediaPlayer->getCurrentSourceId()); - ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::FINISHED, WAIT_TIMEOUT)); + m_audioPlayer->onPlaybackFinished(m_mockMediaPlayer->getCurrentSourceId(), DEFAULT_MEDIA_PLAYER_STATE); + ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::FINISHED, MY_WAIT_TIMEOUT)); } -TEST_F(AudioPlayerTest, test1PlayerPool_PlayEnqueFinishPlay) { +TEST_F(AudioPlayerTest, test1PlayerPool_PlayEnqueueFinishPlay) { reSetUp(1); - testPlayEnqueFinishPlay(); + testPlayEnqueueFinishPlay(); } -TEST_F(AudioPlayerTest, test2PlayerPool_PlayEnqueFinishPlay) { +TEST_F(AudioPlayerTest, test2PlayerPool_PlayEnqueueFinishPlay) { reSetUp(2); - testPlayEnqueFinishPlay(); + testPlayEnqueueFinishPlay(); } -TEST_F(AudioPlayerTest, test3PlayerPool_PlayEnqueFinishPlay) { +TEST_F(AudioPlayerTest, test3PlayerPool_PlayEnqueueFinishPlay) { reSetUp(3); - testPlayEnqueFinishPlay(); + testPlayEnqueueFinishPlay(); } -/* +/** * Test the playRequestor Object can be parsed by the AudioPlayer and reported to its observers via the * AudioPlayerObserverInterface. */ @@ -2211,7 +2578,7 @@ TEST_F(AudioPlayerTest, testPlayRequestor) { std::shared_ptr playDirective = AVSDirective::create("", avsMessageHeader, PLAY_REQUESTOR_PAYLOAD_TEST, m_attachmentManager, CONTEXT_ID_TEST); - EXPECT_CALL(*(m_mockFocusManager.get()), acquireChannel(CHANNEL_NAME, _, NAMESPACE_AUDIO_PLAYER)) + EXPECT_CALL(*(m_mockFocusManager.get()), acquireChannel(CHANNEL_NAME, _)) .Times(1) .WillOnce(InvokeWithoutArgs(this, &AudioPlayerTest::wakeOnAcquireChannel)); @@ -2220,11 +2587,11 @@ TEST_F(AudioPlayerTest, testPlayRequestor) { m_audioPlayer->CapabilityAgent::preHandleDirective(playDirective, std::move(m_mockDirectiveHandlerResult)); m_audioPlayer->CapabilityAgent::handleDirective(MESSAGE_ID_TEST); - ASSERT_EQ(std::future_status::ready, m_wakeAcquireChannelFuture.wait_for(WAIT_TIMEOUT)); + ASSERT_EQ(std::future_status::ready, m_wakeAcquireChannelFuture.wait_for(MY_WAIT_TIMEOUT)); - m_audioPlayer->onFocusChanged(FocusState::FOREGROUND); + m_audioPlayer->onFocusChanged(FocusState::FOREGROUND, MixingBehavior::PRIMARY); - ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PLAYING, WAIT_TIMEOUT)); + ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PLAYING, MY_WAIT_TIMEOUT)); auto playRequestor = m_testAudioPlayerObserver->getPlayRequestorObject(); EXPECT_EQ(playRequestor.type, PLAY_REQUESTOR_TYPE_ALERT); @@ -2256,7 +2623,7 @@ void AudioPlayerTest::verifyMessageOrder( { bool result; std::unique_lock lock(m_mutex); - result = m_messageSentTrigger.wait_for(lock, WAIT_TIMEOUT, [orderedMessageList, &nextIndex] { + result = m_messageSentTrigger.wait_for(lock, MY_WAIT_TIMEOUT, [orderedMessageList, &nextIndex] { if (nextIndex == orderedMessageList.size()) { return true; } @@ -2279,8 +2646,9 @@ TEST_F(AudioPlayerTest, test_playbackFinishedMessageOrder_1Player) { expectedMessages.push_back(PLAYBACK_NEARLY_FINISHED_NAME); expectedMessages.push_back(PLAYBACK_FINISHED_NAME); - verifyMessageOrder( - expectedMessages, 2, [this] { m_audioPlayer->onPlaybackFinished(m_mockMediaPlayer->getCurrentSourceId()); }); + verifyMessageOrder(expectedMessages, 2, [this] { + m_audioPlayer->onPlaybackFinished(m_mockMediaPlayer->getCurrentSourceId(), DEFAULT_MEDIA_PLAYER_STATE); + }); } TEST_F(AudioPlayerTest, test_playbackFinishedMessageOrder_2Players) { @@ -2295,8 +2663,9 @@ TEST_F(AudioPlayerTest, test_playbackFinishedMessageOrder_2Players) { verifyMessageOrder(expectedMessages, 0, [this] { sendPlayDirective(); }); expectedMessages.push_back(PLAYBACK_FINISHED_NAME); - verifyMessageOrder( - expectedMessages, 3, [this] { m_audioPlayer->onPlaybackFinished(m_mockMediaPlayer->getCurrentSourceId()); }); + verifyMessageOrder(expectedMessages, 3, [this] { + m_audioPlayer->onPlaybackFinished(m_mockMediaPlayer->getCurrentSourceId(), DEFAULT_MEDIA_PLAYER_STATE); + }); } TEST_F(AudioPlayerTest, test_playbackStoppedMessageOrder_1Player) { @@ -2310,8 +2679,9 @@ TEST_F(AudioPlayerTest, test_playbackStoppedMessageOrder_1Player) { verifyMessageOrder(expectedMessages, 0, [this] { sendPlayDirective(); }); expectedMessages.push_back(PLAYBACK_STOPPED_NAME); - verifyMessageOrder( - expectedMessages, 2, [this] { m_audioPlayer->onPlaybackStopped(m_mockMediaPlayer->getCurrentSourceId()); }); + verifyMessageOrder(expectedMessages, 2, [this] { + m_audioPlayer->onPlaybackStopped(m_mockMediaPlayer->getCurrentSourceId(), DEFAULT_MEDIA_PLAYER_STATE); + }); } TEST_F(AudioPlayerTest, test_playbackStoppedMessageOrder_2Players) { @@ -2326,8 +2696,9 @@ TEST_F(AudioPlayerTest, test_playbackStoppedMessageOrder_2Players) { verifyMessageOrder(expectedMessages, 0, [this] { sendPlayDirective(); }); expectedMessages.push_back(PLAYBACK_STOPPED_NAME); - verifyMessageOrder( - expectedMessages, 3, [this] { m_audioPlayer->onPlaybackStopped(m_mockMediaPlayer->getCurrentSourceId()); }); + verifyMessageOrder(expectedMessages, 3, [this] { + m_audioPlayer->onPlaybackStopped(m_mockMediaPlayer->getCurrentSourceId(), DEFAULT_MEDIA_PLAYER_STATE); + }); } } // namespace test diff --git a/CapabilityAgents/AudioPlayer/test/ProgressTimerTest.cpp b/CapabilityAgents/AudioPlayer/test/ProgressTimerTest.cpp index 1ad5e9f8..2aa2e26f 100644 --- a/CapabilityAgents/AudioPlayer/test/ProgressTimerTest.cpp +++ b/CapabilityAgents/AudioPlayer/test/ProgressTimerTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018-2020 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. @@ -157,7 +157,7 @@ TEST_F(ProgressTimerTest, test_noDelayOrInterval) { EXPECT_CALL(*(m_mockContext.get()), onProgressReportDelayElapsed()).Times(0); EXPECT_CALL(*(m_mockContext.get()), onProgressReportIntervalElapsed()).Times(0); - m_timer.init(m_mockContext, ProgressTimer::NO_DELAY, ProgressTimer::NO_INTERVAL); + m_timer.init(m_mockContext, ProgressTimer::getNoDelay(), ProgressTimer::getNoInterval()); play(); std::this_thread::sleep_for(MILLIS_100); @@ -169,7 +169,7 @@ TEST_F(ProgressTimerTest, test_zeroInterval) { EXPECT_CALL(*(m_mockContext.get()), onProgressReportDelayElapsed()).Times(0); EXPECT_CALL(*(m_mockContext.get()), onProgressReportIntervalElapsed()).Times(0); - m_timer.init(m_mockContext, ProgressTimer::NO_DELAY, std::chrono::milliseconds{0}); + m_timer.init(m_mockContext, ProgressTimer::getNoDelay(), std::chrono::milliseconds{0}); play(); std::this_thread::sleep_for(MILLIS_100); @@ -184,7 +184,7 @@ TEST_F(ProgressTimerTest, test_justDelay) { EXPECT_CALL(*(m_mockContext.get()), onProgressReportDelayElapsed()).Times(1); EXPECT_CALL(*(m_mockContext.get()), onProgressReportIntervalElapsed()).Times(0); - m_timer.init(m_mockContext, MILLIS_10, ProgressTimer::NO_INTERVAL); + m_timer.init(m_mockContext, MILLIS_10, ProgressTimer::getNoInterval()); play(); std::this_thread::sleep_for(MILLIS_100); @@ -207,7 +207,7 @@ TEST_F(ProgressTimerTest, test_justInterval) { EXPECT_CALL(*(m_mockContext.get()), onProgressReportDelayElapsed()).Times(0); EXPECT_CALL(*(m_mockContext.get()), onProgressReportIntervalElapsed()).WillRepeatedly(Invoke(notifyOnTenReports)); - m_timer.init(m_mockContext, ProgressTimer::NO_DELAY, MILLIS_10); + m_timer.init(m_mockContext, ProgressTimer::getNoDelay(), MILLIS_10); play(); ASSERT_TRUE(gotTenReports.waitFor(FAIL_TIMEOUT)); @@ -308,7 +308,7 @@ TEST_F(ProgressTimerTest, test_resumeDoesNotRepeat) { EXPECT_CALL(*(m_mockContext.get()), onProgressReportDelayElapsed()).Times(1); EXPECT_CALL(*(m_mockContext.get()), onProgressReportIntervalElapsed()).Times(0); - m_timer.init(m_mockContext, MILLIS_10, ProgressTimer::NO_INTERVAL); + m_timer.init(m_mockContext, MILLIS_10, ProgressTimer::getNoInterval()); play(); std::this_thread::sleep_for(MILLIS_100); diff --git a/CapabilityAgents/Bluetooth/CMakeLists.txt b/CapabilityAgents/Bluetooth/CMakeLists.txt index 69ef2860..a9b6894f 100644 --- a/CapabilityAgents/Bluetooth/CMakeLists.txt +++ b/CapabilityAgents/Bluetooth/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.0) project(Bluetooth LANGUAGES CXX) -include(../../build/BuildDefaults.cmake) +include(${AVS_CORE}/build/BuildDefaults.cmake) add_subdirectory("src") add_subdirectory("test") diff --git a/CapabilityAgents/Bluetooth/include/Bluetooth/BasicDeviceConnectionRule.h b/CapabilityAgents/Bluetooth/include/Bluetooth/BasicDeviceConnectionRule.h new file mode 100644 index 00000000..b47006ce --- /dev/null +++ b/CapabilityAgents/Bluetooth/include/Bluetooth/BasicDeviceConnectionRule.h @@ -0,0 +1,70 @@ +/* + * Copyright 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 ALEXA_CLIENT_SDK_CAPABILITYAGENTS_BLUETOOTH_INCLUDE_BLUETOOTH_BASICDEVICECONNECTIONRULE_H_ +#define ALEXA_CLIENT_SDK_CAPABILITYAGENTS_BLUETOOTH_INCLUDE_BLUETOOTH_BASICDEVICECONNECTIONRULE_H_ + +#include + +namespace alexaClientSDK { +namespace capabilityAgents { +namespace bluetooth { + +using namespace avsCommon::sdkInterfaces::bluetooth; +/** + * A class represents the basic connection rule the following Bluetooth device needs to follow: + * 1) DeviceCategory::PHONE + * 2) DeviceCategory::AUDIO_VIDEO + * 3) DeviceCategory::OTHER + * 4) DeviceCategory::UNKNOWN + * + * This rule is created by default and enforces Bluetooth devices falling into the above DeviceCategory to follow. + * Any rule change for a certain DeviceCategory in the above list might need to refactor the Bluetooth CapabilityAgent. + */ +class BasicDeviceConnectionRule : public BluetoothDeviceConnectionRuleInterface { +public: + /** + * A factory method to create a new instance of @c BasicDeviceConnectionRule. + * @return an instance of @c BasicConnectionRule. + */ + static std::shared_ptr create(); + + /// @name BluetoothDeviceConnectionRuleInterface + /// @{ + bool shouldExplicitlyConnect() override; + bool shouldExplicitlyDisconnect() override; + std::set> devicesToDisconnect( + std::map>> connectedDevices) override; + std::set getDeviceCategories() override; + std::set getDependentProfiles() override; + /// @} + +private: + /** + * Constructor. + */ + BasicDeviceConnectionRule(); + + /// Represent the set of categories used in the rule. + std::set m_categories; + + /// Represent the set of profiles the categories rely on. + std::set m_profiles; +}; + +} // namespace bluetooth +} // namespace capabilityAgents +} // namespace alexaClientSDK +#endif // ALEXA_CLIENT_SDK_CAPABILITYAGENTS_BLUETOOTH_INCLUDE_BLUETOOTH_BASICDEVICECONNECTIONRULE_H_ diff --git a/CapabilityAgents/Bluetooth/include/Bluetooth/Bluetooth.h b/CapabilityAgents/Bluetooth/include/Bluetooth/Bluetooth.h index fe221f25..77324c84 100644 --- a/CapabilityAgents/Bluetooth/include/Bluetooth/Bluetooth.h +++ b/CapabilityAgents/Bluetooth/include/Bluetooth/Bluetooth.h @@ -1,5 +1,5 @@ /* - * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018-2020 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. @@ -18,10 +18,10 @@ #include #include +#include #include #include #include -#include #include #include @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -42,16 +43,20 @@ #include #include #include +#include #include #include +#include #include #include #include -#include "Bluetooth/BluetoothAVRCPTransformer.h" -#include "Bluetooth/BluetoothStorageInterface.h" #include #include +#include "Bluetooth/BluetoothEventState.h" +#include "Bluetooth/BluetoothMediaInputTransformer.h" +#include "Bluetooth/BluetoothStorageInterface.h" + namespace alexaClientSDK { namespace capabilityAgents { namespace bluetooth { @@ -62,11 +67,14 @@ namespace bluetooth { * * -# The connectivity of devices. This includes scanning, pairing and connecting. * -# The management of profiles. This includes: - * media control (AVRCP, Audio/Video Remote Control Profile) and - * media playback (A2DP, Advanced Audio Distribution Profile). + * media control (AVRCP, Audio/Video Remote Control Profile) + * media playback (A2DP, Advanced Audio Distribution Profile) + * Human Interface Device Profile + * Serial Port Profile and + * Hands-Free Profile. * * The Bluetooth agent will handle directives from AVS and requests from peer devices. Examples include - * pairing and connection requests, as we as media playback requests. Some examples of this are: + * pairing and connection requests, as well as media playback requests. Some examples of this are: * * - "Alexa, connect". * - Enabling discovery through the companion app. @@ -74,14 +82,11 @@ namespace bluetooth { * - "Alexa next". * * Connectivity is defined as when two devices have paired and established connections of all applicable - * services (A2DP, AVRCP, etc). Alexa does not support multiple connected multimedia devices. If a device is - * currently connected, attempting to connect a second device should force a disconnect on the - * currently connected device. - * - * At this time, the agent does not enforce the disconnect of the currently connected device. - * It is theoretically possible to connect two devices simultaneously, but the behavior is undefined. - * It is advised to disconnect a currently connected device before connecting a new one. - * Enforcement of this will be available in an upcoming release. + * services (A2DP, AVRCP, etc). Alexa supports multiple connected multimedia devices but doesn't support multiple A2DP + * connected devices. The agent enforces the connected devices to follow some Bluetooth device connection rules based on + * DeviceCategory. For example, If a A2DP device is currently connected, attempting to connect a second A2DP device + * should force a disconnect on the currently connected device. However, if a A2DP device is currently connected, + * attempting to connected a SPP/HID device should not cause a disconnect on the currently connected device. * * Interfaces in AVSCommon/SDKInterfaces/Bluetooth can be implemented for customers * who wish to use their own Bluetooth stack. The Bluetooth agent operates based on events. @@ -94,6 +99,9 @@ namespace bluetooth { * * -# AVRCP (Controller, Target) * -# A2DP (Sink, Source) + * -# HFP + * -# HID + * -# SPP */ class Bluetooth : public std::enable_shared_from_this @@ -127,6 +135,59 @@ public: ACTIVE }; + /** + * An enum that represents how the Bluetooth class expects to lose focus. + */ + enum class FocusTransitionState { + /// Focus in Bluetooth class is lost because it explicitly released focus. + INTERNAL, + + /** + * Focus in Bluetooth class that will be lost because it explicitly released focus. + * This state prevents foreground or background focus changes from setting the state to EXTERNAL before the + * none focus change has had the chance to set the state to INTERNAL. + */ + PENDING_INTERNAL, + + /// Focus in Bluetooth class is lost because another class has taken focus. + EXTERNAL + }; + + /** + * An enum that is used to represent the Bluetooth scanning state and if a state change should result in a scan + * report being sent to the Alexa service. + */ + enum class ScanningTransitionState { + /** + * The device is currently scanning. + * + * Any state change should result in sending a scan report. + * + * This state is set when a SCAN_DEVICES directive is sent from the Alexa service. + */ + ACTIVE, + + /** + * The device is not scanning. + * + * A state change to inactive should not result in sending a scan report. + * + * This state is set when a EXIT_DISCOVERABLE_MODE directive is sent or scan mode is disabled as part of the + * PAIR_DEVICES directive. + */ + PENDING_INACTIVE, + + /** + * The device is not scanning. + * + * A state change to inactive should not result in sending a scan report. + * + * This state is set when a state change to inactive is recieved and the previous state was + * PENDING_INACTIVE. + */ + INACTIVE + }; + /** * Creates an instance of the Bluetooth capability agent. * @@ -139,7 +200,9 @@ public: * @param eventBus A bus to abstract Bluetooth stack specific messages. * @param mediaPlayer The Media Player which will handle playback. * @param customerDataManager Object that will track the CustomerDataHandler. - * @param avrcpTransformer Transforms incoming AVRCP commands if supported. + * @param enabledConnectionRules The set of devices connection rules enabled by the Bluetooth stack from + * customers. + * @param mediaInputTransformer Transforms incoming Media commands if supported. */ static std::shared_ptr create( std::shared_ptr contextManager, @@ -151,7 +214,9 @@ public: std::shared_ptr eventBus, std::shared_ptr mediaPlayer, std::shared_ptr customerDataManager, - std::shared_ptr avrcpTransformer = nullptr); + std::unordered_set> + enabledConnectionRules, + std::shared_ptr mediaInputTransformer = nullptr); /// @name CapabilityAgent Functions /// @{ @@ -160,7 +225,7 @@ public: void preHandleDirective(std::shared_ptr info) override; void handleDirective(std::shared_ptr info) override; void cancelDirective(std::shared_ptr info) override; - void onFocusChanged(avsCommon::avs::FocusState newFocus) override; + void onFocusChanged(avsCommon::avs::FocusState newFocus, avsCommon::avs::MixingBehavior behavior) override; /// @} /// @name CapabilityConfigurationInterface Functions @@ -181,13 +246,23 @@ public: /// @name MediaPlayerObserverInterface Functions /// @{ - void onPlaybackStarted(avsCommon::utils::mediaPlayer::MediaPlayerObserverInterface::SourceId id) override; - void onPlaybackStopped(avsCommon::utils::mediaPlayer::MediaPlayerObserverInterface::SourceId id) override; - void onPlaybackFinished(avsCommon::utils::mediaPlayer::MediaPlayerObserverInterface::SourceId id) override; + void onFirstByteRead( + avsCommon::utils::mediaPlayer::MediaPlayerObserverInterface::SourceId id, + const avsCommon::utils::mediaPlayer::MediaPlayerState& state) override; + void onPlaybackStarted( + avsCommon::utils::mediaPlayer::MediaPlayerObserverInterface::SourceId id, + const avsCommon::utils::mediaPlayer::MediaPlayerState& state) override; + void onPlaybackStopped( + avsCommon::utils::mediaPlayer::MediaPlayerObserverInterface::SourceId id, + const avsCommon::utils::mediaPlayer::MediaPlayerState& state) override; + void onPlaybackFinished( + avsCommon::utils::mediaPlayer::MediaPlayerObserverInterface::SourceId id, + const avsCommon::utils::mediaPlayer::MediaPlayerState& state) override; void onPlaybackError( avsCommon::utils::mediaPlayer::MediaPlayerObserverInterface::SourceId id, const avsCommon::utils::mediaPlayer::ErrorType& type, - std::string error) override; + std::string error, + const avsCommon::utils::mediaPlayer::MediaPlayerState& state) override; /// @} /// @name CustomerDataHandler Functions @@ -228,7 +303,9 @@ private: * @param eventBus A bus to abstract Bluetooth stack specific messages. * @param mediaPlayer The Media Player which will handle playback. * @param customerDataManager Object that will track the CustomerDataHandler. - * @param avrcpTransformer Transforms incoming AVRCP commands. + * @param enabledConnectionRules The set of devices connection rules enabled by the Bluetooth stack from + * customers. + * @param mediaInputTransformer Transforms incoming Media commands. */ Bluetooth( std::shared_ptr contextManager, @@ -240,7 +317,9 @@ private: std::shared_ptr eventBus, std::shared_ptr mediaPlayer, std::shared_ptr customerDataManager, - std::shared_ptr avrcpTransformer); + std::unordered_set> + enabledConnectionRules, + std::shared_ptr mediaInputTransformer); /** * Initializes the agent. @@ -259,6 +338,20 @@ private: /// Helper function to update the context. void executeUpdateContext(); + /** + * Helper function to extract AVS compliant profiles. This returns a rapidjson node + * containing an array of supported profiles. + * + * @param device The device. + * @param allocator The allocator which will be used to create @c supportedProfiles. + * @param[out] supportedProfiles A rapidjson node containing the supported profiles. + * @return A bool indicating success. + */ + bool extractAvsProfiles( + std::shared_ptr device, + rapidjson::Document::AllocatorType& allocator, + rapidjson::Value* supportedProfiles); + /** * Marks the directive as completed. * @@ -289,7 +382,7 @@ private: void executeEnterForeground(); /// A state transition function for entering the background. - void executeEnterBackground(); + void executeEnterBackground(avsCommon::avs::MixingBehavior behavior); /// A state transition function for entering the none state. void executeEnterNone(); @@ -306,33 +399,43 @@ private: * Puts the device into the desired scan mode. * * @param scanning A bool indicating whether it should be scanning. + * @param shouldReport A bool that indicates if the scan report should be reported to the Alexa service. * @return A bool indicating success. */ - bool executeSetScanMode(bool scanning); + bool executeSetScanMode(bool scanning, bool shouldReport = true); /** - * Pair with the device matching the given uuid. + * Pair with the devices matching the given uuids. * - * @param uuid The uuid associated with the device. - * @return A bool indicating success. + * @param uuids The uuids associated with the devices. + * @return A bool indicating that all devices have been paired. */ - bool executePairDevice(const std::string& uuid); + bool executePairDevices(const std::unordered_set& uuids); /** - * Unpair with the device matching the given uuid. + * Unpair with the devices matching the given uuids. * - * @param uuid The uuid associated with the device. - * @return A bool indicating success. + * @param uuids The uuids associated with the devices. + * @return A bool indicating that all devices have been unpaired. */ - bool executeUnpairDevice(const std::string& uuid); + bool executeUnpairDevices(const std::unordered_set& uuids); /** - * Connect with the device matching the given uuid. This will connect all available services between + * Set Device Category with the device matching the given uuid. + * + * @param uuidCategoryMap Map of of the devices. + * @return Map of of devices that failed to update. + */ + std::map executeSetDeviceCategories( + const std::map& uuidCategoryMap); + + /** + * Connect with the devices matching the given uuids. This will connect all available services between * the two devices. * - * @param uuid The uuid associated with the device. + * @param uuids The uuids associated with the devices. */ - void executeConnectByDeviceId(const std::string& uuid); + void executeConnectByDeviceIds(const std::unordered_set& uuids); /** * Connect with the most recently connected device that supports the given profile. @@ -345,26 +448,32 @@ private: void executeConnectByProfile(const std::string& profileName, const std::string& profileVersion); /** - * Disconnect with the device matching the given uuid. This will disconnect all available services between + * Disconnect with the devices matching the given uuids. This will disconnect all available services between * the two devices. * - * @param uuid The uuid associated with the device. + * @param uuids The uuids associated with the devices. */ - void executeDisconnectDevice(const std::string& uuid); + void executeDisconnectDevices(const std::unordered_set& uuids); /** * Helper function that encapsulates disconnect logic. * + * @param device The disconnected device. * @param requester The @c Requester who initiated the disconnect. */ - void executeOnDeviceDisconnect(avsCommon::avs::Requester requester); + void executeOnDeviceDisconnect( + std::shared_ptr device, + avsCommon::avs::Requester requester); /** * Helper function that encapsulates connect logic. * - * @param requester The @c Requester who initiated the disconnect. + * @param device The connected device. + * @param shouldNotifyConnection A bool that indicates if observers should be notified the device connection. */ - void executeOnDeviceConnect(std::shared_ptr device); + void executeOnDeviceConnect( + std::shared_ptr device, + bool shouldNotifyConnection = true); /** * Helper function to abstract shared logic in pairing/unpairing/connecting/disconnecting operations. @@ -377,20 +486,36 @@ private: std::function( std::shared_ptr&)> function); - /// Send a play command to the activeDevice. - void executePlay(); - - /// Send a stop command to the activeDevice. - void executeStop(); - - /// Send a next command to the activeDevice. - void executeNext(); - - /// Send a previous command to the activeDevice. - void executePrevious(); + /** + * Send a play command to the device. + * + * @param device The device to play. + */ + void executePlay(std::shared_ptr device); /** - * Drain the command queue of @c AVRCPCommands. We use a queue so we can process the commands + * Send a stop command to the device. + * + * @param device The device to stop. + */ + void executeStop(std::shared_ptr device); + + /** + * Send a next command to the device. + * + * @param device The device to play next. + */ + void executeNext(std::shared_ptr device); + + /** + * Send a previous command to the device. + * + * @param device The device to play previous. + */ + void executePrevious(std::shared_ptr device); + + /** + * Drain the command queue of @c MediaCommands. We use a queue so we can process the commands * after the Bluetooth agent has entered the foreground. */ void executeDrainQueue(); @@ -441,6 +566,23 @@ private: std::shared_ptr retrieveDeviceByUuid( const std::string& uuid); + /** + * Retrieve the @DeviceCategory by its UUID. + * + * @param uuid The generated UUID associated with a device. + * @param category The device category associated with a device. + * @return whether a @c DeviceCategory is successfully obtained by retrieval. + */ + bool retrieveDeviceCategoryByUuid(const std::string& uuid, DeviceCategory* category); + + /** + * Retrieve the @BluetoothDeviceConnectionRuleInterface by the device uuid. + * @param uuid the UUID of the device. + * @return The @BluetoothDeviceConnectionRuleInterface if found, otherwise a nullptr. + */ + std::shared_ptr + retrieveConnectionRuleByUuid(const std::string& uuid); + /** * Retrieve the UUID by its MAC address. If no UUID is found, then one will be generated and inserted. * @@ -451,9 +593,25 @@ private: */ bool retrieveUuid(const std::string& mac, std::string* uuid); + /** + * Retrieve a set of UUIDs from the payload. + * + * @param payload The payload sent down. + * @return A set of UUIDs in the payload. + */ + std::unordered_set retrieveUuidsFromConnectionPayload(const rapidjson::Document& payload); + /// Clears the databse of mac,uuid that are not known by the @BluetoothDeviceManager. void clearUnusedUuids(); + /** + * Event immediately submitted to the executor and is sent. + * + * @param eventName The name of the event. + * @param eventPayload The payload of the event. + */ + void executeSendEvent(const std::string& eventName, const std::string& eventPayload); + /** * Most events require the context, this method queues the event and requests the context. * Once the context is available in onContextAvailable, the event will be dequeued and sent. @@ -469,7 +627,7 @@ private: * @param devices A list of devices. * @param hasMore A bool indicating if we're still looking for more devices. */ - void executeSendScanDevicesUpdated( + void executeSendScanDevicesReport( const std::list>& devices, bool hasMore); @@ -483,45 +641,78 @@ private: void executeSendEnterDiscoverableModeFailed(); /** - * Sends an event to indicate that pairing with a device succeeded. + * Sends an event to indicate that pairing with devices succeeded. * - * @param device The paired device. + * @param devices The paired devices. */ - void executeSendPairDeviceSucceeded( - std::shared_ptr device); - - /// Sends an event to indicate that a device pairing attempt failed. - void executeSendPairDeviceFailed(); + void executeSendPairDevicesSucceeded( + const std::unordered_set>& + devices); /** - * Sends an event to indicate that unpairing with a device succeeded. + * Sends a failed pair event. * - * @param device The unpaired device. + * @param eventName The pair event name. + * @param uuids The uuids of devices. */ - void executeSendUnpairDeviceSucceeded( - std::shared_ptr device); - - /// Sends an event to indicate that unpairing with a device failed. - void executeSendUnpairDeviceFailed(); + void executeSendPairFailedEvent(const std::string& eventName, const std::unordered_set& uuids); /** - * Sends an event to indicate that connecting with a device by uuid succeeded. + * Sends an event to indicate that devices pairing attempt failed. * - * @param device The device. + * @param uuids The uuids of devices. + */ + void executeSendPairDevicesFailed(const std::unordered_set& uuids); + + /** + * Sends an event to indicate that unpairing with devices succeeded. + * + * @param devices The unpaired devices. + */ + void executeSendUnpairDevicesSucceeded( + const std::unordered_set>& + devices); + + /** + * Sends an event to indicate that unpairing with devices failed. + * + * @param uuids The uuids of devices. + */ + void executeSendUnpairDevicesFailed(const std::unordered_set& uuids); + + /** + * Sends an event to indicate that setting device category for each device succeeded. + * + * @param uuidCategoryMap Map of of the devices. + */ + void executeSetDeviceCategoriesSucceeded(const std::map& uuidCategoryMap); + + /** + * Sends an event to indicate that setting device category for each device failed. + * + * @param uuidCategoryMap Map of of the devices. + */ + void executeSetDeviceCategoriesFailed(const std::map& uuidCategoryMap); + + /** + * Sends an event to indicate that connecting with devices by uuids succeeded. + * + * @param devices The devices. * @param requester The @c Requester who initiated the operation. */ - void executeSendConnectByDeviceIdSucceeded( - std::shared_ptr device, + void executeSendConnectByDeviceIdsSucceeded( + const std::unordered_set>& + devices, avsCommon::avs::Requester requester); /** - * Sends an event to indicate that connecting with a device by uuid failed. + * Sends an event to indicate that connecting with devices by uuids failed. * - * @param device The device. + * @param uuids The uuids of devices. * @param requester The @c Requester who initiated the operation. */ - void executeSendConnectByDeviceIdFailed( - std::shared_ptr device, + void executeSendConnectByDeviceIdsFailed( + const std::unordered_set& uuids, avsCommon::avs::Requester requester); /** @@ -546,48 +737,121 @@ private: void executeSendConnectByProfileFailed(const std::string& profileName, avsCommon::avs::Requester requester); /** - * Sends an event to indicate that disconnecting with a device succeeded. + * Sends an event to indicate that disconnecting with devices succeeded. * - * @param device The device. + * @param devices The devices. * @param requester Whether this was initiated by the CLOUD or DEVICE. */ - void executeSendDisconnectDeviceSucceeded( - std::shared_ptr device, + void executeSendDisconnectDevicesSucceeded( + const std::unordered_set>& + devices, avsCommon::avs::Requester requester); /** - * Sends an event to indicate that disconnecting with a device failed. + * Sends a connection failed event. * - * @param device The device. + * @param eventName The event name. + * @param uuids The uuids of devices. * @param requester Whether this was initiated by the CLOUD or DEVICE. */ - void executeSendDisconnectDeviceFailed( - std::shared_ptr device, + void executeSendConnectFailedEvent( + const std::string& eventName, + const std::unordered_set& uuids, avsCommon::avs::Requester requester); - /// Sends an event to indicate we successfully sent an AVRCP play to the target. - void executeSendMediaControlPlaySucceeded(); + /** + * Sends an event to indicate that disconnecting with devices failed. + * + * @param uuids The uuids of devices. + * @param requester Whether this was initiated by the CLOUD or DEVICE. + */ + void executeSendDisconnectDevicesFailed( + const std::unordered_set& uuids, + avsCommon::avs::Requester requester); - /// Sends an event to indicate we failed to send an AVRCP play to the target. - void executeSendMediaControlPlayFailed(); + /** + * Sends an AVRCP event. + * + * @param eventName The AVRCP media event name. + * @param device The device. + */ + void executeSendMediaControlEvent( + const std::string& eventName, + std::shared_ptr device); - /// Sends an event to indicate we successfully sent an AVRCP pause to the target. - void executeSendMediaControlStopSucceeded(); + /** + * Sends an event to indicate we successfully sent an AVRCP play to the target. + * + * @param device The device. + */ + void executeSendMediaControlPlaySucceeded( + std::shared_ptr device); - /// Sends an event to indicate we failed to send an AVRCP pause to the target. - void executeSendMediaControlStopFailed(); + /** + * Sends an event to indicate we failed to send an AVRCP play to the target. + * + * @param device The device. + */ + void executeSendMediaControlPlayFailed( + std::shared_ptr device); - /// Sends an event to indicate we successfully sent an AVRCP next to the target. - void executeSendMediaControlNextSucceeded(); + /** + * Sends an event to indicate we successfully sent an AVRCP pause to the target. + * + * @param device The device. + */ + void executeSendMediaControlStopSucceeded( + std::shared_ptr device); - /// Sends an event to indicate we failed to send an AVRCP next to the target. - void executeSendMediaControlNextFailed(); + /** + * Sends an event to indicate we failed to send an AVRCP pause to the target. + * + * @param device The device. + */ + void executeSendMediaControlStopFailed( + std::shared_ptr device); - /// Sends an event to indicate we successfully sent an AVRCP previous to the target. - void executeSendMediaControlPreviousSucceeded(); + /** + * Sends an event to indicate we successfully sent an AVRCP next to the target. + * + * @param device The device. + */ + void executeSendMediaControlNextSucceeded( + std::shared_ptr device); - /// Sends an event to indicate we failed to send an AVRCP previous to the target. - void executeSendMediaControlPreviousFailed(); + /** + * Sends an event to indicate we failed to send an AVRCP next to the target. + * + * @param device The device. + */ + void executeSendMediaControlNextFailed( + std::shared_ptr device); + + /** + * Sends an event to indicate we successfully sent an AVRCP previous to the target. + * + * @param device The device. + */ + void executeSendMediaControlPreviousSucceeded( + std::shared_ptr device); + + /** + * Sends an event to indicate we failed to send an AVRCP previous to the target. + * + * @param device The device. + */ + void executeSendMediaControlPreviousFailed( + std::shared_ptr device); + + /** + * Sends a media streaming event. + * + * @param eventName The streaming event name. + * @param device The device. + */ + void executeSendStreamingEvent( + const std::string& eventName, + std::shared_ptr device); /** * Sends an event that we have started streaming. @@ -611,6 +875,67 @@ private: ObserverInterface::DeviceAttributes generateDeviceAttributes( std::shared_ptr device); + /** + * Helper function to get a service from a device. + * + * @tparam ServiceType The type of the @c BluetoothServiceInterface. + * @param device the activeDevice. + * @return The instance of the service if successful, else nullptr. + */ + template + std::shared_ptr getService( + std::shared_ptr device); + + /** + * Insert the @c BluetoothEventState into m_bluetoothEventStates to keep track of a bunch of succeeded events needed + * to send to cloud. + * @param device The device. + * @param state The @c DeviceState of the @c BluetoothEventState. + * @param requester The @c Requester of the @c BluetoothEventState. + * @param profile The profile name of the @c BluetoothEventState. + */ + void executeInsertBluetoothEventState( + std::shared_ptr device, + avsCommon::sdkInterfaces::bluetooth::DeviceState state, + avsCommon::utils::Optional requester, + avsCommon::utils::Optional profileName); + + /** + * Remove the @c BluetoothEventState from m_bluetoothEventStates to keep track of a bunch of succeeded events needed + * to send to cloud. + * @param device The device. + * @param state The @c DeviceState of the @c BluetoothEventState. + * @return The @c BluetoothEventState removed from m_bluetoothEvenetStates. + */ + std::shared_ptr executeRemoveBluetoothEventState( + std::shared_ptr device, + avsCommon::sdkInterfaces::bluetooth::DeviceState state); + + /** + * This method is used to restrict A2DP profiles of all paired devices. + */ + void executeRestrictA2DPDevices(); + + /** + * This method is used to unrestrict all previously A2DP restricted devices, and reconnect to previous + * active device. + */ + void executeUnrestrictA2DPDevices(); + + /** + * Acquires focus from the focus manager for the Bluetooth CA. + * + * @param the name of the calling method for logging. + */ + void executeAcquireFocus(const std::string& callingMethodName = ""); + + /** + * Releases focus from the focus manager for the Bluetooth CA. + * + * @param the name of the calling method for logging. + */ + void executeReleaseFocus(const std::string& callingMethodName = ""); + /// Set of capability configurations that will get published using DCF std::unordered_set> m_capabilityConfigurations; @@ -630,6 +955,16 @@ private: */ StreamingState m_streamingState; + /** + * The current state transition that the Bluetooth CA expects to experience when losing focus. + */ + FocusTransitionState m_focusTransitionState; + + /** + * The current scanning transition state. This should only be accessed from a method running on the executor. + */ + ScanningTransitionState m_scanningTransitionState; + /// The current @c FocusState of the device. avsCommon::avs::FocusState m_focusState; @@ -639,17 +974,20 @@ private: /// The @c BluetoothDeviceManagerInterface instance responsible for device management. std::shared_ptr m_deviceManager; - /// A queue to store AVRCP commands. - std::deque m_cmdQueue; + /// A queue to store Media commands. + std::deque m_cmdQueue; /// An event queue used to store events which need to be sent. The pair is . std::queue> m_eventQueue; - /// Keeps track of last paired device to prevent sending duplicate events. - std::string m_lastPairMac; + /// The current activeA2DPDevice. This is the one that is connected and sending media via A2DP. + std::shared_ptr m_activeA2DPDevice; - /// The current activeDevice. This is the one that is connected and sending media via A2DP. - std::shared_ptr m_activeDevice; + /// The cached activeDevice. This is used to help to reconnect to previous connected A2DP device. + std::shared_ptr m_disabledA2DPDevice; + + /// The cached restricted device list. This is used to help to unrestricted previous paired A2DP devices. + std::vector> m_restrictedDevices; /// The MediaPlayer responsible for media playback. std::shared_ptr m_mediaPlayer; @@ -660,8 +998,8 @@ private: /// An eventbus used to abstract Bluetooth stack specific messages. std::shared_ptr m_eventBus; - /// Transforms incoming AVRCP commands. - std::shared_ptr m_avrcpTransformer; + /// Transforms incoming Media commands. + std::shared_ptr m_mediaInputTransformer; /// The A2DP media stream. std::shared_ptr m_mediaStream; @@ -672,11 +1010,27 @@ private: /// A writer to write the A2DP stream buffers into the InProcessAttachment. std::shared_ptr m_mediaAttachmentWriter; - /// An executor used for serializing requests on the Bluetooth agent's own thread of execution. - avsCommon::utils::threading::Executor m_executor; + /// A reader that reads the InProcessAttachment. + std::shared_ptr m_mediaAttachmentReader; /// Set of bluetooth device observers that will get notified on connects or disconnects. std::unordered_set> m_observers; + + /// Map of device connection rules + std::map< + DeviceCategory, + std::shared_ptr> + m_enabledConnectionRules; + + /// Map of > connected Bluetooth devices. + std::map>> + m_connectedDevices; + + /// Map of > used to keep track of Bluetooth event state needed to send to cloud. + std::map>> m_bluetoothEventStates; + + /// An executor used for serializing requests on the Bluetooth agent's own thread of execution. + avsCommon::utils::threading::Executor m_executor; }; /** @@ -702,6 +1056,79 @@ inline std::string streamingStateToString(Bluetooth::StreamingState state) { return "UNKNOWN"; } +/** + * Overload for the @c StreamingState enum. This will write the @c StreamingState as a string to the provided stream. + * + * @param stream An ostream to send the @c StreamingState as a string. + * @param state The @c StreamingState to convert. + * @return The stream. + */ +inline std::ostream& operator<<(std::ostream& stream, const Bluetooth::StreamingState state) { + return stream << streamingStateToString(state); +} + +/** + * Converts an enum @c FocusTransitionState to a string. + * + * @param state The @c FocusTransitionState. + * @return The string form of the enum. + */ +inline std::string focusTransitionStateToString(Bluetooth::FocusTransitionState state) { + switch (state) { + case Bluetooth::FocusTransitionState::INTERNAL: + return "INTERNAL"; + case Bluetooth::FocusTransitionState::PENDING_INTERNAL: + return "PENDING_INTERNAL"; + case Bluetooth::FocusTransitionState::EXTERNAL: + return "EXTERNAL"; + } + + return "UNKNOWN"; +} + +/** + * Overload for the @c FocusTransitionState enum. This will write the @c FocusTransitionState as a string to + * the provided stream. + * + * @param stream An ostream to send the @c FocusTransitionState as a string. + * @param state The @c FocusTransitionState to convert. + * @return The stream. + */ +inline std::ostream& operator<<(std::ostream& stream, const Bluetooth::FocusTransitionState state) { + return stream << focusTransitionStateToString(state); +} + +/** + * Converts an enum @c ScanningTransitionState to a string. + * + * @param state The @c ScanningTransitionState. + * @return The string form of the enum. + */ +inline std::string scanningStateToString(Bluetooth::ScanningTransitionState state) { + switch (state) { + case Bluetooth::ScanningTransitionState::ACTIVE: + return "ACTIVE"; + case Bluetooth::ScanningTransitionState::PENDING_INACTIVE: + return "PENDING_ACTIVE"; + case Bluetooth::ScanningTransitionState::INACTIVE: + return "INACTIVE"; + } + + return "UNKNWON"; +} + +/** + * Overload for the @c ScanningTransitionState enum. This will write the @c ScanningTransitionState as a string to + * the provided stream. + * + * @param stream An ostream to send the @c ScanningTransitionState as a string. + * @param state The @c ScanningTransitionState to convert. + * @return The stream. + */ +inline std::ostream& operator<<(std::ostream& stream, const Bluetooth::ScanningTransitionState state) { + return stream << scanningStateToString(state); +} + } // namespace bluetooth } // namespace capabilityAgents } // namespace alexaClientSDK diff --git a/CapabilityAgents/Bluetooth/include/Bluetooth/BluetoothEventState.h b/CapabilityAgents/Bluetooth/include/Bluetooth/BluetoothEventState.h new file mode 100644 index 00000000..376a6951 --- /dev/null +++ b/CapabilityAgents/Bluetooth/include/Bluetooth/BluetoothEventState.h @@ -0,0 +1,76 @@ +/* + * Copyright 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 ALEXA_CLIENT_SDK_CAPABILITYAGENTS_BLUETOOTH_INCLUDE_BLUETOOTH_BLUETOOTHEVENTSTATE_H_ +#define ALEXA_CLIENT_SDK_CAPABILITYAGENTS_BLUETOOTH_INCLUDE_BLUETOOTH_BLUETOOTHEVENTSTATE_H_ + +#include + +#include +#include +#include + +namespace alexaClientSDK { +namespace capabilityAgents { +namespace bluetooth { +/** + * A class represents a Bluetooth event needed to send to cloud. + */ +class BluetoothEventState { +public: + /** + * Getter for the @c DeviceState. + * + * @return The @c DeviceState of the Bluetooth Event. + */ + avsCommon::sdkInterfaces::bluetooth::DeviceState getDeviceState() const; + + /** + * Getter for the @c Requester. + * @return The @c Requester of the Bluetooth Event. + */ + avsCommon::utils::Optional getRequester() const; + /** + * Getter for the profile name. + * @return The profile name of the Bluetooth Event. + */ + avsCommon::utils::Optional getProfileName() const; + + /** + * Constructor + * @param state The @c DeviceState. + * @param requester The @c Requester. + * @param profileName The profileName. + */ + BluetoothEventState( + avsCommon::sdkInterfaces::bluetooth::DeviceState state, + avsCommon::utils::Optional requester, + avsCommon::utils::Optional profileName); + +private: + /// The @c DeviceState used to indicate the device state of the Bluetooth event. + avsCommon::sdkInterfaces::bluetooth::DeviceState m_state; + + /// The @c Requester used to indicate the event requester of the Bluetooth event. + avsCommon::utils::Optional m_requester; + + /// Used to indicate the profile name of the Bluetooth event. + avsCommon::utils::Optional m_profileName; +}; + +} // namespace bluetooth +} // namespace capabilityAgents +} // namespace alexaClientSDK +#endif // ALEXA_CLIENT_SDK_CAPABILITYAGENTS_BLUETOOTH_INCLUDE_BLUETOOTH_BLUETOOTHEVENTSTATE_H_ diff --git a/CapabilityAgents/Bluetooth/include/Bluetooth/BluetoothAVRCPTransformer.h b/CapabilityAgents/Bluetooth/include/Bluetooth/BluetoothMediaInputTransformer.h similarity index 72% rename from CapabilityAgents/Bluetooth/include/Bluetooth/BluetoothAVRCPTransformer.h rename to CapabilityAgents/Bluetooth/include/Bluetooth/BluetoothMediaInputTransformer.h index 2e629fc9..529ba408 100644 --- a/CapabilityAgents/Bluetooth/include/Bluetooth/BluetoothAVRCPTransformer.h +++ b/CapabilityAgents/Bluetooth/include/Bluetooth/BluetoothMediaInputTransformer.h @@ -1,5 +1,5 @@ /* - * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * 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. @@ -13,8 +13,8 @@ * permissions and limitations under the License. */ -#ifndef ALEXA_CLIENT_SDK_CAPABILITYAGENTS_BLUETOOTH_INCLUDE_BLUETOOTH_BLUETOOTHAVRCPTRANSFORMER_H_ -#define ALEXA_CLIENT_SDK_CAPABILITYAGENTS_BLUETOOTH_INCLUDE_BLUETOOTH_BLUETOOTHAVRCPTRANSFORMER_H_ +#ifndef ALEXA_CLIENT_SDK_CAPABILITYAGENTS_BLUETOOTH_INCLUDE_BLUETOOTH_BLUETOOTHMEDIAINPUTTRANSFORMER_H_ +#define ALEXA_CLIENT_SDK_CAPABILITYAGENTS_BLUETOOTH_INCLUDE_BLUETOOTH_BLUETOOTHMEDIAINPUTTRANSFORMER_H_ #include #include @@ -28,21 +28,21 @@ namespace capabilityAgents { namespace bluetooth { /** - * A class which will convert AVRCP commands to the related @c PlaybackRouterInterface commands. + * A class which will convert Media commands to the related @c PlaybackRouterInterface commands. */ -class BluetoothAVRCPTransformer - : public std::enable_shared_from_this +class BluetoothMediaInputTransformer + : public std::enable_shared_from_this , public avsCommon::utils::bluetooth::BluetoothEventListenerInterface { public: /** - * Creates an instance of the BluetoothAVRCPTransformer. + * Creates an instance of the BluetoothMediaInputTransformer. * - * @param eventBus The @c BluetoothEventBus in which @c AVRCPCommandReceivedEvent events will appear. - * @param playbackRouter The @c PlaybackRouterInterface in which AVRCP commands will be transformed to. + * @param eventBus The @c BluetoothEventBus in which @c MediaCommandReceivedEvent events will appear. + * @param playbackRouter The @c PlaybackRouterInterface in which Media commands will be transformed to. * * @return An instance if successful else a nullptr. */ - static std::shared_ptr create( + static std::shared_ptr create( std::shared_ptr eventBus, std::shared_ptr playbackRouter); @@ -56,10 +56,10 @@ private: /** * Constructor. * - * @param eventBus The @c BluetoothEventBus in which @c AVRCPCommandReceivedEvent events will appear. - * @param playbackRouter The @c PlaybackRouterInterface in which AVRCP commands will be transformed to. + * @param eventBus The @c BluetoothEventBus in which @c MediaCommandReceivedEvent events will appear. + * @param playbackRouter The @c PlaybackRouterInterface in which Media commands will be transformed to. */ - BluetoothAVRCPTransformer( + BluetoothMediaInputTransformer( std::shared_ptr eventBus, std::shared_ptr playbackRouter); @@ -70,7 +70,7 @@ private: */ bool init(); - /// The eventbus on which to listen for @c AVRCPCommandReceivedEvents. + /// The eventbus on which to listen for @c MediaCommandReceivedEvents. std::shared_ptr m_eventBus; /// Componenet responsible for executing the playback commands. @@ -81,4 +81,4 @@ private: } // namespace capabilityAgents } // namespace alexaClientSDK -#endif // ALEXA_CLIENT_SDK_CAPABILITYAGENTS_BLUETOOTH_INCLUDE_BLUETOOTH_BLUETOOTHAVRCPTRANSFORMER_H_ +#endif // ALEXA_CLIENT_SDK_CAPABILITYAGENTS_BLUETOOTH_INCLUDE_BLUETOOTH_BLUETOOTHMEDIAINPUTTRANSFORMER_H_ diff --git a/CapabilityAgents/Bluetooth/include/Bluetooth/BluetoothStorageInterface.h b/CapabilityAgents/Bluetooth/include/Bluetooth/BluetoothStorageInterface.h index e388f5b6..bc95357c 100644 --- a/CapabilityAgents/Bluetooth/include/Bluetooth/BluetoothStorageInterface.h +++ b/CapabilityAgents/Bluetooth/include/Bluetooth/BluetoothStorageInterface.h @@ -1,5 +1,5 @@ /* - * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * 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. @@ -75,6 +75,15 @@ public: */ virtual bool getUuid(const std::string& mac, std::string* uuid) = 0; + /** + * Retrieve the category associated with a UUID. + * + * @param uuid The UUID in which the associated category will be retrieved. + * @param[out] category The category of the associated UUID. + * @return A bool indicating success. + */ + virtual bool getCategory(const std::string& uuid, std::string* category) = 0; + /** * Retrieve a map of MAC to UUID. * @@ -83,6 +92,14 @@ public: */ virtual bool getMacToUuid(std::unordered_map* macToUuid) = 0; + /** + * Retrieve a map of MAC to Category. + * + * @param[out] macToCategory A map of MAC to Category mappings. + * @return A bool indicating success. + */ + virtual bool getMacToCategory(std::unordered_map* macToCategory) = 0; + /** * Retrieve a map of UUID to MAC. * @@ -91,6 +108,14 @@ public: */ virtual bool getUuidToMac(std::unordered_map* uuidToMac) = 0; + /** + * Retrieve a map of UUID to Category. + * + * @param[out] uuidToCategory A map of UUID to Category mappings. + * @return A bool indicating success. + */ + virtual bool getUuidToCategory(std::unordered_map* uuidToCategory) = 0; + /** * Gets a list of MAC Addresses ordered by their insertion order into * the database. @@ -112,6 +137,16 @@ public: */ virtual bool insertByMac(const std::string& mac, const std::string& uuid, bool overwrite) = 0; + /** + * Update an existing entry with category given a UUID. If there is no existing entry, + * the operation should fail. + * + * @param uuid The UUID. + * @param category The category. + * @return A bool indicating success. + */ + virtual bool updateByCategory(const std::string& uuid, const std::string& category) = 0; + /** * Remove the entry by the MAC address. The operation is considered successful if the entry * no longer exists after this call, including the case where the entry did not exist prior. diff --git a/CapabilityAgents/Bluetooth/include/Bluetooth/SQLiteBluetoothStorage.h b/CapabilityAgents/Bluetooth/include/Bluetooth/SQLiteBluetoothStorage.h index 8245a5a1..3e433fa7 100644 --- a/CapabilityAgents/Bluetooth/include/Bluetooth/SQLiteBluetoothStorage.h +++ b/CapabilityAgents/Bluetooth/include/Bluetooth/SQLiteBluetoothStorage.h @@ -1,5 +1,5 @@ /* - * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * 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. @@ -47,11 +47,15 @@ public: void close() override; bool clear() override; bool getUuid(const std::string& mac, std::string* uuid) override; + bool getCategory(const std::string& uuid, std::string* category) override; bool getMac(const std::string& uuid, std::string* mac) override; bool getMacToUuid(std::unordered_map* macToUuid) override; + bool getMacToCategory(std::unordered_map* macToCategory) override; bool getUuidToMac(std::unordered_map* uuidToMac) override; + bool getUuidToCategory(std::unordered_map* uuidToCategory) override; bool getOrderedMac(bool ascending, std::list* macs) override; bool insertByMac(const std::string& mac, const std::string& uuid, bool overwrite = true) override; + bool updateByCategory(const std::string& uuid, const std::string& category) override; bool remove(const std::string& mac) override; /// @} @@ -77,15 +81,19 @@ private: std::unordered_map* row); /** - * Utility that extracts from the database all uuid and mac pairs. The pairs are returned in a map, - * and the key is dependent on the @c keyPreference param. + * Utility that extracts from the database all key and value pairs specified. The pairs are returned + * in a map, and the key is set by key param. * The lock must be obtained before calling this function. * - * @param keyPreference A preference of whether the uuid or the mac should be the key. - * @param[out] mappings A mapping of uuid and mac keyed by keyPreference. - * @return A bool indicationg success. + * @param key Key for the mapping. + * @param value Value for the mapping. + * @param[out] mappings A mapping of key value pairs requested. + * @return A bool indication of success. */ - bool getMappingsLocked(const std::string& keyPreference, std::unordered_map* mappings); + bool getMappingsLocked( + const std::string& key, + const std::string& value, + std::unordered_map* mappings); /** * Utility that gets a data element from the database based on the constraint key. The constraint key must be @@ -103,6 +111,53 @@ private: const std::string& resultKey, std::string* resultVal); + /** + * Utility that updates a data element from the database based on the constraint key. The constraint key must be + * unique. The lock must be obtained before calling this function. + * + * @param constraintKey The key to filter against. Must be unique. + * @param constraintVal The value to filter against. + * @param updateKey The key of the data element to update. + * @param updateVal The resulting value that is to be updated to. + * @return A bool indicating success. + */ + bool updateValueLocked( + const std::string& constraintKey, + const std::string& constraintVal, + const std::string& updateKey, + const std::string& updateVal); + + /** + * Utility that inserts a new data element to the database given the elements. The lock must be obtained before + * calling this function. + * + * @param operation Operation given to database operation. + * @param uuid uuid of device to add. + * @param mac mac of device to add. + * @param category category of device to add. + * @return A bool indicating success. + */ + bool insertEntryLocked( + const std::string& operation, + const std::string& uuid, + const std::string& mac, + const std::string& category); + + /** + * Utility that checks if database has been migrated. The lock must be obtained before calling this function. + * + * @return true if migrated, otherwise false. + */ + bool isDatabaseMigratedLocked(); + + /** + * Utility that migrates the database by adding in the device category column to the existing database and + * set the category entry to OTHER. The lock must be obtained before calling this function. + * + * @return A bool indicating success. + */ + bool migrateDatabaseLocked(); + /// A mutex to protect database access. std::mutex m_databaseMutex; diff --git a/CapabilityAgents/Bluetooth/src/BasicDeviceConnectionRule.cpp b/CapabilityAgents/Bluetooth/src/BasicDeviceConnectionRule.cpp new file mode 100644 index 00000000..2142780e --- /dev/null +++ b/CapabilityAgents/Bluetooth/src/BasicDeviceConnectionRule.cpp @@ -0,0 +1,70 @@ +/* + * Copyright 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 +#include +#include +#include +#include + +namespace alexaClientSDK { +namespace capabilityAgents { +namespace bluetooth { +std::shared_ptr BasicDeviceConnectionRule::create() { + return std::shared_ptr(new BasicDeviceConnectionRule()); +} + +bool BasicDeviceConnectionRule::shouldExplicitlyConnect() { + return true; +} + +bool BasicDeviceConnectionRule::shouldExplicitlyDisconnect() { + return true; +} + +std::set> BasicDeviceConnectionRule::devicesToDisconnect( + std::map>> connectedDevices) { + /** + * Bluetooth CapabilityAgent only supports single A2DP device connected at one time. + * Need to disconnect the connected A2DP device(if available) whenever a new A2DP connects. + */ + std::set> devicesToDisconnect; + for (const auto& category : m_categories) { + auto devicesIt = connectedDevices.find(category); + if (devicesIt != connectedDevices.end()) { + devicesToDisconnect.insert(devicesIt->second.begin(), devicesIt->second.end()); + } + } + return devicesToDisconnect; +} + +BasicDeviceConnectionRule::BasicDeviceConnectionRule() { + m_categories = {DeviceCategory::AUDIO_VIDEO, DeviceCategory::PHONE, DeviceCategory::OTHER, DeviceCategory::UNKNOWN}; + m_profiles = {avsCommon::sdkInterfaces::bluetooth::services::A2DPSinkInterface::UUID, + avsCommon::sdkInterfaces::bluetooth::services::A2DPSourceInterface::UUID, + avsCommon::sdkInterfaces::bluetooth::services::AVRCPControllerInterface::UUID, + avsCommon::sdkInterfaces::bluetooth::services::AVRCPTargetInterface::UUID}; +} + +std::set BasicDeviceConnectionRule::getDeviceCategories() { + return m_categories; +} + +std::set BasicDeviceConnectionRule::getDependentProfiles() { + return m_profiles; +} + +} // namespace bluetooth +} // namespace capabilityAgents +} // namespace alexaClientSDK \ No newline at end of file diff --git a/CapabilityAgents/Bluetooth/src/Bluetooth.cpp b/CapabilityAgents/Bluetooth/src/Bluetooth.cpp index 0891d65e..ed6214fa 100644 --- a/CapabilityAgents/Bluetooth/src/Bluetooth.cpp +++ b/CapabilityAgents/Bluetooth/src/Bluetooth.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018-2020 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. @@ -12,26 +12,31 @@ * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ -#include "Bluetooth/Bluetooth.h" #include +#include #include #include #include #include #include +#include +#include +#include #include #include #include -#include #include +#include #include -#include #include #include #include +#include "Bluetooth/BasicDeviceConnectionRule.h" +#include "Bluetooth/Bluetooth.h" + namespace alexaClientSDK { namespace capabilityAgents { namespace bluetooth { @@ -44,6 +49,7 @@ using namespace avsCommon::sdkInterfaces::bluetooth::services; using namespace avsCommon::utils; using namespace avsCommon::utils::bluetooth; using namespace avsCommon::utils::mediaPlayer; +using MediaPlayerState = avsCommon::utils::mediaPlayer::MediaPlayerState; /// String to identify log entries originating from this file. static const std::string TAG{"Bluetooth"}; @@ -64,8 +70,8 @@ static const NamespaceAndName BLUETOOTH_STATE{NAMESPACE, "BluetoothState"}; /// The @c ScanDevices directive identifier static const NamespaceAndName SCAN_DEVICES{NAMESPACE, "ScanDevices"}; -/// The @c ScanDevicesUpdated event. -static const NamespaceAndName SCAN_DEVICES_UPDATED{NAMESPACE, "ScanDevicesUpdated"}; +/// The @c ScanDevicesReport event. +static const NamespaceAndName SCAN_DEVICES_REPORT{NAMESPACE, "ScanDevicesReport"}; /// The @c ScanDevicesFailed event. static const NamespaceAndName SCAN_DEVICES_FAILED{NAMESPACE, "ScanDevicesFailed"}; @@ -82,32 +88,41 @@ static const NamespaceAndName ENTER_DISCOVERABLE_MODE_FAILED{NAMESPACE, "EnterDi /// The @c ExitDiscoverableMode directive identifier. static const NamespaceAndName EXIT_DISCOVERABLE_MODE{NAMESPACE, "ExitDiscoverableMode"}; -/// The @c PairDeviceSucceeded event. -static const NamespaceAndName PAIR_DEVICE_SUCCEEDED{NAMESPACE, "PairDeviceSucceeded"}; +/// The @c PairDevicesSucceeded event. +static const NamespaceAndName PAIR_DEVICES_SUCCEEDED{NAMESPACE, "PairDevicesSucceeded"}; -/// The @c PairDeviceFailed event. -static const NamespaceAndName PAIR_DEVICE_FAILED{NAMESPACE, "PairDeviceFailed"}; +/// The @c PairDevicesFailed event. +static const NamespaceAndName PAIR_DEVICES_FAILED{NAMESPACE, "PairDevicesFailed"}; -/// The @c PairDevice directive identifier. -static const NamespaceAndName PAIR_DEVICE{NAMESPACE, "PairDevice"}; +/// The @c PairDevices directive identifier. +static const NamespaceAndName PAIR_DEVICES{NAMESPACE, "PairDevices"}; -/// The @c UnpairDevice directive identifier. -static const NamespaceAndName UNPAIR_DEVICE{NAMESPACE, "UnpairDevice"}; +/// The @c UnpairDevices directive identifier. +static const NamespaceAndName UNPAIR_DEVICES{NAMESPACE, "UnpairDevices"}; -/// The @c UnpairDeviceSucceeded event. -static const NamespaceAndName UNPAIR_DEVICE_SUCCEEDED{NAMESPACE, "UnpairDeviceSucceeded"}; +/// The @c UnpairDevicesSucceeded event. +static const NamespaceAndName UNPAIR_DEVICES_SUCCEEDED{NAMESPACE, "UnpairDevicesSucceeded"}; -/// The @c UnpairDeviceFailed event. -static const NamespaceAndName UNPAIR_DEVICE_FAILED{NAMESPACE, "UnpairDeviceFailed"}; +/// The @c UnpairDevicesFailed event. +static const NamespaceAndName UNPAIR_DEVICES_FAILED{NAMESPACE, "UnpairDevicesFailed"}; -/// The @c ConnectByDeviceId directive identifier. -static const NamespaceAndName CONNECT_BY_DEVICE_ID{NAMESPACE, "ConnectByDeviceId"}; +/// The @c SetDeviceCategories directive identifier +static const NamespaceAndName SET_DEVICE_CATEGORIES{NAMESPACE, "SetDeviceCategories"}; -/// The @c ConnectByDeviceIdSucceeded event. -static const NamespaceAndName CONNECT_BY_DEVICE_ID_SUCCEEDED{NAMESPACE, "ConnectByDeviceIdSucceeded"}; +/// The @c SetDeviceCategoriesSucceeded event. +static const NamespaceAndName SET_DEVICE_CATEGORIES_SUCCEEDED{NAMESPACE, "SetDeviceCategoriesSucceeded"}; -/// The @c ConnectByDeviceIdFailed event. -static const NamespaceAndName CONNECT_BY_DEVICE_ID_FAILED{NAMESPACE, "ConnectByDeviceIdFailed"}; +/// The @c SetDeviceCategoriesFailed event. +static const NamespaceAndName SET_DEVICE_CATEGORIES_FAILED{NAMESPACE, "SetDeviceCategoriesFailed"}; + +/// The @c ConnectByDeviceIds directive identifier. +static const NamespaceAndName CONNECT_BY_DEVICE_IDS{NAMESPACE, "ConnectByDeviceIds"}; + +/// The @c ConnectByDeviceIdsSucceeded event. +static const NamespaceAndName CONNECT_BY_DEVICE_IDS_SUCCEEDED{NAMESPACE, "ConnectByDeviceIdsSucceeded"}; + +/// The @c ConnectByDeviceIdsFailed event. +static const NamespaceAndName CONNECT_BY_DEVICE_IDS_FAILED{NAMESPACE, "ConnectByDeviceIdsFailed"}; /// The @c ConnectByProfile directive identifier. static const NamespaceAndName CONNECT_BY_PROFILE{NAMESPACE, "ConnectByProfile"}; @@ -118,14 +133,14 @@ static const NamespaceAndName CONNECT_BY_PROFILE_SUCCEEDED{NAMESPACE, "ConnectBy /// The @c ConnectByProfileFailed event. static const NamespaceAndName CONNECT_BY_PROFILE_FAILED{NAMESPACE, "ConnectByProfileFailed"}; -/// The @c DisconnectDevice directive identifier. -static const NamespaceAndName DISCONNECT_DEVICE{NAMESPACE, "DisconnectDevice"}; +/// The @c DisconnectDevices directive identifier. +static const NamespaceAndName DISCONNECT_DEVICES{NAMESPACE, "DisconnectDevices"}; -/// The @c DisconnectDeviceSucceeded event. -static const NamespaceAndName DISCONNECT_DEVICE_SUCCEEDED{NAMESPACE, "DisconnectDeviceSucceeded"}; +/// The @c DisconnectDevicesSucceeded event. +static const NamespaceAndName DISCONNECT_DEVICES_SUCCEEDED{NAMESPACE, "DisconnectDevicesSucceeded"}; -/// The @c DisconnectDeviceFailed event. -static const NamespaceAndName DISCONNECT_DEVICE_FAILED{NAMESPACE, "DisconnectDeviceFailed"}; +/// The @c DisconnectDevicesFailed event. +static const NamespaceAndName DISCONNECT_DEVICES_FAILED{NAMESPACE, "DisconnectDevicesFailed"}; /// The @c Play directive identifier. static const NamespaceAndName PLAY{NAMESPACE, "Play"}; @@ -170,11 +185,14 @@ static const NamespaceAndName STREAMING_STARTED{NAMESPACE, "StreamingStarted"}; static const NamespaceAndName STREAMING_ENDED{NAMESPACE, "StreamingEnded"}; /// The @c Channel name. -static const std::string CHANNEL_NAME = avsCommon::sdkInterfaces::FocusManagerInterface::CONTENT_CHANNEL_NAME; +static const std::string CHANNEL_NAME = FocusManagerInterface::CONTENT_CHANNEL_NAME; /// Activity ID for use by FocusManager static const std::string ACTIVITY_ID = "Bluetooth"; +/// Max number of paired devices connect by profile should atempt connect to +static const unsigned int MAX_CONNECT_BY_PROFILE_COUNT = 2; + /// A delay to deal with devices that can't process consecutive AVRCP commands. static const std::chrono::milliseconds CMD_DELAY{100}; @@ -184,18 +202,32 @@ static const std::chrono::milliseconds CMD_DELAY{100}; */ static const std::chrono::milliseconds INITIALIZE_SOURCE_DELAY{1000}; +/** + * The maximum about of time to block on a future from the Bluetooth implementation + * before it should be rejected. + */ +static const std::chrono::seconds DEFAULT_FUTURE_TIMEOUT{45}; + +/** + * The amount of time that the Bluetooth CA can hold onto the foreground channel and keep another CA in the background. + * If the time that the Bluetooth CA is in the foreground exceeds this value then other CAs will completely lose focus. + * If the Bluetooth CA loses focus within that time frame then the other CA which had focus will regain it and resume + * normal operation. + */ +static const std::chrono::minutes TRANSIENT_FOCUS_DURATION{2}; + /* * The following are keys used for AVS Directives and Events. */ -/// A key for an active device. -static const char ACTIVE_DEVICE_KEY[] = "activeDevice"; - /// A key for the alexa (host) device. static const char ALEXA_DEVICE_NAME_KEY[] = "alexaDevice"; /// A key for a device. static const char DEVICE_KEY[] = "device"; +/// A key for a devices. +static const char DEVICES_KEY[] = "devices"; + /// A key for discovered devices. static const char DISCOVERED_DEVICES_KEY[] = "discoveredDevices"; @@ -220,6 +252,9 @@ static const char PAIRED_DEVICES_KEY[] = "pairedDevices"; /// Identifying a profile. static const char PROFILE_KEY[] = "profile"; +/// Identifying an array of profiles. +static const char PROFILES_KEY[] = "profiles"; + /// Identifying a profile name. static const char PROFILE_NAME_KEY[] = "profileName"; @@ -229,6 +264,9 @@ static const char REQUESTER_KEY[] = "requester"; /// A key indicating streaming status. static const char STREAMING_KEY[] = "streaming"; +/// A key indicating state. +static const char STATE_KEY[] = "state"; + /// Indicates the supported profiles. static const char SUPPORTED_PROFILES_KEY[] = "supportedProfiles"; @@ -238,6 +276,30 @@ static const char TRUNCATED_MAC_ADDRESS_KEY[] = "truncatedMacAddress"; /// The uuid generated to identify a device. static const char UNIQUE_DEVICE_ID_KEY[] = "uniqueDeviceId"; +/// The metadata. +static const char METADATA_KEY[] = "metadata"; + +/// The Vendor Id. +static const char VENDOR_ID_KEY[] = "vendorId"; + +/// The Product Id. +static const char PRODUCT_ID_KEY[] = "productId"; + +/// The Class of Device. +static const char CLASS_OF_DEVICE_KEY[] = "classOfDevice"; + +/// The Vendor Device SigID. +static const char VENDOR_DEVICE_SIG_ID_KEY[] = "vendorDeviceSigId"; + +/// The Vendor Device ID. +static const char VENDOR_DEVICE_ID_KEY[] = "vendorDeviceId"; + +/// Identifying a device category. +static const char DEVICE_CATEGORY_KEY[] = "deviceCategory"; + +/// Identifying the connection status of a device. +static const char CONNECTION_STATE_KEY[] = "connectionState"; + /* * The following are AVS profile identifiers. */ @@ -257,14 +319,37 @@ static const std::string AVS_A2DP = "A2DP"; */ static const std::string AVS_AVRCP = "AVRCP"; -/// A mapping of AVS profile identifiers to the Bluetooth service UUID. +/// HFP. +static const std::string AVS_HFP = "HFP"; + +/// HID. +static const std::string AVS_HID = "HID"; + +/// SPP. +static const std::string AVS_SPP = "SPP"; + +/// A mapping of Bluetooth service UUIDs to AVS profile identifiers. // clang-format off -static const std::unordered_map AVS_PROFILE_MAP{ - {AVS_A2DP_SOURCE, std::string(A2DPSourceInterface::UUID)}, - {AVS_A2DP_SINK, std::string(A2DPSinkInterface::UUID)}, - {AVS_AVRCP, std::string(AVRCPTargetInterface::UUID)}}; +static const std::map AVS_PROFILE_MAP{ + {std::string(A2DPSourceInterface::UUID), AVS_A2DP_SOURCE}, + {std::string(A2DPSinkInterface::UUID), AVS_A2DP_SINK}, + {std::string(AVRCPTargetInterface::UUID), AVS_AVRCP}, + {std::string(HFPInterface::UUID), AVS_HFP}, + {std::string(HIDInterface::UUID), AVS_HID}, + {std::string(SPPInterface::UUID), AVS_SPP}}; // clang-format on +/** + * A mapping of DeviceCategory to its dependent service UUIDs. + * List all necessary profiles needed for a device with the following DeviceCategory: + * 1)DeviceCategory::REMOTE_CONTROL + * 2)DeviceCategory::GADGET + * All other categories are already enforced to have correct dependent profiles by @c BasicDeviceConnectionRule. + */ +static const std::map> DEVICECATEGORY_PROFILES_MAP{ + {DeviceCategory::REMOTE_CONTROL, {std::string(SPPInterface::UUID), std::string(HIDInterface::UUID)}}, + {DeviceCategory::GADGET, {std::string(SPPInterface::UUID), std::string(HIDInterface::UUID)}}}; + /// Bluetooth capability constants /// Bluetooth interface type static const std::string BLUETOOTH_CAPABILITY_INTERFACE_TYPE = "AlexaInterface"; @@ -273,7 +358,18 @@ static const std::string BLUETOOTH_CAPABILITY_INTERFACE_TYPE = "AlexaInterface"; static const std::string BLUETOOTH_CAPABILITY_INTERFACE_NAME = "Bluetooth"; /// Bluetooth interface version. Version 1.0 works with StreamingStarted and StreamingEnded. -static const std::string BLUETOOTH_CAPABILITY_INTERFACE_VERSION = "1.0"; +static const std::string BLUETOOTH_CAPABILITY_INTERFACE_VERSION = "2.0"; + +/// Bluetooth configurations value. +static const std::string BLUETOOTH_CAPABILITY_CONFIGURATION_VALUE = R"( +{ + "profiles": [ + "AVRCP", + "A2DP_SINK", + "A2DP_SOURCE" + ] +} +)"; /** * Creates the Bluetooth capability configuration. @@ -285,10 +381,37 @@ static std::shared_ptr getBluetoothCapa configMap.insert({CAPABILITY_INTERFACE_TYPE_KEY, BLUETOOTH_CAPABILITY_INTERFACE_TYPE}); configMap.insert({CAPABILITY_INTERFACE_NAME_KEY, BLUETOOTH_CAPABILITY_INTERFACE_NAME}); configMap.insert({CAPABILITY_INTERFACE_VERSION_KEY, BLUETOOTH_CAPABILITY_INTERFACE_VERSION}); + configMap.insert({CAPABILITY_INTERFACE_CONFIGURATIONS_KEY, BLUETOOTH_CAPABILITY_CONFIGURATION_VALUE}); return std::make_shared(configMap); } +/** + * Utility function to truncate a MAC address. + * + * @param mac The mac address. + * @param defaultString The default truncated mac address to return if the mac address is invalid. + * @return The truncated mac address. + */ +static std::string truncateWithDefault(const std::string& mac, const std::string& defaultString = "XX:XX:XX:XX:XX:XX") { + std::unique_ptr macAddressString = MacAddressString::create(mac); + if (macAddressString) { + return macAddressString->getTruncatedString(); + } else { + return defaultString; + } +} + +/** + * Utility function to truncate a Bluetooth device friendly name. + * + * @param friendlyName The Bluetooth device friendly name. + * @return The truncated friendly name. + */ +static std::string truncateFriendlyName(const std::string& friendlyName) { + return truncateWithDefault(friendlyName, friendlyName); +} + /** * Utility function to evaluate whether a device supports a specific AVS profile. * @@ -309,64 +432,53 @@ static bool supportsAvsProfile(std::shared_ptr device, } for (const auto& sdpRecord : device->getSupportedServices()) { - if (AVS_A2DP == avsProfileName && (sdpRecord->getUuid() == AVS_PROFILE_MAP.at(AVS_A2DP_SOURCE) || - sdpRecord->getUuid() == AVS_PROFILE_MAP.at(AVS_A2DP_SINK))) { - return true; - } else if (AVS_A2DP_SOURCE == avsProfileName && sdpRecord->getUuid() == AVS_PROFILE_MAP.at(AVS_A2DP_SOURCE)) { - return true; - } else if (AVS_A2DP_SINK == avsProfileName && sdpRecord->getUuid() == AVS_PROFILE_MAP.at(AVS_A2DP_SINK)) { - return true; - } else if (AVS_AVRCP == avsProfileName && sdpRecord->getUuid() == AVS_PROFILE_MAP.at(AVS_AVRCP)) { - return true; + auto avsProfileIt = AVS_PROFILE_MAP.find(sdpRecord->getUuid()); + if (avsProfileIt != AVS_PROFILE_MAP.end()) { + if (avsProfileIt->second == avsProfileName) { + return true; + } else if (avsProfileIt->second == AVS_A2DP_SINK && avsProfileName == AVS_A2DP) { + return true; + } else if (avsProfileIt->second == AVS_A2DP_SOURCE && avsProfileName == AVS_A2DP) { + return true; + } } } - ACSDK_DEBUG5(LX(__func__) .d("reason", "profileNotSupported") - .d("deviceMac", device->getMac()) + .d("deviceMac", truncateWithDefault(device->getMac())) .d("avsProfile", avsProfileName)); return false; } /** - * Utility function to extract AVS compliant profiles. This returns a rapidjson node - * containing an array of supported profiles. + * Wait for a a future function to be resolved. * - * @param device The device. - * @param allocator The allocator which will be used to create @c supportedProfiles. - * @param[out] supportedProfiles A rapidjson node containing the supported profiles. - * @return A bool indicating success. + * @param future The future + * @param description The desription of the future function. + * @param timeout The timeout given to the future. + * @return A bool indicating the future success. */ -static bool extractAvsProfiles( - std::shared_ptr device, - rapidjson::Document::AllocatorType& allocator, - rapidjson::Value* supportedProfiles) { - if (!device || !supportedProfiles || !supportedProfiles->IsArray()) { - ACSDK_ERROR(LX(__func__).d("reason", "invalidInputParameters")); - return false; - } - - for (const auto& sdp : device->getSupportedServices()) { - if (A2DPSourceInterface::UUID == sdp->getUuid()) { - rapidjson::Value profile(rapidjson::kObjectType); - profile.AddMember("name", AVS_A2DP_SOURCE, allocator); - profile.AddMember("version", sdp->getVersion(), allocator); - supportedProfiles->PushBack(profile, allocator); - } else if (AVRCPTargetInterface::UUID == sdp->getUuid()) { - rapidjson::Value profile(rapidjson::kObjectType); - profile.AddMember("name", AVS_AVRCP, allocator); - profile.AddMember("version", sdp->getVersion(), allocator); - supportedProfiles->PushBack(profile, allocator); - } else if (A2DPSinkInterface::UUID == sdp->getUuid()) { - rapidjson::Value profile(rapidjson::kObjectType); - profile.AddMember("name", AVS_A2DP_SINK, allocator); - profile.AddMember("version", sdp->getVersion(), allocator); - supportedProfiles->PushBack(profile, allocator); +static bool waitOnFuture( + std::future future, + std::string description = "", + std::chrono::milliseconds timeout = DEFAULT_FUTURE_TIMEOUT) { + if (future.valid()) { + std::future_status status = future.wait_for(timeout); + switch (status) { + case std::future_status::timeout: + ACSDK_ERROR(LX(__func__).d("description", description).m("Timeout waiting on a future.")); + break; + case std::future_status::deferred: + ACSDK_WARN(LX(__func__).d("description", description).m("Blocking on a deferred future.")); + /* FALL THROUGH */ + case std::future_status::ready: + return future.get(); } + } else { + ACSDK_ERROR(LX(__func__).d("description", description).m("Cannot wait on invalid future.")); } - - return true; + return false; } /** @@ -374,7 +486,7 @@ static bool extractAvsProfiles( * that AVS has defined. * * @param state The @c StreamingState. - * @return A string represening the streaming state. + * @return A string representing the streaming state. */ static std::string convertToAVSStreamingState(const Bluetooth::StreamingState& state) { switch (state) { @@ -392,6 +504,68 @@ static std::string convertToAVSStreamingState(const Bluetooth::StreamingState& s return "UNKNOWN"; } +/** + * Utility function to evaluate whether connection rules are valid. + * Device categories defined in the @c BasicDeviceConnectionRule are enabled by the Bluetoooth CapabilityAgent by + * default and are not allowed to be overwritten. + * + * @param enabledConnectionRules a set of device connection rules defined by customers. + * @return A bool indicating success. + */ +static bool validateDeviceConnectionRules( + std::unordered_set> + enabledConnectionRules) { + std::set enabledDeviceCategories; + if (!enabledConnectionRules.empty()) { + for (const auto& connectionRule : enabledConnectionRules) { + std::set categories = connectionRule->getDeviceCategories(); + std::set dependentProfiles = connectionRule->getDependentProfiles(); + + for (const auto& category : categories) { + // Verify if the device category is already defined in the other device connection rule. + if (enabledDeviceCategories.find(category) != enabledDeviceCategories.end()) { + ACSDK_ERROR(LX(__func__).d("reason", "RedefinedDeviceCategory").d("category", category)); + return false; + } + enabledDeviceCategories.insert(category); + + // Verify if dependent profiles defined in the rule are valid. + if (DEVICECATEGORY_PROFILES_MAP.find(category) != DEVICECATEGORY_PROFILES_MAP.end()) { + std::unordered_set requiredProfiles = DEVICECATEGORY_PROFILES_MAP.at(category); + for (const auto& requiredProfile : requiredProfiles) { + if (dependentProfiles.count(requiredProfile) == 0) { + ACSDK_ERROR(LX(__func__).d("reason", "RequiredProfileNotAdded").d("uuid", requiredProfile)); + return false; + } + } + } + } + } + } + + return true; +} + +/** + * Utility function to squash @c DeviceState into the connection state that AVS has defined. + * + * @param state The @c DeviceState + * @return A string representing the connection state. + */ +static std::string convertToAVSConnectedState(const DeviceState& state) { + switch (state) { + case DeviceState::CONNECTED: + return "CONNECTED"; + case DeviceState::FOUND: + case DeviceState::IDLE: + case DeviceState::PAIRED: + case DeviceState::UNPAIRED: + case DeviceState::DISCONNECTED: + return "DISCONNECTED"; + } + return "UNKNOWN"; +} + std::shared_ptr Bluetooth::create( std::shared_ptr contextManager, std::shared_ptr focusManager, @@ -402,7 +576,9 @@ std::shared_ptr Bluetooth::create( std::shared_ptr eventBus, std::shared_ptr mediaPlayer, std::shared_ptr customerDataManager, - std::shared_ptr avrcpTransformer) { + std::unordered_set> + enabledConnectionRules, + std::shared_ptr mediaInputTransformer) { ACSDK_DEBUG5(LX(__func__)); if (!contextManager) { @@ -423,6 +599,8 @@ std::shared_ptr Bluetooth::create( ACSDK_ERROR(LX(__func__).d("reason", "nullMediaPlayer")); } else if (!customerDataManager) { ACSDK_ERROR(LX(__func__).d("reason", "nullCustomerDataManager")); + } else if (!validateDeviceConnectionRules(enabledConnectionRules)) { + ACSDK_ERROR(LX(__func__).d("reason", "invalidBluetoothDeviceConnectionRules")); } else { auto bluetooth = std::shared_ptr(new Bluetooth( contextManager, @@ -434,7 +612,8 @@ std::shared_ptr Bluetooth::create( eventBus, mediaPlayer, customerDataManager, - avrcpTransformer)); + enabledConnectionRules, + mediaInputTransformer)); if (bluetooth->init()) { return bluetooth; @@ -467,30 +646,36 @@ bool Bluetooth::init() { m_eventBus->addListener( {avsCommon::utils::bluetooth::BluetoothEventType::DEVICE_DISCOVERED, avsCommon::utils::bluetooth::BluetoothEventType::DEVICE_STATE_CHANGED, - avsCommon::utils::bluetooth::BluetoothEventType::STREAMING_STATE_CHANGED}, + avsCommon::utils::bluetooth::BluetoothEventType::STREAMING_STATE_CHANGED, + avsCommon::utils::bluetooth::BluetoothEventType::SCANNING_STATE_CHANGED, + avsCommon::utils::bluetooth::BluetoothEventType::TOGGLE_A2DP_PROFILE_STATE_CHANGED}, shared_from_this()); return true; } void Bluetooth::syncWithDeviceManager() { - std::vector> extraConnectedDevices; - for (auto& device : m_deviceManager->getDiscoveredDevices()) { + for (const auto& device : m_deviceManager->getDiscoveredDevices()) { if (!device->isConnected()) { ACSDK_DEBUG9(LX(__func__).d("reason", "deviceNotConnected").m("Excluding")); continue; } - - if (!m_activeDevice && supportsAvsProfile(device, AVS_A2DP)) { - executeOnDeviceConnect(device); - } else { - executeFunctionOnDevice(device, &BluetoothDeviceInterface::disconnect); - } + executeOnDeviceConnect(device, false); } - if (m_activeDevice && m_activeDevice->getA2DPSource()) { - if (!m_focusManager->acquireChannel(CHANNEL_NAME, shared_from_this(), ACTIVITY_ID)) { - ACSDK_ERROR(LX(__func__).d("reason", "acquireChannelFailed")); + if (m_activeA2DPDevice && m_activeA2DPDevice->getService(A2DPSourceInterface::UUID)) { + auto streamingState = m_activeA2DPDevice->getStreamingState(); + switch (streamingState) { + case MediaStreamingState::IDLE: + m_streamingState = StreamingState::PAUSED; + break; + case MediaStreamingState::PENDING: + m_streamingState = StreamingState::INACTIVE; + break; + case MediaStreamingState::ACTIVE: + m_streamingState = StreamingState::ACTIVE; + m_executor.submit([this] { executeAcquireFocus(__func__); }); + break; } } } @@ -505,7 +690,9 @@ Bluetooth::Bluetooth( std::shared_ptr eventBus, std::shared_ptr mediaPlayer, std::shared_ptr customerDataManager, - std::shared_ptr avrcpTransformer) : + std::unordered_set> + enabledConnectionRules, + std::shared_ptr mediaInputTransformer) : CapabilityAgent{NAMESPACE, exceptionEncounteredSender}, RequiresShutdown{"Bluetooth"}, CustomerDataHandler{customerDataManager}, @@ -513,15 +700,23 @@ Bluetooth::Bluetooth( m_contextManager{contextManager}, m_focusManager{focusManager}, m_streamingState{StreamingState::INACTIVE}, + m_focusTransitionState{FocusTransitionState::INTERNAL}, + m_scanningTransitionState{ScanningTransitionState::INACTIVE}, m_focusState{FocusState::NONE}, m_sourceId{MediaPlayerInterface::ERROR}, m_deviceManager{std::move(deviceManager)}, m_mediaPlayer{mediaPlayer}, m_db{bluetoothStorage}, m_eventBus{eventBus}, - m_avrcpTransformer{avrcpTransformer}, + m_mediaInputTransformer{mediaInputTransformer}, m_mediaStream{nullptr} { m_capabilityConfigurations.insert(getBluetoothCapabilityConfiguration()); + + for (const auto& connectionRule : enabledConnectionRules) { + for (const auto& category : connectionRule->getDeviceCategories()) { + m_enabledConnectionRules[category] = connectionRule; + } + } } DirectiveHandlerConfiguration Bluetooth::getConfiguration() const { @@ -534,11 +729,12 @@ DirectiveHandlerConfiguration Bluetooth::getConfiguration() const { configuration[SCAN_DEVICES] = neitherNonBlockingPolicy; configuration[ENTER_DISCOVERABLE_MODE] = neitherNonBlockingPolicy; configuration[EXIT_DISCOVERABLE_MODE] = neitherNonBlockingPolicy; - configuration[PAIR_DEVICE] = neitherNonBlockingPolicy; - configuration[UNPAIR_DEVICE] = neitherNonBlockingPolicy; - configuration[CONNECT_BY_DEVICE_ID] = neitherNonBlockingPolicy; + configuration[PAIR_DEVICES] = neitherNonBlockingPolicy; + configuration[UNPAIR_DEVICES] = neitherNonBlockingPolicy; + configuration[SET_DEVICE_CATEGORIES] = neitherNonBlockingPolicy; + configuration[CONNECT_BY_DEVICE_IDS] = neitherNonBlockingPolicy; configuration[CONNECT_BY_PROFILE] = neitherNonBlockingPolicy; - configuration[DISCONNECT_DEVICE] = neitherNonBlockingPolicy; + configuration[DISCONNECT_DEVICES] = neitherNonBlockingPolicy; configuration[PLAY] = audioNonBlockingPolicy; configuration[STOP] = audioNonBlockingPolicy; configuration[NEXT] = audioNonBlockingPolicy; @@ -578,11 +774,15 @@ void Bluetooth::doShutdown() { auto hostController = m_deviceManager->getHostController(); if (hostController) { - hostController->stopScan().get(); - hostController->exitDiscoverableMode().get(); + auto stopScanFuture = hostController->stopScan(); + waitOnFuture(std::move(stopScanFuture), "Stop bluetooth scanning"); + auto exitDiscoverableFuture = hostController->exitDiscoverableMode(); + waitOnFuture(std::move(exitDiscoverableFuture), "Exit discoverable mode"); } - m_activeDevice.reset(); + m_disabledA2DPDevice.reset(); + m_activeA2DPDevice.reset(); + m_restrictedDevices.clear(); // Finalizing the implementation @@ -590,6 +790,8 @@ void Bluetooth::doShutdown() { m_eventBus.reset(); m_observers.clear(); + m_bluetoothEventStates.clear(); + m_connectedDevices.clear(); } std::unordered_set> Bluetooth::getCapabilityConfigurations() { @@ -603,20 +805,25 @@ void Bluetooth::clearData() { // Stop scanning and discoverability. auto hostController = m_deviceManager->getHostController(); if (hostController->isScanning()) { - hostController->stopScan().get(); + auto stopScanFuture = hostController->stopScan(); + waitOnFuture(std::move(stopScanFuture), "Stop bluetooth scanning"); ACSDK_DEBUG5(LX("clearData").d("action", "stoppedScanning")); } if (hostController->isDiscoverable()) { - hostController->exitDiscoverableMode().get(); + auto exitDiscoverableFuture = hostController->exitDiscoverableMode(); + waitOnFuture(std::move(exitDiscoverableFuture), "Exit discoverable mode"); ACSDK_DEBUG5(LX("clearData").d("action", "disabledDiscoverable")); } // Unpair all devices. for (auto& device : m_deviceManager->getDiscoveredDevices()) { if (device->isPaired()) { - device->unpair().get(); - ACSDK_DEBUG5(LX("clearData").d("action", "unpairDevice").d("device", device->getFriendlyName())); + auto unpairFuture = device->unpair(); + waitOnFuture(std::move(unpairFuture), "Unpair device"); + ACSDK_DEBUG5(LX("clearData") + .d("action", "unpairDevice") + .d("device", truncateFriendlyName(device->getFriendlyName()))); } } @@ -628,7 +835,7 @@ void Bluetooth::executeInitializeMediaSource() { ACSDK_DEBUG5(LX(__func__)); std::this_thread::sleep_for(INITIALIZE_SOURCE_DELAY); - auto a2dpSource = m_activeDevice->getA2DPSource(); + auto a2dpSource = getService(m_activeA2DPDevice); if (!a2dpSource) { ACSDK_CRITICAL(LX(__func__).d("reason", "a2dpSourceNotSupported")); return; @@ -665,13 +872,49 @@ bool Bluetooth::retrieveUuid(const std::string& mac, std::string* uuid) { ACSDK_INFO(LX(__func__).d("reason", "noMatchingUUID").d("mac", mac)); *uuid = uuidGeneration::generateUUID(); if (!m_db->insertByMac(mac, *uuid, false)) { - ACSDK_ERROR(LX(__func__).d("reason", "insertingToDBFailed").d("mac", mac).d("uuid", *uuid)); + std::string truncatedMac = truncateWithDefault(mac); + ACSDK_ERROR(LX(__func__).d("reason", "insertingToDBFailed").d("mac", truncatedMac).d("uuid", *uuid)); return false; } return true; } +bool Bluetooth::retrieveDeviceCategoryByUuid(const std::string& uuid, DeviceCategory* category) { + ACSDK_DEBUG5(LX(__func__)); + + if (!category) { + ACSDK_ERROR(LX(__func__).d("reason", "nullDeviceCategory")); + return false; + } + + std::string categoryString; + if (m_db->getCategory(uuid, &categoryString)) { + *category = stringToDeviceCategory(categoryString); + return true; + } + + return false; +} + +std::shared_ptr Bluetooth:: + retrieveConnectionRuleByUuid(const std::string& uuid) { + ACSDK_DEBUG5(LX(__func__)); + + DeviceCategory category = DeviceCategory::UNKNOWN; + if (!retrieveDeviceCategoryByUuid(uuid, &category)) { + ACSDK_ERROR(LX(__func__).d("reason", "retrieveDeviceCategoryFailed")); + return nullptr; + } + + auto ruleIt = m_enabledConnectionRules.find(category); + if (ruleIt != m_enabledConnectionRules.end()) { + return ruleIt->second; + } + + return nullptr; +} + void Bluetooth::clearUnusedUuids() { ACSDK_DEBUG5(LX(__func__)); @@ -701,7 +944,8 @@ std::shared_ptr Bluetooth::retrieveDeviceByUuid(const std::shared_ptr device = retrieveDeviceByMac(mac); if (!device) { - ACSDK_ERROR(LX("retrieveDeviceByUuidFailed").d("reason", "couldNotFindDevice").d("mac", mac)); + std::string truncatedMac = truncateWithDefault(mac); + ACSDK_ERROR(LX("retrieveDeviceByUuidFailed").d("reason", "couldNotFindDevice").d("mac", truncatedMac)); return nullptr; } @@ -709,7 +953,8 @@ std::shared_ptr Bluetooth::retrieveDeviceByUuid(const } std::shared_ptr Bluetooth::retrieveDeviceByMac(const std::string& mac) { - ACSDK_DEBUG5(LX(__func__).d("mac", mac)); + std::string truncatedMac = truncateWithDefault(mac); + ACSDK_DEBUG5(LX(__func__).d("mac", truncatedMac)); auto devices = m_deviceManager->getDiscoveredDevices(); for (const auto& device : devices) { if (device->getMac() == mac) { @@ -729,18 +974,24 @@ void Bluetooth::executeUpdateContext() { FRIENDLY_NAME_KEY, m_deviceManager->getHostController()->getFriendlyName(), payload.GetAllocator()); payload.AddMember(ALEXA_DEVICE_NAME_KEY, alexaDevice, payload.GetAllocator()); - // Construct pairedDevices and activeDevice. + // Construct pairedDevices. rapidjson::Value pairedDevices(rapidjson::kArrayType); std::unordered_map macToUuids; + std::unordered_map uuidToCategory; if (!m_db->getMacToUuid(&macToUuids)) { ACSDK_ERROR(LX(__func__).d("reason", "databaseQueryFailed")); macToUuids.clear(); } + if (!m_db->getUuidToCategory(&uuidToCategory)) { + ACSDK_ERROR(LX(__func__).d("reason", "databaseQueryFailed")); + uuidToCategory.clear(); + } + for (const auto& device : m_deviceManager->getDiscoveredDevices()) { ACSDK_DEBUG9(LX(__func__) - .d("friendlyName", device->getFriendlyName()) + .d("friendlyName", truncateFriendlyName(device->getFriendlyName())) .d("mac", device->getMac()) .d("paired", device->isPaired())); if (!device->isPaired()) { @@ -766,8 +1017,19 @@ void Bluetooth::executeUpdateContext() { } } + // Retrieve the category from our map. + std::string category; + const auto categoryIt = uuidToCategory.find(uuid); + if (uuidToCategory.end() != categoryIt) { + category = categoryIt->second; + } else { + // Category not found in the map, default to UNKNOWN. + category = deviceCategoryToString(DeviceCategory::UNKNOWN); + } + rapidjson::Value deviceJson(rapidjson::kObjectType); deviceJson.AddMember(UNIQUE_DEVICE_ID_KEY, uuid, payload.GetAllocator()); + deviceJson.AddMember(DEVICE_CATEGORY_KEY, category, payload.GetAllocator()); deviceJson.AddMember(FRIENDLY_NAME_KEY, device->getFriendlyName(), payload.GetAllocator()); rapidjson::Value supportedProfiles(rapidjson::kArrayType); @@ -775,17 +1037,9 @@ void Bluetooth::executeUpdateContext() { if (!extractAvsProfiles(device, payload.GetAllocator(), &supportedProfiles)) { supportedProfiles = rapidjson::Value(rapidjson::kArrayType); } - deviceJson.AddMember(SUPPORTED_PROFILES_KEY, supportedProfiles.GetArray(), payload.GetAllocator()); - - // Add activeDevice. - if (m_activeDevice && device == m_activeDevice) { - // Deep copy, otherwise move will occur. - rapidjson::Value activeDevice(rapidjson::kObjectType); - activeDevice.CopyFrom(deviceJson, payload.GetAllocator()); - activeDevice.AddMember(STREAMING_KEY, convertToAVSStreamingState(m_streamingState), payload.GetAllocator()); - payload.AddMember(ACTIVE_DEVICE_KEY, activeDevice, payload.GetAllocator()); - } + deviceJson.AddMember( + CONNECTION_STATE_KEY, convertToAVSConnectedState(device->getDeviceState()), payload.GetAllocator()); pairedDevices.PushBack(deviceJson, payload.GetAllocator()); } @@ -798,11 +1052,53 @@ void Bluetooth::executeUpdateContext() { return; } - ACSDK_INFO(LX(__func__).d("buffer", buffer.GetString())); + ACSDK_DEBUG9(LX(__func__).sensitive("buffer", buffer.GetString())); m_contextManager->setState(BLUETOOTH_STATE, buffer.GetString(), StateRefreshPolicy::NEVER); } +void Bluetooth::executeSendEvent(const std::string& eventName, const std::string& eventPayload) { + ACSDK_DEBUG5(LX(__func__).d("eventName", eventName)); + + auto event = buildJsonEventString(eventName, "", eventPayload, ""); + + ACSDK_DEBUG5(LX("onExecuteSendEventLambda").d("event", event.second)); + auto request = std::make_shared(event.second); + m_messageSender->sendMessage(request); +} + +bool Bluetooth::extractAvsProfiles( + std::shared_ptr device, + rapidjson::Document::AllocatorType& allocator, + rapidjson::Value* supportedProfiles) { + if (!device || !supportedProfiles || !supportedProfiles->IsArray()) { + ACSDK_ERROR(LX(__func__).d("reason", "invalidInputParameters")); + return false; + } + + for (const auto& sdp : device->getSupportedServices()) { + auto profileNameIt = AVS_PROFILE_MAP.find(sdp->getUuid()); + if (profileNameIt != AVS_PROFILE_MAP.end()) { + rapidjson::Value profile(rapidjson::kObjectType); + profile.AddMember(NAME_KEY, profileNameIt->second, allocator); + profile.AddMember(VERSION_KEY, sdp->getVersion(), allocator); + if (A2DPSinkInterface::UUID == profileNameIt->first || A2DPSourceInterface::UUID == profileNameIt->first) { + rapidjson::Value profileState(rapidjson::kObjectType); + if (m_activeA2DPDevice && device == m_activeA2DPDevice) { + profileState.AddMember(STREAMING_KEY, convertToAVSStreamingState(m_streamingState), allocator); + } else { + profileState.AddMember( + STREAMING_KEY, convertToAVSStreamingState(Bluetooth::StreamingState::INACTIVE), allocator); + } + profile.AddMember(STATE_KEY, profileState, allocator); + } + supportedProfiles->PushBack(profile, allocator); + } + } + + return true; +} + void Bluetooth::executeQueueEventAndRequestContext(const std::string& eventName, const std::string& eventPayload) { ACSDK_DEBUG5(LX(__func__).d("eventName", eventName)); @@ -812,34 +1108,38 @@ void Bluetooth::executeQueueEventAndRequestContext(const std::string& eventName, void Bluetooth::executeDrainQueue() { while (!m_cmdQueue.empty()) { - AVRCPCommand cmd = m_cmdQueue.front(); + MediaCommand cmd = m_cmdQueue.front(); m_cmdQueue.pop_front(); - if (!m_activeDevice || !m_activeDevice->getAVRCPTarget()) { + if (!m_activeA2DPDevice || !m_activeA2DPDevice->getService(AVRCPTargetInterface::UUID)) { ACSDK_ERROR(LX(__func__).d("reason", "invalidState")); continue; } - auto avrcpTarget = m_activeDevice->getAVRCPTarget(); + auto avrcpTarget = getService(m_activeA2DPDevice); + switch (cmd) { /* * MediaControl Events should only be sent in response to a directive. * Don't send events for PLAY or PAUSE because they are never queued * as a result of a directive, only as a result of a focus change. */ - case AVRCPCommand::PLAY: + case MediaCommand::PLAY: avrcpTarget->play(); m_streamingState = StreamingState::PENDING_ACTIVE; break; - case AVRCPCommand::PAUSE: + case MediaCommand::PAUSE: avrcpTarget->pause(); m_streamingState = StreamingState::PENDING_PAUSED; break; - case AVRCPCommand::NEXT: - executeNext(); + case MediaCommand::NEXT: + executeNext(m_activeA2DPDevice); break; - case AVRCPCommand::PREVIOUS: - executePrevious(); + case MediaCommand::PREVIOUS: + executePrevious(m_activeA2DPDevice); + break; + case MediaCommand::PLAY_PAUSE: + // No-op break; } @@ -860,25 +1160,27 @@ void Bluetooth::executeAbortMediaPlayback() { if (FocusState::FOREGROUND == m_focusState || FocusState::BACKGROUND == m_focusState) { ACSDK_DEBUG5(LX(__func__).d("reason", "releasingFocus").d("focusState", m_focusState)); - m_focusManager->releaseChannel(CHANNEL_NAME, shared_from_this()); + executeReleaseFocus(__func__); } } void Bluetooth::executeEnterForeground() { ACSDK_DEBUG5(LX(__func__).d("streamingState", streamingStateToString(m_streamingState))); - if (!m_activeDevice) { + if (!m_activeA2DPDevice) { ACSDK_ERROR(LX(__func__).d("reason", "noActiveDevice")); executeAbortMediaPlayback(); return; } - auto avrcpTarget = m_activeDevice->getAVRCPTarget(); + auto avrcpTarget = getService(m_activeA2DPDevice); + if (!avrcpTarget) { ACSDK_INFO(LX(__func__).d("reason", "avrcpTargetNotSupported")); } switch (m_streamingState) { case StreamingState::ACTIVE: + executeDrainQueue(); break; case StreamingState::PENDING_ACTIVE: if (m_mediaStream == nullptr) { @@ -895,7 +1197,7 @@ void Bluetooth::executeEnterForeground() { // We push a play when the BT CA has been backgrounded because some devices do not // auto start playback next/previous command. if (m_focusState == FocusState::BACKGROUND) { - m_cmdQueue.push_front(AVRCPCommand::PLAY); + m_cmdQueue.push_front(MediaCommand::PLAY); executeDrainQueue(); } if (m_mediaStream == nullptr) { @@ -908,16 +1210,20 @@ void Bluetooth::executeEnterForeground() { } } -void Bluetooth::executeEnterBackground() { +void Bluetooth::executeEnterBackground(MixingBehavior behavior) { ACSDK_DEBUG5(LX(__func__).d("streamingState", streamingStateToString(m_streamingState))); - - if (!m_activeDevice) { + if (!m_activeA2DPDevice) { ACSDK_ERROR(LX(__func__).d("reason", "noActiveDevice")); executeAbortMediaPlayback(); return; } - auto avrcpTarget = m_activeDevice->getAVRCPTarget(); + // currently Bluetooth CapabilityAgent must Always Pause on receiving Background focus + if (MixingBehavior::MUST_PAUSE != behavior) { + ACSDK_WARN(LX(__func__).d("Unhandled MixingBehavior", behavior)); + } + + auto avrcpTarget = getService(m_activeA2DPDevice); if (!avrcpTarget) { ACSDK_INFO(LX(__func__).d("reason", "avrcpTargetNotSupported")); } @@ -951,52 +1257,74 @@ void Bluetooth::executeEnterBackground() { void Bluetooth::executeEnterNone() { ACSDK_DEBUG5(LX(__func__).d("streamingState", streamingStateToString(m_streamingState))); - if (!m_activeDevice) { + if (!m_activeA2DPDevice) { ACSDK_DEBUG5(LX(__func__).d("reason", "noActiveDevice")); executeAbortMediaPlayback(); return; } - auto avrcpTarget = m_activeDevice->getAVRCPTarget(); - if (!avrcpTarget) { - ACSDK_INFO(LX(__func__).d("reason", "avrcpTargetNotSupported")); - } + if (FocusTransitionState::EXTERNAL == m_focusTransitionState) { + auto a2dpSource = getService(m_activeA2DPDevice); + /** + * Only disconnect if the remote device is a Bluetooth audio source that is streaming audio and + * the BT CA is transitioning from FOREGROUND or BACKGROUND to NONE + */ + if (a2dpSource && m_focusState != FocusState::NONE) { + if (executeFunctionOnDevice(m_activeA2DPDevice, &BluetoothDeviceInterface::disconnect)) { + executeOnDeviceDisconnect(m_activeA2DPDevice, Requester::DEVICE); + } else { + std::unordered_set uuids; + std::string uuid; + if (retrieveUuid(m_activeA2DPDevice->getMac(), &uuid)) { + uuids.insert(uuid); + } else { + ACSDK_ERROR(LX(__func__).d("reason", "retrieveUuidFailed")); + } + executeSendDisconnectDevicesFailed(uuids, Requester::DEVICE); + } + } + } else { + auto avrcpTarget = getService(m_activeA2DPDevice); + if (!avrcpTarget) { + ACSDK_INFO(LX(__func__).d("reason", "avrcpTargetNotSupported")); + } - switch (m_streamingState) { - case StreamingState::ACTIVE: - case StreamingState::PENDING_ACTIVE: - if (avrcpTarget && !avrcpTarget->pause()) { - ACSDK_ERROR(LX(__func__).d("reason", "avrcpPauseFailed")); - } - m_streamingState = StreamingState::PENDING_PAUSED; - if (!m_mediaPlayer->stop(m_sourceId)) { - ACSDK_ERROR(LX(__func__).d("reason", "stopFailed").d("sourceId", m_sourceId)); - cleanupMediaSource(); - } - break; - case StreamingState::PENDING_PAUSED: - if (!m_mediaPlayer->stop(m_sourceId)) { - ACSDK_ERROR(LX(__func__).d("reason", "stopFailed").d("sourceId", m_sourceId)); - cleanupMediaSource(); - } - break; - case StreamingState::PAUSED: - case StreamingState::INACTIVE: - break; + switch (m_streamingState) { + case StreamingState::ACTIVE: + case StreamingState::PENDING_ACTIVE: + if (avrcpTarget && !avrcpTarget->pause()) { + ACSDK_ERROR(LX(__func__).d("reason", "avrcpPauseFailed")); + } + m_streamingState = StreamingState::PENDING_PAUSED; + if (!m_mediaPlayer->stop(m_sourceId)) { + ACSDK_ERROR(LX(__func__).d("reason", "stopFailed").d("sourceId", m_sourceId)); + cleanupMediaSource(); + } + break; + case StreamingState::PENDING_PAUSED: + if (!m_mediaPlayer->stop(m_sourceId)) { + ACSDK_ERROR(LX(__func__).d("reason", "stopFailed").d("sourceId", m_sourceId)); + cleanupMediaSource(); + } + break; + case StreamingState::PAUSED: + case StreamingState::INACTIVE: + break; + } } } -void Bluetooth::onFocusChanged(FocusState newFocus) { - ACSDK_DEBUG5(LX(__func__).d("current", m_focusState).d("new", newFocus)); +void Bluetooth::onFocusChanged(FocusState newFocus, MixingBehavior behavior) { + ACSDK_DEBUG5(LX(__func__).d("current", m_focusState).d("new", newFocus).d("MixingBehavior", behavior)); - m_executor.submit([this, newFocus] { + m_executor.submit([this, newFocus, behavior] { switch (newFocus) { case FocusState::FOREGROUND: { executeEnterForeground(); break; } case FocusState::BACKGROUND: { - executeEnterBackground(); + executeEnterBackground(behavior); break; } case FocusState::NONE: { @@ -1005,13 +1333,19 @@ void Bluetooth::onFocusChanged(FocusState newFocus) { } } + if (FocusState::NONE == newFocus && FocusTransitionState::PENDING_INTERNAL == m_focusTransitionState) { + m_focusTransitionState = FocusTransitionState::INTERNAL; + } else if (FocusState::NONE != newFocus && FocusTransitionState::PENDING_INTERNAL != m_focusTransitionState) { + m_focusTransitionState = FocusTransitionState::EXTERNAL; + } + m_focusState = newFocus; }); } void Bluetooth::onContextAvailable(const std::string& jsonContext) { m_executor.submit([this, jsonContext] { - ACSDK_DEBUG5(LX("onContextAvailableLambda")); + ACSDK_DEBUG9(LX("onContextAvailableLambda")); if (m_eventQueue.empty()) { ACSDK_ERROR(LX("contextRequestedWithNoQueuedEvents")); @@ -1066,6 +1400,24 @@ static bool parseDirectivePayload(const std::string& payload, Document* document return true; } +std::unordered_set Bluetooth::retrieveUuidsFromConnectionPayload(const rapidjson::Document& payload) { + std::unordered_set uuids; + rapidjson::Value::ConstMemberIterator it; + + if (json::jsonUtils::findNode(payload, DEVICES_KEY, &it) && it->value.IsArray()) { + for (auto deviceIt = it->value.Begin(); deviceIt != it->value.End(); deviceIt++) { + std::string uuid; + if (!json::jsonUtils::retrieveValue(*deviceIt, UNIQUE_DEVICE_ID_KEY, &uuid)) { + ACSDK_ERROR(LX("retrieveValueFailed").d("reason", "uuidNotFound")); + continue; + } + uuids.insert(uuid); + } + } + + return uuids; +} + // TODO: ACSDK-1369 Refactor this method. void Bluetooth::handleDirective(std::shared_ptr info) { ACSDK_DEBUG5(LX(__func__)); @@ -1097,45 +1449,68 @@ void Bluetooth::handleDirective(std::shared_ptr // There are no events to send in case this operation fails. The best we can do is log. executeSetScanMode(false); executeSetDiscoverableMode(false); - } else if (directiveName == PAIR_DEVICE.name) { - rapidjson::Value::ConstMemberIterator it; - std::string uuid; - - if (json::jsonUtils::findNode(payload, DEVICE_KEY, &it) && - json::jsonUtils::retrieveValue(it->value, UNIQUE_DEVICE_ID_KEY, &uuid)) { + } else if (directiveName == PAIR_DEVICES.name) { + std::unordered_set uuids = retrieveUuidsFromConnectionPayload(payload); + if (!uuids.empty()) { /* * AVS expects this sequence of implicit behaviors. * AVS should send individual directives, but we will handle this for now. + * + * Don't send ScanDeviceReport event to cloud in pairing mode. Otherwise, another + * SCAN_DEVICES directive would be sent down to start scan mode again. + * + * If the device fails to pair, start scan mode again. */ - executeSetScanMode(false); + executeSetScanMode(false, false); executeSetDiscoverableMode(false); - if (executePairDevice(uuid)) { - executeConnectByDeviceId(uuid); + + if (!executePairDevices(uuids)) { + executeSetScanMode(true, false); } } else { sendExceptionEncountered( info, "uniqueDeviceId not found.", ExceptionErrorType::UNEXPECTED_INFORMATION_RECEIVED); return; } - } else if (directiveName == UNPAIR_DEVICE.name) { - rapidjson::Value::ConstMemberIterator it; - std::string uuid; - - if (json::jsonUtils::findNode(payload, DEVICE_KEY, &it) && - json::jsonUtils::retrieveValue(it->value, UNIQUE_DEVICE_ID_KEY, &uuid)) { - executeUnpairDevice(uuid); + } else if (directiveName == UNPAIR_DEVICES.name) { + std::unordered_set uuids = retrieveUuidsFromConnectionPayload(payload); + if (!uuids.empty()) { + executeUnpairDevices(uuids); } else { sendExceptionEncountered( info, "uniqueDeviceId not found.", ExceptionErrorType::UNEXPECTED_INFORMATION_RECEIVED); return; } - } else if (directiveName == CONNECT_BY_DEVICE_ID.name) { + } else if (directiveName == SET_DEVICE_CATEGORIES.name) { rapidjson::Value::ConstMemberIterator it; - std::string uuid; - if (json::jsonUtils::findNode(payload, DEVICE_KEY, &it) && - json::jsonUtils::retrieveValue(it->value, UNIQUE_DEVICE_ID_KEY, &uuid)) { - executeConnectByDeviceId(uuid); + if (json::jsonUtils::findNode(payload, DEVICES_KEY, &it) && it->value.IsArray()) { + std::map uuidCategoryMap; + std::string uuid; + std::string category; + + for (auto deviceIt = it->value.Begin(); deviceIt != it->value.End(); deviceIt++) { + if (!json::jsonUtils::retrieveValue(*deviceIt, UNIQUE_DEVICE_ID_KEY, &uuid)) { + ACSDK_ERROR(LX("parsingSetDeviceCategoriesFailed").d("reason", "uuidNotFound")); + continue; + } + if (!json::jsonUtils::retrieveValue(*deviceIt, DEVICE_CATEGORY_KEY, &category)) { + ACSDK_ERROR(LX("parsingSetDeviceCategoriesFailed").d("reason", "categoryNotFound")); + continue; + } + uuidCategoryMap.insert({uuid, category}); + } + + executeSetDeviceCategories(uuidCategoryMap); + } else { + sendExceptionEncountered( + info, "uniqueDeviceId not found.", ExceptionErrorType::UNEXPECTED_INFORMATION_RECEIVED); + return; + } + } else if (directiveName == CONNECT_BY_DEVICE_IDS.name) { + std::unordered_set uuids = retrieveUuidsFromConnectionPayload(payload); + if (!uuids.empty()) { + executeConnectByDeviceIds(uuids); } else { sendExceptionEncountered( info, "uniqueDeviceId not found.", ExceptionErrorType::UNEXPECTED_INFORMATION_RECEIVED); @@ -1163,35 +1538,92 @@ void Bluetooth::handleDirective(std::shared_ptr info, "profileName not found.", ExceptionErrorType::UNEXPECTED_INFORMATION_RECEIVED); return; } - } else if (directiveName == DISCONNECT_DEVICE.name) { - rapidjson::Value::ConstMemberIterator it; - std::string uuid; - - if (json::jsonUtils::findNode(payload, DEVICE_KEY, &it) && - json::jsonUtils::retrieveValue(it->value, UNIQUE_DEVICE_ID_KEY, &uuid)) { - executeDisconnectDevice(uuid); + } else if (directiveName == DISCONNECT_DEVICES.name) { + std::unordered_set uuids = retrieveUuidsFromConnectionPayload(payload); + if (!uuids.empty()) { + executeDisconnectDevices(uuids); } else { sendExceptionEncountered( info, "uniqueDeviceId not found.", ExceptionErrorType::UNEXPECTED_INFORMATION_RECEIVED); return; } } else if (directiveName == PLAY.name) { - executePlay(); + rapidjson::Value::ConstMemberIterator it; + std::string uuid; + + if (json::jsonUtils::findNode(payload, DEVICE_KEY, &it) && + json::jsonUtils::retrieveValue(it->value, UNIQUE_DEVICE_ID_KEY, &uuid)) { + auto device = retrieveDeviceByUuid(uuid); + if (!device) { + ACSDK_ERROR(LX(__func__).d("reason", "deviceNotFound").d("uuid", uuid)); + executeSendMediaControlPlayFailed(device); + } + executePlay(device); + } else { + sendExceptionEncountered( + info, "uniqueDeviceId not found.", ExceptionErrorType::UNEXPECTED_INFORMATION_RECEIVED); + return; + } } else if (directiveName == STOP.name) { - executeStop(); + rapidjson::Value::ConstMemberIterator it; + std::string uuid; + + if (json::jsonUtils::findNode(payload, DEVICE_KEY, &it) && + json::jsonUtils::retrieveValue(it->value, UNIQUE_DEVICE_ID_KEY, &uuid)) { + auto device = retrieveDeviceByUuid(uuid); + if (!device) { + ACSDK_ERROR(LX(__func__).d("reason", "deviceNotFound").d("uuid", uuid)); + executeSendMediaControlStopFailed(device); + } + executeStop(device); + } else { + sendExceptionEncountered( + info, "uniqueDeviceId not found.", ExceptionErrorType::UNEXPECTED_INFORMATION_RECEIVED); + return; + } } else if (directiveName == NEXT.name) { if (FocusState::FOREGROUND == m_focusState) { - executeNext(); + rapidjson::Value::ConstMemberIterator it; + std::string uuid; + + if (json::jsonUtils::findNode(payload, DEVICE_KEY, &it) && + json::jsonUtils::retrieveValue(it->value, UNIQUE_DEVICE_ID_KEY, &uuid)) { + auto device = retrieveDeviceByUuid(uuid); + if (!device) { + ACSDK_ERROR(LX(__func__).d("reason", "deviceNotFound").d("uuid", uuid)); + executeSendMediaControlNextFailed(device); + } + executeNext(device); + } else { + sendExceptionEncountered( + info, "uniqueDeviceId not found.", ExceptionErrorType::UNEXPECTED_INFORMATION_RECEIVED); + return; + } } else { // The queue is drained when we enter the foreground. - m_cmdQueue.push_back(AVRCPCommand::NEXT); + m_cmdQueue.push_back(MediaCommand::NEXT); } } else if (directiveName == PREVIOUS.name) { if (FocusState::FOREGROUND == m_focusState) { - executePrevious(); + rapidjson::Value::ConstMemberIterator it; + std::string uuid; + + if (json::jsonUtils::findNode(payload, DEVICE_KEY, &it) && + json::jsonUtils::retrieveValue(it->value, UNIQUE_DEVICE_ID_KEY, &uuid)) { + auto device = retrieveDeviceByUuid(uuid); + if (!device) { + ACSDK_ERROR(LX(__func__).d("reason", "deviceNotFound").d("uuid", uuid)); + executeSendMediaControlPreviousFailed(device); + } + executePrevious(device); + } else { + sendExceptionEncountered( + info, "uniqueDeviceId not found.", ExceptionErrorType::UNEXPECTED_INFORMATION_RECEIVED); + return; + } } else { // The queue is drained when we enter the foreground. - m_cmdQueue.push_back(AVRCPCommand::PREVIOUS); + m_cmdQueue.push_back(MediaCommand::PREVIOUS); } } else { sendExceptionEncountered(info, "Unexpected Directive", ExceptionErrorType::UNEXPECTED_INFORMATION_RECEIVED); @@ -1202,192 +1634,278 @@ void Bluetooth::handleDirective(std::shared_ptr }); } -void Bluetooth::executePlay() { +void Bluetooth::executePlay(std::shared_ptr device) { ACSDK_DEBUG5(LX(__func__)); - if (!m_activeDevice) { - ACSDK_ERROR(LX(__func__).d("reason", "nullActiveDevice")); - executeSendMediaControlPlayFailed(); + if (!device) { + ACSDK_ERROR(LX(__func__).d("reason", "nullDevice")); return; } - auto avrcpTarget = m_activeDevice->getAVRCPTarget(); + auto avrcpTarget = getService(device); + if (!avrcpTarget) { ACSDK_ERROR(LX(__func__).d("reason", "notSupported")); - executeSendMediaControlPlayFailed(); + executeSendMediaControlPlayFailed(device); return; } - // Some applications treat PLAY/PAUSE as a toggle. Don't send if we're already about to play. - if (StreamingState::ACTIVE == m_streamingState || StreamingState::PENDING_ACTIVE == m_streamingState) { - executeSendMediaControlPlaySucceeded(); - return; - } + if (device == m_activeA2DPDevice) { + // Some applications treat PLAY/PAUSE as a toggle. Don't send if we're already about to play. + if (StreamingState::ACTIVE == m_streamingState || StreamingState::PENDING_ACTIVE == m_streamingState) { + executeSendMediaControlPlaySucceeded(device); + return; + } - bool success = true; - /// This means that we have not yet sent an AVRCP play command yet. Do so. - if (StreamingState::PAUSED == m_streamingState || StreamingState::INACTIVE == m_streamingState) { - success = avrcpTarget->play(); - } + bool success = true; + /// This means that we have not yet sent an AVRCP play command yet. Do so. + if (StreamingState::PAUSED == m_streamingState || StreamingState::INACTIVE == m_streamingState) { + success = avrcpTarget->play(); + } - if (success) { - m_streamingState = StreamingState::PENDING_ACTIVE; - executeSendMediaControlPlaySucceeded(); - if (!m_focusManager->acquireChannel(CHANNEL_NAME, shared_from_this(), ACTIVITY_ID)) { - ACSDK_ERROR(LX(__func__).d("reason", "acquireChannelFailed")); + if (success) { + m_streamingState = StreamingState::PENDING_ACTIVE; + executeSendMediaControlPlaySucceeded(device); + executeAcquireFocus(__func__); + } else { + executeSendMediaControlPlayFailed(device); } } else { - executeSendMediaControlPlayFailed(); + if (avrcpTarget->play()) { + executeSendMediaControlPlaySucceeded(device); + } else { + executeSendMediaControlPlayFailed(device); + } } } -void Bluetooth::executeStop() { +void Bluetooth::executeStop(std::shared_ptr device) { ACSDK_DEBUG5(LX(__func__)); - if (!m_activeDevice) { - ACSDK_ERROR(LX(__func__).d("reason", "nullActiveDevice")); + if (!device) { + ACSDK_ERROR(LX(__func__).d("reason", "nullDevice")); return; } - auto avrcpTarget = m_activeDevice->getAVRCPTarget(); + auto avrcpTarget = getService(device); + if (!avrcpTarget) { ACSDK_ERROR(LX(__func__).d("reason", "notSupported")); - executeSendMediaControlStopFailed(); + executeSendMediaControlStopFailed(device); return; } - // Some applications treat PLAY/PAUSE as a toggle. Don't send if we're already paused. - if (StreamingState::PAUSED == m_streamingState || StreamingState::PENDING_PAUSED == m_streamingState) { - executeSendMediaControlStopSucceeded(); - m_focusManager->releaseChannel(CHANNEL_NAME, shared_from_this()); - return; - } + if (device == m_activeA2DPDevice) { + // Some applications treat PLAY/PAUSE as a toggle. Don't send if we're already paused. + if (StreamingState::PAUSED == m_streamingState || StreamingState::PENDING_PAUSED == m_streamingState) { + executeSendMediaControlStopSucceeded(device); + executeReleaseFocus(__func__); + return; + } - bool success = true; - if (StreamingState::ACTIVE == m_streamingState) { - success = avrcpTarget->pause(); - } + bool success = true; + if (StreamingState::ACTIVE == m_streamingState) { + success = avrcpTarget->pause(); + } - if (success) { - m_streamingState = StreamingState::PENDING_PAUSED; - executeSendMediaControlStopSucceeded(); + if (success) { + m_streamingState = StreamingState::PENDING_PAUSED; + executeSendMediaControlStopSucceeded(device); + } else { + executeSendMediaControlStopFailed(device); + } + + // Even if we failed to stop the stream, release the channel so we stop audio playback. + executeReleaseFocus(__func__); } else { - executeSendMediaControlStopFailed(); + if (avrcpTarget->pause()) { + executeSendMediaControlStopSucceeded(device); + } else { + executeSendMediaControlStopFailed(device); + } } - - // Even if we failed to stop the stream, release the channel so we stop audio playback. - m_focusManager->releaseChannel(CHANNEL_NAME, shared_from_this()); } -void Bluetooth::executeNext() { +void Bluetooth::executeNext(std::shared_ptr device) { ACSDK_DEBUG5(LX(__func__)); - if (!m_activeDevice) { - ACSDK_ERROR(LX(__func__).d("reason", "nullActiveDevice")); + if (!device) { + ACSDK_ERROR(LX(__func__).d("reason", "nullDevice")); return; } - auto avrcpTarget = m_activeDevice->getAVRCPTarget(); + auto avrcpTarget = getService(device); + if (!avrcpTarget) { ACSDK_ERROR(LX(__func__).d("reason", "notSupported")); - executeSendMediaControlNextFailed(); + executeSendMediaControlNextFailed(device); return; } if (avrcpTarget->next()) { - executeSendMediaControlNextSucceeded(); + executeSendMediaControlNextSucceeded(device); } else { - executeSendMediaControlNextFailed(); + executeSendMediaControlNextFailed(device); } } -void Bluetooth::executePrevious() { +void Bluetooth::executePrevious(std::shared_ptr device) { ACSDK_DEBUG5(LX(__func__)); - if (!m_activeDevice) { - ACSDK_ERROR(LX(__func__).d("reason", "nullActiveDevice")); + if (!device) { + ACSDK_ERROR(LX(__func__).d("reason", "nullDevice")); return; } - auto avrcpTarget = m_activeDevice->getAVRCPTarget(); + auto avrcpTarget = getService(device); + if (!avrcpTarget) { ACSDK_ERROR(LX(__func__).d("reason", "notSupported")); - executeSendMediaControlPreviousFailed(); + executeSendMediaControlPreviousFailed(device); return; } if (avrcpTarget->previous()) { - executeSendMediaControlPreviousSucceeded(); + executeSendMediaControlPreviousSucceeded(device); } else { - executeSendMediaControlPreviousFailed(); + executeSendMediaControlPreviousFailed(device); } } -bool Bluetooth::executePairDevice(const std::string& uuid) { +bool Bluetooth::executePairDevices(const std::unordered_set& uuids) { ACSDK_DEBUG5(LX(__func__)); - auto device = retrieveDeviceByUuid(uuid); - if (!device) { - ACSDK_ERROR(LX(__func__).d("reason", "deviceNotFound").d("uuid", uuid)); - return false; - } + bool pairingSuccess = true; - if (executeFunctionOnDevice(device, &BluetoothDeviceInterface::pair)) { - m_lastPairMac = device->getMac(); - executeSendPairDeviceSucceeded(device); - return true; - } else { - executeSendPairDeviceFailed(); - return false; + for (const auto& uuid : uuids) { + auto device = retrieveDeviceByUuid(uuid); + if (!device) { + ACSDK_ERROR(LX(__func__).d("reason", "deviceNotFound").d("uuid", uuid)); + executeSendPairDevicesFailed({uuid}); + pairingSuccess = false; + continue; + } + + DeviceCategory category = DeviceCategory::UNKNOWN; + if (!retrieveDeviceCategoryByUuid(uuid, &category)) { + ACSDK_ERROR(LX(__func__).d("reason", "retrieveDeviceCategoryFailed")); + pairingSuccess = false; + continue; + } + + if (executeFunctionOnDevice(device, &BluetoothDeviceInterface::pair)) { + executeInsertBluetoothEventState( + device, DeviceState::PAIRED, Optional(), Optional()); + /** + * If pairing is successful, connect the device if itself doesn't handle connection logic. + */ + auto connectionRule = retrieveConnectionRuleByUuid(uuid); + if (connectionRule && connectionRule->shouldExplicitlyConnect()) { + executeConnectByDeviceIds({uuid}); + } + } else { + executeSendPairDevicesFailed({uuid}); + pairingSuccess = false; + } } + return pairingSuccess; } -bool Bluetooth::executeUnpairDevice(const std::string& uuid) { +bool Bluetooth::executeUnpairDevices(const std::unordered_set& uuids) { ACSDK_DEBUG5(LX(__func__)); - auto device = retrieveDeviceByUuid(uuid); - if (!device) { - ACSDK_ERROR(LX(__func__).d("reason", "deviceNotFound").d("uuid", uuid)); - return false; - } + bool unpairingSuccess = true; - // If the device is connected, disconnect it before unpairing - if (device->isConnected()) { - executeDisconnectDevice(uuid); - } + for (const auto& uuid : uuids) { + auto device = retrieveDeviceByUuid(uuid); + if (!device) { + ACSDK_ERROR(LX(__func__).d("reason", "deviceNotFound").d("uuid", uuid)); + executeSendUnpairDevicesFailed({uuid}); + unpairingSuccess = false; + continue; + } - if (executeFunctionOnDevice(device, &BluetoothDeviceInterface::unpair)) { - m_lastPairMac.clear(); - m_activeDevice.reset(); + DeviceCategory category = DeviceCategory::UNKNOWN; + if (!retrieveDeviceCategoryByUuid(uuid, &category)) { + ACSDK_ERROR(LX(__func__).d("reason", "retrieveDeviceCategoryFailed")); + unpairingSuccess = false; + continue; + } - executeSendUnpairDeviceSucceeded(device); - return true; - } else { - executeSendUnpairDeviceFailed(); - return false; + /** + * If the device is connected, disconnect it before unpairing if itself doesn't handle the disconnection logic. + */ + auto connectionRule = retrieveConnectionRuleByUuid(uuid); + if (device->isConnected()) { + if (connectionRule && connectionRule->shouldExplicitlyDisconnect()) { + executeDisconnectDevices({uuid}); + } + } + + if (executeFunctionOnDevice(device, &BluetoothDeviceInterface::unpair)) { + executeInsertBluetoothEventState( + device, DeviceState::UNPAIRED, Optional(), Optional()); + } else { + executeSendUnpairDevicesFailed({uuid}); + unpairingSuccess = false; + } } + return unpairingSuccess; } -void Bluetooth::executeConnectByDeviceId(const std::string& uuid) { +std::map Bluetooth::executeSetDeviceCategories( + const std::map& uuidCategoryMap) { ACSDK_DEBUG5(LX(__func__)); - auto device = retrieveDeviceByUuid(uuid); - if (!device) { - ACSDK_ERROR(LX(__func__).d("reason", "deviceNotFound").d("uuid", uuid)); - return; + std::map uuidCategorySucceededMap; + std::map uuidCategoryFailedMap; + + for (const auto& uuidCategory : uuidCategoryMap) { + if (m_db->updateByCategory(uuidCategory.first, uuidCategory.second)) { + uuidCategorySucceededMap.insert({uuidCategory.first, uuidCategory.second}); + } else { + uuidCategoryFailedMap.insert({uuidCategory.first, uuidCategory.second}); + } } - bool supportsA2DP = supportsAvsProfile(device, AVS_A2DP); - - if (!supportsA2DP) { - ACSDK_INFO(LX(__func__).d("reason", "noSupportedA2DPRoles").m("Connect Request Rejected")); + if (!uuidCategorySucceededMap.empty()) { + executeSetDeviceCategoriesSucceeded(uuidCategorySucceededMap); } - if (supportsA2DP && executeFunctionOnDevice(device, &BluetoothDeviceInterface::connect)) { - executeOnDeviceConnect(device); - executeSendConnectByDeviceIdSucceeded(device, Requester::CLOUD); - } else { - executeSendConnectByDeviceIdFailed(device, Requester::CLOUD); + if (!uuidCategoryFailedMap.empty()) { + executeSetDeviceCategoriesFailed(uuidCategoryFailedMap); + } + + return uuidCategoryFailedMap; +} + +void Bluetooth::executeConnectByDeviceIds(const std::unordered_set& uuids) { + ACSDK_DEBUG5(LX(__func__)); + + for (const auto& uuid : uuids) { + auto device = retrieveDeviceByUuid(uuid); + std::unordered_set> devices; + if (!device) { + ACSDK_ERROR(LX(__func__).d("reason", "deviceNotFound").d("uuid", uuid)); + executeSendConnectByDeviceIdsFailed({uuid}, Requester::CLOUD); + continue; + } + + devices.insert(device); + + if (device->isConnected()) { + executeSendConnectByDeviceIdsSucceeded(devices, Requester::CLOUD); + continue; + } + + if (executeFunctionOnDevice(device, &BluetoothDeviceInterface::connect)) { + executeOnDeviceConnect(device, true); + executeInsertBluetoothEventState( + device, DeviceState::CONNECTED, Optional(Requester::CLOUD), Optional()); + + } else { + executeSendConnectByDeviceIdsFailed({uuid}, Requester::CLOUD); + } } } @@ -1402,114 +1920,180 @@ void Bluetooth::executeConnectByProfile(const std::string& profileName, const st return; } - bool matchFound = false; - std::shared_ptr matchedDevice; + std::list> matchedDevices; for (const auto& mac : descendingMacs) { + std::string truncatedMac = truncateWithDefault(mac); auto device = retrieveDeviceByMac(mac); if (!device) { - ACSDK_ERROR(LX(__func__).d("reason", "deviceNotFound").d("mac", mac)); + ACSDK_ERROR(LX(__func__).d("reason", "deviceNotFound").d("mac", truncatedMac)); continue; } /* * We're only connecting devices that have been - * previously connected and currently paired. + * previously connected, have the target profile that matches requirement + * and are currently paired. */ if (!device->isPaired()) { - ACSDK_INFO(LX(__func__).d("reason", "deviceUnpaired").d("mac", mac)); + ACSDK_DEBUG0(LX(__func__).d("reason", "deviceUnpaired").d("mac", truncatedMac)); continue; } - matchFound = supportsAvsProfile(device, profileName); - if (matchFound) { - matchedDevice = device; + if (supportsAvsProfile(device, profileName)) { + matchedDevices.push_back(device); + } + if (matchedDevices.size() == MAX_CONNECT_BY_PROFILE_COUNT) { break; } } - if (matchFound && executeFunctionOnDevice(matchedDevice, &BluetoothDeviceInterface::connect)) { - executeOnDeviceConnect(matchedDevice); - executeSendConnectByProfileSucceeded(matchedDevice, profileName, Requester::CLOUD); - } else { + bool deviceConnected = false; + for (auto& matchedDevice : matchedDevices) { + if (executeFunctionOnDevice(matchedDevice, &BluetoothDeviceInterface::connect)) { + deviceConnected = true; + executeOnDeviceConnect(matchedDevice); + executeInsertBluetoothEventState( + matchedDevice, + DeviceState::CONNECTED, + Optional(Requester::CLOUD), + Optional(profileName)); + break; + } + } + if (!deviceConnected) { executeSendConnectByProfileFailed(profileName, Requester::CLOUD); } } -void Bluetooth::executeOnDeviceConnect(std::shared_ptr device) { +void Bluetooth::executeOnDeviceConnect(std::shared_ptr device, bool shouldNotifyConnection) { ACSDK_DEBUG5(LX(__func__)); - - // Currently there is an active device. Disconnect it since the new device will have priority. - if (m_activeDevice) { - ACSDK_DEBUG(LX(__func__).d("reason", "activeDeviceExists")); - if (m_activeDevice->disconnect().get()) { - executeOnDeviceDisconnect(Requester::DEVICE); - } else { - // Failed to disconnect activeDevice, user will have to manually disconnect the device. - ACSDK_ERROR( - LX(__func__).d("reason", "disconnectExistingActiveDeviceFailed").d("mac", m_activeDevice->getMac())); - } - } - - m_activeDevice = device; - - // Notify observers when a bluetooth device is connected. - for (const auto& observer : m_observers) { - observer->onActiveDeviceConnected(generateDeviceAttributes(m_activeDevice)); + if (!device) { + ACSDK_ERROR(LX(__func__).d("reason", "nullDevice")); + return; } std::string uuid; if (!retrieveUuid(device->getMac(), &uuid)) { - ACSDK_ERROR(LX(__func__).d("reason", "retrieveUuidFailed")); + ACSDK_ERROR(LX(__func__).d("reason", "retrieveDeviceUuidFailed")); return; } + DeviceCategory category = DeviceCategory::UNKNOWN; + if (!retrieveDeviceCategoryByUuid(uuid, &category)) { + ACSDK_ERROR(LX(__func__).d("reason", "retrieveDeviceCategoryFailed")); + return; + } + ACSDK_DEBUG5(LX(__func__).d("uuid", uuid).d("deviceCategory", category)); + + auto connectionRule = retrieveConnectionRuleByUuid(uuid); + if (connectionRule) { + std::set> devicesToDisconnect = + connectionRule->devicesToDisconnect(m_connectedDevices); + + for (const auto& deviceToDisconnect : devicesToDisconnect) { + if (deviceToDisconnect == device) { + continue; + } + + auto disconnectionFuture = deviceToDisconnect->disconnect(); + if (waitOnFuture(std::move(disconnectionFuture), "Disconnect the connected device")) { + executeOnDeviceDisconnect(deviceToDisconnect, Requester::DEVICE); + } else { + // Failed to disconnect the device with same category , user will have to manually disconnect the + // device. + std::string truncatedMac = truncateWithDefault(deviceToDisconnect->getMac()); + ACSDK_ERROR(LX(__func__).d("reason", "disconnectExistingActiveDeviceFailed").d("mac", truncatedMac)); + } + } + } + + if (getService(device) || getService(device)) { + m_activeA2DPDevice = device; + } + + if (m_connectedDevices.find(category) != m_connectedDevices.end()) { + auto& connectedDevices = m_connectedDevices[category]; + connectedDevices.insert(device); + } else { + m_connectedDevices[category] = {device}; + } + + // Notify observers when a bluetooth device is connected. + if (shouldNotifyConnection) { + for (const auto& observer : m_observers) { + observer->onActiveDeviceConnected(generateDeviceAttributes(device)); + } + } // Reinsert into the database for ordering. m_db->insertByMac(device->getMac(), uuid, true); } -void Bluetooth::executeOnDeviceDisconnect(avsCommon::avs::Requester requester) { +void Bluetooth::executeOnDeviceDisconnect( + std::shared_ptr device, + avsCommon::avs::Requester requester) { ACSDK_DEBUG5(LX(__func__)); - if (!m_activeDevice) { - ACSDK_WARN(LX(__func__).d("reason", "noActiveDevice")); + std::string uuid; + if (!retrieveUuid(device->getMac(), &uuid)) { + ACSDK_ERROR(LX(__func__).d("reason", "retrieveDeviceUuidFailed")); + return; + } + DeviceCategory category = DeviceCategory::UNKNOWN; + if (!retrieveDeviceCategoryByUuid(uuid, &category)) { + ACSDK_ERROR(LX(__func__).d("reason", "retrieveDeviceCategoryFailed")); return; } - if (StreamingState::INACTIVE != m_streamingState) { - ACSDK_DEBUG5(LX(__func__) - .d("currentState", streamingStateToString(m_streamingState)) - .d("newState", streamingStateToString(StreamingState::INACTIVE))); - // Needs to be sent while we still have an activeDevice in the Context. - if (StreamingState::ACTIVE == m_streamingState) { - executeSendStreamingEnded(m_activeDevice); + ACSDK_DEBUG5(LX(__func__).d("uuid", uuid).d("deviceCategory", category)); + + if (device == m_activeA2DPDevice) { + if (StreamingState::INACTIVE != m_streamingState) { + ACSDK_DEBUG5(LX(__func__) + .d("currentState", streamingStateToString(m_streamingState)) + .d("newState", streamingStateToString(StreamingState::INACTIVE))); + // Needs to be sent while we still have an active A2DP source device in the Context. + if (StreamingState::ACTIVE == m_streamingState) { + executeSendStreamingEnded(m_activeA2DPDevice); + executeReleaseFocus(__func__); + } + m_streamingState = StreamingState::INACTIVE; } - m_streamingState = StreamingState::INACTIVE; + m_activeA2DPDevice.reset(); } - auto device = m_activeDevice; + if (m_connectedDevices.find(category) != m_connectedDevices.end()) { + auto& connectedDevices = m_connectedDevices[category]; + connectedDevices.erase(device); + if (connectedDevices.empty()) { + m_connectedDevices.erase(category); + } + } // Notify observers when a bluetooth device is disconnected. for (const auto& observer : m_observers) { - observer->onActiveDeviceDisconnected(generateDeviceAttributes(m_activeDevice)); + observer->onActiveDeviceDisconnected(generateDeviceAttributes(device)); } - m_activeDevice.reset(); - executeSendDisconnectDeviceSucceeded(device, requester); + executeInsertBluetoothEventState( + device, DeviceState::DISCONNECTED, Optional(requester), Optional()); } -void Bluetooth::executeDisconnectDevice(const std::string& uuid) { +void Bluetooth::executeDisconnectDevices(const std::unordered_set& uuids) { ACSDK_DEBUG5(LX(__func__)); - auto device = retrieveDeviceByUuid(uuid); - if (!device) { - ACSDK_ERROR(LX(__func__).d("reason", "deviceNotFound").d("uuid", uuid)); - return; - } + for (const auto& uuid : uuids) { + auto device = retrieveDeviceByUuid(uuid); + if (!device) { + ACSDK_ERROR(LX(__func__).d("reason", "deviceNotFound").d("uuid", uuid)); + executeSendDisconnectDevicesFailed({uuid}, Requester::CLOUD); + continue; + } - if (executeFunctionOnDevice(device, &BluetoothDeviceInterface::disconnect)) { - executeOnDeviceDisconnect(Requester::CLOUD); - } else { - executeSendDisconnectDeviceFailed(device, Requester::CLOUD); + if (executeFunctionOnDevice(device, &BluetoothDeviceInterface::disconnect)) { + executeOnDeviceDisconnect(device, Requester::CLOUD); + } else { + executeSendDisconnectDevicesFailed({uuid}, Requester::CLOUD); + } } } @@ -1541,7 +2125,7 @@ void Bluetooth::sendExceptionEncountered( removeDirective(info); } -bool Bluetooth::executeSetScanMode(bool scanning) { +bool Bluetooth::executeSetScanMode(bool scanning, bool shouldReport) { ACSDK_DEBUG5(LX(__func__)); if (!m_deviceManager->getHostController()) { @@ -1556,10 +2140,14 @@ bool Bluetooth::executeSetScanMode(bool scanning) { return false; } - bool success = future.get(); + bool success = waitOnFuture(std::move(future)); if (success) { // If we're scanning, there will be more devices. If we're not, then there won't be. - executeSendScanDevicesUpdated(m_deviceManager->getDiscoveredDevices(), scanning); + if (shouldReport) { + executeSendScanDevicesReport(m_deviceManager->getDiscoveredDevices(), scanning); + } + m_scanningTransitionState = + scanning ? ScanningTransitionState::ACTIVE : ScanningTransitionState::PENDING_INACTIVE; } else { ACSDK_ERROR(LX("executeSetScanModeFailed").d("scanning", scanning)); // This event is only sent in response to a failed processing of "ScanDevices" directive. @@ -1592,7 +2180,7 @@ bool Bluetooth::executeSetDiscoverableMode(bool discoverable) { return false; } - return future.get(); + return waitOnFuture(std::move(future)); } bool Bluetooth::executeFunctionOnDevice( @@ -1604,17 +2192,25 @@ bool Bluetooth::executeFunctionOnDevice( return false; } - ACSDK_DEBUG5(LX(__func__).d("mac", device->getMac())); + std::string truncatedMac = truncateWithDefault(device->getMac()); + ACSDK_DEBUG5(LX(__func__).d("mac", truncatedMac)); - return function(device).get(); + std::stringstream description; + description << "executeFunctionOnDevice mac=" << device->getMac(); + auto future = function(device); + return waitOnFuture(std::move(future), description.str()); } -void Bluetooth::onPlaybackStarted(MediaPlayerObserverInterface::SourceId id) { +void Bluetooth::onFirstByteRead(MediaPlayerObserverInterface::SourceId id, const MediaPlayerState&) { + ACSDK_DEBUG5(LX(__func__).d("sourceId", id)); +} + +void Bluetooth::onPlaybackStarted(MediaPlayerObserverInterface::SourceId id, const MediaPlayerState&) { ACSDK_DEBUG5(LX(__func__).d("sourceId", id)); m_executor.submit([this] { // It means we were pending a pause before the onPlaybackStarted was received. if (m_streamingState == StreamingState::PENDING_PAUSED) { - executeSendStreamingStarted(m_activeDevice); + executeSendStreamingStarted(m_activeA2DPDevice); if (!m_mediaPlayer->stop(m_sourceId)) { ACSDK_ERROR(LX("onPlaybackStartedLambdaFailed").d("reason", "stopFailed")); cleanupMediaSource(); @@ -1622,41 +2218,45 @@ void Bluetooth::onPlaybackStarted(MediaPlayerObserverInterface::SourceId id) { return; } m_streamingState = StreamingState::ACTIVE; - if (!m_activeDevice) { + if (!m_activeA2DPDevice) { ACSDK_ERROR(LX(__func__).d("reason", "noActiveDevice")); } else { - executeSendStreamingStarted(m_activeDevice); + executeSendStreamingStarted(m_activeA2DPDevice); } }); } -void Bluetooth::onPlaybackStopped(MediaPlayerObserverInterface::SourceId id) { +void Bluetooth::onPlaybackStopped(MediaPlayerObserverInterface::SourceId id, const MediaPlayerState&) { ACSDK_DEBUG5(LX(__func__).d("sourceId", id)); m_executor.submit([this] { // Playback has been stopped, cleanup the source. cleanupMediaSource(); - if (m_activeDevice) { + if (m_activeA2DPDevice) { m_streamingState = StreamingState::PAUSED; - executeSendStreamingEnded(m_activeDevice); + executeSendStreamingEnded(m_activeA2DPDevice); } else { m_streamingState = StreamingState::INACTIVE; } }); } -void Bluetooth::onPlaybackFinished(MediaPlayerObserverInterface::SourceId id) { +void Bluetooth::onPlaybackFinished(MediaPlayerObserverInterface::SourceId id, const MediaPlayerState&) { ACSDK_DEBUG5(LX(__func__).d("sourceId", id)); m_executor.submit([this] { m_streamingState = StreamingState::INACTIVE; cleanupMediaSource(); - if (m_activeDevice) { - executeSendStreamingEnded(m_activeDevice); + if (m_activeA2DPDevice) { + executeSendStreamingEnded(m_activeA2DPDevice); } }); } -void Bluetooth::onPlaybackError(MediaPlayerObserverInterface::SourceId id, const ErrorType& type, std::string error) { +void Bluetooth::onPlaybackError( + MediaPlayerObserverInterface::SourceId id, + const ErrorType& type, + std::string error, + const MediaPlayerState&) { ACSDK_DEBUG5(LX(__func__).d("id", id).d("type", type).d("error", error)); m_executor.submit([this, id] { @@ -1668,7 +2268,7 @@ void Bluetooth::onPlaybackError(MediaPlayerObserverInterface::SourceId id, const } // Events. -void Bluetooth::executeSendScanDevicesUpdated( +void Bluetooth::executeSendScanDevicesReport( const std::list>& devices, bool hasMore) { rapidjson::Document payload(rapidjson::kObjectType); @@ -1676,17 +2276,18 @@ void Bluetooth::executeSendScanDevicesUpdated( ACSDK_DEBUG5(LX(__func__).d("count", devices.size())); for (const auto& device : devices) { - ACSDK_DEBUG(LX("foundDevice").d("deviceMac", device->getMac())); + std::string truncatedMac = truncateWithDefault(device->getMac()); + ACSDK_DEBUG(LX("foundDevice").d("deviceMac", truncatedMac)); std::string uuid; if (!retrieveUuid(device->getMac(), &uuid)) { - ACSDK_ERROR(LX("executeSendScanDevicesUpdatedFailed").d("reason", "retrieveUuidFailed")); + ACSDK_ERROR(LX("executeSendScanDevicesReportFailed").d("reason", "retrieveUuidFailed")); return; } if (device->isPaired()) { ACSDK_DEBUG(LX(__func__) .d("reason", "deviceAlreadyPaired") - .d("mac", device->getMac()) + .d("mac", truncatedMac) .d("uuid", uuid) .d("action", "ommitting")); continue; @@ -1695,14 +2296,26 @@ void Bluetooth::executeSendScanDevicesUpdated( rapidjson::Value deviceJson(rapidjson::kObjectType); deviceJson.AddMember(UNIQUE_DEVICE_ID_KEY, uuid, payload.GetAllocator()); deviceJson.AddMember(FRIENDLY_NAME_KEY, device->getFriendlyName(), payload.GetAllocator()); + deviceJson.AddMember(TRUNCATED_MAC_ADDRESS_KEY, truncatedMac, payload.GetAllocator()); - std::unique_ptr mac = MacAddressString::create(device->getMac()); - if (mac) { - deviceJson.AddMember(TRUNCATED_MAC_ADDRESS_KEY, mac->getTruncatedString(), payload.GetAllocator()); - } else { - ACSDK_ERROR(LX("appendingMacAddressFailed").d("reason", "invalidFormat")); + rapidjson::Value metadataJson(rapidjson::kObjectType); + auto metadata = device->getDeviceMetaData(); + if (metadata.vendorId.hasValue()) { + metadataJson.AddMember(VENDOR_ID_KEY, metadata.vendorId.value(), payload.GetAllocator()); + } + if (metadata.productId.hasValue()) { + metadataJson.AddMember(PRODUCT_ID_KEY, metadata.productId.value(), payload.GetAllocator()); + } + metadataJson.AddMember(CLASS_OF_DEVICE_KEY, metadata.classOfDevice, payload.GetAllocator()); + if (metadata.vendorDeviceSigId.hasValue()) { + metadataJson.AddMember( + VENDOR_DEVICE_SIG_ID_KEY, metadata.vendorDeviceSigId.value(), payload.GetAllocator()); + } + if (metadata.vendorDeviceId.hasValue()) { + metadataJson.AddMember(VENDOR_DEVICE_ID_KEY, metadata.vendorDeviceId.value(), payload.GetAllocator()); } + deviceJson.AddMember(METADATA_KEY, metadataJson.GetObject(), payload.GetAllocator()); devicesArray.PushBack(deviceJson, payload.GetAllocator()); } @@ -1718,34 +2331,60 @@ void Bluetooth::executeSendScanDevicesUpdated( executeUpdateContext(); - executeQueueEventAndRequestContext(SCAN_DEVICES_UPDATED.name, buffer.GetString()); + executeSendEvent(SCAN_DEVICES_REPORT.name, buffer.GetString()); } void Bluetooth::executeSendScanDevicesFailed() { - executeQueueEventAndRequestContext(SCAN_DEVICES_FAILED.name, EMPTY_PAYLOAD); + executeSendEvent(SCAN_DEVICES_FAILED.name, EMPTY_PAYLOAD); } void Bluetooth::executeSendEnterDiscoverableModeSucceeded() { - executeQueueEventAndRequestContext(ENTER_DISCOVERABLE_MODE_SUCCEEDED.name, EMPTY_PAYLOAD); + executeSendEvent(ENTER_DISCOVERABLE_MODE_SUCCEEDED.name, EMPTY_PAYLOAD); } void Bluetooth::executeSendEnterDiscoverableModeFailed() { - executeQueueEventAndRequestContext(ENTER_DISCOVERABLE_MODE_FAILED.name, EMPTY_PAYLOAD); + executeSendEvent(ENTER_DISCOVERABLE_MODE_FAILED.name, EMPTY_PAYLOAD); } -void Bluetooth::executeSendPairDeviceSucceeded(std::shared_ptr device) { - rapidjson::Document payload(rapidjson::kObjectType); - - std::string uuid; - if (!retrieveUuid(device->getMac(), &uuid)) { - ACSDK_ERROR(LX(__func__).d("reason", "retrieveUuidFailed")); +void Bluetooth::executeSendPairDevicesSucceeded( + const std::unordered_set>& devices) { + if (devices.empty()) { + ACSDK_ERROR(LX(__func__).d("reason", "emptyDevices")); return; } - rapidjson::Value deviceJson(rapidjson::kObjectType); - deviceJson.AddMember(UNIQUE_DEVICE_ID_KEY, uuid, payload.GetAllocator()); - deviceJson.AddMember(FRIENDLY_NAME_KEY, device->getFriendlyName(), payload.GetAllocator()); - payload.AddMember(DEVICE_KEY, deviceJson.GetObject(), payload.GetAllocator()); + rapidjson::Document payload(rapidjson::kObjectType); + rapidjson::Value devicesJson(rapidjson::kArrayType); + + for (const auto& device : devices) { + std::string uuid; + if (!retrieveUuid(device->getMac(), &uuid)) { + ACSDK_ERROR(LX(__func__).d("reason", "retrieveUuidFailed")); + continue; + } + + rapidjson::Value deviceJson(rapidjson::kObjectType); + deviceJson.AddMember(UNIQUE_DEVICE_ID_KEY, uuid, payload.GetAllocator()); + deviceJson.AddMember(FRIENDLY_NAME_KEY, device->getFriendlyName(), payload.GetAllocator()); + + DeviceCategory category = DeviceCategory::UNKNOWN; + if (!retrieveDeviceCategoryByUuid(uuid, &category)) { + ACSDK_WARN(LX(__func__).d("reason", "retrieveDeviceCategoryByUuidFailed")); + category = DeviceCategory::UNKNOWN; + } + deviceJson.AddMember(DEVICE_CATEGORY_KEY, deviceCategoryToString(category), payload.GetAllocator()); + + rapidjson::Value supportedProfiles(rapidjson::kArrayType); + // If this fails, add an empty array. + if (!extractAvsProfiles(device, payload.GetAllocator(), &supportedProfiles)) { + supportedProfiles = rapidjson::Value(rapidjson::kArrayType); + } + deviceJson.AddMember(PROFILES_KEY, supportedProfiles, payload.GetAllocator()); + + devicesJson.PushBack(deviceJson, payload.GetAllocator()); + } + + payload.AddMember(DEVICES_KEY, devicesJson.GetArray(), payload.GetAllocator()); rapidjson::StringBuffer buffer; rapidjson::Writer writer(buffer); @@ -1756,26 +2395,61 @@ void Bluetooth::executeSendPairDeviceSucceeded(std::shared_ptr device) { +void Bluetooth::executeSendPairFailedEvent(const std::string& eventName, const std::unordered_set& uuids) { rapidjson::Document payload(rapidjson::kObjectType); + rapidjson::Value devicesJson(rapidjson::kArrayType); - std::string uuid; - if (!retrieveUuid(device->getMac(), &uuid)) { - ACSDK_ERROR(LX(__func__).d("reason", "retrieveUuidFailed")); + if (!uuids.empty()) { + for (const auto& uuid : uuids) { + rapidjson::Value deviceJson(rapidjson::kObjectType); + deviceJson.AddMember(UNIQUE_DEVICE_ID_KEY, uuid, payload.GetAllocator()); + devicesJson.PushBack(deviceJson, payload.GetAllocator()); + } + + payload.AddMember(DEVICES_KEY, devicesJson.GetArray(), payload.GetAllocator()); + + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + if (!payload.Accept(writer)) { + ACSDK_ERROR(LX(__func__).d("reason", "writerRefusedJsonObject")); + return; + } + + executeUpdateContext(); + + executeSendEvent(eventName, buffer.GetString()); + } +} + +void Bluetooth::executeSendPairDevicesFailed(const std::unordered_set& uuids) { + executeSendPairFailedEvent(PAIR_DEVICES_FAILED.name, uuids); +} + +void Bluetooth::executeSendUnpairDevicesSucceeded( + const std::unordered_set>& devices) { + if (devices.empty()) { + ACSDK_ERROR(LX(__func__).d("reason", "emptyDevices")); return; } + rapidjson::Document payload(rapidjson::kObjectType); + rapidjson::Value devicesJson(rapidjson::kArrayType); - rapidjson::Value deviceJson(rapidjson::kObjectType); - deviceJson.AddMember(UNIQUE_DEVICE_ID_KEY, uuid, payload.GetAllocator()); - deviceJson.AddMember(FRIENDLY_NAME_KEY, device->getFriendlyName(), payload.GetAllocator()); - payload.AddMember(DEVICE_KEY, deviceJson.GetObject(), payload.GetAllocator()); + for (const auto& device : devices) { + std::string uuid; + if (!retrieveUuid(device->getMac(), &uuid)) { + ACSDK_ERROR(LX(__func__).d("reason", "retrieveUuidFailed")); + continue; + } + + rapidjson::Value deviceJson(rapidjson::kObjectType); + deviceJson.AddMember(UNIQUE_DEVICE_ID_KEY, uuid, payload.GetAllocator()); + devicesJson.PushBack(deviceJson, payload.GetAllocator()); + } + + payload.AddMember(DEVICES_KEY, devicesJson.GetArray(), payload.GetAllocator()); rapidjson::StringBuffer buffer; rapidjson::Writer writer(buffer); @@ -1786,28 +2460,112 @@ void Bluetooth::executeSendUnpairDeviceSucceeded(std::shared_ptr& uuids) { + executeSendPairFailedEvent(UNPAIR_DEVICES_FAILED.name, uuids); } -void Bluetooth::executeSendConnectByDeviceIdSucceeded( - std::shared_ptr device, +void Bluetooth::executeSetDeviceCategoriesSucceeded(const std::map& uuidCategoryMap) { + rapidjson::Document payload(rapidjson::kObjectType); + + rapidjson::Value categorizedDevices(rapidjson::kArrayType); + + for (const auto& uuidCategory : uuidCategoryMap) { + rapidjson::Value deviceJson(rapidjson::kObjectType); + deviceJson.AddMember(UNIQUE_DEVICE_ID_KEY, uuidCategory.first, payload.GetAllocator()); + deviceJson.AddMember(DEVICE_CATEGORY_KEY, uuidCategory.second, payload.GetAllocator()); + categorizedDevices.PushBack(deviceJson, payload.GetAllocator()); + } + + payload.AddMember(DEVICES_KEY, categorizedDevices.GetArray(), payload.GetAllocator()); + + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + if (!payload.Accept(writer)) { + ACSDK_ERROR(LX(__func__).d("reason", "writerRefusedJsonObject")); + return; + } + + executeUpdateContext(); + executeQueueEventAndRequestContext(SET_DEVICE_CATEGORIES_SUCCEEDED.name, buffer.GetString()); +} + +void Bluetooth::executeSetDeviceCategoriesFailed(const std::map& uuidCategoryMap) { + rapidjson::Document payload(rapidjson::kObjectType); + + rapidjson::Value categorizedDevices(rapidjson::kArrayType); + + for (const auto& uuidCategory : uuidCategoryMap) { + rapidjson::Value deviceJson(rapidjson::kObjectType); + + deviceJson.AddMember(UNIQUE_DEVICE_ID_KEY, uuidCategory.first, payload.GetAllocator()); + + auto device = retrieveDeviceByUuid(uuidCategory.first); + if (device) { + rapidjson::Value metadataJson(rapidjson::kObjectType); + + auto metadata = device->getDeviceMetaData(); + if (metadata.vendorId.hasValue()) { + metadataJson.AddMember(VENDOR_ID_KEY, metadata.vendorId.value(), payload.GetAllocator()); + } + if (metadata.productId.hasValue()) { + metadataJson.AddMember(PRODUCT_ID_KEY, metadata.productId.value(), payload.GetAllocator()); + } + metadataJson.AddMember(CLASS_OF_DEVICE_KEY, metadata.classOfDevice, payload.GetAllocator()); + if (metadata.vendorDeviceSigId.hasValue()) { + metadataJson.AddMember( + VENDOR_DEVICE_SIG_ID_KEY, metadata.vendorDeviceSigId.value(), payload.GetAllocator()); + } + if (metadata.vendorDeviceId.hasValue()) { + metadataJson.AddMember(VENDOR_DEVICE_ID_KEY, metadata.vendorDeviceId.value(), payload.GetAllocator()); + } + deviceJson.AddMember(METADATA_KEY, metadataJson.GetObject(), payload.GetAllocator()); + } else { + ACSDK_ERROR(LX(__func__).d("reason", "deviceNotFound").d("uuid", uuidCategory.first)); + } + categorizedDevices.PushBack(deviceJson, payload.GetAllocator()); + } + + payload.AddMember(DEVICES_KEY, categorizedDevices.GetObject(), payload.GetAllocator()); + + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + if (!payload.Accept(writer)) { + ACSDK_ERROR(LX(__func__).d("reason", "writerRefusedJsonObject")); + return; + } + + executeUpdateContext(); + executeQueueEventAndRequestContext(SET_DEVICE_CATEGORIES_FAILED.name, buffer.GetString()); +} + +void Bluetooth::executeSendConnectByDeviceIdsSucceeded( + const std::unordered_set>& devices, Requester requester) { - rapidjson::Document payload(rapidjson::kObjectType); - - std::string uuid; - if (!retrieveUuid(device->getMac(), &uuid)) { - ACSDK_ERROR(LX(__func__).d("reason", "retrieveUuidFailed")); + if (devices.empty()) { + ACSDK_ERROR(LX(__func__).d("reason", "emptyDevices")); return; } - rapidjson::Value deviceJson(rapidjson::kObjectType); - deviceJson.AddMember(UNIQUE_DEVICE_ID_KEY, uuid, payload.GetAllocator()); - deviceJson.AddMember(FRIENDLY_NAME_KEY, device->getFriendlyName(), payload.GetAllocator()); - payload.AddMember(DEVICE_KEY, deviceJson.GetObject(), payload.GetAllocator()); + rapidjson::Document payload(rapidjson::kObjectType); + rapidjson::Value devicesJson(rapidjson::kArrayType); + + for (const auto& device : devices) { + std::string uuid; + if (!retrieveUuid(device->getMac(), &uuid)) { + ACSDK_ERROR(LX(__func__).d("reason", "retrieveUuidFailed")); + continue; + } + + rapidjson::Value deviceJson(rapidjson::kObjectType); + deviceJson.AddMember(UNIQUE_DEVICE_ID_KEY, uuid, payload.GetAllocator()); + deviceJson.AddMember(FRIENDLY_NAME_KEY, device->getFriendlyName(), payload.GetAllocator()); + devicesJson.PushBack(deviceJson, payload.GetAllocator()); + } + + payload.AddMember(DEVICES_KEY, devicesJson.GetArray(), payload.GetAllocator()); payload.AddMember(REQUESTER_KEY, requesterToString(requester), payload.GetAllocator()); rapidjson::StringBuffer buffer; @@ -1819,24 +2577,29 @@ void Bluetooth::executeSendConnectByDeviceIdSucceeded( executeUpdateContext(); - executeQueueEventAndRequestContext(CONNECT_BY_DEVICE_ID_SUCCEEDED.name, buffer.GetString()); + executeSendEvent(CONNECT_BY_DEVICE_IDS_SUCCEEDED.name, buffer.GetString()); } -void Bluetooth::executeSendConnectByDeviceIdFailed( - std::shared_ptr device, +void Bluetooth::executeSendConnectFailedEvent( + const std::string& eventName, + const std::unordered_set& uuids, Requester requester) { - rapidjson::Document payload(rapidjson::kObjectType); - - std::string uuid; - if (!retrieveUuid(device->getMac(), &uuid)) { - ACSDK_ERROR(LX(__func__).d("reason", "retrieveUuidFailed")); + if (uuids.empty()) { + ACSDK_ERROR(LX(__func__).d("reason", "emptyUuids").d("eventName", eventName)); return; } - rapidjson::Value deviceJson(rapidjson::kObjectType); - deviceJson.AddMember(UNIQUE_DEVICE_ID_KEY, uuid, payload.GetAllocator()); - deviceJson.AddMember(FRIENDLY_NAME_KEY, device->getFriendlyName(), payload.GetAllocator()); - payload.AddMember(DEVICE_KEY, deviceJson.GetObject(), payload.GetAllocator()); + rapidjson::Document payload(rapidjson::kObjectType); + rapidjson::Value devicesJson(rapidjson::kArrayType); + + for (const auto& uuid : uuids) { + rapidjson::Value deviceJson(rapidjson::kObjectType); + deviceJson.AddMember(UNIQUE_DEVICE_ID_KEY, uuid, payload.GetAllocator()); + deviceJson.AddMember(FRIENDLY_NAME_KEY, "", payload.GetAllocator()); + devicesJson.PushBack(deviceJson, payload.GetAllocator()); + } + + payload.AddMember(DEVICES_KEY, devicesJson.GetArray(), payload.GetAllocator()); payload.AddMember(REQUESTER_KEY, requesterToString(requester), payload.GetAllocator()); rapidjson::StringBuffer buffer; @@ -1848,13 +2611,22 @@ void Bluetooth::executeSendConnectByDeviceIdFailed( executeUpdateContext(); - executeQueueEventAndRequestContext(CONNECT_BY_DEVICE_ID_FAILED.name, buffer.GetString()); + executeSendEvent(eventName, buffer.GetString()); +} + +void Bluetooth::executeSendConnectByDeviceIdsFailed(const std::unordered_set& uuids, Requester requester) { + executeSendConnectFailedEvent(CONNECT_BY_DEVICE_IDS_FAILED.name, uuids, requester); } void Bluetooth::executeSendConnectByProfileSucceeded( std::shared_ptr device, const std::string& profileName, Requester requester) { + if (!device) { + ACSDK_ERROR(LX(__func__).d("reason", "nullDevice")); + return; + } + rapidjson::Document payload(rapidjson::kObjectType); std::string uuid; @@ -1865,10 +2637,9 @@ void Bluetooth::executeSendConnectByProfileSucceeded( rapidjson::Value deviceJson(rapidjson::kObjectType); deviceJson.AddMember(UNIQUE_DEVICE_ID_KEY, uuid, payload.GetAllocator()); - deviceJson.AddMember(FRIENDLY_NAME_KEY, device->getFriendlyName(), payload.GetAllocator()); payload.AddMember(DEVICE_KEY, deviceJson.GetObject(), payload.GetAllocator()); - payload.AddMember(REQUESTER_KEY, requesterToString(requester), payload.GetAllocator()); payload.AddMember(PROFILE_NAME_KEY, profileName, payload.GetAllocator()); + payload.AddMember(REQUESTER_KEY, requesterToString(requester), payload.GetAllocator()); rapidjson::StringBuffer buffer; rapidjson::Writer writer(buffer); @@ -1879,7 +2650,7 @@ void Bluetooth::executeSendConnectByProfileSucceeded( executeUpdateContext(); - executeQueueEventAndRequestContext(CONNECT_BY_PROFILE_SUCCEEDED.name, buffer.GetString()); + executeSendEvent(CONNECT_BY_PROFILE_SUCCEEDED.name, buffer.GetString()); } void Bluetooth::executeSendConnectByProfileFailed(const std::string& profileName, Requester requester) { @@ -1897,24 +2668,34 @@ void Bluetooth::executeSendConnectByProfileFailed(const std::string& profileName executeUpdateContext(); - executeQueueEventAndRequestContext(CONNECT_BY_PROFILE_FAILED.name, buffer.GetString()); + executeSendEvent(CONNECT_BY_PROFILE_FAILED.name, buffer.GetString()); } -void Bluetooth::executeSendDisconnectDeviceSucceeded( - std::shared_ptr device, +void Bluetooth::executeSendDisconnectDevicesSucceeded( + const std::unordered_set>& devices, Requester requester) { - rapidjson::Document payload(rapidjson::kObjectType); - - std::string uuid; - if (!retrieveUuid(device->getMac(), &uuid)) { - ACSDK_ERROR(LX(__func__).d("reason", "retrieveUuidFailed")); + if (devices.empty()) { + ACSDK_ERROR(LX(__func__).d("reason", "emptyDevices")); return; } - rapidjson::Value deviceJson(rapidjson::kObjectType); - deviceJson.AddMember(UNIQUE_DEVICE_ID_KEY, uuid, payload.GetAllocator()); - deviceJson.AddMember(FRIENDLY_NAME_KEY, device->getFriendlyName(), payload.GetAllocator()); - payload.AddMember(DEVICE_KEY, deviceJson.GetObject(), payload.GetAllocator()); + rapidjson::Document payload(rapidjson::kObjectType); + rapidjson::Value devicesJson(rapidjson::kArrayType); + + for (const auto& device : devices) { + std::string uuid; + if (!retrieveUuid(device->getMac(), &uuid)) { + ACSDK_ERROR(LX(__func__).d("reason", "retrieveUuidFailed")); + continue; + } + + rapidjson::Value deviceJson(rapidjson::kObjectType); + deviceJson.AddMember(UNIQUE_DEVICE_ID_KEY, uuid, payload.GetAllocator()); + deviceJson.AddMember(FRIENDLY_NAME_KEY, device->getFriendlyName(), payload.GetAllocator()); + devicesJson.PushBack(deviceJson, payload.GetAllocator()); + } + + payload.AddMember(DEVICES_KEY, devicesJson.GetArray(), payload.GetAllocator()); payload.AddMember(REQUESTER_KEY, requesterToString(requester), payload.GetAllocator()); rapidjson::StringBuffer buffer; @@ -1926,12 +2707,16 @@ void Bluetooth::executeSendDisconnectDeviceSucceeded( executeUpdateContext(); - executeQueueEventAndRequestContext(DISCONNECT_DEVICE_SUCCEEDED.name, buffer.GetString()); + executeSendEvent(DISCONNECT_DEVICES_SUCCEEDED.name, buffer.GetString()); } -void Bluetooth::executeSendDisconnectDeviceFailed( - std::shared_ptr device, - Requester requester) { +void Bluetooth::executeSendDisconnectDevicesFailed(const std::unordered_set& uuids, Requester requester) { + executeSendConnectFailedEvent(DISCONNECT_DEVICES_FAILED.name, uuids, requester); +} + +void Bluetooth::executeSendMediaControlEvent( + const std::string& eventName, + std::shared_ptr device) { rapidjson::Document payload(rapidjson::kObjectType); std::string uuid; @@ -1942,9 +2727,7 @@ void Bluetooth::executeSendDisconnectDeviceFailed( rapidjson::Value deviceJson(rapidjson::kObjectType); deviceJson.AddMember(UNIQUE_DEVICE_ID_KEY, uuid, payload.GetAllocator()); - deviceJson.AddMember(FRIENDLY_NAME_KEY, device->getFriendlyName(), payload.GetAllocator()); payload.AddMember(DEVICE_KEY, deviceJson.GetObject(), payload.GetAllocator()); - payload.AddMember(REQUESTER_KEY, requesterToString(requester), payload.GetAllocator()); rapidjson::StringBuffer buffer; rapidjson::Writer writer(buffer); @@ -1955,87 +2738,111 @@ void Bluetooth::executeSendDisconnectDeviceFailed( executeUpdateContext(); - executeQueueEventAndRequestContext(DISCONNECT_DEVICE_FAILED.name, buffer.GetString()); + executeSendEvent(eventName, buffer.GetString()); } -void Bluetooth::executeSendMediaControlPlaySucceeded() { - executeQueueEventAndRequestContext(MEDIA_CONTROL_PLAY_SUCCEEDED.name, EMPTY_PAYLOAD); +void Bluetooth::executeSendMediaControlPlaySucceeded(std::shared_ptr device) { + executeSendMediaControlEvent(MEDIA_CONTROL_PLAY_SUCCEEDED.name, device); } -void Bluetooth::executeSendMediaControlPlayFailed() { - executeQueueEventAndRequestContext(MEDIA_CONTROL_PLAY_FAILED.name, EMPTY_PAYLOAD); +void Bluetooth::executeSendMediaControlPlayFailed(std::shared_ptr device) { + executeSendMediaControlEvent(MEDIA_CONTROL_PLAY_FAILED.name, device); } -void Bluetooth::executeSendMediaControlStopSucceeded() { - executeQueueEventAndRequestContext(MEDIA_CONTROL_STOP_SUCCEEDED.name, EMPTY_PAYLOAD); +void Bluetooth::executeSendMediaControlStopSucceeded(std::shared_ptr device) { + executeSendMediaControlEvent(MEDIA_CONTROL_STOP_SUCCEEDED.name, device); } -void Bluetooth::executeSendMediaControlStopFailed() { - executeQueueEventAndRequestContext(MEDIA_CONTROL_STOP_FAILED.name, EMPTY_PAYLOAD); +void Bluetooth::executeSendMediaControlStopFailed(std::shared_ptr device) { + executeSendMediaControlEvent(MEDIA_CONTROL_STOP_FAILED.name, device); } -void Bluetooth::executeSendMediaControlNextSucceeded() { - executeQueueEventAndRequestContext(MEDIA_CONTROL_NEXT_SUCCEEDED.name, EMPTY_PAYLOAD); +void Bluetooth::executeSendMediaControlNextSucceeded(std::shared_ptr device) { + executeSendMediaControlEvent(MEDIA_CONTROL_NEXT_SUCCEEDED.name, device); } -void Bluetooth::executeSendMediaControlNextFailed() { - executeQueueEventAndRequestContext(MEDIA_CONTROL_NEXT_FAILED.name, EMPTY_PAYLOAD); +void Bluetooth::executeSendMediaControlNextFailed(std::shared_ptr device) { + executeSendMediaControlEvent(MEDIA_CONTROL_NEXT_FAILED.name, device); } -void Bluetooth::executeSendMediaControlPreviousSucceeded() { - executeQueueEventAndRequestContext(MEDIA_CONTROL_PREVIOUS_SUCCEEDED.name, EMPTY_PAYLOAD); +void Bluetooth::executeSendMediaControlPreviousSucceeded(std::shared_ptr device) { + executeSendMediaControlEvent(MEDIA_CONTROL_PREVIOUS_SUCCEEDED.name, device); } -void Bluetooth::executeSendMediaControlPreviousFailed() { - executeQueueEventAndRequestContext(MEDIA_CONTROL_PREVIOUS_FAILED.name, EMPTY_PAYLOAD); +void Bluetooth::executeSendMediaControlPreviousFailed(std::shared_ptr device) { + executeSendMediaControlEvent(MEDIA_CONTROL_PREVIOUS_FAILED.name, device); +} + +void Bluetooth::executeSendStreamingEvent( + const std::string& eventName, + std::shared_ptr device) { + rapidjson::Document payload(rapidjson::kObjectType); + std::string uuid; + if (!device) { + ACSDK_ERROR(LX(__func__).d("reason", "nullDevice")); + return; + } + + if (!retrieveUuid(device->getMac(), &uuid)) { + ACSDK_ERROR(LX(__func__).d("reason", "retrieveUuidFailed")); + return; + } + + rapidjson::Value deviceJson(rapidjson::kObjectType); + deviceJson.AddMember(UNIQUE_DEVICE_ID_KEY, uuid, payload.GetAllocator()); + if (supportsAvsProfile(device, AVS_A2DP_SINK)) { + deviceJson.AddMember(PROFILE_NAME_KEY, AVS_A2DP_SINK, payload.GetAllocator()); + } else if (supportsAvsProfile(device, AVS_A2DP_SOURCE)) { + deviceJson.AddMember(PROFILE_NAME_KEY, AVS_A2DP_SOURCE, payload.GetAllocator()); + } + payload.AddMember(DEVICE_KEY, deviceJson.GetObject(), payload.GetAllocator()); + + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + if (!payload.Accept(writer)) { + ACSDK_ERROR(LX(__func__).d("reason", "writerRefusedJsonObject")); + return; + } + + executeUpdateContext(); + + executeQueueEventAndRequestContext(eventName, buffer.GetString()); } void Bluetooth::executeSendStreamingStarted(std::shared_ptr device) { - rapidjson::Document payload(rapidjson::kObjectType); - std::string uuid; - if (!retrieveUuid(device->getMac(), &uuid)) { - ACSDK_ERROR(LX(__func__).d("reason", "retrieveUuidFailed")); - return; - } - - rapidjson::Value deviceJson(rapidjson::kObjectType); - deviceJson.AddMember(UNIQUE_DEVICE_ID_KEY, uuid, payload.GetAllocator()); - payload.AddMember(DEVICE_KEY, deviceJson.GetObject(), payload.GetAllocator()); - - rapidjson::StringBuffer buffer; - rapidjson::Writer writer(buffer); - if (!payload.Accept(writer)) { - ACSDK_ERROR(LX(__func__).d("reason", "writerRefusedJsonObject")); - return; - } - - executeUpdateContext(); - - executeQueueEventAndRequestContext(STREAMING_STARTED.name, buffer.GetString()); + executeSendStreamingEvent(STREAMING_STARTED.name, device); } void Bluetooth::executeSendStreamingEnded(std::shared_ptr device) { - rapidjson::Document payload(rapidjson::kObjectType); - std::string uuid; - if (!retrieveUuid(device->getMac(), &uuid)) { - ACSDK_ERROR(LX(__func__).d("reason", "retrieveUuidFailed")); + executeSendStreamingEvent(STREAMING_ENDED.name, device); +} + +void Bluetooth::executeAcquireFocus(const std::string& callingMethodName) { + if (FocusState::FOREGROUND == m_focusState || FocusState::BACKGROUND == m_focusState) { + ACSDK_DEBUG9(LX(__func__) + .d("focus", m_focusState) + .d("callingMethodName", callingMethodName) + .m("Already acquired channel")); return; } - rapidjson::Value deviceJson(rapidjson::kObjectType); - deviceJson.AddMember(UNIQUE_DEVICE_ID_KEY, uuid, payload.GetAllocator()); - payload.AddMember(DEVICE_KEY, deviceJson.GetObject(), payload.GetAllocator()); + // Create our delayed release activity to initiate delayed release mechanism in AFML. + auto activity = FocusManagerInterface::Activity::create( + ACTIVITY_ID, shared_from_this(), TRANSIENT_FOCUS_DURATION, avsCommon::avs::ContentType::MIXABLE); - rapidjson::StringBuffer buffer; - rapidjson::Writer writer(buffer); - if (!payload.Accept(writer)) { - ACSDK_ERROR(LX(__func__).d("reason", "writerRefusedJsonObject")); - return; + if (!activity) { + ACSDK_ERROR(LX(__func__).d("reason", "activityCreateFailed").d("callingMethodName", callingMethodName)); + } else if (!m_focusManager->acquireChannel(CHANNEL_NAME, activity)) { + ACSDK_ERROR(LX(__func__).d("reason", "acquireChannelFailed").d("callingMethodName", callingMethodName)); + } else { + ACSDK_DEBUG1(LX(__func__).d("callingMethodName", callingMethodName).m("Acquiring channel")); } +} - executeUpdateContext(); - - executeQueueEventAndRequestContext(STREAMING_ENDED.name, buffer.GetString()); +void Bluetooth::executeReleaseFocus(const std::string& callingMethodName) { + m_focusManager->releaseChannel(CHANNEL_NAME, shared_from_this()); + m_focusTransitionState = FocusTransitionState::PENDING_INTERNAL; + ACSDK_DEBUG1(LX(__func__).d("callingMethodName", callingMethodName).m("Releasing channel")); } /* @@ -2053,6 +2860,23 @@ void Bluetooth::onFormattedAudioStreamAdapterData(AudioFormat audioFormat, const } } +template +std::shared_ptr Bluetooth::getService( + std::shared_ptr device) { + ACSDK_DEBUG5(LX(__func__).d("uuid", ServiceType::UUID)); + + std::shared_ptr service = nullptr; + { + if (!device) { + ACSDK_DEBUG5(LX(__func__).d("reason", "nullDevice")); + } else { + service = std::static_pointer_cast(device->getService(ServiceType::UUID)); + } + } + + return service; +} + /* * TODO ACSDK-1402: Create an adapter object between SAS and AttachmentReader to encapsulate logic. * The BTCA should not be responsible for this conversion. @@ -2065,6 +2889,15 @@ void Bluetooth::setCurrentStream(std::shared_ptrclose(); + m_mediaAttachmentReader.reset(); + } + if (m_mediaAttachmentWriter) { + m_mediaAttachmentWriter->close(); + m_mediaAttachmentWriter.reset(); + } + if (m_mediaStream) { m_mediaStream->setListener(nullptr); } @@ -2074,15 +2907,14 @@ void Bluetooth::setCurrentStream(std::shared_ptr("Bluetooth"); - std::shared_ptr attachmentReader = - m_mediaAttachment->createReader(avsCommon::utils::sds::ReaderPolicy::NONBLOCKING); + m_mediaAttachmentReader = m_mediaAttachment->createReader(avsCommon::utils::sds::ReaderPolicy::NONBLOCKING); m_mediaAttachmentWriter = m_mediaAttachment->createWriter(avsCommon::utils::sds::WriterPolicy::ALL_OR_NOTHING); m_mediaStream->setListener(shared_from_this()); auto audioFormat = m_mediaStream->getAudioFormat(); - m_sourceId = m_mediaPlayer->setSource(attachmentReader, &audioFormat); + m_sourceId = m_mediaPlayer->setSource(m_mediaAttachmentReader, &audioFormat); if (MediaPlayerInterface::ERROR == m_sourceId) { ACSDK_ERROR(LX(__func__).d("reason", "setSourceFailed")); m_mediaAttachment.reset(); @@ -2092,6 +2924,67 @@ void Bluetooth::setCurrentStream(std::shared_ptrgetDiscoveredDevices()) { + if (device->isPaired()) { + auto supportA2DP = false; + if (device->getService(A2DPSinkInterface::UUID) != nullptr) { + device->toggleServiceConnection(false, device->getService(A2DPSinkInterface::UUID)); + supportA2DP = true; + } + + if (device->getService(A2DPSourceInterface::UUID) != nullptr) { + device->toggleServiceConnection(false, device->getService(A2DPSourceInterface::UUID)); + supportA2DP = true; + } + + if (supportA2DP) { + m_restrictedDevices.push_back(device); + } + } + } +} + +void Bluetooth::executeUnrestrictA2DPDevices() { + ACSDK_INFO(LX(__func__)); + if (!m_restrictedDevices.empty()) { + for (const auto& device : m_restrictedDevices) { + if (device->getService(A2DPSinkInterface::UUID) != nullptr) { + device->toggleServiceConnection(true, device->getService(A2DPSinkInterface::UUID)); + } + + if (device->getService(A2DPSourceInterface::UUID) != nullptr) { + device->toggleServiceConnection(true, device->getService(A2DPSourceInterface::UUID)); + } + } + m_restrictedDevices.clear(); + } + + if (m_disabledA2DPDevice) { + bool supportsA2DP = supportsAvsProfile(m_disabledA2DPDevice, AVS_A2DP); + if (!supportsA2DP) { + ACSDK_DEBUG0(LX(__func__).d("reason", "noSupportedA2DPRoles").m("Connect Request Rejected")); + } else { + if (!executeFunctionOnDevice(m_disabledA2DPDevice, &BluetoothDeviceInterface::connect)) { + std::string uuid; + if (!retrieveUuid(m_disabledA2DPDevice->getMac(), &uuid)) { + ACSDK_ERROR(LX("executeUnrestrictA2DPDevicesFailed").d("reason", "retrieveUuidFailed")); + } else { + executeSendConnectByDeviceIdsFailed({uuid}, Requester::DEVICE); + } + } + } + m_disabledA2DPDevice.reset(); + } +} + void Bluetooth::addObserver(std::shared_ptr observer) { if (!observer) { ACSDK_ERROR(LX("addObserverFailed").d("reason", "nullObserver")); @@ -2120,6 +3013,58 @@ Bluetooth::ObserverInterface::DeviceAttributes Bluetooth::generateDeviceAttribut return deviceAttributes; } +void Bluetooth::executeInsertBluetoothEventState( + std::shared_ptr device, + DeviceState state, + avsCommon::utils::Optional requester, + avsCommon::utils::Optional profileName) { + if (!device) { + ACSDK_ERROR(LX("insertBluetoothEventStateFailed").d("reason", "nullDevice")); + return; + } + + std::unordered_set> bluetoothEventStates; + const std::string mac = device->getMac(); + auto it = m_bluetoothEventStates.find(mac); + if (it != m_bluetoothEventStates.end()) { + bluetoothEventStates.insert(it->second.begin(), it->second.end()); + m_bluetoothEventStates.erase(mac); + } + + std::shared_ptr eventState = + std::make_shared(state, requester, profileName); + bluetoothEventStates.insert(eventState); + m_bluetoothEventStates.insert({mac, bluetoothEventStates}); +} + +std::shared_ptr Bluetooth::executeRemoveBluetoothEventState( + std::shared_ptr device, + DeviceState state) { + const std::string mac = device->getMac(); + auto it = m_bluetoothEventStates.find(mac); + if (it != m_bluetoothEventStates.end()) { + std::unordered_set> bluetoothEventStates = it->second; + for (const auto& bluetoothEventState : bluetoothEventStates) { + if (bluetoothEventState->getDeviceState() == state) { + auto event = bluetoothEventState; + bluetoothEventStates.erase(event); + if (!bluetoothEventStates.empty()) { + m_bluetoothEventStates[mac] = bluetoothEventStates; + } else { + m_bluetoothEventStates.erase(mac); + } + + return event; + } + } + ACSDK_DEBUG5(LX(__func__).d("reason", "noDeviceStateFound")); + return nullptr; + } + + ACSDK_DEBUG5(LX(__func__).d("reason", "noDeviceFound")); + return nullptr; +} + // Conceptually these are actions that are initiated by the peer device. void Bluetooth::onEventFired(const avsCommon::utils::bluetooth::BluetoothEvent& event) { ACSDK_DEBUG5(LX(__func__)); @@ -2133,9 +3078,31 @@ void Bluetooth::onEventFired(const avsCommon::utils::bluetooth::BluetoothEvent& ACSDK_INFO(LX(__func__) .d("reason", "DEVICE_DISCOVERED") - .d("deviceName", device->getFriendlyName()) - .d("mac", device->getMac())); - m_executor.submit([this] { executeSendScanDevicesUpdated(m_deviceManager->getDiscoveredDevices(), true); }); + .d("deviceName", truncateFriendlyName(device->getFriendlyName())) + .d("mac", truncateWithDefault(device->getMac()))); + m_executor.submit([this] { + if (ScanningTransitionState::ACTIVE == m_scanningTransitionState) { + executeSendScanDevicesReport(m_deviceManager->getDiscoveredDevices(), true); + } + }); + break; + } + case avsCommon::utils::bluetooth::BluetoothEventType::SCANNING_STATE_CHANGED: { + ACSDK_INFO(LX(__func__).d("reason", "SCANNING_STATE_CHANGED").d("isScanning", event.isScanning())); + m_executor.submit([this, event] { + bool isScanning = event.isScanning(); + if (!isScanning) { + if (ScanningTransitionState::PENDING_INACTIVE == m_scanningTransitionState) { + ACSDK_DEBUG5(LX(__func__) + .d("reason", "PENDING_INACTIVE resolved") + .d("m_scanningTransitionState", m_scanningTransitionState) + .d("isScanning", event.isScanning())); + m_scanningTransitionState = ScanningTransitionState::INACTIVE; + } else { + executeSendScanDevicesReport(m_deviceManager->getDiscoveredDevices(), isScanning); + } + } + }); break; } case avsCommon::utils::bluetooth::BluetoothEventType::DEVICE_STATE_CHANGED: { @@ -2148,8 +3115,8 @@ void Bluetooth::onEventFired(const avsCommon::utils::bluetooth::BluetoothEvent& ACSDK_INFO(LX(__func__) .d("event", "DEVICE_STATE_CHANGED") - .d("deviceName", device->getFriendlyName()) - .d("mac", device->getMac()) + .d("deviceName", truncateFriendlyName(device->getFriendlyName())) + .d("mac", truncateWithDefault(device->getMac())) .d("state", event.getDeviceState())); switch (event.getDeviceState()) { @@ -2158,61 +3125,153 @@ void Bluetooth::onEventFired(const avsCommon::utils::bluetooth::BluetoothEvent& break; case avsCommon::sdkInterfaces::bluetooth::DeviceState::PAIRED: { m_executor.submit([this, device] { - if (m_lastPairMac != device->getMac()) { - /* - * Send one of these so we remove the freshly paired device - * from the "Available Devices" page. - */ - executeSendScanDevicesUpdated(m_deviceManager->getDiscoveredDevices(), true); - executeSendPairDeviceSucceeded(device); - } + /* + * Send one of these so we remove the freshly paired device + * from the "Available Devices" page. + */ + executeSendScanDevicesReport(m_deviceManager->getDiscoveredDevices(), true); + executeRemoveBluetoothEventState(device, DeviceState::PAIRED); + std::unordered_set< + std::shared_ptr> + devices({device}); + executeSendPairDevicesSucceeded(devices); }); break; } - case avsCommon::sdkInterfaces::bluetooth::DeviceState::DISCONNECTED: + case avsCommon::sdkInterfaces::bluetooth::DeviceState::DISCONNECTED: { + m_executor.submit([this, device] { + std::shared_ptr disconnectEvent = + executeRemoveBluetoothEventState(device, DeviceState::DISCONNECTED); + if (disconnectEvent) { + /// Cloud initiated disconnect. + Optional requester = disconnectEvent->getRequester(); + if (requester.hasValue()) { + std::unordered_set< + std::shared_ptr> + devices({device}); + executeSendDisconnectDevicesSucceeded(devices, requester.value()); + } else { + ACSDK_ERROR(LX(__func__) + .d("reason", "sendDisconnectDeviceSucceededEventFailed") + .d("error", "retrieveDisconnectRequesterFailed")); + } + } else { + /// Device initiated disconnect. + DeviceCategory category; + std::string uuid; + + if (!retrieveUuid(device->getMac(), &uuid)) { + ACSDK_ERROR(LX(__func__) + .d("reason", "disconnectDeviceFailed") + .d("error", "retrieveDisconnectedDeviceUuidFailed")); + return; + } + + if (!retrieveDeviceCategoryByUuid(uuid, &category)) { + ACSDK_ERROR(LX(__func__) + .d("reason", "disconnectDeviceFailed") + .d("error", "retrieveDisconnectedDeviceCategoryFailed")); + return; + } + + ACSDK_DEBUG9(LX(__func__).d("uuid", uuid).d("deviceCategory", category)); + + auto it = m_connectedDevices.find(category); + + if (it != m_connectedDevices.end() && it->second.find(device) != it->second.end()) { + executeOnDeviceDisconnect(device, Requester::DEVICE); + executeRemoveBluetoothEventState(device, DeviceState::DISCONNECTED); + std::unordered_set< + std::shared_ptr> + devices({device}); + executeSendDisconnectDevicesSucceeded(devices, Requester::DEVICE); + } else { + ACSDK_ERROR(LX(__func__) + .d("reason", "disconnectDeviceFailed") + .d("error", "deviceNotConnectedBefore") + .d("deviceName", truncateFriendlyName(device->getFriendlyName())) + .d("mac", truncateWithDefault(device->getMac()))); + } + } + }); + + break; + } case avsCommon::sdkInterfaces::bluetooth::DeviceState::UNPAIRED: { m_executor.submit([this, device] { - if (device == m_activeDevice) { - executeOnDeviceDisconnect(Requester::DEVICE); - } + executeRemoveBluetoothEventState(device, DeviceState::UNPAIRED); + + std::unordered_set< + std::shared_ptr> + devices({device}); + executeSendUnpairDevicesSucceeded(devices); }); break; } case avsCommon::sdkInterfaces::bluetooth::DeviceState::CONNECTED: { m_executor.submit([this, device] { - if (!supportsAvsProfile(device, AVS_A2DP)) { - /* - * This device does not support A2DP. We will attempt to disconnect, - * AVS won't be made aware of it, and if unsuccessful, it is up to the - * user/client to disconnect. - */ - ACSDK_WARN(LX("deviceConnected") - .d("reason", "deviceDoesNotSupportA2DP") - .m("Please disconnect device")); - - if (device->disconnect().get()) { - executeOnDeviceDisconnect(Requester::DEVICE); - } else { - ACSDK_ERROR(LX("deviceConnected") - .d("reason", "disconnectInvalidDeviceFailed") - .d("mac", device->getMac()) - .d("name", device->getFriendlyName()) - .m("Please disconnect device")); + std::shared_ptr connectEvent = + executeRemoveBluetoothEventState(device, DeviceState::CONNECTED); + if (connectEvent) { + /// Cloud initiated connect. + Optional requester = connectEvent->getRequester(); + Optional profileName = connectEvent->getProfileName(); + if (!requester.hasValue()) { + ACSDK_ERROR(LX(__func__) + .d("reason", + profileName.hasValue() ? "sendConnectByProfileFailed" + : "sendConnectByDeviceIdsFailed") + .d("error", "retrieveConnectRequesterFailed")); + return; } - return; - } - /* - * Otherwise set the device as the new active device. We don't need to call connect() - * again because the device is already connected from the Bluetooth stack's perspective. - */ - else if (device != m_activeDevice) { - executeOnDeviceConnect(device); - /* - * Default to sending a ConnectByDeviceId event since this wasn't a result of a profile - * specific connection. - */ - executeSendConnectByDeviceIdSucceeded(device, Requester::DEVICE); + if (profileName.hasValue()) { + executeSendConnectByProfileSucceeded(device, profileName.value(), requester.value()); + } else { + std::unordered_set< + std::shared_ptr> + devices({device}); + executeSendConnectByDeviceIdsSucceeded(devices, requester.value()); + } + } else { + /// Device initiated connect. + DeviceCategory category; + std::string uuid; + + if (!retrieveUuid(device->getMac(), &uuid)) { + ACSDK_ERROR(LX(__func__) + .d("reason", "connectDeviceFailed") + .d("error", "retrieveConnectedDeviceCategoryFailed")); + return; + } + + if (!retrieveDeviceCategoryByUuid(uuid, &category)) { + ACSDK_ERROR(LX(__func__) + .d("reason", "connectDeviceFailed") + .d("error", "retrieveConnectedDeviceCategoryFailed")); + return; + } + + auto it = m_connectedDevices.find(category); + + if (it == m_connectedDevices.end() || + (it != m_connectedDevices.end() && it->second.find(device) == it->second.end())) { + executeOnDeviceConnect(device, true); + /* + * Default to sending a ConnectByDeviceIds event since this wasn't a result of a profile + * specific connection. + */ + std::unordered_set< + std::shared_ptr> + devices({device}); + executeSendConnectByDeviceIdsSucceeded(devices, Requester::DEVICE); + } else { + ACSDK_ERROR(LX(__func__) + .d("reason", "connectDeviceFailed") + .d("error", "deviceAlreadyConnectedBefore") + .d("deviceName", truncateFriendlyName(device->getFriendlyName())) + .d("mac", truncateWithDefault(device->getMac()))); + } } }); break; @@ -2221,11 +3280,11 @@ void Bluetooth::onEventFired(const avsCommon::utils::bluetooth::BluetoothEvent& break; } case avsCommon::utils::bluetooth::BluetoothEventType::STREAMING_STATE_CHANGED: { - if (event.getDevice() != m_activeDevice) { + if (event.getDevice() != m_activeA2DPDevice) { ACSDK_ERROR(LX(__func__) .d("reason", "mismatchedDevices") .d("eventDevice", event.getDevice() ? event.getDevice()->getMac() : "null") - .d("activeDevice", m_activeDevice ? m_activeDevice->getMac() : "null")); + .d("activeDevice", m_activeA2DPDevice ? m_activeA2DPDevice->getMac() : "null")); break; } @@ -2243,9 +3302,7 @@ void Bluetooth::onEventFired(const avsCommon::utils::bluetooth::BluetoothEvent& m_streamingState == StreamingState::PAUSED || m_streamingState == StreamingState::PENDING_PAUSED) { // Obtain Focus. - if (!m_focusManager->acquireChannel(CHANNEL_NAME, shared_from_this(), ACTIVITY_ID)) { - ACSDK_ERROR(LX(__func__).d("reason", "acquireChannelFailed")); - } + executeAcquireFocus(__func__); } }); /* @@ -2255,7 +3312,7 @@ void Bluetooth::onEventFired(const avsCommon::utils::bluetooth::BluetoothEvent& } else if (MediaStreamingState::IDLE == event.getMediaStreamingState()) { m_executor.submit([this] { if (FocusState::FOREGROUND == m_focusState) { - m_focusManager->releaseChannel(CHANNEL_NAME, shared_from_this()); + executeReleaseFocus(__func__); } }); } @@ -2265,20 +3322,30 @@ void Bluetooth::onEventFired(const avsCommon::utils::bluetooth::BluetoothEvent& m_executor.submit([this] { if (StreamingState::ACTIVE != m_streamingState) { m_streamingState = StreamingState::ACTIVE; - executeSendStreamingStarted(m_activeDevice); + executeSendStreamingStarted(m_activeA2DPDevice); } }); } else if (MediaStreamingState::IDLE == event.getMediaStreamingState()) { m_executor.submit([this] { if (StreamingState::ACTIVE == m_streamingState) { m_streamingState = StreamingState::PAUSED; - executeSendStreamingEnded(m_activeDevice); + executeSendStreamingEnded(m_activeA2DPDevice); } }); } } break; } + case avsCommon::utils::bluetooth::BluetoothEventType::TOGGLE_A2DP_PROFILE_STATE_CHANGED: { + ACSDK_DEBUG5( + LX(__func__).d("event", "TOGGLE_A2DP_PROFILE_STATE_CHANGED").d("a2dpEnable", event.isA2DPEnabled())); + if (event.isA2DPEnabled()) { + m_executor.submit([this] { executeUnrestrictA2DPDevices(); }); + } else { + m_executor.submit([this] { executeRestrictA2DPDevices(); }); + } + break; + } default: ACSDK_ERROR(LX("onEventFired").d("reason", "unexpectedEventType")); break; diff --git a/CapabilityAgents/Bluetooth/src/BluetoothEventState.cpp b/CapabilityAgents/Bluetooth/src/BluetoothEventState.cpp new file mode 100644 index 00000000..9032a5bc --- /dev/null +++ b/CapabilityAgents/Bluetooth/src/BluetoothEventState.cpp @@ -0,0 +1,43 @@ +/* + * Copyright 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 + +namespace alexaClientSDK { +namespace capabilityAgents { +namespace bluetooth { +BluetoothEventState::BluetoothEventState( + alexaClientSDK::avsCommon::sdkInterfaces::bluetooth::DeviceState state, + alexaClientSDK::avsCommon::utils::Optional requester, + alexaClientSDK::avsCommon::utils::Optional profileName) : + m_state(state), + m_requester(requester), + m_profileName(profileName) { +} + +avsCommon::utils::Optional BluetoothEventState::getRequester() const { + return m_requester; +} + +avsCommon::sdkInterfaces::bluetooth::DeviceState BluetoothEventState::getDeviceState() const { + return m_state; +} + +avsCommon::utils::Optional BluetoothEventState::getProfileName() const { + return m_profileName; +} + +} // namespace bluetooth +} // namespace capabilityAgents +} // namespace alexaClientSDK \ No newline at end of file diff --git a/CapabilityAgents/Bluetooth/src/BluetoothAVRCPTransformer.cpp b/CapabilityAgents/Bluetooth/src/BluetoothMediaInputTransformer.cpp similarity index 64% rename from CapabilityAgents/Bluetooth/src/BluetoothAVRCPTransformer.cpp rename to CapabilityAgents/Bluetooth/src/BluetoothMediaInputTransformer.cpp index ab1081e7..4f70a204 100644 --- a/CapabilityAgents/Bluetooth/src/BluetoothAVRCPTransformer.cpp +++ b/CapabilityAgents/Bluetooth/src/BluetoothMediaInputTransformer.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * 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. @@ -13,7 +13,7 @@ * permissions and limitations under the License. */ -#include "Bluetooth/BluetoothAVRCPTransformer.h" +#include "Bluetooth/BluetoothMediaInputTransformer.h" #include #include @@ -29,7 +29,7 @@ using namespace avsCommon::sdkInterfaces::bluetooth::services; using namespace avsCommon::utils::bluetooth; /// String to identify log entries originating from this file. -static const std::string TAG{"BluetoothAVRCPTransformer"}; +static const std::string TAG{"BluetoothMediaInputTransformer"}; /** * Create a LogEntry using this file's TAG and the specified event string. @@ -38,7 +38,7 @@ static const std::string TAG{"BluetoothAVRCPTransformer"}; */ #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) -std::shared_ptr BluetoothAVRCPTransformer::create( +std::shared_ptr BluetoothMediaInputTransformer::create( std::shared_ptr eventBus, std::shared_ptr playbackRouter) { ACSDK_DEBUG5(LX(__func__)); @@ -48,11 +48,11 @@ std::shared_ptr BluetoothAVRCPTransformer::create( } else if (!playbackRouter) { ACSDK_ERROR(LX(__func__).d("reason", "nullPlaybackRouter")); } else { - auto avrcpTransformer = - std::shared_ptr(new BluetoothAVRCPTransformer(eventBus, playbackRouter)); + auto mediaInputTransformer = std::shared_ptr( + new BluetoothMediaInputTransformer(eventBus, playbackRouter)); - if (avrcpTransformer->init()) { - return avrcpTransformer; + if (mediaInputTransformer->init()) { + return mediaInputTransformer; } else { ACSDK_ERROR(LX(__func__).d("reason", "initFailed")); } @@ -61,49 +61,54 @@ std::shared_ptr BluetoothAVRCPTransformer::create( return nullptr; } -BluetoothAVRCPTransformer::BluetoothAVRCPTransformer( +BluetoothMediaInputTransformer::BluetoothMediaInputTransformer( std::shared_ptr eventBus, std::shared_ptr playbackRouter) : m_eventBus{eventBus}, m_playbackRouter{playbackRouter} { } -bool BluetoothAVRCPTransformer::init() { +bool BluetoothMediaInputTransformer::init() { ACSDK_DEBUG5(LX(__func__)); m_eventBus->addListener( - {avsCommon::utils::bluetooth::BluetoothEventType::AVRCP_COMMAND_RECEIVED}, shared_from_this()); + {avsCommon::utils::bluetooth::BluetoothEventType::MEDIA_COMMAND_RECEIVED}, shared_from_this()); return true; } -void BluetoothAVRCPTransformer::onEventFired(const avsCommon::utils::bluetooth::BluetoothEvent& event) { +void BluetoothMediaInputTransformer::onEventFired(const avsCommon::utils::bluetooth::BluetoothEvent& event) { ACSDK_DEBUG5(LX(__func__)); - if (BluetoothEventType::AVRCP_COMMAND_RECEIVED != event.getType()) { + if (BluetoothEventType::MEDIA_COMMAND_RECEIVED != event.getType()) { ACSDK_ERROR(LX(__func__).d("reason", "unexpectedEventReceived")); return; } - std::shared_ptr avrcp = event.getAVRCPCommand(); - if (!avrcp) { - ACSDK_ERROR(LX(__func__).d("reason", "nullAVRCPCommand")); + std::shared_ptr mediaCommand = event.getMediaCommand(); + if (!mediaCommand) { + ACSDK_ERROR(LX(__func__).d("reason", "nullMediaCommand")); return; } - switch (*avrcp) { - case AVRCPCommand::PLAY: + switch (*mediaCommand) { + case MediaCommand::PLAY: m_playbackRouter->buttonPressed(PlaybackButton::PLAY); break; - case AVRCPCommand::PAUSE: + case MediaCommand::PAUSE: m_playbackRouter->buttonPressed(PlaybackButton::PAUSE); break; - case AVRCPCommand::NEXT: + case MediaCommand::NEXT: m_playbackRouter->buttonPressed(PlaybackButton::NEXT); break; - case AVRCPCommand::PREVIOUS: + case MediaCommand::PREVIOUS: m_playbackRouter->buttonPressed(PlaybackButton::PREVIOUS); break; + case MediaCommand::PLAY_PAUSE: + // The AVS cloud treats both play and pause as a play/pause toggle. + // So we will just press play when we get the PLAY_PAUSE command. + m_playbackRouter->buttonPressed(PlaybackButton::PLAY); + break; default: - ACSDK_ERROR(LX(__func__).d("reason", "commandNotSupported").d("command", *avrcp)); + ACSDK_ERROR(LX(__func__).d("reason", "commandNotSupported").d("command", *mediaCommand)); return; } } diff --git a/CapabilityAgents/Bluetooth/src/CMakeLists.txt b/CapabilityAgents/Bluetooth/src/CMakeLists.txt index 4cc1fe1f..870b7a24 100644 --- a/CapabilityAgents/Bluetooth/src/CMakeLists.txt +++ b/CapabilityAgents/Bluetooth/src/CMakeLists.txt @@ -2,8 +2,10 @@ add_definitions("-DACSDK_LOG_MODULE=bluetooth") add_library( Bluetooth SHARED + BasicDeviceConnectionRule.cpp Bluetooth.cpp - BluetoothAVRCPTransformer.cpp + BluetoothEventState.cpp + BluetoothMediaInputTransformer.cpp SQLiteBluetoothStorage.cpp ) diff --git a/CapabilityAgents/Bluetooth/src/SQLiteBluetoothStorage.cpp b/CapabilityAgents/Bluetooth/src/SQLiteBluetoothStorage.cpp index f23d5dea..297c0b7b 100644 --- a/CapabilityAgents/Bluetooth/src/SQLiteBluetoothStorage.cpp +++ b/CapabilityAgents/Bluetooth/src/SQLiteBluetoothStorage.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018-2020 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. @@ -13,10 +13,12 @@ * permissions and limitations under the License. */ -#include "Bluetooth/SQLiteBluetoothStorage.h" -#include "Bluetooth/BluetoothStorageInterface.h" +#include #include +#include "Bluetooth/BluetoothStorageInterface.h" +#include "Bluetooth/SQLiteBluetoothStorage.h" + /// String to identify log entries originating from this file. static const std::string TAG{"SQLiteBluetoothStorage"}; @@ -48,6 +50,9 @@ static const std::string COLUMN_UUID = "uuid"; /// The MAC address column. static const std::string COLUMN_MAC = "mac"; +/// The Category column. +static const std::string COLUMN_CATEGORY = "category"; + std::unique_ptr SQLiteBluetoothStorage::create( const avsCommon::utils::configuration::ConfigurationNode& configurationRoot) { ACSDK_DEBUG5(LX(__func__)); @@ -69,9 +74,13 @@ std::unique_ptr SQLiteBluetoothStorage::create( bool SQLiteBluetoothStorage::createDatabase() { ACSDK_DEBUG5(LX(__func__)); + std::string defaultCategory = deviceCategoryToString(DeviceCategory::UNKNOWN); + // clang-format off const std::string sqlString = "CREATE TABLE " + UUID_TABLE_NAME + "(" + - COLUMN_UUID + " text not null unique, " + COLUMN_MAC + " text not null unique);"; + COLUMN_UUID + " text not null unique, " + + COLUMN_MAC + " text not null unique, " + + COLUMN_CATEGORY + " text not null default "+ defaultCategory +");"; // clang-format on std::lock_guard lock(m_databaseMutex); @@ -93,7 +102,14 @@ bool SQLiteBluetoothStorage::open() { ACSDK_DEBUG5(LX(__func__)); std::lock_guard lock(m_databaseMutex); - return m_db.open(); + bool ret = m_db.open(); + if (ret && !isDatabaseMigratedLocked()) { + // Database exists & database is not migrated yet + ACSDK_INFO(LX(__func__).d("reason", "Legacy Database, migrating database")); + migrateDatabaseLocked(); + } + + return ret; } void SQLiteBluetoothStorage::close() { @@ -127,7 +143,7 @@ bool SQLiteBluetoothStorage::clear() { bool SQLiteBluetoothStorage::getSingleRowLocked( std::unique_ptr& statement, std::unordered_map* row) { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG9(LX(__func__)); if (!statement) { ACSDK_ERROR(LX(__func__).d("reason", "nullStatement")); return false; @@ -162,6 +178,17 @@ bool SQLiteBluetoothStorage::getUuid(const std::string& mac, std::string* uuid) return getAssociatedDataLocked(COLUMN_MAC, mac, COLUMN_UUID, uuid); } +bool SQLiteBluetoothStorage::getCategory(const std::string& uuid, std::string* category) { + ACSDK_DEBUG5(LX(__func__)); + if (!category) { + ACSDK_ERROR(LX(__func__).d("reason", "nullCategory")); + return false; + } + + std::lock_guard lock(m_databaseMutex); + return getAssociatedDataLocked(COLUMN_UUID, uuid, COLUMN_CATEGORY, category); +} + bool SQLiteBluetoothStorage::getMac(const std::string& uuid, std::string* mac) { ACSDK_DEBUG5(LX(__func__)); if (!mac) { @@ -211,18 +238,23 @@ bool SQLiteBluetoothStorage::getAssociatedDataLocked( } bool SQLiteBluetoothStorage::getMappingsLocked( - const std::string& keyPreference, + const std::string& key, + const std::string& value, std::unordered_map* mappings) { - ACSDK_DEBUG5(LX(__func__).d("keyPreference", keyPreference)); + ACSDK_DEBUG5(LX(__func__).d("key", key).d("value", value)); if (!mappings) { ACSDK_ERROR(LX(__func__).d("reason", "nullMappings")); return false; } - if (COLUMN_UUID != keyPreference && COLUMN_MAC != keyPreference) { - ACSDK_ERROR(LX(__func__).d("reason", "invalidKeyPreference").d("keyPreference", keyPreference)); + if (COLUMN_UUID != key && COLUMN_MAC != key && COLUMN_CATEGORY != key) { + ACSDK_ERROR(LX(__func__).d("reason", "invalidKey").d("key", key)); + return false; + } + if (COLUMN_UUID != value && COLUMN_MAC != value && COLUMN_CATEGORY != value) { + ACSDK_ERROR(LX(__func__).d("reason", "invalidValue").d("value", value)); return false; } @@ -236,37 +268,191 @@ bool SQLiteBluetoothStorage::getMappingsLocked( std::unordered_map row; while (getSingleRowLocked(statement, &row)) { - if (0 == row.count(COLUMN_MAC) || 0 == row.count(COLUMN_UUID)) { + if (0 == row.count(key) || 0 == row.count(value)) { ACSDK_ERROR(LX(__func__) .d("reason", "missingData") - .d("macPresent", row.count(COLUMN_MAC)) - .d("uuidPresent", row.count(COLUMN_UUID))); + .d("keyPresent", row.count(key)) + .d("valuePresent", row.count(value))); continue; } - if (COLUMN_UUID == keyPreference) { - mappings->insert({row.at(COLUMN_UUID), row.at(COLUMN_MAC)}); - } else if (COLUMN_MAC == keyPreference) { - mappings->insert({row.at(COLUMN_MAC), row.at(COLUMN_UUID)}); - } else { - ACSDK_ERROR(LX(__func__).d("reason", "unexpectedData").d("keyPreference", keyPreference)); - } + mappings->insert({row.at(key), row.at(value)}); row.clear(); } return true; } +bool SQLiteBluetoothStorage::updateValueLocked( + const std::string& constraintKey, + const std::string& constraintVal, + const std::string& updateKey, + const std::string& updateVal) { + if (COLUMN_UUID != constraintKey && COLUMN_MAC != constraintKey && COLUMN_CATEGORY != constraintKey) { + ACSDK_ERROR(LX(__func__).d("reason", "invalidConstraintKey").d("constraintKey", constraintKey)); + return false; + } + + if (COLUMN_UUID != updateKey && COLUMN_MAC != updateKey && COLUMN_CATEGORY != updateKey) { + ACSDK_ERROR(LX(__func__).d("reason", "invalidUpdateKey").d("updateKey", updateKey)); + return false; + } + + const std::string sqlString = + "UPDATE " + UUID_TABLE_NAME + " SET " + updateKey + "=? WHERE " + constraintKey + "=?;"; + + auto statement = m_db.createStatement(sqlString); + if (!statement) { + ACSDK_ERROR(LX(__func__).d("reason", "createStatementFailed")); + return false; + } + + const int UPDATE_VAL_INDEX = 1; + const int CONSTRAINT_VAL_INDEX = 2; + + if (!statement->bindStringParameter(UPDATE_VAL_INDEX, updateVal) || + !statement->bindStringParameter(CONSTRAINT_VAL_INDEX, constraintVal)) { + ACSDK_ERROR(LX(__func__).d("reason", "bindParameterFailed")); + return false; + } + + // This could be due to entry to update not found in the db. + if (!statement->step()) { + ACSDK_ERROR(LX(__func__).d("reason", "stepFailed")); + return false; + } + + return true; +} + +bool SQLiteBluetoothStorage::insertEntryLocked( + const std::string& operation, + const std::string& uuid, + const std::string& mac, + const std::string& category) { + if (operation != "REPLACE" && operation != "INSERT") { + ACSDK_ERROR(LX(__func__).d("reason", "invalidOperation").d("operation", operation)); + return false; + } + + // clang-format off + const std::string sqlString = operation + " INTO " + UUID_TABLE_NAME + + " (" + COLUMN_UUID + "," + COLUMN_MAC + "," + COLUMN_CATEGORY + ") VALUES (?,?,?);"; + // clang-format on + + auto statement = m_db.createStatement(sqlString); + if (!statement) { + ACSDK_ERROR(LX(__func__).d("reason", "createStatementFailed")); + return false; + } + + const int UUID_INDEX = 1; + const int MAC_INDEX = 2; + const int CATEGORY_INDEX = 3; + + if (!statement->bindStringParameter(UUID_INDEX, uuid) || !statement->bindStringParameter(MAC_INDEX, mac) || + !statement->bindStringParameter(CATEGORY_INDEX, category)) { + ACSDK_ERROR(LX(__func__).d("reason", "bindParameterFailed")); + return false; + } + + // This could be due to a mac or uuid already existing in the db. + if (!statement->step()) { + ACSDK_ERROR(LX(__func__).d("reason", "stepFailed")); + return false; + } + + return true; +} + +bool SQLiteBluetoothStorage::isDatabaseMigratedLocked() { + auto sqlStatement = m_db.createStatement("PRAGMA table_info(" + UUID_TABLE_NAME + ");"); + + if ((!sqlStatement) || (!sqlStatement->step())) { + ACSDK_ERROR(LX(__func__).d("reason", "failedSQLMigrationQuery")); + return false; + } + + const std::string tableInfoColumnName = "name"; + + std::string columnName; + while (SQLITE_ROW == sqlStatement->getStepResult()) { + int columnCount = sqlStatement->getColumnCount(); + + for (int i = 0; i < columnCount; i++) { + std::string tableColumnName = sqlStatement->getColumnName(i); + + if (tableInfoColumnName == tableColumnName) { + columnName = sqlStatement->getColumnText(i); + if (columnName == COLUMN_CATEGORY) { + return true; + } + } + } + if (!sqlStatement->step()) { + ACSDK_ERROR(LX(__func__).d("reason", "stepFailed")); + return false; + } + } + + return false; +} + +bool SQLiteBluetoothStorage::migrateDatabaseLocked() { + const std::string defaultCategory = deviceCategoryToString(DeviceCategory::UNKNOWN); + + if (!m_db.performQuery( + "ALTER TABLE " + UUID_TABLE_NAME + " ADD COLUMN " + COLUMN_CATEGORY + " text not null default " + + defaultCategory + ";")) { + ACSDK_ERROR(LX(__func__).d("reason", "addingCategoryColumnFailed")); + return false; + } + + auto statement = m_db.createStatement("UPDATE " + UUID_TABLE_NAME + " SET " + COLUMN_CATEGORY + "=?;"); + if (!statement) { + ACSDK_ERROR(LX(__func__).d("reason", "createStatementFailed")); + return false; + } + + const int UPDATE_CATEGORY_VAL_INDEX = 1; + const std::string otherCategory = deviceCategoryToString(DeviceCategory::OTHER); + + if (!statement->bindStringParameter(UPDATE_CATEGORY_VAL_INDEX, otherCategory)) { + ACSDK_ERROR(LX(__func__).d("reason", "bindParameterFailed")); + return false; + } + + // This could be due to entry to update not found in the db. + if (!statement->step()) { + ACSDK_ERROR(LX(__func__).d("reason", "stepFailed")); + return false; + } + + return true; +} + bool SQLiteBluetoothStorage::getMacToUuid(std::unordered_map* macToUuid) { ACSDK_DEBUG5(LX(__func__)); std::lock_guard lock(m_databaseMutex); - return getMappingsLocked(COLUMN_MAC, macToUuid); + return getMappingsLocked(COLUMN_MAC, COLUMN_UUID, macToUuid); +} + +bool SQLiteBluetoothStorage::getMacToCategory(std::unordered_map* macToCategory) { + ACSDK_DEBUG5(LX(__func__)); + std::lock_guard lock(m_databaseMutex); + return getMappingsLocked(COLUMN_MAC, COLUMN_CATEGORY, macToCategory); } bool SQLiteBluetoothStorage::getUuidToMac(std::unordered_map* uuidToMac) { ACSDK_DEBUG5(LX(__func__)); std::lock_guard lock(m_databaseMutex); - return getMappingsLocked(COLUMN_UUID, uuidToMac); + return getMappingsLocked(COLUMN_UUID, COLUMN_MAC, uuidToMac); +} + +bool SQLiteBluetoothStorage::getUuidToCategory(std::unordered_map* uuidToCategory) { + ACSDK_DEBUG5(LX(__func__)); + std::lock_guard lock(m_databaseMutex); + return getMappingsLocked(COLUMN_UUID, COLUMN_CATEGORY, uuidToCategory); } bool SQLiteBluetoothStorage::getOrderedMac(bool ascending, std::list* macs) { @@ -303,35 +489,26 @@ bool SQLiteBluetoothStorage::getOrderedMac(bool ascending, std::list lock(m_databaseMutex); + getAssociatedDataLocked(COLUMN_UUID, uuid, COLUMN_CATEGORY, &category); - auto statement = m_db.createStatement(sqlString); - if (!statement) { - ACSDK_ERROR(LX(__func__).d("reason", "createStatementFailed")); - return false; + return insertEntryLocked(operation, uuid, mac, category); +} + +bool SQLiteBluetoothStorage::updateByCategory(const std::string& uuid, const std::string& category) { + ACSDK_DEBUG5(LX(__func__)); + std::string mac = deviceCategoryToString(DeviceCategory::UNKNOWN); + + std::lock_guard lock(m_databaseMutex); + if (getAssociatedDataLocked(COLUMN_UUID, uuid, COLUMN_MAC, &mac)) { + // Do not overwrite & found existing uuid entry, update value + return updateValueLocked(COLUMN_UUID, uuid, COLUMN_CATEGORY, category); } - const int UUID_INDEX = 1; - const int MAC_INDEX = 2; - - if (!statement->bindStringParameter(UUID_INDEX, uuid) || !statement->bindStringParameter(MAC_INDEX, mac)) { - ACSDK_ERROR(LX(__func__).d("reason", "bindParameterFailed")); - return false; - } - - // This could be due to a mac or uuid already existing in the db. - if (!statement->step()) { - ACSDK_ERROR(LX(__func__).d("reason", "stepFailed")); - return false; - } - - return true; + ACSDK_ERROR(LX("updateByCategoryFailed").d("reason", "UUID not found in database.")); + return false; } bool SQLiteBluetoothStorage::remove(const std::string& mac) { diff --git a/CapabilityAgents/Bluetooth/test/BluetoothAVRCPTransformerTest.cpp b/CapabilityAgents/Bluetooth/test/BluetoothMediaInputTransformerTest.cpp similarity index 58% rename from CapabilityAgents/Bluetooth/test/BluetoothAVRCPTransformerTest.cpp rename to CapabilityAgents/Bluetooth/test/BluetoothMediaInputTransformerTest.cpp index 1ee7a556..d5209958 100644 --- a/CapabilityAgents/Bluetooth/test/BluetoothAVRCPTransformerTest.cpp +++ b/CapabilityAgents/Bluetooth/test/BluetoothMediaInputTransformerTest.cpp @@ -18,7 +18,9 @@ #include #include -#include "Bluetooth/BluetoothAVRCPTransformer.h" +#include + +#include "Bluetooth/BluetoothMediaInputTransformer.h" namespace alexaClientSDK { namespace capabilityAgents { @@ -28,6 +30,7 @@ namespace test { using namespace ::testing; using namespace avsCommon::avs; using namespace avsCommon::sdkInterfaces; +using namespace avsCommon::sdkInterfaces::test; using namespace avsCommon::sdkInterfaces::bluetooth::services; using namespace avsCommon::utils; using namespace avsCommon::utils::bluetooth; @@ -40,70 +43,78 @@ public: MOCK_METHOD0(switchToDefaultHandler, void()); }; -class BluetoothAVRCPTransformerTest : public ::testing::Test { +class BluetoothMediaInputTransformerTest : public ::testing::Test { public: /// SetUp before each test case. void SetUp(); protected: - /// The eventbus in which the @c BluetoothAVRCPTransformer will received AVRCP events on. + /// The eventbus in which the @c BluetoothMediaInputTransformer will received Media events on. std::shared_ptr m_eventBus; /// A mock @PlaybackRouterInterface object. std::shared_ptr m_mockRouter; - /// The @c BluetoothAVRCPTransformer instance under test. - std::shared_ptr m_avrcp; + /// The @c BluetoothMediaInputTransformer instance under test. + std::shared_ptr m_mediaInputTransformer; }; -void BluetoothAVRCPTransformerTest::SetUp() { +void BluetoothMediaInputTransformerTest::SetUp() { m_eventBus = std::make_shared(); m_mockRouter = std::make_shared(); - m_avrcp = BluetoothAVRCPTransformer::create(m_eventBus, m_mockRouter); + m_mediaInputTransformer = BluetoothMediaInputTransformer::create(m_eventBus, m_mockRouter); } /// Test that create() returns a nullptr if called with invalid arguments. -TEST_F(BluetoothAVRCPTransformerTest, test_createWithNullParams) { - ASSERT_THAT(BluetoothAVRCPTransformer::create(m_eventBus, nullptr), IsNull()); - ASSERT_THAT(BluetoothAVRCPTransformer::create(nullptr, m_mockRouter), IsNull()); +TEST_F(BluetoothMediaInputTransformerTest, test_createWithNullParams) { + ASSERT_THAT(BluetoothMediaInputTransformer::create(m_eventBus, nullptr), IsNull()); + ASSERT_THAT(BluetoothMediaInputTransformer::create(nullptr, m_mockRouter), IsNull()); } /// Test that a Play AVRCP command is transformed to playButtonPressed(). -TEST_F(BluetoothAVRCPTransformerTest, test_handlePlayCommand) { +TEST_F(BluetoothMediaInputTransformerTest, test_handlePlayCommand) { EXPECT_CALL(*m_mockRouter, buttonPressed(PlaybackButton::PLAY)).Times(1); - AVRCPCommandReceivedEvent event(AVRCPCommand::PLAY); + MediaCommandReceivedEvent event(MediaCommand::PLAY); m_eventBus->sendEvent(event); } /// Test that a Pause AVRCP command is transformed to pauseButtonPressed(). -TEST_F(BluetoothAVRCPTransformerTest, test_handlePauseCommand) { +TEST_F(BluetoothMediaInputTransformerTest, test_handlePauseCommand) { EXPECT_CALL(*m_mockRouter, buttonPressed(PlaybackButton::PAUSE)).Times(1); - AVRCPCommandReceivedEvent event(AVRCPCommand::PAUSE); + MediaCommandReceivedEvent event(MediaCommand::PAUSE); m_eventBus->sendEvent(event); } /// Test that a Next AVRCP command is transformed to nextButtonPressed(). -TEST_F(BluetoothAVRCPTransformerTest, test_handleNextCommand) { +TEST_F(BluetoothMediaInputTransformerTest, test_handleNextCommand) { EXPECT_CALL(*m_mockRouter, buttonPressed(PlaybackButton::NEXT)).Times(1); - AVRCPCommandReceivedEvent event(AVRCPCommand::NEXT); + MediaCommandReceivedEvent event(MediaCommand::NEXT); m_eventBus->sendEvent(event); } /// Test that a Previous AVRCP command is transformed to previousButtonPressed(). -TEST_F(BluetoothAVRCPTransformerTest, test_handlePreviousCommand) { +TEST_F(BluetoothMediaInputTransformerTest, test_handlePreviousCommand) { EXPECT_CALL(*m_mockRouter, buttonPressed(PlaybackButton::PREVIOUS)).Times(1); - AVRCPCommandReceivedEvent event(AVRCPCommand::PREVIOUS); + MediaCommandReceivedEvent event(MediaCommand::PREVIOUS); + m_eventBus->sendEvent(event); +} + +/// Test that a Play/Pause Media command is transformed to playButtonPressed(). +TEST_F(BluetoothMediaInputTransformerTest, handlePlayPauseCommand) { + EXPECT_CALL(*m_mockRouter, buttonPressed(PlaybackButton::PLAY)).Times(1); + + MediaCommandReceivedEvent event(MediaCommand::PLAY_PAUSE); m_eventBus->sendEvent(event); } /// Test that an unrelated event does not trigger any calls to the PlaybackRouter. -TEST_F(BluetoothAVRCPTransformerTest, test_unrelatedEvent) { - auto strictPlaybackRouter = std::shared_ptr(new StrictMock()); - auto avrcpTransformer = BluetoothAVRCPTransformer::create(m_eventBus, strictPlaybackRouter); +TEST_F(BluetoothMediaInputTransformerTest, test_unrelatedEvent) { + auto strictPlaybackRouter = std::make_shared>(); + auto mediaInputTransformer = BluetoothMediaInputTransformer::create(m_eventBus, strictPlaybackRouter); DeviceDiscoveredEvent event(nullptr); m_eventBus->sendEvent(event); diff --git a/CapabilityAgents/Bluetooth/test/BluetoothTest.cpp b/CapabilityAgents/Bluetooth/test/BluetoothTest.cpp new file mode 100644 index 00000000..d48ca506 --- /dev/null +++ b/CapabilityAgents/Bluetooth/test/BluetoothTest.cpp @@ -0,0 +1,1375 @@ +/* + * Copyright 2019-2020 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 +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Bluetooth/BasicDeviceConnectionRule.h" +#include "Bluetooth/Bluetooth.h" +#include "Bluetooth/SQLiteBluetoothStorage.h" + +namespace alexaClientSDK { +namespace capabilityAgents { +namespace bluetooth { +namespace test { + +using namespace avsCommon::avs; +using namespace avsCommon::avs::attachment; +using namespace avsCommon::avs::attachment::test; +using namespace avsCommon::sdkInterfaces; +using namespace avsCommon::sdkInterfaces::bluetooth; +using namespace avsCommon::sdkInterfaces::bluetooth::services; +using namespace avsCommon::sdkInterfaces::bluetooth::test; +using namespace avsCommon::sdkInterfaces::bluetooth::services::test; +using namespace avsCommon::sdkInterfaces::test; +using namespace avsCommon::utils; +using namespace avsCommon::utils::bluetooth; +using namespace avsCommon::utils::configuration; +using namespace avsCommon::utils::json; +using namespace avsCommon::utils::mediaPlayer::test; +using namespace ::testing; +using namespace rapidjson; + +/// String to identify log entries originating from this file. +static const std::string TAG{"BluetoothTest"}; + +/** + * 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) + +/// Test Bluetooth device mac address 1. +static const std::string TEST_BLUETOOTH_DEVICE_MAC = "01:23:45:67:89:ab"; + +/// Test Bluetooth device friendly name 1. +static const std::string TEST_BLUETOOTH_FRIENDLY_NAME = "test_friendly_name_1"; + +/// Test Bluetooth device uuid 1. +static const std::string TEST_BLUETOOTH_UUID = "650f973b-c2ab-4c6e-bff4-3788cd521340"; + +/// Test Bluetooth device mac address 2. +static const std::string TEST_BLUETOOTH_DEVICE_MAC_2 = "11:23:45:67:89:ab"; + +/// Test Bluetooth device friendly name 2. +static const std::string TEST_BLUETOOTH_FRIENDLY_NAME_2 = "test_friendly_name_2"; + +/// Test Bluetooth device uuid 2. +static const std::string TEST_BLUETOOTH_UUID_2 = "650f973b-c2ab-4c6e-bff4-3788cd521341"; + +/// Test Database file name. Can be changed if there are conflicts. +static const std::string TEST_DATABASE = "BluetoothCATest.db"; + +// clang-format off +static const std::string BLUETOOTH_JSON = R"( +{ + "bluetooth" : { + "databaseFilePath":")" + TEST_DATABASE + R"(" + } +} +)"; +// clang-format on + +/// Error message for when the file already exists. +static const std::string FILE_EXISTS_ERROR = "Database File " + TEST_DATABASE + " already exists."; + +/// Namespace of Bluetooth. +static const std::string NAMESPACE_BLUETOOTH = "Bluetooth"; + +/// THe Bluetooth state portion of the Context. +static const NamespaceAndName BLUETOOTH_STATE{NAMESPACE_BLUETOOTH, "BluetoothState"}; + +/// JSON key for the event section of a message. +static const std::string MESSAGE_EVENT_KEY = "event"; + +/// JSON key for the header section of a message. +static const std::string MESSAGE_HEADER_KEY = "header"; + +/// JSON key for the name section of a message. +static const std::string MESSAGE_NAME_KEY = "name"; + +/// JSON key for the payload section of a message. +static const std::string PAYLOAD_KEY = "payload"; + +/// JSON key for the requester section of a message. +static const std::string REQUESTER_KEY = "requester"; + +/// JSON value for the cloud requester. +static const std::string CLOUD_REQUESTER_VALUE = "CLOUD"; + +/// JSON value for the device requester. +static const std::string DEVICE_REQUESTER_VALUE = "DEVICE"; + +/// ConnectByDevice directive. +static const std::string CONNECT_BY_DEVICE_IDS_DIRECTIVE = "ConnectByDeviceIds"; + +/// ConnectByProfile directive. +static const std::string CONNECT_BY_PROFILE_DIRECTIVE = "ConnectByProfile"; + +/// PairDevice directive +static const std::string PAIR_DEVICES_DIRECTIVE = "PairDevices"; + +/// UnpairDevice directive +static const std::string UNPAIR_DEVICES_DIRECTIVE = "UnpairDevices"; + +/// DisconnectDevice directive +static const std::string DISCONNECT_DEVICES_DIRECTIVE = "DisconnectDevices"; + +/// SetDeviceCategories directive +static const std::string SET_DEVICE_CATEGORIES = "SetDeviceCategories"; + +/// Test message id. +static const std::string TEST_MESSAGE_ID = "MessageId_Test"; + +/// Test message id. +static const std::string TEST_MESSAGE_ID_2 = "MessageId_Test_2"; + +// clang-format off +/// ConnectByDeviceIds payload. +static const std::string TEST_CONNECT_BY_DEVICE_IDS_PAYLOAD = R"( +{ + "devices" : [{ + "uniqueDeviceId":")" + TEST_BLUETOOTH_UUID + R"(", + "friendlyName" :")" + TEST_BLUETOOTH_FRIENDLY_NAME + R"(" + }, {"uniqueDeviceId":")" + TEST_BLUETOOTH_UUID_2 + R"(", + "friendlyName" :")" + TEST_BLUETOOTH_FRIENDLY_NAME_2 + R"(" + }] +} +)"; +// clang-format on + +/// The @c ConnectByDeviceIdSucceeded event name. +static const std::string CONNECT_BY_DEVICE_IDS_SUCCEEDED = "ConnectByDeviceIdsSucceeded"; + +/// The @c ConnectByProfileSucceeded event name. +static const std::string CONNECT_BY_PROFILE_SUCCEEDED = "ConnectByProfileSucceeded"; + +/// The @c ConnectByProfileFailed event name. +static const std::string CONNECT_BY_PROFILE_FAILED = "ConnectByProfileFailed"; + +/// The @c PairDeviceSucceeded event name. +static const std::string PAIR_DEVICES_SUCCEEDED = "PairDevicesSucceeded"; + +/// The @c UnpairDeviceSucceeded event name. +static const std::string UNPAIR_DEVICES_SUCCEEDED = "UnpairDevicesSucceeded"; + +/// The @c SetDeviceCategoriesSucceeded event name. +static const std::string SET_DEVICE_CATEGORIES_SUCCEEDED = "SetDeviceCategoriesSucceeded"; + +/// The @c DisconnectDeviceSucceeded event name. +static const std::string DISCONNECT_DEVICES_SUCCEEDED = "DisconnectDevicesSucceeded"; + +/// The @c ScanDevicesUpdated event name. +static const std::string SCAN_DEVICES_REPORT = "ScanDevicesReport"; + +/// The @c StreamingStarted event name. +static const std::string STREAMING_STARTED = "StreamingStarted"; + +/// The @c StreamingEnded event name. +static const std::string STREAMING_ENDED = "StreamingEnded"; + +/// Test unmatched profile name. +static const std::string TEST_UNMATCHED_PROFILE_NAME = "HFP"; + +/// Test matched profile name. +static const std::string TEST_MATCHED_PROFILE_NAME = "AVRCP"; + +/// Test profile version. +static const std::string TEST_PROFILE_VERSION = "1"; + +// clang-format off +/// ConnectByDeviceProfile payload 1. +static const std::string TEST_CONNECT_BY_PROFILE_PAYLOAD_1 = R"( +{ + "profile" : { + "name":")" + TEST_UNMATCHED_PROFILE_NAME + R"(", + "version" :")" + TEST_PROFILE_VERSION + R"(" + } +} +)"; +// clang-format on + +// clang-format off +/// ConnectByDeviceProfile payload 2 +static const std::string TEST_CONNECT_BY_PROFILE_PAYLOAD_2 = R"( +{ + "profile" : { + "name":")" + TEST_MATCHED_PROFILE_NAME + R"(", + "version" :")" + TEST_PROFILE_VERSION + R"(" + } +} +)"; +// clang-format on + +// clang-format off +/// PairDevices payload +static const std::string TEST_PAIR_DEVICES_PAYLOAD = R"( +{ + "devices" : [{ + "uniqueDeviceId":")" + TEST_BLUETOOTH_UUID + R"(" + }, { + "uniqueDeviceId":")" + TEST_BLUETOOTH_UUID_2 + R"(" + }] +} +)"; + +// clang-format on + +// clang-format off +/// UnpairDevices payload +static const std::string TEST_UNPAIR_DEVICES_PAYLOAD = R"( +{ + "devices" : [{ + "uniqueDeviceId":")" + TEST_BLUETOOTH_UUID + R"(" + }, { + "uniqueDeviceId":")" + TEST_BLUETOOTH_UUID_2 + R"(" + }] +} +)"; +// clang-format on + +// clang-format off +/// DisconnectDevices payload +static const std::string TEST_DISCONNECT_DEVICES_PAYLOAD = R"( +{ + "devices" : [{ + "uniqueDeviceId":")" + TEST_BLUETOOTH_UUID + R"(" + }, { + "uniqueDeviceId":")" + TEST_BLUETOOTH_UUID_2 + R"(" + }] +} +)"; +// clang-format on + +// clang-format off +/// SetDeviceCategories payload +static const std::string TEST_SET_DEVICE_CATEGORIES_PAYLOAD = R"( +{ + "devices" : [{ + "uniqueDeviceId":")" + TEST_BLUETOOTH_UUID + R"(", + "deviceCategory": "PHONE" + }, { + "uniqueDeviceId":")" + TEST_BLUETOOTH_UUID_2 + R"(", + "deviceCategory": "GADGET" + }] +} +)"; +// clang-format on + +// clang-format off +/// Mock Context +static const std::string MOCK_CONTEXT = R"( +{ + "context": [{ + "header": { + "namespace": "Bluetooth", + "name": "BluetoothState" + }, + "payload": { + "alexaDevice": { + "friendlyName": "{{STRING}}" + }, + "pairedDevices": [{ + "uniqueDeviceId": "{{STRING}}", + "friendlyName": "{{STRING}}", + "supportedProfiles": [{ + "name": "{{STRING}}", + "version": "{{STRING}}" + }] + }], + "activeDevice": { + "uniqueDeviceId": "{{STRING}}", + "friendlyName": "{{STRING}}", + "supportedProfiles": [{ + "name": "{{STRING}}", + "version": "{{STRING}}" + }], + "streaming": "{{STRING}}" + } + } + }] +} +)"; + +/// Wait timeout. +static std::chrono::milliseconds WAIT_TIMEOUT_MS(1000); +// Delay to let events happen / threads catch up +static std::chrono::milliseconds EVENT_PROCESS_DELAY_MS(500); + +/** + * Checks whether a file exists in the file system. + * + * @param file The file name. + * @return Whether the file exists. + */ +static bool fileExists(const std::string& file) { + std::ifstream dbFile(file); + return dbFile.good(); +} + +class BluetoothTest: public ::testing::Test { +public: + void SetUp() override; + void TearDown() override; + + /// @c Bluetooth to test + std::shared_ptr m_Bluetooth; + + /// @c ContextManager to provide state and update state. + std::shared_ptr m_mockContextManager; + + /// @c FocusManager to request focus to the CONTENT channel. + std::shared_ptr m_mockFocusManager; + + /// A message sender used to send events to AVS. + std::shared_ptr m_mockMessageSender; + + /// An exception sender used to send exception encountered events to AVS. + std::shared_ptr m_mockExceptionSender; + + /// The storage component for @c Bluetooth. + std::shared_ptr m_bluetoothStorage; + + /// Player to send the audio to. + std::shared_ptr m_mockBluetoothMediaPlayer; + + /// A set of device connection rules. + std::unordered_set> m_mockEnabledConnectionRules; + + /// A bus to abstract Bluetooth stack specific messages. + std::shared_ptr m_eventBus; + + /// Object that will track the CustomerDataHandler. + std::shared_ptr m_customerDataManager; + + /// @c BluetoothHostController to create @c MockBluetoothDeviceManager + std::shared_ptr m_mockBluetoothHostController; + + /// The list of discovered devices to create @c MockBluetoothDeviceManager + std::list> + m_mockDiscoveredBluetoothDevices; + + /// Bluetooth devices used to test the Bluetooth CA connection logic. + std::shared_ptr m_mockBluetoothDevice1; + std::shared_ptr m_mockBluetoothDevice2; + + /// Bluetooth device connection rules. + /// Bluetooth device connection rule for DeviceCategory::REMOTE_CONTROL. + std::shared_ptr m_remoteControlConnectionRule; + /// Bluetooth device connection rule for DeviceCategory::GADGET. + std::shared_ptr m_gadgetConnectionRule; + + /// A manager to take care of Bluetooth devices. + std::unique_ptr m_mockDeviceManager; + + /// A directive handler result to send the result to. + std::unique_ptr m_mockDirectiveHandlerResult; + + /// An observer to be notified of the Bluetooth connection change. + std::shared_ptr m_mockBluetoothDeviceObserver; + + /// Condition variable to wake on a message being sent. + std::condition_variable m_messageSentTrigger; + + /// Expected messages. + std::map m_messages; + + /// Function to wait for @c m_wakeSetCompleteFuture to be set. + void wakeOnSetCompleted(); + + /** + * Get the request event name. + * + * @param request The @c MessageRequest to verify. + * @return The event name. + */ + std::string getRequestName(std::shared_ptr request); + + /** + * Verify that the message name matches the expected name. + * + * @param request The @c MessageRequest to verify. + * @param expectedName The expected name to find in the json header. + * @return true if the message name matched the expect name. + */ + bool verifyMessage(std::shared_ptr request, std::string expectedName); + + /** + * Verify that the messages sent matches the ordered list of events. + * + * @param request The @c MessageRequest to verify. + * @param trigger The function to trigger sending the messages. + * @return true if the messages sent matches the ordered list of events. + */ + bool verifyMessagesSentInOrder(std::vector orderedEvents, std::function trigger); + + /** + * Verify that the messages sent matches the count. + * + * @param request The @c MessageRequest to verify. + * @param messages The Map expected messages. + */ + void verifyMessagesCount(std::shared_ptr request, + std::map* messages); + + /// A Constructor which initializes the promises and futures needed for the test class. + BluetoothTest() : m_wakeSetCompletedPromise{}, m_wakeSetCompletedFuture{m_wakeSetCompletedPromise.get_future()} { + } + +protected: + /// Promise to synchronize directive handling through SetCompleted. + std::promise m_wakeSetCompletedPromise; + + /// Future to synchronize directive handling through SetCompleted. + std::future m_wakeSetCompletedFuture; +}; + +void BluetoothTest::SetUp() { + m_mockContextManager = std::make_shared>(); + m_mockFocusManager = std::make_shared>(); + m_mockMessageSender = std::make_shared>(); + m_mockExceptionSender = std::make_shared>(); + + m_eventBus = std::make_shared(); + m_mockBluetoothHostController = std::make_shared>(); + m_mockDirectiveHandlerResult = std::unique_ptr(new MockDirectiveHandlerResult); + m_mockBluetoothDeviceObserver = std::make_shared>(); + m_mockBluetoothMediaPlayer = MockMediaPlayer::create(); + m_customerDataManager = std::make_shared(); + + /* + * Create Mock Devices. + */ + auto metaData =MockBluetoothDevice::MetaData(Optional(), Optional(), + MockBluetoothDevice::MetaData::UNDEFINED_CLASS_VALUE, Optional(), Optional()); + auto a2dpSink = std::make_shared>(std::make_shared("")); + auto avrcpTarget = std::make_shared>(std::make_shared("")); + std::vector> services = {a2dpSink, avrcpTarget}; + m_mockBluetoothDevice1 = std::make_shared>( + TEST_BLUETOOTH_DEVICE_MAC, TEST_BLUETOOTH_FRIENDLY_NAME, metaData, services); + m_mockDiscoveredBluetoothDevices.push_back(m_mockBluetoothDevice1); + + auto metaData2 =MockBluetoothDevice::MetaData(Optional(), Optional(), + MockBluetoothDevice::MetaData::UNDEFINED_CLASS_VALUE, Optional(), Optional()); + auto hid = std::make_shared>(std::make_shared("")); + auto spp = std::make_shared>(std::make_shared("")); + auto a2dpSource = std::make_shared>(std::make_shared("")); + std::vector> services2 = {spp, hid, a2dpSource}; + m_mockBluetoothDevice2 = std::make_shared>( + TEST_BLUETOOTH_DEVICE_MAC_2, TEST_BLUETOOTH_FRIENDLY_NAME_2, metaData2, services2); + m_mockDiscoveredBluetoothDevices.push_back(m_mockBluetoothDevice2); + + /* + * Create mock device connection rules. + */ + std::set remoteCategory{DeviceCategory::REMOTE_CONTROL}; + std::set remoteDependentProfiles{HIDInterface::UUID, SPPInterface::UUID}; + m_remoteControlConnectionRule = + std::make_shared>(remoteCategory, remoteDependentProfiles); + std::set gadgetCategory{DeviceCategory::GADGET}; + std::set gadgetDependentProfiles{HIDInterface::UUID, SPPInterface::UUID}; + m_gadgetConnectionRule = + std::make_shared>(gadgetCategory, gadgetDependentProfiles); + /* + * GadgetConnectionRule: + * 1) No need to explicitly disconnect/connect device. + * 2) No devices needed to disconnect when a new device with DeviceCategory::GADGET connects. + */ + m_gadgetConnectionRule->setExplicitlyConnect(false); + m_gadgetConnectionRule->setExplicitlyDisconnect(true); + + /* + * RemoteControlConnectionRule: + * 1) No need to explicitly disconnect/connect device. + * 2) Devices with DeviceCategory::REMOTE_CONTROL needed to disconnect when a new device with + * DeviceCategory::REMOTE_CONTROL connects. + */ + m_remoteControlConnectionRule->setExplicitlyConnect(false); + m_remoteControlConnectionRule->setExplicitlyDisconnect(false); + + m_mockEnabledConnectionRules = + {m_remoteControlConnectionRule, m_gadgetConnectionRule, BasicDeviceConnectionRule::create()}; + + /* + * Generate a Bluetooth database for testing. + * Ensure the db file does not exist already. We don't want to overwrite anything. + */ + if (fileExists(TEST_DATABASE)) { + ADD_FAILURE() << FILE_EXISTS_ERROR; + exit(1); + } + auto json = std::shared_ptr(new std::stringstream()); + *json << BLUETOOTH_JSON; + std::vector> jsonStream; + jsonStream.push_back(json); + ConfigurationNode::initialize(jsonStream); + m_bluetoothStorage = SQLiteBluetoothStorage::create(ConfigurationNode::getRoot()); + ASSERT_TRUE(m_bluetoothStorage->createDatabase()); + // Insert the test device data into the test database + m_bluetoothStorage->insertByMac(TEST_BLUETOOTH_DEVICE_MAC, TEST_BLUETOOTH_UUID, true); + m_bluetoothStorage->insertByMac(TEST_BLUETOOTH_DEVICE_MAC_2, TEST_BLUETOOTH_UUID_2, true); + m_bluetoothStorage->close(); + + m_Bluetooth = Bluetooth::create( + m_mockContextManager, + m_mockFocusManager, + m_mockMessageSender, + m_mockExceptionSender, + m_bluetoothStorage, + avsCommon::utils::memory::make_unique>( + m_mockBluetoothHostController, m_mockDiscoveredBluetoothDevices, m_eventBus), + m_eventBus, + m_mockBluetoothMediaPlayer, + m_customerDataManager, + m_mockEnabledConnectionRules, + nullptr); + ASSERT_THAT(m_Bluetooth, NotNull()); + m_Bluetooth->addObserver(m_mockBluetoothDeviceObserver); +} + +void BluetoothTest::TearDown() { + if (m_Bluetooth) { + m_Bluetooth->shutdown(); + } + m_mockBluetoothMediaPlayer->shutdown(); + if (fileExists(TEST_DATABASE)) { + remove(TEST_DATABASE.c_str()); + } +} + +void BluetoothTest::wakeOnSetCompleted() { + m_wakeSetCompletedPromise.set_value(); +} + +std::string BluetoothTest::getRequestName(std::shared_ptr request) { + rapidjson::Document document; + document.Parse(request->getJsonContent().c_str()); + EXPECT_FALSE(document.HasParseError()); + + auto event = document.FindMember(MESSAGE_EVENT_KEY); + EXPECT_NE(event, document.MemberEnd()); + + auto header = event->value.FindMember(MESSAGE_HEADER_KEY); + EXPECT_NE(header, event->value.MemberEnd()); + + auto payload = event->value.FindMember(PAYLOAD_KEY); + EXPECT_NE(payload, event->value.MemberEnd()); + + std::string requestName; + jsonUtils::retrieveValue(header->value, MESSAGE_NAME_KEY, &requestName); + return requestName; +} + +bool BluetoothTest::verifyMessage(std::shared_ptr request, + std::string expectedName) { + return getRequestName(request) == expectedName; +} + +bool BluetoothTest::verifyMessagesSentInOrder(std::vector orderedEvents, std::function trigger) { + size_t curIndex = 0; + std::mutex waitMutex; + + EXPECT_CALL(*m_mockMessageSender, sendMessage(_)) + .Times(AtLeast(1)) + .WillRepeatedly( + Invoke([this, orderedEvents, &curIndex](std::shared_ptr request) { + if (curIndex < orderedEvents.size()) { + if (verifyMessage(request, orderedEvents.at(curIndex))) { + if (curIndex < orderedEvents.size()) { + curIndex++; + } + } + } + m_messageSentTrigger.notify_one(); + })); + + trigger(); + + bool result; + { + std::unique_lock lock(waitMutex); + result = m_messageSentTrigger.wait_for(lock, WAIT_TIMEOUT_MS, [orderedEvents, &curIndex] { + if (curIndex == orderedEvents.size()) { + return true; + } + return false; + }); + } + + return result; +} + +void BluetoothTest::verifyMessagesCount(std::shared_ptr request, + std::map* messages) { + std::string requestName = getRequestName(request); + + if (messages->find(requestName) != messages->end()) { + messages->at(requestName) += 1; + } +} + +/// Test that create() returns a nullptr if called with invalid arguments. +TEST_F(BluetoothTest, test_createBTWithNullParams) { + // Create Bluetooth CapabilityAgent with null @c ContextManager. + auto bluetooth1 = Bluetooth::create( + nullptr, + m_mockFocusManager, + m_mockMessageSender, + m_mockExceptionSender, + m_bluetoothStorage, + avsCommon::utils::memory::make_unique>( + m_mockBluetoothHostController, m_mockDiscoveredBluetoothDevices, m_eventBus), + m_eventBus, + m_mockBluetoothMediaPlayer, + m_customerDataManager, + m_mockEnabledConnectionRules, + nullptr); + EXPECT_THAT(bluetooth1, IsNull()); + + // Create Bluetooth CapabilityAgent with null @c FocusManager + auto bluetooth2 = Bluetooth::create( + m_mockContextManager, + nullptr, + m_mockMessageSender, + m_mockExceptionSender, + m_bluetoothStorage, + avsCommon::utils::memory::make_unique>( + m_mockBluetoothHostController, m_mockDiscoveredBluetoothDevices, m_eventBus), + m_eventBus, + m_mockBluetoothMediaPlayer, + m_customerDataManager, + m_mockEnabledConnectionRules, + nullptr); + EXPECT_THAT(bluetooth2, IsNull()); + + // Create Bluetooth CapabilityAgent with null @c MessageSender + auto bluetooth3 = Bluetooth::create( + m_mockContextManager, + m_mockFocusManager, + nullptr, + m_mockExceptionSender, + m_bluetoothStorage, + avsCommon::utils::memory::make_unique>( + m_mockBluetoothHostController, m_mockDiscoveredBluetoothDevices, m_eventBus), + m_eventBus, + m_mockBluetoothMediaPlayer, + m_customerDataManager, + m_mockEnabledConnectionRules, + nullptr); + EXPECT_THAT(bluetooth3, IsNull()); + + // Create Bluetooth CapabilityAgent with null @c ExceptionEncounterSender + auto bluetooth4 = Bluetooth::create( + m_mockContextManager, + m_mockFocusManager, + m_mockMessageSender, + nullptr, + m_bluetoothStorage, + avsCommon::utils::memory::make_unique>( + m_mockBluetoothHostController, m_mockDiscoveredBluetoothDevices, m_eventBus), + m_eventBus, + m_mockBluetoothMediaPlayer, + m_customerDataManager, + m_mockEnabledConnectionRules, + nullptr); + EXPECT_THAT(bluetooth4, IsNull()); + + // Create Bluetooth CapabilityAgent with null @c BluetoothStorage + auto bluetooth5 = Bluetooth::create( + m_mockContextManager, + m_mockFocusManager, + m_mockMessageSender, + m_mockExceptionSender, + nullptr, + avsCommon::utils::memory::make_unique>( + m_mockBluetoothHostController, m_mockDiscoveredBluetoothDevices, m_eventBus), + m_eventBus, + m_mockBluetoothMediaPlayer, + m_customerDataManager, + m_mockEnabledConnectionRules, + nullptr); + EXPECT_THAT(bluetooth5, IsNull()); + + // Create Bluetooth CapabilityAgent with null @c DeviceManager + auto bluetooth6 = Bluetooth::create( + m_mockContextManager, + m_mockFocusManager, + m_mockMessageSender, + m_mockExceptionSender, + m_bluetoothStorage, + nullptr, + m_eventBus, + m_mockBluetoothMediaPlayer, + m_customerDataManager, + m_mockEnabledConnectionRules, + nullptr); + EXPECT_THAT(bluetooth6, IsNull()); + + // Create Bluetooth CapabilityAgent with null @c BluetoothEventBus + auto bluetooth7 = Bluetooth::create( + m_mockContextManager, + m_mockFocusManager, + m_mockMessageSender, + m_mockExceptionSender, + m_bluetoothStorage, + avsCommon::utils::memory::make_unique>( + m_mockBluetoothHostController, m_mockDiscoveredBluetoothDevices, m_eventBus), + nullptr, + m_mockBluetoothMediaPlayer, + m_customerDataManager, + m_mockEnabledConnectionRules, + nullptr); + EXPECT_THAT(bluetooth7, IsNull()); + + // Create Bluetooth CapabilityAgent with null @c MediaPlayer + auto bluetooth8 = Bluetooth::create( + m_mockContextManager, + m_mockFocusManager, + m_mockMessageSender, + m_mockExceptionSender, + m_bluetoothStorage, + avsCommon::utils::memory::make_unique>( + m_mockBluetoothHostController, m_mockDiscoveredBluetoothDevices, m_eventBus), + m_eventBus, + nullptr, + m_customerDataManager, + m_mockEnabledConnectionRules, + nullptr); + EXPECT_THAT(bluetooth8, IsNull()); + + // Create Bluetooth CapabilityAgent with null @c MediaPlayer + auto bluetooth9 = Bluetooth::create( + m_mockContextManager, + m_mockFocusManager, + m_mockMessageSender, + m_mockExceptionSender, + m_bluetoothStorage, + avsCommon::utils::memory::make_unique>( + m_mockBluetoothHostController, m_mockDiscoveredBluetoothDevices, m_eventBus), + m_eventBus, + m_mockBluetoothMediaPlayer, + nullptr, + m_mockEnabledConnectionRules, + nullptr); + EXPECT_THAT(bluetooth8, IsNull()); +} + +/** + * Test that create() returns a nullptr if called with an invalid set of device connection rules. + * Fail due to re-defined device category. + */ +TEST_F(BluetoothTest, test_createBTWithDuplicateDeviceCategoriesInConnectionRules) { + std::set categories1{DeviceCategory::REMOTE_CONTROL}; + std::set categories2{DeviceCategory::REMOTE_CONTROL, DeviceCategory::GADGET}; + std::set dependentProfiles{HIDInterface::UUID, SPPInterface::UUID}; + auto mockDeviceConnectionRule1 = + std::make_shared(categories1, dependentProfiles); + auto mockDeviceConnectionRule2 = + std::make_shared(categories2, dependentProfiles); + std::unordered_set> enabledRules = + {mockDeviceConnectionRule1, mockDeviceConnectionRule2}; + auto bluetooth = Bluetooth::create( + m_mockContextManager, + m_mockFocusManager, + m_mockMessageSender, + m_mockExceptionSender, + m_bluetoothStorage, + avsCommon::utils::memory::make_unique>( + m_mockBluetoothHostController, m_mockDiscoveredBluetoothDevices, m_eventBus), + m_eventBus, + m_mockBluetoothMediaPlayer, + m_customerDataManager, + enabledRules, + nullptr); + ASSERT_THAT(bluetooth, IsNull()); +} + +/** + * Test that create() returns a nullptr if called with an invalid set of device connection rules. + * Fail due to lack of dependent profiles defined in the device connection rule. + */ +TEST_F(BluetoothTest, test_createBTWithLackOfProfilesInConnectionRules) { + std::set categories{DeviceCategory::REMOTE_CONTROL}; + std::set dependentProfiles{HIDInterface::UUID}; + auto mockDeviceConnectionRule = std::make_shared(categories, dependentProfiles); + std::unordered_set> enabledRules = + {mockDeviceConnectionRule}; + auto bluetooth = Bluetooth::create( + m_mockContextManager, + m_mockFocusManager, + m_mockMessageSender, + m_mockExceptionSender, + m_bluetoothStorage, + avsCommon::utils::memory::make_unique>( + m_mockBluetoothHostController, m_mockDiscoveredBluetoothDevices, m_eventBus), + m_eventBus, + m_mockBluetoothMediaPlayer, + m_customerDataManager, + enabledRules, + nullptr); + ASSERT_THAT(bluetooth, IsNull()); +} + +/** + * Test call to handle ConnectByDeviceIds directive with two matched A2DP device UUIDs. + * + * Assumption: + * A @c DeviceManager contains m_mockBluetoothDevice1 and m_mockBluetoothDevice2. Both of them belong to + * DeviceCategory::UNKNOWN. Both devices are in disconnected state. + * Use case: + * A ConnectByDeviceIds directive is sent down, whose payload contains the mockBluetoothDevice1 and + * mockBluetoothDevice2 UUIDs. + * Expected result: + * 1) Only one device which connects later(m_mockBluetoothDevice1) will be connected eventually even + * though both of them were connected successfully. However, The earlier connected device(m_mockBluetoothDevice2 + * in this case) will be disconnected due to @c BasicDeviceConnectionRule. + * 2) The observer should be notified device connection twice and disconnection once. + * 3) Two @c ConnectByDeviceIdsSucceed events, both of which have CLOUD as @c Requester, and one + * @c DisconnectDevicesSucceed event, should be sent to cloud. + */ +TEST_F(BluetoothTest, DISABLED_test_handleConnectByDeviceIdsDirectiveWithTwoA2DPDevices) { + m_mockBluetoothDevice1->disconnect(); + m_mockBluetoothDevice2->disconnect(); + + EXPECT_CALL(*m_mockBluetoothDeviceObserver, onActiveDeviceConnected(_)).Times(Exactly(2)); + EXPECT_CALL(*m_mockBluetoothDeviceObserver, onActiveDeviceDisconnected(_)).Times(Exactly(1)); + EXPECT_CALL(*(m_mockDirectiveHandlerResult.get()), setCompleted()) + .Times(1) + .WillOnce(InvokeWithoutArgs(this, &BluetoothTest::wakeOnSetCompleted)); + EXPECT_CALL(*m_mockContextManager, setState(BLUETOOTH_STATE, _, StateRefreshPolicy::NEVER, _)).Times(Exactly(3)); + std::vector events = {CONNECT_BY_DEVICE_IDS_SUCCEEDED, CONNECT_BY_DEVICE_IDS_SUCCEEDED, + DISCONNECT_DEVICES_SUCCEEDED}; + ASSERT_TRUE(verifyMessagesSentInOrder(events, [this]() { + // Create ConnectedByDeviceId Directive. + auto attachmentManager = std::make_shared>(); + auto avsMessageHeader = + std::make_shared(NAMESPACE_BLUETOOTH, CONNECT_BY_DEVICE_IDS_DIRECTIVE, TEST_MESSAGE_ID); + std::shared_ptr directive = + AVSDirective::create("", avsMessageHeader, TEST_CONNECT_BY_DEVICE_IDS_PAYLOAD, attachmentManager, ""); + // cast to DirectiveHandlerInterface so the compiler can find the correct preHandleDirective signature + std::shared_ptr agentAsDirectiveHandler = m_Bluetooth; + agentAsDirectiveHandler->preHandleDirective(directive, std::move(m_mockDirectiveHandlerResult)); + agentAsDirectiveHandler->handleDirective(TEST_MESSAGE_ID); + m_wakeSetCompletedFuture.wait_for(WAIT_TIMEOUT_MS); + + /* + * Mimic the @c DeviceStateChangedEvent which should happen after device connection status changes. + * In order to guarantee that all @c DeviceStateChangedEvent happen after the corresponding device connection + * status changes, force the test to wait EVENT_PROCESS_DELAY_MS. + * + * TODO: Add send event to @c BluetoothEventBus within @c MockBluetoothDevice. + */ + std::this_thread::sleep_for(std::chrono::milliseconds(EVENT_PROCESS_DELAY_MS)); + m_eventBus->sendEvent(DeviceStateChangedEvent(m_mockBluetoothDevice2, DeviceState::CONNECTED)); + m_eventBus->sendEvent(DeviceStateChangedEvent(m_mockBluetoothDevice1, DeviceState::CONNECTED)); + m_eventBus->sendEvent(DeviceStateChangedEvent(m_mockBluetoothDevice2, DeviceState::DISCONNECTED)); + })); + + // Verify the connection result. + ASSERT_TRUE(m_mockBluetoothDevice1->isConnected()); + ASSERT_FALSE(m_mockBluetoothDevice2->isConnected()); +} + +/** + * Test call to handle ConnectByDeviceIds directive with two matched device UUIDs with different device categories. + * + * Assumption: + * A @c DeviceManager contains m_mockBluetoothDevice1 and m_mockBluetoothDevice2. m_mockBluetoothDevice1 + * belongs to DeviceCategory::PHONE, m_mockBluetoothDevice2 belongs to DeviceCategory::GADGET. Both devices are + * in disconnect state. + * Use case: + * A ConnectByDeviceIds directive is sent down, whose payload contains the mockBluetoothDevice1 and + * mockBluetoothDevice2. + * Expected result: + * 1)Both devices should be connected successfully. + * 2)The observer should be notified deivce connection twice. + * 3)Two @c ConnectByDeviceIdsSucceed events, both of which have CLOUD as @c Requester,should be sent to cloud. + */ +TEST_F(BluetoothTest, test_handleConnectByDeviceIdsDirectiveWithOnePhoneOneGadget) { + m_mockBluetoothDevice1->disconnect(); + m_mockBluetoothDevice2->disconnect(); + // Initially, all devices are stored as DeviceCategory::UNKNOWN. Need to manually update device category in order + // to test. + m_bluetoothStorage->updateByCategory(TEST_BLUETOOTH_UUID, deviceCategoryToString(DeviceCategory::PHONE)); + m_bluetoothStorage->updateByCategory(TEST_BLUETOOTH_UUID_2, deviceCategoryToString(DeviceCategory::GADGET)); + + EXPECT_CALL(*m_mockBluetoothDeviceObserver, onActiveDeviceConnected(_)).Times(Exactly(2)); + EXPECT_CALL(*(m_mockDirectiveHandlerResult.get()), setCompleted()) + .Times(1) + .WillOnce(InvokeWithoutArgs(this, &BluetoothTest::wakeOnSetCompleted)); + EXPECT_CALL(*m_mockContextManager, setState(BLUETOOTH_STATE, _, StateRefreshPolicy::NEVER, _)).Times(Exactly(2)); + // Verify the @c ConnectByDeviceIdsSucceed event. + std::vector events = {CONNECT_BY_DEVICE_IDS_SUCCEEDED, CONNECT_BY_DEVICE_IDS_SUCCEEDED}; + ASSERT_TRUE(verifyMessagesSentInOrder(events, [this]() { + // Create ConnectedByDeviceId Directive. + auto attachmentManager = std::make_shared>(); + auto avsMessageHeader = + std::make_shared(NAMESPACE_BLUETOOTH, CONNECT_BY_DEVICE_IDS_DIRECTIVE, TEST_MESSAGE_ID); + std::shared_ptr directive = + AVSDirective::create("", avsMessageHeader, TEST_CONNECT_BY_DEVICE_IDS_PAYLOAD, attachmentManager, ""); + // cast to DirectiveHandlerInterface so the compiler can find the correct preHandleDirective signature + std::shared_ptr agentAsDirectiveHandler = m_Bluetooth; + agentAsDirectiveHandler->preHandleDirective(directive, std::move(m_mockDirectiveHandlerResult)); + agentAsDirectiveHandler->handleDirective(TEST_MESSAGE_ID); + m_wakeSetCompletedFuture.wait_for(WAIT_TIMEOUT_MS); + + std::this_thread::sleep_for(std::chrono::milliseconds(EVENT_PROCESS_DELAY_MS)); + m_eventBus->sendEvent(DeviceStateChangedEvent(m_mockBluetoothDevice2, DeviceState::CONNECTED)); + m_eventBus->sendEvent(DeviceStateChangedEvent(m_mockBluetoothDevice1, DeviceState::CONNECTED)); + })); + + // Verify the connection result. + ASSERT_TRUE(m_mockBluetoothDevice1->isConnected()); + ASSERT_TRUE(m_mockBluetoothDevice2->isConnected()); + // Reset the device category info stored in the database. + m_bluetoothStorage->updateByCategory(TEST_BLUETOOTH_UUID, deviceCategoryToString(DeviceCategory::UNKNOWN)); + m_bluetoothStorage->updateByCategory(TEST_BLUETOOTH_UUID_2, deviceCategoryToString(DeviceCategory::UNKNOWN)); +} + +/** + * Test call to handle ConnectByDeviceProfile directive with an unmatched profile name. + * + * Assumption: + * A @c DeviceManager contains m_mockBluetoothDevice1 and m_mockBluetoothDevice2. + * m_mockBluetoothDevice1 has @c A2DPSinkInterface and @c AVRCPTargetInterface services. + * m_mockBluetoothDevice2 has @c HIDInterface and @c SPPInterface services. + * Use case: + * A @c ConnectByProfile directive is sent down, which contains @c HFPInterface as a profile. + * Expected result: + * 1) m_mockBluetoothDevice1 and m_mockBluetoothDevice2 should not be connected successfully. + * 2) Because no device is connected, the observer should not be notified. + * 3) One @c ConnectByDeviceProfileFailed event should be sent to cloud. + */ +TEST_F(BluetoothTest, test_handleConnectByProfileWithUnmatchedProfileName) { + /* + * Because We're only connecting devices that have been previously connected and currently paired. + * Therefore, assume m_mockBluetoothDevice1 and m_mockBluetoothDevice2 are currently paired. + */ + m_mockBluetoothDevice1->pair(); + m_mockBluetoothDevice2->pair(); + + EXPECT_CALL(*m_mockBluetoothDeviceObserver, onActiveDeviceConnected(_)).Times(Exactly(0)); + EXPECT_CALL(*(m_mockDirectiveHandlerResult.get()), setCompleted()) + .Times(1) + .WillOnce(InvokeWithoutArgs(this, &BluetoothTest::wakeOnSetCompleted)); + EXPECT_CALL(*m_mockContextManager, setState(BLUETOOTH_STATE, _, StateRefreshPolicy::NEVER, _)).Times(Exactly(1)); + std::vector events = {CONNECT_BY_PROFILE_FAILED}; + ASSERT_TRUE(verifyMessagesSentInOrder(events, [this]() { + // Create ConnectedByDeviceId Directive. + auto attachmentManager = std::make_shared>(); + auto avsMessageHeader = + std::make_shared(NAMESPACE_BLUETOOTH, CONNECT_BY_PROFILE_DIRECTIVE, TEST_MESSAGE_ID); + std::shared_ptr directive = + AVSDirective::create("", avsMessageHeader, TEST_CONNECT_BY_PROFILE_PAYLOAD_1, attachmentManager, ""); + // cast to DirectiveHandlerInterface so the compiler can find the correct preHandleDirective signature + std::shared_ptr agentAsDirectiveHandler = m_Bluetooth; + agentAsDirectiveHandler->preHandleDirective(directive, std::move(m_mockDirectiveHandlerResult)); + agentAsDirectiveHandler->handleDirective(TEST_MESSAGE_ID); + m_wakeSetCompletedFuture.wait_for(WAIT_TIMEOUT_MS); + })); + + ASSERT_FALSE(m_mockBluetoothDevice1->isConnected()); + ASSERT_FALSE(m_mockBluetoothDevice2->isConnected()); +} + +/** + * Test call to handle ConnectByDeviceProfile directive with a matched profile. + * + * Assumption: + * A @c DeviceManager contains m_mockBluetoothDevice1 and m_mockBluetoothDevice2. + * m_mockBluetoothDevice1 has @c A2DPSinkInterface and @c AVRCPTargetInterface services. + * m_mockBluetoothDevice2 has @c HIDInterface and @c SPPInterface services. + * Use case: + * A @c ConnectByProfile directive is sent down, which contains @c AVRCPTargetInterface as a profile. + * Expected result: + * 1) m_mockBluetoothDevice1 should be connected successfully. + * 2) m_mockBluetoothDevice2 should not be connected. + * 3) Because m_mockBluetoothDevice is connected, the observer should be notificed once. + * 4) One @c ConnectByDeviceProfileSucceeded event should be sent to cloud. + */ +TEST_F(BluetoothTest, test_handleConnectByProfileWithMatchedProfileName) { + std::mutex waitMutex; + std::unique_lock waitLock(waitMutex); + + // Assume devices are paired previously. + m_mockBluetoothDevice1->pair(); + m_mockBluetoothDevice2->pair(); + + EXPECT_CALL(*m_mockBluetoothDeviceObserver, onActiveDeviceConnected(_)).Times(Exactly(1)); + EXPECT_CALL(*(m_mockDirectiveHandlerResult.get()), setCompleted()) + .Times(1) + .WillOnce(InvokeWithoutArgs(this, &BluetoothTest::wakeOnSetCompleted)); + std::vector events = {CONNECT_BY_PROFILE_SUCCEEDED}; + ASSERT_TRUE(verifyMessagesSentInOrder(events, [this]() { + // Create ConnectedByDeviceId Directive. + auto attachmentManager = std::make_shared>(); + auto avsMessageHeader = + std::make_shared(NAMESPACE_BLUETOOTH, CONNECT_BY_PROFILE_DIRECTIVE, TEST_MESSAGE_ID); + std::shared_ptr directive = + AVSDirective::create("", avsMessageHeader, TEST_CONNECT_BY_PROFILE_PAYLOAD_2, attachmentManager, ""); + // cast to DirectiveHandlerInterface so the compiler can find the correct preHandleDirective signature + std::shared_ptr agentAsDirectiveHandler = m_Bluetooth; + agentAsDirectiveHandler->preHandleDirective(directive, std::move(m_mockDirectiveHandlerResult)); + agentAsDirectiveHandler->handleDirective(TEST_MESSAGE_ID); + m_wakeSetCompletedFuture.wait_for(WAIT_TIMEOUT_MS); + + std::this_thread::sleep_for(std::chrono::milliseconds(EVENT_PROCESS_DELAY_MS)); + m_eventBus->sendEvent(DeviceStateChangedEvent(m_mockBluetoothDevice1, DeviceState::CONNECTED)); + })); + + ASSERT_TRUE(m_mockBluetoothDevice1->isConnected()); + ASSERT_FALSE(m_mockBluetoothDevice2->isConnected()); +} + +/** + * Test call to handle PairDevices directive with matched device UUIDs. + * Assumption: + * A @c DeviceManager contains m_mockBluetoothDevice1 and m_mockBluetoothDevice2. + * m_mockBluetoothDevice1 belongs to DeviceCategory::PHONE, which follows @c BasicDeviceConnectionRule. + * m_mockBluetoothDevice2 belongs to DeviceCategory::GADGET, which follows @c GadgetConnectionRule. + * Use case: + * A @c PairDevices directive is sent down, whose payload contains m_mockBluetoothDevice1 and m_mockBluetoothDevice2. + * Expected result: + * 1) Both devices should be paired successfully. + * 2) Based on the connection rule (m_mockBluetoothDevice1 follows @c BasicDeviceConnectionRule, + * m_mockBluetoothDevice2 follows @c GadgetConnectionRule), m_mockBluetoothDevice1 should be connected successfully. + * 3) A sequence of {PAIR_DEVICES_SUCCEEDED, CONNECT_BY_DEVICE_IDS_SUCCEEDED, PAIR_DEVICES_SUCCEEDED) + * should be sent to cloud. + * a. {PAIR_DEVICES_SUCCEEDED, CONNECT_BY_DEVICE_IDS_SUCCEEDED} are sent when Bluetooth CapabilityAgent tries to pair + * m_mockBluetoothDevice1. + * b. {PAIR_DEVICES_SUCCEEDED} is sent when Bluetooth CapabilityAgent tries to pair m_mockBluetoothDevice2. + */ +TEST_F(BluetoothTest, DISABLED_test_handlePairDevices) { + // Initially, all devices are stored as DeviceCategory::UNKNOWN. Need to manually update device category in order + // to test. + m_bluetoothStorage->updateByCategory(TEST_BLUETOOTH_UUID, deviceCategoryToString(DeviceCategory::PHONE)); + m_bluetoothStorage->updateByCategory(TEST_BLUETOOTH_UUID_2, deviceCategoryToString(DeviceCategory::GADGET)); + + m_messages.insert({PAIR_DEVICES_SUCCEEDED, 0}); + m_messages.insert({CONNECT_BY_DEVICE_IDS_SUCCEEDED, 0}); + std::mutex waitMutex; + + EXPECT_CALL(*(m_mockDirectiveHandlerResult.get()), setCompleted()) + .Times(1) + .WillOnce(InvokeWithoutArgs(this, &BluetoothTest::wakeOnSetCompleted)); + + EXPECT_CALL(*m_mockMessageSender, sendMessage(_)) + .Times(AtLeast(1)) + .WillRepeatedly(Invoke([this](std::shared_ptr request) { + verifyMessagesCount(request, &m_messages); + m_messageSentTrigger.notify_one(); + })); + + // Create PairDevices Directive. + auto attachmentManager = std::make_shared>(); + auto avsMessageHeader = + std::make_shared(NAMESPACE_BLUETOOTH, PAIR_DEVICES_DIRECTIVE, TEST_MESSAGE_ID); + std::shared_ptr directive = + AVSDirective::create("", avsMessageHeader, TEST_PAIR_DEVICES_PAYLOAD, attachmentManager, ""); + // cast to DirectiveHandlerInterface so the compiler can find the correct preHandleDirective signature + std::shared_ptr agentAsDirectiveHandler = m_Bluetooth; + agentAsDirectiveHandler->preHandleDirective(directive, std::move(m_mockDirectiveHandlerResult)); + agentAsDirectiveHandler->handleDirective(TEST_MESSAGE_ID); + m_wakeSetCompletedFuture.wait_for(WAIT_TIMEOUT_MS); + + std::this_thread::sleep_for(std::chrono::milliseconds(EVENT_PROCESS_DELAY_MS)); + m_eventBus->sendEvent(DeviceStateChangedEvent(m_mockBluetoothDevice1, DeviceState::PAIRED)); + m_eventBus->sendEvent(DeviceStateChangedEvent(m_mockBluetoothDevice1, DeviceState::CONNECTED)); + m_eventBus->sendEvent(DeviceStateChangedEvent(m_mockBluetoothDevice2, DeviceState::PAIRED)); + + std::unique_lock lock(waitMutex); + bool result; + result = m_messageSentTrigger.wait_for(lock, WAIT_TIMEOUT_MS, [this] { + for(auto message : m_messages) { + if (message.first == PAIR_DEVICES_SUCCEEDED) { + if (message.second != 2) { + return false; + } + } else if (message.first == CONNECT_BY_DEVICE_IDS_SUCCEEDED) { + if (message.second != 1) { + return false; + } + } + } + return true; + }); + ASSERT_TRUE(result); + + // Verify the pair result. + ASSERT_TRUE(m_mockBluetoothDevice1->isPaired()); + ASSERT_TRUE(m_mockBluetoothDevice1->isConnected()); + ASSERT_TRUE(m_mockBluetoothDevice2->isPaired()); + ASSERT_FALSE(m_mockBluetoothDevice2->isConnected()); + + // Reset device categories. + m_bluetoothStorage->updateByCategory(TEST_BLUETOOTH_UUID, deviceCategoryToString(DeviceCategory::UNKNOWN)); + m_bluetoothStorage->updateByCategory(TEST_BLUETOOTH_UUID_2, deviceCategoryToString(DeviceCategory::UNKNOWN)); +} + +/** + * Test call to handle UnpairDevices directive with matched device UUIDs. + * + * Assumption: + * A @c DeviceManager contains m_mockBluetoothDevice1 and m_mockBluetoothDevice2. + * m_mockBluetoothDevice1 belongs to DeviceCategory::PHONE, whcih follows @c BasicDeviceConnectionRule. + * m_mockBluetoothDevice2 belongs to DeviceCategory::GADGET, which follows @c GadgetConnectionRule. + * Both devices are in paired and connected state. + * Use case: + * First a @c PairDevices directive is sent down, whose payload contains m_mockBluetoothDevice1 + * and m_mockBluetoothDevice2. + * Then a @c UnpairDevices directive is sent down, whose payload contains m_mockBluetoothDevice1 + * and m_mockBluetoothDevice2. + * Expected result: + * 1) Both m_mockBluetoothDevice1 and m_mockBluetoothDevice2 should be unpaired successfully. + * 2) Based on the connection rule, both m_mockBluetoothDevice1 and m_mockBluetoothDevice2 should be diconnected + * successfully. + * 3) Events: + * A sequence of {DISCONNECT_DEVICES_SUCCEEDED, UNPAIR_DEVICES_SUCCEEDED, DISCONNECT_DEVICES_SUCCEEDED, + * UNPAIR_DEVICES_SUCCEEDED} should be sent to cloud: + * a. {DISCONNECT_DEVICES_SUCCEEDED, UNPAIR_DEVICES_SUCCEEDED} are sent when Bluetooth CapabilityAgents tries to unpair + * m_mockBluetoothDevice1. + * b. {DISCONNECT_DEVICES_SUCCEEDED, UNPAIR_DEVICES_SUCCEEDED} are sent when Bluetooth CapabilityAgents tries to unpair + * m_mockBluetoothDevice2. + */ +TEST_F(BluetoothTest, test_handleUnpairDevices) { + // Initially, all devices are stored as DeviceCategory::UNKNOWN. Need to manually update device category in order + // to test. + m_bluetoothStorage->updateByCategory(TEST_BLUETOOTH_UUID, deviceCategoryToString(DeviceCategory::PHONE)); + m_bluetoothStorage->updateByCategory(TEST_BLUETOOTH_UUID_2, deviceCategoryToString(DeviceCategory::GADGET)); + + // Assume all devices are paired and connected before. + m_mockBluetoothDevice1->pair(); + m_mockBluetoothDevice1->connect(); + m_mockBluetoothDevice2->pair(); + m_mockBluetoothDevice2->connect(); + ASSERT_TRUE(m_mockBluetoothDevice1->isConnected()); + ASSERT_TRUE(m_mockBluetoothDevice2->isConnected()); + + EXPECT_CALL(*m_mockBluetoothDeviceObserver, onActiveDeviceDisconnected(_)).Times(Exactly(2)); + EXPECT_CALL(*(m_mockDirectiveHandlerResult.get()), setCompleted()) + .Times(1) + .WillOnce(InvokeWithoutArgs(this, &BluetoothTest::wakeOnSetCompleted)); + std::vector events = {DISCONNECT_DEVICES_SUCCEEDED, UNPAIR_DEVICES_SUCCEEDED, + DISCONNECT_DEVICES_SUCCEEDED, UNPAIR_DEVICES_SUCCEEDED}; + + ASSERT_TRUE(verifyMessagesSentInOrder(events, [this]() { + auto attachmentManager = std::make_shared>(); + auto avsMessageHeader = + std::make_shared(NAMESPACE_BLUETOOTH, UNPAIR_DEVICES_DIRECTIVE, TEST_MESSAGE_ID); + std::shared_ptr directive = + AVSDirective::create("", avsMessageHeader, TEST_UNPAIR_DEVICES_PAYLOAD, attachmentManager, ""); + // cast to DirectiveHandlerInterface so the compiler can find the correct preHandleDirective signature + std::shared_ptr agentAsDirectiveHandler = m_Bluetooth; + + agentAsDirectiveHandler->preHandleDirective(directive, std::move(m_mockDirectiveHandlerResult)); + agentAsDirectiveHandler->handleDirective(TEST_MESSAGE_ID); + m_wakeSetCompletedFuture.wait_for(WAIT_TIMEOUT_MS); + + std::this_thread::sleep_for(std::chrono::milliseconds(EVENT_PROCESS_DELAY_MS)); + m_eventBus->sendEvent(DeviceStateChangedEvent(m_mockBluetoothDevice1, DeviceState::DISCONNECTED)); + m_eventBus->sendEvent(DeviceStateChangedEvent(m_mockBluetoothDevice1, DeviceState::UNPAIRED)); + m_eventBus->sendEvent(DeviceStateChangedEvent(m_mockBluetoothDevice2, DeviceState::DISCONNECTED)); + m_eventBus->sendEvent(DeviceStateChangedEvent(m_mockBluetoothDevice2, DeviceState::UNPAIRED)); + })); + + // Verify the Unpair result. + ASSERT_FALSE(m_mockBluetoothDevice1->isPaired()); + ASSERT_FALSE(m_mockBluetoothDevice1->isConnected()); + ASSERT_FALSE(m_mockBluetoothDevice2->isPaired()); + ASSERT_FALSE(m_mockBluetoothDevice2->isConnected()); + + // Reset device categories. + m_bluetoothStorage->updateByCategory(TEST_BLUETOOTH_UUID, deviceCategoryToString(DeviceCategory::UNKNOWN)); + m_bluetoothStorage->updateByCategory(TEST_BLUETOOTH_UUID_2, deviceCategoryToString(DeviceCategory::UNKNOWN)); +} + +/** + * Test call to handle DisconnectDevices directive with matched device UUIDs. + * + * Assumption: + * A @c DeviceManager contains m_mockBluetoothDevice1 and m_mockBluetoothDevice2. + * Both devices are in paired and connected state. + * Use case: + * A @c DisconnectDevices directive is sent down, whose payload contains m_mockBluetoothDevice1 + * and m_mockBluetoothDevice2. + * Expected result: + * 1) Both m_mockBluetoothDevice1 and m_mockBluetoothDevice2 should be disconnected successfully. + * 2) The observer should be notified the device disconnection once. + * 3) Events: + * A sequnce of {DISCONNECT_DEVICES_SUCCEEDED, DISCONNECT_DEVICES_SUCCEEDED} should be sent to cloud. One for + * m_mockBluetoothDevice1, one for m_mockBluetoothDevice2. + */ +TEST_F(BluetoothTest, test_handleDisconnectDevices) { + // Assume all devices are paired and connected before. + m_mockBluetoothDevice1->pair(); + m_mockBluetoothDevice1->connect(); + m_mockBluetoothDevice2->pair(); + m_mockBluetoothDevice2->connect(); + ASSERT_TRUE(m_mockBluetoothDevice1->isConnected()); + ASSERT_TRUE(m_mockBluetoothDevice2->isConnected()); + + EXPECT_CALL(*m_mockBluetoothDeviceObserver, onActiveDeviceDisconnected(_)).Times(Exactly(2)); + std::vector events = {DISCONNECT_DEVICES_SUCCEEDED, DISCONNECT_DEVICES_SUCCEEDED}; + ASSERT_TRUE(verifyMessagesSentInOrder(events, [this]() { + auto attachmentManager = std::make_shared>(); + auto avsMessageHeader= + std::make_shared(NAMESPACE_BLUETOOTH, DISCONNECT_DEVICES_DIRECTIVE, TEST_MESSAGE_ID); + std::shared_ptr directive = + AVSDirective::create("", avsMessageHeader, TEST_DISCONNECT_DEVICES_PAYLOAD, attachmentManager, ""); + // cast to DirectiveHandlerInterface so the compiler can find the correct preHandleDirective signature + std::shared_ptr agentAsDirectiveHandler = m_Bluetooth; + agentAsDirectiveHandler->preHandleDirective(directive, std::move(m_mockDirectiveHandlerResult)); + agentAsDirectiveHandler->handleDirective(TEST_MESSAGE_ID); + m_wakeSetCompletedPromise = std::promise(); + m_wakeSetCompletedFuture = m_wakeSetCompletedPromise.get_future(); + m_wakeSetCompletedFuture.wait_for(WAIT_TIMEOUT_MS); + + std::this_thread::sleep_for(std::chrono::milliseconds(EVENT_PROCESS_DELAY_MS)); + m_eventBus->sendEvent(DeviceStateChangedEvent(m_mockBluetoothDevice1, DeviceState::DISCONNECTED)); + m_eventBus->sendEvent(DeviceStateChangedEvent(m_mockBluetoothDevice2, DeviceState::DISCONNECTED)); + })); + + ASSERT_FALSE(m_mockBluetoothDevice1->isConnected()); + ASSERT_FALSE(m_mockBluetoothDevice2->isConnected()); +} + +/** + * Test call to handle SetDeviceCategories directive with matched device UUID. + * + * Assumption: + * A @c DeviceManager contains m_mockBluetoothDevice1 and m_mockBluetoothDevice2. Both of them belong to + * DeviceCategory::UNKNOWN + * Use case: + * A @c SetDeviceCategories directive is sent down, whose payload contains m_mockBluetoothDevice1 (need to be set + * to DeviceCategory::PHONE) and m_mockBluetoothDevice2 (need to be set to DeviceCategory::GADGET). + * Expected result: + * 1) Both devices are set the assigned device category successfully. + * 2) Events: + * A sequnce of {SET_DEVICE_CATEGORIES_SUCCEEDED, SET_DEVICE_CATEGORIES_SUCCEEDED} should be sent to cloud. One for + * m_mockBluetoothDevice1, one for m_mockBluetoothDevice2. + */ +TEST_F(BluetoothTest, test_handleSetDeviceCategories) { + std::vector events = {SET_DEVICE_CATEGORIES_SUCCEEDED}; + ASSERT_TRUE(verifyMessagesSentInOrder(events, [this]() { + auto attachmentManager = std::make_shared>(); + auto avsMessageHeader= + std::make_shared(NAMESPACE_BLUETOOTH, SET_DEVICE_CATEGORIES, TEST_MESSAGE_ID); + std::shared_ptr directive = + AVSDirective::create("", avsMessageHeader, TEST_SET_DEVICE_CATEGORIES_PAYLOAD, attachmentManager, ""); + // cast to DirectiveHandlerInterface so the compiler can find the correct preHandleDirective signature + std::shared_ptr agentAsDirectiveHandler = m_Bluetooth; + agentAsDirectiveHandler->preHandleDirective(directive, std::move(m_mockDirectiveHandlerResult)); + agentAsDirectiveHandler->handleDirective(TEST_MESSAGE_ID); + m_wakeSetCompletedPromise = std::promise(); + m_wakeSetCompletedFuture = m_wakeSetCompletedPromise.get_future(); + m_wakeSetCompletedFuture.wait_for(WAIT_TIMEOUT_MS); + + m_Bluetooth->onContextAvailable(MOCK_CONTEXT); + })); + + std::string category1; + std::string category2; + m_bluetoothStorage->getCategory(TEST_BLUETOOTH_UUID, &category1); + m_bluetoothStorage->getCategory(TEST_BLUETOOTH_UUID_2, &category2); + ASSERT_EQ(deviceCategoryToString(DeviceCategory::PHONE), category1); + ASSERT_EQ(deviceCategoryToString(DeviceCategory::GADGET), category2); + + // Reset device categories. + m_bluetoothStorage->updateByCategory(TEST_BLUETOOTH_UUID, deviceCategoryToString(DeviceCategory::UNKNOWN)); + m_bluetoothStorage->updateByCategory(TEST_BLUETOOTH_UUID_2, deviceCategoryToString(DeviceCategory::UNKNOWN)); +} + +/** + * Test streaming state change of multiple device connections. + * + * Assumption: + * A @c DeviceManager contains m_mockBluetoothDevice1 and m_mockBluetoothDevice2. m_mockBluetoothDevice1 belongs to + * DeviceCategory::AUDIO_VIDEO. m_mockBluetoothDevice2 belongs to DeviceCategory::PHONE. + * Use case: + * m_mockBluetoothDevice1 initiates connection and starts streaming audio. + * Then m_mockBluetoothDevice2 initiates connection. + * Expected Result: + * 1) m_mockBluetoothDevice1 should connect. A @c StreamingStarted event should be sent. + * 2) When m_mockBluetoothDevice2 connects, m_mockBluetoothDevice1 should be disconnected. A @c StreamingEnded event + * should be sent. + */ +TEST_F(BluetoothTest, test_streamingStateChange) { + // Initially, all devices are stored as DeviceCategory::UNKNOWN. Need to manually update device category in order + // to test. + m_bluetoothStorage->updateByCategory(TEST_BLUETOOTH_UUID, deviceCategoryToString(DeviceCategory::AUDIO_VIDEO)); + m_bluetoothStorage->updateByCategory(TEST_BLUETOOTH_UUID_2, deviceCategoryToString(DeviceCategory::PHONE)); + + EXPECT_CALL(*m_mockMessageSender, sendMessage(_)) + .Times(AtLeast(1)) + .WillOnce(Invoke([this](std::shared_ptr request) { + verifyMessage(request, STREAMING_STARTED); + })) + .WillOnce(Invoke([this](std::shared_ptr request) { + verifyMessage(request, STREAMING_ENDED); + })); + + // m_mockBluetoothDevice1 initiates connection and starts streaming media. + m_mockBluetoothDevice1->connect(); + m_eventBus->sendEvent(DeviceStateChangedEvent(m_mockBluetoothDevice1, DeviceState::CONNECTED)); + std::this_thread::sleep_for(std::chrono::milliseconds(EVENT_PROCESS_DELAY_MS)); + m_eventBus->sendEvent(MediaStreamingStateChangedEvent( + MediaStreamingState::ACTIVE, A2DPRole::SOURCE, m_mockBluetoothDevice1)); + std::this_thread::sleep_for(std::chrono::milliseconds(EVENT_PROCESS_DELAY_MS)); + + // m_mockBluetoothDevice2 initiates connection. + m_mockBluetoothDevice2->connect(); + m_eventBus->sendEvent(DeviceStateChangedEvent(m_mockBluetoothDevice2, DeviceState::CONNECTED)); + + // Reset device categories. + m_bluetoothStorage->updateByCategory(TEST_BLUETOOTH_UUID, deviceCategoryToString(DeviceCategory::UNKNOWN)); + m_bluetoothStorage->updateByCategory(TEST_BLUETOOTH_UUID_2, deviceCategoryToString(DeviceCategory::UNKNOWN)); +} +} // namespace test +} // namespace bluetooth +} // namespace capabilityAgents +} // namespace alexaClientSDK \ No newline at end of file diff --git a/CapabilityAgents/Bluetooth/test/CMakeLists.txt b/CapabilityAgents/Bluetooth/test/CMakeLists.txt index 60d2aea1..e624747b 100644 --- a/CapabilityAgents/Bluetooth/test/CMakeLists.txt +++ b/CapabilityAgents/Bluetooth/test/CMakeLists.txt @@ -3,6 +3,7 @@ cmake_minimum_required(VERSION 3.1 FATAL_ERROR) set(INCLUDE_PATH "${Bluetooth_INCLUDE_DIRS}" "${AVSCommon_SOURCE_DIR}/AVS/test" - "${AVSCommon_SOURCE_DIR}/SDKInterfaces/test") + "${AVSCommon_SOURCE_DIR}/SDKInterfaces/test" + "${AVSCommon_SOURCE_DIR}/Utils/test") -discover_unit_tests("${INCLUDE_PATH}" Bluetooth) +discover_unit_tests("${INCLUDE_PATH}" "Bluetooth;UtilsCommonTestLib") diff --git a/CapabilityAgents/Bluetooth/test/SQLiteBluetoothStorageTest.cpp b/CapabilityAgents/Bluetooth/test/SQLiteBluetoothStorageTest.cpp index 23fce845..9d9a6823 100644 --- a/CapabilityAgents/Bluetooth/test/SQLiteBluetoothStorageTest.cpp +++ b/CapabilityAgents/Bluetooth/test/SQLiteBluetoothStorageTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018-2020 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. @@ -14,6 +14,7 @@ */ #include +#include #include #include #include @@ -63,6 +64,24 @@ static const std::string TEST_UUID = "650f973b-c2ab-4c6e-bff4-3788cd521340"; /// Second Test UUID. static const std::string TEST_UUID_2 = "750f973b-c2ab-4c6e-bff4-3788cd521340"; +/// Test Unknown MAC/Category. +static const std::string TEST_UNKNOWN = "UNKNOWN"; + +/// Test Other Category. +static const std::string TEST_OTHER = "OTHER"; + +/// Test Phone Category. +static const std::string TEST_PHONE = "PHONE"; + +/// Table name. +static const std::string UUID_TABLE_NAME = "uuidMapping"; + +/// The UUID column. +static const std::string COLUMN_UUID = "uuid"; + +/// The MAC address column. +static const std::string COLUMN_MAC = "mac"; + /** * Checks whether a file exists in the file system. * @@ -86,6 +105,20 @@ protected: /// Cleanup function to close and delete the database. void closeAndDeleteDB(); + /// Function to create legacy database. + bool createLegacyDatabase(); + + /// Insert Entry for legacy database. + bool insertEntryLegacy(const std::string& uuid, const std::string& mac); + + /** + * Helper function to setup database. + * + * @param migrated Whether using migrated database. + * @return bool Whether database setup successful. + */ + bool setupDatabase(bool migratedDatabase); + /** * Helper function that abstracts the test logic for getOrderedMac teste cases. * @@ -106,14 +139,15 @@ protected: const std::unordered_map& expected); /** - * Helper function that abstracts the logic for getMac and getUuid test cases. + * Helper function that abstracts the logic for insertByMac given a macToUuids map and + * verifies the expected value with the one returned by the retrieveValue function. * - * @param retrieveValue Either the getMac or getUuid function. + * @param retrieveValue function to retrieve value from database (getMac/getUuid/getCategory). * @param key The key (either the mac or uuid). - * @param expectedValue The expected value (either the mac or uuid). + * @param expectedValue The expected value from the given retrieveValue function call. * @param macToUuids A map of macToUuids to initialize the database with. */ - void getMacOrUuidHelper( + void getRetrieveValueHelper( std::function retrieveValue, const std::string& key, const std::string& expectedValue, @@ -121,19 +155,90 @@ protected: /// The database instance. Protected because it needs to be accessed in test cases. std::unique_ptr m_db; + + /// SQLiteDatabase instance. + std::unique_ptr m_sqLiteDb; }; void SQLiteBluetoothStorageTest::closeAndDeleteDB() { if (m_db) { m_db->close(); } + if (m_sqLiteDb) { + m_sqLiteDb->close(); + } m_db.reset(); + m_sqLiteDb.reset(); if (fileExists(TEST_DATABASE)) { remove(TEST_DATABASE.c_str()); } } +bool SQLiteBluetoothStorageTest::createLegacyDatabase() { + m_sqLiteDb = std::unique_ptr( + new alexaClientSDK::storage::sqliteStorage::SQLiteDatabase(TEST_DATABASE)); + + if (!m_sqLiteDb || !m_sqLiteDb->initialize()) { + return false; + } + + if (!m_sqLiteDb->performQuery( + "CREATE TABLE " + UUID_TABLE_NAME + "(" + COLUMN_UUID + " text not null unique, " + COLUMN_MAC + + " text not null unique);")) { + m_sqLiteDb->close(); + return false; + } + + return true; +} + +bool SQLiteBluetoothStorageTest::insertEntryLegacy(const std::string& uuid, const std::string& mac) { + // clang-format off + const std::string sqlString = "INSERT INTO " + UUID_TABLE_NAME + + " (" + COLUMN_UUID + "," + COLUMN_MAC + ") VALUES (?,?);"; + // clang-format on + + auto statement = m_sqLiteDb->createStatement(sqlString); + if (!statement) { + return false; + } + + const int UUID_INDEX = 1; + const int MAC_INDEX = 2; + + if (!statement->bindStringParameter(UUID_INDEX, uuid) || !statement->bindStringParameter(MAC_INDEX, mac)) { + return false; + } + + // This could be due to a mac or uuid already existing in the db. + if (!statement->step()) { + return false; + } + + return true; +} + +bool SQLiteBluetoothStorageTest::setupDatabase(bool migratedDatabase) { + if (migratedDatabase) { + // Create legacy database and migrate. + createLegacyDatabase(); + + m_db = SQLiteBluetoothStorage::create(ConfigurationNode::getRoot()); + if (!m_db || !(m_db->open())) { + return false; + } + } else { + // Create a new database. + m_db = SQLiteBluetoothStorage::create(ConfigurationNode::getRoot()); + if (!m_db || !(m_db->createDatabase())) { + return false; + } + } + + return true; +} + void SQLiteBluetoothStorageTest::SetUp() { // Ensure the db file does not exist already. We don't want to overwrite anything. if (fileExists(TEST_DATABASE)) { @@ -147,11 +252,6 @@ void SQLiteBluetoothStorageTest::SetUp() { std::vector> jsonStream; jsonStream.push_back(json); ASSERT_TRUE(ConfigurationNode::initialize(jsonStream)); - - m_db = SQLiteBluetoothStorage::create(ConfigurationNode::getRoot()); - - ASSERT_THAT(m_db, NotNull()); - ASSERT_TRUE(m_db->createDatabase()); } void SQLiteBluetoothStorageTest::TearDown() { @@ -190,7 +290,7 @@ void SQLiteBluetoothStorageTest::getRowsHelper( ASSERT_THAT(rows, Eq(expected)); } -void SQLiteBluetoothStorageTest::getMacOrUuidHelper( +void SQLiteBluetoothStorageTest::getRetrieveValueHelper( std::function retrieveValue, const std::string& key, const std::string& expectedValue, @@ -204,8 +304,121 @@ void SQLiteBluetoothStorageTest::getMacOrUuidHelper( ASSERT_THAT(value, Eq(expectedValue)); } +/// Test database not created yet, open should fail. +TEST_F(SQLiteBluetoothStorageTest, uninitializedDatabase) { + m_db = SQLiteBluetoothStorage::create(ConfigurationNode::getRoot()); + ASSERT_THAT(m_db, NotNull()); + ASSERT_FALSE(m_db->open()); +} + +/// Test if 2.0 database already created, open should succeed. +TEST_F(SQLiteBluetoothStorageTest, openDatabase) { + m_db = SQLiteBluetoothStorage::create(ConfigurationNode::getRoot()); + ASSERT_THAT(m_db, NotNull()); + ASSERT_TRUE(m_db->createDatabase()); + m_db->close(); + + m_db = SQLiteBluetoothStorage::create(ConfigurationNode::getRoot()); + ASSERT_THAT(m_db, NotNull()); + ASSERT_TRUE(m_db->open()); +} + +/// Test if 1.0 database already created, open should succeed. +TEST_F(SQLiteBluetoothStorageTest, openLegacyDatabase) { + createLegacyDatabase(); + + m_db = SQLiteBluetoothStorage::create(ConfigurationNode::getRoot()); + ASSERT_THAT(m_db, NotNull()); + ASSERT_TRUE(m_db->open()); +} + +/// Test retrieving category for a UUID that does not exist after database migration. +TEST_F(SQLiteBluetoothStorageTest, retrieveCategoryforUnknownUUID) { + createLegacyDatabase(); + + m_db = SQLiteBluetoothStorage::create(ConfigurationNode::getRoot()); + ASSERT_THAT(m_db, NotNull()); + ASSERT_TRUE(m_db->open()); + + std::string category; + ASSERT_FALSE(m_db->getCategory(TEST_UUID, &category)); + ASSERT_THAT(category, Eq("")); +} + +/// Test insertByMac after database migration. +TEST_F(SQLiteBluetoothStorageTest, insertByMacPostDatabaseUpgrade) { + createLegacyDatabase(); + + m_db = SQLiteBluetoothStorage::create(ConfigurationNode::getRoot()); + ASSERT_THAT(m_db, NotNull()); + ASSERT_TRUE(m_db->open()); + + ASSERT_TRUE(m_db->insertByMac(TEST_MAC, TEST_UUID)); + + std::string category; + ASSERT_TRUE(m_db->getCategory(TEST_UUID, &category)); + ASSERT_THAT(category, Eq(TEST_UNKNOWN)); +} + +/// Test retrieving mac for a UUID saved before migration after database migration. +TEST_F(SQLiteBluetoothStorageTest, retrieveMacforKnownUUID) { + createLegacyDatabase(); + + insertEntryLegacy(TEST_UUID, TEST_MAC); + + m_db = SQLiteBluetoothStorage::create(ConfigurationNode::getRoot()); + ASSERT_THAT(m_db, NotNull()); + ASSERT_TRUE(m_db->open()); + + std::string mac; + ASSERT_TRUE(m_db->getMac(TEST_UUID, &mac)); + ASSERT_THAT(mac, Eq(TEST_MAC)); +} + +/// Test retrieving category for a UUID saved before migration after database migration. +TEST_F(SQLiteBluetoothStorageTest, retrieveCategoryforKnownUUID) { + createLegacyDatabase(); + + insertEntryLegacy(TEST_UUID, TEST_MAC); + + m_db = SQLiteBluetoothStorage::create(ConfigurationNode::getRoot()); + ASSERT_THAT(m_db, NotNull()); + ASSERT_TRUE(m_db->open()); + + std::string category; + ASSERT_TRUE(m_db->getCategory(TEST_UUID, &category)); + ASSERT_THAT(category, Eq(TEST_OTHER)); +} + +/// Test retrieving category for multiple UUIDs saved before migration after database migration. +TEST_F(SQLiteBluetoothStorageTest, retrieveCategoryforKnownMultipleUUID) { + createLegacyDatabase(); + + insertEntryLegacy(TEST_UUID, TEST_MAC); + insertEntryLegacy(TEST_UUID_2, TEST_MAC_2); + + m_db = SQLiteBluetoothStorage::create(ConfigurationNode::getRoot()); + ASSERT_THAT(m_db, NotNull()); + ASSERT_TRUE(m_db->open()); + + std::string category; + ASSERT_TRUE(m_db->getCategory(TEST_UUID, &category)); + ASSERT_THAT(category, Eq(TEST_OTHER)); + + ASSERT_TRUE(m_db->getCategory(TEST_UUID_2, &category)); + ASSERT_THAT(category, Eq(TEST_OTHER)); +} + +/// Parameterized tests to test both migrated and newly created databases. +class SQLiteBluetoothStorageParameterizedTests + : public SQLiteBluetoothStorageTest + , public ::testing::WithParamInterface {}; + +INSTANTIATE_TEST_CASE_P(Parameterized, SQLiteBluetoothStorageParameterizedTests, ::testing::Values(true, false)); + /// Tests the create function with an invalid root. -TEST_F(SQLiteBluetoothStorageTest, test_createInvalidConfigurationRoot) { +TEST_P(SQLiteBluetoothStorageParameterizedTests, test_createInvalidConfigurationRoot) { + ASSERT_TRUE(setupDatabase(GetParam())); ConfigurationNode::uninitialize(); std::vector> empty; ConfigurationNode::initialize(empty); @@ -214,13 +427,15 @@ TEST_F(SQLiteBluetoothStorageTest, test_createInvalidConfigurationRoot) { } /// Tests creating a database object. -TEST_F(SQLiteBluetoothStorageTest, test_createValidConfigurationRoot) { +TEST_P(SQLiteBluetoothStorageParameterizedTests, test_createValidConfigurationRoot) { + ASSERT_TRUE(setupDatabase(GetParam())); // SQLite allows simultaneous access to the database. ASSERT_THAT(SQLiteBluetoothStorage::create(ConfigurationNode::getRoot()), NotNull()); } /// Test creating a valid DB. This is implicitly tested in the SetUp() function, but we formally test it here. -TEST_F(SQLiteBluetoothStorageTest, test_createDatabaseSucceeds) { +TEST_P(SQLiteBluetoothStorageParameterizedTests, test_createDatabaseSucceeds) { + ASSERT_TRUE(setupDatabase(GetParam())); closeAndDeleteDB(); m_db = SQLiteBluetoothStorage::create(ConfigurationNode::getRoot()); @@ -229,18 +444,21 @@ TEST_F(SQLiteBluetoothStorageTest, test_createDatabaseSucceeds) { } /// Test that creating an existing DB fails. -TEST_F(SQLiteBluetoothStorageTest, test_createExistingDatabaseFails) { +TEST_P(SQLiteBluetoothStorageParameterizedTests, test_createExistingDatabaseFails) { + ASSERT_TRUE(setupDatabase(GetParam())); ASSERT_FALSE(m_db->createDatabase()); } /// Test opening an existing database. -TEST_F(SQLiteBluetoothStorageTest, test_openExistingDatabaseSucceeds) { +TEST_P(SQLiteBluetoothStorageParameterizedTests, test_openExistingDatabaseSucceeds) { + ASSERT_TRUE(setupDatabase(GetParam())); m_db->close(); ASSERT_TRUE(m_db->open()); } /// Test clearing the table with one row. -TEST_F(SQLiteBluetoothStorageTest, test_clearOnOneRowSucceeds) { +TEST_P(SQLiteBluetoothStorageParameterizedTests, test_clearOnOneRowSucceeds) { + ASSERT_TRUE(setupDatabase(GetParam())); ASSERT_TRUE(m_db->insertByMac(TEST_MAC, TEST_UUID)); ASSERT_TRUE(m_db->clear()); std::unordered_map rows; @@ -249,7 +467,8 @@ TEST_F(SQLiteBluetoothStorageTest, test_clearOnOneRowSucceeds) { } /// Test clearing the table with multiple rows. -TEST_F(SQLiteBluetoothStorageTest, test_clearOnMultipleRowsSucceeds) { +TEST_P(SQLiteBluetoothStorageParameterizedTests, test_clearOnMultipleRowsSucceeds) { + ASSERT_TRUE(setupDatabase(GetParam())); ASSERT_TRUE(m_db->insertByMac(TEST_MAC, TEST_UUID)); ASSERT_TRUE(m_db->insertByMac(TEST_MAC_2, TEST_UUID_2)); ASSERT_TRUE(m_db->clear()); @@ -259,7 +478,8 @@ TEST_F(SQLiteBluetoothStorageTest, test_clearOnMultipleRowsSucceeds) { } /// Test clearing the table when it's already empty. -TEST_F(SQLiteBluetoothStorageTest, test_clearOnEmptySucceeds) { +TEST_P(SQLiteBluetoothStorageParameterizedTests, test_clearOnEmptySucceeds) { + ASSERT_TRUE(setupDatabase(GetParam())); ASSERT_TRUE(m_db->clear()); std::unordered_map rows; ASSERT_TRUE(m_db->getUuidToMac(&rows)); @@ -267,67 +487,116 @@ TEST_F(SQLiteBluetoothStorageTest, test_clearOnEmptySucceeds) { } /// Test getUuid with one row containing UUID. -TEST_F(SQLiteBluetoothStorageTest, test_getUuidWithOneSucceeds) { +TEST_P(SQLiteBluetoothStorageParameterizedTests, test_getUuidWithOneSucceeds) { + ASSERT_TRUE(setupDatabase(GetParam())); const std::unordered_map data{{TEST_MAC, TEST_UUID}}; - getMacOrUuidHelper(&SQLiteBluetoothStorage::getUuid, TEST_MAC, TEST_UUID, data); + getRetrieveValueHelper(&SQLiteBluetoothStorage::getUuid, TEST_MAC, TEST_UUID, data); } /// Test getUuid with multiple rows, one of which contains the UUID. -TEST_F(SQLiteBluetoothStorageTest, test_getUuidWithMultipleSucceeds) { +TEST_P(SQLiteBluetoothStorageParameterizedTests, test_getUuidWithMultipleSucceeds) { + ASSERT_TRUE(setupDatabase(GetParam())); const std::unordered_map data{{TEST_MAC, TEST_UUID}, {TEST_MAC_2, TEST_UUID_2}}; - getMacOrUuidHelper(&SQLiteBluetoothStorage::getUuid, TEST_MAC, TEST_UUID, data); + getRetrieveValueHelper(&SQLiteBluetoothStorage::getUuid, TEST_MAC, TEST_UUID, data); } /// Test getUuid with no matching UUID. -TEST_F(SQLiteBluetoothStorageTest, test_getUuidNoMatchingFails) { +TEST_P(SQLiteBluetoothStorageParameterizedTests, test_getUuidNoMatchingFails) { + ASSERT_TRUE(setupDatabase(GetParam())); std::string uuid; ASSERT_FALSE(m_db->getUuid(TEST_MAC, &uuid)); } /// Test getMac with one row containing MAC. -TEST_F(SQLiteBluetoothStorageTest, test_getMacWithOneSucceeds) { +TEST_P(SQLiteBluetoothStorageParameterizedTests, test_getMacWithOneSucceeds) { + ASSERT_TRUE(setupDatabase(GetParam())); const std::unordered_map data{{TEST_MAC, TEST_UUID}}; - getMacOrUuidHelper(&SQLiteBluetoothStorage::getMac, TEST_UUID, TEST_MAC, data); + getRetrieveValueHelper(&SQLiteBluetoothStorage::getMac, TEST_UUID, TEST_MAC, data); } /// Test getMac with multiple rows, one of which contains the MAC. -TEST_F(SQLiteBluetoothStorageTest, test_getMacWithMultipleSucceeds) { +TEST_P(SQLiteBluetoothStorageParameterizedTests, test_getMacWithMultipleSucceeds) { + ASSERT_TRUE(setupDatabase(GetParam())); const std::unordered_map data{{TEST_MAC, TEST_UUID}, {TEST_MAC_2, TEST_UUID_2}}; - getMacOrUuidHelper(&SQLiteBluetoothStorage::getMac, TEST_UUID, TEST_MAC, data); + getRetrieveValueHelper(&SQLiteBluetoothStorage::getMac, TEST_UUID, TEST_MAC, data); } /// Test getMac with no matching MAC. -TEST_F(SQLiteBluetoothStorageTest, test_getMacNoMatchingFails) { +TEST_P(SQLiteBluetoothStorageParameterizedTests, test_getMacNoMatchingFails) { + ASSERT_TRUE(setupDatabase(GetParam())); std::string mac; ASSERT_FALSE(m_db->getMac(TEST_UUID, &mac)); } +/// Test getCategory with one row containing Unknown Category. +TEST_P(SQLiteBluetoothStorageParameterizedTests, getCategoryWithOneSucceeds) { + ASSERT_TRUE(setupDatabase(GetParam())); + const std::unordered_map data{{TEST_MAC, TEST_UUID}}; + + getRetrieveValueHelper(&SQLiteBluetoothStorage::getCategory, TEST_UUID, TEST_UNKNOWN, data); +} + +/// Test getCategory with multiple rows, two of which contains UNKNOWN, one is updated to PHONE. +TEST_P(SQLiteBluetoothStorageParameterizedTests, getCategoryWithMultipleSucceeds) { + ASSERT_TRUE(setupDatabase(GetParam())); + const std::unordered_map data{{TEST_MAC, TEST_UUID}, {TEST_MAC_2, TEST_UUID_2}}; + + getRetrieveValueHelper(&SQLiteBluetoothStorage::getCategory, TEST_UUID, TEST_UNKNOWN, data); + ASSERT_TRUE(m_db->updateByCategory(TEST_UUID, TEST_PHONE)); + + std::string category; + ASSERT_TRUE(m_db->getCategory(TEST_UUID, &category)); + ASSERT_THAT(category, Eq(TEST_PHONE)); +} + +/// Test getCategory with multiple rows, two of which contains UNKNOWN, one is updated to PHONE, +/// verify insertByMac preserves the category. +TEST_P(SQLiteBluetoothStorageParameterizedTests, getCategoryWithMultipleInsertByMacSucceeds) { + ASSERT_TRUE(setupDatabase(GetParam())); + const std::unordered_map data{{TEST_MAC, TEST_UUID}, {TEST_MAC_2, TEST_UUID_2}}; + + getRetrieveValueHelper(&SQLiteBluetoothStorage::getCategory, TEST_UUID, TEST_UNKNOWN, data); + ASSERT_TRUE(m_db->updateByCategory(TEST_UUID, TEST_PHONE)); + getRetrieveValueHelper(&SQLiteBluetoothStorage::getCategory, TEST_UUID, TEST_PHONE, data); +} + +/// Test getCategory with no matching category for given uuid. +TEST_P(SQLiteBluetoothStorageParameterizedTests, getCategoryNoMatchingFails) { + ASSERT_TRUE(setupDatabase(GetParam())); + std::string category; + ASSERT_FALSE(m_db->getCategory(TEST_UUID, &category)); +} + /// Test getMacToUuid with one row. -TEST_F(SQLiteBluetoothStorageTest, test_getMacToUuidWithOneRowSucceeds) { +TEST_P(SQLiteBluetoothStorageParameterizedTests, test_getMacToUuidWithOneRowSucceeds) { + ASSERT_TRUE(setupDatabase(GetParam())); const std::unordered_map data{{TEST_MAC, TEST_UUID}}; getRowsHelper(&SQLiteBluetoothStorage::getMacToUuid, data, data); } /// Test getMacToUuid with multiple expected. -TEST_F(SQLiteBluetoothStorageTest, test_getMacToUuidWithMultipleRowsSucceeds) { +TEST_P(SQLiteBluetoothStorageParameterizedTests, test_getMacToUuidWithMultipleRowsSucceeds) { + ASSERT_TRUE(setupDatabase(GetParam())); const std::unordered_map data{{TEST_MAC, TEST_UUID}, {TEST_MAC_2, TEST_UUID_2}}; getRowsHelper(&SQLiteBluetoothStorage::getMacToUuid, data, data); } /// Test getMacToUuid when empty. -TEST_F(SQLiteBluetoothStorageTest, test_getMacToUuidWithEmptySucceeds) { +TEST_P(SQLiteBluetoothStorageParameterizedTests, test_getMacToUuidWithEmptySucceeds) { + ASSERT_TRUE(setupDatabase(GetParam())); std::unordered_map data; getRowsHelper(&SQLiteBluetoothStorage::getMacToUuid, data, data); } /// Test getUuidToMac with one row. -TEST_F(SQLiteBluetoothStorageTest, test_getUuidToMacWithOneRowSucceeds) { +TEST_P(SQLiteBluetoothStorageParameterizedTests, test_getUuidToMacWithOneRowSucceeds) { + ASSERT_TRUE(setupDatabase(GetParam())); const std::unordered_map data{{TEST_MAC, TEST_UUID}}; const std::unordered_map expected{{TEST_UUID, TEST_MAC}}; @@ -336,7 +605,8 @@ TEST_F(SQLiteBluetoothStorageTest, test_getUuidToMacWithOneRowSucceeds) { } /// Test getUuidToMac with multiple expected. -TEST_F(SQLiteBluetoothStorageTest, test_getUuidToMacWithMultipleRowsSucceeds) { +TEST_P(SQLiteBluetoothStorageParameterizedTests, test_getUuidToMacWithMultipleRowsSucceeds) { + ASSERT_TRUE(setupDatabase(GetParam())); const std::unordered_map data{{TEST_MAC, TEST_UUID}, {TEST_MAC_2, TEST_UUID_2}}; const std::unordered_map expected{{TEST_UUID, TEST_MAC}, {TEST_UUID_2, TEST_MAC_2}}; @@ -344,23 +614,131 @@ TEST_F(SQLiteBluetoothStorageTest, test_getUuidToMacWithMultipleRowsSucceeds) { } /// Test getUuidToMac when empty. -TEST_F(SQLiteBluetoothStorageTest, test_getUuidToMacWithEmptySucceeds) { +TEST_P(SQLiteBluetoothStorageParameterizedTests, test_getUuidToMacWithEmptySucceeds) { + ASSERT_TRUE(setupDatabase(GetParam())); std::unordered_map data; getRowsHelper(&SQLiteBluetoothStorage::getUuidToMac, data, data); } +/// Test getUuidToCategory with one row. +TEST_P(SQLiteBluetoothStorageParameterizedTests, getUuidToCategoryWithOneRowSucceeds) { + ASSERT_TRUE(setupDatabase(GetParam())); + const std::unordered_map data{{TEST_MAC, TEST_UUID}}; + + const std::unordered_map expected{{TEST_UUID, TEST_UNKNOWN}}; + + getRowsHelper(&SQLiteBluetoothStorage::getUuidToCategory, data, expected); +} + +/// Test getUuidToCategory with one row, updated category to PHONE. +TEST_P(SQLiteBluetoothStorageParameterizedTests, getUuidToCategoryWithOneRowUpdateCategorySucceeds) { + ASSERT_TRUE(setupDatabase(GetParam())); + const std::unordered_map data{{TEST_MAC, TEST_UUID}}; + + const std::unordered_map expected{{TEST_UUID, TEST_UNKNOWN}}; + + const std::unordered_map expectedUpdate{{TEST_UUID, TEST_PHONE}}; + + getRowsHelper(&SQLiteBluetoothStorage::getUuidToCategory, data, expected); + + ASSERT_TRUE(m_db->updateByCategory(TEST_UUID, TEST_PHONE)); + + getRowsHelper(&SQLiteBluetoothStorage::getUuidToCategory, data, expectedUpdate); +} + +/// Test getUuidToCategory with multiple expected. +TEST_P(SQLiteBluetoothStorageParameterizedTests, getUuidToCategoryWithMultipleRowsSucceeds) { + ASSERT_TRUE(setupDatabase(GetParam())); + const std::unordered_map data{{TEST_MAC, TEST_UUID}, {TEST_MAC_2, TEST_UUID_2}}; + + const std::unordered_map expected{{TEST_UUID, TEST_UNKNOWN}, {TEST_UUID_2, TEST_UNKNOWN}}; + + getRowsHelper(&SQLiteBluetoothStorage::getUuidToCategory, data, expected); +} + +/// Test getUuidToCategory when empty. +TEST_P(SQLiteBluetoothStorageParameterizedTests, getUuidToCategoryWithEmptySucceeds) { + ASSERT_TRUE(setupDatabase(GetParam())); + std::unordered_map data; + getRowsHelper(&SQLiteBluetoothStorage::getUuidToCategory, data, data); +} + +/// Test getMacToCategory with one row. +TEST_P(SQLiteBluetoothStorageParameterizedTests, getMacToCategoryWithOneRowSucceeds) { + ASSERT_TRUE(setupDatabase(GetParam())); + const std::unordered_map data{{TEST_MAC, TEST_UUID}}; + + const std::unordered_map expected{{TEST_MAC, TEST_UNKNOWN}}; + + getRowsHelper(&SQLiteBluetoothStorage::getMacToCategory, data, expected); +} + +/// Test getMacToCategory with one row, updated category to PHONE. +TEST_P(SQLiteBluetoothStorageParameterizedTests, getMacToCategoryWithOneRowUpdateCategorySucceeds) { + ASSERT_TRUE(setupDatabase(GetParam())); + const std::unordered_map data{{TEST_MAC, TEST_UUID}}; + + const std::unordered_map expected{{TEST_MAC, TEST_UNKNOWN}}; + + const std::unordered_map expectedUpdate{{TEST_MAC, TEST_PHONE}}; + + getRowsHelper(&SQLiteBluetoothStorage::getMacToCategory, data, expected); + + ASSERT_TRUE(m_db->updateByCategory(TEST_UUID, TEST_PHONE)); + + getRowsHelper(&SQLiteBluetoothStorage::getMacToCategory, data, expectedUpdate); +} + +/// Test getMacToCategory with multiple expected. +TEST_P(SQLiteBluetoothStorageParameterizedTests, getMacToCategoryWithMultipleRowsSucceeds) { + ASSERT_TRUE(setupDatabase(GetParam())); + const std::unordered_map data{{TEST_MAC, TEST_UUID}, {TEST_MAC_2, TEST_UUID_2}}; + + const std::unordered_map expected{{TEST_MAC, TEST_UNKNOWN}, {TEST_MAC_2, TEST_UNKNOWN}}; + + getRowsHelper(&SQLiteBluetoothStorage::getMacToCategory, data, expected); +} + +/// Test getMacToCategory when empty. +TEST_P(SQLiteBluetoothStorageParameterizedTests, getMacToCategoryWithEmptySucceeds) { + ASSERT_TRUE(setupDatabase(GetParam())); + std::unordered_map data; + getRowsHelper(&SQLiteBluetoothStorage::getMacToCategory, data, data); +} + /// Test getOrderedMac and retrieve the macs in ascending insertion order (oldest first). -TEST_F(SQLiteBluetoothStorageTest, test_getOrderedMacAscending) { +TEST_P(SQLiteBluetoothStorageParameterizedTests, test_getOrderedMacAscending) { + ASSERT_TRUE(setupDatabase(GetParam())); getOrderedMacHelper(true); } /// Test getOrderedMac and retrieve the macs in descending insertion order (newest first). -TEST_F(SQLiteBluetoothStorageTest, test_getOrderedMacDescending) { +TEST_P(SQLiteBluetoothStorageParameterizedTests, test_getOrderedMacDescending) { + ASSERT_TRUE(setupDatabase(GetParam())); getOrderedMacHelper(false); } +/// Test updateByCategory succeeds. +TEST_P(SQLiteBluetoothStorageParameterizedTests, updateByCategorySucceeds) { + ASSERT_TRUE(setupDatabase(GetParam())); + ASSERT_TRUE(m_db->insertByMac(TEST_MAC, TEST_UUID)); + ASSERT_TRUE(m_db->updateByCategory(TEST_UUID, TEST_PHONE)); + + std::string category; + ASSERT_TRUE(m_db->getCategory(TEST_UUID, &category)); + ASSERT_THAT(category, Eq(TEST_PHONE)); +} + +/// Test updateByCategory with no matching uuid. +TEST_P(SQLiteBluetoothStorageParameterizedTests, updateByCategoryNoMatchingFails) { + ASSERT_TRUE(setupDatabase(GetParam())); + std::string category; + ASSERT_FALSE(m_db->updateByCategory(TEST_UUID, TEST_PHONE)); +} + /// Test insertByMac succeeds. -TEST_F(SQLiteBluetoothStorageTest, test_insertByMacSucceeds) { +TEST_P(SQLiteBluetoothStorageParameterizedTests, test_insertByMacSucceeds) { + ASSERT_TRUE(setupDatabase(GetParam())); const std::unordered_map expected{{TEST_MAC, TEST_UUID}}; ASSERT_TRUE(m_db->insertByMac(TEST_MAC, TEST_UUID)); @@ -371,13 +749,15 @@ TEST_F(SQLiteBluetoothStorageTest, test_insertByMacSucceeds) { } /// Test insertByMac existing fails. -TEST_F(SQLiteBluetoothStorageTest, test_insertByMacDuplicateFails) { +TEST_P(SQLiteBluetoothStorageParameterizedTests, test_insertByMacDuplicateFails) { + ASSERT_TRUE(setupDatabase(GetParam())); ASSERT_TRUE(m_db->insertByMac(TEST_MAC, TEST_UUID)); ASSERT_FALSE(m_db->insertByMac(TEST_MAC, TEST_UUID, false)); } /// Test insertByMac with overwrite succeeds. -TEST_F(SQLiteBluetoothStorageTest, test_insertByMacOverwriteSucceeds) { +TEST_P(SQLiteBluetoothStorageParameterizedTests, test_insertByMacOverwriteSucceeds) { + ASSERT_TRUE(setupDatabase(GetParam())); const std::unordered_map expected{{TEST_MAC, TEST_UUID}}; ASSERT_TRUE(m_db->insertByMac(TEST_MAC, TEST_UUID_2)); @@ -389,7 +769,8 @@ TEST_F(SQLiteBluetoothStorageTest, test_insertByMacOverwriteSucceeds) { } /// Test remove succeeds. -TEST_F(SQLiteBluetoothStorageTest, test_removeExistingSucceeds) { +TEST_P(SQLiteBluetoothStorageParameterizedTests, test_removeExistingSucceeds) { + ASSERT_TRUE(setupDatabase(GetParam())); ASSERT_TRUE(m_db->insertByMac(TEST_MAC, TEST_UUID)); ASSERT_TRUE(m_db->remove(TEST_MAC)); @@ -400,7 +781,8 @@ TEST_F(SQLiteBluetoothStorageTest, test_removeExistingSucceeds) { } /// Test remove on non-existing record succeeds. -TEST_F(SQLiteBluetoothStorageTest, test_removeNonExistingSucceeds) { +TEST_P(SQLiteBluetoothStorageParameterizedTests, test_removeNonExistingSucceeds) { + ASSERT_TRUE(setupDatabase(GetParam())); ASSERT_TRUE(m_db->remove(TEST_MAC)); std::unordered_map rows; diff --git a/CapabilityAgents/CMakeLists.txt b/CapabilityAgents/CMakeLists.txt index 96bac44b..c604d61c 100644 --- a/CapabilityAgents/CMakeLists.txt +++ b/CapabilityAgents/CMakeLists.txt @@ -1,51 +1,58 @@ cmake_minimum_required(VERSION 3.0) project(CapabilityAgents LANGUAGES CXX) -include(../build/BuildDefaults.cmake) +include(${AVS_CORE}/build/BuildDefaults.cmake) -add_subdirectory("AIP") -add_subdirectory("Alerts") -add_subdirectory("Alexa") -add_subdirectory("ApiGateway") -add_subdirectory("AudioPlayer") -add_subdirectory("Bluetooth") -add_subdirectory("DoNotDisturb") -add_subdirectory("Equalizer") -add_subdirectory("ExternalMediaPlayer") -add_subdirectory("InteractionModel") -add_subdirectory("MRM") -add_subdirectory("Notifications") -add_subdirectory("PlaybackController") -add_subdirectory("SpeakerManager") -add_subdirectory("SpeechSynthesizer") -add_subdirectory("System") -add_subdirectory("TemplateRuntime") +set(CAPABILITY_AGENTS + "AIP" + "Alerts" + "Alexa" + "ApiGateway" + "AudioPlayer" + "Bluetooth" + "DoNotDisturb" + "Equalizer" + "ExternalMediaPlayer" + "InteractionModel" + "MRM" + "Notifications" + "PlaybackController" + "SpeakerManager" + "SpeechSynthesizer" + "System" + "TemplateRuntime") if (COMMS) - add_subdirectory("CallManager") + list(APPEND CAPABILITY_AGENTS "CallManager") endif() if (PCC) - add_subdirectory("PhoneCallController") + list(APPEND CAPABILITY_AGENTS "PhoneCallController") endif() if (MCC) - add_subdirectory("MeetingClientController") + list(APPEND CAPABILITY_AGENTS "MeetingClientController") endif() if (ENDPOINT_CONTROLLERS_POWER_CONTROLLER) - add_subdirectory("PowerController") + list(APPEND CAPABILITY_AGENTS "PowerController") endif() if (ENDPOINT_CONTROLLERS_TOGGLE_CONTROLLER) - add_subdirectory("ToggleController") + list(APPEND CAPABILITY_AGENTS "ToggleController") endif() if (ENDPOINT_CONTROLLERS_RANGE_CONTROLLER) - add_subdirectory("RangeController") + list(APPEND CAPABILITY_AGENTS "RangeController") endif() if (ENDPOINT_CONTROLLERS_MODE_CONTROLLER) - add_subdirectory("ModeController") + list(APPEND CAPABILITY_AGENTS "ModeController") endif() +foreach (CA IN LISTS CAPABILITY_AGENTS) + if (IS_DIRECTORY "${CapabilityAgents_SOURCE_DIR}/${CA}" AND EXISTS "${CapabilityAgents_SOURCE_DIR}/${CA}/CMakeLists.txt") + message("Adding CapabilityAgent ${CA}") + add_subdirectory(${CA}) + endif() +endforeach() diff --git a/CapabilityAgents/DoNotDisturb/include/DoNotDisturbCA/DNDMessageRequest.h b/CapabilityAgents/DoNotDisturb/include/DoNotDisturbCA/DNDMessageRequest.h index cfb84e2a..893099f8 100644 --- a/CapabilityAgents/DoNotDisturb/include/DoNotDisturbCA/DNDMessageRequest.h +++ b/CapabilityAgents/DoNotDisturb/include/DoNotDisturbCA/DNDMessageRequest.h @@ -1,5 +1,5 @@ /* - * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * 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. diff --git a/CapabilityAgents/DoNotDisturb/src/DNDMessageRequest.cpp b/CapabilityAgents/DoNotDisturb/src/DNDMessageRequest.cpp index f4c938f3..af7e1436 100644 --- a/CapabilityAgents/DoNotDisturb/src/DNDMessageRequest.cpp +++ b/CapabilityAgents/DoNotDisturb/src/DNDMessageRequest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * 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. diff --git a/CapabilityAgents/DoNotDisturb/test/DoNotDisturbCapabilityAgentTest.cpp b/CapabilityAgents/DoNotDisturb/test/DoNotDisturbCapabilityAgentTest.cpp index c55b2f29..22ac1cb5 100644 --- a/CapabilityAgents/DoNotDisturb/test/DoNotDisturbCapabilityAgentTest.cpp +++ b/CapabilityAgents/DoNotDisturb/test/DoNotDisturbCapabilityAgentTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018-2020 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. @@ -41,7 +41,7 @@ using namespace settings::storage::test; using namespace settings; /// Amount of time for the test to wait for event to be sent. -static const std::chrono::seconds WAIT_TIMEOUT(2); +static const std::chrono::seconds MY_WAIT_TIMEOUT(2); /// A sample Directive JSON string for the purposes of creating an AVSDirective object. static const std::string SETDNDMODE_DIRECTIVE_VALID_JSON_STRING = R"delim( @@ -130,7 +130,7 @@ bool DoNotDisturbCapabilityAgentTest::expectEventSend( triggerOperation(); auto future = eventPromise.get_future(); - bool isFutureReady = future.wait_for(WAIT_TIMEOUT) == std::future_status::ready; + bool isFutureReady = future.wait_for(MY_WAIT_TIMEOUT) == std::future_status::ready; EXPECT_TRUE(isFutureReady); if (!isFutureReady) { @@ -246,7 +246,7 @@ TEST_F(DoNotDisturbCapabilityAgentTest, test_whileSendingChangedEvent_sendChange m_dndCA->sendChangedEvent("true"); auto future = eventPromise.get_future(); - bool isFutureReady = future.wait_for(WAIT_TIMEOUT) == std::future_status::ready; + bool isFutureReady = future.wait_for(MY_WAIT_TIMEOUT) == std::future_status::ready; EXPECT_TRUE(isFutureReady); // Check the order and existence of the events: Changed first, Report second, both happened. EXPECT_EQ(eventMask, 2); diff --git a/CapabilityAgents/Equalizer/CMakeLists.txt b/CapabilityAgents/Equalizer/CMakeLists.txt index 1fc0164c..e9948316 100644 --- a/CapabilityAgents/Equalizer/CMakeLists.txt +++ b/CapabilityAgents/Equalizer/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.0) project(Equalizer LANGUAGES CXX) -include(../../build/BuildDefaults.cmake) +include(${AVS_CORE}/build/BuildDefaults.cmake) add_subdirectory("src") add_subdirectory("test") diff --git a/CapabilityAgents/Equalizer/include/Equalizer/EqualizerCapabilityAgent.h b/CapabilityAgents/Equalizer/include/Equalizer/EqualizerCapabilityAgent.h index 7ba01dcc..cae03e99 100644 --- a/CapabilityAgents/Equalizer/include/Equalizer/EqualizerCapabilityAgent.h +++ b/CapabilityAgents/Equalizer/include/Equalizer/EqualizerCapabilityAgent.h @@ -1,5 +1,5 @@ /* - * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018-2020 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. @@ -109,6 +109,11 @@ public: void onEqualizerStateChanged(const avsCommon::sdkInterfaces::audio::EqualizerState& state) override; /// @} + /// @name EqualizerControllerListenerInterface Functions + /// @{ + void onEqualizerSameStateChanged(const avsCommon::sdkInterfaces::audio::EqualizerState& state) override; + /// @} + private: /** * Constructor. diff --git a/CapabilityAgents/Equalizer/src/EqualizerCapabilityAgent.cpp b/CapabilityAgents/Equalizer/src/EqualizerCapabilityAgent.cpp index 24632ad3..2f4d53d8 100644 --- a/CapabilityAgents/Equalizer/src/EqualizerCapabilityAgent.cpp +++ b/CapabilityAgents/Equalizer/src/EqualizerCapabilityAgent.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018-2020 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. @@ -419,6 +419,10 @@ void EqualizerCapabilityAgent::onEqualizerStateChanged(const EqualizerState& sta m_messageSender->sendMessage(request); } +void EqualizerCapabilityAgent::onEqualizerSameStateChanged(const EqualizerState& state) { + onEqualizerStateChanged(state); +} + bool EqualizerCapabilityAgent::handleSetBandsDirective( std::shared_ptr info, Document& payload) { diff --git a/CapabilityAgents/ExternalMediaPlayer/CMakeLists.txt b/CapabilityAgents/ExternalMediaPlayer/CMakeLists.txt index e4a4e919..9395e7ff 100644 --- a/CapabilityAgents/ExternalMediaPlayer/CMakeLists.txt +++ b/CapabilityAgents/ExternalMediaPlayer/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.1 FATAL_ERROR) project(ExternalMediaPlayer LANGUAGES CXX) -include(../../build/BuildDefaults.cmake) +include(${AVS_CORE}/build/BuildDefaults.cmake) add_subdirectory("src") add_subdirectory("test") diff --git a/CapabilityAgents/ExternalMediaPlayer/src/ExternalMediaPlayer.cpp b/CapabilityAgents/ExternalMediaPlayer/src/ExternalMediaPlayer.cpp index cf77ebc2..71e22092 100644 --- a/CapabilityAgents/ExternalMediaPlayer/src/ExternalMediaPlayer.cpp +++ b/CapabilityAgents/ExternalMediaPlayer/src/ExternalMediaPlayer.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -225,16 +225,16 @@ static std::unordered_map> g * Generate a @c CapabilityConfiguration object. * * @param type The Capability interface type. - * @param interface The Capability interface name. + * @param interfaceName The Capability interface name. * @param version The Capability interface verison. */ static std::shared_ptr generateCapabilityConfiguration( const std::string& type, - const std::string& interface, + const std::string& interfaceName, const std::string& version) { std::unordered_map configMap; configMap.insert({CAPABILITY_INTERFACE_TYPE_KEY, type}); - configMap.insert({CAPABILITY_INTERFACE_NAME_KEY, interface}); + configMap.insert({CAPABILITY_INTERFACE_NAME_KEY, interfaceName}); configMap.insert({CAPABILITY_INTERFACE_VERSION_KEY, version}); return std::make_shared(configMap); diff --git a/CapabilityAgents/ExternalMediaPlayer/test/ExternalMediaPlayerTest.cpp b/CapabilityAgents/ExternalMediaPlayer/test/ExternalMediaPlayerTest.cpp index 5096569e..b8ad3fe1 100644 --- a/CapabilityAgents/ExternalMediaPlayer/test/ExternalMediaPlayerTest.cpp +++ b/CapabilityAgents/ExternalMediaPlayer/test/ExternalMediaPlayerTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018-2020 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. @@ -70,7 +70,7 @@ using namespace rapidjson; static const unsigned int PROVIDE_STATE_TOKEN_TEST{1}; /// Plenty of time for a test to complete. -static std::chrono::milliseconds WAIT_TIMEOUT(1000); +static std::chrono::milliseconds MY_WAIT_TIMEOUT(1000); // The namespaces used in the context. static const std::string EXTERNALMEDIAPLAYER_STATE_NAMESPACE = "ExternalMediaPlayer"; @@ -645,7 +645,6 @@ void ExternalMediaPlayerTest::SetUp() { m_mockContextManager, m_mockExceptionSender, m_mockPlaybackRouter); - m_mockDirectiveHandlerResult = std::unique_ptr(new MockDirectiveHandlerResult); ASSERT_TRUE(m_externalMediaPlayer); } @@ -854,7 +853,7 @@ TEST_F(ExternalMediaPlayerTest, test_callingProvideSessionState) { EXPECT_CALL(*(MockExternalMediaPlayerAdapter::m_currentActiveMediaPlayerAdapter), getState()); m_externalMediaPlayer->provideState(SESSION_STATE, PROVIDE_STATE_TOKEN_TEST); - ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(WAIT_TIMEOUT)); + ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(MY_WAIT_TIMEOUT)); } /** @@ -877,7 +876,7 @@ TEST_F(ExternalMediaPlayerTest, test_callingProvidePlaybackState) { EXPECT_CALL(*(MockExternalMediaPlayerAdapter::m_currentActiveMediaPlayerAdapter), getState()); m_externalMediaPlayer->provideState(PLAYBACK_STATE, PROVIDE_STATE_TOKEN_TEST); - ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(WAIT_TIMEOUT)); + ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(MY_WAIT_TIMEOUT)); } /** @@ -1040,7 +1039,7 @@ TEST_F(ExternalMediaPlayerTest, test_loginStateChangeObserverIsNotified) { EXPECT_CALL(*(observer), onLoginStateProvided(PLAYER_ID, observableSessionProperties)).Times(1); m_externalMediaPlayer->provideState(SESSION_STATE, PROVIDE_STATE_TOKEN_TEST); - ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(WAIT_TIMEOUT)); + ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(MY_WAIT_TIMEOUT)); } /** @@ -1064,7 +1063,7 @@ TEST_F(ExternalMediaPlayerTest, test_playbackStateChangeObserverIsNotified) { EXPECT_CALL(*(observer), onPlaybackStateProvided(PLAYER_ID, observablePlaybackStateProperties)).Times(1); m_externalMediaPlayer->provideState(PLAYBACK_STATE, PROVIDE_STATE_TOKEN_TEST); - ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(WAIT_TIMEOUT)); + ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(MY_WAIT_TIMEOUT)); } /** @@ -1084,14 +1083,14 @@ TEST_F(ExternalMediaPlayerTest, test_loginStateChangeObserverRemoval) { .WillRepeatedly(Return(createAdapterState())); EXPECT_CALL(*(observer), onLoginStateProvided(_, _)).Times(1); m_externalMediaPlayer->provideState(SESSION_STATE, PROVIDE_STATE_TOKEN_TEST); - ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(WAIT_TIMEOUT)); + ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(MY_WAIT_TIMEOUT)); this->resetWakeOnSetState(); m_externalMediaPlayer->removeObserver(observer); EXPECT_CALL(*(observer), onLoginStateProvided(_, _)).Times(0); m_externalMediaPlayer->provideState(SESSION_STATE, PROVIDE_STATE_TOKEN_TEST); - ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(WAIT_TIMEOUT)); + ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(MY_WAIT_TIMEOUT)); } /** @@ -1112,14 +1111,14 @@ TEST_F(ExternalMediaPlayerTest, test_playbackStateChangeObserverRemoval) { .WillRepeatedly(Return(createAdapterState())); EXPECT_CALL(*(observer), onPlaybackStateProvided(_, _)).Times(1); m_externalMediaPlayer->provideState(PLAYBACK_STATE, PROVIDE_STATE_TOKEN_TEST); - ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(WAIT_TIMEOUT)); + ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(MY_WAIT_TIMEOUT)); this->resetWakeOnSetState(); m_externalMediaPlayer->removeObserver(observer); EXPECT_CALL(*(observer), onPlaybackStateProvided(_, _)).Times(0); m_externalMediaPlayer->provideState(PLAYBACK_STATE, PROVIDE_STATE_TOKEN_TEST); - ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(WAIT_TIMEOUT)); + ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(MY_WAIT_TIMEOUT)); } /** diff --git a/CapabilityAgents/InteractionModel/CMakeLists.txt b/CapabilityAgents/InteractionModel/CMakeLists.txt index 74dfc1d1..f66b0df3 100644 --- a/CapabilityAgents/InteractionModel/CMakeLists.txt +++ b/CapabilityAgents/InteractionModel/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.0 FATAL_ERROR) project(InteractionModel LANGUAGES CXX) -include(../../build/BuildDefaults.cmake) +include(${AVS_CORE}/build/BuildDefaults.cmake) add_subdirectory("src") add_subdirectory("test") \ No newline at end of file diff --git a/CapabilityAgents/MRM/CMakeLists.txt b/CapabilityAgents/MRM/CMakeLists.txt index e1c2e92c..92f5a2ed 100644 --- a/CapabilityAgents/MRM/CMakeLists.txt +++ b/CapabilityAgents/MRM/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.0 FATAL_ERROR) project(MRM LANGUAGES CXX) -include(../../build/BuildDefaults.cmake) +include(${AVS_CORE}/build/BuildDefaults.cmake) add_subdirectory("src") add_subdirectory("test") @@ -11,4 +11,4 @@ if(MRM AND MRM_STANDALONE_APP) add_subdirectory("MRMApp") elseif (MRM) add_subdirectory("MRMHandler") -endif() \ No newline at end of file +endif() diff --git a/CapabilityAgents/MRM/include/MRM/MRMCapabilityAgent.h b/CapabilityAgents/MRM/include/MRM/MRMCapabilityAgent.h index 8775551f..88998c47 100644 --- a/CapabilityAgents/MRM/include/MRM/MRMCapabilityAgent.h +++ b/CapabilityAgents/MRM/include/MRM/MRMCapabilityAgent.h @@ -76,7 +76,7 @@ public: /** * Destructor. */ - ~MRMCapabilityAgent(); + ~MRMCapabilityAgent() override; /// @name Overridden CapabilityAgent methods. /// @{ @@ -205,6 +205,8 @@ private: std::shared_ptr m_speakerManager; /// The User Inactivity Monitor. std::shared_ptr m_userInactivityMonitor; + /// Boolean to store whether or not the last processed CallState was "active" or not. + bool m_wasPreviouslyActive; /// The @c Executor which queues up operations from asynchronous API calls. avsCommon::utils::threading::Executor m_executor; }; diff --git a/CapabilityAgents/MRM/include/MRM/MRMHandlerInterface.h b/CapabilityAgents/MRM/include/MRM/MRMHandlerInterface.h index 1c11e27c..a15c579b 100644 --- a/CapabilityAgents/MRM/include/MRM/MRMHandlerInterface.h +++ b/CapabilityAgents/MRM/include/MRM/MRMHandlerInterface.h @@ -21,8 +21,8 @@ #include #include -#include #include +#include #include namespace alexaClientSDK { @@ -41,12 +41,12 @@ public: /** * Constructor. */ - MRMHandlerInterface(const std::string& shutdownName); + explicit MRMHandlerInterface(const std::string& shutdownName); /** * Destructor. */ - virtual ~MRMHandlerInterface() = default; + virtual ~MRMHandlerInterface() override = default; /** * Returns the string representation of the version of this MRM implementation. diff --git a/CapabilityAgents/MRM/src/CMakeLists.txt b/CapabilityAgents/MRM/src/CMakeLists.txt index 9f8fd456..4b026e29 100644 --- a/CapabilityAgents/MRM/src/CMakeLists.txt +++ b/CapabilityAgents/MRM/src/CMakeLists.txt @@ -15,4 +15,4 @@ elseif (MRM) endif() # install target -asdk_install() \ No newline at end of file +asdk_install() diff --git a/CapabilityAgents/MRM/src/MRMCapabilityAgent.cpp b/CapabilityAgents/MRM/src/MRMCapabilityAgent.cpp index 03b97001..4e48420d 100644 --- a/CapabilityAgents/MRM/src/MRMCapabilityAgent.cpp +++ b/CapabilityAgents/MRM/src/MRMCapabilityAgent.cpp @@ -15,6 +15,9 @@ #include "MRM/MRMCapabilityAgent.h" +#include + +#include #include /// String to identify log entries originating from this file. @@ -34,23 +37,106 @@ namespace mrm { using namespace avsCommon::avs; using namespace avsCommon::sdkInterfaces; using namespace avsCommon::utils; +using namespace avsCommon::utils::json; /// The namespace for this capability agent. -static const std::string NAMESPACE_STR = "MRM"; +static const std::string CAPABILITY_AGENT_NAMESPACE_STR = "MRM"; + +/// Directive namespaces that this capability agent accepts +static const std::string DIRECTIVE_NAMESPACE_STR = "WholeHomeAudio"; +/// Directives under this namespace are for controlling output device skews(bluetooth) +static const std::string SKEW_DIRECTIVE_NAMESPACE_STR = "WholeHomeAudio.Skew"; /// The wildcard namespace signature so the DirectiveSequencer will send us all /// Directives under the namespace. -static const avsCommon::avs::NamespaceAndName WHA_NAMESPACE_WILDCARD{NAMESPACE_STR, "*"}; +static const avsCommon::avs::NamespaceAndName WHA_NAMESPACE_WILDCARD{DIRECTIVE_NAMESPACE_STR, "*"}; +static const avsCommon::avs::NamespaceAndName WHA_SKEW_NAMESPACE_WILDCARD{SKEW_DIRECTIVE_NAMESPACE_STR, "*"}; -/** - * Creates the MRM capability configuration, needed to register with Device - * Capability Framework. - * - * @return The MRM capability configuration. - */ -static std::shared_ptr getMRMCapabilityConfiguration() { - static const std::unordered_map configMap; - return std::make_shared(configMap); +/// The key in our config file to find the root of MRM for this database. +static const std::string MRM_CONFIGURATION_ROOT_KEY = "mrm"; +/// The key in our config file to find the MRM capabilities. +static const std::string MRM_CAPABILITIES_KEY = "capabilities"; + +static std::unordered_set> readCapabilities() { + std::unordered_set> capabilitiesSet; + auto configRoot = configuration::ConfigurationNode::getRoot(); + if (!configRoot) { + ACSDK_ERROR(LX("initializeFailed").d("reason", "configurationRootNotFound")); + return capabilitiesSet; + } + + auto mrmConfig = configRoot[MRM_CONFIGURATION_ROOT_KEY]; + if (!mrmConfig) { + ACSDK_ERROR(LX("initializeFailed") + .d("reason", "configurationKeyNotFound") + .d("configurationKey", MRM_CONFIGURATION_ROOT_KEY)); + return capabilitiesSet; + } + + auto capabilitiesConfig = mrmConfig[MRM_CAPABILITIES_KEY]; + if (!capabilitiesConfig) { + ACSDK_ERROR(LX("initializeFailed").d("reason", "capabilitiesKeyNotFound").d("key", MRM_CAPABILITIES_KEY)); + return capabilitiesSet; + } + + std::string capabilitiesString = capabilitiesConfig.serialize(); + rapidjson::Document capabilities; + if (!jsonUtils::parseJSON(capabilitiesString, &capabilities)) { + ACSDK_ERROR(LX("initializeFailed") + .d("reason", "failedToParseCapabilitiesString") + .d("capabilitiesString", capabilitiesString)); + return capabilitiesSet; + } + for (auto itr = capabilities.MemberBegin(); itr != capabilities.MemberEnd(); ++itr) { + std::string interfaceType; + if (!jsonUtils::retrieveValue(itr->value, CAPABILITY_INTERFACE_TYPE_KEY, &interfaceType)) { + ACSDK_ERROR(LX("initializeFailed") + .d("reason", "failedToFindCapabilityInterfaceTypeKey") + .d("key", CAPABILITY_INTERFACE_TYPE_KEY)); + return capabilitiesSet; + } + + std::string interfaceName; + if (!jsonUtils::retrieveValue(itr->value, CAPABILITY_INTERFACE_NAME_KEY, &interfaceName)) { + ACSDK_ERROR(LX("initializeFailed") + .d("reason", "failedToFindCapabilityInterfaceNameKey") + .d("key", CAPABILITY_INTERFACE_NAME_KEY)); + return capabilitiesSet; + } + + std::string interfaceVersion; + if (!jsonUtils::retrieveValue(itr->value, CAPABILITY_INTERFACE_VERSION_KEY, &interfaceVersion)) { + ACSDK_ERROR(LX("initializeFailed") + .d("reason", "failedToFindCapabilityInterfaceVersionKey") + .d("key", CAPABILITY_INTERFACE_VERSION_KEY)); + return capabilitiesSet; + } + + // Configurations is an optional field. + std::string configurationsString; + rapidjson::Value::ConstMemberIterator configurations; + if (jsonUtils::findNode(itr->value, CAPABILITY_INTERFACE_CONFIGURATIONS_KEY, &configurations)) { + if (!jsonUtils::convertToValue(configurations->value, &configurationsString)) { + ACSDK_ERROR(LX("initializeFailed").d("reason", "failedToConvertConfigurations")); + return capabilitiesSet; + } + } + + std::unordered_map capabilityMap = { + {CAPABILITY_INTERFACE_TYPE_KEY, interfaceType}, + {CAPABILITY_INTERFACE_NAME_KEY, interfaceName}, + {CAPABILITY_INTERFACE_VERSION_KEY, interfaceVersion}}; + if (!configurationsString.empty()) { + capabilityMap[CAPABILITY_INTERFACE_CONFIGURATIONS_KEY] = configurationsString; + } + capabilitiesSet.insert(std::make_shared(capabilityMap)); + } + + if (capabilitiesSet.empty()) { + ACSDK_ERROR(LX("initializeFailed").d("reason", "missingCapabilityConfigurations")); + } + + return capabilitiesSet; } std::shared_ptr MRMCapabilityAgent::create( @@ -91,11 +177,12 @@ MRMCapabilityAgent::MRMCapabilityAgent( std::shared_ptr speakerManager, std::shared_ptr userInactivityMonitor, std::shared_ptr exceptionEncounteredSender) : - CapabilityAgent(NAMESPACE_STR, exceptionEncounteredSender), + CapabilityAgent(CAPABILITY_AGENT_NAMESPACE_STR, exceptionEncounteredSender), RequiresShutdown("MRMCapabilityAgent"), m_mrmHandler{handler}, m_speakerManager{speakerManager}, - m_userInactivityMonitor{userInactivityMonitor} { + m_userInactivityMonitor{userInactivityMonitor}, + m_wasPreviouslyActive{false} { ACSDK_DEBUG5(LX(__func__)); }; @@ -135,8 +222,8 @@ void MRMCapabilityAgent::handleDirectiveImmediately(std::shared_ptrgetVersionString(); } +void MRMCapabilityAgent::setObserver( + std::shared_ptr observer) { + if (!observer) { + ACSDK_ERROR(LX("setObserverFailed").m("Observer is null.")); + return; + } + + ACSDK_DEBUG5(LX(__func__)); + m_executor.submit([this, observer]() { executeSetObserver(observer); }); +} + void MRMCapabilityAgent::executeHandleDirectiveImmediately(std::shared_ptr info) { ACSDK_DEBUG5(LX(__func__)); @@ -199,33 +297,28 @@ void MRMCapabilityAgent::executeOnUserInactivityReportSent() { m_mrmHandler->onUserInactivityReportSent(); } +void MRMCapabilityAgent::executeOnCallStateChange( + const avsCommon::sdkInterfaces::CallStateObserverInterface::CallState callState) { + ACSDK_DEBUG5(LX(__func__)); + bool isCurrentlyActive = CallStateObserverInterface::isStateActive(callState); + + // Only send down the CallStateChange event if the active call state changed + if (m_wasPreviouslyActive != isCurrentlyActive) { + m_mrmHandler->onCallStateChange(isCurrentlyActive); + m_wasPreviouslyActive = isCurrentlyActive; + } else { + ACSDK_WARN(LX(__func__).m("call active state didn't actually change")); + } +} + void MRMCapabilityAgent::executeSetObserver( std::shared_ptr observer) { ACSDK_DEBUG5(LX(__func__)); m_mrmHandler->setObserver(observer); } -void MRMCapabilityAgent::executeOnCallStateChange( - const avsCommon::sdkInterfaces::CallStateObserverInterface::CallState callState) { - ACSDK_DEBUG5(LX(__func__)); - bool active = - (CallStateObserverInterface::CallState::CONNECTING == callState || - CallStateObserverInterface::CallState::INBOUND_RINGING == callState || - CallStateObserverInterface::CallState::CALL_CONNECTED == callState); - - m_mrmHandler->onCallStateChange(active); -} - std::unordered_set> MRMCapabilityAgent::getCapabilityConfigurations() { - std::unordered_set> configs; - configs.insert(getMRMCapabilityConfiguration()); - return configs; -} - -void MRMCapabilityAgent::setObserver( - std::shared_ptr observer) { - ACSDK_DEBUG5(LX(__func__)); - m_executor.submit([this, observer]() { executeSetObserver(observer); }); + return readCapabilities(); } void MRMCapabilityAgent::doShutdown() { diff --git a/CapabilityAgents/Notifications/CMakeLists.txt b/CapabilityAgents/Notifications/CMakeLists.txt index 9ba454e3..f92c64fa 100644 --- a/CapabilityAgents/Notifications/CMakeLists.txt +++ b/CapabilityAgents/Notifications/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.1 FATAL_ERROR) project(Notifications LANGUAGES CXX) -include(../../build/BuildDefaults.cmake) +include(${AVS_CORE}/build/BuildDefaults.cmake) add_subdirectory("src") add_subdirectory("test") diff --git a/CapabilityAgents/Notifications/include/Notifications/NotificationRenderer.h b/CapabilityAgents/Notifications/include/Notifications/NotificationRenderer.h index 5b16cb89..87b30194 100644 --- a/CapabilityAgents/Notifications/include/Notifications/NotificationRenderer.h +++ b/CapabilityAgents/Notifications/include/Notifications/NotificationRenderer.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -62,11 +62,15 @@ public: /// @name MediaPlayerObserverInterface methods /// @{ - void onPlaybackStarted(SourceId sourceId) override; - void onPlaybackStopped(SourceId sourceId) override; - void onPlaybackFinished(SourceId sourceId) override; - void onPlaybackError(SourceId sourceId, const avsCommon::utils::mediaPlayer::ErrorType& type, std::string error) - override; + void onFirstByteRead(SourceId sourceId, const avsCommon::utils::mediaPlayer::MediaPlayerState& state) override; + void onPlaybackStarted(SourceId sourceId, const avsCommon::utils::mediaPlayer::MediaPlayerState& state) override; + void onPlaybackStopped(SourceId sourceId, const avsCommon::utils::mediaPlayer::MediaPlayerState& state) override; + void onPlaybackFinished(SourceId sourceId, const avsCommon::utils::mediaPlayer::MediaPlayerState& state) override; + void onPlaybackError( + SourceId sourceId, + const avsCommon::utils::mediaPlayer::ErrorType& type, + std::string error, + const avsCommon::utils::mediaPlayer::MediaPlayerState& state) override; /// @} /// @name RequiresShutdown methods diff --git a/CapabilityAgents/Notifications/include/Notifications/NotificationsCapabilityAgent.h b/CapabilityAgents/Notifications/include/Notifications/NotificationsCapabilityAgent.h index 8209d880..48b7543b 100644 --- a/CapabilityAgents/Notifications/include/Notifications/NotificationsCapabilityAgent.h +++ b/CapabilityAgents/Notifications/include/Notifications/NotificationsCapabilityAgent.h @@ -1,5 +1,5 @@ /* - * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018-2020 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. @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -42,7 +43,7 @@ namespace notifications { /** * This class implements the @c Notifications capability agent. * - * @see https://developer.amazon.com/docs/alexa-voice-service/notifications.html + * @see https://developer.amazon.com/docs/alexa/alexa-voice-service/notifications.html * * @note For instances of this class to be cleaned up correctly, @c shutdown() must be called. * @note This class makes use of a global configuration to a database file, meaning that it is best used @@ -67,6 +68,7 @@ public: * @param notificationsAudioFactory The audio factory object to produce the default notification sound. * @param observers The set of observers that will be notified of IndicatorState changes. * @param dataManager A dataManager object that will track the CustomerDataHandler. + * @param metricRecorder The metric recorder. * @return A @c std::shared_ptr to the new @c NotificationsCapabilityAgent instance. */ static std::shared_ptr create( @@ -75,7 +77,8 @@ public: std::shared_ptr contextManager, std::shared_ptr exceptionSender, std::shared_ptr notificationsAudioFactory, - std::shared_ptr dataManager); + std::shared_ptr dataManager, + std::shared_ptr metricRecorder = nullptr); /** * Adds a NotificationsObserver to the set of observers. This observer will be notified when a SetIndicator @@ -137,6 +140,7 @@ private: * @param notificationsAudioFactory The audio factory object to produce the default notification sound. * @param observers The set of observers that will be notified of IndicatorState changes. * @param dataManager A dataManager object that will track the CustomerDataHandler. + * @param metricRecorder The metric recorder. */ NotificationsCapabilityAgent( std::shared_ptr notificationsStorage, @@ -144,7 +148,8 @@ private: std::shared_ptr contextManager, std::shared_ptr exceptionSender, std::shared_ptr notificationsAudioFactory, - std::shared_ptr dataManager); + std::shared_ptr dataManager, + std::shared_ptr metricRecorder); /** * Utility to set some member variables and setup the database. @@ -309,6 +314,9 @@ private: /// @} /// @} + /// The metric recorder. + std::shared_ptr m_metricRecorder; + /// Stores notification indicators in the order they are received and the visual indicator state. std::shared_ptr m_notificationsStorage; diff --git a/CapabilityAgents/Notifications/src/NotificationRenderer.cpp b/CapabilityAgents/Notifications/src/NotificationRenderer.cpp index a8afc887..3d38622d 100644 --- a/CapabilityAgents/Notifications/src/NotificationRenderer.cpp +++ b/CapabilityAgents/Notifications/src/NotificationRenderer.cpp @@ -159,7 +159,11 @@ bool NotificationRenderer::cancelNotificationRendering() { return true; } -void NotificationRenderer::onPlaybackStarted(SourceId sourceId) { +void NotificationRenderer::onFirstByteRead(SourceId sourceId, const MediaPlayerState&) { + ACSDK_DEBUG5(LX(__func__).d("sourceId", sourceId)); +} + +void NotificationRenderer::onPlaybackStarted(SourceId sourceId, const MediaPlayerState&) { ACSDK_DEBUG5(LX("onPlaybackStarted").d("sourceId", sourceId)); if (sourceId != m_sourceId) { ACSDK_ERROR(LX("onPlaybackStartedFailed").d("reason", "unexpectedSourceId").d("expected", m_sourceId)); @@ -170,7 +174,7 @@ void NotificationRenderer::onPlaybackStarted(SourceId sourceId) { } } -void NotificationRenderer::onPlaybackStopped(SourceId sourceId) { +void NotificationRenderer::onPlaybackStopped(SourceId sourceId, const MediaPlayerState&) { ACSDK_DEBUG5(LX("onPlaybackStopped").d("sourceId", sourceId)); if (sourceId != m_sourceId) { ACSDK_ERROR(LX("onPlaybackStoppedFailed").d("reason", "unexpectedSourceId").d("expected", m_sourceId)); @@ -179,7 +183,7 @@ void NotificationRenderer::onPlaybackStopped(SourceId sourceId) { onRenderingFinished(sourceId); } -void NotificationRenderer::onPlaybackFinished(SourceId sourceId) { +void NotificationRenderer::onPlaybackFinished(SourceId sourceId, const MediaPlayerState&) { ACSDK_DEBUG5(LX("onPlaybackFinished").d("sourceId", sourceId)); if (sourceId != m_sourceId) { ACSDK_ERROR(LX("onPlaybackFinishedFailed").d("reason", "unexpectedSourceId").d("expected", m_sourceId)); @@ -188,7 +192,11 @@ void NotificationRenderer::onPlaybackFinished(SourceId sourceId) { onRenderingFinished(sourceId); } -void NotificationRenderer::onPlaybackError(SourceId sourceId, const ErrorType& type, std::string error) { +void NotificationRenderer::onPlaybackError( + SourceId sourceId, + const ErrorType& type, + std::string error, + const MediaPlayerState&) { ACSDK_DEBUG5(LX("onPlaybackError").d("sourceId", sourceId).d("type", type).d("error", error)); if (sourceId != m_sourceId) { diff --git a/CapabilityAgents/Notifications/src/NotificationsCapabilityAgent.cpp b/CapabilityAgents/Notifications/src/NotificationsCapabilityAgent.cpp index ba9798ed..63997751 100644 --- a/CapabilityAgents/Notifications/src/NotificationsCapabilityAgent.cpp +++ b/CapabilityAgents/Notifications/src/NotificationsCapabilityAgent.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018-2020 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. @@ -21,6 +21,8 @@ #include #include +#include +#include #include namespace alexaClientSDK { @@ -32,6 +34,7 @@ using namespace avsCommon::sdkInterfaces; using namespace avsCommon::utils::configuration; using namespace avsCommon::utils::json; using namespace avsCommon::utils::logger; +using namespace avsCommon::utils::metrics; /// Notifications capability constants /// Notifications interface type @@ -54,6 +57,9 @@ static const std::string TAG("NotificationsCapabilityAgent"); /// The namespace for this capability agent. static const std::string NAMESPACE = "Notifications"; +/// Metric Activity Name Prefix for NOTIFICATION metric source +static const std::string NOTIFICATION_METRIC_SOURCE_PREFIX = "NOTIFICATION-"; + /// The @c NotificationsCapabilityAgent context state signature. static const NamespaceAndName INDICATOR_STATE_CONTEXT_KEY{NAMESPACE, "IndicatorState"}; @@ -85,13 +91,36 @@ static const std::chrono::milliseconds SHUTDOWN_TIMEOUT{500}; */ static std::shared_ptr getNotificationsCapabilityConfiguration(); +/** + * Submits a metric count of 1 by name + * @param metricRecorder The @c MetricRecorderInterface which records Metric events + * @param eventName The name of the metric event + */ +static void submitMetric(const std::shared_ptr& metricRecorder, const std::string& eventName) { + if (!metricRecorder) { + return; + } + + auto metricEvent = MetricEventBuilder{} + .setActivityName(NOTIFICATION_METRIC_SOURCE_PREFIX + eventName) + .addDataPoint(DataPointCounterBuilder{}.setName(eventName).increment(1).build()) + .build(); + + if (metricEvent == nullptr) { + ACSDK_ERROR(LX("Error creating metric.")); + return; + } + recordMetric(metricRecorder, metricEvent); +} + std::shared_ptr NotificationsCapabilityAgent::create( std::shared_ptr notificationsStorage, std::shared_ptr renderer, std::shared_ptr contextManager, std::shared_ptr exceptionSender, std::shared_ptr notificationsAudioFactory, - std::shared_ptr dataManager) { + std::shared_ptr dataManager, + std::shared_ptr metricRecorder) { if (nullptr == notificationsStorage) { ACSDK_ERROR(LX("createFailed").d("reason", "nullNotificationsStorage")); return nullptr; @@ -118,7 +147,13 @@ std::shared_ptr NotificationsCapabilityAgent::crea } auto notificationsCapabilityAgent = std::shared_ptr(new NotificationsCapabilityAgent( - notificationsStorage, renderer, contextManager, exceptionSender, notificationsAudioFactory, dataManager)); + notificationsStorage, + renderer, + contextManager, + exceptionSender, + notificationsAudioFactory, + dataManager, + metricRecorder)); if (!notificationsCapabilityAgent->init()) { ACSDK_ERROR(LX("createFailed").d("reason", "initFailed")); @@ -133,14 +168,16 @@ NotificationsCapabilityAgent::NotificationsCapabilityAgent( std::shared_ptr contextManager, std::shared_ptr exceptionSender, std::shared_ptr notificationsAudioFactory, - std::shared_ptr dataManager) : - CapabilityAgent{NAMESPACE, exceptionSender}, + std::shared_ptr dataManager, + std::shared_ptr metricRecorder) : + CapabilityAgent{NAMESPACE, std::move(exceptionSender)}, RequiresShutdown{"NotificationsCapabilityAgent"}, - CustomerDataHandler{dataManager}, - m_notificationsStorage{notificationsStorage}, - m_contextManager{contextManager}, - m_renderer{renderer}, - m_notificationsAudioFactory{notificationsAudioFactory}, + CustomerDataHandler{std::move(dataManager)}, + m_metricRecorder{std::move(metricRecorder)}, + m_notificationsStorage{std::move(notificationsStorage)}, + m_contextManager{std::move(contextManager)}, + m_renderer{std::move(renderer)}, + m_notificationsAudioFactory{std::move(notificationsAudioFactory)}, m_isEnabled{false}, m_currentState{NotificationsCapabilityAgentState::IDLE} { m_capabilityConfigurations.insert(getNotificationsCapabilityConfiguration()); @@ -270,6 +307,8 @@ void NotificationsCapabilityAgent::handleDirective(std::shared_ptrdirective->getName()); if (info->directive->getName() == SET_INDICATOR.name) { handleSetIndicatorDirective(info); } else if (info->directive->getName() == CLEAR_INDICATOR.name) { @@ -503,6 +542,7 @@ void NotificationsCapabilityAgent::executeProvideState(bool sendToken, unsigned if (!m_notificationsStorage->getIndicatorState(¤tIndicatorState)) { ACSDK_ERROR(LX("executeProvideState").d("reason", "getIndicatorStateFailed")); + submitMetric(m_metricRecorder, "getIndicatorStateFailed"); return; } diff --git a/CapabilityAgents/Notifications/test/NotificationsCapabilityAgentTest.cpp b/CapabilityAgents/Notifications/test/NotificationsCapabilityAgentTest.cpp index 7698caa2..2fecf6c6 100644 --- a/CapabilityAgents/Notifications/test/NotificationsCapabilityAgentTest.cpp +++ b/CapabilityAgents/Notifications/test/NotificationsCapabilityAgentTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018-2020 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. @@ -31,6 +31,7 @@ #include #include #include +#include #include #include "Notifications/NotificationsCapabilityAgent.h" @@ -52,7 +53,7 @@ using namespace avsCommon::sdkInterfaces::test; using namespace ::testing; /// Plenty of time for a test to complete. -static std::chrono::milliseconds WAIT_TIMEOUT(1000); +static std::chrono::milliseconds MY_WAIT_TIMEOUT(1000); /// Time to simulate a notification rendering. static std::chrono::milliseconds RENDER_TIME(10); @@ -223,7 +224,7 @@ public: * @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); + bool waitForQueueSizeToBe(size_t size, std::chrono::milliseconds timeout = MY_WAIT_TIMEOUT); private: /// The underlying NotificationIndicator queue. @@ -372,12 +373,12 @@ public: /** * Waits for the fulfillment of m_renderStartedPromise, then resets any needed variables. */ - bool waitUntilRenderingStarted(std::chrono::milliseconds timeout = WAIT_TIMEOUT); + bool waitUntilRenderingStarted(std::chrono::milliseconds timeout = MY_WAIT_TIMEOUT); /** * Waits for the fulfillment of m_renderFinishedPromise, then resets any needed variables. */ - bool waitUntilRenderingFinished(std::chrono::milliseconds timeout = WAIT_TIMEOUT); + bool waitUntilRenderingFinished(std::chrono::milliseconds timeout = MY_WAIT_TIMEOUT); private: /// The current renderer observer. @@ -483,14 +484,14 @@ bool MockNotificationRenderer::mockCancel() { bool MockNotificationRenderer::waitForRenderCall() { std::unique_lock lock(m_mutex); - m_renderTrigger.wait_for(lock, WAIT_TIMEOUT, [this]() { return m_startedRendering; }); + m_renderTrigger.wait_for(lock, MY_WAIT_TIMEOUT, [this]() { return m_startedRendering; }); m_renderStartedPromise.set_value(); return true; } bool MockNotificationRenderer::waitForRenderCallDone() { std::unique_lock lock(m_mutex); - m_renderTrigger.wait_for(lock, WAIT_TIMEOUT, [this]() { return m_cancelling || m_finishedRendering; }); + m_renderTrigger.wait_for(lock, MY_WAIT_TIMEOUT, [this]() { return m_cancelling || m_finishedRendering; }); m_renderFinishedPromise.set_value(); return true; } @@ -545,6 +546,9 @@ public: const std::string& assetId = ASSET_ID1, const std::string& assetUrl = ASSET_URL1); + /// The metric recorder. + std::shared_ptr m_metricRecorder; + /// @c A test observer to wait for @c AudioPlayer state changes std::shared_ptr m_testNotificationsObserver; @@ -589,7 +593,8 @@ void NotificationsCapabilityAgentTest::initializeCapabilityAgent() { m_mockContextManager, m_mockExceptionSender, m_testNotificationsAudioFactory, - m_dataManager); + m_dataManager, + m_metricRecorder); ASSERT_TRUE(m_notificationsCapabilityAgent); m_notificationsCapabilityAgent->addObserver(m_testNotificationsObserver); m_renderer->addObserver(m_notificationsCapabilityAgent); @@ -598,7 +603,7 @@ void NotificationsCapabilityAgentTest::initializeCapabilityAgent() { void NotificationsCapabilityAgentTest::SetUp() { auto inString = std::shared_ptr(new std::istringstream(NOTIFICATIONS_CONFIG_JSON)); ASSERT_TRUE(AlexaClientSDKInit::initialize({inString})); - + m_metricRecorder = std::make_shared>(); m_notificationsStorage = std::make_shared(); m_renderer = MockNotificationRenderer::create(); m_mockContextManager = std::make_shared>(); @@ -691,7 +696,8 @@ TEST_F(NotificationsCapabilityAgentTest, test_create) { m_mockContextManager, m_mockExceptionSender, m_testNotificationsAudioFactory, - m_dataManager); + m_dataManager, + m_metricRecorder); EXPECT_EQ(testNotificationsCapabilityAgent, nullptr); testNotificationsCapabilityAgent = NotificationsCapabilityAgent::create( @@ -700,7 +706,8 @@ TEST_F(NotificationsCapabilityAgentTest, test_create) { m_mockContextManager, m_mockExceptionSender, m_testNotificationsAudioFactory, - m_dataManager); + m_dataManager, + m_metricRecorder); EXPECT_EQ(testNotificationsCapabilityAgent, nullptr); testNotificationsCapabilityAgent = NotificationsCapabilityAgent::create( @@ -709,7 +716,8 @@ TEST_F(NotificationsCapabilityAgentTest, test_create) { nullptr, m_mockExceptionSender, m_testNotificationsAudioFactory, - m_dataManager); + m_dataManager, + m_metricRecorder); EXPECT_EQ(testNotificationsCapabilityAgent, nullptr); testNotificationsCapabilityAgent = NotificationsCapabilityAgent::create( @@ -718,11 +726,18 @@ TEST_F(NotificationsCapabilityAgentTest, test_create) { m_mockContextManager, nullptr, m_testNotificationsAudioFactory, - m_dataManager); + m_dataManager, + m_metricRecorder); EXPECT_EQ(testNotificationsCapabilityAgent, nullptr); testNotificationsCapabilityAgent = NotificationsCapabilityAgent::create( - m_notificationsStorage, m_renderer, m_mockContextManager, m_mockExceptionSender, nullptr, m_dataManager); + m_notificationsStorage, + m_renderer, + m_mockContextManager, + m_mockExceptionSender, + nullptr, + m_dataManager, + m_metricRecorder); EXPECT_EQ(testNotificationsCapabilityAgent, nullptr); } @@ -748,10 +763,10 @@ TEST_F(NotificationsCapabilityAgentTest, test_nonEmptyStartupQueue) { TEST_F(NotificationsCapabilityAgentTest, test_sendSetIndicator) { EXPECT_CALL(*(m_renderer.get()), renderNotificationShim(_, _)).Times(0); initializeCapabilityAgent(); - ASSERT_TRUE(m_testNotificationsObserver->waitFor(IndicatorState::OFF, WAIT_TIMEOUT)); + ASSERT_TRUE(m_testNotificationsObserver->waitFor(IndicatorState::OFF, MY_WAIT_TIMEOUT)); sendSetIndicatorDirective(generatePayload(true, false), MESSAGE_ID_TEST); - ASSERT_TRUE(m_testNotificationsObserver->waitFor(IndicatorState::ON, WAIT_TIMEOUT)); + ASSERT_TRUE(m_testNotificationsObserver->waitFor(IndicatorState::ON, MY_WAIT_TIMEOUT)); // check that the NotificationIndicator was dequeued as expected ASSERT_TRUE(m_notificationsStorage->waitForQueueSizeToBe(0)); @@ -764,17 +779,17 @@ TEST_F(NotificationsCapabilityAgentTest, test_sendSetIndicator) { */ TEST_F(NotificationsCapabilityAgentTest, test_sendSetIndicatorIncreasesCount) { initializeCapabilityAgent(); - ASSERT_TRUE(m_testNotificationsObserver->waitFor(0, WAIT_TIMEOUT)); + ASSERT_TRUE(m_testNotificationsObserver->waitFor(0, MY_WAIT_TIMEOUT)); sendSetIndicatorDirective(generatePayload(true, false), MESSAGE_ID_TEST); - ASSERT_TRUE(m_testNotificationsObserver->waitFor(1, WAIT_TIMEOUT)); + ASSERT_TRUE(m_testNotificationsObserver->waitFor(1, MY_WAIT_TIMEOUT)); // A duplicate indication should trigger an increase in indicator count sendSetIndicatorDirective(generatePayload(true, false), MESSAGE_ID_TEST); - ASSERT_TRUE(m_testNotificationsObserver->waitFor(2, WAIT_TIMEOUT)); + ASSERT_TRUE(m_testNotificationsObserver->waitFor(2, MY_WAIT_TIMEOUT)); sendSetIndicatorDirective(generatePayload(true, true), MESSAGE_ID_TEST); - ASSERT_TRUE(m_testNotificationsObserver->waitFor(3, WAIT_TIMEOUT)); + ASSERT_TRUE(m_testNotificationsObserver->waitFor(3, MY_WAIT_TIMEOUT)); } /** @@ -784,13 +799,13 @@ TEST_F(NotificationsCapabilityAgentTest, test_persistVisualIndicatorPreservedInc initializeCapabilityAgent(); sendSetIndicatorDirective(generatePayload(true, false, ASSET_ID1), MESSAGE_ID_TEST); - ASSERT_TRUE(m_testNotificationsObserver->waitFor(1, WAIT_TIMEOUT)); + ASSERT_TRUE(m_testNotificationsObserver->waitFor(1, MY_WAIT_TIMEOUT)); m_notificationsCapabilityAgent->shutdown(); // reboot and check that the indicator count value is preserved initializeCapabilityAgent(); - ASSERT_TRUE(m_testNotificationsObserver->waitFor(1, WAIT_TIMEOUT)); + ASSERT_TRUE(m_testNotificationsObserver->waitFor(1, MY_WAIT_TIMEOUT)); } /** @@ -803,7 +818,7 @@ TEST_F(NotificationsCapabilityAgentTest, test_sendSetIndicatorWithAudio) { initializeCapabilityAgent(); sendSetIndicatorDirective(generatePayload(false, true, ASSET_ID1, ASSET_URL1), MESSAGE_ID_TEST); - ASSERT_TRUE(m_testNotificationsObserver->waitFor(IndicatorState::OFF, WAIT_TIMEOUT)); + ASSERT_TRUE(m_testNotificationsObserver->waitFor(IndicatorState::OFF, MY_WAIT_TIMEOUT)); ASSERT_TRUE(m_renderer->waitUntilRenderingFinished()); } @@ -817,7 +832,7 @@ TEST_F(NotificationsCapabilityAgentTest, test_sendSetIndicatorWithVisualIndicato initializeCapabilityAgent(); sendSetIndicatorDirective(generatePayload(true, false), MESSAGE_ID_TEST); - ASSERT_TRUE(m_testNotificationsObserver->waitFor(IndicatorState::ON, WAIT_TIMEOUT)); + ASSERT_TRUE(m_testNotificationsObserver->waitFor(IndicatorState::ON, MY_WAIT_TIMEOUT)); } /** @@ -831,7 +846,7 @@ TEST_F(NotificationsCapabilityAgentTest, test_sameAssetId) { unsigned int expectedNumSetIndicators = 2; std::unique_lock lock(m_mutex); - if (!m_setIndicatorTrigger.wait_for(lock, WAIT_TIMEOUT, [this, expectedNumSetIndicators]() { + if (!m_setIndicatorTrigger.wait_for(lock, MY_WAIT_TIMEOUT, [this, expectedNumSetIndicators]() { return m_numSetIndicatorsProcessed == expectedNumSetIndicators; })) { return false; @@ -848,7 +863,7 @@ TEST_F(NotificationsCapabilityAgentTest, test_sameAssetId) { 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)); + ASSERT_TRUE(m_testNotificationsObserver->waitFor(IndicatorState::ON, MY_WAIT_TIMEOUT)); } /** @@ -859,22 +874,22 @@ TEST_F(NotificationsCapabilityAgentTest, test_persistVisualIndicatorPreserved) { // set IndicatorState to ON sendSetIndicatorDirective(generatePayload(true, false, ASSET_ID1), MESSAGE_ID_TEST); - ASSERT_TRUE(m_testNotificationsObserver->waitFor(IndicatorState::ON, WAIT_TIMEOUT)); + ASSERT_TRUE(m_testNotificationsObserver->waitFor(IndicatorState::ON, MY_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)); + ASSERT_TRUE(m_testNotificationsObserver->waitFor(IndicatorState::ON, MY_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)); + ASSERT_TRUE(m_testNotificationsObserver->waitFor(IndicatorState::OFF, MY_WAIT_TIMEOUT)); m_notificationsCapabilityAgent->shutdown(); initializeCapabilityAgent(); - ASSERT_TRUE(m_testNotificationsObserver->waitFor(IndicatorState::OFF, WAIT_TIMEOUT)); + ASSERT_TRUE(m_testNotificationsObserver->waitFor(IndicatorState::OFF, MY_WAIT_TIMEOUT)); } /** @@ -883,7 +898,7 @@ TEST_F(NotificationsCapabilityAgentTest, test_persistVisualIndicatorPreserved) { TEST_F(NotificationsCapabilityAgentTest, test_clearIndicatorWithEmptyQueue) { initializeCapabilityAgent(); sendClearIndicatorDirective(MESSAGE_ID_TEST); - ASSERT_TRUE(m_testNotificationsObserver->waitFor(IndicatorState::OFF, WAIT_TIMEOUT)); + ASSERT_TRUE(m_testNotificationsObserver->waitFor(IndicatorState::OFF, MY_WAIT_TIMEOUT)); } /** @@ -898,11 +913,11 @@ TEST_F(NotificationsCapabilityAgentTest, test_clearIndicatorWithEmptyQueueAndInd ASSERT_TRUE(m_renderer->waitUntilRenderingFinished()); - ASSERT_TRUE(m_testNotificationsObserver->waitFor(IndicatorState::ON, WAIT_TIMEOUT)); + ASSERT_TRUE(m_testNotificationsObserver->waitFor(IndicatorState::ON, MY_WAIT_TIMEOUT)); sendClearIndicatorDirective(MESSAGE_ID_TEST2); - ASSERT_TRUE(m_testNotificationsObserver->waitFor(IndicatorState::OFF, WAIT_TIMEOUT)); + ASSERT_TRUE(m_testNotificationsObserver->waitFor(IndicatorState::OFF, MY_WAIT_TIMEOUT)); } /** @@ -973,7 +988,7 @@ TEST_F(NotificationsCapabilityAgentTest, test_clearData) { m_notificationsStorage->getIndicatorState(&state); ASSERT_EQ(state, IndicatorState::OFF); - ASSERT_TRUE(m_testNotificationsObserver->waitFor(IndicatorState::OFF, WAIT_TIMEOUT)); + ASSERT_TRUE(m_testNotificationsObserver->waitFor(IndicatorState::OFF, MY_WAIT_TIMEOUT)); } } // namespace test diff --git a/CapabilityAgents/SpeakerManager/CMakeLists.txt b/CapabilityAgents/SpeakerManager/CMakeLists.txt index 5ed5959f..d6155ef2 100644 --- a/CapabilityAgents/SpeakerManager/CMakeLists.txt +++ b/CapabilityAgents/SpeakerManager/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.0) project(SpeakerManager LANGUAGES CXX) -include(../../build/BuildDefaults.cmake) +include(${AVS_CORE}/build/BuildDefaults.cmake) add_subdirectory("src") add_subdirectory("test") diff --git a/CapabilityAgents/SpeakerManager/include/SpeakerManager/SpeakerManager.h b/CapabilityAgents/SpeakerManager/include/SpeakerManager/SpeakerManager.h index 1f82999d..4ef40bfb 100644 --- a/CapabilityAgents/SpeakerManager/include/SpeakerManager/SpeakerManager.h +++ b/CapabilityAgents/SpeakerManager/include/SpeakerManager/SpeakerManager.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -31,8 +31,11 @@ #include #include #include +#include #include #include +#include +#include namespace alexaClientSDK { namespace capabilityAgents { @@ -67,6 +70,7 @@ public: * by it. SpeakerInterfaces will be grouped by @c SpeakerInterface::Type. * * @param speakers The @c Speakers to register. + * @param metricRecorder The metric recorder. * @param contextManager A @c ContextManagerInterface to manage the context. * @param messageSender A @c MessageSenderInterface to send messages to AVS. * @param exceptionEncounteredSender An @c ExceptionEncounteredSenderInterface to send @@ -74,6 +78,7 @@ public: */ static std::shared_ptr create( const std::vector>& speakers, + std::shared_ptr metricRecorder, std::shared_ptr contextManager, std::shared_ptr messageSender, std::shared_ptr exceptionEncounteredSender); @@ -112,6 +117,9 @@ public: bool forceNoNotifications = false, avsCommon::sdkInterfaces::SpeakerManagerObserverInterface::Source source = avsCommon::sdkInterfaces::SpeakerManagerObserverInterface::Source::LOCAL_API) override; +#ifdef ENABLE_MAXVOLUME_SETTING + std::future setMaximumVolumeLimit(const int8_t maximumVolumeLimit) override; +#endif std::future getSpeakerSettings( avsCommon::sdkInterfaces::SpeakerInterface::Type type, avsCommon::sdkInterfaces::SpeakerInterface::SpeakerSettings* settings) override; @@ -132,6 +140,7 @@ private: * Constructor. Called after validation has occurred on parameters. * * @param speakers The @c Speakers to register. + * @param metricRecorder The metric recorder. * @param contextManager A @c ContextManagerInterface to manage the context. * @param messageSender A @c MessageSenderInterface to send messages to AVS. * @param exceptionEncounteredSender An @c ExceptionEncounteredSenderInterface to send. @@ -140,6 +149,7 @@ private: */ SpeakerManager( const std::vector>& speakerInterfaces, + std::shared_ptr metricRecorder, std::shared_ptr contextManager, std::shared_ptr messageSender, std::shared_ptr exceptionEncounteredSender, @@ -264,6 +274,16 @@ private: avsCommon::sdkInterfaces::SpeakerManagerObserverInterface::Source source = avsCommon::sdkInterfaces::SpeakerManagerObserverInterface::Source::LOCAL_API); +#ifdef ENABLE_MAXVOLUME_SETTING + /** + * Function to set a limit on the maximum volume. This runs on a worker thread. + * + * @param type The type of speaker to modify mute for. + * @return A bool indicating success. + */ + bool executeSetMaximumVolumeLimit(const int8_t maximumVolumeLimit); +#endif + /** * Function to get the speaker settings for a specific @c Type. * This runs on a worker thread. @@ -314,6 +334,27 @@ private: avsCommon::sdkInterfaces::SpeakerInterface::Type type, avsCommon::sdkInterfaces::SpeakerInterface::SpeakerSettings* settings); + /** + * Get the maximum volume limit. + * + * @return The maximum volume limit. + */ + int8_t getMaximumVolumeLimit(); + + /** + * Applies Settings to All Speakers + * Attempts to synchronize by backing off using a retry timeout table + * @tparam Task The type of task to execute. + * @tparam Args The argument types for the task to execute. + * @param task A callable type representing a task. + * @param args The arguments to call the task with. + */ + template + void retryAndApplySettings(Task task, Args&&... args); + + /// The metric recorder. + std::shared_ptr m_metricRecorder; + /// The @c ContextManager used to generate system context for events. std::shared_ptr m_contextManager; @@ -335,12 +376,24 @@ private: /// Set of capability configurations that will get published using the Capabilities API std::unordered_set> m_capabilityConfigurations; + /// Object used to wait for event transmission cancellation. + avsCommon::utils::WaitEvent m_waitCancelEvent; + + /// Retry Timer object. + avsCommon::utils::RetryTimer m_retryTimer; + + /// The number of retries that will be done on an event in case of setting synchronization failure. + const std::size_t m_maxRetries; + /// An executor to perform operations on a worker thread. avsCommon::utils::threading::Executor m_executor; + + /// maximumVolumeLimit The maximum volume level speakers in this system can reach. + int8_t m_maximumVolumeLimit; }; } // namespace speakerManager } // namespace capabilityAgents } // namespace alexaClientSDK -#endif // ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_INCLUDE_SPEAKERMANAGER_SPEAKERMANAGER_H_ \ No newline at end of file +#endif // ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_INCLUDE_SPEAKERMANAGER_SPEAKERMANAGER_H_ diff --git a/CapabilityAgents/SpeakerManager/src/SpeakerManager.cpp b/CapabilityAgents/SpeakerManager/src/SpeakerManager.cpp index e6742b35..f99d122c 100644 --- a/CapabilityAgents/SpeakerManager/src/SpeakerManager.cpp +++ b/CapabilityAgents/SpeakerManager/src/SpeakerManager.cpp @@ -13,6 +13,7 @@ * permissions and limitations under the License. */ +#include #include #include #include @@ -22,8 +23,11 @@ #include #include #include -#include "SpeakerManager/SpeakerManagerConstants.h" +#include +#include +#include +#include "SpeakerManager/SpeakerManagerConstants.h" #include "SpeakerManager/SpeakerManager.h" namespace alexaClientSDK { @@ -35,6 +39,7 @@ using namespace avsCommon::avs::speakerConstants; using namespace avsCommon::sdkInterfaces; using namespace avsCommon::utils::json; using namespace avsCommon::utils::configuration; +using namespace avsCommon::utils::metrics; using namespace rapidjson; /// Speaker capability constants @@ -44,6 +49,10 @@ static const std::string SPEAKER_CAPABILITY_INTERFACE_TYPE = "AlexaInterface"; static const std::string SPEAKER_CAPABILITY_INTERFACE_NAME = "Speaker"; /// Speaker interface version static const std::string SPEAKER_CAPABILITY_INTERFACE_VERSION = "1.0"; +/// Retry timeout table +static const std::vector DEFAULT_RETRY_TABLE = {std::chrono::milliseconds(10).count(), + std::chrono::milliseconds(20).count(), + std::chrono::milliseconds(40).count()}; /// String to identify log entries originating from this file. static const std::string TAG{"SpeakerManager"}; @@ -75,6 +84,20 @@ static bool withinBounds(T value, T min, T max) { return true; } +static void submitMetric( + const std::shared_ptr& metricRecorder, + const std::string& eventName, + int count) { + if (!metricRecorder) { + return; + } + metricRecorder->recordMetric( + MetricEventBuilder{} + .setActivityName("SPEAKER_MANAGER-" + eventName) + .addDataPoint(DataPointCounterBuilder{}.setName(eventName).increment(count).build()) + .build()); +} + /** * Creates the Speaker capability configuration. * @@ -84,6 +107,7 @@ static std::shared_ptr getSpeakerCapabi std::shared_ptr SpeakerManager::create( const std::vector>& speakers, + std::shared_ptr metricRecorder, std::shared_ptr contextManager, std::shared_ptr messageSender, std::shared_ptr exceptionEncounteredSender) { @@ -104,24 +128,29 @@ std::shared_ptr SpeakerManager::create( // If key is present, then read and initilize the value from config or set to default. configurationRoot.getInt(SPEAKERMANAGER_MIN_UNMUTE_VOLUME_KEY, &minUnmuteVolume, MIN_UNMUTE_VOLUME); - auto speakerManager = std::shared_ptr( - new SpeakerManager(speakers, contextManager, messageSender, exceptionEncounteredSender, minUnmuteVolume)); + auto speakerManager = std::shared_ptr(new SpeakerManager( + speakers, metricRecorder, contextManager, messageSender, exceptionEncounteredSender, minUnmuteVolume)); return speakerManager; } SpeakerManager::SpeakerManager( - const std::vector>& speakers, + const std::vector>& speakerInterfaces, + std::shared_ptr metricRecorder, std::shared_ptr contextManager, std::shared_ptr messageSender, std::shared_ptr exceptionEncounteredSender, const int minUnmuteVolume) : CapabilityAgent{NAMESPACE, exceptionEncounteredSender}, RequiresShutdown{"SpeakerManager"}, + m_metricRecorder{metricRecorder}, m_contextManager{contextManager}, m_messageSender{messageSender}, - m_minUnmuteVolume{minUnmuteVolume} { - for (auto speaker : speakers) { + m_minUnmuteVolume{minUnmuteVolume}, + m_retryTimer{DEFAULT_RETRY_TABLE}, + m_maxRetries{DEFAULT_RETRY_TABLE.size()}, + m_maximumVolumeLimit{AVS_SET_VOLUME_MAX} { + for (auto speaker : speakerInterfaces) { m_speakerMap.insert( std::pair>(speaker->getSpeakerType(), speaker)); } @@ -161,6 +190,7 @@ DirectiveHandlerConfiguration SpeakerManager::getConfiguration() const { } void SpeakerManager::doShutdown() { + m_waitCancelEvent.wakeUp(); m_executor.shutdown(); m_messageSender.reset(); m_contextManager.reset(); @@ -219,6 +249,7 @@ void SpeakerManager::removeDirective(std::shared_ptr info) { // In those cases there is no messageId to remove because no result was expected. if (info->directive && info->result) { CapabilityAgent::removeDirective(info->directive->getMessageId()); + m_waitCancelEvent.wakeUp(); } } @@ -416,12 +447,15 @@ bool SpeakerManager::validateSpeakerSettingsConsistency( if (begin == end) { ACSDK_ERROR( LX("validateSpeakerSettingsConsistencyFailed").d("reason", "noSpeakersWithTypeFound").d("type", type)); + submitMetric(m_metricRecorder, "noSpeakersWithType", 1); return false; } + submitMetric(m_metricRecorder, "noSpeakersWithType", 0); // Get settings value to compare the rest against. if (!begin->second->getSpeakerSettings(&comparator)) { ACSDK_ERROR(LX("validateSpeakerSettingsConsistencyFailed").d("reason", "gettingSpeakerSettingsFailed")); + submitMetric(m_metricRecorder, "speakersCannotGetSetting", 1); return false; } @@ -433,10 +467,12 @@ bool SpeakerManager::validateSpeakerSettingsConsistency( // Retrieve speaker settings of current speaker. if (!speaker->getSpeakerSettings(&temp)) { ACSDK_ERROR(LX("validateSpeakerSettingsConsistencyFailed").d("reason", "gettingSpeakerSettingsFailed")); + submitMetric(m_metricRecorder, "speakersCannotGetSetting", 1); return false; } if (comparator.volume != temp.volume || comparator.mute != temp.mute) { + submitMetric(m_metricRecorder, "speakersInconsistent", 1); ACSDK_ERROR(LX("validateSpeakerSettingsConsistencyFailed") .d("reason", "inconsistentSpeakerSettings") .d("comparatorVolume", static_cast(comparator.volume)) @@ -449,8 +485,10 @@ bool SpeakerManager::validateSpeakerSettingsConsistency( } ACSDK_DEBUG9(LX("validateSpeakerSettingsConsistencyResult").d("consistent", consistent)); + submitMetric(m_metricRecorder, "speakersCannotGetSetting", 0); if (consistent && settings) { + submitMetric(m_metricRecorder, "speakersInconsistent", 0); settings->volume = comparator.volume; settings->mute = comparator.mute; ACSDK_DEBUG9( @@ -513,9 +551,28 @@ bool SpeakerManager::executeSetVolume( ACSDK_DEBUG9(LX("executeSetVolumeCalled").d("volume", static_cast(volume))); if (m_speakerMap.count(type) == 0) { ACSDK_ERROR(LX("executeSetVolumeFailed").d("reason", "noSpeakersWithType").d("type", type)); + submitMetric(m_metricRecorder, "setVolumeFailedZeroSpeakers", 1); return false; } + submitMetric(m_metricRecorder, "setVolumeFailedZeroSpeakers", 0); + submitMetric(m_metricRecorder, "setVolume", 1); + if (volume == 0) { + submitMetric(m_metricRecorder, "setVolumeZero", 1); + } + + auto adjustedVolume = volume; + + auto maximumVolumeLimit = getMaximumVolumeLimit(); + + if (volume > maximumVolumeLimit) { + ACSDK_DEBUG0(LX("adjustingSetVolumeValue") + .d("reason", "valueHigherThanLimit") + .d("value", (int)volume) + .d("maximumVolumeLimitSetting", (int)maximumVolumeLimit)); + adjustedVolume = maximumVolumeLimit; + } + SpeakerInterface::SpeakerSettings settings; if (!executeGetSpeakerSettings(type, &settings)) { ACSDK_ERROR(LX("executeSetVolumeFailed").d("reason", "speakerSettingsInconsistent")); @@ -523,18 +580,23 @@ bool SpeakerManager::executeSetVolume( } const int8_t previousVolume = settings.volume; - // Go through list of Speakers with SpeakerInterface::Type equal to type, and call setVolume. - auto beginIteratorAndEndIterator = m_speakerMap.equal_range(type); - auto begin = beginIteratorAndEndIterator.first; - auto end = beginIteratorAndEndIterator.second; + retryAndApplySettings([this, type, adjustedVolume]() -> bool { + // Go through list of Speakers with SpeakerInterface::Type equal to type, and call setVolume. + auto beginIteratorAndEndIterator = m_speakerMap.equal_range(type); + auto begin = beginIteratorAndEndIterator.first; + auto end = beginIteratorAndEndIterator.second; - for (auto typeAndSpeakerIterator = begin; typeAndSpeakerIterator != end; typeAndSpeakerIterator++) { - auto speaker = typeAndSpeakerIterator->second; - // In the future retry logic could be useful to ensure speakers are consistent. - if (!speaker->setVolume(volume)) { - return false; + for (auto typeAndSpeakerIterator = begin; typeAndSpeakerIterator != end; typeAndSpeakerIterator++) { + auto speaker = typeAndSpeakerIterator->second; + if (!speaker->setVolume(adjustedVolume)) { + submitMetric(m_metricRecorder, "setSpeakerVolumeFailed", 1); + return false; + } } - } + + submitMetric(m_metricRecorder, "setSpeakerVolumeFailed", 0); + return true; + }); // All initialized speakers controlled by directives with the same type should have the same state. if (!validateSpeakerSettingsConsistency(type, &settings)) { @@ -578,7 +640,7 @@ std::future SpeakerManager::adjustVolume( SpeakerManagerObserverInterface::Source source) { ACSDK_DEBUG9(LX("adjustVolumeCalled").d("delta", static_cast(delta))); return m_executor.submit([this, type, delta, forceNoNotifications, source] { - return withinBounds(delta, AVS_ADJUST_VOLUME_MIN, AVS_ADJUST_VOLUME_MAX) && + return withinBounds(delta, AVS_ADJUST_VOLUME_MIN, getMaximumVolumeLimit()) && executeAdjustVolume(type, delta, forceNoNotifications, source); }); } @@ -593,26 +655,49 @@ bool SpeakerManager::executeAdjustVolume( ACSDK_ERROR(LX("executeAdjustVolumeFailed").d("reason", "noSpeakersWithType").d("type", type)); return false; } + + submitMetric(m_metricRecorder, "adjustVolume", 1); SpeakerInterface::SpeakerSettings settings; if (!executeGetSpeakerSettings(type, &settings)) { ACSDK_ERROR(LX("executeAdjustVolumeFailed").d("reason", "speakerSettingsInconsistent")); return false; } + + auto maxVolumeLimit = getMaximumVolumeLimit(); + // if the current volume settings is higher than the maxVolumelimit, reset it to maxVolumeLimit to apply delta to. + if (settings.volume > maxVolumeLimit) { + ACSDK_DEBUG0(LX("adjustingSettingsVolumeValue") + .d("reason", "valueHigherThanLimit") + .d("value", (int)settings.volume) + .d("maximumVolumeLimitSetting", (int)maxVolumeLimit)); + settings.volume = maxVolumeLimit; + } + const int8_t previousVolume = settings.volume; - // Go through list of Speakers with SpeakerInterface::Type equal to type, and call adjustVolume. - auto beginIteratorAndEndIterator = m_speakerMap.equal_range(type); - auto begin = beginIteratorAndEndIterator.first; - auto end = beginIteratorAndEndIterator.second; - - for (auto typeAndSpeakerIterator = begin; typeAndSpeakerIterator != end; typeAndSpeakerIterator++) { - auto speaker = typeAndSpeakerIterator->second; - // In the future retry logic could be useful to ensure speakers are consistent. - if (!speaker->adjustVolume(delta)) { - return false; - } + // calculate resultant volume + if (delta > 0) { + settings.volume = std::min((int)settings.volume + delta, (int)maxVolumeLimit); + } else { + settings.volume = std::max((int)settings.volume + delta, (int)AVS_SET_VOLUME_MIN); } + retryAndApplySettings([this, type, settings] { + // Go through list of Speakers with SpeakerInterface::Type equal to type, and call setVolume. + // using setVolume instead of adjustVolume since if a subset of speakers fail : the delta will be reapplied. + auto beginIteratorAndEndIterator = m_speakerMap.equal_range(type); + auto begin = beginIteratorAndEndIterator.first; + auto end = beginIteratorAndEndIterator.second; + + for (auto typeAndSpeakerIterator = begin; typeAndSpeakerIterator != end; typeAndSpeakerIterator++) { + auto speaker = typeAndSpeakerIterator->second; + if (!speaker->setVolume(settings.volume)) { + return false; + } + } + return true; + }); + if (!validateSpeakerSettingsConsistency(type, &settings)) { ACSDK_ERROR(LX("executeAdjustVolumeFailed").d("reason", "speakerSettingsInconsistent")); return false; @@ -651,23 +736,41 @@ bool SpeakerManager::executeSetMute( bool forceNoNotifications, SpeakerManagerObserverInterface::Source source) { ACSDK_DEBUG9(LX("executeSetMuteCalled").d("mute", mute)); + + // if unmuting an already unmute speaker, then ignore the request + if (!mute) { + SpeakerInterface::SpeakerSettings settings; + // All initialized speakers controlled by directives with the same type should have the same state. + if (!validateSpeakerSettingsConsistency(type, &settings)) { + ACSDK_WARN(LX("executeSetMuteWarn") + .m("cannot check if device is muted") + .d("reason", "speakerSettingsInconsistent")); + } else if (!settings.mute) { + return true; + } + } + if (m_speakerMap.count(type) == 0) { ACSDK_ERROR(LX("executeSetMuteFailed").d("reason", "noSpeakersWithType").d("type", type)); return false; } - // Go through list of Speakers with SpeakerInterface::Type equal to type, and call setMute. - auto beginIteratorAndEndIterator = m_speakerMap.equal_range(type); - auto begin = beginIteratorAndEndIterator.first; - auto end = beginIteratorAndEndIterator.second; + submitMetric(m_metricRecorder, mute ? "setMute" : "setUnMute", 1); - for (auto typeAndSpeakerIterator = begin; typeAndSpeakerIterator != end; typeAndSpeakerIterator++) { - auto speaker = typeAndSpeakerIterator->second; - // In the future retry logic could be useful to ensure speakers are consistent. - if (!speaker->setMute(mute)) { - return false; + retryAndApplySettings([this, type, mute] { + // Go through list of Speakers with SpeakerInterface::Type equal to type, and call setMute. + auto beginIteratorAndEndIterator = m_speakerMap.equal_range(type); + auto begin = beginIteratorAndEndIterator.first; + auto end = beginIteratorAndEndIterator.second; + + for (auto typeAndSpeakerIterator = begin; typeAndSpeakerIterator != end; typeAndSpeakerIterator++) { + auto speaker = typeAndSpeakerIterator->second; + if (!speaker->setMute(mute)) { + return false; + } } - } + return true; + }); SpeakerInterface::SpeakerSettings settings; @@ -688,6 +791,47 @@ bool SpeakerManager::executeSetMute( return true; } +#ifdef ENABLE_MAXVOLUME_SETTING +std::future SpeakerManager::setMaximumVolumeLimit(const int8_t maximumVolumeLimit) { + return m_executor.submit([this, maximumVolumeLimit] { + return withinBounds(maximumVolumeLimit, AVS_ADJUST_VOLUME_MIN, AVS_ADJUST_VOLUME_MAX) && + executeSetMaximumVolumeLimit(maximumVolumeLimit); + }); +} + +bool SpeakerManager::executeSetMaximumVolumeLimit(const int8_t maximumVolumeLimit) { + ACSDK_DEBUG3(LX(__func__).d("maximumVolumeLimit", static_cast(maximumVolumeLimit))); + + // First adjust current volumes. + for (auto it = m_speakerMap.begin(); it != m_speakerMap.end(); it = m_speakerMap.upper_bound(it->first)) { + SpeakerInterface::SpeakerSettings speakerSettings; + auto speakerType = it->first; + ACSDK_DEBUG3(LX(__func__).d("type", speakerType)); + + if (!executeGetSpeakerSettings(speakerType, &speakerSettings)) { + ACSDK_ERROR(LX("executeSetMaximumVolumeLimitFailed").d("reason", "getSettingsFailed")); + return false; + } + + if (speakerSettings.volume > maximumVolumeLimit) { + ACSDK_DEBUG1(LX("reducingVolume") + .d("reason", "volumeIsHigherThanNewLimit") + .d("type", it->first) + .d("volume", (int)speakerSettings.volume) + .d("limit", (int)maximumVolumeLimit)); + + if (!executeSetVolume( + speakerType, maximumVolumeLimit, false, SpeakerManagerObserverInterface::Source::DIRECTIVE)) { + ACSDK_ERROR(LX("executeSetMaximumVolumeLimitFailed").d("reason", "setVolumeFailed")); + return false; + } + } + } + m_maximumVolumeLimit = maximumVolumeLimit; + return true; +} +#endif + void SpeakerManager::executeNotifySettingsChanged( const SpeakerInterface::SpeakerSettings& settings, const std::string& eventName, @@ -751,6 +895,28 @@ std::unordered_set> Spe return m_capabilityConfigurations; } +int8_t SpeakerManager::getMaximumVolumeLimit() { + return m_maximumVolumeLimit; +} + +template +void SpeakerManager::retryAndApplySettings(Task task, Args&&... args) { + auto boundTask = std::bind(std::forward(task), std::forward(args)...); + size_t attempt = 0; + m_waitCancelEvent.reset(); + while (attempt < m_maxRetries) { + if (boundTask()) { + break; + } + + // Exponential back-off before retry + // Can be cancelled anytime + if (m_waitCancelEvent.wait(m_retryTimer.calculateTimeToRetry(static_cast(attempt)))) { + break; + } + attempt++; + } +} } // namespace speakerManager } // namespace capabilityAgents } // namespace alexaClientSDK \ No newline at end of file diff --git a/CapabilityAgents/SpeakerManager/test/CMakeLists.txt b/CapabilityAgents/SpeakerManager/test/CMakeLists.txt index 04bfc625..edeeed17 100644 --- a/CapabilityAgents/SpeakerManager/test/CMakeLists.txt +++ b/CapabilityAgents/SpeakerManager/test/CMakeLists.txt @@ -5,4 +5,4 @@ set(INCLUDE_PATH "${AVSCommon_SOURCE_DIR}/AVS/test" "${AVSCommon_SOURCE_DIR}/SDKInterfaces/test") -discover_unit_tests("${INCLUDE_PATH}" SpeakerManager) +discover_unit_tests("${INCLUDE_PATH}" "SpeakerManager") diff --git a/CapabilityAgents/SpeakerManager/test/SpeakerManagerTest.cpp b/CapabilityAgents/SpeakerManager/test/SpeakerManagerTest.cpp index bedb1ad4..96efd8ae 100644 --- a/CapabilityAgents/SpeakerManager/test/SpeakerManagerTest.cpp +++ b/CapabilityAgents/SpeakerManager/test/SpeakerManagerTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -81,6 +81,14 @@ static const std::string UNMUTE_PAYLOAD = "" "}"; +#ifdef ENABLE_MAXVOLUME_SETTING +/// A valid value to be used as maximum volume limit. +static const int8_t VALID_MAXIMUM_VOLUME_LIMIT = AVS_SET_VOLUME_MAX - 10; + +/// An invalid maximum volume limit value +static const int8_t INVALID_MAXIMUM_VOLUME_LIMIT = AVS_SET_VOLUME_MAX + 10; +#endif + /** * A mock object to test that the observer is being correctly notified. */ @@ -90,6 +98,9 @@ public: onSpeakerSettingsChanged, void(const Source&, const SpeakerInterface::Type&, const SpeakerInterface::SpeakerSettings&)); }; +struct MockMetricRecorder : public avsCommon::utils::metrics::MetricRecorderInterface { + MOCK_METHOD1(recordMetric, void(std::shared_ptr)); +}; class SpeakerManagerTest : public ::testing::TestWithParam> { public: @@ -108,6 +119,16 @@ public: /// Helper function to get unique @c Type. std::set getUniqueTypes(std::vector>& speakers); +#ifdef ENABLE_MAXVOLUME_SETTING + /** + * Helper function for create and sent a directive + * + * @param directiveName The directive name. One of SetVolume or AdjustVolume. + * @param volume The value of the volume files within the directive. + */ + void createAndSendVolumeDirective(const std::string directiveName, const int8_t volume); +#endif + /// A constructor which initializes the promises and futures needed for the test class. SpeakerManagerTest() : m_wakeSetCompletedPromise{}, @@ -121,6 +142,9 @@ protected: /// Future to synchronize directive handling through setCompleted. std::future m_wakeSetCompletedFuture; + /// The metric recorder. + std::shared_ptr m_metricRecorder; + /* * Set this to a nice mock. The only instance of the mock being called is the setStateProvider member, which we * explicitly test. @@ -144,6 +168,7 @@ protected: }; void SpeakerManagerTest::SetUp() { + m_metricRecorder = std::make_shared>(); m_mockContextManager = std::make_shared>(); m_mockMessageSender = std::make_shared>(); m_mockExceptionSender = std::make_shared>(); @@ -174,6 +199,40 @@ std::set SpeakerManagerTest::getUniqueTypes( return types; } +#ifdef ENABLE_MAXVOLUME_SETTING +void SpeakerManagerTest::createAndSendVolumeDirective(const std::string directiveName, const int8_t volume) { + EXPECT_CALL(*(m_mockDirectiveHandlerResult.get()), setCompleted()) + .Times(1) + .WillOnce(InvokeWithoutArgs(this, &SpeakerManagerTest::wakeOnSetCompleted)); + + static int id = 1; + const std::string messageId = MESSAGE_ID + std::to_string(id++); + std::string payload = + "{" + "\"volume\":" + + std::to_string(volume) + "}"; + + // Create Directive. + auto attachmentManager = std::make_shared>(); + auto avsMessageHeader = std::make_shared(SET_VOLUME.nameSpace, directiveName, messageId); + + std::shared_ptr directive = + AVSDirective::create("", avsMessageHeader, payload, attachmentManager, ""); + + m_speakerManager->CapabilityAgent::preHandleDirective(directive, std::move(m_mockDirectiveHandlerResult)); + m_speakerManager->CapabilityAgent::handleDirective(messageId); + m_wakeSetCompletedFuture.wait_for(TIMEOUT); +} + +static int8_t getSpeakerVolume(std::shared_ptr speaker) { + SpeakerInterface::SpeakerSettings speakerSettings; + + speaker->getSpeakerSettings(&speakerSettings); + + return speakerSettings.volume; +} +#endif + /// Helper function to generate the VolumeState in JSON for the ContextManager. std::string generateVolumeStateJson(SpeakerInterface::SpeakerSettings settings) { rapidjson::Document state(rapidjson::kObjectType); @@ -196,7 +255,8 @@ TEST_F(SpeakerManagerTest, test_nullContextManager) { std::vector> speakers{ std::make_shared(SpeakerInterface::Type::AVS_SPEAKER_VOLUME)}; - m_speakerManager = SpeakerManager::create(speakers, nullptr, m_mockMessageSender, m_mockExceptionSender); + m_speakerManager = + SpeakerManager::create(speakers, m_metricRecorder, nullptr, m_mockMessageSender, m_mockExceptionSender); ASSERT_EQ(m_speakerManager, nullptr); } @@ -208,7 +268,8 @@ TEST_F(SpeakerManagerTest, test_nullMessageSender) { std::vector> speakers{ std::make_shared(SpeakerInterface::Type::AVS_SPEAKER_VOLUME)}; - m_speakerManager = SpeakerManager::create(speakers, m_mockContextManager, nullptr, m_mockExceptionSender); + m_speakerManager = + SpeakerManager::create(speakers, m_metricRecorder, m_mockContextManager, nullptr, m_mockExceptionSender); ASSERT_EQ(m_speakerManager, nullptr); } @@ -220,7 +281,8 @@ TEST_F(SpeakerManagerTest, test_nullExceptionSender) { std::vector> speakers{ std::make_shared(SpeakerInterface::Type::AVS_SPEAKER_VOLUME)}; - m_speakerManager = SpeakerManager::create(speakers, m_mockContextManager, m_mockMessageSender, nullptr); + m_speakerManager = + SpeakerManager::create(speakers, m_metricRecorder, m_mockContextManager, m_mockMessageSender, nullptr); ASSERT_EQ(m_speakerManager, nullptr); } @@ -229,7 +291,8 @@ TEST_F(SpeakerManagerTest, test_nullExceptionSender) { * Tests creating the SpeakerManager with no speakers. */ TEST_F(SpeakerManagerTest, test_noSpeakers) { - m_speakerManager = SpeakerManager::create({}, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender); + m_speakerManager = + SpeakerManager::create({}, m_metricRecorder, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender); ASSERT_NE(m_speakerManager, nullptr); } @@ -246,8 +309,8 @@ TEST_F(SpeakerManagerTest, test_contextManagerSetStateConstructor) { speaker->DelegateToReal(); std::vector> speakers{speaker}; - m_speakerManager = - SpeakerManager::create(speakers, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender); + m_speakerManager = SpeakerManager::create( + speakers, m_metricRecorder, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender); } /* @@ -260,8 +323,8 @@ TEST_F(SpeakerManagerTest, test_setVolumeUnderBounds) { std::vector> speakers = {speaker}; - m_speakerManager = - SpeakerManager::create(speakers, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender); + m_speakerManager = SpeakerManager::create( + speakers, m_metricRecorder, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender); EXPECT_CALL(*m_observer, onSpeakerSettingsChanged(_, _, _)).Times(Exactly(0)); m_speakerManager->addSpeakerManagerObserver(m_observer); @@ -280,8 +343,8 @@ TEST_F(SpeakerManagerTest, test_setVolumeOverBounds) { EXPECT_CALL(*speaker, setVolume(_)).Times(Exactly(0)); std::vector> speakers = {speaker}; - m_speakerManager = - SpeakerManager::create(speakers, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender); + m_speakerManager = SpeakerManager::create( + speakers, m_metricRecorder, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender); EXPECT_CALL(*m_observer, onSpeakerSettingsChanged(_, _, _)).Times(Exactly(0)); m_speakerManager->addSpeakerManagerObserver(m_observer); @@ -300,8 +363,8 @@ TEST_F(SpeakerManagerTest, test_adjustVolumeUnderBounds) { EXPECT_CALL(*speaker, adjustVolume(_)).Times(Exactly(0)); std::vector> speakers = {speaker}; - m_speakerManager = - SpeakerManager::create(speakers, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender); + m_speakerManager = SpeakerManager::create( + speakers, m_metricRecorder, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender); EXPECT_CALL(*m_observer, onSpeakerSettingsChanged(_, _, _)).Times(Exactly(0)); m_speakerManager->addSpeakerManagerObserver(m_observer); @@ -320,8 +383,8 @@ TEST_F(SpeakerManagerTest, test_adjustVolumeOverBounds) { EXPECT_CALL(*speaker, adjustVolume(_)).Times(Exactly(0)); std::vector> speakers = {speaker}; - m_speakerManager = - SpeakerManager::create(speakers, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender); + m_speakerManager = SpeakerManager::create( + speakers, m_metricRecorder, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender); EXPECT_CALL(*m_observer, onSpeakerSettingsChanged(_, _, _)).Times(Exactly(0)); m_speakerManager->addSpeakerManagerObserver(m_observer); @@ -346,8 +409,8 @@ TEST_F(SpeakerManagerTest, test_setVolumeOutOfSync) { std::vector> speakers = {speaker, speaker2}; - m_speakerManager = - SpeakerManager::create(speakers, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender); + m_speakerManager = SpeakerManager::create( + speakers, m_metricRecorder, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender); EXPECT_CALL(*m_observer, onSpeakerSettingsChanged(_, _, _)).Times(Exactly(0)); m_speakerManager->addSpeakerManagerObserver(m_observer); @@ -372,8 +435,8 @@ TEST_F(SpeakerManagerTest, test_adjustVolumeOutOfSync) { std::vector> speakers = {speaker, speaker2}; - m_speakerManager = - SpeakerManager::create(speakers, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender); + m_speakerManager = SpeakerManager::create( + speakers, m_metricRecorder, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender); EXPECT_CALL(*m_observer, onSpeakerSettingsChanged(_, _, _)).Times(Exactly(0)); m_speakerManager->addSpeakerManagerObserver(m_observer); @@ -389,12 +452,12 @@ TEST_F(SpeakerManagerTest, test_adjustVolumeOutOfSync) { TEST_F(SpeakerManagerTest, test_eventNotSentWhenAdjustVolumeUnchanged) { auto speaker = std::make_shared>(SpeakerInterface::Type::AVS_SPEAKER_VOLUME); speaker->DelegateToReal(); - EXPECT_CALL(*speaker, adjustVolume(AVS_ADJUST_VOLUME_MIN)).Times(Exactly(1)); + EXPECT_CALL(*speaker, adjustVolume(AVS_ADJUST_VOLUME_MIN)).Times(Exactly(0)); std::vector> speakers = {speaker}; - m_speakerManager = - SpeakerManager::create(speakers, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender); + m_speakerManager = SpeakerManager::create( + speakers, m_metricRecorder, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender); // The test adjusts the volume by AVS_ADJUST_VOLUME_MIN, which results in the lowest volume possible. SpeakerInterface::SpeakerSettings expectedSettings{AVS_SET_VOLUME_MIN, UNMUTE}; @@ -430,8 +493,8 @@ TEST_F(SpeakerManagerTest, test_eventNotSentWhenSetVolumeUnchanged) { std::vector> speakers = {speaker}; - m_speakerManager = - SpeakerManager::create(speakers, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender); + m_speakerManager = SpeakerManager::create( + speakers, m_metricRecorder, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender); SpeakerInterface::SpeakerSettings expectedSettings{AVS_SET_VOLUME_MIN, UNMUTE}; m_speakerManager->addSpeakerManagerObserver(m_observer); @@ -471,8 +534,8 @@ TEST_F(SpeakerManagerTest, test_setMuteOutOfSync) { std::vector> speakers = {speaker, speaker2}; - m_speakerManager = - SpeakerManager::create(speakers, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender); + m_speakerManager = SpeakerManager::create( + speakers, m_metricRecorder, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender); EXPECT_CALL(*m_observer, onSpeakerSettingsChanged(_, _, _)).Times(Exactly(0)); m_speakerManager->addSpeakerManagerObserver(m_observer); @@ -495,8 +558,8 @@ TEST_F(SpeakerManagerTest, test_getSpeakerSettingsSpeakersOutOfSync) { std::vector> speakers = {speaker, speaker2}; - m_speakerManager = - SpeakerManager::create(speakers, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender); + m_speakerManager = SpeakerManager::create( + speakers, m_metricRecorder, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender); EXPECT_CALL(*m_observer, onSpeakerSettingsChanged(_, _, _)).Times(Exactly(0)); m_speakerManager->addSpeakerManagerObserver(m_observer); @@ -514,8 +577,8 @@ TEST_F(SpeakerManagerTest, test_getConfiguration) { std::shared_ptr speaker = std::make_shared>(SpeakerInterface::Type::AVS_SPEAKER_VOLUME); - m_speakerManager = - SpeakerManager::create({speaker}, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender); + m_speakerManager = SpeakerManager::create( + {speaker}, m_metricRecorder, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender); auto configuration = m_speakerManager->getConfiguration(); auto audioNonBlockingPolicy = BlockingPolicy(BlockingPolicy::MEDIUM_AUDIO, false); @@ -531,10 +594,10 @@ TEST_F(SpeakerManagerTest, test_addNullObserver) { auto speaker = std::make_shared>(SpeakerInterface::Type::AVS_SPEAKER_VOLUME); speaker->DelegateToReal(); - m_speakerManager = - SpeakerManager::create({speaker}, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender); + m_speakerManager = SpeakerManager::create( + {speaker}, m_metricRecorder, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender); m_speakerManager->addSpeakerManagerObserver(nullptr); - EXPECT_CALL(*m_mockMessageSender, sendMessage(_)).Times(Exactly(3)); + EXPECT_CALL(*m_mockMessageSender, sendMessage(_)).Times(Exactly(2)); m_speakerManager->setVolume(SpeakerInterface::Type::AVS_SPEAKER_VOLUME, AVS_SET_VOLUME_MAX).wait(); m_speakerManager->adjustVolume(SpeakerInterface::Type::AVS_SPEAKER_VOLUME, AVS_ADJUST_VOLUME_MAX).wait(); @@ -549,10 +612,10 @@ TEST_F(SpeakerManagerTest, test_removeSpeakerManagerObserver) { speaker->DelegateToReal(); EXPECT_CALL(*m_observer, onSpeakerSettingsChanged(_, _, _)).Times(Exactly(0)); - EXPECT_CALL(*m_mockMessageSender, sendMessage(_)).Times(Exactly(3)); + EXPECT_CALL(*m_mockMessageSender, sendMessage(_)).Times(Exactly(2)); - m_speakerManager = - SpeakerManager::create({speaker}, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender); + m_speakerManager = SpeakerManager::create( + {speaker}, m_metricRecorder, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender); m_speakerManager->addSpeakerManagerObserver(m_observer); m_speakerManager->removeSpeakerManagerObserver(m_observer); @@ -568,10 +631,10 @@ TEST_F(SpeakerManagerTest, test_removeNullObserver) { auto speaker = std::make_shared>(SpeakerInterface::Type::AVS_SPEAKER_VOLUME); speaker->DelegateToReal(); - EXPECT_CALL(*m_mockMessageSender, sendMessage(_)).Times(Exactly(3)); + EXPECT_CALL(*m_mockMessageSender, sendMessage(_)).Times(Exactly(2)); - m_speakerManager = - SpeakerManager::create({speaker}, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender); + m_speakerManager = SpeakerManager::create( + {speaker}, m_metricRecorder, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender); m_speakerManager->removeSpeakerManagerObserver(nullptr); m_speakerManager->setVolume(SpeakerInterface::Type::AVS_SPEAKER_VOLUME, AVS_SET_VOLUME_MAX).wait(); @@ -579,6 +642,188 @@ TEST_F(SpeakerManagerTest, test_removeNullObserver) { m_speakerManager->setMute(SpeakerInterface::Type::AVS_SPEAKER_VOLUME, MUTE).wait(); } +/* + * Test retryLogic for SetVolume on speaker type AVS_SPEAKER_VOLUME. Returning false once for speaker->setVolume() + * triggers retry and when successful returns the future of value true. + */ +TEST_F(SpeakerManagerTest, test_retryAndApplySettingsForSetVolume) { + auto speaker = std::make_shared>(SpeakerInterface::Type::AVS_SPEAKER_VOLUME); + speaker->DelegateToReal(); + EXPECT_CALL(*speaker, setVolume(_)).Times(Exactly(2)).WillOnce(Return(false)); + + m_speakerManager = SpeakerManager::create( + {speaker}, m_metricRecorder, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender); + + std::future future = + m_speakerManager->setVolume(SpeakerInterface::Type::AVS_SPEAKER_VOLUME, AVS_SET_VOLUME_MIN); + ASSERT_TRUE(future.get()); +} + +/* + * Test retryLogic for AdjustVolume on speaker type AVS_SPEAKER_VOLUME. Returning false once for speaker->setVolume() + * triggers retry and when successful returns the future of value true. + */ +TEST_F(SpeakerManagerTest, test_retryAndApplySettingsForAdjustVolume) { + auto speaker = std::make_shared>(SpeakerInterface::Type::AVS_SPEAKER_VOLUME); + speaker->DelegateToReal(); + EXPECT_CALL(*speaker, setVolume(_)).Times(Exactly(2)).WillOnce(Return(false)); + + m_speakerManager = SpeakerManager::create( + {speaker}, m_metricRecorder, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender); + + std::future future = + m_speakerManager->adjustVolume(SpeakerInterface::Type::AVS_SPEAKER_VOLUME, AVS_SET_VOLUME_MIN); + ASSERT_TRUE(future.get()); +} + +/* + * Test retryLogic for SetMute on speaker type AVS_SPEAKER_VOLUME. Returning false once for speaker->setMute() + * triggers retry and when successful returns the future of value true. + */ +TEST_F(SpeakerManagerTest, test_retryAndApplySettingsForSetMute) { + auto speaker = std::make_shared>(SpeakerInterface::Type::AVS_SPEAKER_VOLUME); + speaker->DelegateToReal(); + EXPECT_CALL(*speaker, setMute(_)).Times(Exactly(2)).WillOnce(Return(false)); + + m_speakerManager = SpeakerManager::create( + {speaker}, m_metricRecorder, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender); + + EXPECT_CALL(*m_mockMessageSender, sendMessage(_)).Times(Exactly(1)); + + std::future future = m_speakerManager->setMute(SpeakerInterface::Type::AVS_SPEAKER_VOLUME, MUTE); + ASSERT_TRUE(future.get()); +} + +#ifdef ENABLE_MAXVOLUME_SETTING +/** + * Test that setting a maximum volume limit succeeds + * and a local call to setVolume or adjustVolume will + * completely fail. + */ + +TEST_F(SpeakerManagerTest, test_setMaximumVolumeLimit) { + auto avsSpeaker = std::make_shared>(SpeakerInterface::Type::AVS_SPEAKER_VOLUME); + auto alertsSpeaker = std::make_shared>(SpeakerInterface::Type::AVS_SPEAKER_VOLUME); + + avsSpeaker->DelegateToReal(); + alertsSpeaker->DelegateToReal(); + + avsSpeaker->setVolume(VALID_MAXIMUM_VOLUME_LIMIT - 1); + alertsSpeaker->setVolume(VALID_MAXIMUM_VOLUME_LIMIT - 1); + + // Expect volumeChanged event. + EXPECT_CALL(*m_mockMessageSender, sendMessage(_)).Times(Exactly(1)); + EXPECT_CALL(*avsSpeaker, setVolume(_)).Times(AtLeast(1)); + EXPECT_CALL(*alertsSpeaker, setVolume(_)).Times(AtLeast(1)); + EXPECT_CALL(*m_observer, onSpeakerSettingsChanged(_, _, _)).Times(0); + + m_speakerManager = SpeakerManager::create( + {avsSpeaker, alertsSpeaker}, + m_metricRecorder, + m_mockContextManager, + m_mockMessageSender, + m_mockExceptionSender); + + EXPECT_TRUE(m_speakerManager->setMaximumVolumeLimit(VALID_MAXIMUM_VOLUME_LIMIT).get()); + + // Local change either with setVolume will set to limit but with adjustVolume will fail + EXPECT_TRUE( + m_speakerManager->setVolume(SpeakerInterface::Type::AVS_SPEAKER_VOLUME, VALID_MAXIMUM_VOLUME_LIMIT + 1).get()); + EXPECT_FALSE( + m_speakerManager->adjustVolume(SpeakerInterface::Type::AVS_SPEAKER_VOLUME, VALID_MAXIMUM_VOLUME_LIMIT + 1) + .get()); + + // The volume went to upper limit. + EXPECT_EQ(getSpeakerVolume(avsSpeaker), VALID_MAXIMUM_VOLUME_LIMIT); + EXPECT_EQ(getSpeakerVolume(alertsSpeaker), VALID_MAXIMUM_VOLUME_LIMIT); + + // Increase the volume by 2, so end result will exceed the limit. + EXPECT_TRUE(m_speakerManager->adjustVolume(SpeakerInterface::Type::AVS_SPEAKER_VOLUME, 2).get()); + + // Following the 2nd adjustVolume, the volume will change to the limit. + EXPECT_EQ(getSpeakerVolume(alertsSpeaker), VALID_MAXIMUM_VOLUME_LIMIT); +} + +/** + * Test that if a new limit was set while the volume was higher + * than the new limit, operation will succeed and the volume will be decreased. + */ + +TEST_F(SpeakerManagerTest, testSetMaximumVolumeLimitWhileVolumeIsHigher) { + auto avsSpeaker = std::make_shared>(SpeakerInterface::Type::AVS_SPEAKER_VOLUME); + auto alertsSpeaker = std::make_shared>(SpeakerInterface::Type::AVS_SPEAKER_VOLUME); + + avsSpeaker->DelegateToReal(); + alertsSpeaker->DelegateToReal(); + + EXPECT_TRUE(avsSpeaker->setVolume(VALID_MAXIMUM_VOLUME_LIMIT + 1)); + EXPECT_TRUE(alertsSpeaker->setVolume(VALID_MAXIMUM_VOLUME_LIMIT + 1)); + + EXPECT_CALL(*avsSpeaker, setVolume(VALID_MAXIMUM_VOLUME_LIMIT)).Times(1); + EXPECT_CALL(*alertsSpeaker, setVolume(VALID_MAXIMUM_VOLUME_LIMIT)).Times(1); + + // Expect volumeChanged event. + EXPECT_CALL(*m_mockMessageSender, sendMessage(_)).Times(Exactly(1)); + + m_speakerManager = SpeakerManager::create( + {avsSpeaker, alertsSpeaker}, + m_metricRecorder, + m_mockContextManager, + m_mockMessageSender, + m_mockExceptionSender); + + EXPECT_TRUE(m_speakerManager->setMaximumVolumeLimit(VALID_MAXIMUM_VOLUME_LIMIT).get()); + + EXPECT_EQ(getSpeakerVolume(avsSpeaker), VALID_MAXIMUM_VOLUME_LIMIT); + EXPECT_EQ(getSpeakerVolume(alertsSpeaker), VALID_MAXIMUM_VOLUME_LIMIT); +} + +/** + * Test that SetVolume directive with volume > limit + * should set the volume to the limit + */ + +TEST_F(SpeakerManagerTest, testAVSSetVolumeHigherThanLimit) { + avsCommon::utils::logger::getConsoleLogger()->setLevel(avsCommon::utils::logger::Level::DEBUG9); + auto avsSpeaker = std::make_shared>(SpeakerInterface::Type::AVS_SPEAKER_VOLUME); + auto alertsSpeaker = std::make_shared>(SpeakerInterface::Type::AVS_SPEAKER_VOLUME); + + avsSpeaker->DelegateToReal(); + alertsSpeaker->DelegateToReal(); + + EXPECT_CALL(*m_mockMessageSender, sendMessage(_)).Times(Exactly(1)); + + EXPECT_TRUE(avsSpeaker->setVolume(VALID_MAXIMUM_VOLUME_LIMIT - 1)); + EXPECT_TRUE(alertsSpeaker->setVolume(VALID_MAXIMUM_VOLUME_LIMIT - 1)); + + m_speakerManager = SpeakerManager::create( + {avsSpeaker, alertsSpeaker}, + m_metricRecorder, + m_mockContextManager, + m_mockMessageSender, + m_mockExceptionSender); + + EXPECT_TRUE(m_speakerManager->setMaximumVolumeLimit(VALID_MAXIMUM_VOLUME_LIMIT).get()); + + createAndSendVolumeDirective(SET_VOLUME.name, VALID_MAXIMUM_VOLUME_LIMIT + 1); + + ASSERT_EQ(getSpeakerVolume(avsSpeaker), VALID_MAXIMUM_VOLUME_LIMIT); + ASSERT_EQ(getSpeakerVolume(alertsSpeaker), VALID_MAXIMUM_VOLUME_LIMIT); +} + +/** + * Test that a call to @c setMaximumVolumeLimit with invalid value fails. + */ +TEST_F(SpeakerManagerTest, testSetMaximumVolumeLimitWithInvalidValue) { + auto avsSpeaker = std::make_shared>(SpeakerInterface::Type::AVS_SPEAKER_VOLUME); + + m_speakerManager = SpeakerManager::create( + {avsSpeaker}, m_metricRecorder, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender); + + EXPECT_FALSE(m_speakerManager->setMaximumVolumeLimit(INVALID_MAXIMUM_VOLUME_LIMIT).get()); +} +#endif + /** * Create different combinations of @c Type for parameterized tests (TEST_P). */ @@ -622,8 +867,12 @@ TEST_P(SpeakerManagerTest, test_setVolume) { speakers.push_back(speaker); } - m_speakerManager = - SpeakerManager::create(speakers, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender); + m_speakerManager = SpeakerManager::create( + speakers, + m_metricRecorder, + m_mockContextManager, + m_mockMessageSender, + m_mockExceptionSender); SpeakerInterface::SpeakerSettings expectedSettings{AVS_SET_VOLUME_MAX, UNMUTE}; m_speakerManager->addSpeakerManagerObserver(m_observer); @@ -650,12 +899,16 @@ TEST_P(SpeakerManagerTest, test_adjustVolume) { for (auto& typeOfSpeaker : GetParam()) { auto speaker = std::make_shared>(typeOfSpeaker); speaker->DelegateToReal(); - EXPECT_CALL(*speaker, adjustVolume(AVS_SET_VOLUME_MAX)).Times(Exactly(1)); + EXPECT_CALL(*speaker, setVolume(AVS_SET_VOLUME_MAX)).Times(Exactly(1)); speakers.push_back(speaker); } - m_speakerManager = - SpeakerManager::create(speakers, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender); + m_speakerManager = SpeakerManager::create( + speakers, + m_metricRecorder, + m_mockContextManager, + m_mockMessageSender, + m_mockExceptionSender); // The test adjusts the volume by AVS_ADJUST_VOLUME_MAX, which results in the lowest volume possible. SpeakerInterface::SpeakerSettings expectedSettings{AVS_SET_VOLUME_MAX, UNMUTE}; @@ -687,8 +940,12 @@ TEST_P(SpeakerManagerTest, test_setMute) { speakers.push_back(speaker); } - m_speakerManager = - SpeakerManager::create(speakers, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender); + m_speakerManager = SpeakerManager::create( + speakers, + m_metricRecorder, + m_mockContextManager, + m_mockMessageSender, + m_mockExceptionSender); SpeakerInterface::SpeakerSettings expectedSettings{DEFAULT_SETTINGS.volume, MUTE}; m_speakerManager->addSpeakerManagerObserver(m_observer); @@ -715,15 +972,17 @@ TEST_P(SpeakerManagerTest, test_getSpeakerSettings) { for (auto& typeOfSpeaker : GetParam()) { auto speaker = std::make_shared>(typeOfSpeaker); speaker->DelegateToReal(); - // There are other calls to getSpeakerSettings(), such as when we initially provide the Context. - EXPECT_CALL(*speaker, getSpeakerSettings(_)).Times(AtLeast(1)); speakers.push_back(speaker); } auto uniqueTypes = getUniqueTypes(speakers); - m_speakerManager = - SpeakerManager::create(speakers, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender); + m_speakerManager = SpeakerManager::create( + speakers, + m_metricRecorder, + m_mockContextManager, + m_mockMessageSender, + m_mockExceptionSender); EXPECT_CALL(*m_observer, onSpeakerSettingsChanged(_, _, _)).Times(Exactly(0)); m_speakerManager->addSpeakerManagerObserver(m_observer); @@ -756,7 +1015,11 @@ TEST_P(SpeakerManagerTest, test_setVolumeDirective) { timesCalled = 1; } - EXPECT_CALL(*speaker, setMute(UNMUTE)).Times(Exactly(timesCalled)); + SpeakerInterface::SpeakerSettings temp; + speaker->getSpeakerSettings(&temp); + if (temp.mute) { + EXPECT_CALL(*speaker, setMute(UNMUTE)).Times(Exactly(timesCalled)); + } EXPECT_CALL(*speaker, setVolume(AVS_SET_VOLUME_MAX)).Times(Exactly(timesCalled)); speakers.push_back(speaker); @@ -780,8 +1043,12 @@ TEST_P(SpeakerManagerTest, test_setVolumeDirective) { .Times(1) .WillOnce(InvokeWithoutArgs(this, &SpeakerManagerTest::wakeOnSetCompleted)); - m_speakerManager = - SpeakerManager::create(speakers, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender); + m_speakerManager = SpeakerManager::create( + speakers, + m_metricRecorder, + m_mockContextManager, + m_mockMessageSender, + m_mockExceptionSender); m_speakerManager->addSpeakerManagerObserver(m_observer); // Create Directive. @@ -814,8 +1081,12 @@ TEST_P(SpeakerManagerTest, test_adjustVolumeDirective) { timesCalled = 1; } - EXPECT_CALL(*speaker, setMute(UNMUTE)).Times(Exactly(timesCalled)); - EXPECT_CALL(*speaker, adjustVolume(AVS_ADJUST_VOLUME_MAX)).Times(Exactly(timesCalled)); + SpeakerInterface::SpeakerSettings temp; + speaker->getSpeakerSettings(&temp); + if (temp.mute) { + EXPECT_CALL(*speaker, setMute(UNMUTE)).Times(Exactly(timesCalled)); + } + EXPECT_CALL(*speaker, setVolume(AVS_SET_VOLUME_MAX)).Times(Exactly(timesCalled)); speakers.push_back(speaker); } @@ -838,8 +1109,12 @@ TEST_P(SpeakerManagerTest, test_adjustVolumeDirective) { .Times(1) .WillOnce(InvokeWithoutArgs(this, &SpeakerManagerTest::wakeOnSetCompleted)); - m_speakerManager = - SpeakerManager::create(speakers, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender); + m_speakerManager = SpeakerManager::create( + speakers, + m_metricRecorder, + m_mockContextManager, + m_mockMessageSender, + m_mockExceptionSender); m_speakerManager->addSpeakerManagerObserver(m_observer); // Create Directive. @@ -896,8 +1171,12 @@ TEST_P(SpeakerManagerTest, test_setMuteDirective) { .Times(1) .WillOnce(InvokeWithoutArgs(this, &SpeakerManagerTest::wakeOnSetCompleted)); - m_speakerManager = - SpeakerManager::create(speakers, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender); + m_speakerManager = SpeakerManager::create( + speakers, + m_metricRecorder, + m_mockContextManager, + m_mockMessageSender, + m_mockExceptionSender); m_speakerManager->addSpeakerManagerObserver(m_observer); // Create Directive. @@ -933,7 +1212,12 @@ TEST_P(SpeakerManagerTest, test_setVolumeDirectiveWhenMuted) { speakers.push_back(speaker); } - m_speakerManager = SpeakerManager::create(speakers, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender); + m_speakerManager = SpeakerManager::create( + speakers, + m_metricRecorder, + m_mockContextManager, + m_mockMessageSender, + m_mockExceptionSender); m_speakerManager->addSpeakerManagerObserver(m_observer); for (auto type : getUniqueTypes(speakers)) { @@ -969,7 +1253,7 @@ TEST_P(SpeakerManagerTest, test_setVolumeDirectiveWhenMuted) { auto attachmentManager = std::make_shared>(); auto avsMessageHeader = std::make_shared(SET_MUTE.nameSpace, SET_MUTE.name, MESSAGE_ID); std::shared_ptr directive = - AVSDirective::create("", avsMessageHeader, UNMUTE_PAYLOAD, attachmentManager, ""); + AVSDirective::create("", avsMessageHeader, UNMUTE_PAYLOAD, attachmentManager, ""); m_speakerManager->CapabilityAgent::preHandleDirective(directive, std::move(m_mockDirectiveHandlerResult)); m_speakerManager->CapabilityAgent::handleDirective(MESSAGE_ID); diff --git a/CapabilityAgents/SpeechSynthesizer/CMakeLists.txt b/CapabilityAgents/SpeechSynthesizer/CMakeLists.txt index 07272b50..78c94f0c 100644 --- a/CapabilityAgents/SpeechSynthesizer/CMakeLists.txt +++ b/CapabilityAgents/SpeechSynthesizer/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.0) project(SpeechSynthesizer LANGUAGES CXX) -include(../../build/BuildDefaults.cmake) +include(${AVS_CORE}/build/BuildDefaults.cmake) add_subdirectory("src") add_subdirectory("test") diff --git a/CapabilityAgents/SpeechSynthesizer/include/SpeechSynthesizer/SpeechSynthesizer.h b/CapabilityAgents/SpeechSynthesizer/include/SpeechSynthesizer/SpeechSynthesizer.h index de0823ea..0a19c346 100644 --- a/CapabilityAgents/SpeechSynthesizer/include/SpeechSynthesizer/SpeechSynthesizer.h +++ b/CapabilityAgents/SpeechSynthesizer/include/SpeechSynthesizer/SpeechSynthesizer.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -38,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -121,7 +122,10 @@ public: void cancelDirective(std::shared_ptr info) override; - void onFocusChanged(avsCommon::avs::FocusState newFocus) override; + /// @name Overridden ChannelObserverInterface methods. + /// @{ + void onFocusChanged(avsCommon::avs::FocusState newFocus, avsCommon::avs::MixingBehavior behavior) override; + /// @} void provideState(const avsCommon::avs::NamespaceAndName& stateProviderName, const unsigned int stateRequestToken) override; @@ -130,13 +134,19 @@ public: void onContextFailure(const avsCommon::sdkInterfaces::ContextRequestError error) override; - void onPlaybackStarted(SourceId id) override; - - void onPlaybackFinished(SourceId id) override; - - void onPlaybackError(SourceId id, const avsCommon::utils::mediaPlayer::ErrorType& type, std::string error) override; - - void onPlaybackStopped(SourceId id) override; + /// @name Overridden MediaPlayerObserverInterface methods. + /// @{ + void onFirstByteRead(SourceId id, const avsCommon::utils::mediaPlayer::MediaPlayerState& state) override; + void onPlaybackStarted(SourceId id, const avsCommon::utils::mediaPlayer::MediaPlayerState& state) override; + void onPlaybackFinished(SourceId id, const avsCommon::utils::mediaPlayer::MediaPlayerState& state) override; + void onPlaybackError( + SourceId id, + const avsCommon::utils::mediaPlayer::ErrorType& type, + std::string error, + const avsCommon::utils::mediaPlayer::MediaPlayerState& state) override; + void onPlaybackStopped(SourceId id, const avsCommon::utils::mediaPlayer::MediaPlayerState& state) override; + void onBufferUnderrun(SourceId id, const avsCommon::utils::mediaPlayer::MediaPlayerState& state) override; + /// @} /// @name CapabilityConfigurationInterface Functions /// @{ @@ -319,6 +329,12 @@ private: */ void executePlaybackError(const avsCommon::utils::mediaPlayer::ErrorType& type, std::string error); + /** + * Submits a metric built using MetricEventBuilder + * @param metricEventBuilder The @c MetricEventBuilder to build the metric activity name and datapoint. + */ + void submitMetric(avsCommon::utils::metrics::MetricEventBuilder& metricEventBuilder); + /** * This function is called whenever the AVS UX dialog state of the system changes. This function will block * processing of other state changes, so any implementation of this should return quickly. diff --git a/CapabilityAgents/SpeechSynthesizer/src/SpeechSynthesizer.cpp b/CapabilityAgents/SpeechSynthesizer/src/SpeechSynthesizer.cpp index ec148c0e..c38deff8 100644 --- a/CapabilityAgents/SpeechSynthesizer/src/SpeechSynthesizer.cpp +++ b/CapabilityAgents/SpeechSynthesizer/src/SpeechSynthesizer.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -21,6 +21,8 @@ #include #include #include +#include +#include #include #include @@ -37,7 +39,9 @@ using namespace avsCommon::avs::attachment; using namespace avsCommon::sdkInterfaces; using namespace avsCommon::utils::mediaPlayer; using namespace avsCommon::utils::metrics; +using namespace avsCommon::sdkInterfaces; using namespace rapidjson; +using MediaPlayerState = avsCommon::utils::mediaPlayer::MediaPlayerState; /// SpeechSynthesizer capability constants /// SpeechSynthesizer interface type @@ -126,6 +130,18 @@ static const std::chrono::seconds STATE_CHANGE_TIMEOUT{5}; /// The component name of power resource static const std::string POWER_RESOURCE_COMPONENT_NAME{"SpeechSynthesizer"}; +/// Metric prefix for SpeechSynthesizer metric source +static const std::string SPEECH_SYNTHESIZER_METRIC_PREFIX = "SPEECH_SYNTHESIZER-"; + +/// Metric to emit when received first audio bytes +static const std::string FIRST_BYTES_AUDIO = "FIRST_BYTES_AUDIO"; + +/// Metric to emit at the start of TTS +static const std::string TTS_STARTED = "TTS_STARTED"; + +/// Metric to emit on TTS buffer underrrun +static const std::string BUFFER_UNDERRUN = "ERROR.TTS_BUFFER_UNDERRUN"; + /** * Creates the SpeechSynthesizer capability configuration. * @@ -234,7 +250,7 @@ void SpeechSynthesizer::cancelDirective(std::shared_ptr info) { m_executor.submit([this, info]() { executeCancel(info); }); } -void SpeechSynthesizer::onFocusChanged(FocusState newFocus) { +void SpeechSynthesizer::onFocusChanged(FocusState newFocus, MixingBehavior behavior) { std::unique_lock lock(m_mutex); m_currentFocus = newFocus; if (m_currentState == m_desiredState) { @@ -243,7 +259,7 @@ void SpeechSynthesizer::onFocusChanged(FocusState newFocus) { } // Set intermediate state to avoid being considered idle - ACSDK_DEBUG(LX("onFocusChanged").d("newFocus", newFocus)); + ACSDK_DEBUG(LX("onFocusChanged").d("newFocus", newFocus).d("MixingBehavior", behavior)); auto desiredState = m_desiredState; switch (newFocus) { case FocusState::FOREGROUND: @@ -315,7 +331,14 @@ void SpeechSynthesizer::onContextFailure(const ContextRequestError error) { // default no-op } -void SpeechSynthesizer::onPlaybackStarted(SourceId id) { +void SpeechSynthesizer::onFirstByteRead(SourceId id, const MediaPlayerState&) { + ACSDK_DEBUG(LX(__func__).d("id", id)); + submitMetric(MetricEventBuilder{} + .setActivityName(SPEECH_SYNTHESIZER_METRIC_PREFIX + FIRST_BYTES_AUDIO) + .addDataPoint(DataPointCounterBuilder{}.setName(FIRST_BYTES_AUDIO).increment(1).build())); +} + +void SpeechSynthesizer::onPlaybackStarted(SourceId id, const MediaPlayerState&) { ACSDK_DEBUG9(LX("onPlaybackStarted").d("callbackSourceId", id)); ACSDK_METRIC_IDS(TAG, "SpeechStarted", "", "", Metrics::Location::SPEECH_SYNTHESIZER_RECEIVE); m_executor.submit([this, id] { @@ -326,12 +349,15 @@ void SpeechSynthesizer::onPlaybackStarted(SourceId id) { .d("sourceId", m_mediaSourceId)); executePlaybackError(ErrorType::MEDIA_ERROR_INTERNAL_DEVICE_ERROR, "executePlaybackStartedFailed"); } else { + submitMetric(MetricEventBuilder{} + .setActivityName(SPEECH_SYNTHESIZER_METRIC_PREFIX + TTS_STARTED) + .addDataPoint(DataPointCounterBuilder{}.setName(TTS_STARTED).increment(1).build())); executePlaybackStarted(); } }); } -void SpeechSynthesizer::onPlaybackFinished(SourceId id) { +void SpeechSynthesizer::onPlaybackFinished(SourceId id, const MediaPlayerState&) { ACSDK_DEBUG9(LX("onPlaybackFinished").d("callbackSourceId", id)); ACSDK_METRIC_IDS(TAG, "SpeechFinished", "", "", Metrics::Location::SPEECH_SYNTHESIZER_RECEIVE); @@ -351,12 +377,13 @@ void SpeechSynthesizer::onPlaybackFinished(SourceId id) { void SpeechSynthesizer::onPlaybackError( SourceId id, const avsCommon::utils::mediaPlayer::ErrorType& type, - std::string error) { + std::string error, + const MediaPlayerState&) { ACSDK_DEBUG9(LX("onPlaybackError").d("callbackSourceId", id)); m_executor.submit([this, type, error]() { executePlaybackError(type, error); }); } -void SpeechSynthesizer::onPlaybackStopped(SourceId id) { +void SpeechSynthesizer::onPlaybackStopped(SourceId id, const MediaPlayerState&) { ACSDK_DEBUG9(LX("onPlaybackStopped").d("callbackSourceId", id)); // MediaPlayer is for some reason stopping the playback of the speech. Call setFailed if isSetFailedCalled flag is @@ -375,6 +402,13 @@ void SpeechSynthesizer::onPlaybackStopped(SourceId id) { }); } +void SpeechSynthesizer::onBufferUnderrun(SourceId id, const MediaPlayerState&) { + ACSDK_WARN(LX("onBufferUnderrun").d("callbackSourceId", id)); + submitMetric(MetricEventBuilder{} + .setActivityName(SPEECH_SYNTHESIZER_METRIC_PREFIX + BUFFER_UNDERRUN) + .addDataPoint(DataPointCounterBuilder{}.setName(BUFFER_UNDERRUN).increment(1).build())); +} + SpeechSynthesizer::SpeakDirectiveInfo::SpeakDirectiveInfo(std::shared_ptr directiveInfo) : directive{directiveInfo->directive}, result{directiveInfo->result}, @@ -630,7 +664,7 @@ void SpeechSynthesizer::executeHandleAfterValidation(std::shared_ptrdirective->getMessageId() != m_speakInfoQueue.front()->directive->getMessageId())) { ACSDK_ERROR(LX("executeHandleFailed") .d("reason", "unexpectedDirective") - .d("messageId", m_currentInfo->directive->getMessageId()) + .d("messageId", speakInfo->directive->getMessageId()) .d("expected", m_speakInfoQueue.empty() ? std::string{"empty"} : m_speakInfoQueue.front()->directive->getMessageId())); @@ -645,7 +679,10 @@ void SpeechSynthesizer::executeHandleAfterValidation(std::shared_ptracquireChannel(CHANNEL_NAME, shared_from_this(), NAMESPACE)) { + + auto activity = FocusManagerInterface::Activity::create( + NAMESPACE, shared_from_this(), std::chrono::milliseconds::zero(), avsCommon::avs::ContentType::MIXABLE); + if (!m_focusManager->acquireChannel(CHANNEL_NAME, activity)) { static const std::string message = std::string("Could not acquire ") + CHANNEL_NAME + " for " + NAMESPACE; ACSDK_ERROR(LX("executeHandleFailed") .d("reason", "CouldNotAcquireChannel") @@ -992,7 +1029,7 @@ void SpeechSynthesizer::setCurrentStateLocked(SpeechSynthesizerObserverInterface break; } for (auto observer : m_observers) { - observer->onStateChanged(m_currentState); + observer->onStateChanged(m_currentState, m_mediaSourceId, m_speechPlayer->getMediaPlayerState(m_mediaSourceId)); } } @@ -1177,20 +1214,18 @@ void SpeechSynthesizer::resetMediaSourceId() { m_mediaSourceId = MediaPlayerInterface::ERROR; } -void SpeechSynthesizer::onDialogUXStateChanged( - avsCommon::sdkInterfaces::DialogUXStateObserverInterface::DialogUXState newState) { +void SpeechSynthesizer::onDialogUXStateChanged(DialogUXStateObserverInterface::DialogUXState newState) { m_executor.submit([this, newState]() { executeOnDialogUXStateChanged(newState); }); } -void SpeechSynthesizer::executeOnDialogUXStateChanged( - avsCommon::sdkInterfaces::DialogUXStateObserverInterface::DialogUXState newState) { +void SpeechSynthesizer::executeOnDialogUXStateChanged(DialogUXStateObserverInterface::DialogUXState newState) { if (!m_initialDialogUXStateReceived) { // The initial dialog UX state change call comes from simply registering as an observer; it is not a deliberate // change to the dialog state which should interrupt a recognize event. m_initialDialogUXStateReceived = true; return; } - if (newState != avsCommon::sdkInterfaces::DialogUXStateObserverInterface::DialogUXState::IDLE) { + if (newState != DialogUXStateObserverInterface::DialogUXState::IDLE) { return; } if (m_currentFocus != avsCommon::avs::FocusState::NONE && @@ -1205,6 +1240,31 @@ std::unordered_set> Spe return m_capabilityConfigurations; } +void SpeechSynthesizer::submitMetric(MetricEventBuilder& metricEventBuilder) { + if (!m_metricRecorder) { + return; + } + + if (m_currentInfo) { + auto metricEvent = metricEventBuilder + .addDataPoint(DataPointStringBuilder{} + .setName("HTTP2_STREAM") + .setValue(m_currentInfo->directive->getAttachmentContextId()) + .build()) + .addDataPoint(DataPointStringBuilder{} + .setName("DIRECTIVE_MESSAGE_ID") + .setValue(m_currentInfo->directive->getMessageId()) + .build()) + .build(); + + if (metricEvent == nullptr) { + ACSDK_ERROR(LX("Error creating metric.")); + return; + } + recordMetric(m_metricRecorder, metricEvent); + } +} + void SpeechSynthesizer::managePowerResource(SpeechSynthesizerObserverInterface::SpeechSynthesizerState newState) { if (!m_powerResourceManager) { return; diff --git a/CapabilityAgents/SpeechSynthesizer/test/SpeechSynthesizerTest.cpp b/CapabilityAgents/SpeechSynthesizer/test/SpeechSynthesizerTest.cpp index 21f7e693..eb9efa26 100644 --- a/CapabilityAgents/SpeechSynthesizer/test/SpeechSynthesizerTest.cpp +++ b/CapabilityAgents/SpeechSynthesizer/test/SpeechSynthesizerTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -30,7 +30,7 @@ #include #include #include -#include +#include #include #include @@ -52,10 +52,14 @@ using namespace avsCommon::utils::mediaPlayer::test; using namespace avsCommon::utils::metrics; using namespace captions::test; using namespace ::testing; +using MediaPlayerState = avsCommon::utils::mediaPlayer::MediaPlayerState; using PowerResourceLevel = PowerResourceManagerInterface::PowerResourceLevel; /// Plenty of time for a test to complete. -static const std::chrono::milliseconds WAIT_TIMEOUT(1000); +static const std::chrono::milliseconds MY_WAIT_TIMEOUT(1000); + +/// Default media player state for all playback events +static const MediaPlayerState DEFAULT_MEDIA_PLAYER_STATE = {std::chrono::milliseconds(0)}; /// Time to wait for state change timeout. This should be set to be longer than STATE_CHANGE_TIMEOUT in /// SpeechSynthesizer. @@ -212,7 +216,7 @@ static SpeakTestInfo generateSpeakInfo(PlayBehavior playBehavior) { generator.addMember("format", FORMAT_TEST); generator.addMember("playBehavior", playBehaviorToString(playBehavior)); generator.addMember("token", token); - return SpeakTestInfo{.payload = generator.toString(), .messageId = (MESSAGE_ID_TEST + idStr), .token = token}; + return SpeakTestInfo{generator.toString(), (MESSAGE_ID_TEST + idStr), token}; } static std::string generatePlayingState(const SpeakTestInfo& info) { @@ -392,13 +396,13 @@ SpeechSynthesizerTest::SpeechSynthesizerTest() : } void SpeechSynthesizerTest::SetUp() { + m_metricRecorder = std::make_shared>(); m_mockContextManager = std::make_shared>(); m_mockFocusManager = std::make_shared>(); m_mockMessageSender = std::make_shared>(); m_mockExceptionSender = std::make_shared>(); m_attachmentManager = std::make_shared(AttachmentManager::AttachmentType::IN_PROCESS); m_mockSpeechPlayer = MockMediaPlayer::create(); - m_metricRecorder = nullptr; m_dialogUXStateAggregator = std::make_shared(); m_mockCaptionManager = std::make_shared>(); m_mockPowerResourceManager = std::make_shared(); @@ -490,7 +494,7 @@ TEST_F(SpeechSynthesizerTest, test_callingHandleImmediately) { std::shared_ptr directive = AVSDirective::create("", avsMessageHeader, PAYLOAD_TEST, m_attachmentManager, CONTEXT_ID_TEST); - EXPECT_CALL(*(m_mockFocusManager.get()), acquireChannel(CHANNEL_NAME, _, NAMESPACE_SPEECH_SYNTHESIZER)) + EXPECT_CALL(*(m_mockFocusManager.get()), acquireChannel(CHANNEL_NAME, _)) .Times(1) .WillOnce(InvokeWithoutArgs(this, &SpeechSynthesizerTest::wakeOnAcquireChannel)); EXPECT_CALL( @@ -501,6 +505,7 @@ TEST_F(SpeechSynthesizerTest, test_callingHandleImmediately) { EXPECT_CALL(*(m_mockSpeechPlayer.get()), getOffset(_)) .Times(1) .WillOnce(Return(OFFSET_IN_CHRONO_MILLISECONDS_TEST)); + EXPECT_CALL(*(m_mockSpeechPlayer.get()), getMediaPlayerState(_)).Times(AtLeast(2)); EXPECT_CALL( *(m_mockContextManager.get()), setState(NAMESPACE_AND_NAME_SPEECH_STATE, PLAYING_STATE_TEST, StateRefreshPolicy::ALWAYS, 0)) @@ -514,11 +519,11 @@ TEST_F(SpeechSynthesizerTest, test_callingHandleImmediately) { .Times(AtLeast(1)); m_speechSynthesizer->handleDirectiveImmediately(directive); - ASSERT_TRUE(std::future_status::ready == m_wakeAcquireChannelFuture.wait_for(WAIT_TIMEOUT)); - m_speechSynthesizer->onFocusChanged(FocusState::FOREGROUND); + ASSERT_TRUE(std::future_status::ready == m_wakeAcquireChannelFuture.wait_for(MY_WAIT_TIMEOUT)); + m_speechSynthesizer->onFocusChanged(FocusState::FOREGROUND, MixingBehavior::PRIMARY); ASSERT_TRUE(m_mockSpeechPlayer->waitUntilPlaybackStarted()); - ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(WAIT_TIMEOUT)); - ASSERT_TRUE(std::future_status::ready == m_wakeSendMessageFuture.wait_for(WAIT_TIMEOUT)); + ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(MY_WAIT_TIMEOUT)); + ASSERT_TRUE(std::future_status::ready == m_wakeSendMessageFuture.wait_for(MY_WAIT_TIMEOUT)); } /** @@ -533,7 +538,7 @@ TEST_F(SpeechSynthesizerTest, test_callingHandle) { std::shared_ptr directive = AVSDirective::create("", avsMessageHeader, PAYLOAD_TEST, m_attachmentManager, CONTEXT_ID_TEST); - EXPECT_CALL(*(m_mockFocusManager.get()), acquireChannel(CHANNEL_NAME, _, NAMESPACE_SPEECH_SYNTHESIZER)) + EXPECT_CALL(*(m_mockFocusManager.get()), acquireChannel(CHANNEL_NAME, _)) .Times(1) .WillOnce(InvokeWithoutArgs(this, &SpeechSynthesizerTest::wakeOnAcquireChannel)); EXPECT_CALL( @@ -544,6 +549,7 @@ TEST_F(SpeechSynthesizerTest, test_callingHandle) { EXPECT_CALL(*(m_mockSpeechPlayer.get()), getOffset(_)) .Times(1) .WillOnce(Return(OFFSET_IN_CHRONO_MILLISECONDS_TEST)); + EXPECT_CALL(*(m_mockSpeechPlayer.get()), getMediaPlayerState(_)).Times(AtLeast(2)); EXPECT_CALL( *(m_mockContextManager.get()), setState(NAMESPACE_AND_NAME_SPEECH_STATE, PLAYING_STATE_TEST, StateRefreshPolicy::ALWAYS, 0)) @@ -561,11 +567,11 @@ TEST_F(SpeechSynthesizerTest, test_callingHandle) { m_speechSynthesizer->CapabilityAgent::preHandleDirective(directive, std::move(m_mockDirHandlerResult)); m_speechSynthesizer->CapabilityAgent::handleDirective(MESSAGE_ID_TEST); - ASSERT_TRUE(std::future_status::ready == m_wakeAcquireChannelFuture.wait_for(WAIT_TIMEOUT)); - m_speechSynthesizer->onFocusChanged(FocusState::FOREGROUND); + ASSERT_TRUE(std::future_status::ready == m_wakeAcquireChannelFuture.wait_for(MY_WAIT_TIMEOUT)); + m_speechSynthesizer->onFocusChanged(FocusState::FOREGROUND, MixingBehavior::PRIMARY); ASSERT_TRUE(m_mockSpeechPlayer->waitUntilPlaybackStarted()); - ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(WAIT_TIMEOUT)); - ASSERT_TRUE(std::future_status::ready == m_wakeSendMessageFuture.wait_for(WAIT_TIMEOUT)); + ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(MY_WAIT_TIMEOUT)); + ASSERT_TRUE(std::future_status::ready == m_wakeSendMessageFuture.wait_for(MY_WAIT_TIMEOUT)); } /** @@ -599,7 +605,7 @@ TEST_F(SpeechSynthesizerTest, test_callingCancelAfterHandle) { std::shared_ptr directive = AVSDirective::create("", avsMessageHeader, PAYLOAD_TEST, m_attachmentManager, CONTEXT_ID_TEST); - EXPECT_CALL(*(m_mockFocusManager.get()), acquireChannel(CHANNEL_NAME, _, NAMESPACE_SPEECH_SYNTHESIZER)) + EXPECT_CALL(*(m_mockFocusManager.get()), acquireChannel(CHANNEL_NAME, _)) .Times(1) .WillOnce(InvokeWithoutArgs(this, &SpeechSynthesizerTest::wakeOnAcquireChannel)); EXPECT_CALL( @@ -608,7 +614,7 @@ TEST_F(SpeechSynthesizerTest, test_callingCancelAfterHandle) { .Times(AtLeast(1)); EXPECT_CALL(*(m_mockSpeechPlayer.get()), play(_)).Times(AtLeast(1)); EXPECT_CALL(*(m_mockSpeechPlayer.get()), getOffset(_)) - .Times(2) + .Times(AtLeast(2)) .WillRepeatedly(Return(OFFSET_IN_CHRONO_MILLISECONDS_TEST)); EXPECT_CALL( *(m_mockContextManager.get()), @@ -633,13 +639,13 @@ TEST_F(SpeechSynthesizerTest, test_callingCancelAfterHandle) { m_speechSynthesizer->CapabilityAgent::preHandleDirective(directive, std::move(m_mockDirHandlerResult)); m_speechSynthesizer->CapabilityAgent::handleDirective(MESSAGE_ID_TEST); - ASSERT_TRUE(std::future_status::ready == m_wakeAcquireChannelFuture.wait_for(WAIT_TIMEOUT)); - m_speechSynthesizer->onFocusChanged(FocusState::FOREGROUND); + ASSERT_TRUE(std::future_status::ready == m_wakeAcquireChannelFuture.wait_for(MY_WAIT_TIMEOUT)); + m_speechSynthesizer->onFocusChanged(FocusState::FOREGROUND, MixingBehavior::PRIMARY); ASSERT_TRUE(m_mockSpeechPlayer->waitUntilPlaybackStarted()); - ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(WAIT_TIMEOUT)); + ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(MY_WAIT_TIMEOUT)); m_wakeSetStatePromise = std::promise(); m_wakeSetStateFuture = m_wakeSetStatePromise.get_future(); - ASSERT_TRUE(std::future_status::ready == m_wakeSendMessageFuture.wait_for(WAIT_TIMEOUT)); + ASSERT_TRUE(std::future_status::ready == m_wakeSendMessageFuture.wait_for(MY_WAIT_TIMEOUT)); m_wakeSendMessagePromise = std::promise(); m_wakeSendMessageFuture = m_wakeSendMessagePromise.get_future(); m_speechSynthesizer->CapabilityAgent::cancelDirective(MESSAGE_ID_TEST); @@ -647,9 +653,9 @@ TEST_F(SpeechSynthesizerTest, test_callingCancelAfterHandle) { .WillOnce(InvokeWithoutArgs(this, &SpeechSynthesizerTest::wakeOnSendMessage)); EXPECT_CALL(*m_mockPowerResourceManager, releasePowerResource(COMPONENT_NAME)).Times(AtLeast(1)); ASSERT_TRUE(m_mockSpeechPlayer->waitUntilPlaybackStopped()); - ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(WAIT_TIMEOUT)); - ASSERT_TRUE(std::future_status::ready == m_wakeReleaseChannelFuture.wait_for(WAIT_TIMEOUT)); - ASSERT_TRUE(std::future_status::ready == m_wakeSendMessageFuture.wait_for(WAIT_TIMEOUT)); + ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(MY_WAIT_TIMEOUT)); + ASSERT_TRUE(std::future_status::ready == m_wakeReleaseChannelFuture.wait_for(MY_WAIT_TIMEOUT)); + ASSERT_TRUE(std::future_status::ready == m_wakeSendMessageFuture.wait_for(MY_WAIT_TIMEOUT)); } /** @@ -666,7 +672,7 @@ TEST_F(SpeechSynthesizerTest, test_callingProvideStateWhenNotPlaying) { m_speechSynthesizer->provideState(NAMESPACE_AND_NAME_SPEECH_STATE, PROVIDE_STATE_TOKEN_TEST); - ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(WAIT_TIMEOUT)); + ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(MY_WAIT_TIMEOUT)); } /** @@ -681,7 +687,7 @@ TEST_F(SpeechSynthesizerTest, test_callingProvideStateWhenPlaying) { std::shared_ptr directive = AVSDirective::create("", avsMessageHeader, PAYLOAD_TEST, m_attachmentManager, CONTEXT_ID_TEST); - EXPECT_CALL(*(m_mockFocusManager.get()), acquireChannel(CHANNEL_NAME, _, NAMESPACE_SPEECH_SYNTHESIZER)) + EXPECT_CALL(*(m_mockFocusManager.get()), acquireChannel(CHANNEL_NAME, _)) .Times(1) .WillOnce(InvokeWithoutArgs(this, &SpeechSynthesizerTest::wakeOnAcquireChannel)); EXPECT_CALL( @@ -711,15 +717,15 @@ TEST_F(SpeechSynthesizerTest, test_callingProvideStateWhenPlaying) { m_speechSynthesizer->CapabilityAgent::preHandleDirective(directive, std::move(m_mockDirHandlerResult)); m_speechSynthesizer->CapabilityAgent::handleDirective(MESSAGE_ID_TEST); - ASSERT_TRUE(std::future_status::ready == m_wakeAcquireChannelFuture.wait_for(WAIT_TIMEOUT)); - m_speechSynthesizer->onFocusChanged(FocusState::FOREGROUND); + ASSERT_TRUE(std::future_status::ready == m_wakeAcquireChannelFuture.wait_for(MY_WAIT_TIMEOUT)); + m_speechSynthesizer->onFocusChanged(FocusState::FOREGROUND, MixingBehavior::PRIMARY); ASSERT_TRUE(m_mockSpeechPlayer->waitUntilPlaybackStarted()); - ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(WAIT_TIMEOUT)); + ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(MY_WAIT_TIMEOUT)); m_wakeSetStatePromise = std::promise(); m_wakeSetStateFuture = m_wakeSetStatePromise.get_future(); - ASSERT_TRUE(std::future_status::ready == m_wakeSendMessageFuture.wait_for(WAIT_TIMEOUT)); + ASSERT_TRUE(std::future_status::ready == m_wakeSendMessageFuture.wait_for(MY_WAIT_TIMEOUT)); m_speechSynthesizer->provideState(NAMESPACE_AND_NAME_SPEECH_STATE, PROVIDE_STATE_TOKEN_TEST); - ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(WAIT_TIMEOUT)); + ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(MY_WAIT_TIMEOUT)); } /** @@ -737,7 +743,7 @@ TEST_F(SpeechSynthesizerTest, testTimer_bargeInWhilePlaying) { std::shared_ptr directive2 = AVSDirective::create("", avsMessageHeader2, PAYLOAD_TEST, m_attachmentManager, CONTEXT_ID_TEST_2); - EXPECT_CALL(*(m_mockFocusManager.get()), acquireChannel(CHANNEL_NAME, _, NAMESPACE_SPEECH_SYNTHESIZER)) + EXPECT_CALL(*(m_mockFocusManager.get()), acquireChannel(CHANNEL_NAME, _)) .Times(AtLeast(1)) .WillRepeatedly(InvokeWithoutArgs(this, &SpeechSynthesizerTest::wakeOnAcquireChannel)); EXPECT_CALL( @@ -746,7 +752,7 @@ TEST_F(SpeechSynthesizerTest, testTimer_bargeInWhilePlaying) { .Times(AtLeast(1)); EXPECT_CALL(*(m_mockSpeechPlayer.get()), play(_)).Times(AtLeast(1)); EXPECT_CALL(*(m_mockSpeechPlayer.get()), getOffset(_)) - .Times(2) + .Times(AtLeast(2)) .WillRepeatedly(Return(OFFSET_IN_CHRONO_MILLISECONDS_TEST)); EXPECT_CALL( *(m_mockContextManager.get()), @@ -775,15 +781,15 @@ TEST_F(SpeechSynthesizerTest, testTimer_bargeInWhilePlaying) { m_speechSynthesizer->CapabilityAgent::preHandleDirective(directive, std::move(m_mockDirHandlerResult)); m_speechSynthesizer->CapabilityAgent::handleDirective(MESSAGE_ID_TEST); - ASSERT_TRUE(std::future_status::ready == m_wakeAcquireChannelFuture.wait_for(WAIT_TIMEOUT)); + ASSERT_TRUE(std::future_status::ready == m_wakeAcquireChannelFuture.wait_for(MY_WAIT_TIMEOUT)); m_wakeAcquireChannelPromise = std::promise(); m_wakeAcquireChannelFuture = m_wakeAcquireChannelPromise.get_future(); - m_speechSynthesizer->onFocusChanged(FocusState::FOREGROUND); + m_speechSynthesizer->onFocusChanged(FocusState::FOREGROUND, MixingBehavior::PRIMARY); ASSERT_TRUE(m_mockSpeechPlayer->waitUntilPlaybackStarted()); - ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(WAIT_TIMEOUT)); + ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(MY_WAIT_TIMEOUT)); m_wakeSetStatePromise = std::promise(); m_wakeSetStateFuture = m_wakeSetStatePromise.get_future(); - ASSERT_TRUE(std::future_status::ready == m_wakeSendMessageFuture.wait_for(WAIT_TIMEOUT)); + ASSERT_TRUE(std::future_status::ready == m_wakeSendMessageFuture.wait_for(MY_WAIT_TIMEOUT)); m_wakeSendMessagePromise = std::promise(); m_wakeSendMessageFuture = m_wakeSendMessagePromise.get_future(); @@ -791,10 +797,10 @@ TEST_F(SpeechSynthesizerTest, testTimer_bargeInWhilePlaying) { m_speechSynthesizer->CapabilityAgent::cancelDirective(MESSAGE_ID_TEST); m_speechSynthesizer->handleDirectiveImmediately(directive2); ASSERT_TRUE(m_mockSpeechPlayer->waitUntilPlaybackStopped()); - ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(WAIT_TIMEOUT)); - ASSERT_TRUE(std::future_status::ready == m_wakeReleaseChannelFuture.wait_for(WAIT_TIMEOUT)); - ASSERT_TRUE(std::future_status::ready == m_wakeAcquireChannelFuture.wait_for(WAIT_TIMEOUT)); - ASSERT_TRUE(std::future_status::ready == m_wakeSendMessageFuture.wait_for(WAIT_TIMEOUT)); + ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(MY_WAIT_TIMEOUT)); + ASSERT_TRUE(std::future_status::ready == m_wakeReleaseChannelFuture.wait_for(MY_WAIT_TIMEOUT)); + ASSERT_TRUE(std::future_status::ready == m_wakeAcquireChannelFuture.wait_for(MY_WAIT_TIMEOUT)); + ASSERT_TRUE(std::future_status::ready == m_wakeSendMessageFuture.wait_for(MY_WAIT_TIMEOUT)); } /** @@ -817,7 +823,7 @@ TEST_F(SpeechSynthesizerTest, testTimer_notCallStopTwice) { std::shared_ptr directive2 = AVSDirective::create("", avsMessageHeader2, PAYLOAD_TEST, m_attachmentManager, CONTEXT_ID_TEST_2); - EXPECT_CALL(*(m_mockFocusManager.get()), acquireChannel(CHANNEL_NAME, _, NAMESPACE_SPEECH_SYNTHESIZER)) + EXPECT_CALL(*(m_mockFocusManager.get()), acquireChannel(CHANNEL_NAME, _)) .Times(AtLeast(1)) .WillRepeatedly(InvokeWithoutArgs(this, &SpeechSynthesizerTest::wakeOnAcquireChannel)); EXPECT_CALL( @@ -849,7 +855,7 @@ TEST_F(SpeechSynthesizerTest, testTimer_notCallStopTwice) { .Times(1) .WillOnce(Invoke([this](avsCommon::utils::mediaPlayer::MediaPlayerInterface::SourceId id) { wakeOnStopped(); - m_speechSynthesizer->onPlaybackStopped(id); + m_speechSynthesizer->onPlaybackStopped(id, DEFAULT_MEDIA_PLAYER_STATE); return true; })) .WillRepeatedly(Return(true)); @@ -860,26 +866,26 @@ TEST_F(SpeechSynthesizerTest, testTimer_notCallStopTwice) { // send Speak directive and getting focus and wait until playback started m_speechSynthesizer->CapabilityAgent::preHandleDirective(directive, std::move(m_mockDirHandlerResult)); m_speechSynthesizer->CapabilityAgent::handleDirective(MESSAGE_ID_TEST); - ASSERT_TRUE(std::future_status::ready == m_wakeAcquireChannelFuture.wait_for(WAIT_TIMEOUT)); + ASSERT_TRUE(std::future_status::ready == m_wakeAcquireChannelFuture.wait_for(MY_WAIT_TIMEOUT)); m_wakeAcquireChannelPromise = std::promise(); m_wakeAcquireChannelFuture = m_wakeAcquireChannelPromise.get_future(); - m_speechSynthesizer->onFocusChanged(FocusState::FOREGROUND); + m_speechSynthesizer->onFocusChanged(FocusState::FOREGROUND, MixingBehavior::PRIMARY); ASSERT_TRUE(m_mockSpeechPlayer->waitUntilPlaybackStarted()); - ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(WAIT_TIMEOUT)); + ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(MY_WAIT_TIMEOUT)); m_wakeSetStatePromise = std::promise(); m_wakeSetStateFuture = m_wakeSetStatePromise.get_future(); - ASSERT_TRUE(std::future_status::ready == m_wakeSendMessageFuture.wait_for(WAIT_TIMEOUT)); + ASSERT_TRUE(std::future_status::ready == m_wakeSendMessageFuture.wait_for(MY_WAIT_TIMEOUT)); m_wakeSendMessagePromise = std::promise(); m_wakeSendMessageFuture = m_wakeSendMessagePromise.get_future(); EXPECT_CALL(*m_mockPowerResourceManager, releasePowerResource(COMPONENT_NAME)).Times(AtLeast(1)); // cancel directive, this should result in calling stop() m_speechSynthesizer->CapabilityAgent::cancelDirective(MESSAGE_ID_TEST); - ASSERT_TRUE(std::future_status::ready == m_wakeStoppedFuture.wait_for(WAIT_TIMEOUT)); + ASSERT_TRUE(std::future_status::ready == m_wakeStoppedFuture.wait_for(MY_WAIT_TIMEOUT)); // goes to background, this should not result in calling the 2nd stop() - m_speechSynthesizer->onFocusChanged(FocusState::BACKGROUND); - ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(WAIT_TIMEOUT)); + m_speechSynthesizer->onFocusChanged(FocusState::BACKGROUND, MixingBehavior::MUST_PAUSE); + ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(MY_WAIT_TIMEOUT)); m_wakeSetStatePromise = std::promise(); m_wakeSetStateFuture = m_wakeSetStatePromise.get_future(); @@ -887,7 +893,7 @@ TEST_F(SpeechSynthesizerTest, testTimer_notCallStopTwice) { * onPlaybackStopped, this will result in an error with reason=nullptrDirectiveInfo. But this shouldn't break the * SpeechSynthesizer */ - ASSERT_TRUE(std::future_status::ready == m_wakeReleaseChannelFuture.wait_for(WAIT_TIMEOUT)); + ASSERT_TRUE(std::future_status::ready == m_wakeReleaseChannelFuture.wait_for(MY_WAIT_TIMEOUT)); m_wakeReleaseChannelPromise = std::promise(); m_wakeReleaseChannelFuture = m_wakeReleaseChannelPromise.get_future(); @@ -895,9 +901,9 @@ TEST_F(SpeechSynthesizerTest, testTimer_notCallStopTwice) { .Times(AtLeast(1)); // send second speak directive and make sure it working m_speechSynthesizer->handleDirectiveImmediately(directive2); - ASSERT_TRUE(std::future_status::ready == m_wakeAcquireChannelFuture.wait_for(WAIT_TIMEOUT)); - m_speechSynthesizer->onFocusChanged(FocusState::FOREGROUND); - ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(WAIT_TIMEOUT)); + ASSERT_TRUE(std::future_status::ready == m_wakeAcquireChannelFuture.wait_for(MY_WAIT_TIMEOUT)); + m_speechSynthesizer->onFocusChanged(FocusState::FOREGROUND, MixingBehavior::PRIMARY); + ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(MY_WAIT_TIMEOUT)); m_wakeSetStatePromise = std::promise(); m_wakeSetStateFuture = m_wakeSetStatePromise.get_future(); ASSERT_TRUE(m_mockSpeechPlayer->waitUntilPlaybackStarted()); @@ -919,12 +925,12 @@ TEST_F(SpeechSynthesizerTest, testSlow_callingCancelBeforeOnFocusChanged) { std::shared_ptr directive2 = AVSDirective::create("", avsMessageHeader2, PAYLOAD_TEST, m_attachmentManager, CONTEXT_ID_TEST_2); - EXPECT_CALL(*m_mockFocusManager, acquireChannel(CHANNEL_NAME, _, NAMESPACE_SPEECH_SYNTHESIZER)) + EXPECT_CALL(*m_mockFocusManager, acquireChannel(CHANNEL_NAME, _)) .WillOnce(InvokeWithoutArgs(this, &SpeechSynthesizerTest::wakeOnAcquireChannel)); m_speechSynthesizer->CapabilityAgent::preHandleDirective(directive, std::move(m_mockDirHandlerResult)); m_speechSynthesizer->CapabilityAgent::handleDirective(MESSAGE_ID_TEST); - ASSERT_TRUE(std::future_status::ready == m_wakeAcquireChannelFuture.wait_for(WAIT_TIMEOUT)); + ASSERT_TRUE(std::future_status::ready == m_wakeAcquireChannelFuture.wait_for(MY_WAIT_TIMEOUT)); m_wakeAcquireChannelPromise = std::promise(); m_wakeAcquireChannelFuture = m_wakeAcquireChannelPromise.get_future(); @@ -933,16 +939,16 @@ TEST_F(SpeechSynthesizerTest, testSlow_callingCancelBeforeOnFocusChanged) { .Times(AtLeast(1)) .WillRepeatedly(InvokeWithoutArgs(this, &SpeechSynthesizerTest::wakeOnReleaseChannel)); m_speechSynthesizer->CapabilityAgent::cancelDirective(MESSAGE_ID_TEST); - ASSERT_TRUE(std::future_status::ready == m_wakeReleaseChannelFuture.wait_for(WAIT_TIMEOUT)); + ASSERT_TRUE(std::future_status::ready == m_wakeReleaseChannelFuture.wait_for(MY_WAIT_TIMEOUT)); m_wakeReleaseChannelPromise = std::promise(); m_wakeReleaseChannelFuture = m_wakeReleaseChannelPromise.get_future(); // FocusManager might still be processing the initial acquire focus. - m_speechSynthesizer->onFocusChanged(FocusState::FOREGROUND); - m_speechSynthesizer->onFocusChanged(FocusState::NONE); + m_speechSynthesizer->onFocusChanged(FocusState::FOREGROUND, MixingBehavior::PRIMARY); + m_speechSynthesizer->onFocusChanged(FocusState::NONE, MixingBehavior::MUST_STOP); // Expect the next directive to start playing. - EXPECT_CALL(*m_mockFocusManager, acquireChannel(CHANNEL_NAME, _, NAMESPACE_SPEECH_SYNTHESIZER)) + EXPECT_CALL(*m_mockFocusManager, acquireChannel(CHANNEL_NAME, _)) .WillOnce(InvokeWithoutArgs(this, &SpeechSynthesizerTest::wakeOnAcquireChannel)); EXPECT_CALL( *m_mockContextManager, @@ -952,15 +958,15 @@ TEST_F(SpeechSynthesizerTest, testSlow_callingCancelBeforeOnFocusChanged) { *m_mockSpeechPlayer, attachmentSetSource(A>(), nullptr)); EXPECT_CALL(*m_mockSpeechPlayer, play(_)); - EXPECT_CALL(*m_mockSpeechPlayer, getOffset(_)).WillOnce(Return(OFFSET_IN_CHRONO_MILLISECONDS_TEST)); + EXPECT_CALL(*m_mockSpeechPlayer, getOffset(_)).WillRepeatedly(Return(OFFSET_IN_CHRONO_MILLISECONDS_TEST)); EXPECT_CALL(*m_mockPowerResourceManager, acquirePowerResource(COMPONENT_NAME, PowerResourceLevel::ACTIVE_HIGH)) .Times(AtLeast(1)); // send second speak directive and make sure it working m_speechSynthesizer->handleDirectiveImmediately(directive2); - ASSERT_TRUE(std::future_status::ready == m_wakeAcquireChannelFuture.wait_for(WAIT_TIMEOUT)); - m_speechSynthesizer->onFocusChanged(FocusState::FOREGROUND); - ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(WAIT_TIMEOUT)); + ASSERT_TRUE(std::future_status::ready == m_wakeAcquireChannelFuture.wait_for(MY_WAIT_TIMEOUT)); + m_speechSynthesizer->onFocusChanged(FocusState::FOREGROUND, MixingBehavior::PRIMARY); + ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(MY_WAIT_TIMEOUT)); ASSERT_TRUE(m_mockSpeechPlayer->waitUntilPlaybackStarted()); } @@ -978,21 +984,21 @@ TEST_F(SpeechSynthesizerTest, test_callingCancelBeforeOnExecuteStateChanged) { std::shared_ptr directive2 = AVSDirective::create("", avsMessageHeader2, PAYLOAD_TEST, m_attachmentManager, CONTEXT_ID_TEST_2); - EXPECT_CALL(*m_mockFocusManager, acquireChannel(CHANNEL_NAME, _, NAMESPACE_SPEECH_SYNTHESIZER)) + EXPECT_CALL(*m_mockFocusManager, acquireChannel(CHANNEL_NAME, _)) .WillOnce(InvokeWithoutArgs(this, &SpeechSynthesizerTest::wakeOnAcquireChannel)); m_speechSynthesizer->CapabilityAgent::preHandleDirective(directive, std::move(m_mockDirHandlerResult)); m_speechSynthesizer->CapabilityAgent::handleDirective(MESSAGE_ID_TEST); - ASSERT_TRUE(std::future_status::ready == m_wakeAcquireChannelFuture.wait_for(WAIT_TIMEOUT)); + ASSERT_TRUE(std::future_status::ready == m_wakeAcquireChannelFuture.wait_for(MY_WAIT_TIMEOUT)); m_wakeAcquireChannelPromise = std::promise(); m_wakeAcquireChannelFuture = m_wakeAcquireChannelPromise.get_future(); m_speechSynthesizer->CapabilityAgent::cancelDirective(MESSAGE_ID_TEST); - m_speechSynthesizer->onFocusChanged(FocusState::FOREGROUND); + m_speechSynthesizer->onFocusChanged(FocusState::FOREGROUND, MixingBehavior::PRIMARY); // Expect the next directive to start playing. - EXPECT_CALL(*m_mockFocusManager, acquireChannel(CHANNEL_NAME, _, NAMESPACE_SPEECH_SYNTHESIZER)) + EXPECT_CALL(*m_mockFocusManager, acquireChannel(CHANNEL_NAME, _)) .WillOnce(InvokeWithoutArgs(this, &SpeechSynthesizerTest::wakeOnAcquireChannel)); EXPECT_CALL( *m_mockContextManager, @@ -1002,15 +1008,15 @@ TEST_F(SpeechSynthesizerTest, test_callingCancelBeforeOnExecuteStateChanged) { *m_mockSpeechPlayer, attachmentSetSource(A>(), nullptr)); EXPECT_CALL(*m_mockSpeechPlayer, play(_)); - EXPECT_CALL(*m_mockSpeechPlayer, getOffset(_)).WillOnce(Return(OFFSET_IN_CHRONO_MILLISECONDS_TEST)); + EXPECT_CALL(*m_mockSpeechPlayer, getOffset(_)).WillRepeatedly(Return(OFFSET_IN_CHRONO_MILLISECONDS_TEST)); EXPECT_CALL(*m_mockPowerResourceManager, acquirePowerResource(COMPONENT_NAME, PowerResourceLevel::ACTIVE_HIGH)) .Times(AtLeast(1)); // send second speak directive and make sure it working m_speechSynthesizer->handleDirectiveImmediately(directive2); - ASSERT_TRUE(std::future_status::ready == m_wakeAcquireChannelFuture.wait_for(WAIT_TIMEOUT)); - m_speechSynthesizer->onFocusChanged(FocusState::FOREGROUND); - ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(WAIT_TIMEOUT)); + ASSERT_TRUE(std::future_status::ready == m_wakeAcquireChannelFuture.wait_for(MY_WAIT_TIMEOUT)); + m_speechSynthesizer->onFocusChanged(FocusState::FOREGROUND, MixingBehavior::PRIMARY); + ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(MY_WAIT_TIMEOUT)); ASSERT_TRUE(m_mockSpeechPlayer->waitUntilPlaybackStarted()); } @@ -1033,7 +1039,7 @@ TEST_F(SpeechSynthesizerTest, test_mediaPlayerFailedToStop) { std::shared_ptr directive2 = AVSDirective::create("", avsMessageHeader2, PAYLOAD_TEST, m_attachmentManager, CONTEXT_ID_TEST_2); - EXPECT_CALL(*(m_mockFocusManager.get()), acquireChannel(CHANNEL_NAME, _, NAMESPACE_SPEECH_SYNTHESIZER)) + EXPECT_CALL(*(m_mockFocusManager.get()), acquireChannel(CHANNEL_NAME, _)) .Times(AtLeast(1)) .WillRepeatedly(InvokeWithoutArgs(this, &SpeechSynthesizerTest::wakeOnAcquireChannel)); EXPECT_CALL( @@ -1072,27 +1078,27 @@ TEST_F(SpeechSynthesizerTest, test_mediaPlayerFailedToStop) { // send Speak directive and getting focus and wait until playback started m_speechSynthesizer->CapabilityAgent::preHandleDirective(directive, std::move(m_mockDirHandlerResult)); m_speechSynthesizer->CapabilityAgent::handleDirective(MESSAGE_ID_TEST); - ASSERT_TRUE(std::future_status::ready == m_wakeAcquireChannelFuture.wait_for(WAIT_TIMEOUT)); + ASSERT_TRUE(std::future_status::ready == m_wakeAcquireChannelFuture.wait_for(MY_WAIT_TIMEOUT)); m_wakeAcquireChannelPromise = std::promise(); m_wakeAcquireChannelFuture = m_wakeAcquireChannelPromise.get_future(); - m_speechSynthesizer->onFocusChanged(FocusState::FOREGROUND); + m_speechSynthesizer->onFocusChanged(FocusState::FOREGROUND, MixingBehavior::PRIMARY); ASSERT_TRUE(m_mockSpeechPlayer->waitUntilPlaybackStarted()); - ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(WAIT_TIMEOUT)); + ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(MY_WAIT_TIMEOUT)); m_wakeSetStatePromise = std::promise(); m_wakeSetStateFuture = m_wakeSetStatePromise.get_future(); - ASSERT_TRUE(std::future_status::ready == m_wakeSendMessageFuture.wait_for(WAIT_TIMEOUT)); + ASSERT_TRUE(std::future_status::ready == m_wakeSendMessageFuture.wait_for(MY_WAIT_TIMEOUT)); m_wakeSendMessagePromise = std::promise(); m_wakeSendMessageFuture = m_wakeSendMessagePromise.get_future(); EXPECT_CALL(*m_mockPowerResourceManager, releasePowerResource(COMPONENT_NAME)).Times(AtLeast(1)); // cancel directive, this should result in calling stop() m_speechSynthesizer->CapabilityAgent::cancelDirective(MESSAGE_ID_TEST); - ASSERT_TRUE(std::future_status::ready == m_wakeStoppedFuture.wait_for(WAIT_TIMEOUT)); + ASSERT_TRUE(std::future_status::ready == m_wakeStoppedFuture.wait_for(MY_WAIT_TIMEOUT)); // goes to background, this should not result in calling the 2nd stop() - ASSERT_TRUE(std::future_status::ready == m_wakeReleaseChannelFuture.wait_for(WAIT_TIMEOUT)); - m_speechSynthesizer->onFocusChanged(FocusState::BACKGROUND); - ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(WAIT_TIMEOUT)); + ASSERT_TRUE(std::future_status::ready == m_wakeReleaseChannelFuture.wait_for(MY_WAIT_TIMEOUT)); + m_speechSynthesizer->onFocusChanged(FocusState::BACKGROUND, MixingBehavior::MUST_PAUSE); + ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(MY_WAIT_TIMEOUT)); m_wakeSetStatePromise = std::promise(); m_wakeSetStateFuture = m_wakeSetStatePromise.get_future(); @@ -1100,7 +1106,7 @@ TEST_F(SpeechSynthesizerTest, test_mediaPlayerFailedToStop) { * onPlaybackStopped, this will result in an error with reason=nullptrDirectiveInfo. But this shouldn't break the * SpeechSynthesizer */ - ASSERT_TRUE(std::future_status::ready == m_wakeReleaseChannelFuture.wait_for(WAIT_TIMEOUT)); + ASSERT_TRUE(std::future_status::ready == m_wakeReleaseChannelFuture.wait_for(MY_WAIT_TIMEOUT)); m_wakeReleaseChannelPromise = std::promise(); m_wakeReleaseChannelFuture = m_wakeReleaseChannelPromise.get_future(); @@ -1108,9 +1114,9 @@ TEST_F(SpeechSynthesizerTest, test_mediaPlayerFailedToStop) { .Times(AtLeast(1)); // send second speak directive and make sure it working m_speechSynthesizer->handleDirectiveImmediately(directive2); - ASSERT_TRUE(std::future_status::ready == m_wakeAcquireChannelFuture.wait_for(WAIT_TIMEOUT)); - m_speechSynthesizer->onFocusChanged(FocusState::FOREGROUND); - ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(WAIT_TIMEOUT)); + ASSERT_TRUE(std::future_status::ready == m_wakeAcquireChannelFuture.wait_for(MY_WAIT_TIMEOUT)); + m_speechSynthesizer->onFocusChanged(FocusState::FOREGROUND, MixingBehavior::PRIMARY); + ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(MY_WAIT_TIMEOUT)); m_wakeSetStatePromise = std::promise(); m_wakeSetStateFuture = m_wakeSetStatePromise.get_future(); ASSERT_TRUE(m_mockSpeechPlayer->waitUntilPlaybackStarted()); @@ -1137,7 +1143,7 @@ TEST_F(SpeechSynthesizerTest, testTimer_mediaPlayerAlwaysFailToStop) { std::shared_ptr directive = AVSDirective::create("", avsMessageHeader, PAYLOAD_TEST, m_attachmentManager, CONTEXT_ID_TEST); - EXPECT_CALL(*m_mockFocusManager, acquireChannel(_, _, _)) + EXPECT_CALL(*m_mockFocusManager, acquireChannel(_, _)) .Times(AtLeast(1)) .WillRepeatedly(InvokeWithoutArgs(this, &SpeechSynthesizerTest::wakeOnAcquireChannel)); EXPECT_CALL(*m_mockSpeechPlayer, attachmentSetSource(_, _)).Times(AtLeast(1)); @@ -1152,8 +1158,8 @@ TEST_F(SpeechSynthesizerTest, testTimer_mediaPlayerAlwaysFailToStop) { // send Speak directive and getting focus and wait until playback started speechSynthesizer->CapabilityAgent::preHandleDirective(directive, std::move(m_mockDirHandlerResult)); speechSynthesizer->CapabilityAgent::handleDirective(MESSAGE_ID_TEST); - EXPECT_TRUE(std::future_status::ready == m_wakeAcquireChannelFuture.wait_for(WAIT_TIMEOUT)); - speechSynthesizer->onFocusChanged(FocusState::FOREGROUND); + EXPECT_TRUE(std::future_status::ready == m_wakeAcquireChannelFuture.wait_for(MY_WAIT_TIMEOUT)); + speechSynthesizer->onFocusChanged(FocusState::FOREGROUND, MixingBehavior::PRIMARY); ASSERT_TRUE(m_mockSpeechPlayer->waitUntilPlaybackStarted()); speechSynthesizer->shutdown(); @@ -1170,7 +1176,7 @@ TEST_F(SpeechSynthesizerTest, testSlow_setStateTimeout) { std::shared_ptr directive = AVSDirective::create("", avsMessageHeader, PAYLOAD_TEST, m_attachmentManager, CONTEXT_ID_TEST); - EXPECT_CALL(*(m_mockFocusManager.get()), acquireChannel(CHANNEL_NAME, _, NAMESPACE_SPEECH_SYNTHESIZER)) + EXPECT_CALL(*(m_mockFocusManager.get()), acquireChannel(CHANNEL_NAME, _)) .Times(AtLeast(1)) .WillRepeatedly(InvokeWithoutArgs(this, &SpeechSynthesizerTest::wakeOnAcquireChannel)); EXPECT_CALL( @@ -1207,26 +1213,26 @@ TEST_F(SpeechSynthesizerTest, testSlow_setStateTimeout) { // Send Speak directive and getting focus and wait until state change timeout. m_speechSynthesizer->CapabilityAgent::preHandleDirective(directive, std::move(m_mockDirHandlerResult)); m_speechSynthesizer->CapabilityAgent::handleDirective(MESSAGE_ID_TEST); - ASSERT_TRUE(std::future_status::ready == m_wakeAcquireChannelFuture.wait_for(WAIT_TIMEOUT)); + ASSERT_TRUE(std::future_status::ready == m_wakeAcquireChannelFuture.wait_for(MY_WAIT_TIMEOUT)); m_wakeAcquireChannelPromise = std::promise(); m_wakeAcquireChannelFuture = m_wakeAcquireChannelPromise.get_future(); - m_speechSynthesizer->onFocusChanged(FocusState::FOREGROUND); + m_speechSynthesizer->onFocusChanged(FocusState::FOREGROUND, MixingBehavior::PRIMARY); ASSERT_TRUE(std::future_status::ready == m_wakeSetFailedFuture.wait_for(STATE_CHANGE_TIMEOUT)); // Upon getting onPlaybackedStarted, expect state to be updated, but SpeechStarted event will not be sent. - m_speechSynthesizer->onPlaybackStarted(m_mockSpeechPlayer->getCurrentSourceId()); - ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(WAIT_TIMEOUT)); + m_speechSynthesizer->onPlaybackStarted(m_mockSpeechPlayer->getCurrentSourceId(), DEFAULT_MEDIA_PLAYER_STATE); + ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(MY_WAIT_TIMEOUT)); m_wakeSetStatePromise = std::promise(); m_wakeSetStateFuture = m_wakeSetStatePromise.get_future(); // Upon getting onPlaybackStopped, expect state to be updated, but SpeechFinished event will not be sent. - m_speechSynthesizer->onPlaybackStopped(m_mockSpeechPlayer->getCurrentSourceId()); - ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(WAIT_TIMEOUT)); + m_speechSynthesizer->onPlaybackStopped(m_mockSpeechPlayer->getCurrentSourceId(), DEFAULT_MEDIA_PLAYER_STATE); + ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(MY_WAIT_TIMEOUT)); m_wakeSetStatePromise = std::promise(); m_wakeSetStateFuture = m_wakeSetStatePromise.get_future(); - ASSERT_TRUE(std::future_status::ready == m_wakeReleaseChannelFuture.wait_for(WAIT_TIMEOUT)); - m_speechSynthesizer->onFocusChanged(FocusState::BACKGROUND); + ASSERT_TRUE(std::future_status::ready == m_wakeReleaseChannelFuture.wait_for(MY_WAIT_TIMEOUT)); + m_speechSynthesizer->onFocusChanged(FocusState::BACKGROUND, MixingBehavior::MUST_PAUSE); } /** @@ -1239,7 +1245,7 @@ TEST_F(SpeechSynthesizerTest, test_givenPlayingStateFocusBecomesNone) { std::shared_ptr directive = AVSDirective::create("", avsMessageHeader, PAYLOAD_TEST, m_attachmentManager, CONTEXT_ID_TEST); - EXPECT_CALL(*(m_mockFocusManager.get()), acquireChannel(CHANNEL_NAME, _, NAMESPACE_SPEECH_SYNTHESIZER)) + EXPECT_CALL(*(m_mockFocusManager.get()), acquireChannel(CHANNEL_NAME, _)) .Times(1) .WillOnce(InvokeWithoutArgs(this, &SpeechSynthesizerTest::wakeOnAcquireChannel)); EXPECT_CALL( @@ -1248,7 +1254,7 @@ TEST_F(SpeechSynthesizerTest, test_givenPlayingStateFocusBecomesNone) { .Times(AtLeast(1)); EXPECT_CALL(*(m_mockSpeechPlayer.get()), play(_)).Times(AtLeast(1)); EXPECT_CALL(*(m_mockSpeechPlayer.get()), getOffset(_)) - .Times(2) + .Times(AtLeast(2)) .WillRepeatedly(Return(OFFSET_IN_CHRONO_MILLISECONDS_TEST)); EXPECT_CALL(*(m_mockDirHandlerResult.get()), setFailed(_)) .Times(1) @@ -1259,11 +1265,11 @@ TEST_F(SpeechSynthesizerTest, test_givenPlayingStateFocusBecomesNone) { m_speechSynthesizer->CapabilityAgent::preHandleDirective(directive, std::move(m_mockDirHandlerResult)); m_speechSynthesizer->CapabilityAgent::handleDirective(MESSAGE_ID_TEST); - EXPECT_TRUE(std::future_status::ready == m_wakeAcquireChannelFuture.wait_for(WAIT_TIMEOUT)); - m_speechSynthesizer->onFocusChanged(FocusState::FOREGROUND); + EXPECT_TRUE(std::future_status::ready == m_wakeAcquireChannelFuture.wait_for(MY_WAIT_TIMEOUT)); + m_speechSynthesizer->onFocusChanged(FocusState::FOREGROUND, MixingBehavior::PRIMARY); EXPECT_TRUE(m_mockSpeechPlayer->waitUntilPlaybackStarted()); EXPECT_CALL(*m_mockPowerResourceManager, releasePowerResource(COMPONENT_NAME)).Times(AtLeast(1)); - m_speechSynthesizer->onFocusChanged(FocusState::NONE); + m_speechSynthesizer->onFocusChanged(FocusState::NONE, MixingBehavior::MUST_STOP); EXPECT_TRUE(std::future_status::ready == m_wakeSetFailedFuture.wait_for(STATE_CHANGE_TIMEOUT)); } @@ -1277,7 +1283,7 @@ TEST_F(SpeechSynthesizerTest, testTimer_onPlayedStopped) { std::shared_ptr directive = AVSDirective::create("", avsMessageHeader, PAYLOAD_TEST, m_attachmentManager, CONTEXT_ID_TEST); - EXPECT_CALL(*(m_mockFocusManager.get()), acquireChannel(CHANNEL_NAME, _, NAMESPACE_SPEECH_SYNTHESIZER)) + EXPECT_CALL(*(m_mockFocusManager.get()), acquireChannel(CHANNEL_NAME, _)) .Times(1) .WillOnce(InvokeWithoutArgs(this, &SpeechSynthesizerTest::wakeOnAcquireChannel)); EXPECT_CALL( @@ -1285,7 +1291,7 @@ TEST_F(SpeechSynthesizerTest, testTimer_onPlayedStopped) { attachmentSetSource(A>(), nullptr)) .Times(AtLeast(1)); EXPECT_CALL(*(m_mockSpeechPlayer.get()), play(_)).Times(AtLeast(1)); - EXPECT_CALL(*(m_mockSpeechPlayer.get()), getOffset(_)).WillOnce(Return(OFFSET_IN_CHRONO_MILLISECONDS_TEST)); + EXPECT_CALL(*(m_mockSpeechPlayer.get()), getOffset(_)).WillRepeatedly(Return(OFFSET_IN_CHRONO_MILLISECONDS_TEST)); EXPECT_CALL(*(m_mockDirHandlerResult.get()), setFailed(_)) .Times(1) .WillOnce(InvokeWithoutArgs(this, &SpeechSynthesizerTest::wakeOnSetFailed)); @@ -1295,11 +1301,11 @@ TEST_F(SpeechSynthesizerTest, testTimer_onPlayedStopped) { m_speechSynthesizer->CapabilityAgent::preHandleDirective(directive, std::move(m_mockDirHandlerResult)); m_speechSynthesizer->CapabilityAgent::handleDirective(MESSAGE_ID_TEST); - EXPECT_TRUE(std::future_status::ready == m_wakeAcquireChannelFuture.wait_for(WAIT_TIMEOUT)); - m_speechSynthesizer->onFocusChanged(FocusState::FOREGROUND); + EXPECT_TRUE(std::future_status::ready == m_wakeAcquireChannelFuture.wait_for(MY_WAIT_TIMEOUT)); + m_speechSynthesizer->onFocusChanged(FocusState::FOREGROUND, MixingBehavior::PRIMARY); EXPECT_TRUE(m_mockSpeechPlayer->waitUntilPlaybackStarted()); EXPECT_CALL(*m_mockPowerResourceManager, releasePowerResource(COMPONENT_NAME)).Times(AtLeast(1)); - m_speechSynthesizer->onPlaybackStopped(m_mockSpeechPlayer->getCurrentSourceId()); + m_speechSynthesizer->onPlaybackStopped(m_mockSpeechPlayer->getCurrentSourceId(), DEFAULT_MEDIA_PLAYER_STATE); EXPECT_TRUE(std::future_status::ready == m_wakeSetFailedFuture.wait_for(STATE_CHANGE_TIMEOUT)); } @@ -1311,13 +1317,13 @@ bool SpeechSynthesizerTest::setupActiveSpeech( std::shared_ptr directive = AVSDirective::create("", avsMessageHeader, info.payload, m_attachmentManager, CONTEXT_ID_TEST); - EXPECT_CALL(*m_mockFocusManager, acquireChannel(CHANNEL_NAME, _, NAMESPACE_SPEECH_SYNTHESIZER)) + EXPECT_CALL(*m_mockFocusManager, acquireChannel(CHANNEL_NAME, _)) .WillOnce(InvokeWithoutArgs(this, &SpeechSynthesizerTest::wakeOnAcquireChannel)); EXPECT_CALL( *m_mockSpeechPlayer, attachmentSetSource(A>(), nullptr)); EXPECT_CALL(*m_mockSpeechPlayer, play(_)).Times(AtLeast(1)); - EXPECT_CALL(*m_mockSpeechPlayer, getOffset(_)).WillOnce(Return(OFFSET_IN_CHRONO_MILLISECONDS_TEST)); + EXPECT_CALL(*m_mockSpeechPlayer, getOffset(_)).WillRepeatedly(Return(OFFSET_IN_CHRONO_MILLISECONDS_TEST)); EXPECT_CALL( *m_mockContextManager, setState(NAMESPACE_AND_NAME_SPEECH_STATE, generatePlayingState(info), StateRefreshPolicy::ALWAYS, 0)) @@ -1330,11 +1336,11 @@ bool SpeechSynthesizerTest::setupActiveSpeech( m_speechSynthesizer->CapabilityAgent::preHandleDirective(directive, std::move(resultHandler)); m_speechSynthesizer->CapabilityAgent::handleDirective(info.messageId); - EXPECT_TRUE(std::future_status::ready == m_wakeAcquireChannelFuture.wait_for(WAIT_TIMEOUT)); - m_speechSynthesizer->onFocusChanged(FocusState::FOREGROUND); + EXPECT_TRUE(std::future_status::ready == m_wakeAcquireChannelFuture.wait_for(MY_WAIT_TIMEOUT)); + m_speechSynthesizer->onFocusChanged(FocusState::FOREGROUND, MixingBehavior::PRIMARY); EXPECT_TRUE(m_mockSpeechPlayer->waitUntilPlaybackStarted()); - EXPECT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(WAIT_TIMEOUT)); - EXPECT_TRUE(std::future_status::ready == m_wakeSendMessageFuture.wait_for(WAIT_TIMEOUT)); + EXPECT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(MY_WAIT_TIMEOUT)); + EXPECT_TRUE(std::future_status::ready == m_wakeSendMessageFuture.wait_for(MY_WAIT_TIMEOUT)); // Reset promises. m_wakeSendMessagePromise = std::promise(); @@ -1354,7 +1360,7 @@ bool SpeechSynthesizerTest::setupPendingSpeech( NAMESPACE_SPEECH_SYNTHESIZER, NAME_SPEAK, info.messageId, DIALOG_REQUEST_ID_TEST); std::shared_ptr directive = AVSDirective::create("", avsMessageHeader, info.payload, m_attachmentManager, CONTEXT_ID_TEST); - EXPECT_CALL(*m_mockFocusManager, acquireChannel(CHANNEL_NAME, _, NAMESPACE_SPEECH_SYNTHESIZER)) + EXPECT_CALL(*m_mockFocusManager, acquireChannel(CHANNEL_NAME, _)) .WillOnce(InvokeWithoutArgs(this, &SpeechSynthesizerTest::wakeOnAcquireChannel)); EXPECT_CALL(*m_mockPowerResourceManager, acquirePowerResource(COMPONENT_NAME, PowerResourceLevel::ACTIVE_HIGH)) .Times(AtLeast(1)); @@ -1363,7 +1369,7 @@ bool SpeechSynthesizerTest::setupPendingSpeech( m_speechSynthesizer->CapabilityAgent::preHandleDirective(directive, std::move(resultHandler)); m_speechSynthesizer->CapabilityAgent::handleDirective(info.messageId); - EXPECT_TRUE(std::future_status::ready == m_wakeAcquireChannelFuture.wait_for(WAIT_TIMEOUT)); + EXPECT_TRUE(std::future_status::ready == m_wakeAcquireChannelFuture.wait_for(MY_WAIT_TIMEOUT)); m_wakeAcquireChannelPromise = std::promise(); m_wakeAcquireChannelFuture = m_wakeAcquireChannelPromise.get_future(); return !Test::HasFailure(); @@ -1389,9 +1395,9 @@ TEST_F(SpeechSynthesizerTest, test_replaceAllWithEmptyQueue) { .WillOnce(InvokeWithoutArgs(this, &SpeechSynthesizerTest::wakeOnSendMessage)); m_mockSpeechPlayer->mockFinished(m_mockSpeechPlayer->getCurrentSourceId()); - EXPECT_TRUE(std::future_status::ready == m_wakeSendMessageFuture.wait_for(WAIT_TIMEOUT)); - EXPECT_TRUE(std::future_status::ready == m_wakeSetCompletedFuture.wait_for(WAIT_TIMEOUT)); - EXPECT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(WAIT_TIMEOUT)); + EXPECT_TRUE(std::future_status::ready == m_wakeSendMessageFuture.wait_for(MY_WAIT_TIMEOUT)); + EXPECT_TRUE(std::future_status::ready == m_wakeSetCompletedFuture.wait_for(MY_WAIT_TIMEOUT)); + EXPECT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(MY_WAIT_TIMEOUT)); } /** @@ -1418,13 +1424,13 @@ TEST_F(SpeechSynthesizerTest, test_replaceAllWithNonEmptyQueue) { { SCOPED_TRACE("Setup Expectations"); - EXPECT_CALL(*m_mockFocusManager, acquireChannel(CHANNEL_NAME, _, NAMESPACE_SPEECH_SYNTHESIZER)) + EXPECT_CALL(*m_mockFocusManager, acquireChannel(CHANNEL_NAME, _)) .WillOnce(InvokeWithoutArgs(this, &SpeechSynthesizerTest::wakeOnAcquireChannel)); EXPECT_CALL( *m_mockSpeechPlayer, attachmentSetSource(A>(), nullptr)); EXPECT_CALL(*m_mockSpeechPlayer, play(_)).Times(AtLeast(1)); - EXPECT_CALL(*m_mockSpeechPlayer, getOffset(_)).WillOnce(Return(OFFSET_IN_CHRONO_MILLISECONDS_TEST)); + EXPECT_CALL(*m_mockSpeechPlayer, getOffset(_)).WillRepeatedly(Return(OFFSET_IN_CHRONO_MILLISECONDS_TEST)); EXPECT_CALL( *m_mockContextManager, setState(NAMESPACE_AND_NAME_SPEECH_STATE, generatePlayingState(speak), StateRefreshPolicy::ALWAYS, 0)) @@ -1437,15 +1443,15 @@ TEST_F(SpeechSynthesizerTest, test_replaceAllWithNonEmptyQueue) { SCOPED_TRACE("Test Directive Handling"); m_speechSynthesizer->CapabilityAgent::preHandleDirective(directive, std::move(mockResultHandler)); m_speechSynthesizer->CapabilityAgent::handleDirective(speak.messageId); - EXPECT_TRUE(std::future_status::ready == m_wakeAcquireChannelFuture.wait_for(WAIT_TIMEOUT)); + EXPECT_TRUE(std::future_status::ready == m_wakeAcquireChannelFuture.wait_for(MY_WAIT_TIMEOUT)); } { SCOPED_TRACE("Check Speech Playback"); - m_speechSynthesizer->onFocusChanged(FocusState::FOREGROUND); + m_speechSynthesizer->onFocusChanged(FocusState::FOREGROUND, MixingBehavior::PRIMARY); EXPECT_TRUE(m_mockSpeechPlayer->waitUntilPlaybackStarted()); - EXPECT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(WAIT_TIMEOUT)); - EXPECT_TRUE(std::future_status::ready == m_wakeSendMessageFuture.wait_for(WAIT_TIMEOUT)); + EXPECT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(MY_WAIT_TIMEOUT)); + EXPECT_TRUE(std::future_status::ready == m_wakeSendMessageFuture.wait_for(MY_WAIT_TIMEOUT)); m_wakeSendMessagePromise = std::promise(); m_wakeSendMessageFuture = m_wakeSendMessagePromise.get_future(); m_wakeSetStatePromise = std::promise(); @@ -1462,9 +1468,9 @@ TEST_F(SpeechSynthesizerTest, test_replaceAllWithNonEmptyQueue) { .WillOnce(InvokeWithoutArgs(this, &SpeechSynthesizerTest::wakeOnSendMessage)); m_mockSpeechPlayer->mockFinished(m_mockSpeechPlayer->getCurrentSourceId()); - EXPECT_TRUE(std::future_status::ready == m_wakeSendMessageFuture.wait_for(WAIT_TIMEOUT)); - EXPECT_TRUE(std::future_status::ready == m_wakeSetCompletedFuture.wait_for(WAIT_TIMEOUT)); - EXPECT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(WAIT_TIMEOUT)); + EXPECT_TRUE(std::future_status::ready == m_wakeSendMessageFuture.wait_for(MY_WAIT_TIMEOUT)); + EXPECT_TRUE(std::future_status::ready == m_wakeSetCompletedFuture.wait_for(MY_WAIT_TIMEOUT)); + EXPECT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(MY_WAIT_TIMEOUT)); } } @@ -1501,14 +1507,14 @@ TEST_F(SpeechSynthesizerTest, test_replaceAllStopActiveSpeech) { EXPECT_CALL(*m_mockMessageSender, sendMessage(IsInterruptedEvent())); // New directive handling. - EXPECT_CALL(*m_mockFocusManager, acquireChannel(CHANNEL_NAME, _, NAMESPACE_SPEECH_SYNTHESIZER)) + EXPECT_CALL(*m_mockFocusManager, acquireChannel(CHANNEL_NAME, _)) .WillOnce(InvokeWithoutArgs(this, &SpeechSynthesizerTest::wakeOnAcquireChannel)); EXPECT_CALL( *m_mockSpeechPlayer, attachmentSetSource(A>(), nullptr)); EXPECT_CALL(*m_mockSpeechPlayer, play(_)).Times(AtLeast(1)); EXPECT_CALL(*m_mockSpeechPlayer, getOffset(_)) - .Times(2) + .Times(AtLeast(2)) .WillRepeatedly(Return(OFFSET_IN_CHRONO_MILLISECONDS_TEST)); EXPECT_CALL( *m_mockContextManager, @@ -1524,16 +1530,16 @@ TEST_F(SpeechSynthesizerTest, test_replaceAllStopActiveSpeech) { SCOPED_TRACE("Test Directive Handling"); m_speechSynthesizer->CapabilityAgent::preHandleDirective(directive, std::move(mockResultHandler)); m_speechSynthesizer->CapabilityAgent::handleDirective(speak.messageId); - EXPECT_TRUE(std::future_status::ready == m_wakeAcquireChannelFuture.wait_for(WAIT_TIMEOUT)); + EXPECT_TRUE(std::future_status::ready == m_wakeAcquireChannelFuture.wait_for(MY_WAIT_TIMEOUT)); } { SCOPED_TRACE("Check Speech Playback"); - m_speechSynthesizer->onFocusChanged(FocusState::NONE); - m_speechSynthesizer->onFocusChanged(FocusState::FOREGROUND); + m_speechSynthesizer->onFocusChanged(FocusState::NONE, MixingBehavior::MUST_STOP); + m_speechSynthesizer->onFocusChanged(FocusState::FOREGROUND, MixingBehavior::PRIMARY); EXPECT_TRUE(m_mockSpeechPlayer->waitUntilPlaybackStarted()); - EXPECT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(WAIT_TIMEOUT)); - EXPECT_TRUE(std::future_status::ready == m_wakeSendMessageFuture.wait_for(WAIT_TIMEOUT)); + EXPECT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(MY_WAIT_TIMEOUT)); + EXPECT_TRUE(std::future_status::ready == m_wakeSendMessageFuture.wait_for(MY_WAIT_TIMEOUT)); m_wakeSendMessagePromise = std::promise(); m_wakeSendMessageFuture = m_wakeSendMessagePromise.get_future(); m_wakeSetStatePromise = std::promise(); @@ -1551,8 +1557,8 @@ TEST_F(SpeechSynthesizerTest, test_replaceAllStopActiveSpeech) { EXPECT_CALL(*m_mockPowerResourceManager, releasePowerResource(COMPONENT_NAME)).Times(AtLeast(1)); m_mockSpeechPlayer->mockFinished(m_mockSpeechPlayer->getCurrentSourceId()); - EXPECT_TRUE(std::future_status::ready == m_wakeSendMessageFuture.wait_for(WAIT_TIMEOUT)); - EXPECT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(WAIT_TIMEOUT)); + EXPECT_TRUE(std::future_status::ready == m_wakeSendMessageFuture.wait_for(MY_WAIT_TIMEOUT)); + EXPECT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(MY_WAIT_TIMEOUT)); } } @@ -1591,7 +1597,7 @@ TEST_F(SpeechSynthesizerTest, test_enqueueWithActiveSpeech) { EXPECT_CALL(*m_mockMessageSender, sendMessage(IsFinishedEvent())) .WillOnce(InvokeWithoutArgs(this, &SpeechSynthesizerTest::wakeOnSendMessage)); m_mockSpeechPlayer->mockFinished(m_mockSpeechPlayer->getCurrentSourceId()); - EXPECT_TRUE(std::future_status::ready == m_wakeSendMessageFuture.wait_for(WAIT_TIMEOUT)); + EXPECT_TRUE(std::future_status::ready == m_wakeSendMessageFuture.wait_for(MY_WAIT_TIMEOUT)); } // Reset promises. @@ -1601,16 +1607,16 @@ TEST_F(SpeechSynthesizerTest, test_enqueueWithActiveSpeech) { { // Start new speech speech. SCOPED_TRACE("Start Second"); - EXPECT_CALL(*m_mockFocusManager, acquireChannel(CHANNEL_NAME, _, NAMESPACE_SPEECH_SYNTHESIZER)) + EXPECT_CALL(*m_mockFocusManager, acquireChannel(CHANNEL_NAME, _)) .WillOnce(InvokeWithoutArgs(this, &SpeechSynthesizerTest::wakeOnAcquireChannel)); m_speechSynthesizer->CapabilityAgent::handleDirective(secondDirective.messageId); - EXPECT_TRUE(std::future_status::ready == m_wakeAcquireChannelFuture.wait_for(WAIT_TIMEOUT)); + EXPECT_TRUE(std::future_status::ready == m_wakeAcquireChannelFuture.wait_for(MY_WAIT_TIMEOUT)); EXPECT_CALL( *m_mockSpeechPlayer, attachmentSetSource(A>(), nullptr)); EXPECT_CALL(*m_mockSpeechPlayer, play(_)); - EXPECT_CALL(*m_mockSpeechPlayer, getOffset(_)).WillOnce(Return(OFFSET_IN_CHRONO_MILLISECONDS_TEST)); + EXPECT_CALL(*m_mockSpeechPlayer, getOffset(_)).WillRepeatedly(Return(OFFSET_IN_CHRONO_MILLISECONDS_TEST)); EXPECT_CALL( *m_mockContextManager, setState( @@ -1621,10 +1627,10 @@ TEST_F(SpeechSynthesizerTest, test_enqueueWithActiveSpeech) { EXPECT_CALL(*m_mockPowerResourceManager, acquirePowerResource(COMPONENT_NAME, PowerResourceLevel::ACTIVE_HIGH)) .Times(AtLeast(1)); - m_speechSynthesizer->onFocusChanged(FocusState::NONE); - m_speechSynthesizer->onFocusChanged(FocusState::FOREGROUND); - EXPECT_TRUE(std::future_status::ready == m_wakeSendMessageFuture.wait_for(WAIT_TIMEOUT)); - EXPECT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(WAIT_TIMEOUT)); + m_speechSynthesizer->onFocusChanged(FocusState::NONE, MixingBehavior::MUST_STOP); + m_speechSynthesizer->onFocusChanged(FocusState::FOREGROUND, MixingBehavior::PRIMARY); + EXPECT_TRUE(std::future_status::ready == m_wakeSendMessageFuture.wait_for(MY_WAIT_TIMEOUT)); + EXPECT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(MY_WAIT_TIMEOUT)); } // Reset promises. @@ -1646,8 +1652,8 @@ TEST_F(SpeechSynthesizerTest, test_enqueueWithActiveSpeech) { .WillOnce(InvokeWithoutArgs(this, &SpeechSynthesizerTest::wakeOnSendMessage)); EXPECT_CALL(*m_mockPowerResourceManager, releasePowerResource(COMPONENT_NAME)).Times(AtLeast(1)); m_mockSpeechPlayer->mockFinished(m_mockSpeechPlayer->getCurrentSourceId()); - EXPECT_TRUE(std::future_status::ready == m_wakeSendMessageFuture.wait_for(WAIT_TIMEOUT)); - EXPECT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(WAIT_TIMEOUT)); + EXPECT_TRUE(std::future_status::ready == m_wakeSendMessageFuture.wait_for(MY_WAIT_TIMEOUT)); + EXPECT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(MY_WAIT_TIMEOUT)); } } @@ -1696,11 +1702,11 @@ TEST_F(SpeechSynthesizerTest, test_replaceEnqueuedWithAnotherEnqueuedItem) { NAMESPACE_AND_NAME_SPEECH_STATE, generateFinishedState(firstDirective), StateRefreshPolicy::NEVER, 0)); EXPECT_CALL(*m_mockMessageSender, sendMessage(IsFinishedEvent())); // New speech. - EXPECT_CALL(*m_mockFocusManager, acquireChannel(CHANNEL_NAME, _, NAMESPACE_SPEECH_SYNTHESIZER)) + EXPECT_CALL(*m_mockFocusManager, acquireChannel(CHANNEL_NAME, _)) .WillOnce(InvokeWithoutArgs(this, &SpeechSynthesizerTest::wakeOnAcquireChannel)); m_mockSpeechPlayer->mockFinished(m_mockSpeechPlayer->getCurrentSourceId()); - EXPECT_TRUE(std::future_status::ready == m_wakeAcquireChannelFuture.wait_for(WAIT_TIMEOUT)); + EXPECT_TRUE(std::future_status::ready == m_wakeAcquireChannelFuture.wait_for(MY_WAIT_TIMEOUT)); m_wakeAcquireChannelPromise = std::promise(); m_wakeAcquireChannelFuture = m_wakeAcquireChannelPromise.get_future(); } @@ -1712,7 +1718,7 @@ TEST_F(SpeechSynthesizerTest, test_replaceEnqueuedWithAnotherEnqueuedItem) { *m_mockSpeechPlayer, attachmentSetSource(A>(), nullptr)); EXPECT_CALL(*m_mockSpeechPlayer, play(_)); - EXPECT_CALL(*m_mockSpeechPlayer, getOffset(_)).WillOnce(Return(OFFSET_IN_CHRONO_MILLISECONDS_TEST)); + EXPECT_CALL(*m_mockSpeechPlayer, getOffset(_)).WillRepeatedly(Return(OFFSET_IN_CHRONO_MILLISECONDS_TEST)); EXPECT_CALL( *m_mockContextManager, setState( @@ -1723,10 +1729,10 @@ TEST_F(SpeechSynthesizerTest, test_replaceEnqueuedWithAnotherEnqueuedItem) { EXPECT_CALL(*m_mockPowerResourceManager, acquirePowerResource(COMPONENT_NAME, PowerResourceLevel::ACTIVE_HIGH)) .Times(AtLeast(1)); - m_speechSynthesizer->onFocusChanged(FocusState::NONE); - m_speechSynthesizer->onFocusChanged(FocusState::FOREGROUND); - EXPECT_TRUE(std::future_status::ready == m_wakeSendMessageFuture.wait_for(WAIT_TIMEOUT)); - EXPECT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(WAIT_TIMEOUT)); + m_speechSynthesizer->onFocusChanged(FocusState::NONE, MixingBehavior::MUST_STOP); + m_speechSynthesizer->onFocusChanged(FocusState::FOREGROUND, MixingBehavior::PRIMARY); + EXPECT_TRUE(std::future_status::ready == m_wakeSendMessageFuture.wait_for(MY_WAIT_TIMEOUT)); + EXPECT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(MY_WAIT_TIMEOUT)); } // Reset promises. @@ -1746,8 +1752,8 @@ TEST_F(SpeechSynthesizerTest, test_replaceEnqueuedWithAnotherEnqueuedItem) { .WillOnce(InvokeWithoutArgs(this, &SpeechSynthesizerTest::wakeOnSendMessage)); EXPECT_CALL(*m_mockPowerResourceManager, releasePowerResource(COMPONENT_NAME)).Times(AtLeast(1)); m_mockSpeechPlayer->mockFinished(m_mockSpeechPlayer->getCurrentSourceId()); - EXPECT_TRUE(std::future_status::ready == m_wakeSendMessageFuture.wait_for(WAIT_TIMEOUT)); - EXPECT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(WAIT_TIMEOUT)); + EXPECT_TRUE(std::future_status::ready == m_wakeSendMessageFuture.wait_for(MY_WAIT_TIMEOUT)); + EXPECT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(MY_WAIT_TIMEOUT)); } } diff --git a/CapabilityAgents/System/include/System/ReportStateHandler.h b/CapabilityAgents/System/include/System/ReportStateHandler.h index d4a3576a..b40240e1 100644 --- a/CapabilityAgents/System/include/System/ReportStateHandler.h +++ b/CapabilityAgents/System/include/System/ReportStateHandler.h @@ -18,6 +18,7 @@ #include #include +#include #include #include @@ -112,6 +113,15 @@ private: */ void sendReportState(); + /** + * Initialization after construction. + */ + void initialize(); + +private: + /// Synchronize access since we use members from multiple threads + std::mutex m_stateMutex; + /// The @c Executor which queues up operations from asynchronous API calls. avsCommon::utils::threading::Executor m_executor; diff --git a/CapabilityAgents/System/include/System/SoftwareInfoSender.h b/CapabilityAgents/System/include/System/SoftwareInfoSender.h index a4713752..993e7245 100644 --- a/CapabilityAgents/System/include/System/SoftwareInfoSender.h +++ b/CapabilityAgents/System/include/System/SoftwareInfoSender.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -38,8 +38,8 @@ class SoftwareInfoSendRequest; * @c SoftwareInfoSender is a @c CapabilityAgent that handles the @c System.ReportSoftwareInfo directive and * the sending of @c System.SoftwareInfo events to AVS. * - * @see https://developer.amazon.com/docs/alexa-voice-service/system.html#reportsoftwareinfo-directive - * @see https://developer.amazon.com/docs/alexa-voice-service/system.html#softwareinfo-event + * @see https://developer.amazon.com/docs/alexa/alexa-voice-service/system.html#reportsoftwareinfo-directive + * @see https://developer.amazon.com/docs/alexa/alexa-voice-service/system.html#softwareinfo-event */ class SoftwareInfoSender : public avsCommon::avs::CapabilityAgent diff --git a/CapabilityAgents/System/include/System/StateReportGenerator.h b/CapabilityAgents/System/include/System/StateReportGenerator.h index 0269eb8d..a7b028ba 100644 --- a/CapabilityAgents/System/include/System/StateReportGenerator.h +++ b/CapabilityAgents/System/include/System/StateReportGenerator.h @@ -1,5 +1,5 @@ /* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2019-2020 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. @@ -20,6 +20,7 @@ #include #include +#include #include #include #include @@ -67,14 +68,15 @@ public: */ StateReportGenerator() = default; -private: +protected: /** - * Constructor. + * Constructor (protected for unit tests). * * @param reportFunctions The collection of report functions used to generate the entire report. */ - StateReportGenerator(const std::vector>& reportFunctions); + explicit StateReportGenerator(const std::vector>& reportFunctions); +private: /** * Utility structure used to wrap each setting into a report generation function. * diff --git a/CapabilityAgents/System/src/ReportStateHandler.cpp b/CapabilityAgents/System/src/ReportStateHandler.cpp index 5fc31de8..5fbfda92 100644 --- a/CapabilityAgents/System/src/ReportStateHandler.cpp +++ b/CapabilityAgents/System/src/ReportStateHandler.cpp @@ -137,7 +137,7 @@ std::unique_ptr ReportStateHandler::create( return nullptr; } - auto eventSender = settings::SettingEventSender::create(REPORT_STATE_METADATA, messageSender); + auto eventSender = settings::SettingEventSender::create(REPORT_STATE_METADATA, std::move(messageSender)); bool pendingReport = false; if (!storage->tableEntryExists( @@ -146,14 +146,18 @@ std::unique_ptr ReportStateHandler::create( return nullptr; } - return std::unique_ptr(new ReportStateHandler( - dataManager, - exceptionEncounteredSender, - connectionManager, - storage, + auto handler = std::unique_ptr(new ReportStateHandler( + std::move(dataManager), + std::move(exceptionEncounteredSender), + std::move(connectionManager), + std::move(storage), std::move(eventSender), generators, pendingReport)); + + handler->initialize(); + + return handler; } DirectiveHandlerConfiguration ReportStateHandler::getConfiguration() const { @@ -223,15 +227,20 @@ ReportStateHandler::ReportStateHandler( std::unique_ptr eventSender, const std::vector& generators, bool pendingReport) : - CapabilityAgent{REPORT_STATE_NAMESPACE, exceptionEncounteredSender}, - registrationManager::CustomerDataHandler{dataManager}, - m_connectionManager{connectionManager}, - m_storage{storage}, + CapabilityAgent{REPORT_STATE_NAMESPACE, std::move(exceptionEncounteredSender)}, + registrationManager::CustomerDataHandler{std::move(dataManager)}, + m_connectionManager{std::move(connectionManager)}, + m_storage{std::move(storage)}, m_generators{generators}, m_eventSender{std::move(eventSender)}, m_pendingReport{pendingReport} { +} + +void ReportStateHandler::initialize() { + std::lock_guard lock(m_stateMutex); m_connectionObserver = SettingConnectionObserver::create([this](bool isConnected) { if (isConnected) { + std::lock_guard lock(m_stateMutex); m_executor.submit([this] { sendReportState(); }); } }); @@ -239,10 +248,12 @@ ReportStateHandler::ReportStateHandler( } ReportStateHandler::~ReportStateHandler() { + m_executor.shutdown(); m_connectionManager->removeConnectionStatusObserver(m_connectionObserver); } void ReportStateHandler::sendReportState() { + std::lock_guard lock(m_stateMutex); ACSDK_DEBUG5(LX(__func__).d("pendingReport", m_pendingReport)); if (m_pendingReport) { json::JsonGenerator jsonGenerator; @@ -268,6 +279,7 @@ void ReportStateHandler::sendReportState() { } void system::ReportStateHandler::clearData() { + std::lock_guard lock(m_stateMutex); m_storage->clearTable(REPORT_STATE_COMPONENT_NAME, REPORT_STATE_TABLE); m_storage->deleteTable(REPORT_STATE_COMPONENT_NAME, REPORT_STATE_TABLE); } diff --git a/CapabilityAgents/System/src/SystemCapabilityProvider.cpp b/CapabilityAgents/System/src/SystemCapabilityProvider.cpp index 9a6de318..649952ca 100644 --- a/CapabilityAgents/System/src/SystemCapabilityProvider.cpp +++ b/CapabilityAgents/System/src/SystemCapabilityProvider.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018-2020 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. diff --git a/CapabilityAgents/System/src/UserInactivityMonitor.cpp b/CapabilityAgents/System/src/UserInactivityMonitor.cpp index 40982671..fb02ec36 100644 --- a/CapabilityAgents/System/src/UserInactivityMonitor.cpp +++ b/CapabilityAgents/System/src/UserInactivityMonitor.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -145,7 +145,7 @@ void UserInactivityMonitor::startTimer() { m_eventTimer.start( m_sendPeriod, Timer::PeriodType::ABSOLUTE, - Timer::FOREVER, + Timer::getForever(), std::bind(&UserInactivityMonitor::sendInactivityReport, this)); } diff --git a/CapabilityAgents/System/test/CMakeLists.txt b/CapabilityAgents/System/test/CMakeLists.txt index 2fb1b35b..a874a47f 100644 --- a/CapabilityAgents/System/test/CMakeLists.txt +++ b/CapabilityAgents/System/test/CMakeLists.txt @@ -7,4 +7,4 @@ set(INCLUDE_PATH "${AVSCommon_SOURCE_DIR}/SDKInterfaces/test" "${DeviceSettings_SOURCE_DIR}/test") -discover_unit_tests("${INCLUDE_PATH}" "AVSSystem;ADSL;UtilsCommonTestLib") +discover_unit_tests("${INCLUDE_PATH}" "AVSSystem;ADSL;UtilsCommonTestLib;SDKInterfacesTests") diff --git a/CapabilityAgents/System/test/ReportStateHandlerTest.cpp b/CapabilityAgents/System/test/ReportStateHandlerTest.cpp new file mode 100644 index 00000000..82b88301 --- /dev/null +++ b/CapabilityAgents/System/test/ReportStateHandlerTest.cpp @@ -0,0 +1,150 @@ +/* + * Copyright 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 +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using alexaClientSDK::avsCommon::avs::AVSDirective; +using alexaClientSDK::avsCommon::avs::AVSMessageHeader; +using alexaClientSDK::avsCommon::avs::MessageRequest; +using alexaClientSDK::avsCommon::avs::attachment::AttachmentManager; +using alexaClientSDK::avsCommon::sdkInterfaces::MessageRequestObserverInterface; +using alexaClientSDK::avsCommon::sdkInterfaces::storage::test::StubMiscStorage; +using alexaClientSDK::avsCommon::sdkInterfaces::test::MockAVSConnectionManager; +using alexaClientSDK::avsCommon::sdkInterfaces::test::MockDirectiveHandlerResult; +using alexaClientSDK::avsCommon::sdkInterfaces::test::MockExceptionEncounteredSender; +using alexaClientSDK::avsCommon::sdkInterfaces::test::MockMessageSender; +using alexaClientSDK::registrationManager::CustomerDataManager; + +namespace alexaClientSDK { +namespace capabilityAgents { +namespace system { +namespace test { + +/// Fake message ID used for testing. +static const std::string TEST_MESSAGE_ID("c88f970a-3519-4ecb-bdcc-0488aca22b87"); + +/// Fake request ID used for testing. +static const std::string TEST_REQUEST_ID("4b73575e-2e7d-425b-bfa4-c6615e0fbd43"); + +/// Fake context ID used for testing. +static const std::string TEST_CONTEXT_ID("71c967d8-ad58-47b0-924d-b752deb75e4e"); + +class MockStateReportGenerator : public StateReportGenerator { +public: + MockStateReportGenerator(std::function reportFunction) : StateReportGenerator({reportFunction}) { + } +}; + +class ReportStateHandlerTest : public ::testing::Test { +protected: + void SetUp() override { + m_customerDataManager = std::make_shared(); + m_mockExceptionEncounteredSender = std::make_shared(); + m_mockAVSConnectionManager = std::make_shared<::testing::NiceMock>(); + m_mockMessageSender = std::make_shared(); + m_stubMiscStorage = StubMiscStorage::create(); + + m_attachmentManager = std::make_shared(AttachmentManager::AttachmentType::IN_PROCESS); + m_mockDirectiveHandlerResult = std::unique_ptr(new MockDirectiveHandlerResult); + } + + void waitUntilEventSent() { + std::unique_lock lock(m_mutex); + // This will take 2 seconds only in case it fails (normally it exits right away). + ASSERT_TRUE(m_condition.wait_for( + lock, std::chrono::seconds(2), [this] { return m_directiveCompleted && m_eventSent; })); + } + + std::shared_ptr m_unit; + std::shared_ptr m_customerDataManager; + std::shared_ptr m_mockExceptionEncounteredSender; + std::shared_ptr m_mockMessageSender; + std::shared_ptr<::testing::NiceMock> m_mockAVSConnectionManager; + std::shared_ptr m_stubMiscStorage; + std::vector generators; + std::shared_ptr m_attachmentManager; + std::unique_ptr m_mockDirectiveHandlerResult; + + std::mutex m_mutex; + std::condition_variable m_condition; + bool m_directiveCompleted = false; + bool m_eventSent = false; + std::string m_eventJson; + + std::shared_ptr createDirective() { + auto avsMessageHeader = + std::make_shared("System", "ReportState", TEST_MESSAGE_ID, TEST_REQUEST_ID); + auto directive = AVSDirective::create("", avsMessageHeader, "", m_attachmentManager, TEST_CONTEXT_ID); + return std::move(directive); + } +}; + +TEST_F(ReportStateHandlerTest, testReportState) { + void* leak = new int[23]; + leak = nullptr; + ASSERT_EQ(nullptr, leak); + + MockStateReportGenerator mockGenerator([] { return R"({"unitTest":"ON","complaints":"OFF"})"; }); + generators.push_back(mockGenerator); + m_unit = ReportStateHandler::create( + m_customerDataManager, + m_mockExceptionEncounteredSender, + m_mockAVSConnectionManager, + m_mockMessageSender, + m_stubMiscStorage, + generators); + + EXPECT_CALL(*m_mockDirectiveHandlerResult, setCompleted()).WillOnce(::testing::InvokeWithoutArgs([this] { + std::lock_guard lock(m_mutex); + m_directiveCompleted = true; + m_condition.notify_all(); + })); + + EXPECT_CALL(*m_mockMessageSender, sendMessage(::testing::_)) + .WillOnce(::testing::WithArg<0>(::testing::Invoke([this](std::shared_ptr request) { + std::lock_guard lock(m_mutex); + m_eventSent = true; + m_eventJson = request->getJsonContent(); + request->sendCompleted(MessageRequestObserverInterface::Status::SUCCESS); + m_condition.notify_all(); + }))); + + m_unit->CapabilityAgent::preHandleDirective(createDirective(), std::move(m_mockDirectiveHandlerResult)); + m_unit->CapabilityAgent::handleDirective(TEST_MESSAGE_ID); + + waitUntilEventSent(); + ASSERT_TRUE(m_eventJson.find(R"("unitTest":"ON")") != m_eventJson.npos); +} + +} // namespace test +} // namespace system +} // namespace capabilityAgents +} // namespace alexaClientSDK diff --git a/CapabilityAgents/TemplateRuntime/include/TemplateRuntime/TemplateRuntime.h b/CapabilityAgents/TemplateRuntime/include/TemplateRuntime/TemplateRuntime.h index b5ebdeaf..a8d3cc14 100644 --- a/CapabilityAgents/TemplateRuntime/include/TemplateRuntime/TemplateRuntime.h +++ b/CapabilityAgents/TemplateRuntime/include/TemplateRuntime/TemplateRuntime.h @@ -90,7 +90,7 @@ public: /// @name ChannelObserverInterface Functions /// @{ - void onFocusChanged(avsCommon::avs::FocusState newFocus) override; + void onFocusChanged(avsCommon::avs::FocusState newFocus, avsCommon::avs::MixingBehavior behavior) override; /// @} /// @name RenderPlayerInfoCardsObserverInterface Functions diff --git a/CapabilityAgents/TemplateRuntime/src/TemplateRuntime.cpp b/CapabilityAgents/TemplateRuntime/src/TemplateRuntime.cpp index 16dacdd2..58b5e46f 100644 --- a/CapabilityAgents/TemplateRuntime/src/TemplateRuntime.cpp +++ b/CapabilityAgents/TemplateRuntime/src/TemplateRuntime.cpp @@ -196,7 +196,7 @@ DirectiveHandlerConfiguration TemplateRuntime::getConfiguration() const { return configuration; } -void TemplateRuntime::onFocusChanged(avsCommon::avs::FocusState newFocus) { +void TemplateRuntime::onFocusChanged(avsCommon::avs::FocusState newFocus, MixingBehavior) { m_executor.submit([this, newFocus]() { executeOnFocusChangedEvent(newFocus); }); } diff --git a/CapabilityAgents/TemplateRuntime/test/TemplateRuntimeTest.cpp b/CapabilityAgents/TemplateRuntime/test/TemplateRuntimeTest.cpp index 797f810b..e3b98b15 100644 --- a/CapabilityAgents/TemplateRuntime/test/TemplateRuntimeTest.cpp +++ b/CapabilityAgents/TemplateRuntime/test/TemplateRuntimeTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -29,7 +29,6 @@ #include #include #include -#include #include #include "TemplateRuntime/TemplateRuntime.h" @@ -253,14 +252,14 @@ void TemplateRuntimeTest::SetUp() { m_templateRuntime->addObserver(m_mockGui); ON_CALL(*m_mockFocusManager, acquireChannel(_, _, _)).WillByDefault(InvokeWithoutArgs([this] { - m_templateRuntime->onFocusChanged(avsCommon::avs::FocusState::FOREGROUND); + m_templateRuntime->onFocusChanged(avsCommon::avs::FocusState::FOREGROUND, MixingBehavior::PRIMARY); return true; })); ON_CALL(*m_mockFocusManager, releaseChannel(_, _)).WillByDefault(InvokeWithoutArgs([this] { auto releaseChannelSuccess = std::make_shared>(); std::future returnValue = releaseChannelSuccess->get_future(); - m_templateRuntime->onFocusChanged(avsCommon::avs::FocusState::NONE); + m_templateRuntime->onFocusChanged(avsCommon::avs::FocusState::NONE, MixingBehavior::MUST_STOP); releaseChannelSuccess->set_value(true); return returnValue; })); @@ -768,7 +767,7 @@ TEST_F(TemplateRuntimeTest, test_focusNone) { m_templateRuntime->CapabilityAgent::handleDirective(MESSAGE_ID); m_wakeSetCompletedFuture.wait_for(TIMEOUT); m_wakeRenderTemplateCardFuture.wait_for(TIMEOUT); - m_templateRuntime->onFocusChanged(FocusState::NONE); + m_templateRuntime->onFocusChanged(FocusState::NONE, MixingBehavior::MUST_STOP); m_wakeClearTemplateCardFuture.wait_for(TIMEOUT); } @@ -792,7 +791,7 @@ TEST_F(TemplateRuntimeTest, test_displayCardCleared) { EXPECT_CALL(*m_mockFocusManager, releaseChannel(_, _)).Times(Exactly(1)).WillOnce(InvokeWithoutArgs([this] { auto releaseChannelSuccess = std::make_shared>(); std::future returnValue = releaseChannelSuccess->get_future(); - m_templateRuntime->onFocusChanged(avsCommon::avs::FocusState::NONE); + m_templateRuntime->onFocusChanged(avsCommon::avs::FocusState::NONE, MixingBehavior::MUST_STOP); releaseChannelSuccess->set_value(true); wakeOnReleaseChannel(); return returnValue; @@ -850,7 +849,7 @@ TEST_F(TemplateRuntimeTest, test_reacquireChannel) { .WillOnce(InvokeWithoutArgs(this, &TemplateRuntimeTest::wakeOnRenderTemplateCard)); m_templateRuntime->handleDirectiveImmediately(directive1); - m_templateRuntime->onFocusChanged(avsCommon::avs::FocusState::NONE); + m_templateRuntime->onFocusChanged(avsCommon::avs::FocusState::NONE, MixingBehavior::MUST_STOP); m_wakeRenderTemplateCardFuture.wait_for(TIMEOUT); } @@ -888,7 +887,7 @@ TEST_F(TemplateRuntimeTest, testTimer_RenderPlayerInfoAfterPlayerActivityChanged EXPECT_CALL(*m_mockFocusManager, releaseChannel(_, _)).Times(Exactly(1)).WillOnce(InvokeWithoutArgs([this] { auto releaseChannelSuccess = std::make_shared>(); std::future returnValue = releaseChannelSuccess->get_future(); - m_templateRuntime->onFocusChanged(avsCommon::avs::FocusState::NONE); + m_templateRuntime->onFocusChanged(avsCommon::avs::FocusState::NONE, MixingBehavior::MUST_STOP); releaseChannelSuccess->set_value(true); wakeOnReleaseChannel(); return returnValue; @@ -908,7 +907,7 @@ TEST_F(TemplateRuntimeTest, testTimer_RenderPlayerInfoAfterPlayerActivityChanged m_wakeReleaseChannelFuture.wait_for(TIMEOUT); context.audioItemId = AUDIO_ITEM_ID_1; m_templateRuntime->onRenderPlayerCardsInfoChanged(avsCommon::avs::PlayerActivity::PLAYING, context); - m_templateRuntime->onFocusChanged(avsCommon::avs::FocusState::FOREGROUND); + m_templateRuntime->onFocusChanged(avsCommon::avs::FocusState::FOREGROUND, MixingBehavior::PRIMARY); m_templateRuntime->displayCardCleared(); m_wakeReleaseChannelFuture.wait_for(TIMEOUT); } diff --git a/Captions/Implementation/include/Captions/CaptionManager.h b/Captions/Implementation/include/Captions/CaptionManager.h index db768f61..44534421 100644 --- a/Captions/Implementation/include/Captions/CaptionManager.h +++ b/Captions/Implementation/include/Captions/CaptionManager.h @@ -1,5 +1,5 @@ /* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2019-2020 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. @@ -91,15 +91,29 @@ public: /// @name MediaPlayerObserverInterface methods /// @{ - void onPlaybackStarted(CaptionFrame::MediaPlayerSourceId id) override; - void onPlaybackFinished(CaptionFrame::MediaPlayerSourceId id) override; + void onPlaybackStarted( + CaptionFrame::MediaPlayerSourceId id, + const avsCommon::utils::mediaPlayer::MediaPlayerState& state) override; + void onPlaybackFinished( + CaptionFrame::MediaPlayerSourceId id, + const avsCommon::utils::mediaPlayer::MediaPlayerState& state) override; void onPlaybackError( CaptionFrame::MediaPlayerSourceId id, const avsCommon::utils::mediaPlayer::ErrorType& type, - std::string error) override; - void onPlaybackPaused(CaptionFrame::MediaPlayerSourceId id) override; - void onPlaybackResumed(CaptionFrame::MediaPlayerSourceId id) override; - void onPlaybackStopped(CaptionFrame::MediaPlayerSourceId id) override; + std::string error, + const avsCommon::utils::mediaPlayer::MediaPlayerState& state) override; + void onPlaybackPaused( + CaptionFrame::MediaPlayerSourceId id, + const avsCommon::utils::mediaPlayer::MediaPlayerState& state) override; + void onPlaybackResumed( + CaptionFrame::MediaPlayerSourceId id, + const avsCommon::utils::mediaPlayer::MediaPlayerState& state) override; + void onPlaybackStopped( + CaptionFrame::MediaPlayerSourceId id, + const avsCommon::utils::mediaPlayer::MediaPlayerState& state) override; + void onFirstByteRead( + CaptionFrame::MediaPlayerSourceId id, + const avsCommon::utils::mediaPlayer::MediaPlayerState& state) override{}; ///@} private: diff --git a/Captions/Implementation/src/CaptionManager.cpp b/Captions/Implementation/src/CaptionManager.cpp index 8a53ff11..ec1076ee 100644 --- a/Captions/Implementation/src/CaptionManager.cpp +++ b/Captions/Implementation/src/CaptionManager.cpp @@ -235,7 +235,7 @@ void CaptionManager::onParsed(const CaptionFrame& captionFrame) { ACSDK_DEBUG5(LX("finishedOnParsed")); } -void CaptionManager::onPlaybackStarted(CaptionFrame::MediaPlayerSourceId id) { +void CaptionManager::onPlaybackStarted(CaptionFrame::MediaPlayerSourceId id, const MediaPlayerState&) { ACSDK_DEBUG3(LX(__func__).d("id", id)); std::lock_guard lock(m_mutex); @@ -247,7 +247,7 @@ void CaptionManager::onPlaybackStarted(CaptionFrame::MediaPlayerSourceId id) { } } -void CaptionManager::onPlaybackFinished(CaptionFrame::MediaPlayerSourceId id) { +void CaptionManager::onPlaybackFinished(CaptionFrame::MediaPlayerSourceId id, const MediaPlayerState&) { ACSDK_DEBUG3(LX(__func__).d("id", id)); std::lock_guard lock(m_mutex); @@ -262,7 +262,11 @@ void CaptionManager::onPlaybackFinished(CaptionFrame::MediaPlayerSourceId id) { m_parser->releaseResourcesFor(id); } -void CaptionManager::onPlaybackError(CaptionFrame::MediaPlayerSourceId id, const ErrorType& type, std::string error) { +void CaptionManager::onPlaybackError( + CaptionFrame::MediaPlayerSourceId id, + const ErrorType& type, + std::string error, + const MediaPlayerState&) { ACSDK_DEBUG3(LX(__func__).d("type", type).d("error", error).d("id", id)); std::lock_guard lock(m_mutex); @@ -276,7 +280,7 @@ void CaptionManager::onPlaybackError(CaptionFrame::MediaPlayerSourceId id, const m_parser->releaseResourcesFor(id); } -void CaptionManager::onPlaybackPaused(CaptionFrame::MediaPlayerSourceId id) { +void CaptionManager::onPlaybackPaused(CaptionFrame::MediaPlayerSourceId id, const MediaPlayerState&) { ACSDK_DEBUG3(LX(__func__).d("id", id)); std::lock_guard lock(m_mutex); @@ -288,7 +292,7 @@ void CaptionManager::onPlaybackPaused(CaptionFrame::MediaPlayerSourceId id) { } } -void CaptionManager::onPlaybackResumed(CaptionFrame::MediaPlayerSourceId id) { +void CaptionManager::onPlaybackResumed(CaptionFrame::MediaPlayerSourceId id, const MediaPlayerState&) { ACSDK_DEBUG3(LX(__func__).d("id", id)); std::lock_guard lock(m_mutex); @@ -300,7 +304,7 @@ void CaptionManager::onPlaybackResumed(CaptionFrame::MediaPlayerSourceId id) { } } -void CaptionManager::onPlaybackStopped(CaptionFrame::MediaPlayerSourceId id) { +void CaptionManager::onPlaybackStopped(CaptionFrame::MediaPlayerSourceId id, const MediaPlayerState&) { ACSDK_DEBUG3(LX(__func__).d("id", id)); std::lock_guard lock(m_mutex); diff --git a/Captions/Implementation/test/CaptionManagerTest.cpp b/Captions/Implementation/test/CaptionManagerTest.cpp index f28dbe30..ffbf4740 100644 --- a/Captions/Implementation/test/CaptionManagerTest.cpp +++ b/Captions/Implementation/test/CaptionManagerTest.cpp @@ -146,8 +146,8 @@ TEST_F(CaptionManagerTest, test_sourceIdDoesNotChange) { auto mockTimingAdapter = m_timingFactory->getMockTimingAdapter(); int sourceID1 = 1; - auto expectedCaptionFrame = new CaptionFrame(sourceID1); - EXPECT_CALL(*mockTimingAdapter.get(), queueForDisplay(*expectedCaptionFrame, _)).Times(1); + auto expectedCaptionFrame = CaptionFrame(sourceID1); + EXPECT_CALL(*mockTimingAdapter.get(), queueForDisplay(expectedCaptionFrame, _)).Times(1); caption_manager->onParsed(CaptionFrame(sourceID1)); } @@ -161,15 +161,15 @@ TEST_F(CaptionManagerTest, test_singleMediaPlayerPause) { CaptionLine expectedLine1 = CaptionLine("The time is 2:17 PM.", {TextStyle()}); expectedLines.emplace_back(expectedLine1); auto expectedCaptionFrame = - new CaptionFrame(1, std::chrono::milliseconds(1), std::chrono::milliseconds(0), expectedLines); - EXPECT_CALL(*mockTimingAdapter.get(), queueForDisplay(*expectedCaptionFrame, _)).Times(1); + CaptionFrame(1, std::chrono::milliseconds(1), std::chrono::milliseconds(0), expectedLines); + EXPECT_CALL(*mockTimingAdapter.get(), queueForDisplay(expectedCaptionFrame, _)).Times(1); EXPECT_CALL(*m_presenter, getWrapIndex(_)).Times(1).WillOnce(Return(std::pair(false, 0))); std::vector lines; CaptionLine line = CaptionLine("The time is 2:17 PM.", {}); lines.emplace_back(line); - auto captionFrame = new CaptionFrame(1, std::chrono::milliseconds(1), std::chrono::milliseconds(0), lines); - caption_manager->onParsed(*captionFrame); + auto captionFrame = CaptionFrame(1, std::chrono::milliseconds(1), std::chrono::milliseconds(0), lines); + caption_manager->onParsed(captionFrame); } /** @@ -181,15 +181,15 @@ TEST_F(CaptionManagerTest, test_splitCaptionFrameWhitespaceOnly) { CaptionLine expectedLine1 = CaptionLine(" ", {TextStyle()}); expectedLines.emplace_back(expectedLine1); auto expectedCaptionFrame = - new CaptionFrame(1, std::chrono::milliseconds(1), std::chrono::milliseconds(0), expectedLines); - EXPECT_CALL(*mockTimingAdapter, queueForDisplay(*expectedCaptionFrame, _)).Times(1); + CaptionFrame(1, std::chrono::milliseconds(1), std::chrono::milliseconds(0), expectedLines); + EXPECT_CALL(*mockTimingAdapter, queueForDisplay(expectedCaptionFrame, _)).Times(1); EXPECT_CALL(*m_presenter, getWrapIndex(_)).Times(1).WillOnce(Return(std::pair(false, 0))); std::vector lines; CaptionLine line = CaptionLine(" ", {}); lines.emplace_back(line); - auto captionFrame = new CaptionFrame(1, std::chrono::milliseconds(1), std::chrono::milliseconds(0), lines); - caption_manager->onParsed(*captionFrame); + auto captionFrame = CaptionFrame(1, std::chrono::milliseconds(1), std::chrono::milliseconds(0), lines); + caption_manager->onParsed(captionFrame); } /** @@ -201,8 +201,8 @@ TEST_F(CaptionManagerTest, test_splitCaptionFrameWhitespaceAfterLineWrap) { CaptionLine expectedLine1 = CaptionLine("The time is 2:17 PM.", {TextStyle()}); expectedLines.emplace_back(expectedLine1); auto expectedCaptionFrame = - new CaptionFrame(1, std::chrono::milliseconds(1), std::chrono::milliseconds(0), expectedLines); - EXPECT_CALL(*mockTimingAdapter, queueForDisplay(*expectedCaptionFrame, _)).Times(1); + CaptionFrame(1, std::chrono::milliseconds(1), std::chrono::milliseconds(0), expectedLines); + EXPECT_CALL(*mockTimingAdapter, queueForDisplay(expectedCaptionFrame, _)).Times(1); EXPECT_CALL(*m_presenter, getWrapIndex(_)) .Times(2) .WillOnce(Return(std::pair(true, 20))) @@ -210,8 +210,8 @@ TEST_F(CaptionManagerTest, test_splitCaptionFrameWhitespaceAfterLineWrap) { std::vector lines; CaptionLine line = CaptionLine("The time is 2:17 PM. ", {}); lines.emplace_back(line); - auto captionFrame = new CaptionFrame(1, std::chrono::milliseconds(1), std::chrono::milliseconds(0), lines); - caption_manager->onParsed(*captionFrame); + auto captionFrame = CaptionFrame(1, std::chrono::milliseconds(1), std::chrono::milliseconds(0), lines); + caption_manager->onParsed(captionFrame); } /** @@ -227,8 +227,8 @@ TEST_F(CaptionManagerTest, test_splitCaptionFrameNoWhitespaceBeforeWrapIndex) { expectedLines.emplace_back(expectedLine2); expectedLines.emplace_back(expectedLine3); auto expectedCaptionFrame = - new CaptionFrame(1, std::chrono::milliseconds(1), std::chrono::milliseconds(0), expectedLines); - EXPECT_CALL(*mockTimingAdapter, queueForDisplay(*expectedCaptionFrame, _)).Times(1); + CaptionFrame(1, std::chrono::milliseconds(1), std::chrono::milliseconds(0), expectedLines); + EXPECT_CALL(*mockTimingAdapter, queueForDisplay(expectedCaptionFrame, _)).Times(1); EXPECT_CALL(*m_presenter, getWrapIndex(_)) .Times(3) .WillOnce(Return(std::pair(true, 9))) @@ -237,8 +237,8 @@ TEST_F(CaptionManagerTest, test_splitCaptionFrameNoWhitespaceBeforeWrapIndex) { std::vector lines; CaptionLine line = CaptionLine("Thiscaptionhasnospaces", {}); lines.emplace_back(line); - auto captionFrame = new CaptionFrame(1, std::chrono::milliseconds(1), std::chrono::milliseconds(0), lines); - caption_manager->onParsed(*captionFrame); + auto captionFrame = CaptionFrame(1, std::chrono::milliseconds(1), std::chrono::milliseconds(0), lines); + caption_manager->onParsed(captionFrame); } /** @@ -250,15 +250,15 @@ TEST_F(CaptionManagerTest, test_splitCaptionFrameFalseWillNotSplitLine) { CaptionLine expectedLine1 = CaptionLine("The time is 2:17 PM.", {TextStyle()}); expectedLines.emplace_back(expectedLine1); auto expectedCaptionFrame = - new CaptionFrame(1, std::chrono::milliseconds(1), std::chrono::milliseconds(0), expectedLines); - EXPECT_CALL(*mockTimingAdapter, queueForDisplay(*expectedCaptionFrame, _)).Times(1); + CaptionFrame(1, std::chrono::milliseconds(1), std::chrono::milliseconds(0), expectedLines); + EXPECT_CALL(*mockTimingAdapter, queueForDisplay(expectedCaptionFrame, _)).Times(1); EXPECT_CALL(*m_presenter, getWrapIndex(_)).Times(1).WillOnce(Return(std::pair(false, 0))); std::vector lines; CaptionLine line = CaptionLine("The time is 2:17 PM.", {}); lines.emplace_back(line); - auto captionFrame = new CaptionFrame(1, std::chrono::milliseconds(1), std::chrono::milliseconds(0), lines); - caption_manager->onParsed(*captionFrame); + auto captionFrame = CaptionFrame(1, std::chrono::milliseconds(1), std::chrono::milliseconds(0), lines); + caption_manager->onParsed(captionFrame); } /** @@ -273,18 +273,18 @@ TEST_F(CaptionManagerTest, test_splitCaptionFrameAtSpaceIndex) { expectedLines.emplace_back(expectedLine1); expectedLines.emplace_back(expectedLine2); auto expectedCaptionFrame = - new CaptionFrame(1, std::chrono::milliseconds(1), std::chrono::milliseconds(0), expectedLines); + CaptionFrame(1, std::chrono::milliseconds(1), std::chrono::milliseconds(0), expectedLines); EXPECT_CALL(*m_presenter, getWrapIndex(_)) .Times(2) .WillOnce(Return(std::pair(true, 12))) .WillOnce(Return(std::pair(false, 0))); - EXPECT_CALL(*mockTimingAdapter, queueForDisplay(*expectedCaptionFrame, _)).Times(1); + EXPECT_CALL(*mockTimingAdapter, queueForDisplay(expectedCaptionFrame, _)).Times(1); std::vector lines; CaptionLine line1 = CaptionLine("The time is 2:17 PM.", {}); lines.emplace_back(line1); - auto captionFrame = new CaptionFrame(1, std::chrono::milliseconds(1), std::chrono::milliseconds(0), lines); - caption_manager->onParsed(*captionFrame); + auto captionFrame = CaptionFrame(1, std::chrono::milliseconds(1), std::chrono::milliseconds(0), lines); + caption_manager->onParsed(captionFrame); } } // namespace test diff --git a/Captions/Implementation/test/MockCaptionManager.h b/Captions/Implementation/test/MockCaptionManager.h index d3b4a95d..98bdec0e 100644 --- a/Captions/Implementation/test/MockCaptionManager.h +++ b/Captions/Implementation/test/MockCaptionManager.h @@ -1,5 +1,5 @@ /* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2019-2020 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. @@ -47,9 +47,16 @@ public: /// @name MediaPlayerObserverInterface methods /// @{ - MOCK_METHOD1(onPlaybackStarted, void(SourceId)); - MOCK_METHOD1(onPlaybackFinished, void(SourceId)); - MOCK_METHOD3(onPlaybackError, void(SourceId, const avsCommon::utils::mediaPlayer::ErrorType&, std::string)); + MOCK_METHOD2(onPlaybackStarted, void(SourceId, const avsCommon::utils::mediaPlayer::MediaPlayerState&)); + MOCK_METHOD2(onPlaybackFinished, void(SourceId, const avsCommon::utils::mediaPlayer::MediaPlayerState&)); + MOCK_METHOD4( + onPlaybackError, + void( + SourceId, + const avsCommon::utils::mediaPlayer::ErrorType&, + std::string, + const avsCommon::utils::mediaPlayer::MediaPlayerState&)); + MOCK_METHOD2(onFirstByteRead, void(SourceId, const avsCommon::utils::mediaPlayer::MediaPlayerState&)); /// @} }; diff --git a/ContextManager/include/ContextManager/ContextManager.h b/ContextManager/include/ContextManager/ContextManager.h index 99bf5996..46a3739f 100644 --- a/ContextManager/include/ContextManager/ContextManager.h +++ b/ContextManager/include/ContextManager/ContextManager.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -49,12 +50,14 @@ public: * * @param deviceInfo Structure used to retrieve the default endpoint id. * @param multiTimer Object used to schedule request timeout. + * @param metricRecorder The metric recorder. * @return Returns a new @c ContextManager. */ static std::shared_ptr create( const avsCommon::utils::DeviceInfo& deviceInfo, std::shared_ptr multiTimer = - std::make_shared()); + std::make_shared(), + std::shared_ptr metricRecorder = nullptr); /// Destructor. ~ContextManager() override; @@ -174,10 +177,13 @@ private: // Private method declarations. * * @param defaultEndpointId The default endpoint used for legacy methods where no endpoint id is provided. * @param multiTimer Object used to schedule request timeout. + * @param metricRecorder The metric recorder. */ ContextManager( const std::string& defaultEndpointId, - std::shared_ptr multiTimer); + std::shared_ptr multiTimer, + std::shared_ptr metricRecorder); + /// /** * Updates the state of a capability. @@ -187,7 +193,7 @@ private: // Private method declarations. * @param refreshPolicy The refresh policy for the state. * @deprecated @c StateRefreshPolicy has been deprecated. */ - bool updateCapabilityState( + void updateCapabilityState( const avsCommon::avs::CapabilityTag& capabilityIdentifier, const std::string& jsonState, const avsCommon::avs::StateRefreshPolicy& refreshPolicy); @@ -241,6 +247,9 @@ private: // Member variable declarations /// Mutex used to guard the pending state requests. This is only needed because of @c setState. std::mutex m_requestsMutex; + /// The metric recorder. + std::shared_ptr m_metricRecorder; + /// The request token counter. unsigned int m_requestCounter; diff --git a/ContextManager/src/ContextManager.cpp b/ContextManager/src/ContextManager.cpp index 924e8eed..e137d2bd 100644 --- a/ContextManager/src/ContextManager.cpp +++ b/ContextManager/src/ContextManager.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -17,6 +17,8 @@ #include #include +#include "AVSCommon/Utils/Metrics/MetricEventBuilder.h" +#include "AVSCommon/Utils/Metrics/DataPointCounterBuilder.h" #include "ContextManager/ContextManager.h" @@ -28,6 +30,7 @@ using namespace avsCommon; using namespace avsCommon::avs; using namespace avsCommon::sdkInterfaces; using namespace avsCommon::utils; +using namespace avsCommon::utils::metrics; /// String to identify log entries originating from this file. static const std::string TAG("ContextManager"); @@ -42,15 +45,19 @@ static const std::string TAG("ContextManager"); /// An empty token to identify @c setState that is a proactive setter. static const ContextRequestToken EMPTY_TOKEN = 0; +static const std::string STATE_PROVIDER_TIMEOUT_METRIC_PREFIX = "ERROR.StateProviderTimeout."; + std::shared_ptr ContextManager::create( const DeviceInfo& deviceInfo, - std::shared_ptr multiTimer) { + std::shared_ptr multiTimer, + std::shared_ptr metricRecorder) { if (!multiTimer) { ACSDK_ERROR(LX("createFailed").d("reason", "nullMultiTimer")); return nullptr; } - std::shared_ptr contextManager(new ContextManager(deviceInfo.getDefaultEndpointId(), multiTimer)); + std::shared_ptr contextManager( + new ContextManager(deviceInfo.getDefaultEndpointId(), multiTimer, metricRecorder)); return contextManager; } @@ -150,7 +157,9 @@ SetStateResult ContextManager::setState( ContextManager::ContextManager( const std::string& defaultEndpointId, - std::shared_ptr multiTimer) : + std::shared_ptr multiTimer, + std::shared_ptr metricRecorder) : + m_metricRecorder{std::move(metricRecorder)}, m_requestCounter{0}, m_shutdown{false}, m_defaultEndpointId{defaultEndpointId}, @@ -320,6 +329,15 @@ void ContextManager::sendContextRequestFailedLocked(unsigned int requestToken, C ACSDK_DEBUG0(LX(__func__).d("result", "nullRequester").d("token", requestToken)); return; } + for (auto& pendingState : m_pendingStateRequest[requestToken]) { + auto metricName = STATE_PROVIDER_TIMEOUT_METRIC_PREFIX + pendingState.nameSpace; + recordMetric( + m_metricRecorder, + MetricEventBuilder{} + .setActivityName("CONTEXT_MANAGER-" + metricName) + .addDataPoint(DataPointCounterBuilder{}.setName(metricName).increment(1).build()) + .build()); + } request.contextRequester->onContextFailure(error, requestToken); } @@ -352,8 +370,11 @@ void ContextManager::sendContextIfReadyLocked(unsigned int requestToken, const E for (auto& capability : m_endpointsState[requestEndpointId]) { if (capability.second.legacyCapability || (capability.second.stateProvider && capability.second.stateProvider->canStateBeRetrieved())) { - // Ignore if the state is not available (used for legacy SOMETIMES refresh policy). - if (capability.second.capabilityState.hasValue()) { + // Ignore if the state is not available for legacy SOMETIMES refresh policy. + if (capability.second.refreshPolicy == StateRefreshPolicy::SOMETIMES && + !capability.second.capabilityState.hasValue()) { + ACSDK_DEBUG5(LX(__func__).d("skipping state for capabilityIdentifier", capability.first)); + } else { ACSDK_DEBUG5(LX(__func__).sensitive("addState", capability.first)); context.addState(capability.first, capability.second.capabilityState.value()); } @@ -372,7 +393,7 @@ void ContextManager::updateCapabilityState( capabilitiesState[capabilityIdentifier] = StateInfo(stateProvider, capabilityState); } -bool ContextManager::updateCapabilityState( +void ContextManager::updateCapabilityState( const avsCommon::avs::CapabilityTag& capabilityIdentifier, const std::string& jsonState, const avsCommon::avs::StateRefreshPolicy& refreshPolicy) { @@ -380,23 +401,7 @@ bool ContextManager::updateCapabilityState( auto& endpointId = capabilityIdentifier.endpointId.empty() ? m_defaultEndpointId : capabilityIdentifier.endpointId; auto& capabilityInfo = m_endpointsState[endpointId]; auto& stateProvider = capabilityInfo[capabilityIdentifier].stateProvider; - if (jsonState.empty()) { - auto& capabilityState = capabilityInfo[capabilityIdentifier].capabilityState; - if (!capabilityState.hasValue()) { - if (StateRefreshPolicy::ALWAYS == refreshPolicy) { - ACSDK_ERROR(LX("updateCapabilityStateFailed") - .d("reason", "emptyValue") - .d("capability", capabilityIdentifier.nameSpace + "::" + capabilityIdentifier.name)); - return false; - } - } else { - capabilityInfo[capabilityIdentifier] = - StateInfo(stateProvider, capabilityState.value().valuePayload, refreshPolicy); - } - } else { - capabilityInfo[capabilityIdentifier] = StateInfo(stateProvider, jsonState, refreshPolicy); - } - return true; + capabilityInfo[capabilityIdentifier] = StateInfo(stateProvider, jsonState, refreshPolicy); } ContextManager::StateInfo::StateInfo( diff --git a/ContextManager/test/CMakeLists.txt b/ContextManager/test/CMakeLists.txt index f7201efd..c53481bd 100644 --- a/ContextManager/test/CMakeLists.txt +++ b/ContextManager/test/CMakeLists.txt @@ -1,2 +1,2 @@ -discover_unit_tests("${ContextManager}/include" ContextManager) +discover_unit_tests("${ContextManager}/include" "ContextManager") add_definitions("-DACSDK_LOG_MODULE=contextManagerTest") diff --git a/ContextManager/test/ContextManagerTest.cpp b/ContextManager/test/ContextManagerTest.cpp index a16a9fec..2bc240ba 100644 --- a/ContextManager/test/ContextManagerTest.cpp +++ b/ContextManager/test/ContextManagerTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -84,8 +84,8 @@ protected: }; void ContextManagerTest::SetUp() { - auto deviceInfo = - avsCommon::utils::DeviceInfo::create("clientId", "productId", "1234", "manufacturer", "my device"); + auto deviceInfo = avsCommon::utils::DeviceInfo::create( + "clientId", "productId", "1234", "manufacturer", "my device", "friendlyName", "deviceType"); ASSERT_NE(deviceInfo, nullptr); m_contextManager = ContextManager::create(*deviceInfo); @@ -243,25 +243,50 @@ TEST_F(ContextManagerTest, test_incorrectToken) { } /** - * Set the states with a @c StateRefreshPolicy @c ALWAYS for @c StateProviderInterfaces that are registered with the - * @c ContextManager. Request for context by calling @c getContext. Expect that the context is returned within the - * timeout period. - * - * There's a dummyProvider with StateRefreshPolicy @c SOMETIMES that returns an empty context. Check ContextManager is - * okay with it and would include the context provided by the dummyProvider. - * - * Check the context that is returned by the @c ContextManager. Expect it should match the test value. + * Test that a @c StateProvider that has a refreshPolicy @c SOMETIMES is queried on call to @c getContext(). + * If the stateProvider provides a valid (non-empty) state, test that it is reflected in the corresponding context that + * is generated. */ -TEST_F(ContextManagerTest, test_sometimesProvider) { +TEST_F(ContextManagerTest, test_sometimesProviderWithValidState) { auto sometimesProvider = std::make_shared(); auto sometimesCapability = NamespaceAndName("Namespace", "Name"); std::string sometimesPayload{R"({"state":"value"})"}; m_contextManager->setStateProvider(sometimesCapability, sometimesProvider); + utils::WaitEvent provideStateEvent; + EXPECT_CALL(*sometimesProvider, provideState(_, _)).WillOnce((InvokeWithoutArgs([&provideStateEvent] { + provideStateEvent.wakeUp(); + }))); + + auto requester = std::make_shared(); + std::promise contextStatesPromise; + EXPECT_CALL(*requester, onContextAvailable(_, _, _)) + .WillOnce(WithArg<1>(Invoke([&contextStatesPromise](const AVSContext& context) { + contextStatesPromise.set_value(context.getStates()); + }))); + + auto requestToken = m_contextManager->getContext(requester); + const std::chrono::milliseconds timeout{100}; + provideStateEvent.wait(timeout); ASSERT_EQ( - m_contextManager->setState(sometimesCapability, sometimesPayload, StateRefreshPolicy::SOMETIMES), + m_contextManager->setState(sometimesCapability, sometimesPayload, StateRefreshPolicy::SOMETIMES, requestToken), SetStateResult::SUCCESS); + auto statesFuture = contextStatesPromise.get_future(); + ASSERT_EQ(statesFuture.wait_for(timeout), std::future_status::ready); + EXPECT_EQ(statesFuture.get()[sometimesCapability].valuePayload, sometimesPayload); +} + +/** + * Test that a @c StateProvider that has a refreshPolicy @c SOMETIMES is queried on call to @c getContext(). + * If the stateProvider provides an empty state, test that it is NOT reflected in the corresponding context that is + * generated. + */ +TEST_F(ContextManagerTest, test_sometimesProviderWithEmptyState) { + auto sometimesProvider = std::make_shared(); + auto sometimesCapability = NamespaceAndName("Namespace", "Name"); + m_contextManager->setStateProvider(sometimesCapability, sometimesProvider); + utils::WaitEvent provideStateEvent; EXPECT_CALL(*sometimesProvider, provideState(_, _)).WillOnce((InvokeWithoutArgs([&provideStateEvent] { provideStateEvent.wakeUp(); @@ -283,7 +308,8 @@ TEST_F(ContextManagerTest, test_sometimesProvider) { auto statesFuture = contextStatesPromise.get_future(); ASSERT_EQ(statesFuture.wait_for(timeout), std::future_status::ready); - EXPECT_EQ(statesFuture.get()[sometimesCapability].valuePayload, sometimesPayload); + auto states = statesFuture.get(); + EXPECT_EQ(states.find(sometimesCapability), states.end()); } /** diff --git a/Endpoints/include/Endpoints/EndpointAttributeValidation.h b/Endpoints/include/Endpoints/EndpointAttributeValidation.h index a952183e..31549d6f 100644 --- a/Endpoints/include/Endpoints/EndpointAttributeValidation.h +++ b/Endpoints/include/Endpoints/EndpointAttributeValidation.h @@ -1,5 +1,5 @@ /* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2019-2020 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. @@ -26,7 +26,7 @@ namespace endpoints { /** * Returns whether the given identifier follows AVS specification. * - * @see https://developer.amazon.com/alexa-voice-service/alexa-discovery.html#addorupdatereport + * @see https://developer.amazon.com/docs/alexa/alexa-voice-service/alexa-discovery.html#addorupdatereport * * @param identifier The identifier to be validated. * @return @c true if valid; otherwise, return @c false. @@ -36,7 +36,7 @@ bool isEndpointIdValid(const avsCommon::sdkInterfaces::endpoints::EndpointIdenti /** * Returns whether the given name follows AVS specification. * - * @see https://developer.amazon.com/alexa-voice-service/alexa-discovery.html#addorupdatereport + * @see https://developer.amazon.com/docs/alexa/alexa-voice-service/alexa-discovery.html#addorupdatereport * * @param name The friendly name to be validated. * @return @c true if valid; otherwise, return @c false. @@ -46,7 +46,7 @@ bool isFriendlyNameValid(const std::string& name); /** * Returns whether the given description follows AVS specification. * - * @see https://developer.amazon.com/alexa-voice-service/alexa-discovery.html#addorupdatereport + * @see https://developer.amazon.com/docs/alexa/alexa-voice-service/alexa-discovery.html#addorupdatereport * * @param description The description to be validated. * @return @c true if valid; otherwise, return @c false. @@ -56,7 +56,7 @@ bool isDescriptionValid(const std::string& description); /** * Returns whether the given manufacturer name follows AVS specification. * - * @see https://developer.amazon.com/alexa-voice-service/alexa-discovery.html#addorupdatereport + * @see https://developer.amazon.com/docs/alexa/alexa-voice-service/alexa-discovery.html#addorupdatereport * * @param manufacturerName The manufacturer name to be validated. * @return @c true if valid; otherwise, return @c false. @@ -67,7 +67,7 @@ bool isManufacturerNameValid(const std::string& manufacturerName); * Returns whether the given attributes follows AVS specification. * * See format specification here: - * @see https://developer.amazon.com/alexa-voice-service/alexa-discovery.html#addorupdatereport + * @see https://developer.amazon.com/docs/alexa/alexa-voice-service/alexa-discovery.html#addorupdatereport * * @param attributes The attributes to be validated. * @return @c true if all attributes are valid; otherwise, return @c false. @@ -78,7 +78,7 @@ bool isAdditionalAttributesValid( /** * Returns whether the given connections values follows AVS specification. * - * @see https://developer.amazon.com/alexa-voice-service/alexa-discovery.html#addorupdatereport + * @see https://developer.amazon.com/docs/alexa/alexa-voice-service/alexa-discovery.html#addorupdatereport * * @param connections The list of maps of connections objects * @return @c true if valid; otherwise, return @c false. @@ -88,7 +88,7 @@ bool areConnectionsValid(const std::vector>& /** * Returns whether the given cookies follow the AVS specification. * - * @see https://developer.amazon.com/alexa-voice-service/alexa-discovery.html#addorupdatereport + * @see https://developer.amazon.com/docs/alexa/alexa-voice-service/alexa-discovery.html#addorupdatereport * * @param cookies The map of cookies name and values. * @return @c true if valid; otherwise, return @c false. diff --git a/Endpoints/include/Endpoints/EndpointBuilder.h b/Endpoints/include/Endpoints/EndpointBuilder.h index 21dabaa9..6bf3bb1d 100644 --- a/Endpoints/include/Endpoints/EndpointBuilder.h +++ b/Endpoints/include/Endpoints/EndpointBuilder.h @@ -166,6 +166,14 @@ public: */ bool finishDefaultEndpointConfiguration(); + /** + * Builds the default endpoint. + * + * This will ensure that only the owner of this builder can actually build the default endpoint. + * @return Whether the build succeeded or not. + */ + bool buildDefaultEndpoint(); + private: /// Defines a function that can be used to build a capability. using CapabilityBuilder = @@ -187,6 +195,13 @@ private: std::shared_ptr exceptionSender, std::shared_ptr alexaMessageSender); + /** + * Implements the build logic used by @c buildDefaultEndpoint() and @c build(). + * + * @return A unique endpointId if the build succeeds; otherwise, an empty endpointId object. + */ + avsCommon::utils::Optional buildImplementation(); + /// Flag used to identify the default endpoint builder. /// We use this flag to restrict the amount of customization available for the default endpoint. bool m_isDefaultEndpoint; diff --git a/Endpoints/src/EndpointBuilder.cpp b/Endpoints/src/EndpointBuilder.cpp index ad299fa1..2370363e 100644 --- a/Endpoints/src/EndpointBuilder.cpp +++ b/Endpoints/src/EndpointBuilder.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2019-2020 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. @@ -54,6 +54,9 @@ const std::string ENDPOINT_ID_CONCAT = "::"; /// We will limit the suffix length to 10 characters for now to ensure that we don't go over the endpointId length. static constexpr size_t MAX_SUFFIX_LENGTH = 10; +/// The display category for the AVS device endpoint; +const std::string ALEXA_DISPLAY_CATEGORY = "ALEXA_VOICE_ENABLED"; + std::unique_ptr EndpointBuilder::create( const avsCommon::utils::DeviceInfo& deviceInfo, std::shared_ptr endpointRegistrationManager, @@ -131,7 +134,7 @@ bool EndpointBuilder::finishDefaultEndpointConfiguration() { m_attributes.registration.set( EndpointAttributes::Registration(m_deviceInfo.getProductId(), m_deviceInfo.getDeviceSerialNumber())); m_attributes.endpointId = m_deviceInfo.getDefaultEndpointId(); - m_attributes.displayCategories = {"ALEXA_VOICE_ENABLED"}; + m_attributes.displayCategories = {ALEXA_DISPLAY_CATEGORY}; m_attributes.manufacturerName = m_deviceInfo.getManufacturerName(); m_attributes.description = m_deviceInfo.getDeviceDescription(); m_isDefaultEndpoint = true; @@ -421,14 +424,30 @@ alexaClientSDK::endpoints::EndpointBuilder& alexaClientSDK::endpoints::EndpointB } avsCommon::utils::Optional EndpointBuilder::build() { + if (m_isDefaultEndpoint) { + ACSDK_ERROR(LX("buildFailed").d("reason", "buildDefaultEndpointNotAllowed")); + return avsCommon::utils::Optional(); + } + return buildImplementation(); +} + +bool EndpointBuilder::buildDefaultEndpoint() { + if (!m_isDefaultEndpoint) { + ACSDK_ERROR(LX("buildDefaultEndpointFailed").d("reason", "notDefaultEndpoint")); + return false; + } + return buildImplementation().hasValue(); +} + +avsCommon::utils::Optional EndpointBuilder::buildImplementation() { avsCommon::utils::Optional endpointId; if (m_hasBeenBuilt) { - ACSDK_ERROR(LX("buildFailed").d("reason", "endpointAlreadyBuilt")); + ACSDK_ERROR(LX("buildImplementationFailed").d("reason", "endpointAlreadyBuilt")); return endpointId; } if (m_invalidConfiguration) { - ACSDK_ERROR(LX("buildFailed").d("reason", "invalidConfiguration")); + ACSDK_ERROR(LX("buildImplementationFailed").d("reason", "invalidConfiguration")); return endpointId; } @@ -445,7 +464,7 @@ avsCommon::utils::Optional EndpointBuilder: auto alexaCapabilityAgent = capabilityAgents::alexa::AlexaInterfaceCapabilityAgent::create( m_deviceInfo, m_attributes.endpointId, m_exceptionSender, m_alexaMessageSender); if (!alexaCapabilityAgent) { - ACSDK_ERROR(LX("buildFailed").d("reason", "unableToCreateAlexaCapabilityAgent")); + ACSDK_ERROR(LX("buildImplementationFailed").d("reason", "unableToCreateAlexaCapabilityAgent")); return endpointId; } endpoint->addCapability(alexaCapabilityAgent->getCapabilityConfiguration(), alexaCapabilityAgent); @@ -456,7 +475,7 @@ avsCommon::utils::Optional EndpointBuilder: if (!capability.second) { // Default endpoint might have capability configurations without a directive handler. if (!m_isDefaultEndpoint) { - ACSDK_ERROR(LX("buildFailed").d("reason", "buildCapabilityFailed")); + ACSDK_ERROR(LX("buildImplementationFailed").d("reason", "buildCapabilityFailed")); return endpointId; } endpoint->addCapabilityConfiguration(capability.first); @@ -473,7 +492,9 @@ avsCommon::utils::Optional EndpointBuilder: if ((resultFuture.wait_for(std::chrono::milliseconds(0)) == std::future_status::ready)) { auto result = resultFuture.get(); if (result != EndpointRegistrationManagerInterface::RegistrationResult::SUCCEEDED) { - ACSDK_ERROR(LX("buildFailed").d("reason", "registrationFailed").d("result", static_cast(result))); + ACSDK_ERROR(LX("buildImplementationFailed") + .d("reason", "registrationFailed") + .d("result", static_cast(result))); return endpointId; } } diff --git a/Endpoints/test/EndpointRegistrationManagerTest.cpp b/Endpoints/test/EndpointRegistrationManagerTest.cpp index d0645c03..f82ff4e8 100644 --- a/Endpoints/test/EndpointRegistrationManagerTest.cpp +++ b/Endpoints/test/EndpointRegistrationManagerTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2019-2020 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. @@ -160,7 +160,8 @@ TEST_F(EndpointRegistrationManagerTest, test_registerNullEndpointFailsImmediatel EXPECT_EQ(result.get(), RegistrationResult::CONFIGURATION_ERROR); } -TEST_F(EndpointRegistrationManagerTest, test_registerDuplicatedEndpointIdFails) { +// Disabling unit test according to ACSDK-3414 +TEST_F(EndpointRegistrationManagerTest, DISABLED_test_registerDuplicatedEndpointIdFails) { // Configure endpoint object expectations. auto endpoint = std::make_shared(); AVSDiscoveryEndpointAttributes attributes; diff --git a/EqualizerImplementations/include/EqualizerImplementations/EqualizerController.h b/EqualizerImplementations/include/EqualizerImplementations/EqualizerController.h index ca5c5325..0a016e30 100644 --- a/EqualizerImplementations/include/EqualizerImplementations/EqualizerController.h +++ b/EqualizerImplementations/include/EqualizerImplementations/EqualizerController.h @@ -1,5 +1,5 @@ /* - * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018-2020 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. @@ -187,30 +187,22 @@ private: /** * Performs the actual equalizer state change. Applies changes to all equalizer implementations, then notifies - * all listeners of the changes applied if any. Method assumes that m_stateMutex is locked. + * all listeners of the changes applied if any. */ - void updateStateLocked(); + void updateState(); /** * Applies transformation operation over the current equalizer state's band levels. - * Method assumes m_stateMutex to be locked. * * @param changesDataMap @c EqualizerBandLevelMap containing changes data for required bands. * @param operation Modification operation to perform on current state's band value and a change. Operation accepts * original band level and data for the change to be applied and returns modified value. */ - void applyChangesToCurrentStateLocked( + void applyChangesToCurrentState( const avsCommon::sdkInterfaces::audio::EqualizerBandLevelMap& changesDataMap, std::function operation); - /** - * Internal method to start equalizer band changes. Method assumes m_stateMutex to be locked. - * - * @param bandLevelMap New levels for the equalizer bands. - */ - void setBandLevelsLocked(const avsCommon::sdkInterfaces::audio::EqualizerBandLevelMap& bandLevelMap); - /** * Loads equalizer state from persistent storage. * @@ -226,6 +218,13 @@ private: */ int truncateBandLevel(int level); + /** + * Notify all listeners for equalizer state changes. + * + * @param changed band level changed or not. + */ + void notifyListenersOnStateChanged(bool changed); + /// Interface to handle equalizer mode changes. std::shared_ptr m_modeController; diff --git a/EqualizerImplementations/src/EqualizerController.cpp b/EqualizerImplementations/src/EqualizerController.cpp index 54dd8956..76478071 100644 --- a/EqualizerImplementations/src/EqualizerController.cpp +++ b/EqualizerImplementations/src/EqualizerController.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018-2020 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. @@ -140,13 +140,15 @@ int EqualizerController::truncateBandLevel(int level) { } void EqualizerController::setBandLevel(EqualizerBand band, int level) { - std::lock_guard guard(m_stateMutex); + std::unique_lock lock(m_stateMutex); level = truncateBandLevel(level); auto iter = m_currentState.bandLevels.find(band); if (m_currentState.bandLevels.end() != iter) { if (iter->second == level) { + lock.unlock(); + notifyListenersOnStateChanged(false); return; } iter->second = level; @@ -154,19 +156,18 @@ void EqualizerController::setBandLevel(EqualizerBand band, int level) { ACSDK_ERROR(LX("setBandLevelFailed").d("reason", "Invalid band requested")); return; } - - updateStateLocked(); + lock.unlock(); + updateState(); } void EqualizerController::setBandLevels(const EqualizerBandLevelMap& bandLevelMap) { - std::lock_guard guard(m_stateMutex); - - setBandLevelsLocked(bandLevelMap); + applyChangesToCurrentState(bandLevelMap, [](int originalValue, int changeValue) { return changeValue; }); } -void EqualizerController::applyChangesToCurrentStateLocked( +void EqualizerController::applyChangesToCurrentState( const EqualizerBandLevelMap& changesDataMap, std::function operation) { + std::unique_lock lock(m_stateMutex); bool hasChanges = false; bool hasInvalidBands = false; @@ -185,30 +186,25 @@ void EqualizerController::applyChangesToCurrentStateLocked( hasInvalidBands = true; } } + lock.unlock(); if (hasChanges) { - updateStateLocked(); - } - - if (hasInvalidBands) { + updateState(); + } else if (hasInvalidBands) { ACSDK_WARN(LX(__func__).m("Invalid bands requested")); + } else { + notifyListenersOnStateChanged(false); } } -void EqualizerController::setBandLevelsLocked(const EqualizerBandLevelMap& bandLevelMap) { - applyChangesToCurrentStateLocked(bandLevelMap, [](int originalValue, int changeValue) { return changeValue; }); -} - void EqualizerController::adjustBandLevels(const EqualizerBandLevelMap& bandAdjustmentMap) { - std::lock_guard guard(m_stateMutex); - - applyChangesToCurrentStateLocked(bandAdjustmentMap, [this](int originalValue, int changeValue) { + applyChangesToCurrentState(bandAdjustmentMap, [this](int originalValue, int changeValue) { return truncateBandLevel(originalValue + changeValue); }); } void EqualizerController::resetBands(const std::set& bands) { - std::lock_guard guard(m_stateMutex); + std::unique_lock lock(m_stateMutex); bool hasChanges = false; bool hasInvalidBands = false; @@ -228,13 +224,14 @@ void EqualizerController::resetBands(const std::set& bands) { hasInvalidBands = true; } } + lock.unlock(); if (hasChanges) { - updateStateLocked(); - } - - if (hasInvalidBands) { + updateState(); + } else if (hasInvalidBands) { ACSDK_WARN(LX(__func__).m("Invalid bands requested")); + } else { + notifyListenersOnStateChanged(false); } } @@ -270,9 +267,10 @@ void EqualizerController::setCurrentMode(EqualizerMode mode) { } { - std::lock_guard stateGuard(m_stateMutex); + std::unique_lock lock(m_stateMutex); m_currentState.mode = mode; - updateStateLocked(); + lock.unlock(); + updateState(); } } @@ -345,7 +343,8 @@ std::shared_ptr EqualizerController::getConfigu return m_configuration; } -void EqualizerController::updateStateLocked() { +void EqualizerController::updateState() { + std::unique_lock lock(m_stateMutex); std::string stateString = "mode=" + equalizerModeToString(m_currentState.mode); for (auto& bandIt : m_currentState.bandLevels) { EqualizerBand band = bandIt.first; @@ -373,8 +372,21 @@ void EqualizerController::updateStateLocked() { } } - for (auto listener : m_listeners) { - listener->onEqualizerStateChanged(m_currentState); + lock.unlock(); + notifyListenersOnStateChanged(true); +} + +void EqualizerController::notifyListenersOnStateChanged(bool changed) { + std::unique_lock lock(m_stateMutex); + auto listenersCopy = m_listeners; + auto currentStateCopy = m_currentState; + lock.unlock(); + for (const auto& listener : listenersCopy) { + if (changed) { + listener->onEqualizerStateChanged(currentStateCopy); + } else { + listener->onEqualizerSameStateChanged(currentStateCopy); + } } } diff --git a/EqualizerImplementations/test/EqualizerControllerTest.cpp b/EqualizerImplementations/test/EqualizerControllerTest.cpp index a111a855..22e30438 100644 --- a/EqualizerImplementations/test/EqualizerControllerTest.cpp +++ b/EqualizerImplementations/test/EqualizerControllerTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018-2020 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. @@ -206,8 +206,10 @@ TEST_F(EqualizerControllerTest, test_providedBandLevelChanges_addRemoveListener_ controller->setBandLevel(EqualizerBand::TREBLE, NON_DEFAULT_TREBLE); EXPECT_EQ(reportedState.bandLevels[EqualizerBand::TREBLE], NON_DEFAULT_TREBLE); - // Call again with the same value. Must not report changes + // Call again with the same value. Must not report changes by onEqualizerStateChanged() EXPECT_CALL(*(listener.get()), onEqualizerStateChanged(_)).Times(0); + // Call again with the same value. Must report changes by onEqualizerSameStateChanged() once + EXPECT_CALL(*(listener.get()), onEqualizerSameStateChanged(_)).Times(AtLeast(1)); controller->setBandLevel(EqualizerBand::TREBLE, NON_DEFAULT_TREBLE); // Reset level to default and try to adjust. diff --git a/Integration/AlexaClientSDKConfig.json b/Integration/AlexaClientSDKConfig.json index 859e9b1b..35f9d9c5 100644 --- a/Integration/AlexaClientSDKConfig.json +++ b/Integration/AlexaClientSDKConfig.json @@ -92,7 +92,7 @@ // Note: The firmware version should be a positive 32-bit integer in the range [1-2147483647]. // e.g. "firmwareVersion": 123 // The default endpoint to connect to. - // See https://developer.amazon.com/docs/alexa-voice-service/api-overview.html#endpoints for regions and values + // See https://developer.amazon.com/docs/alexa/alexa-voice-service/api-overview.html#endpoints for regions and values // e.g. "endpoint": "https://alexa.na.gateway.devices.a2z.com" // Example of specifying suggested latency in seconds when openning PortAudio stream. By default, diff --git a/Integration/include/Integration/ACLTestContext.h b/Integration/include/Integration/ACLTestContext.h index 1a26a967..3ba1bbef 100644 --- a/Integration/include/Integration/ACLTestContext.h +++ b/Integration/include/Integration/ACLTestContext.h @@ -1,5 +1,5 @@ /* - * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018-2020 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. @@ -27,8 +27,8 @@ #include #include "Integration/AuthDelegateTestContext.h" -#include "Integration/SDKTestContext.h" #include "Integration/ConnectionStatusObserver.h" +#include "Integration/SDKTestContext.h" namespace alexaClientSDK { namespace integration { diff --git a/Integration/include/Integration/TestMediaPlayer.h b/Integration/include/Integration/TestMediaPlayer.h index ee4001dc..07f7282a 100644 --- a/Integration/include/Integration/TestMediaPlayer.h +++ b/Integration/include/Integration/TestMediaPlayer.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -78,6 +78,9 @@ public: void removeObserver( std::shared_ptr playerObserver) override; + avsCommon::utils::Optional getMediaPlayerState( + avsCommon::utils::mediaPlayer::MediaPlayerInterface::SourceId id) override; + uint64_t getNumBytesBuffered() override; private: diff --git a/Integration/include/Integration/TestSpeechSynthesizerObserver.h b/Integration/include/Integration/TestSpeechSynthesizerObserver.h index 20bc7128..43ae780f 100644 --- a/Integration/include/Integration/TestSpeechSynthesizerObserver.h +++ b/Integration/include/Integration/TestSpeechSynthesizerObserver.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -17,9 +17,9 @@ #define ALEXA_CLIENT_SDK_INTEGRATION_INCLUDE_INTEGRATION_TESTSPEECHSYNTHESIZEROBSERVER_H_ #include +#include #include #include -#include #include @@ -37,7 +37,9 @@ public: ~TestSpeechSynthesizerObserver() = default; void onStateChanged( - avsCommon::sdkInterfaces::SpeechSynthesizerObserverInterface::SpeechSynthesizerState state) override; + avsCommon::sdkInterfaces::SpeechSynthesizerObserverInterface::SpeechSynthesizerState state, + const avsCommon::utils::mediaPlayer::MediaPlayerInterface::SourceId mediaSourceId, + const avsCommon::utils::Optional& mediaPlayerState) override; bool checkState( const avsCommon::sdkInterfaces::SpeechSynthesizerObserverInterface::SpeechSynthesizerState expectedState, diff --git a/Integration/src/ACLTestContext.cpp b/Integration/src/ACLTestContext.cpp index 95865ef1..8a745f74 100644 --- a/Integration/src/ACLTestContext.cpp +++ b/Integration/src/ACLTestContext.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018-2020 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. @@ -13,19 +13,16 @@ * permissions and limitations under the License. */ -#include #include #include #include #include -#include #include #include #include -#include #include #include "Integration/ACLTestContext.h" @@ -36,7 +33,6 @@ namespace test { using namespace acl; using namespace avsCommon::avs::attachment; -using namespace avsCommon::avs::initialization; using namespace avsCommon::sdkInterfaces; using namespace avsCommon::utils::configuration; using namespace avsCommon::utils::libcurlUtils; diff --git a/Integration/src/SDKTestContext.cpp b/Integration/src/SDKTestContext.cpp index 2de5312e..084c945b 100644 --- a/Integration/src/SDKTestContext.cpp +++ b/Integration/src/SDKTestContext.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018-2020 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. diff --git a/Integration/src/TestMediaPlayer.cpp b/Integration/src/TestMediaPlayer.cpp index 37b124bf..428040e1 100644 --- a/Integration/src/TestMediaPlayer.cpp +++ b/Integration/src/TestMediaPlayer.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -24,6 +24,10 @@ namespace test { /// String to identify log entries originating from this file. static const std::string TAG("TestMediaPlayer"); +/// Default media player state for reporting all playback eventing +static const avsCommon::utils::mediaPlayer::MediaPlayerState DEFAULT_MEDIA_PLAYER_STATE = { + std::chrono::milliseconds(0)}; + /// A counter used to increment the source id when a new source is set. static std::atomic g_sourceId{0}; @@ -66,7 +70,7 @@ bool TestMediaPlayer::play(avsCommon::utils::mediaPlayer::MediaPlayerInterface:: return false; } for (const auto& observer : m_observers) { - observer->onPlaybackStarted(id); + observer->onPlaybackStarted(id, DEFAULT_MEDIA_PLAYER_STATE); } m_playbackFinished = true; m_timer = std::unique_ptr(new avsCommon::utils::timing::Timer); @@ -74,7 +78,7 @@ bool TestMediaPlayer::play(avsCommon::utils::mediaPlayer::MediaPlayerInterface:: m_timer->start(std::chrono::milliseconds(600), [this, id] { for (const auto& observer : m_observers) { if (m_playbackFinished) { - observer->onPlaybackFinished(id); + observer->onPlaybackFinished(id, DEFAULT_MEDIA_PLAYER_STATE); } } m_playbackFinished = false; @@ -88,7 +92,7 @@ bool TestMediaPlayer::stop(avsCommon::utils::mediaPlayer::MediaPlayerInterface:: } for (const auto& observer : m_observers) { if (m_playbackFinished) { - observer->onPlaybackStopped(id); + observer->onPlaybackStopped(id, DEFAULT_MEDIA_PLAYER_STATE); } } m_playbackFinished = false; @@ -123,6 +127,14 @@ uint64_t TestMediaPlayer::getNumBytesBuffered() { return 0; } +avsCommon::utils::Optional TestMediaPlayer::getMediaPlayerState( + avsCommon::utils::mediaPlayer::MediaPlayerInterface::SourceId id) { + auto offset = getOffset(id); + auto state = avsCommon::utils::mediaPlayer::MediaPlayerState(); + state.offset = offset; + return avsCommon::utils::Optional(state); +} + } // namespace test } // namespace integration } // namespace alexaClientSDK diff --git a/Integration/src/TestSpeechSynthesizerObserver.cpp b/Integration/src/TestSpeechSynthesizerObserver.cpp index 9c8f48f0..d9cfec9a 100644 --- a/Integration/src/TestSpeechSynthesizerObserver.cpp +++ b/Integration/src/TestSpeechSynthesizerObserver.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -25,7 +25,10 @@ TestSpeechSynthesizerObserver::TestSpeechSynthesizerObserver() : m_state(SpeechSynthesizerObserverInterface::SpeechSynthesizerState::FINISHED) { } -void TestSpeechSynthesizerObserver::onStateChanged(SpeechSynthesizerObserverInterface::SpeechSynthesizerState state) { +void TestSpeechSynthesizerObserver::onStateChanged( + SpeechSynthesizerObserverInterface::SpeechSynthesizerState state, + const avsCommon::utils::mediaPlayer::MediaPlayerInterface::SourceId mediaSourceId, + const avsCommon::utils::Optional& mediaPlayerState) { std::unique_lock lock(m_mutex); m_state = state; m_queue.push_back(state); @@ -47,6 +50,7 @@ SpeechSynthesizerObserverInterface::SpeechSynthesizerState TestSpeechSynthesizer if (!m_wakeTrigger.wait_for(lock, duration, [this]() { return !m_queue.empty(); })) { return m_state; } + ret = m_queue.front(); m_queue.pop_front(); return ret; diff --git a/Integration/test/AlertsIntegrationTest.cpp b/Integration/test/AlertsIntegrationTest.cpp index 067470af..59b3749a 100644 --- a/Integration/test/AlertsIntegrationTest.cpp +++ b/Integration/test/AlertsIntegrationTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2020 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. @@ -36,9 +36,6 @@ #include #include #include -#include -#include -#include #include