using System;
using System.Threading.Tasks;
using Unity.Services.Analytics.Data;
using Unity.Services.Analytics.Internal;
using Unity.Services.Analytics.Platform;
using Unity.Services.Authentication.Internal;
using Unity.Services.Core.Configuration.Internal;
using Unity.Services.Core.Device.Internal;
using UnityEngine;
using Event = Unity.Services.Analytics.Internal.Event;
namespace Unity.Services.Analytics
{
partial class AnalyticsServiceInstance : IAnalyticsService
{
public string PrivacyUrl => "https://unity3d.com/legal/privacy-policy";
const string k_CollectUrlPattern = "https://collect.analytics.unity3d.com/api/analytics/collect/v1/projects/{0}/environments/{1}";
const string k_ForgetCallingId = "com.unity.services.analytics.Events." + nameof(OptOut);
internal IPlayerId PlayerId { get; private set; }
internal IInstallationId InstallId { get; private set; }
internal ICloudProjectId CloudProjectIdProvider { get; private set; }
internal string CloudProjectId => CloudProjectIdProvider?.GetCloudProjectId() ?? Application.cloudProjectId;
internal string CustomAnalyticsId { get; private set; }
internal IBuffer dataBuffer = new Internal.Buffer(new BufferSystemCalls());
int m_BufferLengthAtLastGameRunning;
internal IDataGenerator dataGenerator = new DataGenerator();
internal IDispatcher dataDispatcher { get; set; }
string m_CollectURL;
readonly StdCommonParams m_CommonParams = new StdCommonParams();
readonly string m_StartUpCallingId = "com.unity.services.analytics.Events.Startup";
internal IIDeviceIdentifiersInternal deviceIdentifiersInternal = new DeviceIdentifiersInternal();
internal bool ServiceEnabled { get; private set; } = true;
internal ICoreStatsHelper m_CoreStatsHelper = new CoreStatsHelper();
internal IConsentTracker ConsentTracker;
public string SessionID { get; }
internal AnalyticsServiceInstance()
{
ConsentTracker = new ConsentTracker(m_CoreStatsHelper);
// Add a check to ensure a project id is set.
if (string.IsNullOrEmpty(Application.cloudProjectId))
{
Debug.LogError("No cloud project ID was found by the Analytics SDK. This means Analytics events will not be sent. Please make sure to link your cloud project in the Unity editor to fix this problem.");
return;
}
dataDispatcher = new Dispatcher(dataBuffer, new WebRequestHelper(), ConsentTracker);
SessionID = Guid.NewGuid().ToString();
m_CommonParams.ClientVersion = Application.version;
m_CommonParams.ProjectID = Application.cloudProjectId;
m_CommonParams.GameBundleID = Application.identifier;
m_CommonParams.Platform = Runtime.Name();
m_CommonParams.BuildGuuid = Application.buildGUID;
m_CommonParams.Idfv = deviceIdentifiersInternal.Idfv;
}
public void Flush()
{
if (!ServiceEnabled)
{
return;
}
if (string.IsNullOrEmpty(CloudProjectId))
{
return;
}
if (InstallId == null)
{
#if UNITY_ANALYTICS_DEVELOPMENT
Debug.Log("The Core callback hasn't yet triggered.");
#endif
return;
}
if (ConsentTracker.IsGeoIpChecked() && ConsentTracker.IsConsentGiven())
{
dataBuffer.InstallID = InstallId.GetOrCreateIdentifier();
dataBuffer.PlayerID = PlayerId?.PlayerId;
dataBuffer.UserID = GetAnalyticsUserID();
dataBuffer.SessionID = SessionID;
dataDispatcher.CollectUrl = m_CollectURL;
dataDispatcher.Flush();
}
if (ConsentTracker.IsOptingOutInProgress())
{
analyticsForgetter.AttemptToForget();
}
}
public void RecordInternalEvent(Event eventToRecord)
{
if (!ServiceEnabled)
{
return;
}
dataBuffer.PushEvent(eventToRecord);
}
internal void SetDependencies(ICloudProjectId cloudProjectId, IInstallationId installId, IPlayerId playerId, string environment, string customAnalyticsId)
{
CloudProjectIdProvider = cloudProjectId;
InstallId = installId;
PlayerId = playerId;
CustomAnalyticsId = customAnalyticsId;
m_CommonParams.ProjectID = CloudProjectId;
m_CollectURL = string.Format(k_CollectUrlPattern, CloudProjectId, environment.ToLowerInvariant());
}
internal async Task Initialize(ICloudProjectId cloudProjectId, IInstallationId installId, IPlayerId playerId, string environment, string customAnalyticsId)
{
SetDependencies(cloudProjectId, installId, playerId, environment, customAnalyticsId);
if (ServiceEnabled)
{
AnalyticsContainer.Initialize();
await InitializeUser();
// Startup events require a user ID (either Installation or Custom).
RecordStartupEvents();
}
}
async Task InitializeUser()
{
SetVariableCommonParams();
#if UNITY_ANALYTICS_DEVELOPMENT
Debug.LogFormat("UA2 Setup\nCollectURL:{0}\nSessionID:{1}", m_CollectURL, SessionID);
#endif
try
{
await ConsentTracker.CheckGeoIP();
if (ConsentTracker.IsGeoIpChecked() && (ConsentTracker.IsConsentDenied() || ConsentTracker.IsOptingOutInProgress()))
{
OptOut();
}
}
#if UNITY_ANALYTICS_EVENT_LOGS
catch (ConsentCheckException e)
{
Debug.Log("Initial GeoIP lookup fail: " + e.Message);
}
#else
catch (ConsentCheckException)
{
}
#endif
}
void RecordStartupEvents()
{
// Startup Events.
dataGenerator.SdkStartup(ref dataBuffer, DateTime.Now, m_CommonParams, m_StartUpCallingId);
dataGenerator.ClientDevice(ref dataBuffer, DateTime.Now, m_CommonParams, m_StartUpCallingId, SystemInfo.processorType, SystemInfo.graphicsDeviceName, SystemInfo.processorCount, SystemInfo.systemMemorySize, Screen.width, Screen.height, (int)Screen.dpi);
#if UNITY_DOTSRUNTIME
var isTiny = true;
#else
var isTiny = false;
#endif
dataGenerator.GameStarted(ref dataBuffer, DateTime.Now, m_CommonParams, m_StartUpCallingId, Application.buildGUID, SystemInfo.operatingSystem, isTiny, DebugDevice.IsDebugDevice(), Locale.AnalyticsRegionLanguageCode());
if (InstallId != null && new InternalNewPlayerHelper(InstallId).IsNewPlayer())
{
dataGenerator.NewPlayer(ref dataBuffer, DateTime.Now, m_CommonParams, m_StartUpCallingId, SystemInfo.deviceModel);
}
}
///
/// Records the gameEnded event, and flushes the buffer to upload all events.
///
internal void GameEnded()
{
if (!ServiceEnabled)
{
return;
}
dataGenerator.GameEnded(ref dataBuffer, DateTime.Now, m_CommonParams, "com.unity.services.analytics.Events.Shutdown", DataGenerator.SessionEndState.QUIT);
// Need to null check the consent tracker here in case the game ends before the tracker can be initialised.
if (ConsentTracker != null && ConsentTracker.IsGeoIpChecked())
{
Flush();
}
}
internal void RecordGameRunningIfNecessary()
{
if (ServiceEnabled)
{
if (dataBuffer.Length == 0 || dataBuffer.Length == m_BufferLengthAtLastGameRunning)
{
SetVariableCommonParams();
dataGenerator.GameRunning(ref dataBuffer, DateTime.Now, m_CommonParams, "com.unity.services.analytics.AnalyticsServiceInstance.RecordGameRunningIfNecessary");
m_BufferLengthAtLastGameRunning = dataBuffer.Length;
}
else
{
m_BufferLengthAtLastGameRunning = dataBuffer.Length;
}
}
}
//
// Internal tick is called by the Heartbeat at set intervals.
//
internal void InternalTick()
{
if (ServiceEnabled &&
ConsentTracker.IsGeoIpChecked())
{
Flush();
}
}
void SetVariableCommonParams()
{
m_CommonParams.Idfv = deviceIdentifiersInternal.Idfv;
m_CommonParams.DeviceVolume = DeviceVolumeProvider.GetDeviceVolume();
m_CommonParams.BatteryLoad = SystemInfo.batteryLevel;
m_CommonParams.UasUserID = PlayerId?.PlayerId;
}
void GameEnded(DataGenerator.SessionEndState quitState)
{
if (!ServiceEnabled)
{
return;
}
dataGenerator.GameEnded(ref dataBuffer, DateTime.Now, m_CommonParams, "com.unity.services.analytics.Events.GameEnded", quitState);
}
public async Task SetAnalyticsEnabled(bool enabled)
{
if (enabled && !ServiceEnabled)
{
dataBuffer = new Internal.Buffer(new BufferSystemCalls());
dataDispatcher = new Dispatcher(dataBuffer, new WebRequestHelper(), ConsentTracker);
await InitializeUser();
ServiceEnabled = true;
}
else if (!enabled && ServiceEnabled)
{
dataBuffer.ClearBuffer();
dataBuffer.ClearDiskCache();
dataBuffer = new BufferRevoked();
ServiceEnabled = false;
}
}
}
}