769 lines
36 KiB
C++
769 lines
36 KiB
C++
////////////////////////////////////////////////////////////////////////////////
|
|
// (c) Copyright, Ubisoft Entertainment Inc. || Created by Harbour //
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
#include "ubiservices/applicationStateManager.h"
|
|
#include "ubiservices/core/configs/configpriv.h"
|
|
#include "ubiservices/core/chrono/chronometer.h"
|
|
#include "ubiservices/core/chrono/clockSystem.h"
|
|
#include "ubiservices/core/debug/assert.h"
|
|
#include "ubiservices/core/helpers/facadesManager.h"
|
|
#include "ubiservices/core/helpers/helpers.h"
|
|
#include "ubiservices/core/helpers/instancesManager.h"
|
|
#include "ubiservices/core/locks/criticalSection.h"
|
|
#include "ubiservices/core/locks/scopedCS.h"
|
|
#include "ubiservices/core/log/eventLogHelper.h"
|
|
#include "ubiservices/core/log/remoteLoggerHelper.h"
|
|
#include "ubiservices/core/notifications/notificationSource.inl"
|
|
#include "ubiservices/core/tasks/asyncResultBatch.h"
|
|
#include "ubiservices/core/tasks/asyncResultInternal.h"
|
|
#include "ubiservices/core/tasks/JobManager.h"
|
|
#include "ubiservices/core/tasks/scheduler.h"
|
|
#include "ubiservices/facadeInternal.h"
|
|
#include "ubiservices/core/helpers/platformHelper/platformHelperProxy.h"
|
|
#include "ubiservices/services/authentication/suspendedMode/jobResumeFocusMaster.h"
|
|
#include "ubiservices/services/authentication/suspendedMode/jobResumeFromSuspended.h"
|
|
#include "ubiservices/services/event/eventClientImpl.h"
|
|
#include "ubiservices/services/event/eventClientProxy.h"
|
|
#include "ubiservices/services/parameters/parametersSettings.h"
|
|
#if defined(US_TARGET_WITH_GDK)
|
|
#include <appnotify.h>
|
|
#endif
|
|
|
|
namespace US_NS {;
|
|
|
|
// Note 1 : We use a macro instead of a function in order to keep US_FILE and US_LINE accurate
|
|
// Note 2 : We need to be thread safe in this macro and take into account that a Facade can be removed
|
|
// while executing this macro. So we keep the facade alive by copying the SmartPtr while the remote log
|
|
// is generated.
|
|
#define US_WRITE_LOG_OR_REMOTE_LOG(instancesManagerPtr, logLevel, log) \
|
|
do { \
|
|
if ((instancesManagerPtr) != nullptr) \
|
|
{ \
|
|
SmartPtr<FacadeInternal> firstFacade = ApplicationStateManager_BF::getFirstFacade(*instancesManagerPtr); \
|
|
if (firstFacade != nullptr) \
|
|
{ \
|
|
US_LOG_REMOTE(*firstFacade, (logLevel), LogCategory::Core, (log)); \
|
|
} \
|
|
else \
|
|
{ \
|
|
US_LOG((logLevel), LogCategory::Core, (log)); \
|
|
} \
|
|
} \
|
|
else \
|
|
{ \
|
|
US_LOG((logLevel), LogCategory::Core, (log)); \
|
|
} \
|
|
} ONCE
|
|
|
|
namespace ApplicationStateManager_BF
|
|
{
|
|
SmartPtr<FacadeInternal> getFirstFacade(InstancesManager& im)
|
|
{
|
|
// Note : We need to be thread safe in this function and take into account that a Facade can removed
|
|
// while executing this function. So we first retrieve a copy of the facade list (using smartPtr)
|
|
// And we return a SmartPtr on the facade, so that the FacadeInternal won't be deleted while using it.
|
|
const Vector<SmartPtr<FacadeInternal>> facadesList = im.getFacadesManager().getFacadesList();
|
|
return (facadesList.size() > 0)
|
|
? im.getFacadesManager().getFacadesList().front()
|
|
: nullptr;
|
|
}
|
|
}
|
|
|
|
#if defined(US_TARGET_WITH_GDK)
|
|
class ApplicationEventsManager final
|
|
{
|
|
public:
|
|
ApplicationEventsManager()
|
|
: m_appStateChangeRoutine(nullptr)
|
|
, m_appContrainsChangeRoutine(nullptr)
|
|
{
|
|
InstancesManager* ctrInstancesManager = US_NS::InstancesManager::getInstance();
|
|
|
|
StringStream ssRegisterFail;
|
|
ssRegisterFail << "Function CreateEventEx returned invalid handle for suspended/background mode event registration.\n";
|
|
ssRegisterFail << "Several things will not be completely working as intended: Primary and secondary store updates, many club/uplay related features and others.\n";
|
|
ssRegisterFail << "In order to work, this function must be called from the main UI thread using\n";
|
|
ssRegisterFail << "US_NS::configureSdk().\n";
|
|
ssRegisterFail << "Note : This is necessary only on XBoxOne & Scarlett platforms";
|
|
|
|
const HRESULT resultSuspend = RegisterAppStateChangeNotification(
|
|
callbackStateChangeNotification, nullptr, &m_appStateChangeRoutine);
|
|
if (FAILED(resultSuspend))
|
|
{
|
|
US_WRITE_LOG_OR_REMOTE_LOG(ctrInstancesManager, LogLevel::Warning, ssRegisterFail.getContent());
|
|
}
|
|
|
|
const HRESULT resultConstrained = RegisterAppConstrainedChangeNotification(
|
|
callbackConstrainedChangeNotification, nullptr, &m_appContrainsChangeRoutine);
|
|
if (FAILED(resultConstrained))
|
|
{
|
|
US_WRITE_LOG_OR_REMOTE_LOG(ctrInstancesManager, LogLevel::Warning, ssRegisterFail.getContent());
|
|
}
|
|
}
|
|
|
|
~ApplicationEventsManager()
|
|
{
|
|
UnregisterAppStateChangeNotification(m_appStateChangeRoutine);
|
|
UnregisterAppConstrainedChangeNotification(m_appContrainsChangeRoutine);
|
|
}
|
|
|
|
private:
|
|
|
|
static void callbackStateChangeNotification(BOOLEAN suspending, PVOID context)
|
|
{
|
|
US_UNUSED(context);
|
|
|
|
StringStream ssSuspended;
|
|
ssSuspended << "ApplicationStateManager::ApplicationEventsManager received RegisterAppStateChangeNotification. Application state change to ";
|
|
|
|
InstancesManager* instancesManager = US_NS::InstancesManager::getInstance();
|
|
|
|
if (suspending)
|
|
{
|
|
s_isFirstPartySuspended = true;
|
|
ssSuspended << "SUSPENDED.";
|
|
US_WRITE_LOG_OR_REMOTE_LOG(instancesManager, LogLevel::Warning, ssSuspended.getContent());
|
|
US_NS::PlatformHelperProxy::changeState(US_NS::PlatformState::Suspended);
|
|
}
|
|
else
|
|
{
|
|
s_isFirstPartySuspended = false;
|
|
ssSuspended << "NOT-SUSPENDED.";
|
|
US_WRITE_LOG_OR_REMOTE_LOG(instancesManager, LogLevel::Warning, ssSuspended.getContent());
|
|
US_NS::PlatformHelperProxy::changeState(US_NS::PlatformState::Foreground);
|
|
}
|
|
}
|
|
|
|
static void callbackConstrainedChangeNotification(BOOLEAN constrained, PVOID context)
|
|
{
|
|
US_UNUSED(context);
|
|
|
|
StringStream ssConstrained;
|
|
ssConstrained << "ApplicationStateManager::ApplicationEventsManager received RegisterAppConstrainedChangeNotification. Application state change to ";
|
|
|
|
InstancesManager* instancesManager = US_NS::InstancesManager::getInstance();
|
|
|
|
if (constrained)
|
|
{
|
|
ssConstrained << "CONSTRAINED.";
|
|
US_WRITE_LOG_OR_REMOTE_LOG(instancesManager, LogLevel::Warning, ssConstrained.getContent());
|
|
US_NS::PlatformHelperProxy::changeState(US_NS::PlatformState::Background);
|
|
}
|
|
else
|
|
{
|
|
ssConstrained << "NOT-CONSTRAINED.";
|
|
US_WRITE_LOG_OR_REMOTE_LOG(instancesManager, LogLevel::Warning, ssConstrained.getContent());
|
|
if (s_isFirstPartySuspended)
|
|
{
|
|
// In this case, we receive a notification "back from constrained" but the device is still in suspended mode
|
|
// The UbiServices SDK should NOT go to foreground in such case, because the device is actually still suspended,
|
|
// and calls to the first party API will fail because of being suspended.
|
|
// The notification is ignored.
|
|
US_WRITE_LOG_OR_REMOTE_LOG(instancesManager, LogLevel::Warning, "Platform is in suspended mode, the back-from-constrained notification is ignored");
|
|
}
|
|
else
|
|
{
|
|
US_NS::PlatformHelperProxy::changeState(US_NS::PlatformState::Foreground);
|
|
}
|
|
}
|
|
}
|
|
|
|
PAPPSTATE_REGISTRATION m_appStateChangeRoutine;
|
|
PAPPCONSTRAIN_REGISTRATION m_appContrainsChangeRoutine;
|
|
|
|
static bool s_isFirstPartySuspended;
|
|
};
|
|
bool ApplicationEventsManager::s_isFirstPartySuspended = false;
|
|
|
|
#elif defined(US_TARGET_XONE)
|
|
|
|
namespace PlatformHelper_BF
|
|
{
|
|
StateNotificationType convertStateToEvent(PlatformState state);
|
|
}
|
|
|
|
ref class ApplicationEventsManager sealed
|
|
{
|
|
public:
|
|
ApplicationEventsManager()
|
|
{
|
|
using Windows::ApplicationModel::Core::CoreApplicationView;
|
|
using Windows::ApplicationModel::Core::CoreApplication;
|
|
|
|
m_handlerToken1 = CoreApplication::ResourceAvailabilityChanged::add(
|
|
ref new Windows::Foundation::EventHandler<Platform::Object^>(this, &ApplicationEventsManager::OnResourceAvailabilityChanged));
|
|
m_handlerToken2 = CoreApplication::Suspending::add(
|
|
ref new Windows::Foundation::EventHandler<Windows::ApplicationModel::SuspendingEventArgs^>(this, &ApplicationEventsManager::OnSuspending));
|
|
m_handlerToken3 = CoreApplication::Resuming::add(
|
|
ref new Windows::Foundation::EventHandler<Platform::Object^>(this, &ApplicationEventsManager::OnResuming));
|
|
|
|
CoreApplicationView^ mainView = CoreApplication::MainView;
|
|
|
|
US_ASSERT(mainView != nullptr, "Unexpected behaviour.");
|
|
|
|
// Note : Registering to Activated and VisibilityChanged events must be performed from the thread that launched
|
|
// Windows::ApplicationModel::Core::IFrameworkView::SetWindow(), otherwise, the += operation raises a WrongThreadException
|
|
// Do a first try in the current thread
|
|
try {
|
|
mainView->CoreWindow->Activated +=
|
|
ref new Windows::Foundation::TypedEventHandler<Windows::UI::Core::CoreWindow^,
|
|
Windows::UI::Core::WindowActivatedEventArgs^>(this, &ApplicationEventsManager::OnActivated);
|
|
mainView->CoreWindow->VisibilityChanged +=
|
|
ref new Windows::Foundation::TypedEventHandler<Windows::UI::Core::CoreWindow^,
|
|
Windows::UI::Core::VisibilityChangedEventArgs^>(this, &ApplicationEventsManager::OnVisibilityChanged);
|
|
}
|
|
catch (Platform::WrongThreadException^ exception)
|
|
{
|
|
// The current thread cannot register to events. Try with RunAsync(), this will run from the thread that calls
|
|
// Windows::UI::Core::CoreDispatcher::ProcessEvents()
|
|
mainView->CoreWindow->Dispatcher->RunAsync(Windows::UI::Core::CoreDispatcherPriority::Low,
|
|
ref new Windows::UI::Core::DispatchedHandler
|
|
([=]()
|
|
{
|
|
CoreApplicationView^ mainView = CoreApplication::MainView;
|
|
|
|
try
|
|
{
|
|
mainView->CoreWindow->Activated +=
|
|
ref new Windows::Foundation::TypedEventHandler<Windows::UI::Core::CoreWindow^,
|
|
Windows::UI::Core::WindowActivatedEventArgs^>(this, &ApplicationEventsManager::OnActivated);
|
|
mainView->CoreWindow->VisibilityChanged +=
|
|
ref new Windows::Foundation::TypedEventHandler<Windows::UI::Core::CoreWindow^,
|
|
Windows::UI::Core::VisibilityChangedEventArgs^>(this, &ApplicationEventsManager::OnVisibilityChanged);
|
|
}
|
|
catch (Platform::WrongThreadException^ e)
|
|
{
|
|
// This thread is still not the correct one. Printing an error log and rethrow the exception
|
|
US_LOG(LogLevel::Error, LogCategory::Core, "Couldn't register to the XBoxOne events for background detection. "
|
|
"Several things will not be completely working as intended : Primary and secondary store updates, "
|
|
"many club related features and others. In order to work, one of the following methods needs to be"
|
|
"run from the thread that calls the Windows::ApplicationModel::Core::IFrameworkView::SetWindow() method : "
|
|
"US_NS::configureSdk() or Windows::UI::Core::CoreDispatcher::ProcessEvents(). "
|
|
"Note : This is necessary only on XBoxOne platform");
|
|
throw;
|
|
}
|
|
}
|
|
));
|
|
}
|
|
}
|
|
|
|
virtual ~ApplicationEventsManager()
|
|
{
|
|
Windows::ApplicationModel::Core::CoreApplication::ResourceAvailabilityChanged -= m_handlerToken1;
|
|
Windows::ApplicationModel::Core::CoreApplication::Suspending -= m_handlerToken2;
|
|
Windows::ApplicationModel::Core::CoreApplication::Resuming -= m_handlerToken3;
|
|
}
|
|
|
|
void OnActivated(Windows::UI::Core::CoreWindow^ applicationView, Windows::UI::Core::WindowActivatedEventArgs^ args)
|
|
{
|
|
switch (args->WindowActivationState)
|
|
{
|
|
case Windows::UI::Core::CoreWindowActivationState::CodeActivated:
|
|
case Windows::UI::Core::CoreWindowActivationState::PointerActivated:
|
|
US_LOG(LogLevel::Debug, LogCategory::Core, "ApplicationStateManager: OnActivated where WindowActivationState is CodeActivated/PointerActivated");
|
|
US_NS::PlatformHelperProxy::changeState(US_NS::PlatformState::Foreground);
|
|
break;
|
|
case Windows::UI::Core::CoreWindowActivationState::Deactivated:
|
|
US_LOG(LogLevel::Debug, LogCategory::Core, "ApplicationStateManager: OnActivated where WindowActivationState is Deactivated");
|
|
US_NS::PlatformHelperProxy::changeState(US_NS::PlatformState::Background);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void OnVisibilityChanged(Windows::UI::Core::CoreWindow^ applicationView, Windows::UI::Core::VisibilityChangedEventArgs^ args)
|
|
{
|
|
US_LOG(LogLevel::Debug, LogCategory::Core, "ApplicationStateManager: OnVisibilityChanged to " << (args->Visible ? "Visible" : "Invisible"));
|
|
US_NS::PlatformHelperProxy::changeState(args->Visible
|
|
? US_NS::PlatformState::Foreground
|
|
: US_NS::PlatformState::Background);
|
|
}
|
|
|
|
void OnResourceAvailabilityChanged(Platform::Object^ sender, Platform::Object^ args)
|
|
{
|
|
using Windows::ApplicationModel::Core::CoreApplication;
|
|
using Windows::ApplicationModel::Core::ResourceAvailability;
|
|
|
|
InstancesManager* im = InstancesManager::getInstance();
|
|
US_WRITE_LOG_OR_REMOTE_LOG(im, LogLevel::Warning, US_STREAM(
|
|
"ApplicationStateManager: OnResourceAvailabilityChanged into ResourceAvailability::" << (
|
|
CoreApplication::ResourceAvailability == ResourceAvailability::Full ? "Full" :
|
|
CoreApplication::ResourceAvailability == ResourceAvailability::Constrained ? "Constrained" :
|
|
CoreApplication::ResourceAvailability == ResourceAvailability::FullWithExtendedSystemReserve ? "FullWithExtendedSystemReserve" : "Unknown")));
|
|
|
|
US_UNUSED(sender);
|
|
US_UNUSED(args);
|
|
|
|
switch (CoreApplication::ResourceAvailability)
|
|
{
|
|
case ResourceAvailability::Full:
|
|
case ResourceAvailability::FullWithExtendedSystemReserve:
|
|
US_NS::PlatformHelperProxy::changeState(US_NS::PlatformState::Foreground);
|
|
break;
|
|
case ResourceAvailability::Constrained:
|
|
break;
|
|
default:
|
|
US_ASSERT(false, "Unexpected enumeration value");
|
|
break;
|
|
}
|
|
}
|
|
|
|
void OnSuspending(Platform::Object^ sender, Windows::ApplicationModel::SuspendingEventArgs^ args)
|
|
{
|
|
US_LOG(LogLevel::Debug, LogCategory::Core, "ApplicationStateManager: OnSuspending");
|
|
US_NS::PlatformHelperProxy::changeState(US_NS::PlatformState::Suspended);
|
|
}
|
|
|
|
void OnResuming(Platform::Object^ sender, Platform::Object^ args)
|
|
{
|
|
US_LOG(LogLevel::Debug, LogCategory::Core ,"ApplicationStateManager: OnResuming");
|
|
US_NS::PlatformHelperProxy::changeState(US_NS::PlatformState::Foreground);
|
|
}
|
|
|
|
private:
|
|
Windows::Foundation::EventRegistrationToken m_handlerToken1;
|
|
Windows::Foundation::EventRegistrationToken m_handlerToken2;
|
|
Windows::Foundation::EventRegistrationToken m_handlerToken3;
|
|
};
|
|
#endif
|
|
|
|
ApplicationStateManager::ApplicationStateManager()
|
|
: m_jobManager(US_NEW(JobManager, MaxConcurrentCalls, "ApplicationStateManager"))
|
|
, m_currentSdkState(UbiServicesSdkStateInternal::Foreground)
|
|
, m_mustSwitchSdkToNextState(false)
|
|
, m_backgroundToForeground(Vector<Facade*>(), "ApplicationStateManager::m_backgroundToForeground")
|
|
, m_suspendedToForeground(Vector<Facade*>(), "ApplicationStateManager::m_suspendedToForeground")
|
|
, m_stateLock("stateLock", LockMasks::Global) // LockGlobal since cancel is called inside this critical section
|
|
, m_switchLock("switchLock", LockMasks::Last) // LockLast since it protects only a flag used only in this class
|
|
#if defined(US_TARGET_XONE) || defined(US_TARGET_MANJARO)
|
|
#if defined(US_TARGET_WITH_GDK)
|
|
, m_applicationEventsManager(US_NEW(ApplicationEventsManager))
|
|
#else
|
|
, m_applicationEventsManager(ref new ApplicationEventsManager())
|
|
#endif
|
|
, m_timeSinceLastTransitionFromSuspended(US_NEW(HighResolutionChrono,
|
|
ClockSteady::getNanoFromMilli(ParametersSettings::s_DEFAULT_VALUE_RESUME_FROM_SUSPENDED_DELAY_MSEC)))
|
|
#endif
|
|
{
|
|
}
|
|
|
|
ApplicationStateManager::~ApplicationStateManager()
|
|
{
|
|
#if defined(US_TARGET_XONE) || defined(US_TARGET_MANJARO)
|
|
#if defined(US_TARGET_WITH_GDK)
|
|
US_DELETE(m_applicationEventsManager);
|
|
#else
|
|
m_applicationEventsManager = nullptr;
|
|
#endif
|
|
US_DELETE(m_timeSinceLastTransitionFromSuspended);
|
|
#endif
|
|
}
|
|
|
|
AsyncResultBatch<Facade*, Empty> ApplicationStateManager::transitionTo(InstancesManager& im,
|
|
PlatformState toState)
|
|
{
|
|
US_PERF_TASK_FUNC();
|
|
AsyncResultInternal<Map<Facade*, AsyncResult<Empty> > > result(US_WHERE());
|
|
|
|
ScopedCS cs(m_stateLock);
|
|
updateSdkState();
|
|
|
|
switch (m_currentSdkState)
|
|
{
|
|
case UbiServicesSdkStateInternal::Foreground:
|
|
return transitionFromForegroundTo(im, toState, result);
|
|
case UbiServicesSdkStateInternal::Background:
|
|
return transitionFromBackgroundTo(im, toState, result);
|
|
case UbiServicesSdkStateInternal::Suspended:
|
|
return transitionFromSuspendedTo(im, toState, result);
|
|
case UbiServicesSdkStateInternal::BackgroundToForeground:
|
|
return transitionFromBackgroundToForegroundTo(im, toState, result);
|
|
case UbiServicesSdkStateInternal::SuspendedToForeground:
|
|
return transitionFromSuspendedToForegroundTo(im, toState, result);
|
|
case UbiServicesSdkStateInternal::SuspendedToForegroundToBackground:
|
|
return transitionFromSuspendedToForegroundToBackgroundTo(im, toState, result);
|
|
default:
|
|
SET_ASYNC_TO_ERROR(result, US_BUILD_ERROR_CODE(US_NS::Errors::RESUME_INVALID_STATE),
|
|
"UbiServices SDK is currently in an unknown/corrupted state. Reset the state to Foreground");
|
|
m_currentSdkState = UbiServicesSdkStateInternal::Foreground;
|
|
return AsyncResultBatch<Facade*, Empty>(result);
|
|
}
|
|
}
|
|
|
|
PlatformState ApplicationStateManager::getApplicationState()
|
|
{
|
|
ScopedCS cs(m_stateLock);
|
|
updateSdkState();
|
|
|
|
switch (m_currentSdkState)
|
|
{
|
|
case UbiServicesSdkStateInternal::Foreground:
|
|
return PlatformState::Foreground;
|
|
|
|
case UbiServicesSdkStateInternal::Background:
|
|
case UbiServicesSdkStateInternal::BackgroundToForeground:
|
|
return PlatformState::Background;
|
|
|
|
case UbiServicesSdkStateInternal::Suspended:
|
|
case UbiServicesSdkStateInternal::SuspendedToForeground:
|
|
case UbiServicesSdkStateInternal::SuspendedToForegroundToBackground:
|
|
return PlatformState::Suspended;
|
|
|
|
default:
|
|
US_ASSERT(false, US_STREAM_CSTR("UbiServicesSDKState is unknown/corrupted: " << m_currentSdkState));
|
|
return PlatformState::Foreground; // Return Foreground arbitrarily
|
|
}
|
|
}
|
|
|
|
void ApplicationStateManager::switchToNextState()
|
|
{
|
|
// This function is called from the scheduler thread, inside the scheduler lock.
|
|
// So updating the flag m_currentSdkState here cannot be done since the m_stateLock
|
|
// has a higher level than the scheduler lock (it could cause a dealock)
|
|
// In order to keep the flag m_currentSdkState thread-safe
|
|
// just raise a flag here to trigger the update of the state before using the state flags again
|
|
ScopedCS csSwitch(m_switchLock);
|
|
m_mustSwitchSdkToNextState = true;
|
|
}
|
|
|
|
UbiServicesSdkStateInternal::Enum ApplicationStateManager::getUbiServicesSdkStateInternal()
|
|
{
|
|
return m_currentSdkState;
|
|
}
|
|
|
|
void ApplicationStateManager::updateSdkState()
|
|
{
|
|
ScopedCS csSwitch(m_switchLock);
|
|
if (m_mustSwitchSdkToNextState)
|
|
{
|
|
switch (m_currentSdkState)
|
|
{
|
|
case UbiServicesSdkStateInternal::BackgroundToForeground:
|
|
case UbiServicesSdkStateInternal::SuspendedToForeground:
|
|
m_currentSdkState = UbiServicesSdkStateInternal::Foreground;
|
|
break;
|
|
|
|
case UbiServicesSdkStateInternal::SuspendedToForegroundToBackground:
|
|
m_currentSdkState = UbiServicesSdkStateInternal::Background;
|
|
break;
|
|
|
|
default:
|
|
// In other sdk states, there is no transition in progress,
|
|
// So just keep the current state
|
|
break;
|
|
}
|
|
m_mustSwitchSdkToNextState = false;
|
|
}
|
|
}
|
|
|
|
// Must be called inside the m_stateLock critical section
|
|
AsyncResultBatch<Facade*, Empty> ApplicationStateManager::transitionFromForegroundTo(InstancesManager& im,
|
|
PlatformState toState,
|
|
AsyncResultInternal<Map<Facade*, AsyncResult<Empty> > >& result)
|
|
{
|
|
switch (toState)
|
|
{
|
|
case PlatformState::Foreground:
|
|
// Keep the current state, do nothing
|
|
US_WRITE_LOG_OR_REMOTE_LOG(&im, LogLevel::Warning,
|
|
"Previous transition already led to Foreground (Current state : Foreground). New call to transitionTo(Foreground) has no effect.");
|
|
SET_ASYNC_TO_SUCCESS(result, "Success");
|
|
break;
|
|
|
|
case PlatformState::Background:
|
|
goToBackground(im, result);
|
|
break;
|
|
|
|
case PlatformState::Suspended:
|
|
goToSuspended(im, result);
|
|
break;
|
|
|
|
default:
|
|
US_ASSERT(false, US_STREAM_CSTR("Destination state is not valid: " << static_cast<uint32>(toState)));
|
|
SET_ASYNC_TO_ERROR(result, US_BUILD_ERROR_CODE(US_NS::Errors::SDK_STATE_TRANSITION_TO_INVALID), US_STREAM_CSTR("Destination state is not valid: " << static_cast<uint32>(toState)));
|
|
break;
|
|
}
|
|
|
|
return AsyncResultBatch<Facade*, Empty>(result);
|
|
}
|
|
|
|
// Must be called inside the m_stateLock critical section
|
|
AsyncResultBatch<Facade*, Empty> ApplicationStateManager::transitionFromBackgroundTo(InstancesManager& im, PlatformState toState,
|
|
AsyncResultInternal<Map<Facade*, AsyncResult<Empty> > >& result)
|
|
{
|
|
switch (toState)
|
|
{
|
|
case PlatformState::Foreground:
|
|
resumeFromBackground(im, result);
|
|
return m_backgroundToForeground;
|
|
|
|
case PlatformState::Background:
|
|
// Keep the current state, do nothing
|
|
US_WRITE_LOG_OR_REMOTE_LOG(&im, LogLevel::Warning,
|
|
"Previous transition already led to Background (Current state : Background). New call to transitionTo(Background) has no effect.");
|
|
SET_ASYNC_TO_SUCCESS(result, "Success");
|
|
break;
|
|
|
|
case PlatformState::Suspended:
|
|
goToSuspended(im, result);
|
|
break;
|
|
|
|
default:
|
|
US_ASSERT(false, US_STREAM_CSTR("Destination state is not valid: " << static_cast<uint32>(toState)));
|
|
SET_ASYNC_TO_ERROR(result, US_BUILD_ERROR_CODE(US_NS::Errors::SDK_STATE_TRANSITION_TO_INVALID), US_STREAM_CSTR("Destination state is not valid: " << static_cast<uint32>(toState)));
|
|
break;
|
|
}
|
|
|
|
return AsyncResultBatch<Facade*, Empty>(result);
|
|
}
|
|
|
|
// Must be called inside the m_stateLock critical section
|
|
AsyncResultBatch<Facade*, Empty> ApplicationStateManager::transitionFromSuspendedTo(InstancesManager& im, PlatformState toState,
|
|
AsyncResultInternal<Map<Facade*, AsyncResult<Empty> > >& result)
|
|
{
|
|
switch (toState)
|
|
{
|
|
case PlatformState::Foreground:
|
|
resumeFromSuspended(im, result);
|
|
return m_suspendedToForeground;
|
|
|
|
case PlatformState::Background:
|
|
// This transition is not supported, suspended to background should only be on the first-party side
|
|
SET_ASYNC_TO_ERROR(result, US_BUILD_ERROR_CODE(US_NS::Errors::RESUME_INVALID_STATE),
|
|
"Transition to Foreground is required");
|
|
break;
|
|
|
|
case PlatformState::Suspended:
|
|
{
|
|
US_WRITE_LOG_OR_REMOTE_LOG(&im, LogLevel::Warning,
|
|
"Previous transition already led to Suspended (Current state : Suspended). New call to transitionTo(Suspended) has no effect.");
|
|
SET_ASYNC_TO_SUCCESS(result, "Success");
|
|
}
|
|
break;
|
|
|
|
default:
|
|
US_ASSERT(false, US_STREAM_CSTR("Destination state is not valid: " << static_cast<uint32>(toState)));
|
|
SET_ASYNC_TO_ERROR(result, US_BUILD_ERROR_CODE(US_NS::Errors::SDK_STATE_TRANSITION_TO_INVALID), US_STREAM_CSTR("Destination state is not valid: " << static_cast<uint32>(toState)));
|
|
break;
|
|
}
|
|
|
|
return AsyncResultBatch<Facade*, Empty>(result);
|
|
}
|
|
|
|
// Must be called inside the m_stateLock critical section
|
|
AsyncResultBatch<Facade*, Empty> ApplicationStateManager::transitionFromBackgroundToForegroundTo(InstancesManager& im, PlatformState toState,
|
|
AsyncResultInternal<Map<Facade*, AsyncResult<Empty> > >& result)
|
|
{
|
|
switch (toState)
|
|
{
|
|
case PlatformState::Foreground:
|
|
// Already transitioning to Foreground, just return the existing asyncResult
|
|
US_WRITE_LOG_OR_REMOTE_LOG(&im, LogLevel::Warning,
|
|
"Transition Background -> Foreground already processing. Return the AsyncResult of the previous transition");
|
|
return m_backgroundToForeground;
|
|
|
|
case PlatformState::Background:
|
|
// cancel previous async to avoid creating many jobs if the player is spamming the button "foreground <-> background"
|
|
m_backgroundToForeground.cancel();
|
|
goToBackground(im, result);
|
|
break;
|
|
|
|
case PlatformState::Suspended:
|
|
goToSuspended(im, result);
|
|
break;
|
|
|
|
default:
|
|
US_ASSERT(false, US_STREAM_CSTR("Destination state is not valid: " << static_cast<uint32>(toState)));
|
|
SET_ASYNC_TO_ERROR(result, US_BUILD_ERROR_CODE(US_NS::Errors::SDK_STATE_TRANSITION_TO_INVALID), US_STREAM_CSTR("Destination state is not valid: " << static_cast<uint32>(toState)));
|
|
break;
|
|
}
|
|
|
|
return AsyncResultBatch<Facade*, Empty>(result);
|
|
}
|
|
|
|
// Must be called inside the m_stateLock critical section
|
|
AsyncResultBatch<Facade*, Empty> ApplicationStateManager::transitionFromSuspendedToForegroundTo(InstancesManager& im, PlatformState toState,
|
|
AsyncResultInternal<Map<Facade*, AsyncResult<Empty> > >& result)
|
|
{
|
|
switch (toState)
|
|
{
|
|
case PlatformState::Foreground:
|
|
// Already transitioning to Foreground, just return the existing AsyncResult
|
|
US_WRITE_LOG_OR_REMOTE_LOG(&im, LogLevel::Warning,
|
|
"Transition Suspended -> Foreground already processing. Return the AsyncResult of the previous transition");
|
|
return m_suspendedToForeground;
|
|
|
|
case PlatformState::Background:
|
|
// The SDK is still resuming from suspended mode.
|
|
// Return the AsyncResult indicating when the suspended mode will be finished
|
|
// Nothing to do more since the "going to background" transition is not doing anything.
|
|
m_currentSdkState = UbiServicesSdkStateInternal::SuspendedToForegroundToBackground;
|
|
return m_suspendedToForeground;
|
|
|
|
case PlatformState::Suspended:
|
|
goToSuspended(im, result);
|
|
break;
|
|
|
|
default:
|
|
US_ASSERT(false, US_STREAM_CSTR("Destination state is not valid: " << static_cast<uint32>(toState)));
|
|
SET_ASYNC_TO_ERROR(result, US_BUILD_ERROR_CODE(US_NS::Errors::SDK_STATE_TRANSITION_TO_INVALID), US_STREAM_CSTR("Destination state is not valid: " << static_cast<uint32>(toState)));
|
|
break;
|
|
}
|
|
|
|
return AsyncResultBatch<Facade*, Empty>(result);
|
|
}
|
|
|
|
// Must be called inside the m_stateLock critical section
|
|
AsyncResultBatch<Facade*, Empty> ApplicationStateManager::transitionFromSuspendedToForegroundToBackgroundTo(InstancesManager& im, PlatformState toState,
|
|
AsyncResultInternal<Map<Facade*, AsyncResult<Empty> > >& result)
|
|
{
|
|
switch (toState)
|
|
{
|
|
case PlatformState::Foreground:
|
|
// The SDK is still resuming from suspended mode, and the application went to background
|
|
// Return the AsyncResult indicating when the suspended mode will be finished.
|
|
// Nothing to do more since steps done in "background to foreground" transition
|
|
// are also done in the "suspended to foreground" transition.
|
|
// This could mean a bit out of date data, but it allows avoiding spamming the server with requests
|
|
m_currentSdkState = UbiServicesSdkStateInternal::SuspendedToForeground;
|
|
return m_suspendedToForeground;
|
|
|
|
case PlatformState::Background:
|
|
// The SDK is still resuming from suspended mode, and the application goes to background
|
|
// Return the AsyncResult indicating when the suspended mode will be finished.
|
|
// State of the SDK is kept to SuspendedToForegroundToBackground
|
|
US_WRITE_LOG_OR_REMOTE_LOG(&im, LogLevel::Warning,
|
|
"Previous transition already leads to Background (Current state : Suspended). New call to transitionTo(Background) has no effect.");
|
|
return m_suspendedToForeground;
|
|
|
|
case PlatformState::Suspended:
|
|
goToSuspended(im, result);
|
|
break;
|
|
|
|
default:
|
|
US_ASSERT(false, US_STREAM_CSTR("Destination state is not valid: " << static_cast<uint32>(toState)));
|
|
SET_ASYNC_TO_ERROR(result, US_BUILD_ERROR_CODE(US_NS::Errors::SDK_STATE_TRANSITION_TO_INVALID), US_STREAM_CSTR("Destination state is not valid: " << static_cast<uint32>(toState)));
|
|
break;
|
|
}
|
|
|
|
return AsyncResultBatch<Facade*, Empty>(result);
|
|
}
|
|
|
|
// Must be called inside the m_stateLock critical section
|
|
void ApplicationStateManager::goToBackground(InstancesManager& im, AsyncResultInternal<Map<Facade*, AsyncResult<Empty> > >& result)
|
|
{
|
|
#if defined (US_TARGET_XONE) || defined(US_TARGET_MANJARO)
|
|
im.resetBackgroundChrono();
|
|
#else
|
|
US_UNUSED(im);
|
|
#endif
|
|
|
|
// Nothing to do, just return success
|
|
SET_ASYNC_TO_SUCCESS(result, "Success");
|
|
m_currentSdkState = UbiServicesSdkStateInternal::Background;
|
|
}
|
|
|
|
// Must be called inside the m_stateLock critical section
|
|
void ApplicationStateManager::resumeFromBackground(InstancesManager& im, AsyncResultInternal<Map<Facade*, AsyncResult<Empty> > >& result)
|
|
{
|
|
// Launch the job that will do the check when resuming from background (namely if the profile has been linked)
|
|
const Vector<SmartPtr<FacadeInternal> > facades = im.getFacadesManager().getFacadesList();
|
|
|
|
US_WRITE_LOG_OR_REMOTE_LOG(&im, LogLevel::Info,
|
|
"UbiServices SDK resuming from background mode.");
|
|
|
|
m_jobManager->launch(result, US_NEW(JobResumeFocusMaster, result, facades, ClockSystem::TTimeNano(im.getBackgroundTime())));
|
|
|
|
// Store the async result in case new transitions would need this to be returned
|
|
m_backgroundToForeground = AsyncResultBatch<Facade*, Empty>(result);
|
|
|
|
m_currentSdkState = UbiServicesSdkStateInternal::BackgroundToForeground;
|
|
}
|
|
|
|
// Must be called inside the m_stateLock critical section
|
|
void ApplicationStateManager::goToSuspended(InstancesManager& im, AsyncResultInternal<Map<Facade*, AsyncResult<Empty> > >& result)
|
|
{
|
|
#if defined(US_TARGET_WITH_GDK)
|
|
im.networkEnginesShutdown();
|
|
#elif defined(US_TARGET_XONE)
|
|
// Force close on websockets due to a Xone OS bug
|
|
im.closeWebsocketsOnSuspending();
|
|
#endif
|
|
|
|
// Stop the scheduler thread
|
|
// Note : There could be a thread-safety issue here if resumeFromSuspended is in progress
|
|
// However, this would mean susp -> fg -> susp in a very short time, which is unlikely to happen.
|
|
// For 3.2, the risk is considered lower than implementing a complex system handling this case.
|
|
// Unfortunately, no time to log this at this place...
|
|
im.resetSuspendChrono();
|
|
im.getScheduler().stopProcessingJobs();
|
|
|
|
storeEventPeriodicJobRunning(im);
|
|
|
|
SET_ASYNC_TO_SUCCESS(result, "Success");
|
|
m_currentSdkState = UbiServicesSdkStateInternal::Suspended;
|
|
}
|
|
|
|
// Must be called inside the m_stateLock critical section
|
|
void ApplicationStateManager::resumeFromSuspended(InstancesManager& im, AsyncResultInternal<Map<Facade*, AsyncResult<Empty> > >& result)
|
|
{
|
|
#if defined(US_TARGET_XONE) || defined(US_TARGET_MANJARO)
|
|
// Reset chrono when resuming from suspended mode
|
|
m_timeSinceLastTransitionFromSuspended->resetState();
|
|
#if defined(US_TARGET_WITH_GDK)
|
|
im.networkEnginesRelaunch();
|
|
#endif
|
|
#endif
|
|
|
|
// Flush all jobs in the scheduler.
|
|
im.getScheduler().cancelJobs(Job::CancelType::Suspend);
|
|
|
|
// Restart scheduler execution
|
|
// The existing jobs have been canceled, but are still in the scheduler here.
|
|
im.getScheduler().restartProcessingJobs();
|
|
|
|
const ClockSystem::TTimeNano suspendedDuration = im.getSuspendedTime();
|
|
|
|
const Vector<SmartPtr<FacadeInternal> > facades = im.getFacadesManager().getFacadesList();
|
|
US_WRITE_LOG_OR_REMOTE_LOG(&im, LogLevel::Info,
|
|
String::formatText("UbiServices SDK resuming from suspended mode. Suspend lasted %llu seconds.", ClockSteady::getMilliFromNano(suspendedDuration) / 1000));
|
|
|
|
// JobResumeFromSuspend updates the s_applicationState flag
|
|
m_jobManager->launch(result, US_NEW(JobResumeFromSuspended, result, facades, suspendedDuration, m_eventPeriodicJobsRunning));
|
|
|
|
m_suspendedToForeground = AsyncResultBatch<Facade*, Empty>(result);
|
|
m_currentSdkState = UbiServicesSdkStateInternal::SuspendedToForeground;
|
|
}
|
|
|
|
#if defined(US_TARGET_XONE) || defined(US_TARGET_MANJARO)
|
|
const uint64 ApplicationStateManager::getElapseMsecSinceLastTransitionFromSuspended()
|
|
{
|
|
return ClockSteady::getMilliFromNano(m_timeSinceLastTransitionFromSuspended->getElapsed());
|
|
}
|
|
#endif
|
|
|
|
void ApplicationStateManager::storeEventPeriodicJobRunning(InstancesManager& im)
|
|
{
|
|
// No time for logging here
|
|
if (!m_suspendedToForeground.isProcessing())
|
|
{
|
|
// Do not update the list if a resumeFromSuspend is processing
|
|
// In the following case :
|
|
// Susp -> fg -> susp
|
|
// This would mean the event periodic job would never be restarted,
|
|
// because the transition susp -> fp cancels the event periodic job (along with all jobs in the scheduler).
|
|
// The state before the first suspend must be kept.
|
|
m_eventPeriodicJobsRunning.clear();
|
|
Vector<SmartPtr<FacadeInternal> > facades = im.getFacadesManager().getFacadesList();
|
|
for (Vector<SmartPtr<FacadeInternal> >::iterator it = facades.begin(); it != facades.end(); ++it)
|
|
{
|
|
FacadeInternal* facade = reinterpret_cast<FacadeInternal*>(it->getPtr());
|
|
m_eventPeriodicJobsRunning[facade] = EventClientProxy::getEventClientImpl(facade->getEventClient()).isPeriodicJobRunning();
|
|
}
|
|
}
|
|
}
|
|
|
|
#undef US_WRITE_LOG_OR_REMOTE_LOG
|
|
|
|
};
|