diff --git a/Directory.Build.props b/Directory.Build.props index 3fb696d..c36bf80 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -9,7 +9,7 @@ - + diff --git a/Samples/Sample.Xamarin.Core/Sample.Xamarin.Core.csproj b/Samples/Sample.Xamarin.Core/Sample.Xamarin.Core.csproj index dfce5ed..f457355 100644 --- a/Samples/Sample.Xamarin.Core/Sample.Xamarin.Core.csproj +++ b/Samples/Sample.Xamarin.Core/Sample.Xamarin.Core.csproj @@ -15,7 +15,7 @@ - + diff --git a/Samples/Sample.Xamarin.Droid/MainActivity.cs b/Samples/Sample.Xamarin.Droid/MainActivity.cs index 760a51d..c4e8925 100644 --- a/Samples/Sample.Xamarin.Droid/MainActivity.cs +++ b/Samples/Sample.Xamarin.Droid/MainActivity.cs @@ -19,9 +19,8 @@ protected override void OnCreate(Bundle savedInstanceState) { options.Dsn = "https://5a193123a9b841bc8d8e42531e7242a1@o447951.ingest.sentry.io/5560112"; options.AddXamarinFormsIntegration(); -#if DEBUG options.Debug = true; -#endif + options.AttachScreenshots = true; }); TabLayoutResource = Resource.Layout.Tabbar; diff --git a/Samples/Sample.Xamarin.Droid/Sample.Xamarin.Droid.csproj b/Samples/Sample.Xamarin.Droid/Sample.Xamarin.Droid.csproj index 9c6cdc0..4f354c6 100644 --- a/Samples/Sample.Xamarin.Droid/Sample.Xamarin.Droid.csproj +++ b/Samples/Sample.Xamarin.Droid/Sample.Xamarin.Droid.csproj @@ -67,7 +67,7 @@ 4.3.4 - + diff --git a/Samples/Sample.Xamarin.UWP/App.xaml.cs b/Samples/Sample.Xamarin.UWP/App.xaml.cs index c1a41c2..7207b16 100644 --- a/Samples/Sample.Xamarin.UWP/App.xaml.cs +++ b/Samples/Sample.Xamarin.UWP/App.xaml.cs @@ -1,4 +1,5 @@ using Sentry; +using Sentry.Infrastructure; using System; using Windows.ApplicationModel; using Windows.ApplicationModel.Activation; @@ -16,9 +17,9 @@ protected override void OnLaunched(LaunchActivatedEventArgs e) { options.Dsn = "https://5a193123a9b841bc8d8e42531e7242a1@o447951.ingest.sentry.io/5560112"; options.AddXamarinFormsIntegration(); -#if DEBUG options.Debug = true; -#endif + options.DiagnosticLogger = new TraceDiagnosticLogger(SentryLevel.Debug); + options.AttachScreenshots = true; }); Frame rootFrame = Window.Current.Content as Frame; diff --git a/Samples/Sample.Xamarin.UWP/Package.appxmanifest b/Samples/Sample.Xamarin.UWP/Package.appxmanifest index dd692ba..eee947f 100644 --- a/Samples/Sample.Xamarin.UWP/Package.appxmanifest +++ b/Samples/Sample.Xamarin.UWP/Package.appxmanifest @@ -4,7 +4,8 @@ xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10" xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest" xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10" - IgnorableNamespaces="uap mp"> + xmlns:uap6="http://schemas.microsoft.com/appx/manifest/uap/windows10/6" + IgnorableNamespaces="uap mp uap6"> + \ No newline at end of file diff --git a/Samples/Sample.Xamarin.UWP/Sample.Xamarin.UWP.csproj b/Samples/Sample.Xamarin.UWP/Sample.Xamarin.UWP.csproj index bdb5eef..963bc4c 100644 --- a/Samples/Sample.Xamarin.UWP/Sample.Xamarin.UWP.csproj +++ b/Samples/Sample.Xamarin.UWP/Sample.Xamarin.UWP.csproj @@ -159,7 +159,7 @@ 2.0.0.9 - 1.5.3.2 + 1.6.1 4.8.0.1451 @@ -196,4 +196,4 @@ --> - \ No newline at end of file + diff --git a/Samples/Sample.Xamarin.iOS/Sample.Xamarin.iOS.csproj b/Samples/Sample.Xamarin.iOS/Sample.Xamarin.iOS.csproj index 1664f4c..880715d 100644 --- a/Samples/Sample.Xamarin.iOS/Sample.Xamarin.iOS.csproj +++ b/Samples/Sample.Xamarin.iOS/Sample.Xamarin.iOS.csproj @@ -132,7 +132,7 @@ 2.0.0.9 - + diff --git a/Src/Sentry.Xamarin/Extensions/SentryXamarinOptionsExtensions.cs b/Src/Sentry.Xamarin/Extensions/SentryXamarinOptionsExtensions.cs index 45cd7b7..2648f86 100644 --- a/Src/Sentry.Xamarin/Extensions/SentryXamarinOptionsExtensions.cs +++ b/Src/Sentry.Xamarin/Extensions/SentryXamarinOptionsExtensions.cs @@ -2,6 +2,7 @@ using Xamarin.Essentials; using Sentry.Xamarin.Internals; using Sentry.Internals.Session; +using Sentry.Internals.Device.Screenshot; namespace Sentry { @@ -62,6 +63,16 @@ internal static void RegisterXamarinEventProcessors(this SentryXamarinOptions op #endif } + internal static void RegisterScreenshotEventProcessor(this SentryXamarinOptions options) + { +#if NATIVE_PROCESSOR && !UAP10_0_16299 + if (options.AttachScreenshots) + { + options.AddEventProcessor(new ScreenshotEventProcessor(options)); + } +#endif + } + internal static bool RegisterNativeIntegrations(this SentryXamarinOptions options) { if (options.NativeIntegrationEnabled) diff --git a/Src/Sentry.Xamarin/Internals/Device/Screenshot/ScreenshotAttachment.cs b/Src/Sentry.Xamarin/Internals/Device/Screenshot/ScreenshotAttachment.cs new file mode 100644 index 0000000..cd6bef6 --- /dev/null +++ b/Src/Sentry.Xamarin/Internals/Device/Screenshot/ScreenshotAttachment.cs @@ -0,0 +1,26 @@ +using System; +using System.IO; + +namespace Sentry.Internals.Device.Screenshot +{ + internal class ScreenshotAttachment : Attachment + { + public ScreenshotAttachment(ScreenshotAttachmentContent content) + : this( + AttachmentType.Default, + content, + "screenshot", + "image/jpeg") + { + } + + private ScreenshotAttachment( + AttachmentType type, + IAttachmentContent content, + string fileName, + string? contentType) + : base(type, content, fileName, contentType) + { + } + } +} diff --git a/Src/Sentry.Xamarin/Internals/Device/Screenshot/ScreenshotAttachmentContent.cs b/Src/Sentry.Xamarin/Internals/Device/Screenshot/ScreenshotAttachmentContent.cs new file mode 100644 index 0000000..db87381 --- /dev/null +++ b/Src/Sentry.Xamarin/Internals/Device/Screenshot/ScreenshotAttachmentContent.cs @@ -0,0 +1,48 @@ +using System.IO; + +namespace Sentry.Internals.Device.Screenshot +{ + internal class ScreenshotAttachmentContent : IAttachmentContent + { + private byte[] _data { get; set; } + + private bool _wasRead { get; set; } + + /// + /// The content ctor + /// + /// A Ready once stream containing the image data. + public ScreenshotAttachmentContent(Stream stream) + { + SetNewData(stream); + } + + public void SetNewData(Stream stream) + { + _data = new byte[stream.Length]; + stream.Read(_data, 0, _data.Length); + } + + /// + /// Inform is the content was previously read. + /// By calling this function you will reset the read state to false and return the current state. + /// + /// true if the content was read. + public bool ResetWasRead() + { + var read = _wasRead; + _wasRead = false; + return read; + } + + /// + /// Get a stream from the attachment data. + /// + /// A Memory stream containing the data. + public Stream GetStream() + { + _wasRead = true; + return new MemoryStream(_data); + } + } +} diff --git a/Src/Sentry.Xamarin/Internals/Device/Screenshot/ScreenshotEventProcessor.droid.ios.cs b/Src/Sentry.Xamarin/Internals/Device/Screenshot/ScreenshotEventProcessor.droid.ios.cs new file mode 100644 index 0000000..3f3826a --- /dev/null +++ b/Src/Sentry.Xamarin/Internals/Device/Screenshot/ScreenshotEventProcessor.droid.ios.cs @@ -0,0 +1,53 @@ +using Sentry.Extensibility; +using Xamarin.Essentials; +using System.IO; +using System; + +namespace Sentry.Internals.Device.Screenshot +{ + internal class ScreenshotEventProcessor : ISentryEventProcessor + { + private ScreenshotAttachmentContent? _screenshot { get; set; } + private SentryXamarinOptions _options { get; } + + public ScreenshotEventProcessor(SentryXamarinOptions options) => _options = options; + + public SentryEvent? Process(SentryEvent @event) + { + try + { + if (_options.SessionLogger?.IsBackground() == false) + { + var stream = Capture(); + if (stream != null) + { + /* Create a new attachment if no screenshot attachment was found. + * If there's an attachment content but it wasnt read during the last event processing + * Assume it was cleared and create a new one. + */ + if (_screenshot?.ResetWasRead() != true) + { + _screenshot = new ScreenshotAttachmentContent(stream); + SentrySdk.ConfigureScope(s => s.AddAttachment(new ScreenshotAttachment(_screenshot))); + } + else + { + _screenshot.SetNewData(stream); + } + } + } + } + catch (Exception ex) + { + _options.DiagnosticLogger?.LogError("Failed to capture a screenshot", ex); + } + return @event; + } + + private Stream Capture() + { + var screenStream = global::Xamarin.Essentials.Screenshot.CaptureAsync().ConfigureAwait(false).GetAwaiter().GetResult(); + return screenStream.OpenReadAsync(ScreenshotFormat.Jpeg).ConfigureAwait(false).GetAwaiter().GetResult(); + } + } +} diff --git a/Src/Sentry.Xamarin/Internals/Session/DeviceActiveLogger.droid.cs b/Src/Sentry.Xamarin/Internals/Session/DeviceActiveLogger.droid.cs index 6e4d976..7c31543 100644 --- a/Src/Sentry.Xamarin/Internals/Session/DeviceActiveLogger.droid.cs +++ b/Src/Sentry.Xamarin/Internals/Session/DeviceActiveLogger.droid.cs @@ -9,6 +9,12 @@ internal class DeviceActiveLogger : IDeviceActiveLogger /// private bool IsPaused; + /// + /// Informs if the device is on background. + /// + /// True if the app is on background, otherwise false + public bool IsBackground() => IsPaused; + /// public void StatePaused() { diff --git a/Src/Sentry.Xamarin/Internals/Session/DeviceActiveLogger.ios.cs b/Src/Sentry.Xamarin/Internals/Session/DeviceActiveLogger.ios.cs deleted file mode 100644 index b71257e..0000000 --- a/Src/Sentry.Xamarin/Internals/Session/DeviceActiveLogger.ios.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Sentry.Internals.Session -{ - internal class DeviceActiveLogger : IDeviceActiveLogger - { - /// - public void StatePaused() => SentrySdk.PauseSession(); - - /// - public void StateResumed() => SentrySdk.ResumeSession(); - } -} diff --git a/Src/Sentry.Xamarin/Internals/Session/DeviceActiveLogger.ios.uwp.cs b/Src/Sentry.Xamarin/Internals/Session/DeviceActiveLogger.ios.uwp.cs new file mode 100644 index 0000000..31cf219 --- /dev/null +++ b/Src/Sentry.Xamarin/Internals/Session/DeviceActiveLogger.ios.uwp.cs @@ -0,0 +1,27 @@ +namespace Sentry.Internals.Session +{ + internal class DeviceActiveLogger : IDeviceActiveLogger + { + private bool _isPaused { get; set; } = true; + + /// + /// Informs if the device is on background. + /// + /// True if the app is on background, otherwise false + public bool IsBackground() => _isPaused; + + /// + public void StatePaused() + { + _isPaused = true; + SentrySdk.PauseSession(); + } + + /// + public void StateResumed() + { + _isPaused = false; + SentrySdk.ResumeSession(); + } + } +} diff --git a/Src/Sentry.Xamarin/Internals/Session/DeviceActiveLogger.uwp.cs b/Src/Sentry.Xamarin/Internals/Session/DeviceActiveLogger.uwp.cs deleted file mode 100644 index b71257e..0000000 --- a/Src/Sentry.Xamarin/Internals/Session/DeviceActiveLogger.uwp.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Sentry.Internals.Session -{ - internal class DeviceActiveLogger : IDeviceActiveLogger - { - /// - public void StatePaused() => SentrySdk.PauseSession(); - - /// - public void StateResumed() => SentrySdk.ResumeSession(); - } -} diff --git a/Src/Sentry.Xamarin/Internals/Session/IDeviceActiveLogger.cs b/Src/Sentry.Xamarin/Internals/Session/IDeviceActiveLogger.cs index 774569d..72bf24f 100644 --- a/Src/Sentry.Xamarin/Internals/Session/IDeviceActiveLogger.cs +++ b/Src/Sentry.Xamarin/Internals/Session/IDeviceActiveLogger.cs @@ -5,5 +5,7 @@ internal interface IDeviceActiveLogger void StatePaused(); void StateResumed(); + + bool IsBackground(); } } diff --git a/Src/Sentry.Xamarin/Sentry.Xamarin.csproj b/Src/Sentry.Xamarin/Sentry.Xamarin.csproj index 4a21913..bddf03f 100644 --- a/Src/Sentry.Xamarin/Sentry.Xamarin.csproj +++ b/Src/Sentry.Xamarin/Sentry.Xamarin.csproj @@ -30,37 +30,37 @@ - - - - + + + + + + + + + + - - - - - - - + - + - + - + - + diff --git a/Src/Sentry.Xamarin/SentryXamarin.cs b/Src/Sentry.Xamarin/SentryXamarin.cs index 666cb24..db3f607 100644 --- a/Src/Sentry.Xamarin/SentryXamarin.cs +++ b/Src/Sentry.Xamarin/SentryXamarin.cs @@ -37,6 +37,7 @@ public static void Init(SentryXamarinOptions options) options.RegisterNativeActivityStatus(); options.RegisterNativeIntegrations(); options.RegisterXamarinEventProcessors(); + options.RegisterScreenshotEventProcessor(); options.RegisterXamarinInAppExclude(); options.ProtocolPackageName ??= ProtocolPackageName; SentrySdk.Init(options); diff --git a/Src/Sentry.Xamarin/SentryXamarinOptions.cs b/Src/Sentry.Xamarin/SentryXamarinOptions.cs index 7b51bcd..ee36d5a 100644 --- a/Src/Sentry.Xamarin/SentryXamarinOptions.cs +++ b/Src/Sentry.Xamarin/SentryXamarinOptions.cs @@ -8,6 +8,19 @@ namespace Sentry /// public class SentryXamarinOptions : SentryOptions { + /// + /// Attaches screenshots from the app to events automatically whenever possible. + /// + /// + /// Pii information might be exposed by activating this feature. + /// + public bool AttachScreenshots { get; set; } + + /// + /// Define the range of time that duplicated internal breadcrumbs will be ignored. + /// + public int InternalBreadcrumbDuplicationTimeSpan { get; set; } = 2; + internal bool XamarinLoggerEnabled { get; set; } = true; internal bool NativeIntegrationEnabled { get; set; } = true; internal bool InternalCacheEnabled { get; set; } = true; @@ -22,10 +35,7 @@ public class SentryXamarinOptions : SentryOptions /// device's app status. /// internal IDeviceActiveLogger SessionLogger { get; set; } - /// - /// Define the range of time that duplicated internal breadcrumbs will be ignored. - /// - public int InternalBreadcrumbDuplicationTimeSpan { get; set; } = 2; + internal Breadcrumb LastInternalBreadcrumb {get;set;} /// @@ -35,6 +45,7 @@ public SentryXamarinOptions() { IsEnvironmentUser = false; AutoSessionTracking = true; + IsGlobalModeEnabled = true; } } }