From 642a2592774152420a0b4e246f8187cbc759c364 Mon Sep 17 00:00:00 2001 From: William Bradley Date: Sun, 19 Nov 2017 22:24:13 +1300 Subject: [PATCH] -WARNING: Major Breaking Change! All App Settings now default as Local, with a boolean Property to make them Roam. This might cause issues if you don't realise it and invert it if set (It use to be all properties roam unless IsLocal = true). -Removed GetSettingsContainer from Helpers, getting the RoamingSettingsContainer automatically returns Local Settings if not supported. -You can now find out the Current Services Platform by checking AppServices.ServicePlatform, and UI Platform with AppServices.UI.UIPlatform. -Default Creation Collision options can now be changed with IOBindings.DefaultFileCreationCollision and IOBindings.DefaultFolderCreationCollision. -Created IPathResolver as a way for different Layers to check if the provided path from IOBindings.GetFile and IOBindings.GetFolder returns a Valid StorageItem from their service. This is to produce SMBFileContainers from SMB Paths, if a path string is provided. This will also be used to determine Android SAF Files and Folders from a Path. -Renamed CreateContainer to GetContainer for ISettingsContainer as it will return existing containers. -Moved Picker Functions out of IOBindings into new Abstract FileSystemPickers Class, as well as FutureAccess Methods into IFutureAccessManager. This reduces the length of the class for Platforms that don't support picking or Future Access. It also makes checking support easier. -Removed ICredentialManager Update Function, use Store to get back the Platform Credential Container, which will self update. -CredentialContainers now have platform specific implementations to handle automatic value updating. -Added more Abstractions to IOBindings, Local/RoamingSettings are now properties instead of methods, renamed OpenFile/Folder to OpenFile/FolderForDisplay as it is more understandable. It is now easier to implement IOBindings due to these changes. -Added RequestTextFromUserAsync method, a way to request text from the user in a Modal Dialog. -SMBFile/FolderContainers now use SMB Paths instead of UNC Paths as the path property. -To use the SMB Extension, you now need to call SMBService.Register(). -Added Type Checking on the UWPSettingsContainer, so that exception don't occur if the value is the wrong type. -UWP File/Folder Pickers and PromptUser methods are now Background Thread safe, as they will automatically go on the UI thread to work. -Fixed Android Dialog prompts not working from background threads. -AndroidSAFFile/Folders now display their paths correctly. -AndroidSettingsContainer now works properly, creating Settings Containers now produces the Preference Store on the Device, fetching All Subcontainers also works now, GetValue now has Type Safety. -Created AndroidCredentialManager and AndroidCredentialContainer, Stores credentials in a hidden SettingsContainer, using Encryption. You can replace the IKeyGenerator in AndroidAppSettings to provide a stronger, more secure Credential Encryption. -Created AndroidFutureAccessManager, Grants permissions and redeemable token. -Created TestPageGenerators, a cross platform way to generate Test Buttons, and Result Properties quickly. -Converted most Test Models to TestPages, created Settings and Credential Manager Test Pages. --- Android/AndroidAppServices.cs | 7 +- Android/Models/AndroidCredentialContainer.cs | 215 ++++++++++++++ .../DialogHandling/AlertDialogBuilderBase.cs | 6 +- .../Models/Encryption/DefaultKeyGenerator.cs | 22 ++ Android/Models/Encryption/IKeyGenerator.cs | 12 + .../FileSystem/AndroidSAFFileContainer.cs | 7 +- .../FileSystem/AndroidSAFFolderContainer.cs | 7 +- .../Models/FileSystem/IAndroidSAFContainer.cs | 7 + .../Settings/AndroidSettingsContainer.cs | 91 +++--- Android/Services/AndroidCredentialManager.cs | 57 ++++ Android/Services/AndroidFileSystemPickers.cs | 99 +++++++ .../Services/AndroidFutureAccessManager.cs | 81 ++++++ Android/Services/AndroidIOBindings.cs | 177 +----------- Android/Services/AndroidUIBindings.cs | 25 +- Console/Services/ConsoleUIBindings.cs | 9 + Core/AppServices.cs | 20 +- Core/Common/PlatformBindingHelpers.cs | 34 ++- Core/Enums/Platform.cs | 11 + Core/Models/CredentialContainer.cs | 10 +- .../FileSystem/Containers/FolderContainer.cs | 10 +- Core/Models/FileSystem/CorePathResolver.cs | 24 ++ Core/Models/FileSystem/IPathResolver.cs | 9 + .../{ => Options}/FileTypeFilter.cs | 2 +- .../{ => Options}/FolderOpenOptions.cs | 2 +- .../FileSystem/{ => Options}/QueryOptions.cs | 2 +- .../FileSystem/Pickers/PickerProperties.cs | 1 + Core/Models/Settings/AppSetting.cs | 21 +- Core/Models/Settings/AppSettingList.cs | 13 +- Core/Models/Settings/ISettingsContainer.cs | 2 +- .../Settings/Properties/PropertyList.cs | 2 +- .../Models/Settings/Properties/PropertySet.cs | 2 +- .../Settings/Properties/SerialPropertyList.cs | 4 +- Core/Models/Settings/SerialAppSettingList.cs | 13 +- Core/Models/Settings/SerialisedAppSetting.cs | 21 +- Core/Services/FileSystemPickers.cs | 70 +++++ Core/Services/ICredentialManager.cs | 4 +- Core/Services/IFutureAccessManager.cs | 46 +++ Core/Services/IOBindings.cs | 239 ++++++++++------ Core/Services/UIBindings.cs | 34 +++ .../Models/Settings/CoreSettingsContainer.cs | 2 +- NETCore/NETCoreServices.cs | 2 +- NETCore/Services/CoreIOBindings.cs | 84 +----- SMB/Common/SMBExtensions.cs | 2 +- SMB/Models/FileSystem/SMBFileContainer.cs | 3 +- SMB/Models/FileSystem/SMBFolderContainer.cs | 5 +- SMB/Models/FileSystem/SMBPathResolver.cs | 26 ++ SMB/SMBService.cs | 18 ++ SMB/SMBSettings.cs | 9 - TestApps/Android/Test-Android/MainActivity.cs | 14 +- .../Resources/Resource.Designer.cs | 158 +++++------ .../Resources/layout/BindingTests.axml | 60 ++-- .../Resources/layout/LoopTests.axml | 20 -- .../Resources/layout/Pickers.axml | 21 -- .../Services/AndroidTestPageGenerator.cs | 60 ++++ .../Android/Test-Android/Test-Android.csproj | 5 +- .../Test-Android/Views/BindingTests.cs | 8 + .../Test-Android/Views/ContextMenuTest.cs | 2 +- .../Test-Android/Views/CredentialTests.cs | 22 ++ .../Test-Android/Views/FilePickerTest.cs | 12 +- .../Android/Test-Android/Views/FileTests.cs | 43 +-- .../Android/Test-Android/Views/LoopTests.cs | 23 +- .../Test-Android/Views/SettingTests.cs | 22 ++ TestApps/Tests-Universal/Preparation.cs | 12 + .../TestGenerator/ITestPageGenerator.cs | 9 + .../TestGenerator/TestProperty.cs | 28 ++ .../Tests-Universal/TestGenerator/TestTask.cs | 19 ++ ...extMenuTests.cs => ContextMenuTestPage.cs} | 4 +- .../Tests/CredentialTestPage.cs | 128 +++++++++ .../Tests/FileFolderTestPage.cs | 94 +++++++ .../Tests-Universal/Tests/FileFolderTests.cs | 76 ----- .../Tests/LoopTimerTestPage.cs | 66 +++++ .../Tests-Universal/Tests/LoopTimerTests.cs | 47 ---- .../Tests-Universal/Tests/PickerTestPage.cs | 97 +++++++ TestApps/Tests-Universal/Tests/PickerTests.cs | 65 ----- .../Tests-Universal/Tests/SettingTestPage.cs | 264 ++++++++++++++++++ TestApps/Tests-Universal/Tests/TestPage.cs | 44 +++ TestApps/UWP/Test-UWP/App.xaml.cs | 1 + .../Test-UWP/Services/UWPTestPageGenerator.cs | 61 ++++ TestApps/UWP/Test-UWP/Test-UWP.csproj | 15 + TestApps/UWP/Test-UWP/Views/BindingTests.xaml | 2 + .../UWP/Test-UWP/Views/BindingTests.xaml.cs | 12 + .../Test-UWP/Views/ContextMenuTest.xaml.cs | 5 +- .../UWP/Test-UWP/Views/CredentialTests.xaml | 9 + .../Test-UWP/Views/CredentialTests.xaml.cs | 23 ++ .../UWP/Test-UWP/Views/FilePickerTest.xaml | 6 - .../UWP/Test-UWP/Views/FilePickerTest.xaml.cs | 7 +- TestApps/UWP/Test-UWP/Views/FileTests.xaml | 6 +- TestApps/UWP/Test-UWP/Views/FileTests.xaml.cs | 7 +- TestApps/UWP/Test-UWP/Views/LoopTests.xaml | 10 +- TestApps/UWP/Test-UWP/Views/LoopTests.xaml.cs | 11 +- .../UWP/Test-UWP/Views/SettingsTests.xaml | 9 + .../UWP/Test-UWP/Views/SettingsTests.xaml.cs | 23 ++ .../FileSystem/IUWPFileSystemContainer.cs | 9 + UWP/Models/FileSystem/UWPFileContainer.cs | 4 +- UWP/Models/FileSystem/UWPFolderContainer.cs | 4 +- UWP/Models/FileSystem/UWPPathResolver.cs | 28 ++ .../IncrementalLoadingConverter.cs | 6 +- ...s => UWPListSupportsIncrementalLoading.cs} | 4 +- ...ng.cs => UWPSupportsIncrementalLoading.cs} | 4 +- ...gsContainer.cs => UWPSettingsContainer.cs} | 19 +- UWP/Models/UWPCredentialContainer.cs | 27 ++ UWP/Services/UWPCredentialManager.cs | 54 ++-- UWP/Services/UWPFileSystemPickers.cs | 102 +++++++ UWP/Services/UWPFutureAccessManager.cs | 79 ++++++ UWP/Services/UWPIOBindings.cs | 173 +----------- UWP/Services/UWPUIBindings.cs | 72 ++++- UWP/UWPAppServices.cs | 3 +- XamarinForms/Services/XamarinUIBindings.cs | 20 +- XamarinForms/XamarinAppServices.cs | 3 +- 109 files changed, 2685 insertions(+), 1125 deletions(-) create mode 100644 Android/Models/AndroidCredentialContainer.cs create mode 100644 Android/Models/Encryption/DefaultKeyGenerator.cs create mode 100644 Android/Models/Encryption/IKeyGenerator.cs create mode 100644 Android/Models/FileSystem/IAndroidSAFContainer.cs create mode 100644 Android/Services/AndroidCredentialManager.cs create mode 100644 Android/Services/AndroidFileSystemPickers.cs create mode 100644 Android/Services/AndroidFutureAccessManager.cs create mode 100644 Core/Enums/Platform.cs create mode 100644 Core/Models/FileSystem/CorePathResolver.cs create mode 100644 Core/Models/FileSystem/IPathResolver.cs rename Core/Models/FileSystem/{ => Options}/FileTypeFilter.cs (71%) rename Core/Models/FileSystem/{ => Options}/FolderOpenOptions.cs (78%) rename Core/Models/FileSystem/{ => Options}/QueryOptions.cs (82%) create mode 100644 Core/Services/FileSystemPickers.cs create mode 100644 Core/Services/IFutureAccessManager.cs create mode 100644 SMB/Models/FileSystem/SMBPathResolver.cs create mode 100644 SMB/SMBService.cs delete mode 100644 TestApps/Android/Test-Android/Resources/layout/LoopTests.axml delete mode 100644 TestApps/Android/Test-Android/Resources/layout/Pickers.axml create mode 100644 TestApps/Android/Test-Android/Services/AndroidTestPageGenerator.cs create mode 100644 TestApps/Android/Test-Android/Views/CredentialTests.cs create mode 100644 TestApps/Android/Test-Android/Views/SettingTests.cs create mode 100644 TestApps/Tests-Universal/Preparation.cs create mode 100644 TestApps/Tests-Universal/TestGenerator/ITestPageGenerator.cs create mode 100644 TestApps/Tests-Universal/TestGenerator/TestProperty.cs create mode 100644 TestApps/Tests-Universal/TestGenerator/TestTask.cs rename TestApps/Tests-Universal/Tests/{ContextMenuTests.cs => ContextMenuTestPage.cs} (94%) create mode 100644 TestApps/Tests-Universal/Tests/CredentialTestPage.cs create mode 100644 TestApps/Tests-Universal/Tests/FileFolderTestPage.cs delete mode 100644 TestApps/Tests-Universal/Tests/FileFolderTests.cs create mode 100644 TestApps/Tests-Universal/Tests/LoopTimerTestPage.cs delete mode 100644 TestApps/Tests-Universal/Tests/LoopTimerTests.cs create mode 100644 TestApps/Tests-Universal/Tests/PickerTestPage.cs delete mode 100644 TestApps/Tests-Universal/Tests/PickerTests.cs create mode 100644 TestApps/Tests-Universal/Tests/SettingTestPage.cs create mode 100644 TestApps/Tests-Universal/Tests/TestPage.cs create mode 100644 TestApps/UWP/Test-UWP/Services/UWPTestPageGenerator.cs create mode 100644 TestApps/UWP/Test-UWP/Views/CredentialTests.xaml create mode 100644 TestApps/UWP/Test-UWP/Views/CredentialTests.xaml.cs create mode 100644 TestApps/UWP/Test-UWP/Views/SettingsTests.xaml create mode 100644 TestApps/UWP/Test-UWP/Views/SettingsTests.xaml.cs create mode 100644 UWP/Models/FileSystem/IUWPFileSystemContainer.cs create mode 100644 UWP/Models/FileSystem/UWPPathResolver.cs rename UWP/Models/IncrementalLoading/{WinListSupportsIncrementalLoading.cs => UWPListSupportsIncrementalLoading.cs} (95%) rename UWP/Models/IncrementalLoading/{WinSupportsIncrementalLoading.cs => UWPSupportsIncrementalLoading.cs} (85%) rename UWP/Models/Settings/{WinSettingsContainer.cs => UWPSettingsContainer.cs} (75%) create mode 100644 UWP/Models/UWPCredentialContainer.cs create mode 100644 UWP/Services/UWPFileSystemPickers.cs create mode 100644 UWP/Services/UWPFutureAccessManager.cs diff --git a/Android/AndroidAppServices.cs b/Android/AndroidAppServices.cs index 4566399..a6d2b03 100644 --- a/Android/AndroidAppServices.cs +++ b/Android/AndroidAppServices.cs @@ -1,4 +1,6 @@ using Android.App; +using PlatformBindings.Enums; +using PlatformBindings.Models.Encryption; using PlatformBindings.Services; using System; @@ -8,13 +10,15 @@ public class AndroidAppServices : AppServices { public AndroidAppServices(bool HasUI, bool UseAppCompatUI) : this(HasUI) { + KeyGenerator = new DefaultKeyGenerator(); AndroidAppServices.UseAppCompatUI = UseAppCompatUI; } - public AndroidAppServices(bool HasUI) : base(HasUI) + public AndroidAppServices(bool HasUI) : base(HasUI, Platform.Android) { if (HasUI) UI = new AndroidUIBindings(); IO = new AndroidIOBindings(); + Credentials = new AndroidCredentialManager(); } public override Version GetAppVersion() @@ -23,6 +27,7 @@ public override Version GetAppVersion() return new Version(info.VersionName); } + public static IKeyGenerator KeyGenerator { get; protected set; } public static bool UseAppCompatUI { get; private set; } = false; } } \ No newline at end of file diff --git a/Android/Models/AndroidCredentialContainer.cs b/Android/Models/AndroidCredentialContainer.cs new file mode 100644 index 0000000..b46e5b2 --- /dev/null +++ b/Android/Models/AndroidCredentialContainer.cs @@ -0,0 +1,215 @@ +using System; +using PlatformBindings.Models.Settings; +using Javax.Crypto; +using Java.Security; +using Javax.Crypto.Spec; +using System.Text; +using System.Runtime.InteropServices; +using System.Security; +using Java.IO; + +namespace PlatformBindings.Models +{ + internal class AndroidCredentialContainer : CredentialContainer + { + public AndroidCredentialContainer(ISettingsContainer Container, CredentialContainer Credentials) : this() + { + this.Container = Container; + var newheader = $"{Credentials.ResourceName}^R^{Credentials.Username}"; + EncryptedHeader = Encrypt(newheader, writer); + SetHeaderData(newheader); + + Password = Credentials.Password; + } + + public AndroidCredentialContainer(ISettingsContainer Container, string Header) : this() + { + this.Container = Container; + EncryptedHeader = Header; + try + { + var decryptedHeader = Decrypt(Header); + SetHeaderData(decryptedHeader); + } + catch + { + Container.RemoveKey(Header); + HeaderData = new string[] { DecryptionError, DecryptionError }; + } + } + + private void SetHeaderData(string DecryptedHeader) + { + HeaderData = DecryptedHeader.Split(new string[] { "^R^" }, StringSplitOptions.None); + } + + public override string ResourceName { get => HeaderData[0]; } + public override string Username { get => HeaderData[1]; } + + public override string Password + { + get + { + try + { + var encryptedVal = Container.GetValue(EncryptedHeader); + return Decrypt(encryptedVal); + } + catch { return null; } + } + set + { + var encryptedVal = Encrypt(value, writer); + Container.SetValue(EncryptedHeader, encryptedVal); + } + } + + /// + /// Decrypted HeaderData + /// + private string[] HeaderData { get; set; } + + internal string EncryptedHeader { get; } + internal ISettingsContainer Container { get; } + + /* + Copyright (C) 2012 Sveinung Kval Bakken, sveinung.bakken@gmail.com + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + */ + // Converted to C# for PlatformBindings-Android. + #region Encryption + + private AndroidCredentialContainer() + { + try + { + writer = Cipher.GetInstance(TRANSFORMATION); + reader = Cipher.GetInstance(TRANSFORMATION); + Encoding = Encoding.GetEncoding(CHARSET); + + using (var key = AndroidAppServices.KeyGenerator.GetSecureKey()) + { + InitCiphers(key); + } + } + catch (GeneralSecurityException e) + { + throw new SecurityException(e.Message); + } + catch (UnsupportedEncodingException e) + { + throw new SecurityException(e.Message); + } + } + + private readonly string TRANSFORMATION = "AES/CBC/PKCS5Padding"; + private readonly string SECRET_KEY_HASH_TRANSFORMATION = "SHA-256"; + private readonly string CHARSET = "UTF-8"; + public static readonly string DecryptionError = "ERR_DECRYPT"; + + private Encoding Encoding; + private Cipher writer; + private Cipher reader; + + protected void InitCiphers(SecureString secureKey) + { + var bstr = Marshal.SecureStringToBSTR(secureKey); + var key = Marshal.PtrToStringBSTR(bstr); + + IvParameterSpec ivSpec = GetIv(); + SecretKeySpec secretKey = GetSecretKey(key); + + writer.Init(CipherMode.EncryptMode, secretKey, ivSpec); + reader.Init(CipherMode.DecryptMode, secretKey, ivSpec); + } + + protected IvParameterSpec GetIv() + { + byte[] iv = new byte[writer.BlockSize]; + Array.Copy(Encoding.GetBytes("fldsjfodasjifudslfjdsaofshaufihadsf"), 0, iv, 0, writer.BlockSize); + return new IvParameterSpec(iv); + } + + protected SecretKeySpec GetSecretKey(string key) + { + byte[] keyBytes = CreateKeyBytes(key); + return new SecretKeySpec(keyBytes, TRANSFORMATION); + } + + protected byte[] CreateKeyBytes(string key) + { + MessageDigest md = MessageDigest.GetInstance(SECRET_KEY_HASH_TRANSFORMATION); + md.Reset(); + var keyBytes = md.Digest(Encoding.GetBytes(key)); + return keyBytes; + } + + protected String Encrypt(string value, Cipher writer) + { + byte[] secureValue; + try + { + secureValue = Convert(writer, Encoding.GetEncoding(CHARSET).GetBytes(value)); + } + catch (UnsupportedEncodingException e) + { + throw new SecurityException(e.Message); + } + var secureValueEncoded = System.Convert.ToBase64String(secureValue); + return secureValueEncoded; + } + + protected string Decrypt(string securedEncodedValue) + { + byte[] securedValue = System.Convert.FromBase64String(securedEncodedValue); + byte[] value = Convert(reader, securedValue); + try + { + return Encoding.GetString(value); + } + catch (UnsupportedEncodingException e) + { + throw new SecurityException(e.Message); + } + } + + private static byte[] Convert(Cipher cipher, byte[] bs) + { + try + { + return cipher.DoFinal(bs); + } + catch (Exception e) + { + throw new SecurityException(e.Message); + } + } + + ~AndroidCredentialContainer() + { + writer?.Dispose(); + reader?.Dispose(); + } + + #endregion Encryption + } +} \ No newline at end of file diff --git a/Android/Models/DialogHandling/AlertDialogBuilderBase.cs b/Android/Models/DialogHandling/AlertDialogBuilderBase.cs index fc77a7f..893bde7 100644 --- a/Android/Models/DialogHandling/AlertDialogBuilderBase.cs +++ b/Android/Models/DialogHandling/AlertDialogBuilderBase.cs @@ -1,6 +1,7 @@ using Android.Content; using Android.Views; using Java.Lang; +using PlatformBindings.Common; using PlatformBindings.Enums; using System.Threading.Tasks; @@ -55,7 +56,10 @@ public void SetSecondaryButton(string text) public async Task ShowAsync() { - Show(); + PlatformBindingHelpers.OnUIThread(() => + { + Show(); + }); return await Waiter.Task; } diff --git a/Android/Models/Encryption/DefaultKeyGenerator.cs b/Android/Models/Encryption/DefaultKeyGenerator.cs new file mode 100644 index 0000000..5dc4681 --- /dev/null +++ b/Android/Models/Encryption/DefaultKeyGenerator.cs @@ -0,0 +1,22 @@ +using Android.App; +using PlatformBindings.Common; +using System.Security; + +namespace PlatformBindings.Models.Encryption +{ + public class DefaultKeyGenerator : IKeyGenerator + { + public SecureString GetSecureKey() + { + var raw = Application.Context.PackageName + Application.Context.ApplicationInfo.Uid.ToString(); + var data = PlatformBindingHelpers.ConvertToBase64(raw); + + var secure = new SecureString(); + foreach (var c in data) + { + secure.AppendChar(c); + } + return secure; + } + } +} \ No newline at end of file diff --git a/Android/Models/Encryption/IKeyGenerator.cs b/Android/Models/Encryption/IKeyGenerator.cs new file mode 100644 index 0000000..f38c9c8 --- /dev/null +++ b/Android/Models/Encryption/IKeyGenerator.cs @@ -0,0 +1,12 @@ +using System.Security; + +namespace PlatformBindings.Models.Encryption +{ + /// + /// Generates a Key to Encrypt/Decrypt the Credential Data, this key must be the same during Encryption/Decryption otherwise you will not be able to read any of the credentials. + /// + public interface IKeyGenerator + { + SecureString GetSecureKey(); + } +} \ No newline at end of file diff --git a/Android/Models/FileSystem/AndroidSAFFileContainer.cs b/Android/Models/FileSystem/AndroidSAFFileContainer.cs index fe01676..686d6ea 100644 --- a/Android/Models/FileSystem/AndroidSAFFileContainer.cs +++ b/Android/Models/FileSystem/AndroidSAFFileContainer.cs @@ -4,10 +4,11 @@ using Android.Content; using PlatformBindings.Common; using Android.Support.V4.Provider; +using Android.Net; namespace PlatformBindings.Models.FileSystem { - public class AndroidSAFFileContainer : FileContainer + public class AndroidSAFFileContainer : FileContainer, IAndroidSAFContainer { public AndroidSAFFileContainer(Android.Net.Uri Uri) { @@ -58,8 +59,10 @@ public override Task DeleteAsync() public override bool CanWrite => File.CanWrite(); public override string Name => File.Name; - public override string Path => File.Uri.SchemeSpecificPart; + public override string Path => Uri.ToString(); public DocumentFile File { get; } private ContentResolver Resolver => AndroidHelpers.GetCurrentActivity().ContentResolver; + + public Android.Net.Uri Uri => File.Uri; } } \ No newline at end of file diff --git a/Android/Models/FileSystem/AndroidSAFFolderContainer.cs b/Android/Models/FileSystem/AndroidSAFFolderContainer.cs index 5b1fc18..419759d 100644 --- a/Android/Models/FileSystem/AndroidSAFFolderContainer.cs +++ b/Android/Models/FileSystem/AndroidSAFFolderContainer.cs @@ -5,10 +5,11 @@ using Java.Net; using Android.Support.V4.Provider; using System.Linq; +using Android.Net; namespace PlatformBindings.Models.FileSystem { - public class AndroidSAFFolderContainer : FolderContainer + public class AndroidSAFFolderContainer : FolderContainer, IAndroidSAFContainer { public AndroidSAFFolderContainer(Android.Net.Uri Uri) { @@ -135,7 +136,9 @@ public override Task FileExists(string FileName) public override bool CanWrite => Folder.CanWrite(); public override string Name => Folder.Name; - public override string Path => Folder.Uri.SchemeSpecificPart; + public override string Path => Uri.ToString(); public DocumentFile Folder { get; } + + public Android.Net.Uri Uri => Folder.Uri; } } \ No newline at end of file diff --git a/Android/Models/FileSystem/IAndroidSAFContainer.cs b/Android/Models/FileSystem/IAndroidSAFContainer.cs new file mode 100644 index 0000000..09b5f88 --- /dev/null +++ b/Android/Models/FileSystem/IAndroidSAFContainer.cs @@ -0,0 +1,7 @@ +namespace PlatformBindings.Models.FileSystem +{ + public interface IAndroidSAFContainer + { + Android.Net.Uri Uri { get; } + } +} \ No newline at end of file diff --git a/Android/Models/Settings/AndroidSettingsContainer.cs b/Android/Models/Settings/AndroidSettingsContainer.cs index 865e507..0f1920f 100644 --- a/Android/Models/Settings/AndroidSettingsContainer.cs +++ b/Android/Models/Settings/AndroidSettingsContainer.cs @@ -15,18 +15,22 @@ public AndroidSettingsContainer(string Name, AndroidSettingsContainer Parent) { this.Name = Name; this.Parent = Parent; - Prefs = Application.Context.GetSharedPreferences(GetStoreName(), FileCreationMode.Private); - StoreName = GetStoreName(); - } - - private string GetStoreName() - { if (Parent != null) { - return $"{((AndroidSettingsContainer)Parent).GetStoreName()}.{Name}"; + StoreName = $"{Parent.StoreName}.{Name}"; + } + else + { + StoreName = Name; + this.Name = Name == "Settings" ? "Local" : Name; + } + + Prefs = Application.Context.GetSharedPreferences(StoreName, FileCreationMode.Private); + using (var editor = Prefs.Edit()) + { + editor.Commit(); } - else return Name; } public void Clear() @@ -41,7 +45,7 @@ public bool ContainsKey(string Key) return Prefs.Contains(Key); } - public ISettingsContainer CreateContainer(string ContainerName) + public ISettingsContainer GetContainer(string ContainerName) { return new AndroidSettingsContainer(ContainerName, this); } @@ -55,10 +59,12 @@ public IReadOnlyList GetContainers() { foreach (var pref in prefsdir.List()) { - string name = pref.Substring(0, pref.Length - 4); - if (name.StartsWith(GetStoreName())) + string name = System.IO.Path.GetFileNameWithoutExtension(pref); + + string path = StoreName + "."; + if (name != StoreName && name.StartsWith(path)) { - var subName = name.Replace(GetStoreName() + ".", ""); + var subName = name.Replace(path, ""); if (!subName.Contains('.')) { containers.Add(new AndroidSettingsContainer(subName, this)); @@ -73,33 +79,36 @@ public T GetValue(string Key) { var type = PlatformBindingHelpers.DetermineGeneric(); object result = null; - switch (type) + try { - case ObjectType.Int: - result = Prefs.GetInt(Key, 0); - break; + switch (type) + { + case ObjectType.Int: + result = Prefs.GetInt(Key, 0); + break; - case ObjectType.Long: - result = Prefs.GetLong(Key, 0); - break; + case ObjectType.Long: + result = Prefs.GetLong(Key, 0); + break; - case ObjectType.Bool: - result = Prefs.GetBoolean(Key, false); - break; + case ObjectType.Bool: + result = Prefs.GetBoolean(Key, false); + break; - case ObjectType.Float: - result = Prefs.GetFloat(Key, 0); - break; + case ObjectType.Float: + result = Prefs.GetFloat(Key, 0); + break; - default: - result = Prefs.GetString(Key, null); - if (type == ObjectType.ComplexObject) - { - result = JsonConvert.DeserializeObject(result as string); - } - break; + default: + result = Prefs.GetString(Key, null); + if (type == ObjectType.ComplexObject) + { + result = JsonConvert.DeserializeObject(result as string); + } + break; + } } - + catch { result = default(T); } return (T)result; } @@ -108,17 +117,19 @@ public Dictionary GetValues() return Prefs.All.ToDictionary(item => item.Key, item => item.Value); } + internal IReadOnlyList GetValueHeaders() + { + return Prefs.All.Keys.ToList(); + } + public void Remove() { - var prefsdir = SharedPrefs; - var files = SharedPrefs.ListFiles(); - var file = files.FirstOrDefault(item => StoreName == item.Name.Substring(0, item.Name.Length - 4)); - file.Delete(); + Application.Context.DeleteSharedPreferences(StoreName); } public void RemoveContainer(string ContainerName) { - var container = CreateContainer(ContainerName); + var container = GetContainer(ContainerName); container.Remove(); } @@ -157,7 +168,7 @@ public void SetValue(string Key, T Value) break; } - editor.Apply(); + editor.Commit(); } public string Name { get; } @@ -168,7 +179,7 @@ public void SetValue(string Key, T Value) private static File SharedPrefs { - get { return new File(Android.OS.Environment.DataDirectory, "shared_prefs"); } + get { return new File(Application.Context.DataDir, "shared_prefs"); } } } } \ No newline at end of file diff --git a/Android/Services/AndroidCredentialManager.cs b/Android/Services/AndroidCredentialManager.cs new file mode 100644 index 0000000..18d3990 --- /dev/null +++ b/Android/Services/AndroidCredentialManager.cs @@ -0,0 +1,57 @@ +using System.Collections.Generic; +using System.Linq; +using PlatformBindings.Models; +using Android.App; +using PlatformBindings.Models.Settings; + +namespace PlatformBindings.Services +{ + public class AndroidCredentialManager : ICredentialManager + { + public AndroidCredentialManager() + { + var package = Application.Context.PackageName.Split().Last(); + Container = new AndroidSettingsContainer("_SPECIAL", null); + } + + public IReadOnlyList FetchByResource(string Resource) + { + return AllCredentials.Where(item => item.ResourceName == Resource).ToList(); + } + + public CredentialContainer Store(CredentialContainer Credential) + { + if (Credential is AndroidCredentialContainer) return Credential; + else + { + return new AndroidCredentialContainer(Container, Credential); + } + } + + public CredentialContainer Retrieve(string Resource, string Username) + { + return FetchByResource(Resource).FirstOrDefault(item => item.Username == Username); + } + + public void Remove(CredentialContainer Credential) + { + var andrcred = Credential as AndroidCredentialContainer ?? Retrieve(Credential.ResourceName, Credential.Username) as AndroidCredentialContainer; + if (andrcred != null) andrcred.Container.RemoveKey(andrcred.EncryptedHeader); + } + + public void Clear() + { + Container.Clear(); + } + + public IReadOnlyList AllCredentials + { + get + { + return Container.GetValueHeaders().Select(item => new AndroidCredentialContainer(Container, item)).ToList(); + } + } + + private AndroidSettingsContainer Container { get; } + } +} \ No newline at end of file diff --git a/Android/Services/AndroidFileSystemPickers.cs b/Android/Services/AndroidFileSystemPickers.cs new file mode 100644 index 0000000..5c1e354 --- /dev/null +++ b/Android/Services/AndroidFileSystemPickers.cs @@ -0,0 +1,99 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using PlatformBindings.Models.FileSystem; +using Android.App; +using Android.Content; +using PlatformBindings.Common; +using PlatformBindings.Models; + +namespace PlatformBindings.Services +{ + public class AndroidFileSystemPickers : FileSystemPickers + { + public override bool SupportsPickFile => true; + public override bool SupportsPickFolder => true; + + public override async Task PickFile(FilePickerProperties Properties) + { + var result = await CreateFilePicker(Properties, false); + if (result != null && result.ResultCode == Result.Ok && result.Data != null) + { + return new AndroidSAFFileContainer(result.Data.Data); + } + else return null; + } + + public override async Task> PickFiles(FilePickerProperties Properties) + { + List Files = new List(); + + var result = await CreateFilePicker(Properties, true); + if (result != null && result.ResultCode == Result.Ok && result.Data != null) + { + var clipData = result.Data.ClipData; + if (clipData != null) + { + for (int i = 0; i < clipData.ItemCount; i++) + { + var item = clipData.GetItemAt(i); + Files.Add(new AndroidSAFFileContainer(item.Uri)); + } + } + else + { + Files.Add(new AndroidSAFFileContainer(result.Data.Data)); + } + return Files; + } + return null; + } + + public override async Task PickFolder(FolderPickerProperties Properties) + { + Intent intent = new Intent(Intent.ActionOpenDocumentTree); + var activity = AndroidHelpers.GetCurrentActivity(); + + var result = await activity.StartActivityForResultAsync(intent); + if (result != null && result.ResultCode == Result.Ok && result.Data != null) + { + return new AndroidSAFFolderContainer(result.Data.Data); + } + else return null; + } + + private async Task CreateFilePicker(FilePickerProperties Properties, bool Multiple) + { + Intent intent = new Intent(Intent.ActionOpenDocument); + intent.AddCategory(Intent.CategoryOpenable); + if (Multiple) intent.PutExtra(Intent.ExtraAllowMultiple, Multiple); + bool HasNoTypes = true; + + if (Properties != null) + { + if (Properties.FileTypes.Any()) + { + HasNoTypes = false; + + intent.SetType(Properties.FileTypes.First().MimeType); + + if (Properties.FileTypes.Count > 1) + { + string Altmimes = ""; + var others = Properties.FileTypes.Skip(1); + int count = others.Count(); + for (int i = 0; i < count; i++) + { + Altmimes += others.ElementAt(i).MimeType; + if (i + 1 < count) Altmimes += "|"; + } + intent.PutExtra(Intent.ExtraMimeTypes, Altmimes); + } + } + } + + if (HasNoTypes) intent.SetType("*/*"); + return await AndroidHelpers.GetCurrentActivity().StartActivityForResultAsync(intent); + } + } +} \ No newline at end of file diff --git a/Android/Services/AndroidFutureAccessManager.cs b/Android/Services/AndroidFutureAccessManager.cs new file mode 100644 index 0000000..c54fa86 --- /dev/null +++ b/Android/Services/AndroidFutureAccessManager.cs @@ -0,0 +1,81 @@ +using Android.Content; +using Newtonsoft.Json; +using PlatformBindings.Common; +using PlatformBindings.Models.FileSystem; +using System.Linq; +using System.Threading.Tasks; + +namespace PlatformBindings.Services +{ + public class AndroidFutureAccessManager : IFutureAccessManager + { + public string GetFutureAccessPermission(FileSystemContainer Item) + { + var isFolder = Item is AndroidSAFFolderContainer; + var uri = Item is IAndroidSAFContainer saf ? saf.Uri : null; + if (uri != null) + { + var activity = AndroidHelpers.GetCurrentActivity(); + var flags = ActivityFlags.GrantReadUriPermission | ActivityFlags.GrantWriteUriPermission; + activity.GrantUriPermission(activity.PackageName, uri, flags); + activity.ContentResolver.TakePersistableUriPermission(uri, flags); + + var result = JsonConvert.SerializeObject(new SAFToken + { + URI = uri.SchemeSpecificPart, + IsFolder = isFolder + }); + return PlatformBindingHelpers.ConvertToBase64(result); + } + else return null; + } + + public Task RedeemFutureAccessTokenAsync(string Token) + { + FileSystemContainer item = null; + + var result = GetToken(Token); + + var activity = AndroidHelpers.GetCurrentActivity(); + if (activity.ContentResolver.PersistedUriPermissions.Any(perm => perm.Uri == result.uri)) + { + if (result.props.IsFolder) item = new AndroidSAFFolderContainer(result.uri); + else item = new AndroidSAFFileContainer(result.uri); + } + return Task.FromResult(item); + } + + public void RemoveFutureAccessPermission(string Token) + { + var resolver = AndroidHelpers.GetCurrentActivity().ContentResolver; + var permission = resolver.PersistedUriPermissions.FirstOrDefault(item => item.Uri.SchemeSpecificPart == Token); + if (permission != null) + { + resolver.PersistedUriPermissions.Remove(permission); + } + } + + public bool TokenValid(string Token) + { + var result = GetToken(Token); + var resolver = AndroidHelpers.GetCurrentActivity().ContentResolver; + + return resolver.PersistedUriPermissions.FirstOrDefault(item => item.Uri == result.uri) != null; + } + + private (SAFToken props, Android.Net.Uri uri) GetToken(string EncodedString) + { + var result = JsonConvert.DeserializeObject(PlatformBindingHelpers.ConvertFromBase64(EncodedString)); + var uri = Android.Net.Uri.Parse(result.URI); + return (result, uri); + } + + public bool FutureAccessFull => false; + + private class SAFToken + { + public string URI { get; set; } + public bool IsFolder { get; set; } + } + } +} \ No newline at end of file diff --git a/Android/Services/AndroidIOBindings.cs b/Android/Services/AndroidIOBindings.cs index 60a7d80..0ff2cdf 100644 --- a/Android/Services/AndroidIOBindings.cs +++ b/Android/Services/AndroidIOBindings.cs @@ -1,53 +1,27 @@ using Android.App; using Android.Content; using System; -using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; using PlatformBindings.Common; using PlatformBindings.Enums; -using PlatformBindings.Models; using PlatformBindings.Models.FileSystem; using PlatformBindings.Models.Settings; using Android.OS; using PlatformBindings.Exceptions; +using PlatformBindings.Models.FileSystem.Options; namespace PlatformBindings.Services { public class AndroidIOBindings : IOBindings { - public override async Task CreateFile(FilePath Path) - { - var folder = await GetFolder(Path); - return await folder.CreateFileAsync(Path.FileName); - } - - public override Task GetFile(string Path) - { - return Task.FromResult((FileContainer)new CoreFileContainer(Path)); - } - - public override async Task GetFile(FilePath Path) - { - var folder = await GetFolder(Path); - return await folder.GetFileAsync(Path.FileName); - } - - public override Task GetFolder(string Path) - { - return Task.FromResult((FolderContainer)new CoreFolderContainer(Path)); - } + public override bool SupportsRoaming => false; + public override bool SupportsOpenFolderForDisplay => false; + public override bool SupportsOpenFileForDisplay => true; - public override async Task GetFolder(FolderPath Path) - { - FolderContainer Folder = GetBaseFolder(Path.Root); + public override IFutureAccessManager FutureAccess => new AndroidFutureAccessManager(); + public override FileSystemPickers Pickers => new AndroidFileSystemPickers(); - foreach (var piece in PlatformBindingHelpers.GetPathPieces(Path.Path)) - { - Folder = await Folder.GetFolderAsync(piece); - } - return Folder; - } + public override ISettingsContainer LocalSettings { get; } = new AndroidSettingsContainer("Settings", null); public override FolderContainer GetBaseFolder(PathRoot Root) { @@ -73,13 +47,7 @@ public override FolderContainer GetBaseFolder(PathRoot Root) return Folder; } - public override ISettingsContainer GetLocalSettingsContainer() - { - var name = Application.Context.PackageName.Split('.').First(); - return new AndroidSettingsContainer(name, null); - } - - public override async Task OpenFile(FileContainer File) + public override async Task OpenFileForDisplay(FileContainer File) { var activity = AndroidHelpers.GetCurrentActivity(); ParcelFileDescriptor fd = null; @@ -116,135 +84,10 @@ public override async Task OpenFile(FileContainer File) } } - public override Task OpenFolder(FolderContainer Folder, FolderOpenOptions Options) - { - throw new NotImplementedException(); - } - - private async Task CreateFilePicker(FilePickerProperties Properties, bool Multiple) - { - Intent intent = new Intent(Intent.ActionOpenDocument); - intent.AddCategory(Intent.CategoryOpenable); - if (Multiple) intent.PutExtra(Intent.ExtraAllowMultiple, Multiple); - bool HasNoTypes = true; - - if (Properties != null) - { - if (Properties.FileTypes.Any()) - { - HasNoTypes = false; - - intent.SetType(Properties.FileTypes.First().MimeType); - - if (Properties.FileTypes.Count > 1) - { - string Altmimes = ""; - var others = Properties.FileTypes.Skip(1); - int count = others.Count(); - for (int i = 0; i < count; i++) - { - Altmimes += others.ElementAt(i).MimeType; - if (i + 1 < count) Altmimes += "|"; - } - intent.PutExtra(Intent.ExtraMimeTypes, Altmimes); - } - } - } - - if (HasNoTypes) intent.SetType("*/*"); - return await AndroidHelpers.GetCurrentActivity().StartActivityForResultAsync(intent); - } - - public override async Task PickFile(FilePickerProperties Properties) - { - var result = await CreateFilePicker(Properties, false); - if (result != null && result.ResultCode == Result.Ok && result.Data != null) - { - return new AndroidSAFFileContainer(result.Data.Data); - } - else return null; - } - - public override async Task> PickFiles(FilePickerProperties Properties) - { - List Files = new List(); - - var result = await CreateFilePicker(Properties, true); - if (result != null && result.ResultCode == Result.Ok && result.Data != null) - { - var clipData = result.Data.ClipData; - if (clipData != null) - { - for (int i = 0; i < clipData.ItemCount; i++) - { - var item = clipData.GetItemAt(i); - Files.Add(new AndroidSAFFileContainer(item.Uri)); - } - } - else - { - Files.Add(new AndroidSAFFileContainer(result.Data.Data)); - } - return Files; - } - return null; - } - - public override async Task PickFolder(FolderPickerProperties Properties) - { - Intent intent = new Intent(Intent.ActionOpenDocumentTree); - var activity = AndroidHelpers.GetCurrentActivity(); - - var result = await activity.StartActivityForResultAsync(intent); - if (result != null && result.ResultCode == Result.Ok && result.Data != null) - { - var uri = result.Data.Data; - var flags = ActivityFlags.GrantReadUriPermission | ActivityFlags.GrantWriteUriPermission; - activity.GrantUriPermission(activity.PackageName, uri, flags); - activity.ContentResolver.TakePersistableUriPermission(uri, result.Data.Flags & flags); - return new AndroidSAFFolderContainer(uri); - } - else return null; - } - //NOT SUPPORTED - - public override ISettingsContainer GetRoamingSettingsContainer() - { - return GetLocalSettingsContainer(); - } - - public override string GetFutureAccessToken(FileSystemContainer Item) - { - var uri = Item is AndroidSAFFolderContainer folder ? folder.Folder.Uri : Item is AndroidSAFFileContainer file ? file.File.Uri : null; - if (uri != null) - { - AndroidHelpers.GetCurrentActivity().ContentResolver.TakePersistableUriPermission(uri, ActivityFlags.GrantReadUriPermission | ActivityFlags.GrantWriteUriPermission); - return uri.SchemeSpecificPart; - } - else return null; - } - - public override void RemoveFutureAccessToken(string Token) + public override Task OpenFolderForDisplay(FolderContainer Folder, FolderOpenOptions Options) { - var resolver = AndroidHelpers.GetCurrentActivity().ContentResolver; - var permission = resolver.PersistedUriPermissions.FirstOrDefault(item => item.Uri.SchemeSpecificPart == Token); - if (permission != null) - { - resolver.PersistedUriPermissions.Remove(permission); - } + throw new NotSupportedException(); } - - public override bool RequiresFutureAccessToken => true; - - public override bool SupportsRoaming => false; - - public override bool SupportsOpenFolder => false; - - public override bool SupportsOpenFile => true; - - public override bool SupportsPickFile => true; - - public override bool SupportsPickFolder => true; } } \ No newline at end of file diff --git a/Android/Services/AndroidUIBindings.cs b/Android/Services/AndroidUIBindings.cs index 130abfd..eefc685 100644 --- a/Android/Services/AndroidUIBindings.cs +++ b/Android/Services/AndroidUIBindings.cs @@ -9,12 +9,13 @@ using Android.Graphics; using PlatformBindings.Models.DialogHandling; using PlatformBindings.Models; +using Android.Widget; namespace PlatformBindings.Services { public class AndroidUIBindings : UIBindings { - public AndroidUIBindings() + public AndroidUIBindings() : base(Platform.Android) { DefaultUIBinding = new AndroidUIBindingInfo(); } @@ -74,5 +75,27 @@ public override void ShowMenu(Menu Menu, IMenuBinding Binding) public override void SetWindowText(string Text) { } + + public override async Task RequestTextFromUserAsync(string Title, string Message, string OKButtonText, string CancelButtonText, IUIBindingInfo UIBinding) + { + var activity = AndroidHelpers.GetCurrentActivity(UIBinding); + + var builder = AlertDialogBuilderBase.Pick(activity); + var entry = new EditText(activity); + + builder.SetTitle(CreateFormattedString(Title)); + builder.SetMessage(CreateFormattedString(Message)); + builder.SetPrimaryButton(OKButtonText); + builder.SetView(entry); + + if (!string.IsNullOrWhiteSpace(CancelButtonText)) + { + builder.SetSecondaryButton(CancelButtonText); + } + + var result = await builder.ShowAsync(); + if (result == DialogResult.Primary) return entry.Text; + return null; + } } } \ No newline at end of file diff --git a/Console/Services/ConsoleUIBindings.cs b/Console/Services/ConsoleUIBindings.cs index d3bbd9c..eb8d925 100644 --- a/Console/Services/ConsoleUIBindings.cs +++ b/Console/Services/ConsoleUIBindings.cs @@ -9,6 +9,10 @@ namespace PlatformBindings.Services { public abstract class ConsoleUIBindings : UIBindings { + public ConsoleUIBindings() : base(Platform.Console) + { + } + public override InteractionManager InteractionManager => null; public override IUIBindingInfo DefaultUIBinding => new ConsoleUIBindingInfo(); @@ -39,6 +43,11 @@ public override Task PromptUserAsync(string Title, string Message, return Task.FromResult(result); } + public override Task RequestTextFromUserAsync(string Title, string Message, string OKButtonText, string CancelButtonText, IUIBindingInfo UIBinding) + { + throw new NotImplementedException(); + } + public override void SetWindowText(string Text = "") { Console.Title = Text; diff --git a/Core/AppServices.cs b/Core/AppServices.cs index f0896c6..31106da 100644 --- a/Core/AppServices.cs +++ b/Core/AppServices.cs @@ -1,4 +1,5 @@ -using PlatformBindings.Services; +using PlatformBindings.Enums; +using PlatformBindings.Services; using System; namespace PlatformBindings @@ -9,9 +10,12 @@ namespace PlatformBindings /// public abstract class AppServices { - public AppServices(bool HasUI) + public AppServices(bool HasUI, Platform Platform) { + Current = this; this.HasUI = HasUI; + ServicePlatform = Platform; + NetworkUtilities = new NetworkUtilities(); } /// @@ -49,6 +53,16 @@ public AppServices(bool HasUI) /// /// Functions for Testing Connection to the Internet and other Sources. /// - public static NetworkUtilities NetworkUtilities { get; protected set; } = new NetworkUtilities(); + public static NetworkUtilities NetworkUtilities { get; protected set; } + + /// + /// The Current Platform the Framework is running on. + /// + public static Platform ServicePlatform { get; private set; } + + /// + /// The Current AppService, this is required for accessing Platform Specific Methods. + /// + public static AppServices Current { get; private set; } } } \ No newline at end of file diff --git a/Core/Common/PlatformBindingHelpers.cs b/Core/Common/PlatformBindingHelpers.cs index 68c83d0..480d38e 100644 --- a/Core/Common/PlatformBindingHelpers.cs +++ b/Core/Common/PlatformBindingHelpers.cs @@ -1,12 +1,12 @@ using System; using System.Threading.Tasks; using PlatformBindings.Enums; -using PlatformBindings.Models.Settings; using System.IO; using System.Collections.Generic; using System.Linq; using PlatformBindings.Models.FileSystem; using PlatformBindings.Models; +using System.Text; namespace PlatformBindings.Common { @@ -68,6 +68,28 @@ public static byte[] GetByteArrayFromStream(this Stream Stream) } } + /// + /// Converts a UTF8 String to a Base 64 Encoded String + /// + /// UTF8 String + /// Encoded String + public static string ConvertToBase64(string Original) + { + byte[] bytes = Encoding.UTF8.GetBytes(Original); + return Convert.ToBase64String(bytes); + } + + /// + /// Converts a Base 64 Encoded String to a UTF8 String + /// + /// Encoded String + /// UTF8 String + public static string ConvertFromBase64(string Encoded) + { + var bytes = Convert.FromBase64String(Encoded); + return Encoding.UTF8.GetString(bytes); + } + /// /// Performs an Action on the UI Thread /// @@ -108,16 +130,6 @@ public static async Task OnUIThreadAsync(IUIBindingInfo UIBinding, Action action await UIBinding.ExecuteAsync(action); } - /// - /// Gets the Supported Settings Container, If GetLocal is false, it will attempt to get the Roaming Container if Supported, otherwise it will return the Local Settings Cluster. - /// - /// Get the Local Settings Container? - /// Local/Roaming Settings Container - public static ISettingsContainer GetSettingsContainer(bool GetLocal) - { - return GetLocal || !AppServices.IO.SupportsRoaming ? AppServices.IO.GetLocalSettingsContainer() : AppServices.IO.GetRoamingSettingsContainer(); - } - /// /// Determines the Type of a Generic, useful for performing actions dependent on Type. /// diff --git a/Core/Enums/Platform.cs b/Core/Enums/Platform.cs new file mode 100644 index 0000000..1077b31 --- /dev/null +++ b/Core/Enums/Platform.cs @@ -0,0 +1,11 @@ +namespace PlatformBindings.Enums +{ + public enum Platform + { + UWP, + Android, + Console, + NETCore, + XamarinForms + } +} \ No newline at end of file diff --git a/Core/Models/CredentialContainer.cs b/Core/Models/CredentialContainer.cs index 8e2c889..3ca94c2 100644 --- a/Core/Models/CredentialContainer.cs +++ b/Core/Models/CredentialContainer.cs @@ -6,15 +6,15 @@ public CredentialContainer() { } - public CredentialContainer(string Resource, string Username, string Password) + public CredentialContainer(string ResourceName, string Username, string Password) { - this.ResourceName = Resource; + this.ResourceName = ResourceName; this.Username = Username; this.Password = Password; } - public string ResourceName { get; set; } - public string Username { get; set; } - public string Password { get; set; } + public virtual string ResourceName { get; } + public virtual string Username { get; } + public virtual string Password { get; set; } } } \ No newline at end of file diff --git a/Core/Models/FileSystem/Containers/FolderContainer.cs b/Core/Models/FileSystem/Containers/FolderContainer.cs index 6b7a41c..9c59352 100644 --- a/Core/Models/FileSystem/Containers/FolderContainer.cs +++ b/Core/Models/FileSystem/Containers/FolderContainer.cs @@ -1,4 +1,6 @@ using PlatformBindings.Enums; +using PlatformBindings.Models.FileSystem.Options; +using PlatformBindings.Services; using System.Collections.Generic; using System.IO; using System.Linq; @@ -46,23 +48,23 @@ public virtual async Task GetFileAsync(string FileName) public abstract Task> GetFilesAsync(); /// - /// Creates a Subfolder with the Specified Name. Will fail if Folder already exists. + /// Creates a Subfolder with the Specified Name. Will use if Folder already exists. /// /// Name of the new Folder /// The new Subfolder public Task CreateFolderAsync(string FolderName) { - return CreateFolderAsync(FolderName, CreationCollisionOption.FailIfExists); + return CreateFolderAsync(FolderName, IOBindings.DefaultFolderCreationCollision); } /// - /// Creates a File with the Specified Name. Will fail if File already exists. + /// Creates a File with the Specified Name. Will use if File already exists. /// /// Name of the new File /// The new File public Task CreateFileAsync(string FileName) { - return CreateFileAsync(FileName, CreationCollisionOption.FailIfExists); + return CreateFileAsync(FileName, IOBindings.DefaultFileCreationCollision); } /// diff --git a/Core/Models/FileSystem/CorePathResolver.cs b/Core/Models/FileSystem/CorePathResolver.cs new file mode 100644 index 0000000..9c4accf --- /dev/null +++ b/Core/Models/FileSystem/CorePathResolver.cs @@ -0,0 +1,24 @@ +using System.IO; +using System.Threading.Tasks; + +namespace PlatformBindings.Models.FileSystem +{ + public class CorePathResolver : IPathResolver + { + public Task TryResolve(string Path) + { + return Task.Run(() => + { + if (File.Exists(Path)) + { + return (FileSystemContainer)new CoreFileContainer(Path); + } + else if (Directory.Exists(Path)) + { + return new CoreFolderContainer(Path); + } + return null; + }); + } + } +} \ No newline at end of file diff --git a/Core/Models/FileSystem/IPathResolver.cs b/Core/Models/FileSystem/IPathResolver.cs new file mode 100644 index 0000000..f5ab879 --- /dev/null +++ b/Core/Models/FileSystem/IPathResolver.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace PlatformBindings.Models.FileSystem +{ + public interface IPathResolver + { + Task TryResolve(string Path); + } +} \ No newline at end of file diff --git a/Core/Models/FileSystem/FileTypeFilter.cs b/Core/Models/FileSystem/Options/FileTypeFilter.cs similarity index 71% rename from Core/Models/FileSystem/FileTypeFilter.cs rename to Core/Models/FileSystem/Options/FileTypeFilter.cs index 11f7ca0..dffb86f 100644 --- a/Core/Models/FileSystem/FileTypeFilter.cs +++ b/Core/Models/FileSystem/Options/FileTypeFilter.cs @@ -1,4 +1,4 @@ -namespace PlatformBindings.Models.FileSystem +namespace PlatformBindings.Models.FileSystem.Options { public class FileTypeFilter { diff --git a/Core/Models/FileSystem/FolderOpenOptions.cs b/Core/Models/FileSystem/Options/FolderOpenOptions.cs similarity index 78% rename from Core/Models/FileSystem/FolderOpenOptions.cs rename to Core/Models/FileSystem/Options/FolderOpenOptions.cs index 42b1733..04500b1 100644 --- a/Core/Models/FileSystem/FolderOpenOptions.cs +++ b/Core/Models/FileSystem/Options/FolderOpenOptions.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; -namespace PlatformBindings.Models.FileSystem +namespace PlatformBindings.Models.FileSystem.Options { public class FolderOpenOptions { diff --git a/Core/Models/FileSystem/QueryOptions.cs b/Core/Models/FileSystem/Options/QueryOptions.cs similarity index 82% rename from Core/Models/FileSystem/QueryOptions.cs rename to Core/Models/FileSystem/Options/QueryOptions.cs index 1bbd07b..0688cd1 100644 --- a/Core/Models/FileSystem/QueryOptions.cs +++ b/Core/Models/FileSystem/Options/QueryOptions.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using PlatformBindings.Enums; -namespace PlatformBindings.Models.FileSystem +namespace PlatformBindings.Models.FileSystem.Options { public class QueryOptions { diff --git a/Core/Models/FileSystem/Pickers/PickerProperties.cs b/Core/Models/FileSystem/Pickers/PickerProperties.cs index d876258..fd3513d 100644 --- a/Core/Models/FileSystem/Pickers/PickerProperties.cs +++ b/Core/Models/FileSystem/Pickers/PickerProperties.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using PlatformBindings.Enums; +using PlatformBindings.Models.FileSystem.Options; namespace PlatformBindings.Models.FileSystem { diff --git a/Core/Models/Settings/AppSetting.cs b/Core/Models/Settings/AppSetting.cs index 9eabb27..9582685 100644 --- a/Core/Models/Settings/AppSetting.cs +++ b/Core/Models/Settings/AppSetting.cs @@ -1,19 +1,28 @@ using System.Runtime.CompilerServices; using PlatformBindings.Models.Settings.Properties; -using PlatformBindings.Common; namespace PlatformBindings.Models.Settings { public class AppSetting : Property { - public AppSetting(bool IsLocal = false, [CallerMemberName]string SettingName = "", T Default = default(T)) : base(SettingName, Default) + public AppSetting([CallerMemberName] string SettingName = "") : this(false, SettingName) { - this.IsLocal = IsLocal; + } + + public AppSetting(bool Roam, [CallerMemberName] string SettingName = "") : this(default(T), Roam, SettingName) + { + } - //Will Throw if Settings Containers unwired. - Attach(PlatformBindingHelpers.GetSettingsContainer(IsLocal)); + public AppSetting(T Default, [CallerMemberName] string SettingName = "") : this(Default, false, SettingName) + { + } + + public AppSetting(T Default, bool Roam, [CallerMemberName] string SettingName = "") : base(SettingName, Default) + { + this.Roam = Roam; + Attach(Roam ? AppServices.IO.RoamingSettings : AppServices.IO.LocalSettings); } - public bool IsLocal { get; private set; } + public bool Roam { get; private set; } } } \ No newline at end of file diff --git a/Core/Models/Settings/AppSettingList.cs b/Core/Models/Settings/AppSettingList.cs index 3617a60..6df6447 100644 --- a/Core/Models/Settings/AppSettingList.cs +++ b/Core/Models/Settings/AppSettingList.cs @@ -1,19 +1,20 @@ using System.Runtime.CompilerServices; -using PlatformBindings.Common; using PlatformBindings.Models.Settings.Properties; namespace PlatformBindings.Models.Settings { public class AppSettingList : PropertyList where T : IProperty { - public AppSettingList(bool IsLocal = false, [CallerMemberName]string PropertyName = "") : base(PropertyName) + public AppSettingList([CallerMemberName] string SettingName = "") : this(false, SettingName) { - this.IsLocal = IsLocal; + } - //Will Throw if Settings Containers unwired. - Attach(PlatformBindingHelpers.GetSettingsContainer(IsLocal)); + public AppSettingList(bool Roam, [CallerMemberName] string SettingName = "") : base(SettingName) + { + this.Roam = Roam; + Attach(Roam ? AppServices.IO.RoamingSettings : AppServices.IO.LocalSettings); } - public bool IsLocal { get; private set; } + public bool Roam { get; private set; } } } \ No newline at end of file diff --git a/Core/Models/Settings/ISettingsContainer.cs b/Core/Models/Settings/ISettingsContainer.cs index 1c8f49d..8772bee 100644 --- a/Core/Models/Settings/ISettingsContainer.cs +++ b/Core/Models/Settings/ISettingsContainer.cs @@ -4,7 +4,7 @@ namespace PlatformBindings.Models.Settings { public interface ISettingsContainer { - ISettingsContainer CreateContainer(string ContainerName); + ISettingsContainer GetContainer(string ContainerName); void RemoveContainer(string ContainerName); diff --git a/Core/Models/Settings/Properties/PropertyList.cs b/Core/Models/Settings/Properties/PropertyList.cs index 6bfd01b..f72a0d2 100644 --- a/Core/Models/Settings/Properties/PropertyList.cs +++ b/Core/Models/Settings/Properties/PropertyList.cs @@ -18,7 +18,7 @@ public void Attach(ISettingsContainer Parent) this.Parent = Parent; if (Container == null) { - Container = Parent.CreateContainer(PropertyName); + Container = Parent.GetContainer(PropertyName); foreach (var item in Container.GetContainers()) { var newElement = Activator.CreateInstance(); diff --git a/Core/Models/Settings/Properties/PropertySet.cs b/Core/Models/Settings/Properties/PropertySet.cs index 4aeea95..cd43ffb 100644 --- a/Core/Models/Settings/Properties/PropertySet.cs +++ b/Core/Models/Settings/Properties/PropertySet.cs @@ -13,7 +13,7 @@ public virtual void Attach(ISettingsContainer Parent) this.Parent = Parent; if (Container == null) { - Container = Parent.CreateContainer(PropertyName); + Container = Parent.GetContainer(PropertyName); foreach (IProperty item in Properties) { item.Attach(Container); diff --git a/Core/Models/Settings/Properties/SerialPropertyList.cs b/Core/Models/Settings/Properties/SerialPropertyList.cs index a38effb..6882f42 100644 --- a/Core/Models/Settings/Properties/SerialPropertyList.cs +++ b/Core/Models/Settings/Properties/SerialPropertyList.cs @@ -24,7 +24,7 @@ public void Attach(ISettingsContainer Parent) private void LoadList() { Clear(); - var listContainer = Parent.CreateContainer(PropertyName); + var listContainer = Parent.GetContainer(PropertyName); foreach (var item in listContainer.GetValues().OrderBy(item => Convert.ToInt32(item.Key))) { Add(JsonConvert.DeserializeObject((string)item.Value)); @@ -33,7 +33,7 @@ private void LoadList() public void Update() { - var listContainer = Parent.CreateContainer(PropertyName); + var listContainer = Parent.GetContainer(PropertyName); listContainer.Clear(); foreach (var value in this) { diff --git a/Core/Models/Settings/SerialAppSettingList.cs b/Core/Models/Settings/SerialAppSettingList.cs index f251c4a..da89b08 100644 --- a/Core/Models/Settings/SerialAppSettingList.cs +++ b/Core/Models/Settings/SerialAppSettingList.cs @@ -1,19 +1,20 @@ using System.Runtime.CompilerServices; -using PlatformBindings.Common; using PlatformBindings.Models.Settings.Properties; namespace PlatformBindings.Models.Settings { public class SerialAppSettingList : SerialPropertyList { - public SerialAppSettingList(bool IsLocal = false, [CallerMemberName]string PropertyName = "") : base(PropertyName) + public SerialAppSettingList([CallerMemberName] string SettingName = "") : this(false, SettingName) { - this.IsLocal = IsLocal; + } - //Will Throw if Settings Containers unwired. - Attach(PlatformBindingHelpers.GetSettingsContainer(IsLocal)); + public SerialAppSettingList(bool Roam, [CallerMemberName] string SettingName = "") : base(SettingName) + { + this.Roam = Roam; + Attach(Roam ? AppServices.IO.RoamingSettings : AppServices.IO.LocalSettings); } - public bool IsLocal { get; private set; } + public bool Roam { get; private set; } = false; } } \ No newline at end of file diff --git a/Core/Models/Settings/SerialisedAppSetting.cs b/Core/Models/Settings/SerialisedAppSetting.cs index 1f0075a..13bf372 100644 --- a/Core/Models/Settings/SerialisedAppSetting.cs +++ b/Core/Models/Settings/SerialisedAppSetting.cs @@ -1,19 +1,28 @@ using System.Runtime.CompilerServices; -using PlatformBindings.Common; using PlatformBindings.Models.Settings.Properties; namespace PlatformBindings.Models.Settings { public class SerialisedAppSetting : SerialProperty { - public SerialisedAppSetting(bool IsLocal = false, [CallerMemberName] string SettingName = "", T Default = default(T)) : base(SettingName, Default) + public SerialisedAppSetting([CallerMemberName] string SettingName = "") : this(false, SettingName) { - this.IsLocal = IsLocal; + } + + public SerialisedAppSetting(bool Roam, [CallerMemberName] string SettingName = "") : this(default(T), Roam, SettingName) + { + } - //Will Throw if Settings Containers unwired. - Attach(PlatformBindingHelpers.GetSettingsContainer(IsLocal)); + public SerialisedAppSetting(T Default, [CallerMemberName] string SettingName = "") : this(Default, false, SettingName) + { + } + + public SerialisedAppSetting(T Default, bool Roam, [CallerMemberName] string SettingName = "") : base(SettingName, Default) + { + this.Roam = Roam; + Attach(Roam ? AppServices.IO.RoamingSettings : AppServices.IO.LocalSettings); } - public bool IsLocal { get; private set; } + public bool Roam { get; private set; } } } \ No newline at end of file diff --git a/Core/Services/FileSystemPickers.cs b/Core/Services/FileSystemPickers.cs new file mode 100644 index 0000000..3177f8c --- /dev/null +++ b/Core/Services/FileSystemPickers.cs @@ -0,0 +1,70 @@ +using PlatformBindings.Models.FileSystem; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace PlatformBindings.Services +{ + /// + /// Provides ways for Users to Pick Files and Folders. + /// + public abstract class FileSystemPickers + { + /// + /// Used to Determine if the OS Supports Picking Files using a File Picker. + /// + public abstract bool SupportsPickFile { get; } + + /// + /// Used to Determine if the OS Supports Picking Folders using a Folder Picker. + /// + public abstract bool SupportsPickFolder { get; } + + /// + /// Opens a File Picker to Pick Files from the Operating System. See to ensure that this is Supported. + /// + /// A List of User Picked Files, or if Cancelled + public Task> PickFiles() + { + return PickFiles(null); + } + + /// + /// Opens a File Picker to Pick Files from the Operating System. See to ensure that this is Supported. + /// + /// Filters to narrow down the Observable Files. + /// A List of User Picked Files, or if Cancelled + public abstract Task> PickFiles(FilePickerProperties Properties); + + /// + /// Opens a File Picker to Pick a File from the Operating System. See to ensure that this is Supported. + /// + /// A Picked File, or if Cancelled + public Task PickFile() + { + return PickFile(null); + } + + /// + /// Opens a File Picker to Pick a File from the Operating System. See to ensure that this is Supported. + /// + /// Filters to narrow down the Observable Files. + /// A Picked File, or if Cancelled + public abstract Task PickFile(FilePickerProperties Properties); + + /// + /// Opens a Folder Picker to Pick a Folder from the Operating System. See to ensure that this is Supported. + /// + /// A Picked Folder, or if Cancelled + public Task PickFolder() + { + return PickFolder(null); + } + + /// + /// Opens a Folder Picker to Pick a Folder from the Operating System. See to ensure that this is Supported. + /// + /// Filters to narrow down the Observable Files. + /// A Picked Folder, or if Cancelled + public abstract Task PickFolder(FolderPickerProperties Properties); + } +} \ No newline at end of file diff --git a/Core/Services/ICredentialManager.cs b/Core/Services/ICredentialManager.cs index 6a82351..4218a95 100644 --- a/Core/Services/ICredentialManager.cs +++ b/Core/Services/ICredentialManager.cs @@ -9,9 +9,7 @@ public interface ICredentialManager CredentialContainer Retrieve(string Resource, string Username); - void Update(CredentialContainer Credential); - - void Store(CredentialContainer Credential); + CredentialContainer Store(CredentialContainer Credential); void Remove(CredentialContainer Credential); diff --git a/Core/Services/IFutureAccessManager.cs b/Core/Services/IFutureAccessManager.cs new file mode 100644 index 0000000..2dfb840 --- /dev/null +++ b/Core/Services/IFutureAccessManager.cs @@ -0,0 +1,46 @@ +using PlatformBindings.Models.FileSystem; +using System.Threading.Tasks; + +namespace PlatformBindings.Services +{ + /// + /// Future Access Manager allows you to access Files and Folders across App sessions, by using Tokens. + /// + public interface IFutureAccessManager + { + /// + /// Gets Future Access Token for a Specified File or Folder. to ensure that this is Required. + /// This is Required by some Platforms to access User Files and Folders in future Sessions of the App, if the App is closed. You can store this Token in Local Settings to Access this file/folder Again. + /// Might return null if it is an unsupported Type, or if the Future Access Manager is full (Which occurs on UWP). To check if it is full, see . + /// + /// File/Folder to get Access Token for. + /// Future Access Token. + string GetFutureAccessPermission(FileSystemContainer Item); + + /// + /// Returns the + /// + /// + /// + Task RedeemFutureAccessTokenAsync(string Token); + + /// + /// Checks to see if the provided Token is Valid and Redeemable. + /// + /// Token to Validate + /// Is Token Valid + bool TokenValid(string Token); + + /// + /// Removes a file/folder from the Future Access List, using the Future Access Token. + /// + /// Token of File/Folder to Remove + void RemoveFutureAccessPermission(string Token); + + /// + /// Determines if the Future Access Manager is full, this might occur on UWP at 1000 Entries. + /// If it is full, no more Tokens will be given out, unless you Remove some Permissions. + /// + bool FutureAccessFull { get; } + } +} \ No newline at end of file diff --git a/Core/Services/IOBindings.cs b/Core/Services/IOBindings.cs index a669b24..e2928f5 100644 --- a/Core/Services/IOBindings.cs +++ b/Core/Services/IOBindings.cs @@ -3,184 +3,257 @@ using PlatformBindings.Enums; using PlatformBindings.Models.FileSystem; using PlatformBindings.Models.Settings; +using PlatformBindings.Common; +using PlatformBindings.Models.FileSystem.Options; +using System.Linq; namespace PlatformBindings.Services { /// /// Methods for handling IO through the File System, and Settings Clusters /// - public abstract partial class IOBindings + public abstract class IOBindings { + static IOBindings() + { + PathResolvers.Add(new CorePathResolver()); + } + /// - /// Gets a File from a Raw Path, this path must observe the Operating System's Path Structure to work correctly. + /// Adds a Path Resolver to the top of the Stack, this Resolver will evaluate first. /// - /// OS Style Path - /// Specified File - public abstract Task GetFile(string Path); + /// Resolver + public static void AddResolver(IPathResolver Resolver) + { + PathResolvers.Add(Resolver); + } /// - /// Gets a File from the FileSystem, the Path is Resolved and is OS Independent. + /// Adds a Path Resolver to the Bottom of the Stack, this Resolver will evaluate last. /// - /// File Path - /// Specified File - public abstract Task GetFile(FilePath Path); + /// Resolver + public static void AddResolverFirst(IPathResolver Resolver) + { + PathResolvers.Insert(0, Resolver); + } /// - /// Creates a File from the Resolved Path. + /// Removes a Resolver from the evaluation Stack. /// - /// File Path - /// Created File - public abstract Task CreateFile(FilePath Path); + /// Resolver Type + public static void RemoveResolver(System.Type Resolver) + { + var resolver = PathResolvers.FirstOrDefault(item => item.GetType().Equals(Resolver)); + if (resolver != null) PathResolvers.Remove(resolver); + } /// - /// Gets a Folder from a Raw Path, this path must observe the Operating System's Path Structure to work correctly. + /// Gets the closest OS Equivalent Folder from the provided PathRoot. /// - /// OS Style Path - /// Specified Folder - public abstract Task GetFolder(string Path); + /// Requested Root Folder + /// Root Folder + public abstract FolderContainer GetBaseFolder(PathRoot Root); /// - /// Gets a Folder from the FileSystem, the Path is Resolved and is OS Independent. + /// Attempts to get a File/Folder from a Raw Path, this path will be evaulated by any Available . + /// To add a Path Resolver, use or . /// - /// Folder Path - /// Specified Folder - public abstract Task GetFolder(FolderPath Path); + /// Path to File System Item + /// Found Item + public async Task GetFileSystemItem(string Path) + { + foreach (var resolver in PathResolvers) + { + var result = await resolver.TryResolve(Path); + if (result != null) return result; + } + return null; + } /// - /// Gets the closest OS Equivalent Folder from the provided PathRoot. + /// Attempts to get a Folder from a Raw Path, this path will be evaulated by any Available . + /// To add a Path Resolver, use or . /// - /// Requested Root Folder - /// Root Folder - public abstract FolderContainer GetBaseFolder(PathRoot Root); + /// Path to Folder + /// Found Folder + public async Task GetFolder(string Path) + { + return (await GetFileSystemItem(Path)) as FolderContainer; + } /// - /// Opens a File Picker to Pick Files from the Operating System. See to ensure that this is Supported. + /// Gets a Folder from the FileSystem, the Path is Resolved and is OS Independent. /// - /// A List of User Picked Files, or if Cancelled - public Task> PickFiles() + /// Folder Path + /// Specified Folder + public virtual async Task GetFolder(FolderPath Path) { - return PickFiles(null); + var folder = GetBaseFolder(Path.Root); + foreach (var piece in PlatformBindingHelpers.GetPathPieces(Path.Path)) + { + folder = await folder.GetFolderAsync(piece); + } + return folder; } /// - /// Opens a File Picker to Pick Files from the Operating System. See to ensure that this is Supported. + /// Gets a File from a Raw Path, this path must observe the Operating System's Path Structure to work correctly. /// - /// Filters to narrow down the Observable Files. - /// A List of User Picked Files, or if Cancelled - public abstract Task> PickFiles(FilePickerProperties Properties); + /// OS Style Path + /// Specified File + public async Task GetFile(string Path) + { + return (await GetFileSystemItem(Path)) as FileContainer; + } /// - /// Opens a File Picker to Pick a File from the Operating System. See to ensure that this is Supported. + /// Gets a File from the FileSystem, the Path is Resolved and is OS Independent. /// - /// A Picked File, or if Cancelled - public Task PickFile() + /// File Path + /// Specified File + public virtual async Task GetFile(FilePath Path) { - return PickFile(null); + var folder = await GetFolder(Path); + return await folder.GetFileAsync(Path.FileName); } /// - /// Opens a File Picker to Pick a File from the Operating System. See to ensure that this is Supported. + /// Creates a File from the Resolved Path. /// - /// Filters to narrow down the Observable Files. - /// A Picked File, or if Cancelled - public abstract Task PickFile(FilePickerProperties Properties); + /// File Path + /// Created File + public async Task CreateFile(string Path) + { + return await CreateFile(Path, DefaultFileCreationCollision); + } /// - /// Opens a Folder Picker to Pick a Folder from the Operating System. See to ensure that this is Supported. + /// Creates a File from the Resolved Path. /// - /// A Picked Folder, or if Cancelled - public Task PickFolder() + /// File Path + /// Created File + public async Task CreateFile(FilePath Path) { - return PickFolder(null); + return await CreateFile(Path, DefaultFileCreationCollision); } /// - /// Opens a Folder Picker to Pick a Folder from the Operating System. See to ensure that this is Supported. + /// Creates a File from the Resolved Path. Will use if File already exists. /// - /// Filters to narrow down the Observable Files. - /// A Picked Folder, or if Cancelled - public abstract Task PickFolder(FolderPickerProperties Properties); + /// File Path + /// Created File + public virtual async Task CreateFile(string Path, CreationCollisionOption Option) + { + var path = System.IO.Path.GetDirectoryName(Path); + var folder = await GetFolder(path); + return await folder.CreateFileAsync(path, Option); + } /// - /// Opens a Folder for Viewing using the File Manager for the Operating System. See to ensure that this is Supported. + /// Creates a File from the Resolved Path. + /// + /// File Path + /// Created File + public virtual async Task CreateFile(FilePath Path, CreationCollisionOption Option) + { + var folder = await GetFolder(Path); + return await folder.CreateFileAsync(Path.FileName, Option); + } + + /// + /// Opens a Folder for Viewing using the File Manager for the Operating System. See to ensure that this is Supported. /// /// Folder to Open - /// - public Task OpenFolder(FolderContainer Folder) + /// + public Task OpenFolderForDisplay(FolderContainer Folder) { - return OpenFolder(Folder); + return OpenFolderForDisplay(Folder, null); } /// - /// Opens a Folder for Viewing using the File Manager for the Operating System. See to ensure that this is Supported. + /// Opens a Folder for Viewing using the File Manager for the Operating System. See to ensure that this is Supported. /// /// Folder to Open /// Options for modifying how the Folder is Displayed, such as Pre-Selecting Files/Folders if supported - /// - public abstract Task OpenFolder(FolderContainer Folder, FolderOpenOptions Options); + /// + public abstract Task OpenFolderForDisplay(FolderContainer Folder, FolderOpenOptions Options); /// - /// Opens a File for Viewing in the Default Application for the File in the Operating System. See to ensure that this is Supported. + /// Opens a File for Viewing in the Default Application for the File in the Operating System. See to ensure that this is Supported. /// /// File to Open /// - /// - public abstract Task OpenFile(FileContainer File); + /// + public abstract Task OpenFileForDisplay(FileContainer File); /// - /// Gets the Roaming Settings Cluster. See to ensure that this is Supported, or use to Attempt getting Roaming Settings before defaulting to Local Settings. + /// Gets the Roaming Settings Cluster. + /// See to check if this is Supported, returns the Local Settings container if not Supported. /// - /// The Roaming Settings Cluster if Supported. - public abstract ISettingsContainer GetRoamingSettingsContainer(); + /// The Roaming Settings Cluster if Supported, otherwise the Local Settings Cluster. + public virtual ISettingsContainer RoamingSettings => LocalSettings; /// /// Gets the Local Settings Cluster. /// /// The Local Settings Cluster. - public abstract ISettingsContainer GetLocalSettingsContainer(); + public abstract ISettingsContainer LocalSettings { get; } /// - /// Creates a Future Access Token for a Specified Folder. to ensure that this is Required. - /// This is Required on UWP to access User Files and Folders in future Sessions of the App, if the App is closed. You can store this Token in Local Settings to Access this file/folder Again. + /// Gets the Future Access Manager for accessing Files and Folders across App sessions. + /// Check to see if this is Required by the Platform. /// - /// File/Folder to get Access Token for. - /// Future Access Token. - public abstract string GetFutureAccessToken(FileSystemContainer Item); + /// + public abstract IFutureAccessManager FutureAccess { get; } /// - /// Removes a file/folder from the Future Access List, using the Future Access Token. + /// Provides ways for Users to Pick Files and Folders, or Open Files and Folders. /// - /// Token of File/Folder to Remove - public abstract void RemoveFutureAccessToken(string Token); + /// + public abstract FileSystemPickers Pickers { get; } /// - /// Used to Determine if the OS Requires Future Access Tokens to access Files/Folders in Future Sessions. + /// Used to Determine if the OS Requires Future Access Permissions to access Files/Folders in Future Sessions. + /// Represents availaibility of . /// - public abstract bool RequiresFutureAccessToken { get; } + public virtual bool RequiresFutureAccessPermission => FutureAccess != null; /// - /// Used to Determine if the OS supports Roaming AppData accross Devices. + /// Used to Determine if the OS Supports creating File/Folder Pickers. + /// Represents availaibility of . + /// + public virtual bool SupportsPickers => Pickers != null; + + /// + /// Used to Determine if the OS Supports Displaying Folders using the OS File Manager. + /// + public abstract bool SupportsOpenFolderForDisplay { get; } + + /// + /// Used to Determine if the OS Supports Opening Files with the Default Application for a File. /// - public abstract bool SupportsRoaming { get; } + public abstract bool SupportsOpenFileForDisplay { get; } /// - /// Used to Determine if the OS Supports Picking Files using a File Picker. + /// Used to Determine if the OS supports Roaming AppData accross Devices. /// - public abstract bool SupportsPickFile { get; } + public virtual bool SupportsRoaming => RoamingSettings != LocalSettings; /// - /// Used to Determine if the OS Supports Picking Folders using a Folder Picker. + /// Customises the default CreationCollisionOption for use in Folder Creation Methods. + /// Defaults to . /// - public abstract bool SupportsPickFolder { get; } + public static CreationCollisionOption DefaultFolderCreationCollision { get; set; } = CreationCollisionOption.FailIfExists; /// - /// Used to Determine if the OS Supports Displaying Folders using the OS File Manager. + /// Customises the default CreationCollisionOption for use in File Creation Methods. + /// Defaults to . /// - public abstract bool SupportsOpenFolder { get; } + public static CreationCollisionOption DefaultFileCreationCollision { get; set; } = CreationCollisionOption.FailIfExists; /// - /// Used to Determine if the OS Supports Opening Files with the Default Application for a File. + /// Stores Resolvers for fetching Files/Folders from string Paths. /// - public abstract bool SupportsOpenFile { get; } + private static List PathResolvers = new List(); } } \ No newline at end of file diff --git a/Core/Services/UIBindings.cs b/Core/Services/UIBindings.cs index b406dc8..3b3e1dd 100644 --- a/Core/Services/UIBindings.cs +++ b/Core/Services/UIBindings.cs @@ -11,6 +11,11 @@ namespace PlatformBindings.Services /// public abstract class UIBindings { + public UIBindings(Platform Platform) + { + UIPlatform = Platform; + } + /// /// The Interaction Manager for the Current Session, this is used to react to User Input, such as Keyboard combinations, Controller Input, etc. /// @@ -85,6 +90,30 @@ public async void PromptUser(string Title, string Message, string PrimaryButtonT await PromptUserAsync(Title, Message, PrimaryButtonText, null, null); } + /// + /// Requests text from the User using a modal dialog. + /// + /// Title for the Dialog + /// Header/Placeholder for the Text Request + /// Text of Button that Validates the Result + /// Text of Button that Cancels the Request + /// Requested Text + public Task RequestTextFromUserAsync(string Title, string Message, string OKButtonText, string CancelButtonText) + { + return RequestTextFromUserAsync(Title, Message, OKButtonText, CancelButtonText, null); + } + + /// + /// Requests text from the User using a modal dialog. + /// + /// Title for the Dialog + /// Header/Placeholder for the Text Request + /// Text of Button that Validates the Result + /// Text of Button that Cancels the Request + /// Additional UI Context for Handling the Dialog + /// Requested Text + public abstract Task RequestTextFromUserAsync(string Title, string Message, string OKButtonText, string CancelButtonText, IUIBindingInfo UIBinding); + /// /// Sets the Name of the Current Window, if this is Unsupported, do nothing. /// @@ -103,5 +132,10 @@ public async void PromptUser(string Title, string Message, string PrimaryButtonT /// /// Uri to Open public abstract void OpenLink(Uri Uri); + + /// + /// The Current UI Platform the Framework is running on. + /// + public Platform UIPlatform { get; } } } \ No newline at end of file diff --git a/NETCore/Models/Settings/CoreSettingsContainer.cs b/NETCore/Models/Settings/CoreSettingsContainer.cs index 0a7493d..3df935e 100644 --- a/NETCore/Models/Settings/CoreSettingsContainer.cs +++ b/NETCore/Models/Settings/CoreSettingsContainer.cs @@ -38,7 +38,7 @@ public bool ContainsKey(string Key) return file != null; } - public ISettingsContainer CreateContainer(string ContainerName) + public ISettingsContainer GetContainer(string ContainerName) { return new CoreSettingsContainer(ContainerName, this); } diff --git a/NETCore/NETCoreServices.cs b/NETCore/NETCoreServices.cs index 768c951..23b0954 100644 --- a/NETCore/NETCoreServices.cs +++ b/NETCore/NETCoreServices.cs @@ -5,7 +5,7 @@ namespace PlatformBindings { public class NETCoreServices : AppServices { - public NETCoreServices() : base(true) + public NETCoreServices() : base(true, Enums.Platform.NETCore) { UI = new CoreUIBindings(); IO = new CoreIOBindings(); diff --git a/NETCore/Services/CoreIOBindings.cs b/NETCore/Services/CoreIOBindings.cs index 8a27bb1..a1d68dd 100644 --- a/NETCore/Services/CoreIOBindings.cs +++ b/NETCore/Services/CoreIOBindings.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.IO; using System.Threading.Tasks; using PlatformBindings.Enums; @@ -7,66 +6,28 @@ using PlatformBindings.Models.Settings; using System.Reflection; using PlatformBindings.Common; +using PlatformBindings.Models.FileSystem.Options; namespace PlatformBindings.Services { public class CoreIOBindings : IOBindings { - public override bool RequiresFutureAccessToken => false; - public override bool SupportsRoaming => false; + public override bool SupportsOpenFolderForDisplay => false; + public override bool SupportsOpenFileForDisplay => false; - public override bool SupportsOpenFolder => false; - - public override bool SupportsOpenFile => false; + public override IFutureAccessManager FutureAccess => null; + public override FileSystemPickers Pickers => null; - public override bool SupportsPickFile => false; + public override ISettingsContainer LocalSettings { get; } = new CoreSettingsContainer(GetSettingsCluster(), null); - public override bool SupportsPickFolder => false; - - private CoreFolderContainer GetSettingsCluster() + private static CoreFolderContainer GetSettingsCluster() { - var root = GetBaseFolder(PathRoot.Application); + var root = AppServices.IO.GetBaseFolder(PathRoot.Application); var settings = root.CreateFolderAsync("Settings").Result as CoreFolderContainer; return settings; } - public override ISettingsContainer GetLocalSettingsContainer() - { - var root = GetSettingsCluster(); - return new CoreSettingsContainer(root, null); - } - - public override ISettingsContainer GetRoamingSettingsContainer() - { - return GetLocalSettingsContainer(); - } - - public override Task GetFile(string Path) - { - throw new NotImplementedException(); - } - - public override Task GetFile(FilePath Path) - { - throw new NotImplementedException(); - } - - public override Task CreateFile(FilePath Path) - { - throw new NotImplementedException(); - } - - public override Task GetFolder(string Path) - { - throw new NotImplementedException(); - } - - public override Task GetFolder(FolderPath Path) - { - throw new NotImplementedException(); - } - public override FolderContainer GetBaseFolder(PathRoot Root) { switch (Root) @@ -106,37 +67,12 @@ public override FolderContainer GetBaseFolder(PathRoot Root) } } - public override Task> PickFiles(FilePickerProperties Properties) - { - throw new NotSupportedException(); - } - - public override Task PickFile(FilePickerProperties Properties) - { - throw new NotSupportedException(); - } - - public override Task PickFolder(FolderPickerProperties Properties) + public override Task OpenFileForDisplay(FileContainer File) { throw new NotSupportedException(); } - public override Task OpenFolder(FolderContainer Folder, FolderOpenOptions Options) - { - throw new NotImplementedException(); - } - - public override Task OpenFile(FileContainer File) - { - throw new NotImplementedException(); - } - - public override string GetFutureAccessToken(FileSystemContainer Item) - { - throw new NotImplementedException(); - } - - public override void RemoveFutureAccessToken(string Token) + public override Task OpenFolderForDisplay(FolderContainer Folder, FolderOpenOptions Options) { throw new NotImplementedException(); } diff --git a/SMB/Common/SMBExtensions.cs b/SMB/Common/SMBExtensions.cs index dc9933e..0eeea5e 100644 --- a/SMB/Common/SMBExtensions.cs +++ b/SMB/Common/SMBExtensions.cs @@ -39,7 +39,7 @@ public static FileContainer GetSMBFile(this IOBindings IOBinding, string URL, NT return new SMBFileContainer(file); } - private static string EnsureSafe(string URL) + internal static string EnsureSafe(string URL) { var result = URL; if (!URL.StartsWith("smb://")) diff --git a/SMB/Models/FileSystem/SMBFileContainer.cs b/SMB/Models/FileSystem/SMBFileContainer.cs index 5ef4caa..e04384d 100644 --- a/SMB/Models/FileSystem/SMBFileContainer.cs +++ b/SMB/Models/FileSystem/SMBFileContainer.cs @@ -9,7 +9,6 @@ public class SMBFileContainer : FileContainer { public SMBFileContainer(SmbFile File) { - SMBSettings.EnsureSMBReady(); this.File = File; if (!File.IsFile()) { @@ -21,7 +20,7 @@ public SMBFileContainer(SmbFile File) public void Refresh() { _Name = File.GetName(); - _Path = File.GetUncPath(); + _Path = File.GetPath(); _CanWrite = File.CanWrite(); } diff --git a/SMB/Models/FileSystem/SMBFolderContainer.cs b/SMB/Models/FileSystem/SMBFolderContainer.cs index ff22b56..b55b4bd 100644 --- a/SMB/Models/FileSystem/SMBFolderContainer.cs +++ b/SMB/Models/FileSystem/SMBFolderContainer.cs @@ -10,9 +10,8 @@ public class SMBFolderContainer : FolderContainer { public SMBFolderContainer(SmbFile Folder) { - SMBSettings.EnsureSMBReady(); this.Folder = Folder; - if (!Folder.Exists()) + if (!Folder.IsDirectory()) { throw new FormatException("SmbFile is not a Directory"); } @@ -23,7 +22,7 @@ public void Refresh() { _Name = Folder.GetName(); _Name = _Name.Remove(_Name.Length - 1); - _Path = Folder.GetUncPath(); + _Path = Folder.GetPath(); _CanWrite = Folder.CanWrite(); } diff --git a/SMB/Models/FileSystem/SMBPathResolver.cs b/SMB/Models/FileSystem/SMBPathResolver.cs new file mode 100644 index 0000000..3501c34 --- /dev/null +++ b/SMB/Models/FileSystem/SMBPathResolver.cs @@ -0,0 +1,26 @@ +using System.Threading.Tasks; +using SharpCifs.Smb; + +namespace PlatformBindings.Models.FileSystem +{ + public class SMBPathResolver : IPathResolver + { + public Task TryResolve(string Path) + { + return Task.Run(() => + { + var lowerpath = Path.ToLower(); + if (!lowerpath.StartsWith("smb://") && !lowerpath.StartsWith("\\\\")) + { + return null; + } + + var url = SMBExtensions.EnsureSafe(Path); + + var item = new SmbFile(url); + if (item.IsDirectory()) return (FileSystemContainer)new SMBFolderContainer(item); + else return new SMBFileContainer(item); + }); + } + } +} \ No newline at end of file diff --git a/SMB/SMBService.cs b/SMB/SMBService.cs new file mode 100644 index 0000000..5e04da8 --- /dev/null +++ b/SMB/SMBService.cs @@ -0,0 +1,18 @@ +using PlatformBindings.Models.FileSystem; +using PlatformBindings.Services; + +namespace PlatformBindings +{ + public static class SMBService + { + public static void Register() + { + var address = AppServices.NetworkUtilities.LocalIPAddress; + if (address != null) + { + SMBSettings.LocalIPAddress = address; + } + IOBindings.AddResolver(new SMBPathResolver()); + } + } +} \ No newline at end of file diff --git a/SMB/SMBSettings.cs b/SMB/SMBSettings.cs index c6d8146..d7098f7 100644 --- a/SMB/SMBSettings.cs +++ b/SMB/SMBSettings.cs @@ -68,15 +68,6 @@ public static int LocalPort set { SharpCifs.Config.SetProperty(PropLocalPort, value.ToString()); } } - internal static void EnsureSMBReady() - { - var address = AppServices.NetworkUtilities.LocalIPAddress; - if (address != null) - { - LocalIPAddress = address; - } - } - private static readonly string PropDFSDisabled = "jcifs.smb.client.dfs.disabled"; private static readonly string PropGlobalUsername = "jcifs.smb.client.username"; private static readonly string PropGlobalPassword = "jcifs.smb.client.password"; diff --git a/TestApps/Android/Test-Android/MainActivity.cs b/TestApps/Android/Test-Android/MainActivity.cs index 33dd810..9deb0b4 100644 --- a/TestApps/Android/Test-Android/MainActivity.cs +++ b/TestApps/Android/Test-Android/MainActivity.cs @@ -2,7 +2,6 @@ using Android.OS; using Test_Android.Views; using PlatformBindings; -using PlatformBindings.Models.Settings; using PlatformBindings.Activities; namespace Test_Android @@ -10,20 +9,21 @@ namespace Test_Android [Activity(Label = "Test_Android", MainLauncher = true, Icon = "@drawable/icon")] public class MainActivity : PlatformBindingActivity { - public static AndroidAppServices Services = new AndroidAppServices(true); + public static AndroidAppServices Services { get; private set; } + + public MainActivity() + { + Services = new AndroidAppServices(true); + } protected override void OnCreate(Bundle bundle) { // Build App Services before calling base, to allow binding. base.OnCreate(bundle); + Tests.Preparation.Prepare(); StartActivity(typeof(BindingTests)); - - TimesRan.Value++; - Finish(); } - - public AppSetting TimesRan = new AppSetting(); } } \ No newline at end of file diff --git a/TestApps/Android/Test-Android/Resources/Resource.Designer.cs b/TestApps/Android/Test-Android/Resources/Resource.Designer.cs index 291d3d4..dd6cf6a 100644 --- a/TestApps/Android/Test-Android/Resources/Resource.Designer.cs +++ b/TestApps/Android/Test-Android/Resources/Resource.Designer.cs @@ -1810,38 +1810,20 @@ public partial class Id // aapt resource value: 0x7f0a0024 public const int CTRL = 2131361828; - // aapt resource value: 0x7f0a006a - public const int ContextMenuButton = 2131361898; + // aapt resource value: 0x7f0a006c + public const int ContextMenuButton = 2131361900; // aapt resource value: 0x7f0a0025 public const int FUNCTION = 2131361829; - // aapt resource value: 0x7f0a006e - public const int LoopTest_Elapsed = 2131361902; - - // aapt resource value: 0x7f0a006c - public const int LoopTest_Start = 2131361900; - - // aapt resource value: 0x7f0a006d - public const int LoopTest_Stop = 2131361901; - // aapt resource value: 0x7f0a0068 public const int LoopTests = 2131361896; // aapt resource value: 0x7f0a0026 public const int META = 2131361830; - // aapt resource value: 0x7f0a0082 - public const int PickFileButton = 2131361922; - - // aapt resource value: 0x7f0a0083 - public const int PickFilesButton = 2131361923; - - // aapt resource value: 0x7f0a0084 - public const int PickFolderButton = 2131361924; - - // aapt resource value: 0x7f0a006b - public const int RegisteredContextMenu = 2131361899; + // aapt resource value: 0x7f0a006d + public const int RegisteredContextMenu = 2131361901; // aapt resource value: 0x7f0a0027 public const int SHIFT = 2131361831; @@ -1858,11 +1840,17 @@ public partial class Id // aapt resource value: 0x7f0a0069 public const int Test_AsyncAct = 2131361897; + // aapt resource value: 0x7f0a006b + public const int Test_CredMan = 2131361899; + + // aapt resource value: 0x7f0a006a + public const int Test_Settings = 2131361898; + // aapt resource value: 0x7f0a0066 public const int Tests_FileTests = 2131361894; - // aapt resource value: 0x7f0a0072 - public const int action0 = 2131361906; + // aapt resource value: 0x7f0a0071 + public const int action0 = 2131361905; // aapt resource value: 0x7f0a0056 public const int action_bar = 2131361878; @@ -1885,17 +1873,17 @@ public partial class Id // aapt resource value: 0x7f0a0034 public const int action_bar_title = 2131361844; - // aapt resource value: 0x7f0a006f - public const int action_container = 2131361903; + // aapt resource value: 0x7f0a006e + public const int action_container = 2131361902; // aapt resource value: 0x7f0a0057 public const int action_context_bar = 2131361879; - // aapt resource value: 0x7f0a0076 - public const int action_divider = 2131361910; + // aapt resource value: 0x7f0a0075 + public const int action_divider = 2131361909; - // aapt resource value: 0x7f0a0070 - public const int action_image = 2131361904; + // aapt resource value: 0x7f0a006f + public const int action_image = 2131361903; // aapt resource value: 0x7f0a0002 public const int action_menu_divider = 2131361794; @@ -1912,11 +1900,11 @@ public partial class Id // aapt resource value: 0x7f0a0036 public const int action_mode_close_button = 2131361846; - // aapt resource value: 0x7f0a0071 - public const int action_text = 2131361905; + // aapt resource value: 0x7f0a0070 + public const int action_text = 2131361904; - // aapt resource value: 0x7f0a007f - public const int actions = 2131361919; + // aapt resource value: 0x7f0a007e + public const int actions = 2131361918; // aapt resource value: 0x7f0a0037 public const int activity_chooser_view_content = 2131361847; @@ -1945,14 +1933,14 @@ public partial class Id // aapt resource value: 0x7f0a003d public const int buttonPanel = 2131361853; - // aapt resource value: 0x7f0a0073 - public const int cancel_action = 2131361907; + // aapt resource value: 0x7f0a0072 + public const int cancel_action = 2131361906; // aapt resource value: 0x7f0a004d public const int checkbox = 2131361869; - // aapt resource value: 0x7f0a007b - public const int chronometer = 2131361915; + // aapt resource value: 0x7f0a007a + public const int chronometer = 2131361914; // aapt resource value: 0x7f0a002a public const int collapseActionView = 2131361834; @@ -1981,8 +1969,8 @@ public partial class Id // aapt resource value: 0x7f0a0021 public const int end = 2131361825; - // aapt resource value: 0x7f0a0081 - public const int end_padder = 2131361921; + // aapt resource value: 0x7f0a0080 + public const int end_padder = 2131361920; // aapt resource value: 0x7f0a0038 public const int expand_activities_button = 2131361848; @@ -2002,8 +1990,8 @@ public partial class Id // aapt resource value: 0x7f0a003c public const int icon = 2131361852; - // aapt resource value: 0x7f0a0080 - public const int icon_group = 2131361920; + // aapt resource value: 0x7f0a007f + public const int icon_group = 2131361919; // aapt resource value: 0x7f0a002b public const int ifRoom = 2131361835; @@ -2011,8 +1999,8 @@ public partial class Id // aapt resource value: 0x7f0a0039 public const int image = 2131361849; - // aapt resource value: 0x7f0a007c - public const int info = 2131361916; + // aapt resource value: 0x7f0a007b + public const int info = 2131361915; // aapt resource value: 0x7f0a0033 public const int italic = 2131361843; @@ -2029,11 +2017,11 @@ public partial class Id // aapt resource value: 0x7f0a003b public const int list_item = 2131361851; - // aapt resource value: 0x7f0a0075 - public const int media_actions = 2131361909; + // aapt resource value: 0x7f0a0074 + public const int media_actions = 2131361908; - // aapt resource value: 0x7f0a0085 - public const int message = 2131361925; + // aapt resource value: 0x7f0a0081 + public const int message = 2131361921; // aapt resource value: 0x7f0a0022 public const int middle = 2131361826; @@ -2050,14 +2038,14 @@ public partial class Id // aapt resource value: 0x7f0a000f public const int normal = 2131361807; - // aapt resource value: 0x7f0a007e - public const int notification_background = 2131361918; - - // aapt resource value: 0x7f0a0078 - public const int notification_main_column = 2131361912; + // aapt resource value: 0x7f0a007d + public const int notification_background = 2131361917; // aapt resource value: 0x7f0a0077 - public const int notification_main_column_container = 2131361911; + public const int notification_main_column = 2131361911; + + // aapt resource value: 0x7f0a0076 + public const int notification_main_column_container = 2131361910; // aapt resource value: 0x7f0a003f public const int parentPanel = 2131361855; @@ -2071,11 +2059,11 @@ public partial class Id // aapt resource value: 0x7f0a004f public const int radio = 2131361871; - // aapt resource value: 0x7f0a007d - public const int right_icon = 2131361917; + // aapt resource value: 0x7f0a007c + public const int right_icon = 2131361916; - // aapt resource value: 0x7f0a0079 - public const int right_side = 2131361913; + // aapt resource value: 0x7f0a0078 + public const int right_side = 2131361912; // aapt resource value: 0x7f0a0019 public const int screen = 2131361817; @@ -2149,8 +2137,8 @@ public partial class Id // aapt resource value: 0x7f0a001c public const int src_over = 2131361820; - // aapt resource value: 0x7f0a0074 - public const int status_bar_latest_event_content = 2131361908; + // aapt resource value: 0x7f0a0073 + public const int status_bar_latest_event_content = 2131361907; // aapt resource value: 0x7f0a0050 public const int submenuarrow = 2131361872; @@ -2173,8 +2161,8 @@ public partial class Id // aapt resource value: 0x7f0a0043 public const int textSpacerNoTitle = 2131361859; - // aapt resource value: 0x7f0a007a - public const int time = 2131361914; + // aapt resource value: 0x7f0a0079 + public const int time = 2131361913; // aapt resource value: 0x7f0a000d public const int title = 2131361805; @@ -2335,70 +2323,64 @@ public partial class Layout public const int ContextMenu = 2130903068; // aapt resource value: 0x7f03001d - public const int LoopTests = 2130903069; + public const int notification_action = 2130903069; // aapt resource value: 0x7f03001e - public const int notification_action = 2130903070; + public const int notification_action_tombstone = 2130903070; // aapt resource value: 0x7f03001f - public const int notification_action_tombstone = 2130903071; + public const int notification_media_action = 2130903071; // aapt resource value: 0x7f030020 - public const int notification_media_action = 2130903072; + public const int notification_media_cancel_action = 2130903072; // aapt resource value: 0x7f030021 - public const int notification_media_cancel_action = 2130903073; + public const int notification_template_big_media = 2130903073; // aapt resource value: 0x7f030022 - public const int notification_template_big_media = 2130903074; + public const int notification_template_big_media_custom = 2130903074; // aapt resource value: 0x7f030023 - public const int notification_template_big_media_custom = 2130903075; + public const int notification_template_big_media_narrow = 2130903075; // aapt resource value: 0x7f030024 - public const int notification_template_big_media_narrow = 2130903076; + public const int notification_template_big_media_narrow_custom = 2130903076; // aapt resource value: 0x7f030025 - public const int notification_template_big_media_narrow_custom = 2130903077; + public const int notification_template_custom_big = 2130903077; // aapt resource value: 0x7f030026 - public const int notification_template_custom_big = 2130903078; + public const int notification_template_icon_group = 2130903078; // aapt resource value: 0x7f030027 - public const int notification_template_icon_group = 2130903079; + public const int notification_template_lines_media = 2130903079; // aapt resource value: 0x7f030028 - public const int notification_template_lines_media = 2130903080; + public const int notification_template_media = 2130903080; // aapt resource value: 0x7f030029 - public const int notification_template_media = 2130903081; + public const int notification_template_media_custom = 2130903081; // aapt resource value: 0x7f03002a - public const int notification_template_media_custom = 2130903082; + public const int notification_template_part_chronometer = 2130903082; // aapt resource value: 0x7f03002b - public const int notification_template_part_chronometer = 2130903083; + public const int notification_template_part_time = 2130903083; // aapt resource value: 0x7f03002c - public const int notification_template_part_time = 2130903084; + public const int select_dialog_item_material = 2130903084; // aapt resource value: 0x7f03002d - public const int Pickers = 2130903085; + public const int select_dialog_multichoice_material = 2130903085; // aapt resource value: 0x7f03002e - public const int select_dialog_item_material = 2130903086; + public const int select_dialog_singlechoice_material = 2130903086; // aapt resource value: 0x7f03002f - public const int select_dialog_multichoice_material = 2130903087; + public const int support_simple_spinner_dropdown_item = 2130903087; // aapt resource value: 0x7f030030 - public const int select_dialog_singlechoice_material = 2130903088; - - // aapt resource value: 0x7f030031 - public const int support_simple_spinner_dropdown_item = 2130903089; - - // aapt resource value: 0x7f030032 - public const int tooltip = 2130903090; + public const int tooltip = 2130903088; static Layout() { diff --git a/TestApps/Android/Test-Android/Resources/layout/BindingTests.axml b/TestApps/Android/Test-Android/Resources/layout/BindingTests.axml index 15a9dbd..3fd4940 100644 --- a/TestApps/Android/Test-Android/Resources/layout/BindingTests.axml +++ b/TestApps/Android/Test-Android/Resources/layout/BindingTests.axml @@ -5,29 +5,39 @@ android:layout_height="match_parent" android:minWidth="25px" android:minHeight="25px"> -