/* * 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; } } }