-
-
Notifications
You must be signed in to change notification settings - Fork 49
+semver:minor Added PrivacyDlg to allow control over analytics tracking #1493
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
tombogle
wants to merge
5
commits into
master
Choose a base branch
from
add-dlg-for-analytics-opt-out
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
a5fdc13
+semver:minor Added PrivacyDlg to allow user to control whether analy…
tombogle fe4d560
Fixed a couple minor issues raised by Devin review
tombogle 20d8a98
Improved logic when clicking OK, especially in case where saving fails.
tombogle 52da232
Merge branch 'master' into add-dlg-for-analytics-opt-out
tombogle d4c0713
Fixed LocalizationManagerId in PrivacyDlg
tombogle File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| // -------------------------------------------------------------------------------------------- | ||
| #region // Copyright (c) 2026, SIL Global. All Rights Reserved. | ||
| // <copyright from='2026' to='2026' company='SIL Global'> | ||
| // Copyright (c) 2026, SIL Global. All Rights Reserved. | ||
| // | ||
| // Distributable under the terms of the MIT License (https://sil.mit-license.org/) | ||
| // </copyright> | ||
| #endregion | ||
| // -------------------------------------------------------------------------------------------- | ||
| using System; | ||
|
|
||
| namespace SIL.Core.Desktop.Privacy | ||
| { | ||
| /// <summary> | ||
| /// Provides data for the AllowTrackingChanged event. | ||
| /// </summary> | ||
| public sealed class AllowTrackingChangedEventArgs : EventArgs | ||
| { | ||
| /// <summary> | ||
| /// Gets a value indicating whether analytics tracking is now permitted. | ||
| /// </summary> | ||
| public bool IsTrackingAllowed { get; } | ||
|
|
||
| public AllowTrackingChangedEventArgs(bool isTrackingAllowed) | ||
| { | ||
| IsTrackingAllowed = isTrackingAllowed; | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| // -------------------------------------------------------------------------------------------- | ||
| #region // Copyright (c) 2026, SIL Global. All Rights Reserved. | ||
| // <copyright from='2026' to='2026' company='SIL Global'> | ||
| // Copyright (c) 2026, SIL Global. All Rights Reserved. | ||
| // | ||
| // Distributable under the terms of the MIT License (https://sil.mit-license.org/) | ||
| // </copyright> | ||
| #endregion | ||
| // -------------------------------------------------------------------------------------------- | ||
|
|
||
| using System; | ||
|
|
||
| namespace SIL.Core.Desktop.Privacy | ||
| { | ||
| /// <summary> | ||
| /// Provides configuration and identity information used for analytics tracking. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// Implementations determine whether analytics events may be sent, | ||
| /// based on product-level and (optionally) organization-level settings. | ||
| /// </remarks> | ||
| public interface IAnalytics | ||
| { | ||
| event EventHandler<AllowTrackingChangedEventArgs> AllowTrackingChanged; | ||
|
|
||
| /// <summary> | ||
| /// Gets the name of the product (suitable for displaying in the UI) sending analytics | ||
| /// events. | ||
| /// </summary> | ||
| string ProductName { get; } | ||
|
|
||
| /// <summary> | ||
| /// Gets the name of the organization associated with the product (suitable for displaying | ||
| /// in the UI). | ||
| /// </summary> | ||
| string OrganizationName { get; } | ||
|
|
||
| /// <summary> | ||
| /// Gets a value indicating whether analytics tracking is currently permitted for this product. | ||
| /// This is determined as follows: | ||
| /// 1. If a product-specific setting exists, it takes precedence. | ||
| /// 2. Otherwise, the global/organization-wide setting is used. | ||
| /// 3. If neither setting is present, tracking is allowed by default. | ||
| /// </summary> | ||
| bool AllowTracking { get; } | ||
|
|
||
| /// <summary> | ||
| /// Gets a value indicating whether analytics is enabled at the organization level. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// A value of <c>true</c> tracking is currently permitted at the organization level. | ||
| /// A value of <c>false</c> tracking is currently disallowed at the organization level. | ||
| /// A value of <c>null</c> no organization-level preference has been specified. | ||
| /// </remarks> | ||
| bool? OrganizationAnalyticsEnabled { get; } | ||
|
|
||
| /// <summary> | ||
| /// Updates with the specified product-specific tracking permission, applying the setting | ||
| /// organization-wide if so requested. | ||
| /// </summary> | ||
| /// <param name="allowTracking">A value indicating whether analytics tracking is permitted | ||
| /// for this product.</param> | ||
| /// <param name="applyOrganizationWide">A value indicating whether the update should apply | ||
| /// to all desktop programs published the organization.</param> | ||
| /// <exception cref="T:System.UnauthorizedAccessException">The settings cannot be written | ||
| /// because the user does not have the necessary access rights.</exception> | ||
| /// <remarks> | ||
| /// In practice, this *either* sets the global value (and removes the product-specific | ||
| /// setting if present) OR it sets only the product-specific setting. This ensures that if | ||
| /// a later decision is made in a different product to change the global setting, it will | ||
| /// apply to this product as well (i.e., the product-specific setting won't override it). | ||
| /// </remarks> | ||
| void Update(bool allowTracking, bool applyOrganizationWide = false); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,113 @@ | ||
| using System; | ||
| using System.Linq; | ||
| using JetBrains.Annotations; | ||
| using Microsoft.Win32; | ||
| using SIL.Core.Desktop.Privacy; | ||
|
|
||
| namespace SIL.Windows.Forms.Privacy | ||
| { | ||
| /// <summary> | ||
| /// An analytics implementation that saves settings in the Windows registry. | ||
| /// </summary> | ||
| [PublicAPI] | ||
| public class AnalyticsProxy : IAnalytics | ||
| { | ||
| private const string kRegistryValueName = "Enabled"; | ||
|
|
||
| public event EventHandler<AllowTrackingChangedEventArgs> AllowTrackingChanged; | ||
|
|
||
| public string ProductName { get; } | ||
|
|
||
| /// <summary> | ||
| /// Constructs an instance of the AnalyticsProxy class with the specified product name. | ||
| /// </summary> | ||
| /// <param name="productName"> | ||
| /// The name of the product (suitable for displaying in the UI). This will be also be used | ||
| /// as part of the registry key path for storing the product-specific analytics-enabled | ||
| /// setting, unless <see cref="ProductRegistryKeyId"/> is overridden. | ||
| /// </param> | ||
| /// <exception cref="ArgumentNullException"> | ||
| /// The <paramref name="productName"/> was null | ||
| /// </exception> | ||
| public AnalyticsProxy(string productName) | ||
| { | ||
| ProductName = productName ?? throw new ArgumentNullException(nameof(productName)); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// If <see cref="ProductName"/> (the UI name of the product) is not suitable to be used | ||
| /// as part of a registry key path, this property can be overridden to provide an alternate | ||
| /// identifier. This should be unique across products published by the organization. | ||
| /// </summary> | ||
| public virtual string ProductRegistryKeyId => ProductName; | ||
|
|
||
| public virtual string OrganizationName { get; } = "SIL Global"; | ||
|
|
||
| public virtual string OrganizationRegistryKeyId { get; } = "SIL"; | ||
|
|
||
| private string OrganizationRegistryKeyPath => $@"Software\{OrganizationRegistryKeyId}"; | ||
|
|
||
| private string GetKeyPath(string productKeyId) | ||
| { | ||
| var productPart = productKeyId != null ? $@"\{productKeyId}" : string.Empty; | ||
| return $@"{OrganizationRegistryKeyPath}{productPart}\Analytics"; | ||
| } | ||
|
|
||
| public bool AllowTracking => | ||
| ReadAnalyticsEnabledState(ProductRegistryKeyId) // product-specific | ||
| ?? (OrganizationAnalyticsEnabled ?? true); // fall back to global, then default to true | ||
|
|
||
| public bool? OrganizationAnalyticsEnabled => ReadAnalyticsEnabledState(null); | ||
|
|
||
| public void Update(bool allowTracking, bool applyOrganizationWide = false) | ||
| { | ||
| if (applyOrganizationWide) | ||
| { | ||
| WriteAnalyticsEnabledState(null, allowTracking); | ||
| // Since the global value is being set, we must remove the product-specific setting, so that | ||
| // if a *later* decision is made in a different product to change the global setting, it | ||
| // will apply to this product as well (i.e., the product-specific setting won't override it). | ||
| RemoveProductAnalyticsEnabledSetting(); | ||
| } | ||
| else | ||
| { | ||
| // The global value is *not* being set. If it was previously set in some other product, it | ||
| // will remain in effect, but the product-specific value will override it for this product. | ||
| WriteAnalyticsEnabledState(ProductRegistryKeyId, allowTracking); | ||
| } | ||
|
|
||
| AllowTrackingChanged?.Invoke(this, new AllowTrackingChangedEventArgs(allowTracking)); | ||
| } | ||
|
|
||
| private bool? ReadAnalyticsEnabledState(string productKeyId) | ||
| { | ||
| try | ||
| { | ||
| using var key = Registry.CurrentUser.OpenSubKey(GetKeyPath(productKeyId)); | ||
|
|
||
| var value = key?.GetValue(kRegistryValueName); | ||
| if (value == null) | ||
| return null; | ||
| return Convert.ToInt32(value) != 0; | ||
| } | ||
| catch | ||
| { | ||
| return null; | ||
| } | ||
| } | ||
|
|
||
| private void WriteAnalyticsEnabledState(string productKeyId, bool value) | ||
| { | ||
| using var key = Registry.CurrentUser.CreateSubKey(GetKeyPath(productKeyId)); | ||
| key?.SetValue(kRegistryValueName, value ? 1 : 0, RegistryValueKind.DWord); | ||
| } | ||
|
|
||
| private void RemoveProductAnalyticsEnabledSetting() | ||
| { | ||
| using var key = | ||
| Registry.CurrentUser.OpenSubKey(GetKeyPath(ProductRegistryKeyId), true); | ||
| if (key != null && key.GetValueNames().Contains(kRegistryValueName)) | ||
| key.DeleteValue(kRegistryValueName); | ||
| } | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.