/*
* Copyright 2015-2015 Amazon.com, Inc. or its affiliates. All 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.
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
using Amazon.Runtime;
using Amazon.Util;
using Amazon.Runtime.Internal;
using Amazon.MobileAnalytics.MobileAnalyticsManager.Internal;
using Amazon.Runtime.Internal.Util;
namespace Amazon.MobileAnalytics.MobileAnalyticsManager
{
///
/// MobileAnalyticsManager is the entry point to recording analytic events for your application
///
public partial class MobileAnalyticsManager : IDisposable
{
private static Object _lock = new Object();
private static IDictionary _instanceDictionary = new Dictionary();
private Logger _logger = Logger.GetLogger(typeof(MobileAnalyticsManager));
private static BackgroundRunner _backgroundRunner = new BackgroundRunner();
#region constructor
///
/// Gets or creates Mobile Analytics Manager instance. If the instance already exists, returns the instance; otherwise
/// creates new instance and returns it.
///
/// Amazon Mobile Analytics Application ID.
/// AWS Credentials.
/// Region endpoint.
/// Amazon Mobile Analytics Manager configuration.
/// Mobile Analytics Manager instance.
public static MobileAnalyticsManager GetOrCreateInstance(string appID, AWSCredentials credentials, RegionEndpoint regionEndpoint, MobileAnalyticsManagerConfig maConfig)
{
if (string.IsNullOrEmpty(appID))
throw new ArgumentNullException("appID");
if (null == credentials)
throw new ArgumentNullException("credentials");
if (null == regionEndpoint)
throw new ArgumentNullException("regionEndpoint");
if (null == maConfig)
throw new ArgumentNullException("maConfig");
return GetOrCreateInstanceHelper(appID, credentials, regionEndpoint, maConfig);
}
///
/// Gets or creates Mobile Analytics Manager instance. If the instance already exists, returns the instance; otherwise
/// creates new instance and returns it.
///
/// Amazon Mobile Analytics Application ID.
/// AWS Credentials.
/// Region endpoint.
/// Mobile Analytics Manager instance.
public static MobileAnalyticsManager GetOrCreateInstance(string appID, AWSCredentials credentials, RegionEndpoint regionEndpoint)
{
if (string.IsNullOrEmpty(appID))
throw new ArgumentNullException("appID");
if (null == credentials)
throw new ArgumentNullException("credentials");
if (null == regionEndpoint)
throw new ArgumentNullException("regionEndpoint");
MobileAnalyticsManagerConfig maConfig = new MobileAnalyticsManagerConfig();
return GetOrCreateInstanceHelper(appID, credentials, regionEndpoint, maConfig);
}
///
/// Gets Mobile Analytics Manager instance by Application ID. Returns Mobile Analytics Manager instance if it's found.
/// Throws InvalidOperationException if the instance has not been instantiated.
///
/// Amazon Mobile Analytics Application ID.
/// The Mobile Analytics Manager instance.
public static MobileAnalyticsManager GetInstance(string appID)
{
if (string.IsNullOrEmpty(appID))
throw new ArgumentNullException("appID");
MobileAnalyticsManager managerInstance = null;
lock (_lock)
{
if (_instanceDictionary.TryGetValue(appID, out managerInstance))
{
return managerInstance;
}
else
{
throw new InvalidOperationException("Cannot find MobileAnalyticsManager instance for appID " + appID + ". Please call GetOrCreateInstance() first.");
}
}
}
private static MobileAnalyticsManager GetOrCreateInstanceHelper(string appID, AWSCredentials credentials, RegionEndpoint regionEndpoint, MobileAnalyticsManagerConfig maConfig)
{
#if BCL
ValidateParameters();
#endif
MobileAnalyticsManager managerInstance = null;
bool isNewInstance = false;
lock (_lock)
{
if (_instanceDictionary.TryGetValue(appID, out managerInstance))
{
return managerInstance;
}
else
{
managerInstance = new MobileAnalyticsManager(appID, credentials, regionEndpoint, maConfig);
_instanceDictionary[appID] = managerInstance;
isNewInstance = true;
}
}
if (isNewInstance)
{
managerInstance.Session.Start();
}
_backgroundRunner.StartWork();
return managerInstance;
}
private MobileAnalyticsManager(string appID, AWSCredentials credentials, RegionEndpoint regionEndpoint, MobileAnalyticsManagerConfig maConfig)
{
#if PCL
this.ClientContext = new ClientContext(appID);
#elif BCL
if (null == maConfig)
maConfig = new MobileAnalyticsManagerConfig();
this.ClientContext = new ClientContext(appID, maConfig.ClientContextConfiguration);
#endif
this.BackgroundDeliveryClient = new DeliveryClient(maConfig, ClientContext, credentials, regionEndpoint, this);
this.Session = new Session(appID, maConfig);
}
#endregion
#region public
///
/// Pauses the current session.
/// PauseSession() is the entry point into the Amazon Mobile Analytics SDK where sessions can be paused. Session is created and started immediately
/// after instantiating the MobileAnalyticsManager object. The session remains active until it is paused. When in a paused state, the session time will
/// not accumulate. When resuming a session, if enough time has elapsed from when the session is paused to when it's resumed, the session is ended and
/// a new session is created and started. Otherwise, the paused session is resumed and the session time continues to accumulate. Currently session
/// time out default value is 5 seconds.
///
/// For example, on Android platform, when MobileAnalyticsManager is first instantiated, it creates Session 1. As the user transitions from activity to activity, the old
/// activity will pause the current session, and the new activity will immediately resume the current session. In this case, Session 1 remains active
/// and accumulates session time. The user continues to use the App for a total of 3 minutes, at which point, the user receives a phone call.
/// When transitioning to the phone call, the current activity will pause the session and then transition to the phone app. In this case Session 1
/// remains paused while the phone call is in progress and session time does not accumulate. After completing the phone call a few minutes later,
/// the user returns to the App and the activity will resume Session 1. Since enough time has elapsed since resuming Session 1, Session 1 will be ended
/// with a play time of 3 minutes. Session 2 will then be immediately created and started.
///
/// In order for MobileAnalyticsManager to track sessions, you must call the PauseSession() and ResumeSession() in each activity of your app.
///
/// The example below shows how to pause and resume session in Xamarin Android
///
///public class MainActivity : Activity
///{
/// private static MobileAnalyticsManager _manager = null;
///
/// protected override void OnCreate(Bundle bundle)
/// {
/// _manager = MobileAnalyticsManager.GetOrCreateInstance(YourAppId, YourCredential, RegionEndpoint.USEast1, YourConfig);
/// base.OnCreate(bundle);
/// }
/// protected override void OnResume()
/// {
/// await _manager.ResumeSession();
/// base.OnResume();
/// }
/// protected override void OnPause()
/// {
/// await _manager.PauseSession();
/// base.OnPause();
/// }
///}
///
///
///
public void PauseSession()
{
try
{
Session.Pause();
}
catch (Exception e)
{
_logger.Error(e, "An exception occurred when pause session.");
MobileAnalyticsErrorEventArgs eventArgs = new MobileAnalyticsErrorEventArgs(this.GetType().Name, "An exception occurred when pausing session.", e, new List());
OnRaiseErrorEvent(eventArgs);
}
}
///
/// Resume the current session.
/// ResumeSession() is the entry point into the Amazon Mobile Analytics SDK where sessions can be resumed. Session is created and started immediately
/// after instantiating the MobileAnalyticsManager object. The session remains active until it is paused. When in a paused state, the session time will
/// not accumulate. When resuming a session, if enough time has elapsed from when the session is paused to when it's resumed, the session is ended and
/// a new session is created and started. Otherwise, the paused session is resumed and the session time continues to accumulate. Currently session
/// time out default value is 5 seconds.
///
/// For example, on Android platform, when MobileAnalyticsManager is first instantiated, it creates Session 1. As the user transitions from activity to activity, the old
/// activity will pause the current session, and the new activity will immediately resume the current session. In this case, Session 1 remains active
/// and accumulates session time. The user continues to use the App for a total of 3 minutes, at which point, the user receives a phone call.
/// When transitioning to the phone call, the current activity will pause the session and then transition to the phone app. In this case Session 1
/// remains paused while the phone call is in progress and session time does not accumulate. After completing the phone call a few minutes later,
/// the user returns to the App and the activity will resume Session 1. Since enough time has elapsed since resuming Session 1, Session 1 will be ended
/// with a play time of 3 minutes. Session 2 will then be immediately created and started.
///
/// In order for MobileAnalyticsManager to track sessions, you must call the PauseSession() and ResumeSession() in each activity of your app.
///
/// The example below shows how to pause and resume session in Xamarin Android
///
///public class MainActivity : Activity
///{
/// private static MobileAnalyticsManager _manager = null;
///
/// protected override void OnCreate(Bundle bundle)
/// {
/// _manager = MobileAnalyticsManager.GetOrCreateInstance(YourAppId, YourCredential, RegionEndpoint.USEast1, YourConfig);
/// base.OnCreate(bundle);
/// }
/// protected override void OnResume()
/// {
/// await _manager.ResumeSession();
/// base.OnResume();
/// }
/// protected override void OnPause()
/// {
/// await _manager.PauseSession();
/// base.OnPause();
/// }
///}
///
///
///
public void ResumeSession()
{
try
{
Session.Resume();
}
catch (Exception e)
{
_logger.Error(e, "An exception occurred when resume session.");
MobileAnalyticsErrorEventArgs eventArgs = new MobileAnalyticsErrorEventArgs(this.GetType().Name, "An exception occurred when resuming session.", e, new List());
OnRaiseErrorEvent(eventArgs);
}
}
///
/// Records the custom event to the local persistent storage. Background thread will deliver the event later.
///
/// The Mobile Analytics event.
public void RecordEvent(CustomEvent customEvent)
{
if (null == customEvent)
throw new ArgumentNullException("customEvent");
customEvent.Timestamp = AWSSDKUtils.CorrectedUtcNow;
Amazon.MobileAnalytics.Model.Event modelEvent = customEvent.ConvertToMobileAnalyticsModelEvent(this.Session);
BackgroundDeliveryClient.EnqueueEventsForDelivery(modelEvent);
}
///
/// Adds client context custom attribute
/// Refer Rest API for more information
///
/// Key.
/// Value.
public void AddCustomAttributeToClientContext(string key, string value)
{
if (string.IsNullOrEmpty(key))
{
throw new ArgumentNullException("key");
}
if (null == value)
{
throw new ArgumentNullException("value");
}
ClientContext.AddCustomAttributes(key, value);
}
///
/// The event for MobileAnalyticsErrorEvent notifications. All subscribers will be notified
/// when a new Mobile Analytics error event is raised.
///
/// The MobileAnalyticsErrorEvent is fired as error happens in Mobile Analytics Manager.
/// The delegates attached to the event will be passed information detailing what is the error.
/// For example, the error can be Amazon Mobile Analytics server return error, local event storage error,
/// File I/O error etc.
///
/// The example below shows how to subscribe to this event.
///
/// 1. Define a method with a signature similar to this one:
///
/// private void errorHandler(object sender, MobileAnalyticsErrorEventArgs args)
/// {
/// Console.WriteLine(args);
/// }
///
/// 2. Add this method to the MobileAnalyticsErrorEvent delegate's invocation list
///
/// _manager = MobileAnalyticsManager.GetOrCreateInstance(YourAppId, YourCredential, RegionEndpoint.USEast1, YourConfig);
/// _manager.MobileAnalyticsErrorEvent += errorHandler;
///
///
///
public event EventHandler MobileAnalyticsErrorEvent;
#endregion
#region internal
internal Session Session { get; set; }
internal ClientContext ClientContext { get; set; }
internal IDeliveryClient BackgroundDeliveryClient { get; private set; }
internal static IDictionary CopyOfInstanceDictionary
{
get
{
lock (_lock)
{
return new Dictionary(_instanceDictionary);
}
}
}
internal void OnRaiseErrorEvent(MobileAnalyticsErrorEventArgs eventArgs)
{
AWSSDKUtils.InvokeInBackground(MobileAnalyticsErrorEvent, eventArgs, this);
}
#endregion
#region Dispose Pattern Implementation
///
/// Implement the dispose pattern
///
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
///
/// Implement the dispose pattern
///
///
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
this.Session.Dispose();
this.BackgroundDeliveryClient.Dispose();
}
}
#endregion
}
///
/// Encapsulates the information needed to notify
/// errors of Mobile Analytics Manager.
///
public class MobileAnalyticsErrorEventArgs : EventArgs
{
///
/// The constructor of MobileAnalyticsErrorEventArgs
///
/// The class name where the error is caught.
/// The message that describes reason of the error.
/// The exception thrown in Mobile Analytics Manager.
/// The list of events that caused the error. This is a list of low level event objects. This list might be empty if the error is not caused by mal-formatted events.
internal MobileAnalyticsErrorEventArgs(string className, string errorMessage, Exception exception, List undeliveredEvents)
{
if (null == className)
throw new ArgumentNullException("className");
if (null == errorMessage)
throw new ArgumentNullException("errorMessage");
if (null == exception)
throw new ArgumentNullException("exception");
if (null == undeliveredEvents)
throw new ArgumentNullException("undeliveredEvents");
this.ClassName = className;
this.ErrorMessage = errorMessage;
this.Exception = exception;
this.UndeliveredEvents = undeliveredEvents;
}
///
/// The class name where the error is caught.
///
public string ClassName { get; set; }
///
/// The message that describes reason of the error.
///
public string ErrorMessage { get; set; }
///
/// The exception thrown in Mobile Analytics Manager.
///
public Exception Exception { get; set; }
///
/// The list of events that caused the error. This is a list of low level event objects.
/// This list might be empty if the error is not caused by mal-formatted events.
///
public List UndeliveredEvents { get; set; }
}
}