Fix Clipboard clear after time on iOS (#1679)

* Fixed Clipboard clear after x seconds depending on what the user set. Also refactored a bit to make the Clipboard a custom service to provide a better way to handle this situation #1464

* Clear some usings #1464
This commit is contained in:
Federico Maccaroni 2021-12-10 17:41:36 -03:00 committed by GitHub
parent 23a164b245
commit 705b8ac12b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 157 additions and 131 deletions

View File

@ -149,6 +149,7 @@
<Compile Include="Utilities\ThemeHelpers.cs" />
<Compile Include="WebAuthCallbackActivity.cs" />
<Compile Include="Renderers\SelectableLabelRenderer.cs" />
<Compile Include="Services\ClipboardService.cs" />
</ItemGroup>
<ItemGroup>
<AndroidAsset Include="Assets\FontAwesome.ttf" />

View File

@ -34,9 +34,7 @@ namespace Bit.Droid
private IBroadcasterService _broadcasterService;
private IUserService _userService;
private IAppIdService _appIdService;
private IStorageService _storageService;
private IEventService _eventService;
private PendingIntent _clearClipboardPendingIntent;
private PendingIntent _eventUploadPendingIntent;
private AppOptions _appOptions;
private string _activityKey = $"{nameof(MainActivity)}_{Java.Lang.JavaSystem.CurrentTimeMillis().ToString()}";
@ -48,9 +46,6 @@ namespace Bit.Droid
var eventUploadIntent = new Intent(this, typeof(EventUploadReceiver));
_eventUploadPendingIntent = PendingIntent.GetBroadcast(this, 0, eventUploadIntent,
PendingIntentFlags.UpdateCurrent);
var clearClipboardIntent = new Intent(this, typeof(ClearClipboardAlarmReceiver));
_clearClipboardPendingIntent = PendingIntent.GetBroadcast(this, 0, clearClipboardIntent,
PendingIntentFlags.UpdateCurrent);
var policy = new StrictMode.ThreadPolicy.Builder().PermitAll().Build();
StrictMode.SetThreadPolicy(policy);
@ -60,7 +55,6 @@ namespace Bit.Droid
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
_userService = ServiceContainer.Resolve<IUserService>("userService");
_appIdService = ServiceContainer.Resolve<IAppIdService>("appIdService");
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_eventService = ServiceContainer.Resolve<IEventService>("eventService");
TabLayoutResource = Resource.Layout.Tabbar;
@ -108,10 +102,6 @@ namespace Bit.Droid
{
ExitApp();
}
else if (message.Command == "copiedToClipboard")
{
var task = ClearClipboardAlarmAsync(message.Data as Tuple<string, int?, bool>);
}
});
}
@ -388,30 +378,6 @@ namespace Bit.Droid
Java.Lang.JavaSystem.Exit(0);
}
private async Task ClearClipboardAlarmAsync(Tuple<string, int?, bool> data)
{
if (data.Item3)
{
return;
}
var clearMs = data.Item2;
if (clearMs == null)
{
var clearSeconds = await _storageService.GetAsync<int?>(Constants.ClearClipboardKey);
if (clearSeconds != null)
{
clearMs = clearSeconds.Value * 1000;
}
}
if (clearMs == null)
{
return;
}
var triggerMs = Java.Lang.JavaSystem.CurrentTimeMillis() + clearMs.Value;
var alarmManager = GetSystemService(AlarmService) as AlarmManager;
alarmManager.Set(AlarmType.Rtc, triggerMs, _clearClipboardPendingIntent);
}
private void StartEventAlarm()
{
var alarmManager = GetSystemService(AlarmService) as AlarmManager;

View File

@ -113,6 +113,7 @@ namespace Bit.Droid
ServiceContainer.Register<ICryptoPrimitiveService>("cryptoPrimitiveService", cryptoPrimitiveService);
ServiceContainer.Register<IStorageService>("storageService", mobileStorageService);
ServiceContainer.Register<IStorageService>("secureStorageService", secureStorageService);
ServiceContainer.Register<IClipboardService>("clipboardService", new ClipboardService(mobileStorageService));
ServiceContainer.Register<IDeviceActionService>("deviceActionService", deviceActionService);
ServiceContainer.Register<IPlatformUtilsService>("platformUtilsService", platformUtilsService);
ServiceContainer.Register<IBiometricService>("biometricService", biometricService);

View File

@ -0,0 +1,57 @@
using System;
using System.Threading.Tasks;
using Android.App;
using Android.Content;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Droid.Receivers;
using Plugin.CurrentActivity;
using Xamarin.Essentials;
namespace Bit.Droid.Services
{
public class ClipboardService : IClipboardService
{
private readonly IStorageService _storageService;
private readonly Lazy<PendingIntent> _clearClipboardPendingIntent;
public ClipboardService(IStorageService storageService)
{
_storageService = storageService;
_clearClipboardPendingIntent = new Lazy<PendingIntent>(() =>
PendingIntent.GetBroadcast(CrossCurrentActivity.Current.Activity,
0,
new Intent(CrossCurrentActivity.Current.Activity, typeof(ClearClipboardAlarmReceiver)),
PendingIntentFlags.UpdateCurrent));
}
public async Task CopyTextAsync(string text, int expiresInMs = -1)
{
await Clipboard.SetTextAsync(text);
await ClearClipboardAlarmAsync(expiresInMs);
}
private async Task ClearClipboardAlarmAsync(int expiresInMs = -1)
{
var clearMs = expiresInMs;
if (clearMs < 0)
{
// if not set then we need to check if the user set this config
var clearSeconds = await _storageService.GetAsync<int?>(Constants.ClearClipboardKey);
if (clearSeconds != null)
{
clearMs = clearSeconds.Value * 1000;
}
}
if (clearMs < 0)
{
return;
}
var triggerMs = Java.Lang.JavaSystem.CurrentTimeMillis() + clearMs;
var alarmManager = CrossCurrentActivity.Current.Activity.GetSystemService(Context.AlarmService) as AlarmManager;
alarmManager.Set(AlarmType.Rtc, triggerMs, _clearClipboardPendingIntent.Value);
}
}
}

View File

@ -12,6 +12,7 @@ namespace Bit.App.Pages
{
private readonly IPlatformUtilsService _platformUtilsService;
private readonly IPasswordGenerationService _passwordGenerationService;
private readonly IClipboardService _clipboardService;
private bool _showNoData;
@ -20,6 +21,7 @@ namespace Bit.App.Pages
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_passwordGenerationService = ServiceContainer.Resolve<IPasswordGenerationService>(
"passwordGenerationService");
_clipboardService = ServiceContainer.Resolve<IClipboardService>("clipboardService");
PageTitle = AppResources.PasswordHistory;
History = new ExtendedObservableCollection<GeneratedPasswordHistory>();
@ -51,7 +53,7 @@ namespace Bit.App.Pages
private async void CopyAsync(GeneratedPasswordHistory ph)
{
await _platformUtilsService.CopyToClipboardAsync(ph.Password);
await _clipboardService.CopyTextAsync(ph.Password);
_platformUtilsService.ShowToast("info", null,
string.Format(AppResources.ValueHasBeenCopied, AppResources.Password));
}

View File

@ -13,6 +13,7 @@ namespace Bit.App.Pages
{
private readonly IPasswordGenerationService _passwordGenerationService;
private readonly IPlatformUtilsService _platformUtilsService;
private readonly IClipboardService _clipboardService;
private PasswordGenerationOptions _options;
private PasswordGeneratorPolicyOptions _enforcedPolicyOptions;
@ -38,6 +39,8 @@ namespace Bit.App.Pages
_passwordGenerationService = ServiceContainer.Resolve<IPasswordGenerationService>(
"passwordGenerationService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_clipboardService = ServiceContainer.Resolve<IClipboardService>("clipboardService");
PageTitle = AppResources.PasswordGenerator;
TypeOptions = new List<string> { AppResources.Password, AppResources.Passphrase };
}
@ -305,7 +308,7 @@ namespace Bit.App.Pages
public async Task CopyAsync()
{
await _platformUtilsService.CopyToClipboardAsync(Password);
await _clipboardService.CopyTextAsync(Password);
_platformUtilsService.ShowToast("success", null,
string.Format(AppResources.ValueHasBeenCopied, AppResources.Password));
}

View File

@ -28,6 +28,7 @@ namespace Bit.App.Pages
private readonly IPolicyService _policyService;
private readonly ILocalizeService _localizeService;
private readonly IKeyConnectorService _keyConnectorService;
private readonly IClipboardService _clipboardService;
private const int CustomVaultTimeoutValue = -100;
@ -78,6 +79,7 @@ namespace Bit.App.Pages
_policyService = ServiceContainer.Resolve<IPolicyService>("policyService");
_localizeService = ServiceContainer.Resolve<ILocalizeService>("localizeService");
_keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
_clipboardService = ServiceContainer.Resolve<IClipboardService>("clipboardService");
GroupedItems = new ExtendedObservableCollection<SettingsPageListGroup>();
PageTitle = AppResources.Settings;
@ -135,7 +137,7 @@ namespace Bit.App.Pages
AppResources.Close);
if (copy)
{
await _platformUtilsService.CopyToClipboardAsync(debugText);
await _clipboardService.CopyTextAsync(debugText);
}
}

View File

@ -12,6 +12,7 @@ namespace Bit.App.Pages
{
private readonly IPlatformUtilsService _platformUtilsService;
private readonly ICipherService _cipherService;
private readonly IClipboardService _clipboardService;
private bool _showNoData;
@ -19,6 +20,7 @@ namespace Bit.App.Pages
{
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_cipherService = ServiceContainer.Resolve<ICipherService>("cipherService");
_clipboardService = ServiceContainer.Resolve<IClipboardService>("clipboardService");
PageTitle = AppResources.PasswordHistory;
History = new ExtendedObservableCollection<PasswordHistoryView>();
@ -45,7 +47,7 @@ namespace Bit.App.Pages
private async void CopyAsync(PasswordHistoryView ph)
{
await _platformUtilsService.CopyToClipboardAsync(ph.Password);
await _clipboardService.CopyTextAsync(ph.Password);
_platformUtilsService.ShowToast("info", null,
string.Format(AppResources.ValueHasBeenCopied, AppResources.Password));
}

View File

@ -1,4 +1,8 @@
using Bit.App.Abstractions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.App.Utilities;
using Bit.Core.Abstractions;
@ -6,10 +10,6 @@ using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.View;
using Bit.Core.Utilities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Xamarin.Forms;
namespace Bit.App.Pages
@ -26,6 +26,8 @@ namespace Bit.App.Pages
private readonly IEventService _eventService;
private readonly IPasswordRepromptService _passwordRepromptService;
private readonly ILocalizeService _localizeService;
private readonly IClipboardService _clipboardService;
private CipherView _cipher;
private List<ViewPageFieldViewModel> _fields;
private bool _canAccessPremium;
@ -54,6 +56,8 @@ namespace Bit.App.Pages
_eventService = ServiceContainer.Resolve<IEventService>("eventService");
_passwordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService");
_localizeService = ServiceContainer.Resolve<ILocalizeService>("localizeService");
_clipboardService = ServiceContainer.Resolve<IClipboardService>("clipboardService");
CopyCommand = new Command<string>((id) => CopyAsync(id, null));
CopyUriCommand = new Command<LoginUriView>(CopyUri);
CopyFieldCommand = new Command<FieldView>(CopyField);
@ -653,7 +657,7 @@ namespace Bit.App.Pages
if (text != null)
{
await _platformUtilsService.CopyToClipboardAsync(text);
await _clipboardService.CopyTextAsync(text);
if (!string.IsNullOrWhiteSpace(name))
{
_platformUtilsService.ShowToast("info", null, string.Format(AppResources.ValueHasBeenCopied, name));

View File

@ -1,19 +1,19 @@
using Bit.App.Abstractions;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Models;
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Plugin.Fingerprint;
using Plugin.Fingerprint.Abstractions;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Xamarin.Essentials;
using Xamarin.Forms;
namespace Bit.App.Services
{
public class MobilePlatformUtilsService : IPlatformUtilsService
{
{
private static readonly Random _random = new Random();
private const int DialogPromiseExpiration = 600000; // 10 minutes
@ -21,6 +21,7 @@ namespace Bit.App.Services
private readonly IDeviceActionService _deviceActionService;
private readonly IMessagingService _messagingService;
private readonly IBroadcasterService _broadcasterService;
private readonly Dictionary<int, Tuple<TaskCompletionSource<bool>, DateTime>> _showDialogResolves =
new Dictionary<int, Tuple<TaskCompletionSource<bool>, DateTime>>();
@ -201,17 +202,6 @@ namespace Bit.App.Services
return false;
}
public async Task CopyToClipboardAsync(string text, Dictionary<string, object> options = null)
{
var clearMs = options != null && options.ContainsKey("clearMs") ? (int?)options["clearMs"] : null;
var clearing = options != null && options.ContainsKey("clearing") ? (bool)options["clearing"] : false;
await Clipboard.SetTextAsync(text);
if (!clearing)
{
_messagingService.Send("copiedToClipboard", new Tuple<string, int?, bool>(text, clearMs, clearing));
}
}
public async Task<string> ReadFromClipboardAsync(Dictionary<string, object> options = null)
{
return await Clipboard.GetTextAsync();

View File

@ -27,6 +27,8 @@ namespace Bit.App.Utilities
var platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
var eventService = ServiceContainer.Resolve<IEventService>("eventService");
var vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
var clipboardService = ServiceContainer.Resolve<IClipboardService>("clipboardService");
var options = new List<string> { AppResources.View };
if (!cipher.IsDeleted)
{
@ -92,7 +94,7 @@ namespace Bit.App.Utilities
}
else if (selection == AppResources.CopyUsername)
{
await platformUtilsService.CopyToClipboardAsync(cipher.Login.Username);
await clipboardService.CopyTextAsync(cipher.Login.Username);
platformUtilsService.ShowToast("info", null,
string.Format(AppResources.ValueHasBeenCopied, AppResources.Username));
}
@ -100,7 +102,7 @@ namespace Bit.App.Utilities
{
if (cipher.Reprompt == CipherRepromptType.None || await passwordRepromptService.ShowPasswordPromptAsync())
{
await platformUtilsService.CopyToClipboardAsync(cipher.Login.Password);
await clipboardService.CopyTextAsync(cipher.Login.Password);
platformUtilsService.ShowToast("info", null,
string.Format(AppResources.ValueHasBeenCopied, AppResources.Password));
var task = eventService.CollectAsync(Core.Enums.EventType.Cipher_ClientCopiedPassword, cipher.Id);
@ -114,7 +116,7 @@ namespace Bit.App.Utilities
var totp = await totpService.GetCodeAsync(cipher.Login.Totp);
if (!string.IsNullOrWhiteSpace(totp))
{
await platformUtilsService.CopyToClipboardAsync(totp);
await clipboardService.CopyTextAsync(totp);
platformUtilsService.ShowToast("info", null,
string.Format(AppResources.ValueHasBeenCopied, AppResources.VerificationCodeTotp));
}
@ -128,7 +130,7 @@ namespace Bit.App.Utilities
{
if (cipher.Reprompt == CipherRepromptType.None || await passwordRepromptService.ShowPasswordPromptAsync())
{
await platformUtilsService.CopyToClipboardAsync(cipher.Card.Number);
await clipboardService.CopyTextAsync(cipher.Card.Number);
platformUtilsService.ShowToast("info", null,
string.Format(AppResources.ValueHasBeenCopied, AppResources.Number));
}
@ -137,7 +139,7 @@ namespace Bit.App.Utilities
{
if (cipher.Reprompt == CipherRepromptType.None || await passwordRepromptService.ShowPasswordPromptAsync())
{
await platformUtilsService.CopyToClipboardAsync(cipher.Card.Code);
await clipboardService.CopyTextAsync(cipher.Card.Code);
platformUtilsService.ShowToast("info", null,
string.Format(AppResources.ValueHasBeenCopied, AppResources.SecurityCode));
var task = eventService.CollectAsync(Core.Enums.EventType.Cipher_ClientCopiedCardCode, cipher.Id);
@ -145,7 +147,7 @@ namespace Bit.App.Utilities
}
else if (selection == AppResources.CopyNotes)
{
await platformUtilsService.CopyToClipboardAsync(cipher.Notes);
await clipboardService.CopyTextAsync(cipher.Notes);
platformUtilsService.ShowToast("info", null,
string.Format(AppResources.ValueHasBeenCopied, AppResources.Notes));
}
@ -200,7 +202,8 @@ namespace Bit.App.Utilities
return;
}
var platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
await platformUtilsService.CopyToClipboardAsync(GetSendUrl(send));
var clipboardService = ServiceContainer.Resolve<IClipboardService>("clipboardService");
await clipboardService.CopyTextAsync(GetSendUrl(send));
platformUtilsService.ShowToast("info", null,
string.Format(AppResources.ValueHasBeenCopied, AppResources.SendLink));
}

View File

@ -0,0 +1,16 @@
using System.Threading.Tasks;
namespace Bit.Core.Abstractions
{
public interface IClipboardService
{
/// <summary>
/// Copies the <paramref name="text"/> to the Clipboard.
/// If <paramref name="expiresInMs"/> is set > 0 then the Clipboard will be cleared after this time in milliseconds.
/// if less than 0 then it takes the configuration that the user set in Options.
/// </summary>
/// <param name="text">Text to be copied to the Clipboard</param>
/// <param name="expiresInMs">Expiration time in milliseconds of the copied text</param>
Task CopyTextAsync(string text, int expiresInMs = -1);
}
}

View File

@ -9,7 +9,6 @@ namespace Bit.Core.Abstractions
{
string IdentityClientId { get; }
Task CopyToClipboardAsync(string text, Dictionary<string, object> options = null);
string GetApplicationVersion();
DeviceType GetDevice();
string GetDeviceString();

View File

@ -0,0 +1,40 @@
using System.Threading.Tasks;
using Bit.Core.Abstractions;
using Foundation;
using MobileCoreServices;
using UIKit;
using Xamarin.Forms;
namespace Bit.iOS.Core.Services
{
public class ClipboardService : IClipboardService
{
private readonly IStorageService _storageService;
public ClipboardService(IStorageService storageService)
{
_storageService = storageService;
}
public async Task CopyTextAsync(string text, int expiresInMs = -1)
{
int clearSeconds = -1;
if (expiresInMs < 0)
{
clearSeconds = await _storageService.GetAsync<int?>(Bit.Core.Constants.ClearClipboardKey) ?? -1;
}
else
{
clearSeconds = expiresInMs * 1000;
}
var dictArr = new NSDictionary<NSString, NSObject>[1];
dictArr[0] = new NSDictionary<NSString, NSObject>(new NSString(UTType.UTF8PlainText), new NSString(text));
Device.BeginInvokeOnMainThread(() => UIPasteboard.General.SetItems(dictArr, new UIPasteboardOptions
{
LocalOnly = true,
ExpirationDate = clearSeconds > 0 ? NSDate.FromTimeIntervalSinceNow(clearSeconds) : null
}));
}
}
}

View File

@ -52,6 +52,7 @@ namespace Bit.iOS.Core.Utilities
var cryptoPrimitiveService = new CryptoPrimitiveService();
var mobileStorageService = new MobileStorageService(preferencesStorage, liteDbStorage);
var deviceActionService = new DeviceActionService(mobileStorageService, messagingService);
var clipboardService = new ClipboardService(mobileStorageService);
var platformUtilsService = new MobilePlatformUtilsService(deviceActionService, messagingService,
broadcasterService);
var biometricService = new BiometricService(mobileStorageService);
@ -67,6 +68,7 @@ namespace Bit.iOS.Core.Utilities
ServiceContainer.Register<IStorageService>("storageService", mobileStorageService);
ServiceContainer.Register<IStorageService>("secureStorageService", secureStorageService);
ServiceContainer.Register<IDeviceActionService>("deviceActionService", deviceActionService);
ServiceContainer.Register<IClipboardService>("clipboardService", clipboardService);
ServiceContainer.Register<IPlatformUtilsService>("platformUtilsService", platformUtilsService);
ServiceContainer.Register<IBiometricService>("biometricService", biometricService);
ServiceContainer.Register<ICryptoFunctionService>("cryptoFunctionService", cryptoFunctionService);

View File

@ -191,6 +191,7 @@
<Compile Include="Views\Toast.cs" />
<Compile Include="Renderers\SelectableLabelRenderer.cs" />
<Compile Include="Effects\ScrollEnabledEffect.cs" />
<Compile Include="Services\ClipboardService.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\App\App.csproj">

View File

@ -71,14 +71,6 @@ namespace Bit.iOS
iOSCoreHelpers.AppearanceAdjustments();
});
}
else if (message.Command == "copiedToClipboard")
{
Device.BeginInvokeOnMainThread(() =>
{
var task = ClearClipboardTimerAsync(message.Data as Tuple<string, int?, bool>);
});
}
else if (message.Command == "listenYubiKeyOTP")
{
iOSCoreHelpers.ListenYubiKey((bool)message.Data, _deviceActionService, _nfcSession, _nfcDelegate);
@ -329,61 +321,6 @@ namespace Bit.iOS
"pushNotificationService", iosPushNotificationService);
}
private async Task ClearClipboardTimerAsync(Tuple<string, int?, bool> data)
{
if (data.Item3)
{
return;
}
var clearMs = data.Item2;
if (clearMs == null)
{
var clearSeconds = await _storageService.GetAsync<int?>(Constants.ClearClipboardKey);
if (clearSeconds != null)
{
clearMs = clearSeconds.Value * 1000;
}
}
if (clearMs == null)
{
return;
}
if (_clipboardBackgroundTaskId > 0)
{
UIApplication.SharedApplication.EndBackgroundTask(_clipboardBackgroundTaskId);
_clipboardBackgroundTaskId = 0;
}
_clipboardBackgroundTaskId = UIApplication.SharedApplication.BeginBackgroundTask(() =>
{
UIApplication.SharedApplication.EndBackgroundTask(_clipboardBackgroundTaskId);
_clipboardBackgroundTaskId = 0;
});
_clipboardTimer?.Invalidate();
_clipboardTimer?.Dispose();
_clipboardTimer = null;
var lastClipboardChangeCount = UIPasteboard.General.ChangeCount;
var clearMsSpan = TimeSpan.FromMilliseconds(clearMs.Value);
_clipboardTimer = NSTimer.CreateScheduledTimer(clearMsSpan, timer =>
{
Device.BeginInvokeOnMainThread(() =>
{
var changeNow = UIPasteboard.General.ChangeCount;
if (changeNow == 0 || lastClipboardChangeCount == changeNow)
{
UIPasteboard.General.String = string.Empty;
}
_clipboardTimer?.Invalidate();
_clipboardTimer?.Dispose();
_clipboardTimer = null;
if (_clipboardBackgroundTaskId > 0)
{
UIApplication.SharedApplication.EndBackgroundTask(_clipboardBackgroundTaskId);
_clipboardBackgroundTaskId = 0;
}
});
});
}
private void ShowAppExtension(ExtensionPageViewModel extensionPageViewModel)
{
var itemProvider = new NSItemProvider(new NSDictionary(), Core.Constants.UTTypeAppExtensionSetup);